diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..5322788644 --- /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/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..540e14934e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,23 @@ +- [ ] I am running an official build of TWRP, downloaded from https://twrp.me/Devices/ +- [ ] I am running the latest version of TWRP +- [ ] I have read the FAQ (https://twrp.me/FAQ/) +- [ ] I have searched for my issue and it does not already exist + +**Device codename**: +**TWRP version**: + +#### WHAT STEPS WILL REPRODUCE THE PROBLEM? + + +#### WHAT IS THE EXPECTED RESULT? + + +#### WHAT HAPPENS INSTEAD? + + +#### ADDITIONAL INFORMATION + + + +`/tmp/recovery.log`: +`dmesg`: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..d04c212b6c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +# WE DO NOT MERGE PULL REQUESTS SUBMITTED HERE + +You will need to submit it through [OmniRom Gerrit](https://gerrit.omnirom.org/#/admin/projects/android_bootable_recovery/) + +For changes to device trees, use [TWRP Gerrit](https://gerrit.twrp.me/) + +This guide explani how to use [Gerrit code review](https://forum.xda-developers.com/general/xda-university/guide-using-gerrit-code-review-t3720802) \ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000000..07fc27d951 --- /dev/null +++ b/Android.bp @@ -0,0 +1,4 @@ +subdirs = [ +// "bootloader_message", +// "otautil", +] diff --git a/Android.mk b/Android.mk index 85cdd325bd..6880ed2c26 100644 --- a/Android.mk +++ b/Android.mk @@ -61,7 +61,8 @@ LOCAL_SRC_FILES := \ twrp-functions.cpp \ twrpDigestDriver.cpp \ openrecoveryscript.cpp \ - tarWrite.c + tarWrite.c \ + twrpAdbBuFifo.cpp ifneq ($(TARGET_RECOVERY_REBOOT_SRC),) LOCAL_SRC_FILES += $(TARGET_RECOVERY_REBOOT_SRC) @@ -104,24 +105,25 @@ LOCAL_CLANG := true LOCAL_C_INCLUDES += \ system/vold \ - system/extras/ext4_utils \ + system/extras \ system/core/adb \ system/core/libsparse \ - external/zlib + external/zlib \ + $(LOCAL_PATH)/bootloader_message_twrp/include LOCAL_C_INCLUDES += bionic ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) LOCAL_C_INCLUDES += external/stlport/stlport external/openssl/include else - LOCAL_C_INCLUDES += external/boringssl/include + LOCAL_C_INCLUDES += external/boringssl/include external/libcxx/include endif LOCAL_STATIC_LIBRARIES := LOCAL_SHARED_LIBRARIES := LOCAL_STATIC_LIBRARIES += libguitwrp -LOCAL_SHARED_LIBRARIES += libaosprecovery libz libc libcutils libstdc++ libtar libblkid libminuitwrp libminadbd libmtdutils libminzip libtwadbbu libbootloader_message -LOCAL_SHARED_LIBRARIES += libcrecovery libtwadbbu libtwrpdigest +LOCAL_SHARED_LIBRARIES += libaosprecovery libz libc libcutils libstdc++ libtar libblkid libminuitwrp libminadbd libmtdutils libtwadbbu libbootloader_message_twrp +LOCAL_SHARED_LIBRARIES += libcrecovery libtwadbbu libtwrpdigest libc++ ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) LOCAL_SHARED_LIBRARIES += libstlport @@ -132,13 +134,20 @@ ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0) LOCAL_C_INCLUDES += $(LOCAL_PATH)/libmincrypt/includes LOCAL_CFLAGS += -DUSE_OLD_VERIFIER else - LOCAL_SHARED_LIBRARIES += libc++ libcrypto + LOCAL_SHARED_LIBRARIES += libcrypto endif ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?),0) LOCAL_SHARED_LIBRARIES += libbase endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0) + LOCAL_SHARED_LIBRARIES += libziparchive +else + LOCAL_SHARED_LIBRARIES += libminzip + LOCAL_CFLAGS += -DUSE_MINZIP +endif + ifneq ($(wildcard system/core/libsparse/Android.mk),) LOCAL_SHARED_LIBRARIES += libsparse endif @@ -147,7 +156,6 @@ ifeq ($(TW_OEM_BUILD),true) LOCAL_CFLAGS += -DTW_OEM_BUILD BOARD_HAS_NO_REAL_SDCARD := true TW_USE_TOOLBOX := true - TW_EXCLUDE_SUPERSU := true TW_EXCLUDE_MTP := true endif @@ -161,14 +169,14 @@ ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) endif LOCAL_C_INCLUDES += external/libselinux/include LOCAL_SHARED_LIBRARIES += libselinux -ifneq ($(TARGET_USERIMAGES_USE_EXT4), true) - LOCAL_CFLAGS += -DUSE_EXT4 - LOCAL_C_INCLUDES += system/extras/ext4_utils - LOCAL_SHARED_LIBRARIES += libext4_utils - ifneq ($(wildcard external/lz4/Android.mk),) - LOCAL_STATIC_LIBRARIES += liblz4 - endif -endif +#ifneq ($(TARGET_USERIMAGES_USE_EXT4), true) +# LOCAL_CFLAGS += -DUSE_EXT4 +# LOCAL_C_INCLUDES += system/extras/ext4_utils +# LOCAL_SHARED_LIBRARIES += libext4_utils +# ifneq ($(wildcard external/lz4/Android.mk),) +# LOCAL_STATIC_LIBRARIES += liblz4 +# endif +#endif ifeq ($(AB_OTA_UPDATER),true) LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 @@ -221,9 +229,6 @@ endif ifneq ($(TW_EXTERNAL_STORAGE_MOUNT_POINT),) LOCAL_CFLAGS += -DTW_EXTERNAL_STORAGE_MOUNT_POINT=$(TW_EXTERNAL_STORAGE_MOUNT_POINT) endif -ifeq ($(TW_HAS_NO_RECOVERY_PARTITION), true) - LOCAL_CFLAGS += -DTW_HAS_NO_RECOVERY_PARTITION -endif ifeq ($(TW_HAS_NO_BOOT_PARTITION), true) LOCAL_CFLAGS += -DTW_HAS_NO_BOOT_PARTITION endif @@ -263,9 +268,6 @@ endif ifneq ($(BOARD_UMS_LUNFILE),) LOCAL_CFLAGS += -DCUSTOM_LUN_FILE=\"$(BOARD_UMS_LUNFILE)\" endif -#ifeq ($(TW_FLASH_FROM_STORAGE), true) Making this the default behavior - LOCAL_CFLAGS += -DTW_FLASH_FROM_STORAGE -#endif ifeq ($(TW_HAS_DOWNLOAD_MODE), true) LOCAL_CFLAGS += -DTW_HAS_DOWNLOAD_MODE endif @@ -304,7 +306,7 @@ ifeq ($(TW_INCLUDE_CRYPTO), true) endif endif WITH_CRYPTO_UTILS := \ - $(if $(wildcard system/core/libcrypto_utils/Android.mk),true) + $(if $(wildcard system/core/libcrypto_utils/android_pubkey.c),true) ifeq ($(TW_USE_MODEL_HARDWARE_ID_FOR_DEVICE_ID), true) LOCAL_CFLAGS += -DTW_USE_MODEL_HARDWARE_ID_FOR_DEVICE_ID endif @@ -332,7 +334,9 @@ else LOCAL_CFLAGS += -DTW_EXCLUDE_ENCRYPTED_BACKUPS endif ifeq ($(TARGET_RECOVERY_QCOM_RTC_FIX),) - ifeq ($(TARGET_CPU_VARIANT),krait) + ifneq ($(filter msm8226 msm8x26 msm8610 msm8974 msm8x74 msm8084 msm8x84 apq8084 msm8909 msm8916 msm8992 msm8994 msm8952 msm8996 msm8937 msm8953 msm8998,$(TARGET_BOARD_PLATFORM)),) + LOCAL_CFLAGS += -DQCOM_RTC_FIX + else ifeq ($(TARGET_CPU_VARIANT),krait) LOCAL_CFLAGS += -DQCOM_RTC_FIX endif else ifeq ($(TARGET_RECOVERY_QCOM_RTC_FIX),true) @@ -355,7 +359,9 @@ ifneq ($(TW_DEFAULT_LANGUAGE),) else LOCAL_CFLAGS += -DTW_DEFAULT_LANGUAGE=en endif - +ifneq ($(TW_CLOCK_OFFSET),) + LOCAL_CFLAGS += -DTW_CLOCK_OFFSET=$(TW_CLOCK_OFFSET) +endif LOCAL_ADDITIONAL_DEPENDENCIES += \ dump_image \ erase_image \ @@ -370,7 +376,8 @@ LOCAL_ADDITIONAL_DEPENDENCIES += \ mkfs.fat \ permissive.sh \ simg2img_twrp \ - libbootloader_message \ + libbootloader_message_twrp \ + init.recovery.hlthchrg.rc \ init.recovery.service.rc ifneq ($(TARGET_ARCH), arm64) @@ -422,18 +429,6 @@ ifeq ($(TW_INCLUDE_DUMLOCK), true) htcdumlock htcdumlocksys flash_imagesys dump_imagesys libbmlutils.so \ libflashutils.so libmmcutils.so libmtdutils.so HTCDumlock.apk endif -ifneq ($(TW_EXCLUDE_SUPERSU), true) - LOCAL_ADDITIONAL_DEPENDENCIES += \ - install-recovery.sh 99SuperSUDaemon Superuser.apk - ifeq ($(TARGET_ARCH), arm) - LOCAL_ADDITIONAL_DEPENDENCIES += \ - chattr.pie libsupol.so suarm supolicy - endif - ifeq ($(TARGET_ARCH), arm64) - LOCAL_ADDITIONAL_DEPENDENCIES += \ - libsupol.soarm64 suarm64 supolicyarm64 - endif -endif ifeq ($(TW_INCLUDE_FB2PNG), true) LOCAL_ADDITIONAL_DEPENDENCIES += fb2png endif @@ -549,7 +544,9 @@ endif # !TW_USE_TOOLBOX # =============================== ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?),0) 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 @@ -561,9 +558,11 @@ endif # =============================== ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?),0) 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) @@ -574,7 +573,7 @@ endif 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 := -Wall -Werror LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE LOCAL_MODULE_TAGS := optional @@ -589,6 +588,60 @@ else endif include $(BUILD_SHARED_LIBRARY) +# static libfusesideload +# =============================== (required to fix build errors in 8.1 due to use by tests) +include $(CLEAR_VARS) +LOCAL_SRC_FILES := fuse_sideload.cpp +LOCAL_CLANG := true +LOCAL_CFLAGS := -Wall -Werror +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := libfusesideload +LOCAL_SHARED_LIBRARIES := libcutils libc +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0) + LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes + LOCAL_STATIC_LIBRARIES += libmincrypttwrp + LOCAL_CFLAGS += -DUSE_MINCRYPT +else + LOCAL_STATIC_LIBRARIES += libcrypto_static +endif +include $(BUILD_STATIC_LIBRARY) + +# libmounts (static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := mounts.cpp +LOCAL_CFLAGS := \ + -Wall \ + -Werror +LOCAL_MODULE := libmounts +LOCAL_STATIC_LIBRARIES := libbase +include $(BUILD_STATIC_LIBRARY) + +# librecovery (static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + install.cpp +LOCAL_CFLAGS := -Wall -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 \ + libziparchive \ + +include $(BUILD_STATIC_LIBRARY) + # shared libaosprecovery for Apache code # =============================== include $(CLEAR_VARS) @@ -596,7 +649,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := libaosprecovery LOCAL_MODULE_TAGS := eng optional LOCAL_CFLAGS := -std=gnu++0x -LOCAL_SRC_FILES := adb_install.cpp asn1_decoder.cpp legacy_property_service.cpp set_metadata.cpp tw_atomic.cpp installcommand.cpp +LOCAL_SRC_FILES := adb_install.cpp legacy_property_service.cpp set_metadata.cpp tw_atomic.cpp installcommand.cpp zipwrap.cpp LOCAL_SHARED_LIBRARIES += libc liblog libcutils libmtdutils libfusesideload libselinux libminzip LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) @@ -608,15 +661,24 @@ endif ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0) LOCAL_SHARED_LIBRARIES += libmincrypttwrp LOCAL_C_INCLUDES += $(LOCAL_PATH)/libmincrypt/includes - LOCAL_SRC_FILES += verifier24/verifier.cpp + LOCAL_SRC_FILES += verifier24/verifier.cpp verifier24/asn1_decoder.cpp LOCAL_CFLAGS += -DUSE_OLD_VERIFIER else LOCAL_SHARED_LIBRARIES += libcrypto libbase - LOCAL_SRC_FILES += verifier.cpp + LOCAL_SRC_FILES += verifier.cpp asn1_decoder.cpp endif + ifeq ($(AB_OTA_UPDATER),true) LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0) + LOCAL_SRC_FILES += otautil/ZipUtil.cpp otautil/SysUtil.cpp + LOCAL_SHARED_LIBRARIES += libziparchive libext4_utils libcrypto libcrypto_utils + LOCAL_STATIC_LIBRARIES += libvintf_recovery libfs_mgr liblogwrap libavb libvintf libtinyxml2 libz + LOCAL_WHOLE_STATIC_LIBRARIES += +else + LOCAL_CFLAGS += -DUSE_MINZIP +endif include $(BUILD_SHARED_LIBRARY) @@ -624,7 +686,6 @@ include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_MODULE := libverifier -LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ asn1_decoder.cpp \ verifier.cpp \ @@ -632,21 +693,50 @@ LOCAL_SRC_FILES := \ LOCAL_STATIC_LIBRARIES := libcrypto_static include $(BUILD_STATIC_LIBRARY) +# Wear default device +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := wear_device.cpp + +# Should match TARGET_RECOVERY_UI_LIB in BoardConfig.mk. +LOCAL_MODULE := librecovery_ui_wear + +include $(BUILD_STATIC_LIBRARY) + +# vr headset default device +# =============================== +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := vr_device.cpp + +# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk +LOCAL_MODULE := librecovery_ui_vr + +include $(BUILD_STATIC_LIBRARY) + commands_recovery_local_path := $(LOCAL_PATH) -include $(LOCAL_PATH)/tests/Android.mk \ - $(LOCAL_PATH)/tools/Android.mk \ + +include \ + $(LOCAL_PATH)/applypatch/Android.mk \ + $(LOCAL_PATH)/boot_control/Android.mk \ $(LOCAL_PATH)/edify/Android.mk \ $(LOCAL_PATH)/otafault/Android.mk \ - $(LOCAL_PATH)/bootloader_message/Android.mk \ + $(LOCAL_PATH)/tests/Android.mk \ + $(LOCAL_PATH)/tools/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ $(LOCAL_PATH)/update_verifier/Android.mk \ - $(LOCAL_PATH)/applypatch/Android.mk + $(LOCAL_PATH)/bootloader_message/Android.mk \ + $(LOCAL_PATH)/bootloader_message_twrp/Android.mk ifeq ($(wildcard system/core/uncrypt/Android.mk),) include $(commands_recovery_local_path)/uncrypt/Android.mk endif ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 22; echo $$?),0) + ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0) + TARGET_GLOBAL_CFLAGS += -DTW_USE_MINUI_WITH_DATA + CLANG_TARGET_GLOBAL_CFLAGS += -DTW_USE_MINUI_WITH_DATA + endif include $(commands_recovery_local_path)/minadbd/Android.mk \ $(commands_recovery_local_path)/minui/Android.mk else @@ -669,6 +759,7 @@ include $(commands_recovery_local_path)/injecttwrp/Android.mk \ $(commands_recovery_local_path)/libcrecovery/Android.mk \ $(commands_recovery_local_path)/libblkid/Android.mk \ $(commands_recovery_local_path)/minuitwrp/Android.mk \ + $(commands_recovery_local_path)/otautil/Android.mk \ $(commands_recovery_local_path)/openaes/Android.mk \ $(commands_recovery_local_path)/toolbox/Android.mk \ $(commands_recovery_local_path)/twrpTarMain/Android.mk \ diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000..09754c6509 --- /dev/null +++ b/OWNERS @@ -0,0 +1,3 @@ +enh+aosp-gerrit@google.com +tbao@google.com +xunchang@google.com diff --git a/README.md b/README.md index 96f3789cd3..820a8b7781 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ **Team Win Recovery Project (TWRP)** You can find a compiling guide [here](http://forum.xda-developers.com/showthread.php?t=1943625 "Guide"). - diff --git a/adb_install.cpp b/adb_install.cpp index 448d61746d..291708c69c 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -14,22 +14,27 @@ * limitations under the License. */ -#include +#include "adb_install.h" + #include #include +#include +#include #include #include +#include #include #include #include #include #include #include +#include #include "ui.h" #include "cutils/properties.h" -#include "adb_install.h" -#include "minadbd/fuse_adb_provider.h" + +#include "common.h" #include "fuse_sideload.h" #ifdef USE_OLD_VERIFIER #include "verifier24/verifier.h" @@ -37,53 +42,92 @@ #include "verifier.h" #endif -static RecoveryUI* ui = NULL; - -void -set_usb_driver(bool enabled) { - int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY); - if (fd < 0) { -/* These error messages show when built in older Android branches (e.g. Gingerbread) - It's not a critical error so we're disabling the error messages. - ui->Print("failed to open driver control: %s\n", strerror(errno)); +static void set_usb_driver(bool enabled) { + char configfs[PROPERTY_VALUE_MAX]; + property_get("sys.usb.configfs", configfs, "false"); + if (strcmp(configfs, "false") == 0 || strcmp(configfs, "0") == 0) + return; + + int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY); + if (fd < 0) { +/* These error messages show when built in older Android branches (e.g. Gingerbread) + It's not a critical error so we're disabling the error messages. + ui->Print("failed to open driver control: %s\n", strerror(errno)); */ - printf("failed to open driver control: %s\n", strerror(errno)); - return; - } + printf("failed to open driver control: %s\n", strerror(errno)); + return; + } - if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) { + if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) { /* - ui->Print("failed to set driver control: %s\n", strerror(errno)); + ui->Print("failed to set driver control: %s\n", strerror(errno)); */ - printf("failed to set driver control: %s\n", strerror(errno)); - } - if (close(fd) < 0) { + printf("failed to set driver control: %s\n", strerror(errno)); + } + if (close(fd) < 0) { /* - ui->Print("failed to close driver control: %s\n", strerror(errno)); + ui->Print("failed to close driver control: %s\n", strerror(errno)); */ - printf("failed to close driver control: %s\n", strerror(errno)); + printf("failed to close driver control: %s\n", strerror(errno)); + } +} + +// On Android 8.0 for some reason init can't seem to completely stop adbd +// so we have to kill it too if it doesn't die on its own. +static void kill_adbd() { + DIR* dir = opendir("/proc"); + if (dir) { + struct dirent* de = 0; + + while ((de = readdir(dir)) != 0) { + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + + int pid = -1; + int ret = sscanf(de->d_name, "%d", &pid); + + if (ret == 1) { + char cmdpath[PATH_MAX]; + sprintf(cmdpath, "/proc/%d/cmdline", pid); + + FILE* file = fopen(cmdpath, "r"); + size_t task_size = PATH_MAX; + char task[PATH_MAX]; + char* p = task; + if (getline(&p, &task_size, file) > 0) { + if (strstr(task, "adbd") != 0) { + printf("adbd pid %d found, sending kill.\n", pid); + kill(pid, SIGINT); + usleep(5000); + kill(pid, SIGKILL); + } + } + fclose(file); + } } + closedir(dir); + } } -static void -stop_adbd() { - property_set("ctl.stop", "adbd"); - set_usb_driver(false); +static void stop_adbd() { + printf("Stopping adbd...\n"); + property_set("ctl.stop", "adbd"); + usleep(5000); + kill_adbd(); + set_usb_driver(false); } -bool is_ro_debuggable() { - char value[PROPERTY_VALUE_MAX+1]; - return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); +static bool is_ro_debuggable() { + char value[PROPERTY_VALUE_MAX+1]; + return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); } -void -maybe_restart_adbd() { - char value[PROPERTY_VALUE_MAX+1]; - if (is_ro_debuggable()) { - printf("Restarting adbd...\n"); - set_usb_driver(true); - property_set("ctl.start", "adbd"); - } +static void maybe_restart_adbd() { + if (is_ro_debuggable()) { + printf("Restarting adbd...\n"); + set_usb_driver(true); + property_set("ctl.start", "adbd"); + } } // How long (in seconds) we wait for the host to start sending us a @@ -93,77 +137,83 @@ maybe_restart_adbd() { int apply_from_adb(const char* install_file, pid_t* child_pid) { - stop_adbd(); - set_usb_driver(true); + stop_adbd(); + set_usb_driver(true); /* - ui->Print("\n\nNow send the package you want to apply\n" - "to the device with \"adb sideload \"...\n"); -*/ - pid_t child; - if ((child = fork()) == 0) { - execl("/sbin/recovery", "recovery", "--adbd", install_file, NULL); - _exit(-1); - } +int apply_from_adb(RecoveryUI* ui, bool* wipe_cache, const char* install_file) { + modified_flash = true; - *child_pid = child; - // caller can now kill the child thread from another thread - - // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host - // connects and starts serving a package. Poll for its - // appearance. (Note that inotify doesn't work with FUSE.) - int result = INSTALL_ERROR; - int status; - bool waited = false; - struct stat st; - for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { - if (waitpid(child, &status, WNOHANG) != 0) { - result = -1; - waited = true; - break; - } + stop_adbd(ui); + set_usb_driver(ui, true); - if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { - if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) { - sleep(1); - continue; - } else { - printf("\nTimed out waiting for package: %s\n\n", strerror(errno)); - result = -1; - kill(child, SIGKILL); - break; - } - } - // Install is handled elsewhere in TWRP - //install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false); - return 0; + ui->Print("\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload \"...\n"); +*/ + pid_t child; + if ((child = fork()) == 0) { + execl("/sbin/recovery", "recovery", "--adbd", install_file, NULL); + _exit(-1); + } + + *child_pid = child; + // caller can now kill the child thread from another thread + + // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host + // connects and starts serving a package. Poll for its + // appearance. (Note that inotify doesn't work with FUSE.) + int result = INSTALL_ERROR; + int status; + bool waited = false; + struct stat st; + for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { + if (waitpid(child, &status, WNOHANG) != 0) { + result = -1; + waited = true; + break; } - // if we got here, something failed - *child_pid = 0; - - if (!waited) { - // Calling stat() on this magic filename signals the minadbd - // subprocess to shut down. - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); - - // TODO(dougz): there should be a way to cancel waiting for a - // package (by pushing some button combo on the device). For now - // you just have to 'adb sideload' a file that's not a valid - // package, like "/dev/null". - waitpid(child, &status, 0); + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { + if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) { + sleep(1); + continue; + } else { + printf("\nTimed out waiting for package: %s\n\n", strerror(errno)); + result = -1; + kill(child, SIGKILL); + break; + } } - - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - if (WEXITSTATUS(status) == 3) { - printf("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); - result = -2; - } else if (!WIFSIGNALED(status)) { - printf("status %d\n", WEXITSTATUS(status)); - } + // Install is handled elsewhere in TWRP + //install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false); + return 0; + } + + // if we got here, something failed + *child_pid = 0; + + if (!waited) { + // Calling stat() on this magic filename signals the minadbd + // subprocess to shut down. + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + + // TODO(dougz): there should be a way to cancel waiting for a + // package (by pushing some button combo on the device). For now + // you just have to 'adb sideload' a file that's not a valid + // package, like "/dev/null". + waitpid(child, &status, 0); + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (WEXITSTATUS(status) == 3) { + printf("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); + result = -2; + } else if (!WIFSIGNALED(status)) { + printf("adbd status %d\n", WEXITSTATUS(status)); } + } - set_usb_driver(false); - maybe_restart_adbd(); + set_usb_driver(false); + maybe_restart_adbd(); - return result; + return result; } diff --git a/adb_install.h b/adb_install.h index 24e9e21f6e..121ae3cd24 100644 --- a/adb_install.h +++ b/adb_install.h @@ -17,10 +17,12 @@ #ifndef _ADB_INSTALL_H #define _ADB_INSTALL_H +#include + //class RecoveryUI; -void set_usb_driver(bool enabled); -void maybe_restart_adbd(); +static void set_usb_driver(bool enabled); +static void maybe_restart_adbd(); int apply_from_adb(const char* install_file, pid_t* child_pid); #endif diff --git a/adbbu/Android.mk b/adbbu/Android.mk index 33d3ad7d31..8f8dbd0fb1 100644 --- a/adbbu/Android.mk +++ b/adbbu/Android.mk @@ -1,10 +1,35 @@ LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := libtwadbbu +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS = -fno-strict-aliasing -D_LARGFILE_SOURCE #-D_DEBUG_ADB_BACKUP +LOCAL_C_INCLUDES += bionic external/zlib +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) + LOCAL_C_INCLUDES += external/stlport/stlport +endif + +LOCAL_SRC_FILES = \ + libtwadbbu.cpp \ + twrpback.cpp + +LOCAL_SHARED_LIBRARIES += libz libc libstdc++ libtwrpdigest + +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) + LOCAL_SHARED_LIBRARIES += libstlport +else + LOCAL_SHARED_LIBRARIES += libc++ +endif + +include $(BUILD_SHARED_LIBRARY) + include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - twrpback.cpp + adbbumain.cpp + +LOCAL_SHARED_LIBRARIES += libstdc++ libz libtwadbbu -LOCAL_SHARED_LIBRARIES += libstdc++ libz libtwrpdigest ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) LOCAL_C_INCLUDES += external/stlport/stlport LOCAL_SHARED_LIBRARIES += libstlport @@ -21,21 +46,3 @@ LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin include $(BUILD_EXECUTABLE) -include $(CLEAR_VARS) -LOCAL_MODULE := libtwadbbu -LOCAL_MODULE_TAGS := optional -LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -fno-strict-aliasing -LOCAL_C_INCLUDES += bionic external/zlib -ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) - LOCAL_C_INCLUDES += external/stlport/stlport - LOCAL_SHARED_LIBRARIES += libstlport -else - LOCAL_SHARED_LIBRARIES += libc++ -endif - -LOCAL_SRC_FILES = \ - libtwadbbu.cpp - -LOCAL_SHARED_LIBRARIES += libz libc libstdc++ - -include $(BUILD_SHARED_LIBRARY) diff --git a/adbbu/adbbumain.cpp b/adbbu/adbbumain.cpp new file mode 100644 index 0000000000..2e66bceb6e --- /dev/null +++ b/adbbu/adbbumain.cpp @@ -0,0 +1,95 @@ +/* + Copyright 2013 to 2017 TeamWin + TWRP is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + TWRP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TWRP. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "twrpback.hpp" +#include "twadbstream.h" + + +int main(int argc, char **argv) { + int index; + int pos = 0; + bool ret = false; + int maxpos = sizeof(TWRPARG + 2); + std::string command; + twrpback tw; + + tw.adblogwrite("Starting adb backup and restore\n"); + command = argv[1]; + for (index = 2; index < argc; index++) { + command = command + " " + argv[index]; + } + + pos = command.find(TWRP_BACKUP_ARG); + if (pos < 0 || pos > (maxpos + sizeof(TWRP_BACKUP_ARG) + 1)) { + pos = command.find(TWRP_RESTORE_ARG); + } + if (pos < 0 || pos > maxpos + sizeof(TWRP_STREAM_ARG + 1)) { + pos = command.find(TWRP_STREAM_ARG); + } + + tw.adblogwrite("command: " + command + "\n"); + command.erase(0, pos); + command.erase(std::remove(command.begin(), command.end(), '\''), command.end()); + + if (command.substr(0, sizeof(TWRP_BACKUP_ARG) - 1) == TWRP_BACKUP_ARG) { + tw.adblogwrite("Starting adb backup\n"); + if (isdigit(*argv[1])) + tw.adbd_fd = atoi(argv[1]); + else + tw.adbd_fd = 1; + ret = tw.backup(command); + } + else if (command.substr(0, sizeof(TWRP_RESTORE_ARG) - 1) == TWRP_RESTORE_ARG) { + tw.adblogwrite("Starting adb restore\n"); + if (isdigit(*argv[1])) + tw.adbd_fd = atoi(argv[1]); + else + tw.adbd_fd = 0; + ret = tw.restore(); + } + else if (command.substr(0, sizeof(TWRP_STREAM_ARG) - 1) == TWRP_STREAM_ARG) { + tw.setStreamFileName(argv[3]); + tw.threadStream(); + ret = true; + } + if (ret) + tw.adblogwrite("Adb backup/restore completed\n"); + else + tw.adblogwrite("Adb backup/restore failed\n"); + + if (unlink(TW_ADB_BU_CONTROL) < 0) { + std::stringstream str; + str << strerror(errno); + tw.adblogwrite("Unable to remove TW_ADB_BU_CONTROL: " + str.str()); + } + unlink(TW_ADB_TWRP_CONTROL); + if (ret) + return 0; + else + return -1; +} diff --git a/adbbu/libtwadbbu.cpp b/adbbu/libtwadbbu.cpp index cdc2170bac..39803b0ce6 100644 --- a/adbbu/libtwadbbu.cpp +++ b/adbbu/libtwadbbu.cpp @@ -1,5 +1,5 @@ /* - Copyright 2013 to 2016 TeamWin + Copyright 2013 to 2017 TeamWin TWRP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -27,11 +27,108 @@ #include #include #include +#include #include #include +#include #include "twadbstream.h" #include "libtwadbbu.hpp" +#include "twrpback.hpp" + +bool twadbbu::Check_ADB_Backup_File(std::string fname) { + struct AdbBackupStreamHeader adbbuhdr; + uint32_t crc, adbbuhdrcrc; + unsigned char buf[MAX_ADB_READ]; + int bytes; + + int fd = open(fname.c_str(), O_RDONLY); + if (fd < 0) { + printf("Unable to open %s for reading: %s.\n", fname.c_str(), strerror(errno)); + close(fd); + return false; + } + bytes = read(fd, &buf, sizeof(buf)); + close(fd); + + if (memcpy(&adbbuhdr, buf, sizeof(adbbuhdr)) == NULL) { + printf("Unable to memcpy: %s (%s).\n", fname.c_str(), strerror(errno)); + return false; + } + adbbuhdrcrc = adbbuhdr.crc; + memset(&adbbuhdr.crc, 0, sizeof(adbbuhdr.crc)); + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (const unsigned char*) &adbbuhdr, sizeof(adbbuhdr)); + + return (crc == adbbuhdrcrc); +} + +std::vector twadbbu::Get_ADB_Backup_Files(std::string fname) { + unsigned char buf[MAX_ADB_READ]; + struct AdbBackupControlType structcmd; + std::vector adb_partitions; + + int fd = open(fname.c_str(), O_RDONLY); + if (fd < 0) { + printf("Unable to open %s for reading: %s\n", fname.c_str(), strerror(errno)); + close(fd); + return std::vector(); + } + + while (true) { + std::string cmdstr; + int readbytes; + if ((readbytes = read(fd, &buf, sizeof(buf))) > 0) { + memcpy(&structcmd, buf, sizeof(structcmd)); + assert(structcmd.type == TWENDADB || structcmd.type == TWIMG || structcmd.type == TWFN); + cmdstr = structcmd.type; + std::string cmdtype = cmdstr.substr(0, sizeof(structcmd.type) - 1); + if (cmdtype == TWENDADB) { + struct AdbBackupControlType endadb; + uint32_t crc, endadbcrc; + + memcpy(&endadb, buf, sizeof(endadb)); + endadbcrc = endadb.crc; + memset(&endadb.crc, 0, sizeof(endadb.crc)); + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (const unsigned char*) &endadb, sizeof(endadb)); + + if (crc == endadbcrc) { + break; + } + else { + printf("ADB TWENDADB crc header doesn't match\n"); + close(fd); + return std::vector(); + } + } + else if (cmdtype == TWIMG || cmdtype == TWFN) { + struct twfilehdr twfilehdr; + uint32_t crc, twfilehdrcrc; + + memcpy(&twfilehdr, buf, sizeof(twfilehdr)); + twfilehdrcrc = twfilehdr.crc; + memset(&twfilehdr.crc, 0, sizeof(twfilehdr.crc)); + + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (const unsigned char*) &twfilehdr, sizeof(twfilehdr)); + if (crc == twfilehdrcrc) { + std::string adbfile = twfilehdr.name; + int pos = adbfile.find_last_of("/") + 1; + adbfile = adbfile.substr(pos, adbfile.size()); + adb_partitions.push_back(adbfile); + } + else { + printf("ADB crc header doesn't match\n"); + close(fd); + return std::vector(); + } + } + } + } + close(fd); + return adb_partitions; +} bool twadbbu::Write_ADB_Stream_Header(uint64_t partition_count) { struct AdbBackupStreamHeader twhdr; @@ -40,7 +137,7 @@ bool twadbbu::Write_ADB_Stream_Header(uint64_t partition_count) { memset(&twhdr, 0, sizeof(twhdr)); adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK); if (adb_control_bu_fd < 0) { - printf("Cannot write to TW_ADB_BU_CONTROL.\n"); + printf("Cannot write to TW_ADB_BU_CONTROL: %s\n", strerror(errno)); return false; } @@ -52,7 +149,7 @@ bool twadbbu::Write_ADB_Stream_Header(uint64_t partition_count) { twhdr.crc = crc32(0L, Z_NULL, 0); twhdr.crc = crc32(twhdr.crc, (const unsigned char*) &twhdr, sizeof(twhdr)); if (write(adb_control_bu_fd, &twhdr, sizeof(twhdr)) < 0) { - printf("Cannot write to adb control channel\n"); + printf("Cannot write to adb control channel: %s\n", strerror(errno)); close(adb_control_bu_fd); return false; } @@ -67,7 +164,7 @@ bool twadbbu::Write_ADB_Stream_Trailer() { adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY); if (adb_control_bu_fd < 0) { - printf("Error opening adb_control_bu_fd\n"); + printf("Error opening adb_control_bu_fd: %s\n", strerror(errno)); return false; } strncpy(endadb.start_of_header, TWRP, sizeof(endadb.start_of_header)); @@ -75,7 +172,7 @@ bool twadbbu::Write_ADB_Stream_Trailer() { endadb.crc = crc32(0L, Z_NULL, 0); endadb.crc = crc32(endadb.crc, (const unsigned char*) &endadb, sizeof(endadb)); if (write(adb_control_bu_fd, &endadb, sizeof(endadb)) < 0) { - printf("Cannot write to ADB control.\n"); + printf("Cannot write to ADB control: %s\n", strerror(errno)); close(adb_control_bu_fd); return false; } @@ -97,10 +194,11 @@ bool twadbbu::Write_TWFN(std::string Backup_FileName, uint64_t file_size, bool u printf("Sending TWFN to adb\n"); if (write(adb_control_bu_fd, &twfilehdr, sizeof(twfilehdr)) < 1) { - printf("Cannot that write to adb_control_bu_fd\n"); + printf("Cannot that write to adb_control_bu_fd: %s\n", strerror(errno)); close(adb_control_bu_fd); return false; } + fsync(adb_control_bu_fd); close(adb_control_bu_fd); return true; } @@ -118,7 +216,7 @@ bool twadbbu::Write_TWIMG(std::string Backup_FileName, uint64_t file_size) { twimghdr.crc = crc32(twimghdr.crc, (const unsigned char*) &twimghdr, sizeof(twimghdr)); printf("Sending TWIMG to adb\n"); if (write(adb_control_bu_fd, &twimghdr, sizeof(twimghdr)) < 1) { - printf("Cannot write to adb control channel\n"); + printf("Cannot write to adb control channel: %s\n", strerror(errno)); return false; } @@ -168,7 +266,7 @@ bool twadbbu::Write_TWERROR() { twerror.crc = crc32(0L, Z_NULL, 0); twerror.crc = crc32(twerror.crc, (const unsigned char*) &twerror, sizeof(twerror)); if (write(adb_control_bu_fd, &twerror, sizeof(twerror)) < 0) { - printf("Cannot write to adb control channel"); + printf("Cannot write to adb control channel: %s\n", strerror(errno)); return false; } close(adb_control_bu_fd); @@ -194,3 +292,16 @@ bool twadbbu::Write_TWENDADB() { close(adb_control_bu_fd); return true; } + +bool twadbbu::Write_TWDATA(FILE* adbd_fp) { + struct AdbBackupControlType data_block; + memset(&data_block, 0, sizeof(data_block)); + strncpy(data_block.start_of_header, TWRP, sizeof(data_block.start_of_header)); + strncpy(data_block.type, TWDATA, sizeof(data_block.type)); + data_block.crc = crc32(0L, Z_NULL, 0); + data_block.crc = crc32(data_block.crc, (const unsigned char*) &data_block, sizeof(data_block)); + if (fwrite(&data_block, 1, sizeof(data_block), adbd_fp) != sizeof(data_block)) { + return false; + } + return true; +} diff --git a/adbbu/libtwadbbu.hpp b/adbbu/libtwadbbu.hpp index bcd8b6aa3c..9244bb54d6 100644 --- a/adbbu/libtwadbbu.hpp +++ b/adbbu/libtwadbbu.hpp @@ -1,5 +1,5 @@ /* - Copyright 2013 to 2016 TeamWin + Copyright 2013 to 2017 TeamWin TWRP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -13,6 +13,8 @@ You should have received a copy of the GNU General Public License along with TWRP. If not, see . */ +#ifndef _LIBTWADBBU_HPP +#define _LIBTWADBBU_HPP #include #include @@ -26,14 +28,16 @@ #include #include #include +#include #include #include #include "twadbstream.h" -#include "twrpback.hpp" class twadbbu { public: + static bool Check_ADB_Backup_File(std::string fname); //Check if file is ADB Backup file + static std::vector Get_ADB_Backup_Files(std::string fname); //List ADB Files in String Vector static bool Write_ADB_Stream_Header(uint64_t partition_count); //Write ADB Stream Header to stream static bool Write_ADB_Stream_Trailer(); //Write ADB Stream Trailer to stream static bool Write_TWFN(std::string Backup_FileName, uint64_t file_size, bool use_compression); //Write a tar image to stream @@ -41,4 +45,7 @@ class twadbbu { static bool Write_TWEOF(); //Write ADB End-Of-File marker to stream static bool Write_TWERROR(); //Write error message occurred to stream static bool Write_TWENDADB(); //Write ADB End-Of-Stream command to stream + static bool Write_TWDATA(FILE* adbd_fp); //Write TWDATA separator }; + +#endif //__LIBTWADBBU_HPP diff --git a/adbbu/twadbstream.h b/adbbu/twadbstream.h index 5fa6bde5da..bef463cf89 100644 --- a/adbbu/twadbstream.h +++ b/adbbu/twadbstream.h @@ -1,27 +1,33 @@ /* - TWRP is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + TWRP is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - TWRP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + TWRP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with TWRP. If not, see . + You should have received a copy of the GNU General Public License + along with TWRP. If not, see . */ #ifndef __TWADBSTREAM_H #define __TWADBSTREAM_H +#define TWRPARG "--twrp" +#define TWRP_BACKUP_ARG "backup" +#define TWRP_RESTORE_ARG "restore" +#define TWRP_STREAM_ARG "stream" #define TW_ADB_BACKUP "/tmp/twadbbackup" //FIFO for adb backup #define TW_ADB_RESTORE "/tmp/twadbrestore" //FIFO for adb restore #define TW_ADB_BU_CONTROL "/tmp/twadbbucontrol" //FIFO for sending control from TWRP to ADB Backup #define TW_ADB_TWRP_CONTROL "/tmp/twadbtwrpcontrol" //FIFO for sending control from ADB Backup to TWRP #define TWRP "TWRP" //Magic Value #define ADB_BU_MAX_ERROR 20 //Max amount of errors for while loops +#define ADB_BACKUP_OP "adbbackup" +#define ADB_RESTORE_OP "adbrestore" //ADB Backup Control Commands #define TWSTREAMHDR "twstreamheader" //TWRP Parititon Count Control @@ -33,7 +39,7 @@ #define TWMD5 "twverifymd5" //This command is compared to the md5trailer by ORS to verify transfer #define TWENDADB "twendadb" //End Protocol #define TWERROR "twerror" //Send error -#define ADB_BACKUP_VERSION 1 //Backup Version +#define ADB_BACKUP_VERSION 3 //Backup Version #define DATA_MAX_CHUNK_SIZE 1048576 //Maximum size between each data header #define MAX_ADB_READ 512 //align with default tar size for amount to read fom adb stream diff --git a/adbbu/twrpback.cpp b/adbbu/twrpback.cpp index 8652de2acb..56bc13d480 100644 --- a/adbbu/twrpback.cpp +++ b/adbbu/twrpback.cpp @@ -1,5 +1,5 @@ /* - Copyright 2013 to 2016 TeamWin + Copyright 2013 to 2017 TeamWin TWRP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -28,30 +28,66 @@ #include #include #include -#include #include +#include #include +#include +#include #include "twadbstream.h" #include "twrpback.hpp" +#include "libtwadbbu.hpp" #include "../twrpDigest/twrpDigest.hpp" #include "../twrpDigest/twrpMD5.hpp" +#include "../twrpAdbBuFifo.hpp" twrpback::twrpback(void) { + adbd_fp = NULL; read_fd = 0; write_fd = 0; adb_control_twrp_fd = 0; adb_control_bu_fd = 0; adb_read_fd = 0; adb_write_fd = 0; - adb_write_fd = 0; ors_fd = 0; + debug_adb_fd = 0; firstPart = true; + createFifos(); adbloginit(); } twrpback::~twrpback(void) { adblogfile.close(); + closeFifos(); +} + +void twrpback::printErrMsg(std::string msg, int errNum) { + std::stringstream str; + str << strerror(errNum); + adblogwrite(msg + " " + str.str() + "\n"); +} + +void twrpback::createFifos(void) { + if (mkfifo(TW_ADB_BU_CONTROL, 0666) < 0) { + std::string msg = "Unable to create TW_ADB_BU_CONTROL fifo: "; + printErrMsg(msg, errno); + } + if (mkfifo(TW_ADB_TWRP_CONTROL, 0666) < 0) { + std::string msg = "Unable to create TW_ADB_TWRP_CONTROL fifo: "; + printErrMsg(msg, errno); + unlink(TW_ADB_BU_CONTROL); + } +} + +void twrpback::closeFifos(void) { + if (unlink(TW_ADB_BU_CONTROL) < 0) { + std::string msg = "Unable to remove TW_ADB_BU_CONTROL: "; + printErrMsg(msg, errno); + } + if (unlink(TW_ADB_TWRP_CONTROL) < 0) { + std::string msg = "Unable to remove TW_ADB_TWRP_CONTROL: "; + printErrMsg(msg, errno); + } } void twrpback::adbloginit(void) { @@ -71,6 +107,10 @@ void twrpback::close_backup_fds() { close(adb_read_fd); if (adb_control_bu_fd > 0) close(adb_control_bu_fd); + #ifdef _DEBUG_ADB_BACKUP + if (debug_adb_fd > 0) + close(debug_adb_fd); + #endif if (adbd_fp != NULL) fclose(adbd_fp); if (access(TW_ADB_BACKUP, F_OK) == 0) @@ -90,15 +130,17 @@ void twrpback::close_restore_fds() { fclose(adbd_fp); if (access(TW_ADB_RESTORE, F_OK) == 0) unlink(TW_ADB_RESTORE); + #ifdef _DEBUG_ADB_BACKUP + if (debug_adb_fd > 0) + close(debug_adb_fd); + #endif } -int twrpback::backup(std::string command) { +bool twrpback::backup(std::string command) { twrpMD5 digest; - bool breakloop = false; int bytes = 0, errctr = 0; - char result[MAX_ADB_READ]; - uint64_t totalbytes = 0, dataChunkBytes = 0; - int64_t count = -1; // Count of how many blocks set + char adbReadStream[MAX_ADB_READ]; + uint64_t totalbytes = 0, dataChunkBytes = 0, fileBytes = 0; uint64_t md5fnsize = 0; struct AdbBackupControlType endadb; @@ -111,24 +153,26 @@ int twrpback::backup(std::string command) { adbd_fp = fdopen(adbd_fd, "w"); if (adbd_fp == NULL) { adblogwrite("Unable to open adb_fp\n"); - return -1; + return false; } if (mkfifo(TW_ADB_BACKUP, 0666) < 0) { adblogwrite("Unable to create TW_ADB_BACKUP fifo\n"); - return -1; + return false; } - adblogwrite("opening ORS_INPUT_FILE\n"); - write_fd = open(ORS_INPUT_FILE, O_WRONLY); + adblogwrite("opening TW_ADB_FIFO\n"); + + write_fd = open(TW_ADB_FIFO, O_WRONLY); while (write_fd < 0) { - write_fd = open(ORS_INPUT_FILE, O_WRONLY); + write_fd = open(TW_ADB_FIFO, O_WRONLY); usleep(10000); errctr++; if (errctr > ADB_BU_MAX_ERROR) { - adblogwrite("Unable to open ORS_INPUT_FILE\n"); + std::string msg = "Unable to open TW_ADB_FIFO"; + printErrMsg(msg, errno); close_backup_fds(); - return -1; + return false; } } @@ -136,23 +180,15 @@ int twrpback::backup(std::string command) { if (snprintf(operation, sizeof(operation), "adbbackup %s", command.c_str()) >= sizeof(operation)) { adblogwrite("Operation too big to write to ORS_INPUT_FILE\n"); close_backup_fds(); - return -1; + return false; } if (write(write_fd, operation, sizeof(operation)) != sizeof(operation)) { adblogwrite("Unable to write to ORS_INPUT_FILE\n"); close_backup_fds(); - return -1; - } - - adblogwrite("opening ORS_OUTPUT_FILE\n"); - ors_fd = open(ORS_OUTPUT_FILE, O_RDONLY); - if (ors_fd < 0) { - adblogwrite("Unable to open ORS_OUTPUT_FILE\n"); - close_backup_fds(); - return -1; + return false; } - memset(&result, 0, sizeof(result)); + memset(&adbReadStream, 0, sizeof(adbReadStream)); memset(&cmd, 0, sizeof(cmd)); adblogwrite("opening TW_ADB_BU_CONTROL\n"); @@ -160,7 +196,7 @@ int twrpback::backup(std::string command) { if (adb_control_bu_fd < 0) { adblogwrite("Unable to open TW_ADB_BU_CONTROL for reading.\n"); close_backup_fds(); - return -1; + return false; } adblogwrite("opening TW_ADB_BACKUP\n"); @@ -168,11 +204,11 @@ int twrpback::backup(std::string command) { if (adb_read_fd < 0) { adblogwrite("Unable to open TW_ADB_BACKUP for reading.\n"); close_backup_fds(); - return -1; + return false; } //loop until TWENDADB sent - while (!breakloop) { + while (true) { if (read(adb_control_bu_fd, &cmd, sizeof(cmd)) > 0) { struct AdbBackupControlType structcmd; @@ -184,7 +220,7 @@ int twrpback::backup(std::string command) { writedata = false; adblogwrite("Error received. Quitting...\n"); close_backup_fds(); - return -1; + return false; } //we received the end of adb backup stream so we should break the loop else if (cmdtype == TWENDADB) { @@ -194,18 +230,17 @@ int twrpback::backup(std::string command) { std::stringstream str; str << totalbytes; adblogwrite(str.str() + " total bytes written\n"); - breakloop = true; + break; } //we recieved the TWSTREAMHDR structure metadata to write to adb else if (cmdtype == TWSTREAMHDR) { writedata = false; - adblogwrite("Writing TWSTREAMHDR\n"); + adblogwrite("writing TWSTREAMHDR\n"); if (fwrite(cmd, 1, sizeof(cmd), adbd_fp) != sizeof(cmd)) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Error writing TWSTREAMHDR to adbd" + str.str() + "\n"); + std::string msg = "Error writing TWSTREAMHDR to adbd"; + printErrMsg(msg, errno); close_backup_fds(); - return -1; + return false; } fflush(adbd_fp); } @@ -213,16 +248,25 @@ int twrpback::backup(std::string command) { else if (cmdtype == TWIMG) { struct twfilehdr twimghdr; - adblogwrite("Writing TWIMG\n"); + adblogwrite("writing TWIMG\n"); digest.init(); memset(&twimghdr, 0, sizeof(twimghdr)); memcpy(&twimghdr, cmd, sizeof(cmd)); md5fnsize = twimghdr.size; + compressed = false; + + #ifdef _DEBUG_ADB_BACKUP + std::string debug_fname = "/data/media/"; + debug_fname.append(basename(twimghdr.name)); + debug_fname.append("-backup.img"); + debug_adb_fd = open(debug_fname.c_str(), O_WRONLY | O_CREAT, 0666); + adblogwrite("Opening adb debug tar\n"); + #endif - if (fwrite(cmd, 1, sizeof(cmd), adbd_fp) != sizeof(cmd)) { + if (fwrite(cmd, 1, sizeof(cmd), adbd_fp) != sizeof(cmd)) { adblogwrite("Error writing TWIMG to adbd\n"); close_backup_fds(); - return -1; + return false; } fflush(adbd_fp); writedata = true; @@ -231,7 +275,7 @@ int twrpback::backup(std::string command) { else if (cmdtype == TWFN) { struct twfilehdr twfilehdr; - adblogwrite("Writing TWFN\n"); + adblogwrite("writing TWFN\n"); digest.init(); ADBSTRUCT_STATIC_ASSERT(sizeof(twfilehdr) == MAX_ADB_READ); @@ -242,10 +286,18 @@ int twrpback::backup(std::string command) { compressed = twfilehdr.compressed == 1 ? true: false; + #ifdef _DEBUG_ADB_BACKUP + std::string debug_fname = "/data/media/"; + debug_fname.append(basename(twfilehdr.name)); + debug_fname.append("-backup.tar"); + debug_adb_fd = open(debug_fname.c_str(), O_WRONLY | O_CREAT, 0666); + adblogwrite("Opening adb debug tar\n"); + #endif + if (fwrite(cmd, 1, sizeof(cmd), adbd_fp) != sizeof(cmd)) { adblogwrite("Error writing TWFN to adbd\n"); close_backup_fds(); - return -1; + return false; } fflush(adbd_fp); writedata = true; @@ -260,36 +312,57 @@ int twrpback::backup(std::string command) { */ else if (cmdtype == TWEOF) { adblogwrite("received TWEOF\n"); - count = totalbytes / MAX_ADB_READ + 1; - count = count * MAX_ADB_READ; - - while ((bytes = read(adb_read_fd, &result, sizeof(result))) == MAX_ADB_READ) { + while ((bytes = read(adb_read_fd, &adbReadStream, sizeof(adbReadStream)) != 0)) { totalbytes += bytes; - char *writeresult = new char [bytes]; - memcpy(writeresult, result, bytes); - digest.update((unsigned char *) writeresult, bytes); - if (fwrite(writeresult, 1, bytes, adbd_fp) != bytes) { - adblogwrite("Error writing backup data to adbd\n"); - close_backup_fds(); - return -1; + fileBytes += bytes; + dataChunkBytes += bytes; + + char *writeAdbReadStream = new char [bytes]; + memcpy(writeAdbReadStream, adbReadStream, bytes); + + digest.update((unsigned char *) writeAdbReadStream, bytes); + if (fwrite(writeAdbReadStream, 1, bytes, adbd_fp) < 0) { + std::string msg = "Cannot write to adbd stream: "; + printErrMsg(msg, errno); + } + #if defined(_DEBUG_ADB_BACKUP) + if (write(debug_adb_fd, writeAdbReadStream, bytes) < 1) { + std::string msg = "Cannot write to ADB_CONTROL_READ_FD: "; + printErrMsg(msg, errno); + close_restore_fds(); + return false; } + #endif fflush(adbd_fp); - delete [] writeresult; - memset(&result, 0, sizeof(result)); + delete [] writeAdbReadStream; + memset(adbReadStream, 0, sizeof(adbReadStream)); } - if ((totalbytes % MAX_ADB_READ) != 0) { - adblogwrite("writing padding to stream\n"); - char padding[count - totalbytes]; - memset(padding, 0, sizeof(padding)); - if (fwrite(padding, 1, sizeof(padding), adbd_fp) != sizeof(padding)) { + if (fileBytes % DATA_MAX_CHUNK_SIZE != 0) { + int64_t count = fileBytes / DATA_MAX_CHUNK_SIZE + 1; + uint64_t ceilingBytes = count * DATA_MAX_CHUNK_SIZE; + char padding[ceilingBytes - fileBytes]; + int paddingBytes = sizeof(padding); + memset(padding, 0, paddingBytes); + std::stringstream paddingStr; + paddingStr << paddingBytes; + adblogwrite("writing padding to stream: " + paddingStr.str() + " bytes\n"); + if (fwrite(padding, 1, paddingBytes, adbd_fp) != sizeof(padding)) { adblogwrite("Error writing padding to adbd\n"); close_backup_fds(); - return -1; + return false; + } + #if defined(_DEBUG_ADB_BACKUP) + if (write(debug_adb_fd, padding, paddingBytes) < 1) { + std::string msg = "Cannot write to ADB_CONTROL_READ_FD: "; + printErrMsg(msg, errno); + close_restore_fds(); + return false; } - digest.update((unsigned char *) padding, sizeof(padding)); + #endif + totalbytes += paddingBytes; + digest.update((unsigned char *) padding, paddingBytes); fflush(adbd_fp); - totalbytes = 0; } AdbBackupFileTrailer md5trailer; @@ -312,72 +385,107 @@ int twrpback::backup(std::string command) { if (fwrite(&md5trailer, 1, sizeof(md5trailer), adbd_fp) != sizeof(md5trailer)) { adblogwrite("Error writing md5trailer to adbd\n"); close_backup_fds(); - return -1; + return false; } fflush(adbd_fp); writedata = false; firstDataPacket = true; + fileBytes = 0; } memset(&cmd, 0, sizeof(cmd)); + dataChunkBytes = 0; } //If we are to write data because of a new file stream, lets write all the data. //This will allow us to not write data after a command structure has been written //to the adb stream. //If the stream is compressed, we need to always write the data. if (writedata || compressed) { - while ((bytes = read(adb_read_fd, &result, sizeof(result))) > 0) { + while ((bytes = read(adb_read_fd, &adbReadStream, sizeof(adbReadStream))) > 0) { if (firstDataPacket) { - struct AdbBackupControlType data_block; - - memset(&data_block, 0, sizeof(data_block)); - strncpy(data_block.start_of_header, TWRP, sizeof(data_block.start_of_header)); - strncpy(data_block.type, TWDATA, sizeof(data_block.type)); - data_block.crc = crc32(0L, Z_NULL, 0); - data_block.crc = crc32(data_block.crc, (const unsigned char*) &data_block, sizeof(data_block)); - if (fwrite(&data_block, 1, sizeof(data_block), adbd_fp) != sizeof(data_block)) { - adblogwrite("Error writing data_block to adbd\n"); + if (!twadbbu::Write_TWDATA(adbd_fp)) { close_backup_fds(); - return -1; + return false; } + fileBytes += MAX_ADB_READ; fflush(adbd_fp); firstDataPacket = false; + dataChunkBytes += sizeof(adbReadStream); } - char *writeresult = new char [bytes]; - memcpy(writeresult, result, bytes); + char *writeAdbReadStream = new char [bytes]; + memcpy(writeAdbReadStream, adbReadStream, bytes); - digest.update((unsigned char *) writeresult, bytes); + digest.update((unsigned char *) writeAdbReadStream, bytes); totalbytes += bytes; + fileBytes += bytes; dataChunkBytes += bytes; - if (fwrite(writeresult, 1, bytes, adbd_fp) != bytes) { + if (fwrite(writeAdbReadStream, 1, bytes, adbd_fp) != bytes) { adblogwrite("Error writing backup data to adbd\n"); close_backup_fds(); - return -1; + return false; } + #ifdef _DEBUG_ADB_BACKUP + if (write(debug_adb_fd, writeAdbReadStream, bytes) < 1) { + std::string msg = "Cannot write to ADB_CONTROL_READ_FD: "; + printErrMsg(msg, errno); + close_restore_fds(); + return false; + } + #endif fflush(adbd_fp); + delete [] writeAdbReadStream; - delete [] writeresult; - memset(&result, 0, sizeof(result)); - if (dataChunkBytes == DATA_MAX_CHUNK_SIZE - sizeof(result)) { - struct AdbBackupControlType data_block; - - memset(&data_block, 0, sizeof(data_block)); - strncpy(data_block.start_of_header, TWRP, sizeof(data_block.start_of_header)); - strncpy(data_block.type, TWDATA, sizeof(data_block.type)); - data_block.crc = crc32(0L, Z_NULL, 0); - data_block.crc = crc32(data_block.crc, (const unsigned char*) &data_block, sizeof(data_block)); - if (fwrite(&data_block, 1, sizeof(data_block), adbd_fp) != sizeof(data_block)) { - adblogwrite("Error writing data_block to adbd\n"); - close_backup_fds(); - return -1; + memset(&adbReadStream, 0, sizeof(adbReadStream)); + + if (dataChunkBytes == DATA_MAX_CHUNK_SIZE) { + dataChunkBytes = 0; + firstDataPacket = true; + } + else if (dataChunkBytes > (DATA_MAX_CHUNK_SIZE - sizeof(adbReadStream))) { + int bytesLeft = DATA_MAX_CHUNK_SIZE - dataChunkBytes; + char extraData[bytesLeft]; + + memset(&extraData, 0, bytesLeft); + while ((bytes = read(adb_read_fd, &extraData, bytesLeft)) != 0) { + if (bytes > 0) { + totalbytes += bytes; + fileBytes += bytes; + dataChunkBytes += bytes; + + bytesLeft -= bytes; + char *writeAdbReadStream = new char [bytes]; + memcpy(writeAdbReadStream, extraData, bytes); + + digest.update((unsigned char *) writeAdbReadStream, bytes); + if (fwrite(writeAdbReadStream, 1, bytes, adbd_fp) < 0) { + std::string msg = "Cannot write to adbd stream: "; + printErrMsg(msg, errno); + close_restore_fds(); + return false; + } + #ifdef _DEBUG_ADB_BACKUP + if (write(debug_adb_fd, writeAdbReadStream, bytes) < 1) { + std::string msg = "Cannot write to ADB_CONTROL_READ_FD: "; + printErrMsg(msg, errno); + close_restore_fds(); + return false; + } + #endif + fflush(adbd_fp); + delete [] writeAdbReadStream; + } + memset(&extraData, 0, bytesLeft); + if (bytesLeft == 0) { + break; + } } + fflush(adbd_fp); dataChunkBytes = 0; + firstDataPacket = true; } - } - compressed = false; } } @@ -385,91 +493,82 @@ int twrpback::backup(std::string command) { if (fwrite(&endadb, 1, sizeof(endadb), adbd_fp) != sizeof(endadb)) { adblogwrite("Error writing endadb to adbd\n"); close_backup_fds(); - return -1; + return false; } fflush(adbd_fp); close_backup_fds(); - return 0; + return true; } -int twrpback::restore(void) { +bool twrpback::restore(void) { twrpMD5 digest; char cmd[MAX_ADB_READ]; - char result[MAX_ADB_READ]; + char readAdbStream[MAX_ADB_READ]; struct AdbBackupControlType structcmd; - int adb_control_twrp_fd, errctr = 0; + int errctr = 0; uint64_t totalbytes = 0, dataChunkBytes = 0; - uint64_t md5fnsize = 0; + uint64_t md5fnsize = 0, fileBytes = 0; bool writedata, read_from_adb; - bool breakloop, eofsent, md5trsent; + bool eofsent, md5trsent, md5sumdata; + bool compressed, tweofrcvd, extraData; - breakloop = false; read_from_adb = true; signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); adbd_fp = fdopen(adbd_fd, "r"); if (adbd_fp == NULL) { adblogwrite("Unable to open adb_fp\n"); close_restore_fds(); - return -1; + return false; } if(mkfifo(TW_ADB_RESTORE, 0666)) { adblogwrite("Unable to create TW_ADB_RESTORE fifo\n"); close_restore_fds(); - return -1; + return false; } - adblogwrite("opening ORS_INPUT_FILE\n"); - write_fd = open(ORS_INPUT_FILE, O_WRONLY); + adblogwrite("opening TW_ADB_FIFO\n"); + write_fd = open(TW_ADB_FIFO, O_WRONLY); while (write_fd < 0) { - write_fd = open(ORS_INPUT_FILE, O_WRONLY); + write_fd = open(TW_ADB_FIFO, O_WRONLY); errctr++; if (errctr > ADB_BU_MAX_ERROR) { - adblogwrite("Unable to open ORS_INPUT_FILE\n"); + std::string msg = "Unable to open TW_ADB_FIFO."; + printErrMsg(msg, errno); close_restore_fds(); - return -1; + return false; } } memset(operation, 0, sizeof(operation)); sprintf(operation, "adbrestore"); if (write(write_fd, operation, sizeof(operation)) != sizeof(operation)) { - adblogwrite("Unable to write to ORS_INPUT_FILE\n"); - close_restore_fds(); - return -1; - } - - ors_fd = open(ORS_OUTPUT_FILE, O_RDONLY); - if (ors_fd < 0) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Unable to write to ORS_OUTPUT_FILE: " + str.str() + "\n"); + adblogwrite("Unable to write to TW_ADB_FIFO\n"); close_restore_fds(); - return -1; + return false; } - memset(&result, 0, sizeof(result)); + memset(&readAdbStream, 0, sizeof(readAdbStream)); memset(&cmd, 0, sizeof(cmd)); adblogwrite("opening TW_ADB_BU_CONTROL\n"); adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_RDONLY | O_NONBLOCK); if (adb_control_bu_fd < 0) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Unable to open TW_ADB_BU_CONTROL for writing. " + str.str() + "\n"); + std::string msg = "Unable to open TW_ADB_BU_CONTROL for writing."; + printErrMsg(msg, errno); close_restore_fds(); - return -1; + return false; } adblogwrite("opening TW_ADB_TWRP_CONTROL\n"); adb_control_twrp_fd = open(TW_ADB_TWRP_CONTROL, O_WRONLY | O_NONBLOCK); if (adb_control_twrp_fd < 0) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Unable to open TW_ADB_TWRP_CONTROL for writing. " + str.str() + ". Retrying...\n"); + std::string msg = "Unable to open TW_ADB_TWRP_CONTROL for writing. Retrying..."; + printErrMsg(msg, errno); while (adb_control_twrp_fd < 0) { adb_control_twrp_fd = open(TW_ADB_TWRP_CONTROL, O_WRONLY | O_NONBLOCK); usleep(10000); @@ -477,13 +576,13 @@ int twrpback::restore(void) { if (errctr > ADB_BU_MAX_ERROR) { adblogwrite("Unable to open TW_ADB_TWRP_CONTROL\n"); close_backup_fds(); - return -1; + return false; } } } //Loop until we receive TWENDADB from TWRP - while (!breakloop) { + while (true) { memset(&cmd, 0, sizeof(cmd)); if (read(adb_control_bu_fd, &cmd, sizeof(cmd)) > 0) { struct AdbBackupControlType structcmd; @@ -493,31 +592,27 @@ int twrpback::restore(void) { //If we receive TWEOF from TWRP close adb data fifo if (cmdtype == TWEOF) { adblogwrite("Received TWEOF\n"); - struct AdbBackupControlType tweof; - - memset(&tweof, 0, sizeof(tweof)); - memcpy(&tweof, result, sizeof(result)); read_from_adb = true; + tweofrcvd = true; + close(adb_write_fd); } //Break when TWRP sends TWENDADB else if (cmdtype == TWENDADB) { adblogwrite("Received TWENDADB\n"); - breakloop = true; - close_restore_fds(); + break; } //we received an error, exit and unlink else if (cmdtype == TWERROR) { adblogwrite("Error received. Quitting...\n"); close_restore_fds(); - return -1; + return false; } } //If we should read from the adb stream, write commands and data to TWRP if (read_from_adb) { int readbytes; - if ((readbytes = fread(result, 1, sizeof(result), adbd_fp)) == sizeof(result)) { - totalbytes += readbytes; - memcpy(&structcmd, result, sizeof(result)); + if ((readbytes = fread(readAdbStream, 1, sizeof(readAdbStream), adbd_fp)) == sizeof(readAdbStream)) { + memcpy(&structcmd, readAdbStream, sizeof(readAdbStream)); std::string cmdtype = structcmd.get_type(); //Tell TWRP we have read the entire adb stream @@ -525,29 +620,28 @@ int twrpback::restore(void) { struct AdbBackupControlType endadb; uint32_t crc, endadbcrc; - totalbytes -= sizeof(result); + md5sumdata = false; memset(&endadb, 0, sizeof(endadb)); - memcpy(&endadb, result, sizeof(result)); + memcpy(&endadb, readAdbStream, sizeof(readAdbStream)); endadbcrc = endadb.crc; memset(&endadb.crc, 0, sizeof(endadb.crc)); crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, (const unsigned char*) &endadb, sizeof(endadb)); if (crc == endadbcrc) { - adblogwrite("Sending TWENDADB\n"); + adblogwrite("sending TWENDADB\n"); if (write(adb_control_twrp_fd, &endadb, sizeof(endadb)) < 1) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Cannot write to ADB_CONTROL_READ_FD: " + str.str() + "\n"); + std::string msg = "Cannot write to ADB_CONTROL_READ_FD: "; + printErrMsg(msg, errno); close_restore_fds(); - return -1; + return false; } read_from_adb = false; } else { adblogwrite("ADB TWENDADB crc header doesn't match\n"); close_restore_fds(); - return -1; + return false; } } //Send TWRP partition metadata @@ -556,10 +650,10 @@ int twrpback::restore(void) { uint32_t crc, cnthdrcrc; ADBSTRUCT_STATIC_ASSERT(sizeof(cnthdr) == MAX_ADB_READ); - totalbytes -= sizeof(result); + md5sumdata = false; memset(&cnthdr, 0, sizeof(cnthdr)); - memcpy(&cnthdr, result, sizeof(result)); + memcpy(&cnthdr, readAdbStream, sizeof(readAdbStream)); cnthdrcrc = cnthdr.crc; memset(&cnthdr.crc, 0, sizeof(cnthdr.crc)); crc = crc32(0L, Z_NULL, 0); @@ -567,30 +661,33 @@ int twrpback::restore(void) { if (crc == cnthdrcrc) { adblogwrite("Restoring TWSTREAMHDR\n"); - if (write(adb_control_twrp_fd, result, sizeof(result)) < 0) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); + if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 0) { + std::string msg = "Cannot write to adb_control_twrp_fd: "; + printErrMsg(msg, errno); close_restore_fds(); - return -1; + return false; } } else { adblogwrite("ADB TWSTREAMHDR crc header doesn't match\n"); close_restore_fds(); - return -1; + return false; } } //Tell TWRP we are sending a partition image else if (cmdtype == TWIMG) { struct twfilehdr twimghdr; uint32_t crc, twimghdrcrc; + md5sumdata = false; + fileBytes = 0; + read_from_adb = true; + dataChunkBytes = 0; + extraData = false; digest.init(); - totalbytes -= sizeof(result); adblogwrite("Restoring TWIMG\n"); memset(&twimghdr, 0, sizeof(twimghdr)); - memcpy(&twimghdr, result, sizeof(result)); + memcpy(&twimghdr, readAdbStream, sizeof(readAdbStream)); md5fnsize = twimghdr.size; twimghdrcrc = twimghdr.crc; memset(&twimghdr.crc, 0, sizeof(twimghdr.crc)); @@ -598,19 +695,28 @@ int twrpback::restore(void) { crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, (const unsigned char*) &twimghdr, sizeof(twimghdr)); if (crc == twimghdrcrc) { - if (write(adb_control_twrp_fd, result, sizeof(result)) < 1) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); + if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 1) { + std::string msg = "Cannot write to adb_control_twrp_fd: "; + printErrMsg(msg, errno); close_restore_fds(); - return -1; + return false; } } else { adblogwrite("ADB TWIMG crc header doesn't match\n"); close_restore_fds(); - return -1; + return false; } + + #ifdef _DEBUG_ADB_BACKUP + std::string debug_fname = "/data/media/"; + debug_fname.append(basename(twimghdr.name)); + debug_fname.append("-restore.img"); + adblogwrite("image: " + debug_fname + "\n"); + debug_adb_fd = open(debug_fname.c_str(), O_WRONLY | O_CREAT, 0666); + adblogwrite("Opened restore image\n"); + #endif + adblogwrite("opening TW_ADB_RESTORE\n"); adb_write_fd = open(TW_ADB_RESTORE, O_WRONLY); } @@ -618,12 +724,16 @@ int twrpback::restore(void) { else if (cmdtype == TWFN) { struct twfilehdr twfilehdr; uint32_t crc, twfilehdrcrc; - digest.init(); + fileBytes = 0; + md5sumdata = false; + read_from_adb = true; + dataChunkBytes = 0; + extraData = false; - totalbytes -= sizeof(result); + digest.init(); adblogwrite("Restoring TWFN\n"); memset(&twfilehdr, 0, sizeof(twfilehdr)); - memcpy(&twfilehdr, result, sizeof(result)); + memcpy(&twfilehdr, readAdbStream, sizeof(readAdbStream)); md5fnsize = twfilehdr.size; twfilehdrcrc = twfilehdr.crc; memset(&twfilehdr.crc, 0, sizeof(twfilehdr.crc)); @@ -632,190 +742,194 @@ int twrpback::restore(void) { crc = crc32(crc, (const unsigned char*) &twfilehdr, sizeof(twfilehdr)); if (crc == twfilehdrcrc) { - if (write(adb_control_twrp_fd, result, sizeof(result)) < 1) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); + if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 1) { + std::string msg = "Cannot write to adb_control_twrp_fd: "; + printErrMsg(msg, errno); close_restore_fds(); - return -1; + return false; } } else { adblogwrite("ADB TWFN crc header doesn't match\n"); close_restore_fds(); - return -1; + return false; } + #ifdef _DEBUG_ADB_BACKUP + std::string debug_fname = "/data/media/"; + debug_fname.append(basename(twfilehdr.name)); + debug_fname.append("-restore.tar"); + adblogwrite("tar: " + debug_fname + "\n"); + debug_adb_fd = open(debug_fname.c_str(), O_WRONLY | O_CREAT, 0666); + adblogwrite("Opened restore tar\n"); + #endif + + compressed = twfilehdr.compressed == 1 ? true: false; adblogwrite("opening TW_ADB_RESTORE\n"); adb_write_fd = open(TW_ADB_RESTORE, O_WRONLY); } + else if (cmdtype == MD5TRAILER) { + if (fileBytes >= md5fnsize) + close(adb_write_fd); + if (tweofrcvd) { + read_from_adb = true; + tweofrcvd = false; + } + else + read_from_adb = false; //don't read from adb until TWRP sends TWEOF + md5sumdata = false; + if (!checkMD5Trailer(readAdbStream, md5fnsize, &digest)) { + close_restore_fds(); + break; + } + continue; + } //Send the tar or partition image md5 to TWRP else if (cmdtype == TWDATA) { - totalbytes -= sizeof(result); - while (1) { - if ((readbytes = fread(result, 1, sizeof(result), adbd_fp)) != sizeof(result)) { + dataChunkBytes += sizeof(readAdbStream); + while (true) { + if ((readbytes = fread(readAdbStream, 1, sizeof(readAdbStream), adbd_fp)) != sizeof(readAdbStream)) { close_restore_fds(); - return -1; + return false; } - totalbytes += readbytes; - memcpy(&structcmd, result, sizeof(result)); + memcpy(&structcmd, readAdbStream, sizeof(readAdbStream)); std::string cmdtype = structcmd.get_type(); - if (cmdtype.substr(0, sizeof(MD5TRAILER) - 1) == MD5TRAILER) { - struct AdbBackupFileTrailer md5tr; - uint32_t crc, md5trcrc, md5ident, md5identmatch; - - ADBSTRUCT_STATIC_ASSERT(sizeof(md5tr) == MAX_ADB_READ); - memset(&md5tr, 0, sizeof(md5tr)); - memcpy(&md5tr, result, sizeof(result)); - md5ident = md5tr.ident; - - memset(&md5tr.ident, 0, sizeof(md5tr.ident)); - - md5identmatch = crc32(0L, Z_NULL, 0); - md5identmatch = crc32(md5identmatch, (const unsigned char*) &md5tr, sizeof(md5tr)); - md5identmatch = crc32(md5identmatch, (const unsigned char*) &md5fnsize, sizeof(md5fnsize)); + dataChunkBytes += readbytes; + totalbytes += readbytes; + fileBytes += readbytes; - if (md5identmatch == md5ident) { - totalbytes -= sizeof(result); + if (cmdtype == MD5TRAILER) { + if (fileBytes >= md5fnsize) close(adb_write_fd); - adblogwrite("Restoring MD5TRAILER\n"); - md5trcrc = md5tr.crc; - memset(&md5tr.crc, 0, sizeof(md5tr.crc)); - crc = crc32(0L, Z_NULL, 0); - crc = crc32(crc, (const unsigned char*) &md5tr, sizeof(md5tr)); - if (crc == md5trcrc) { - if (write(adb_control_twrp_fd, result, sizeof(result)) < 1) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); - close_restore_fds(); - return -1; - } - } - else { - adblogwrite("ADB MD5TRAILER crc header doesn't match\n"); - close_restore_fds(); - return -1; - } - - AdbBackupFileTrailer md5; - - memset(&md5, 0, sizeof(md5)); - strncpy(md5.start_of_trailer, TWRP, sizeof(md5.start_of_trailer)); - strncpy(md5.type, TWMD5, sizeof(md5.type)); - std::string md5string = digest.return_digest_string(); - strncpy(md5.md5, md5string.c_str(), sizeof(md5.md5)); - - adblogwrite("Sending MD5Check\n"); - if (write(adb_control_twrp_fd, &md5, sizeof(md5)) < 1) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); - close_restore_fds(); - return -1; - } + if (tweofrcvd) { + tweofrcvd = false; + read_from_adb = true; + } + else read_from_adb = false; //don't read from adb until TWRP sends TWEOF + if (!checkMD5Trailer(readAdbStream, md5fnsize, &digest)) { + close_restore_fds(); break; } + break; } - digest.update((unsigned char*)result, sizeof(result)); - dataChunkBytes += readbytes; - if (write(adb_write_fd, result, sizeof(result)) < 0) { - std::stringstream str; - str << strerror(errno); - adblogwrite("Cannot write to adb_write_fd\n" + str.str() + ". Retrying.\n"); - while(write(adb_write_fd, result, sizeof(result)) < 0) { - adblogwrite("Cannot write to adb_write_fd\n" + str.str() + ". Retrying.\n"); - continue; - } + digest.update((unsigned char*)readAdbStream, readbytes); + + read_from_adb = true; + + #ifdef _DEBUG_ADB_BACKUP + if (write(debug_adb_fd, readAdbStream, sizeof(readAdbStream)) < 0) { + std::string msg = "Cannot write to ADB_CONTROL_READ_FD: "; + printErrMsg(msg, errno); + close_restore_fds(); + return false; } - if (dataChunkBytes == ((DATA_MAX_CHUNK_SIZE) - sizeof(result))) { + #endif + + if (write(adb_write_fd, readAdbStream, sizeof(readAdbStream)) < 0) { + std::string msg = "Cannot write to TWRP ADB FIFO: "; + md5sumdata = true; + printErrMsg(msg, errno); + adblogwrite("end of stream reached.\n"); + break; + } + + if (dataChunkBytes == DATA_MAX_CHUNK_SIZE) { dataChunkBytes = 0; + md5sumdata = false; break; } - memset(&result, 0, sizeof(result)); } } + else if (md5sumdata) { + digest.update((unsigned char*)readAdbStream, sizeof(readAdbStream)); + md5sumdata = true; + } } } } - std::stringstream str; str << totalbytes; + close_restore_fds(); adblogwrite(str.str() + " bytes restored from adbbackup\n"); - return 0; + return true; } -int main(int argc, char **argv) { - int index; - int ret = 0, pos = 0; - std::string command; - twrpback tw; - - tw.adblogwrite("Starting adb backup and restore\n"); - if (mkfifo(TW_ADB_BU_CONTROL, 0666) < 0) { - std::stringstream str; - str << strerror(errno); - tw.adblogwrite("Unable to create TW_ADB_BU_CONTROL fifo: " + str.str() + "\n"); - unlink(TW_ADB_BU_CONTROL); - return -1; - } - if (mkfifo(TW_ADB_TWRP_CONTROL, 0666) < 0) { - std::stringstream str; - str << strerror(errno); - tw.adblogwrite("Unable to create TW_ADB_TWRP_CONTROL fifo: " + str.str() + "\n"); - unlink(TW_ADB_TWRP_CONTROL); - unlink(TW_ADB_BU_CONTROL); - return -1; - } +void twrpback::streamFileForTWRP(void) { + adblogwrite("streamFileForTwrp" + streamFn + "\n"); +} - if (argc <= 1) { - tw.adblogwrite("No parameters given, exiting...\n"); - tw.close_restore_fds(); - return -1; +void twrpback::setStreamFileName(std::string fn) { + streamFn = fn; + adbd_fd = open(fn.c_str(), O_RDONLY); + if (adbd_fd < 0) { + adblogwrite("Unable to open adb_fd\n"); + close(adbd_fd); + return; } + restore(); +} - command = argv[1]; - for (index = 2; index < argc; index++) { - command = command + " " + argv[index]; - } +void twrpback::threadStream(void) { + pthread_t thread; + ThreadPtr streamPtr = &twrpback::streamFileForTWRP; + PThreadPtr p = *(PThreadPtr*)&streamPtr; + pthread_create(&thread, NULL, p, this); + pthread_join(thread, NULL); +} - pos = command.find("backup"); - if (pos < 0) { - pos = command.find("restore"); - } - command.erase(0, pos); - command.erase(std::remove(command.begin(), command.end(), '\''), command.end()); - tw.adblogwrite("command: " + command + "\n"); - - if (command.substr(0, sizeof("backup") - 1) == "backup") { - tw.adblogwrite("Starting adb backup\n"); - if (isdigit(*argv[1])) - tw.adbd_fd = atoi(argv[1]); - else - tw.adbd_fd = 1; - ret = tw.backup(command); - } - else if (command.substr(0, sizeof("restore") - 1) == "restore") { - tw.adblogwrite("Starting adb restore\n"); - if (isdigit(*argv[1])) - tw.adbd_fd = atoi(argv[1]); - else - tw.adbd_fd = 0; - ret = tw.restore(); - } - if (ret == 0) - tw.adblogwrite("Adb backup/restore completed\n"); - else - tw.adblogwrite("Adb backup/restore failed\n"); - - if (unlink(TW_ADB_BU_CONTROL) < 0) { - std::stringstream str; - str << strerror(errno); - tw.adblogwrite("Unable to remove TW_ADB_BU_CONTROL: " + str.str()); +bool twrpback::checkMD5Trailer(char readAdbStream[], uint64_t md5fnsize, twrpMD5 *digest) { + struct AdbBackupFileTrailer md5tr; + uint32_t crc, md5trcrc, md5ident, md5identmatch; + + ADBSTRUCT_STATIC_ASSERT(sizeof(md5tr) == MAX_ADB_READ); + memcpy(&md5tr, readAdbStream, MAX_ADB_READ); + md5ident = md5tr.ident; + + memset(&md5tr.ident, 0, sizeof(md5tr.ident)); + + md5identmatch = crc32(0L, Z_NULL, 0); + md5identmatch = crc32(md5identmatch, (const unsigned char*) &md5tr, sizeof(md5tr)); + md5identmatch = crc32(md5identmatch, (const unsigned char*) &md5fnsize, sizeof(md5fnsize)); + + if (md5identmatch == md5ident) { + adblogwrite("checking MD5TRAILER\n"); + md5trcrc = md5tr.crc; + memset(&md5tr.crc, 0, sizeof(md5tr.crc)); + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (const unsigned char*) &md5tr, sizeof(md5tr)); + if (crc == md5trcrc) { + if (write(adb_control_twrp_fd, &md5tr, sizeof(md5tr)) < 1) { + std::string msg = "Cannot write to adb_control_twrp_fd: "; + printErrMsg(msg, errno); + close_restore_fds(); + return false; + } + } + else { + adblogwrite("ADB MD5TRAILER crc header doesn't match\n"); + close_restore_fds(); + return false; + } + + AdbBackupFileTrailer md5; + + memset(&md5, 0, sizeof(md5)); + strncpy(md5.start_of_trailer, TWRP, sizeof(md5.start_of_trailer)); + strncpy(md5.type, TWMD5, sizeof(md5.type)); + std::string md5string = digest->return_digest_string(); + strncpy(md5.md5, md5string.c_str(), sizeof(md5.md5)); + + adblogwrite("sending MD5 verification: " + md5string + "\n"); + if (write(adb_control_twrp_fd, &md5, sizeof(md5)) < 1) { + std::string msg = "Cannot write to adb_control_twrp_fd: "; + printErrMsg(msg, errno); + close_restore_fds(); + return false; + } + return true; } - unlink(TW_ADB_TWRP_CONTROL); - return ret; + return false; } diff --git a/adbbu/twrpback.hpp b/adbbu/twrpback.hpp index 752d35ea12..edc1626512 100644 --- a/adbbu/twrpback.hpp +++ b/adbbu/twrpback.hpp @@ -1,5 +1,5 @@ /* - Copyright 2013 to 2016 TeamWin + Copyright 2013 to 2017 TeamWin TWRP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -14,23 +14,25 @@ along with TWRP. If not, see . */ -#include +#ifndef _TWRPBACK_HPP +#define _TWRPBACK_HPP -#include "../orscmd/orscmd.h" -#include "../variables.h" -#include "../twcommon.h" +#include +#include "../twrpDigest/twrpMD5.hpp" class twrpback { public: int adbd_fd; // adbd data stream - twrpback(void); virtual ~twrpback(void); - int backup(std::string command); // adb backup stream - int restore(void); // adb restore stream + bool backup(std::string command); // adb backup stream + bool restore(void); // adb restore stream void adblogwrite(std::string writemsg); // adb debugging log function - void close_backup_fds(); // close backup resources - void close_restore_fds(); // close restore resources + void createFifos(void); // create fifos needed for adb backup + void closeFifos(void); // close created fifos + void streamFileForTWRP(void); // stream file to twrp via bu + void setStreamFileName(std::string fn); // tell adb backup what file to load on storage + void threadStream(void); // thread bu for streaming private: int read_fd; // ors input fd @@ -40,10 +42,20 @@ class twrpback { int adb_control_bu_fd; // fd for twrp to bu communication int adb_read_fd; // adb read data stream int adb_write_fd; // adb write data stream + int debug_adb_fd; // fd to write debug tars bool firstPart; // first partition in the stream FILE *adbd_fp; // file pointer for adb stream char cmd[512]; // store result of commands char operation[512]; // operation to send to ors std::ofstream adblogfile; // adb stream log file + std::string streamFn; + typedef void (twrpback::*ThreadPtr)(void); + typedef void* (*PThreadPtr)(void *); void adbloginit(void); // setup adb log stream file + void close_backup_fds(); // close backup resources + void close_restore_fds(); // close restore resources + bool checkMD5Trailer(char adbReadStream[], uint64_t md5fnsize, twrpMD5* digest); // Check MD5 Trailer + void printErrMsg(std::string msg, int errNum); // print error msg to adb log }; + +#endif // _TWRPBACK_HPP diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 2f96f0ab79..1f71c44652 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -14,13 +14,31 @@ LOCAL_PATH := $(call my-dir) -ifdef project-path-for - RECOVERY_PATH := $(call project-path-for,recovery) -else - RECOVERY_PATH := bootable/recovery -endif - +# libapplypatch (static library) +# =============================== include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + applypatch.cpp \ + bspatch.cpp \ + freecache.cpp \ + imgpatch.cpp +LOCAL_MODULE := libapplypatch +LOCAL_MODULE_TAGS := eng +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + $(commands_recovery_local_path) +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_STATIC_LIBRARIES := \ + libotafault \ + libbase \ + libcrypto \ + libbspatch \ + libbz \ + libz +LOCAL_WHOLE_STATIC_LIBRARIES += libmtdutils +LOCAL_CFLAGS := \ + -DZLIB_CONST \ + -Werror BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY @@ -30,55 +48,145 @@ $(foreach board_define,$(BOARD_RECOVERY_DEFINES), \ ) \ ) -LOCAL_C_INCLUDES += \ - external/bzip2 \ - external/zlib \ - $(commands_recovery_local_path) - -LOCAL_CLANG := true -LOCAL_SRC_FILES := applypatch.cpp bspatch.cpp freecache.cpp imgpatch.cpp utils.cpp -LOCAL_MODULE := libapplypatch -LOCAL_MODULE_TAGS := eng -LOCAL_C_INCLUDES += $(RECOVERY_PATH) -LOCAL_STATIC_LIBRARIES += libbase libotafault libmtdutils libcrypto_static libbz libz - include $(BUILD_STATIC_LIBRARY) +# libimgpatch (static library) +# =============================== include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + bspatch.cpp \ + imgpatch.cpp +LOCAL_MODULE := libimgpatch +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + $(commands_recovery_local_path) +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +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 += $(RECOVERY_PATH) +LOCAL_MODULE_HOST_OS := linux +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + $(commands_recovery_local_path) 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 := $(commands_recovery_local_path) +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 += $(RECOVERY_PATH) -LOCAL_STATIC_LIBRARIES += libapplypatch libbase libotafault libmtdutils libcrypto_static libbz \ - libedify \ - -LOCAL_SHARED_LIBRARIES += libz libcutils libc - +LOCAL_C_INCLUDES := $(commands_recovery_local_path) +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) + +# 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) -LOCAL_CLANG := true -LOCAL_SRC_FILES := imgdiff.cpp utils.cpp bsdiff.cpp +# imgdiff (host static executable) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := imgdiff_main.cpp LOCAL_MODULE := imgdiff -LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_C_INCLUDES += external/zlib external/bzip2 -LOCAL_STATIC_LIBRARIES += libz libbz - +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 0000000000..fb4984303b --- /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 c2fe11b3b7..43e9b80dca 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "applypatch/applypatch.h" + #include #include #include @@ -25,74 +27,68 @@ #include #include +#include #include #include +#include +#include +#include +#include #include +#include -#include "openssl/sha.h" -#include "applypatch.h" #include "bmlutils/bmlutils.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 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 int LoadPartitionContents(const std::string& filename, FileContents* file); +static size_t FileSink(const unsigned char* data, size_t len, int fd); +static int GenerateTarget(const FileContents& source_file, const std::unique_ptr& patch, + const std::string& target_filename, + const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data); static bool mtd_partitions_scanned = false; -// Read a file into memory; store the file contents and associated -// metadata in *file. -// +// 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 || - strncmp(filename, "BML:", 4) == 0) { - return LoadPartitionContents(filename, file); - } - - if (stat(filename, &file->st) != 0) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return -1; - } + // 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 || + strncmp(filename, "BML:", 4) == 0) { + return LoadPartitionContents(filename, file); + } + + if (stat(filename, &file->st) == -1) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return -1; + } - std::vector 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 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::::::..." (or -// "EMMC::..."). The smallest size_n bytes for +// "EMMC::...". 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. @@ -106,223 +102,166 @@ int LoadFileContents(const char* filename, FileContents* file) { // to find one of those hashes. enum PartitionType { MTD, EMMC }; -static int LoadPartitionContents(const char* filename, FileContents* file) { - std::string copy(filename); - std::vector 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 if (pieces[0] == "BML") { - type = EMMC; - } else { - printf("LoadPartitionContents called with bad filename (%s)\n", filename); +static int LoadPartitionContents(const std::string& filename, FileContents* file) { + std::vector pieces = android::base::Split(filename, ":"); + if (pieces.size() < 4 || pieces.size() % 2 != 0) { + printf("LoadPartitionContents called with bad filename \"%s\"\n", filename.c_str()); + return -1; + } + + enum PartitionType type; + if (pieces[0] == "MTD") { + type = MTD; + } else if (pieces[0] == "EMMC") { + type = EMMC; + } else if (pieces[0] == "BML") { + type = EMMC; + } else { + 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> 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 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(); if (pieces[0] == "BML") { - if (strcmp(partition, "boot") == 0) { - partition = BOARD_BML_BOOT; - } else if (strcmp(partition, "recovery") == 0) { - partition = BOARD_BML_RECOVERY; - } + if (strcmp(partition, "boot") == 0) { + partition = BOARD_BML_BOOT; + } else if (strcmp(partition, "recovery") == 0) { + partition = BOARD_BML_RECOVERY; + } } - size_t pairs = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename - std::vector index(pairs); - std::vector size(pairs); - std::vector sha1sum(pairs); - - 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; - } - - // 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; - } - } + // 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); - 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 data(size[index[pairs-1]]); - char* p = reinterpret_cast(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 (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; } - switch (type) { - case MTD: - mtd_read_close(ctx); - break; - - case EMMC: - ota_fclose(dev); - break; + 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; } + } + 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; + } - 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; - } - - SHA1_Final(file->sha1, &sha_ctx); + SHA1_Final(file->sha1, &sha_ctx); - 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; + 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; + return 0; } - // 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)); - return -1; - } + 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; + } - ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); - if (bytes_written != static_cast(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; - } - if (ota_close(fd) != 0) { - printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } + size_t bytes_written = FileSink(file->data.data(), file->data.size(), fd); + if (bytes_written != 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; + } - 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; - } + 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 -// "MTD:[:...]" or "EMMC:[:...]". The target name +// "EMMC:[:...]". 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) { +int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) { std::string copy(target); std::vector pieces = android::base::Split(copy, ":"); if (pieces.size() < 2) { - printf("WriteToPartition called with bad target (%s)\n", target); + printf("WriteToPartition called with bad target (%s)\n", target.c_str()); return -1; } @@ -334,7 +273,7 @@ int WriteToPartition(const unsigned char* data, size_t len, const char* target) } else if (pieces[0] == "BML") { type = EMMC; } else { - printf("WriteToPartition called with bad target (%s)\n", target); + printf("WriteToPartition called with bad target (%s)\n", target.c_str()); return -1; } @@ -358,7 +297,7 @@ int WriteToPartition(const unsigned char* data, size_t len, const char* target) } if (partition == NULL) { - printf("bad partition target name \"%s\"\n", target); + printf("bad partition target name \"%s\"\n", target.c_str()); return -1; } @@ -404,7 +343,7 @@ int WriteToPartition(const unsigned char* data, size_t len, const char* target) case EMMC: { size_t start = 0; bool success = false; - int fd = ota_open(partition, O_RDWR | O_SYNC); + unique_fd fd(ota_open(partition, O_RDWR | O_SYNC)); if (fd < 0) { printf("failed to open %s: %s\n", partition, strerror(errno)); return -1; @@ -434,7 +373,7 @@ int WriteToPartition(const unsigned char* data, size_t len, const char* target) printf("failed to close %s (%s)\n", partition, strerror(errno)); return -1; } - fd = ota_open(partition, O_RDONLY); + unique_fd fd(ota_open(partition, O_RDONLY)); if (fd < 0) { printf("failed to reopen %s for verify (%s)\n", partition, strerror(errno)); return -1; @@ -443,7 +382,7 @@ int WriteToPartition(const unsigned char* data, size_t len, const char* target) // 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); + 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 { @@ -513,7 +452,6 @@ int WriteToPartition(const unsigned char* data, size_t len, const char* target) 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 // the form ":". Return 0 on success, -1 on any @@ -546,52 +484,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& 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& 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() { @@ -599,25 +532,17 @@ int ShowLicenses() { return 0; } -ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) { - int fd = *static_cast(token); - ssize_t done = 0; - ssize_t wrote; - while (done < len) { - wrote = TEMP_FAILURE_RETRY(ota_write(fd, data+done, len-done)); - if (wrote == -1) { - printf("error writing %zd bytes: %s\n", (len-done), strerror(errno)); - return done; - } - done += wrote; - } - return done; -} - -ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { - std::string* s = static_cast(token); - s->append(reinterpret_cast(data), len); - return len; +static size_t FileSink(const unsigned char* data, size_t len, int fd) { + size_t done = 0; + while (done < len) { + ssize_t wrote = TEMP_FAILURE_RETRY(ota_write(fd, data + done, len - done)); + if (wrote == -1) { + printf("error writing %zd bytes: %s\n", (len - done), strerror(errno)); + return done; + } + done += wrote; + } + return done; } // Return the amount of free space (in bytes) on the filesystem @@ -633,116 +558,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 is , -// does nothing and exits successfully. +// - If the SHA-1 hash of is , does nothing and exits +// successfully. // -// - otherwise, if the sha1 hash of is one of the -// entries in , the corresponding patch from -// (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 , -// moves it to replace , and exits successfully. -// Note that if and are not the -// same, is NOT deleted on success. -// may be the string "-" to mean "the same as -// source_filename". +// - Otherwise, if the SHA-1 hash of is one of the entries in , +// the corresponding patch from (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 , moves it to replace , and exits +// successfully. Note that if and are not the same, +// is NOT deleted on success. may be the string "-" to mean +// "the same as ". // -// - otherwise, or if any error is encountered, exits with non-zero -// status. +// - Otherwise, or if any error is encountered, exits with non-zero status. // -// 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); +// 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. 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& patch_sha1_str, + const std::vector>& 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); } /* @@ -754,281 +660,126 @@ 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 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 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 || - strncmp(target_filename, "BML:", 4) == 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 || - strncmp(source_filename, "BML:", 4) == 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 ".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(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& 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. + std::string memory_sink_str; // Don't need to reserve space. + SinkFn sink = [&memory_sink_str](const unsigned char* data, size_t len) { + memory_sink_str.append(reinterpret_cast(data), len); + return len; + }; + + 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, &ctx); + } else { + result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink, + &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(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/applypatch.sh b/applypatch/applypatch.sh deleted file mode 100755 index 8ea68a1a9f..0000000000 --- a/applypatch/applypatch.sh +++ /dev/null @@ -1,350 +0,0 @@ -#!/bin/bash -# -# A test suite for applypatch. Run in a client where you have done -# envsetup, choosecombo, etc. -# -# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your -# system partition. -# -# -# TODO: find some way to get this run regularly along with the rest of -# the tests. - -EMULATOR_PORT=5580 -DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/applypatch/testdata - -# This must be the filename that applypatch uses for its copies. -CACHE_TEMP_SOURCE=/cache/saved.file - -# Put all binaries and files here. We use /cache because it's a -# temporary filesystem in the emulator; it's created fresh each time -# the emulator starts. -WORK_DIR=/system - -# partition that WORK_DIR is located on, without the leading slash -WORK_FS=system - -# set to 0 to use a device instead -USE_EMULATOR=1 - -# ------------------------ - -tmpdir=$(mktemp -d) - -if [ "$USE_EMULATOR" == 1 ]; then - emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & - pid_emulator=$! - ADB="adb -s emulator-$EMULATOR_PORT " -else - ADB="adb -d " -fi - -echo "waiting to connect to device" -$ADB wait-for-device -echo "device is available" -$ADB remount -# free up enough space on the system partition for the test to run. -$ADB shell rm -r /system/media - -# run a command on the device; exit with the exit status of the device -# command. -run_command() { - $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' -} - -testname() { - echo - echo "$1"... - testname="$1" -} - -fail() { - echo - echo FAIL: $testname - echo - [ "$open_pid" == "" ] || kill $open_pid - [ "$pid_emulator" == "" ] || kill $pid_emulator - exit 1 -} - -sha1() { - sha1sum $1 | awk '{print $1}' -} - -free_space() { - run_command df | awk "/$1/ {print gensub(/K/, \"\", \"g\", \$6)}" -} - -cleanup() { - # not necessary if we're about to kill the emulator, but nice for - # running on real devices or already-running emulators. - testname "removing test files" - run_command rm $WORK_DIR/bloat.dat - run_command rm $WORK_DIR/old.file - run_command rm $WORK_DIR/foo - run_command rm $WORK_DIR/patch.bsdiff - run_command rm $WORK_DIR/applypatch - run_command rm $CACHE_TEMP_SOURCE - run_command rm /cache/bloat*.dat - - [ "$pid_emulator" == "" ] || kill $pid_emulator - - if [ $# == 0 ]; then - rm -rf $tmpdir - fi -} - -cleanup leave_tmp - -$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch - -BAD1_SHA1=$(printf "%040x" $RANDOM) -BAD2_SHA1=$(printf "%040x" $RANDOM) -OLD_SHA1=$(sha1 $DATA_DIR/old.file) -NEW_SHA1=$(sha1 $DATA_DIR/new.file) -NEW_SIZE=$(stat -c %s $DATA_DIR/new.file) - -# --------------- basic execution ---------------------- - -testname "usage message" -run_command $WORK_DIR/applypatch && fail - -testname "display license" -run_command $WORK_DIR/applypatch -l | grep -q -i copyright || fail - - -# --------------- check mode ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR - -testname "check mode single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "check mode cache (corrupted) single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode cache (corrupted) multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode cache (corrupted) failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - -# remove the old file entirely -run_command rm $WORK_DIR/old.file - -testname "check mode cache (missing) single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode cache (missing) multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode cache (missing) failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - - -# --------------- apply patch ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR -echo hello > $tmpdir/foo -$ADB push $tmpdir/foo $WORK_DIR - -# Check that the partition has enough space to apply the patch without -# copying. If it doesn't, we'll be testing the low-space condition -# when we intend to test the not-low-space condition. -testname "apply patches (with enough space)" -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS." -if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then - echo "Not enough space on /$WORK_FS to patch test file." - echo - echo "This doesn't mean that applypatch is necessarily broken;" - echo "just that /$WORK_FS doesn't have enough free space to" - echo "properly run this test." - exit 1 -fi - -testname "apply bsdiff patch" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - - -# --------------- apply patch in new location ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -# Check that the partition has enough space to apply the patch without -# copying. If it doesn't, we'll be testing the low-space condition -# when we intend to test the not-low-space condition. -testname "apply patch to new location (with enough space)" -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS." -if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then - echo "Not enough space on /$WORK_FS to patch test file." - echo - echo "This doesn't mean that applypatch is necessarily broken;" - echo "just that /$WORK_FS doesn't have enough free space to" - echo "properly run this test." - exit 1 -fi - -run_command rm $WORK_DIR/new.file -run_command rm $CACHE_TEMP_SOURCE - -testname "apply bsdiff patch to new location" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch to new location" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "apply bsdiff patch to new location with corrupted source" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -# put some junk in the cache copy, too -run_command dd if=/dev/urandom of=$CACHE_TEMP_SOURCE count=100 bs=1024 || fail - -run_command rm $WORK_DIR/new.file -testname "apply bsdiff patch to new location with corrupted source and copy (no new file)" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail - -# put some junk in the new file -run_command dd if=/dev/urandom of=$WORK_DIR/new.file count=100 bs=1024 || fail - -testname "apply bsdiff patch to new location with corrupted source and copy (bad new file)" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail - -# --------------- apply patch with low space on /system ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS; we'll soon fix that." -echo run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail -run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS now." - -testname "apply bsdiff patch with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -# --------------- apply patch with low space on /system and /cache ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS" - -run_command mkdir /cache/subdir -run_command 'echo > /cache/subdir/a.file' -run_command 'echo > /cache/a.file' -run_command mkdir /cache/recovery /cache/recovery/otatest -run_command 'echo > /cache/recovery/otatest/b.file' -run_command "echo > $CACHE_TEMP_SOURCE" -free_kb=$(free_space cache) -echo "${free_kb}kb free on /cache; we'll soon fix that." -run_command dd if=/dev/zero of=/cache/bloat_small.dat count=128 bs=1024 || fail -run_command dd if=/dev/zero of=/cache/bloat_large.dat count=$((free_kb-640)) bs=1024 || fail -free_kb=$(free_space cache) -echo "${free_kb}kb free on /cache now." - -testname "apply bsdiff patch with low space, full cache, can't delete enough" -$ADB shell 'cat >> /cache/bloat_large.dat' & open_pid=$! -echo "open_pid is $open_pid" - -# size check should fail even though it deletes some stuff -run_command $WORK_DIR/applypatch -s $NEW_SIZE && fail -run_command ls /cache/bloat_small.dat && fail # was deleted -run_command ls /cache/a.file && fail # was deleted -run_command ls /cache/recovery/otatest/b.file && fail # was deleted -run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open -run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -# should fail; not enough files can be deleted -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff && fail -run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open -run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -kill $open_pid # /cache/bloat_large.dat is no longer open - -testname "apply bsdiff patch with low space, full cache, can delete enough" - -# should succeed after deleting /cache/bloat_large.dat -run_command $WORK_DIR/applypatch -s $NEW_SIZE || fail -run_command ls /cache/bloat_large.dat && fail # was deleted -run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -# should succeed -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail -run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE && fail # was deleted because patching overwrote it, then deleted it - -# --------------- apply patch from cache ---------------------- - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "apply bsdiff patch from cache (corrupted source) with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# remove the old file entirely -run_command rm $WORK_DIR/old.file - -testname "apply bsdiff patch from cache (missing source) with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - - -# --------------- cleanup ---------------------- - -cleanup - -echo -echo PASS -echo - diff --git a/applypatch/applypatch_main.cpp b/applypatch/applypatch_main.cpp new file mode 100644 index 0000000000..197077c938 --- /dev/null +++ b/applypatch/applypatch_main.cpp @@ -0,0 +1,28 @@ +/* + * 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_modes.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. + +int main(int argc, char** argv) { + return applypatch_modes(argc, const_cast(argv)); +} diff --git a/applypatch/main.cpp b/applypatch/applypatch_modes.cpp similarity index 61% rename from applypatch/main.cpp rename to applypatch/applypatch_modes.cpp index 9013760c4a..aa32d57ef4 100644 --- a/applypatch/main.cpp +++ b/applypatch/applypatch_modes.cpp @@ -14,61 +14,59 @@ * limitations under the License. */ +#include "applypatch_modes.h" + #include #include #include #include #include +#include #include -#include "applypatch.h" +#include +#include +#include + +#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); -} - -static int SpaceMode(int argc, char** argv) { - if (argc != 3) { - return 2; - } - char* endptr; - size_t bytes = strtol(argv[2], &endptr, 10); - if (bytes == 0 && endptr == argv[2]) { - printf("can't parse \"%s\" as byte count\n\n", argv[2]); - return 1; + std::vector sha1; + for (int i = 3; i < argc; i++) { + sha1.push_back(argv[i]); } - return CacheSizeCheck(bytes); + + return applypatch_check(argv[2], sha1); } -// Parse arguments (which should be of the form ":" -// into the new parallel arrays *sha1s and *files.Returns true on -// success. -static bool ParsePatchArgs(int argc, char** argv, std::vector* sha1s, +// Parse arguments (which should be of the form ":" into the +// new parallel arrays *sha1s and *files. Returns true on success. +static bool ParsePatchArgs(int argc, const char** argv, std::vector* sha1s, std::vector* 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 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 +79,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(bonusFc.data.data()); + bonus.type = VAL_BLOB; + bonus.data = std::string(bonusFc.data.cbegin(), bonusFc.data.cend()); argc -= 2; argv += 2; } @@ -103,42 +98,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 : 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 sha1s; + + std::vector sha1s; std::vector files; if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) { printf("failed to parse patch args\n"); return 1; } - std::vector patches(files.size()); - std::vector patch_ptrs(files.size()); + + std::vector> 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(files[i].data.data()); - patch_ptrs[i] = &patches[i]; + patches.push_back(std::make_unique( + 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,24 +151,23 @@ static int PatchMode(int argc, char** argv) { // - otherwise, or if any error is encountered, exits with non-zero // status. // -// (or in check mode) may refer to an MTD partition +// (or 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( "usage: %s [-b ] " "[: ...]\n" " or %s -c [ ...]\n" - " or %s -s \n" " or %s -l\n" "\n" "Filenames may be of the form\n" - " MTD::::::...\n" - "to specify reading from or writing to an MTD partition.\n\n", - argv[0], argv[0], argv[0], argv[0]); + " EMMC::::::...\n" + "to specify reading from or writing to an EMMC partition.\n\n", + argv[0], argv[0], argv[0]); return 2; } @@ -187,8 +177,6 @@ int main(int argc, char** argv) { result = ShowLicenses(); } else if (strncmp(argv[1], "-c", 3) == 0) { result = CheckMode(argc, argv); - } else if (strncmp(argv[1], "-s", 3) == 0) { - result = SpaceMode(argc, argv); } else { result = PatchMode(argc, argv); } diff --git a/applypatch/applypatch_modes.h b/applypatch/applypatch_modes.h new file mode 100644 index 0000000000..3d9d08df57 --- /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 55dbe5cf1a..0000000000 --- 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 - -#include -#include -#include -#include -#include -#include -#include - -#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;kstart) split(I,V,start,jj-start,h); - - for(i=0;ikk) 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;i0;i--) buckets[i]=buckets[i-1]; - buckets[0]=0; - - for(i=0;iy) { - *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(malloc((oldsize+1) * sizeof(off_t))); - V = reinterpret_cast(malloc((oldsize+1) * sizeof(off_t))); - qsufsort(*IP, V, old, oldsize); - free(V); - } - I = *IP; - - if(((db=reinterpret_cast(malloc(newsize+1)))==NULL) || - ((eb=reinterpret_cast(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(scanoldscore+8)) break; - - if((scan+lastoffsetSf*2-lenf) { Sf=s; lenf=i; }; - }; - - lenb=0; - if(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;iSs) { Ss=s; lens=i+1; }; - }; - - lenf+=lens-overlap; - lenb-=lens; - }; - - for(i=0;i -#include #include -#include -#include -#include -#include +#include -#include "openssl/sha.h" -#include "applypatch.h" +#include +#include +#include + +#include "applypatch/applypatch.h" +#include "print_sha1.h" void ShowBSDiffLicense() { puts("The bsdiff library used herein is:\n" @@ -64,184 +64,31 @@ 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 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(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 ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - std::vector* 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; +int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch, + size_t patch_offset, SinkFn sink, SHA_CTX* ctx) { + auto sha_sink = [&sink, &ctx](const uint8_t* data, size_t len) { + len = sink(data, len); + if (ctx) SHA1_Update(ctx, data, len); + return len; + }; + + CHECK(patch != nullptr); + CHECK_LE(patch_offset, patch->data.size()); + + int result = bsdiff::bspatch(old_data, old_size, + reinterpret_cast(&patch->data[patch_offset]), + patch->data.size() - patch_offset, sha_sink); + if (result != 0) { + LOG(ERROR) << "bspatch failed, result: " << result; + // print SHA1 of the patch in the case of a data error. + if (result == 2) { + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast(patch->data.data() + patch_offset), + patch->data.size() - patch_offset, digest); + std::string patch_sha1 = print_sha1(digest); + LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: " + << patch_sha1; } - - 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; -} + } + return result; +} \ No newline at end of file diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp index c84f42797d..331cae265f 100644 --- a/applypatch/freecache.cpp +++ b/applypatch/freecache.cpp @@ -32,7 +32,7 @@ #include #include -#include "applypatch.h" +#include "applypatch/applypatch.h" static int EliminateOpenFiles(std::set* files) { std::unique_ptr d(opendir("/proc"), closedir); diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp index f22502e381..fc240644f2 100644 --- a/applypatch/imgdiff.cpp +++ b/applypatch/imgdiff.cpp @@ -121,618 +121,690 @@ * information that is stored on the system partition. */ +#include "applypatch/imgdiff.h" + #include -#include +#include #include #include #include #include -#include #include +#include -#include "zlib.h" -#include "imgdiff.h" -#include "utils.h" +#include +#include +#include -typedef struct { - int type; // CHUNK_NORMAL, CHUNK_DEFLATE - size_t start; // offset of chunk in original image file +#include +#include +#include +#include +#include - size_t len; - unsigned char* data; // data to be patched (uncompressed, for deflate chunks) +#include +#include - 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* 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 data); + bool SetBonusData(const std::vector& 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& 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* input_file_ptr_; // ptr to the full content of original input file + size_t raw_data_len_; + + // --- for CHUNK_DEFLATE chunks only: --- + std::vector 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(st.st_size); - unsigned char* img = reinterpret_cast(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 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& 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(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& 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(source_start_)); + Write8(fd, static_cast(source_len_)); + Write8(fd, static_cast(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(source_start_)); + Write8(fd, static_cast(source_len_)); + Write8(fd, static_cast(offset)); + Write8(fd, static_cast(source_uncompressed_len_)); + Write8(fd, static_cast(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(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(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; } + } - unsigned char* lh = img + hoffset; + return false; +} + +/* + * 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 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& 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(&zip_file[i]) == 0x06054b50) { + // double-check: this archive consists of a single "disk". + CHECK_EQ(get_unaligned(&zip_file[i + 4]), 0); + + uint16_t comment_length = get_unaligned(&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(malloc((entrycount*2+2) * sizeof(ImageChunk))); - ImageChunk* curr = *chunks; + // EOCD not found, this file is likely not a valid zip file. + return false; +} - 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; +static bool ReadZip(const char* filename, std::vector* chunks, + std::vector* zip_file, bool include_pseudo_chunk) { + CHECK(chunks != nullptr && zip_file != 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 (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(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(malloc(curr->len)); + // Create a list of deflated zip entries, sorted by offset. + std::vector> 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; + }); + + EndIteration(cookie); - 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; + 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(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 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* chunks, + std::vector* 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(st.st_size); - unsigned char* img = reinterpret_cast(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(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(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(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 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(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; + } - curr->type = CHUNK_NORMAL; - curr->start = pos; - curr->len = GZIP_FOOTER_LEN; - curr->data = img+pos; - curr->I = NULL; - - pos += curr->len; - p += curr->len; - ++curr; - - // 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; + // The footer 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_index = pos + raw_data_len + GZIP_FOOTER_LEN - 4; + if (sz - footer_index < 4) { + printf("Warning: invalid footer position; treating as a nomal chunk\n"); + continue; } - } 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(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) { - break; - } + size_t footer_size = get_unaligned(img->data() + footer_index); + if (footer_size != uncompressed_len) { + printf("Warning: footer size %zu != decompressed size %zu; treating as a nomal chunk\n", + footer_size, uncompressed_len); + continue; } - pos += curr->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; + ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len); + uncompressed_data.resize(uncompressed_len); + body.SetUncompressedData(std::move(uncompressed_data)); + chunks->push_back(body); - if (memcmp(out, chunk->deflate_data+p, have) != 0) { - // mismatch; data isn't the same. - deflateEnd(&strm); - return -1; - } - 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; -} + pos += raw_data_len; -/* - * 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; - } + // create a normal chunk for the footer + chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN); - size_t p = 0; - unsigned char* out = reinterpret_cast(malloc(BUFFER_SIZE)); + pos += GZIP_FOOTER_LEN; + } else { + // 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(img->data() + pos + data_len) == 0x00088b1f) { + break; + } + data_len++; + } + chunks->emplace_back(CHUNK_NORMAL, pos, img, data_len); - // 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; + pos += data_len; } } - 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* 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(st.st_size); - // TODO: Memory leak on error return. - unsigned char* data = reinterpret_cast(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_t patch_size = tgt->DataLengthForPatch(); + patch_data->resize(patch_size); + std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin()); + return true; } - - *size = sz; - - FILE* f = fopen(ptemp, "rb"); - if (f == NULL) { - printf("failed to open 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; } - if (fread(data, 1, sz, f) != sz) { - printf("failed to read patch %s: %s\n", ptemp, strerror(errno)); - return NULL; - } - 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 +812,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* 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& 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& 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 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(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 ] \n", argv[0]); return 2; } - int num_src_chunks; - ImageChunk* src_chunks; - int num_tgt_chunks; - ImageChunk* tgt_chunks; - int i; + std::vector src_chunks; + std::vector tgt_chunks; + std::vector src_file; + std::vector 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 +916,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 +969,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 +988,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 +1006,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(malloc( - num_tgt_chunks * sizeof(unsigned char*))); - size_t* patch_size = reinterpret_cast(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> 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(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 +1050,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(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 0000000000..7d5bdf9aae --- /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(argv)); +} diff --git a/applypatch/imgdiff_test.sh b/applypatch/imgdiff_test.sh deleted file mode 100755 index dcdb922b46..0000000000 --- a/applypatch/imgdiff_test.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash -# -# A script for testing imgdiff/applypatch. It takes two full OTA -# packages as arguments. It generates (on the host) patches for all -# the zip/jar/apk files they have in common, as well as boot and -# recovery images. It then applies the patches on the device (or -# emulator) and checks that the resulting file is correct. - -EMULATOR_PORT=5580 - -# set to 0 to use a device instead -USE_EMULATOR=0 - -# where on the device to do all the patching. -WORK_DIR=/data/local/tmp - -START_OTA_PACKAGE=$1 -END_OTA_PACKAGE=$2 - -# ------------------------ - -tmpdir=$(mktemp -d) - -if [ "$USE_EMULATOR" == 1 ]; then - emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & - pid_emulator=$! - ADB="adb -s emulator-$EMULATOR_PORT " -else - ADB="adb -d " -fi - -echo "waiting to connect to device" -$ADB wait-for-device - -# run a command on the device; exit with the exit status of the device -# command. -run_command() { - $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' -} - -testname() { - echo - echo "$1"... - testname="$1" -} - -fail() { - echo - echo FAIL: $testname - echo - [ "$open_pid" == "" ] || kill $open_pid - [ "$pid_emulator" == "" ] || kill $pid_emulator - exit 1 -} - -sha1() { - sha1sum $1 | awk '{print $1}' -} - -size() { - stat -c %s $1 | tr -d '\n' -} - -cleanup() { - # not necessary if we're about to kill the emulator, but nice for - # running on real devices or already-running emulators. - testname "removing test files" - run_command rm $WORK_DIR/applypatch - run_command rm $WORK_DIR/source - run_command rm $WORK_DIR/target - run_command rm $WORK_DIR/patch - - [ "$pid_emulator" == "" ] || kill $pid_emulator - - rm -rf $tmpdir -} - -$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch - -patch_and_apply() { - local fn=$1 - shift - - unzip -p $START_OTA_PACKAGE $fn > $tmpdir/source - unzip -p $END_OTA_PACKAGE $fn > $tmpdir/target - imgdiff "$@" $tmpdir/source $tmpdir/target $tmpdir/patch - bsdiff $tmpdir/source $tmpdir/target $tmpdir/patch.bs - echo "patch for $fn is $(size $tmpdir/patch) [of $(size $tmpdir/target)] ($(size $tmpdir/patch.bs) with bsdiff)" - echo "$fn $(size $tmpdir/patch) of $(size $tmpdir/target) bsdiff $(size $tmpdir/patch.bs)" >> /tmp/stats.txt - $ADB push $tmpdir/source $WORK_DIR/source || fail "source push failed" - run_command rm /data/local/tmp/target - $ADB push $tmpdir/patch $WORK_DIR/patch || fail "patch push failed" - run_command /data/local/tmp/applypatch /data/local/tmp/source \ - /data/local/tmp/target $(sha1 $tmpdir/target) $(size $tmpdir/target) \ - $(sha1 $tmpdir/source):/data/local/tmp/patch \ - || fail "applypatch of $fn failed" - $ADB pull /data/local/tmp/target $tmpdir/result - diff -q $tmpdir/target $tmpdir/result || fail "patch output not correct!" -} - -# --------------- basic execution ---------------------- - -for i in $((zipinfo -1 $START_OTA_PACKAGE; zipinfo -1 $END_OTA_PACKAGE) | \ - sort | uniq -d | egrep -e '[.](apk|jar|zip)$'); do - patch_and_apply $i -z -done -patch_and_apply boot.img -patch_and_apply system/recovery.img - - -# --------------- cleanup ---------------------- - -cleanup - -echo -echo PASS -echo - diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp index d175d63850..df75f98d49 100644 --- a/applypatch/imgpatch.cpp +++ b/applypatch/imgpatch.cpp @@ -14,31 +14,122 @@ * 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 + +#include #include +#include #include #include -#include #include -#include +#include +#include #include -#include "zlib.h" -#include "openssl/sha.h" -#include "applypatch.h" -#include "imgdiff.h" -#include "utils.h" - -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(const_cast(patch_data))}; - return ApplyImagePatch( - old_data, old_size, &patch, sink, token, nullptr, nullptr); +#include +#include +#include +#include +#include +#include + +static inline int64_t Read8(const void *address) { + return android::base::get_unaligned(address); +} + +static inline int32_t Read4(const void *address) { + return android::base::get_unaligned(address); +} + +// This function is a wrapper of ApplyBSDiffPatch(). It has a custom sink function to deflate the +// patched data and stream the deflated data to output. +static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_len, + const Value* patch, size_t patch_offset, + const char* deflate_header, SinkFn sink, SHA_CTX* ctx) { + size_t expected_target_length = static_cast(Read8(deflate_header + 32)); + int level = Read4(deflate_header + 40); + int method = Read4(deflate_header + 44); + int window_bits = Read4(deflate_header + 48); + int mem_level = Read4(deflate_header + 52); + int strategy = Read4(deflate_header + 56); + + std::unique_ptr strm(new z_stream(), deflateEnd); + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + strm->avail_in = 0; + strm->next_in = nullptr; + int ret = deflateInit2(strm.get(), level, method, window_bits, mem_level, strategy); + if (ret != Z_OK) { + LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret; + return false; + } + + // Define a custom sink wrapper that feeds to bspatch. It deflates the available patch data on + // the fly and outputs the compressed data to the given sink. + size_t actual_target_length = 0; + size_t total_written = 0; + static constexpr size_t buffer_size = 32768; + auto compression_sink = [&](const uint8_t* data, size_t len) -> size_t { + // The input patch length for an update never exceeds INT_MAX. + strm->avail_in = len; + strm->next_in = data; + do { + std::vector buffer(buffer_size); + strm->avail_out = buffer_size; + strm->next_out = buffer.data(); + if (actual_target_length + len < expected_target_length) { + ret = deflate(strm.get(), Z_NO_FLUSH); + } else { + ret = deflate(strm.get(), Z_FINISH); + } + if (ret != Z_OK && ret != Z_STREAM_END) { + LOG(ERROR) << "Failed to deflate stream: " << ret; + // zero length indicates an error in the sink function of bspatch(). + return 0; + } + + size_t have = buffer_size - strm->avail_out; + total_written += have; + if (sink(buffer.data(), have) != have) { + LOG(ERROR) << "Failed to write " << have << " compressed bytes to output."; + return 0; + } + if (ctx) SHA1_Update(ctx, buffer.data(), have); + } while ((strm->avail_in != 0 || strm->avail_out == 0) && ret != Z_STREAM_END); + + actual_target_length += len; + return len; + }; + + if (ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr) != 0) { + return false; + } + + if (ret != Z_STREAM_END) { + LOG(ERROR) << "ret is expected to be Z_STREAM_END, but it's " << ret; + return false; + } + + if (expected_target_length != actual_target_length) { + LOG(ERROR) << "target length is expected to be " << expected_target_length << ", but it's " + << actual_target_length; + return false; + } + LOG(DEBUG) << "bspatch writes " << total_written << " bytes in total to streaming output."; + + return true; +} + +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data, + size_t patch_size, SinkFn sink) { + Value patch(VAL_BLOB, std::string(reinterpret_cast(patch_data), patch_size)); + + return ApplyImagePatch(old_data, old_size, &patch, sink, nullptr, nullptr); } /* @@ -47,203 +138,154 @@ 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, size_t old_size, const Value* patch, SinkFn sink, + 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; - } + } + + size_t src_start = static_cast(Read8(normal_header)); + size_t src_len = static_cast(Read8(normal_header + 8)); + size_t patch_offset = static_cast(Read8(normal_header + 16)); + + if (src_start + src_len > old_size) { + printf("source data too short\n"); + return -1; + } + if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) { + printf("Failed to apply bsdiff patch.\n"); + return -1; + } + } 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; + } + + size_t data_len = static_cast(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(&patch->data[pos]), data_len) != 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(Read8(deflate_header)); + size_t src_len = static_cast(Read8(deflate_header + 8)); + size_t patch_offset = static_cast(Read8(deflate_header + 16)); + size_t expanded_len = static_cast(Read8(deflate_header + 24)); - int num_chunks = Read4(header+8); + if (src_start + src_len > 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 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 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; + // 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; } - 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(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(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 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 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& 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; + inflateEnd(&strm); + + if (bonus_size) { + memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0], + bonus_size); } + } + + if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch, + patch_offset, deflate_header, sink, ctx)) { + LOG(ERROR) << "Fail to apply streaming bspatch."; + return -1; + } + + } 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 similarity index 63% rename from applypatch/applypatch.h rename to applypatch/include/applypatch/applypatch.h index f392c55348..581360ef1f 100644 --- a/applypatch/applypatch.h +++ b/applypatch/include/applypatch/applypatch.h @@ -17,11 +17,16 @@ #ifndef _APPLYPATCH_H #define _APPLYPATCH_H +#include #include +#include +#include +#include #include -#include "openssl/sha.h" +#include + #include "edify/expr.h" struct FileContents { @@ -37,48 +42,37 @@ struct FileContents { // and use it as the source instead. #define CACHE_TEMP_SOURCE "/cache/saved.file" -typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*); +using SinkFn = std::function; -// 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& patch_sha1_str, + const std::vector>& patch_data, + const Value* bonus_data); int applypatch_check(const char* filename, - int num_patches, - char** const patch_sha1_str); + const std::vector& 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, - SinkFn sink, void* token, SHA_CTX* ctx); -int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - std::vector* new_data); +int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch, + size_t patch_offset, SinkFn sink, SHA_CTX* ctx); // imgpatch.cpp -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); +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink, + SHA_CTX* ctx, const Value* bonus_data); // freecache.cpp int MakeFreeSpaceOnCache(size_t bytes_needed); diff --git a/applypatch/imgdiff.h b/applypatch/include/applypatch/imgdiff.h similarity index 69% rename from applypatch/imgdiff.h rename to applypatch/include/applypatch/imgdiff.h index f2069b4f3f..22cbd4fa02 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 + // 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 64d9aa9eb6..07c66094ff 100644 --- a/applypatch/include/applypatch/imgpatch.h +++ b/applypatch/include/applypatch/imgpatch.h @@ -14,13 +14,16 @@ * 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 -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, - const unsigned char* patch_data, ssize_t patch_size, - SinkFn sink, void* token); +#include -#endif //_IMGPATCH_H +using SinkFn = std::function; + +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data, + size_t patch_size, SinkFn sink); + +#endif // _APPLYPATCH_IMGPATCH_H diff --git a/applypatch/libimgpatch.pc b/applypatch/libimgpatch.pc new file mode 100644 index 0000000000..e5002934fa --- /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/testdata/new.file b/applypatch/testdata/new.file deleted file mode 100644 index cdeb8fd505..0000000000 Binary files a/applypatch/testdata/new.file and /dev/null differ diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file deleted file mode 100644 index 166c8732eb..0000000000 Binary files a/applypatch/testdata/old.file and /dev/null differ diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff deleted file mode 100644 index b78d385736..0000000000 Binary files a/applypatch/testdata/patch.bsdiff and /dev/null differ diff --git a/applypatch/utils.cpp b/applypatch/utils.cpp deleted file mode 100644 index 4a80be75f6..0000000000 --- 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 - -#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(pv); - return (int)(((unsigned int)p[1] << 8) | - (unsigned int)p[0]); -} - -int Read4(void* pv) { - unsigned char* p = reinterpret_cast(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(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 e7aef781c0..285214f16c 100644 --- a/asn1_decoder.cpp +++ b/asn1_decoder.cpp @@ -14,178 +14,145 @@ * limitations under the License. */ -#include -#include -#include - #include "asn1_decoder.h" +#include -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(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 b17141c441..3e992115a8 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 -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/boot_control/Android.mk b/boot_control/Android.mk new file mode 100644 index 0000000000..27e3d97655 --- /dev/null +++ b/boot_control/Android.mk @@ -0,0 +1,34 @@ +# +# 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. +# + +LOCAL_PATH := $(my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := bootctrl.bcb +LOCAL_MODULE_RELATIVE_PATH := hw +LOCAL_SRC_FILES := boot_control.cpp +LOCAL_CFLAGS := \ + -D_FILE_OFFSET_BITS=64 \ + -Werror \ + -Wall \ + -Wextra \ + -Wno-unused-parameter +LOCAL_SHARED_LIBRARIES := liblog +LOCAL_STATIC_LIBRARIES := libbootloader_message libfs_mgr libbase +LOCAL_POST_INSTALL_CMD := \ + $(hide) mkdir -p $(TARGET_OUT_SHARED_LIBRARIES)/hw && \ + ln -sf bootctrl.bcb.so $(TARGET_OUT_SHARED_LIBRARIES)/hw/bootctrl.default.so +include $(BUILD_SHARED_LIBRARY) diff --git a/boot_control/boot_control.cpp b/boot_control/boot_control.cpp new file mode 100644 index 0000000000..ec97b6ced3 --- /dev/null +++ b/boot_control/boot_control.cpp @@ -0,0 +1,401 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +struct boot_control_private_t { + // The base struct needs to be first in the list. + boot_control_module_t base; + + // Whether this struct was initialized with data from the bootloader message + // that doesn't change until next reboot. + bool initialized; + + // The path to the misc_device as reported in the fstab. + const char* misc_device; + + // The number of slots present on the device. + unsigned int num_slots; + + // The slot where we are running from. + unsigned int current_slot; +}; + +namespace { + +// The number of boot attempts that should be made from a new slot before +// rolling back to the previous slot. +constexpr unsigned int kDefaultBootAttempts = 7; +static_assert(kDefaultBootAttempts < 8, "tries_remaining field only has 3 bits"); + +constexpr unsigned int kMaxNumSlots = + sizeof(bootloader_control::slot_info) / sizeof(bootloader_control::slot_info[0]); +constexpr const char* kSlotSuffixes[kMaxNumSlots] = { "_a", "_b", "_c", "_d" }; +constexpr off_t kBootloaderControlOffset = offsetof(bootloader_message_ab, slot_suffix); + +static uint32_t CRC32(const uint8_t* buf, size_t size) { + static uint32_t crc_table[256]; + + // Compute the CRC-32 table only once. + if (!crc_table[1]) { + for (uint32_t i = 0; i < 256; ++i) { + uint32_t crc = i; + for (uint32_t j = 0; j < 8; ++j) { + uint32_t mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + crc_table[i] = crc; + } + } + + uint32_t ret = -1; + for (size_t i = 0; i < size; ++i) { + ret = (ret >> 8) ^ crc_table[(ret ^ buf[i]) & 0xFF]; + } + + return ~ret; +} + +// Return the little-endian representation of the CRC-32 of the first fields +// in |boot_ctrl| up to the crc32_le field. +uint32_t BootloaderControlLECRC(const bootloader_control* boot_ctrl) { + return htole32( + CRC32(reinterpret_cast(boot_ctrl), offsetof(bootloader_control, crc32_le))); +} + +bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer) { + android::base::unique_fd fd(open(misc_device, O_RDONLY)); + if (fd.get() == -1) { + PLOG(ERROR) << "failed to open " << misc_device; + return false; + } + if (lseek(fd, kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) { + PLOG(ERROR) << "failed to lseek " << misc_device; + return false; + } + if (!android::base::ReadFully(fd.get(), buffer, sizeof(bootloader_control))) { + PLOG(ERROR) << "failed to read " << misc_device; + return false; + } + return true; +} + +bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control* buffer) { + buffer->crc32_le = BootloaderControlLECRC(buffer); + android::base::unique_fd fd(open(misc_device, O_WRONLY | O_SYNC)); + if (fd.get() == -1) { + PLOG(ERROR) << "failed to open " << misc_device; + return false; + } + if (lseek(fd.get(), kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) { + PLOG(ERROR) << "failed to lseek " << misc_device; + return false; + } + if (!android::base::WriteFully(fd.get(), buffer, sizeof(bootloader_control))) { + PLOG(ERROR) << "failed to write " << misc_device; + return false; + } + return true; +} + +void InitDefaultBootloaderControl(const boot_control_private_t* module, + bootloader_control* boot_ctrl) { + memset(boot_ctrl, 0, sizeof(*boot_ctrl)); + + if (module->current_slot < kMaxNumSlots) { + strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[module->current_slot], + sizeof(boot_ctrl->slot_suffix)); + } + boot_ctrl->magic = BOOT_CTRL_MAGIC; + boot_ctrl->version = BOOT_CTRL_VERSION; + + // Figure out the number of slots by checking if the partitions exist, + // otherwise assume the maximum supported by the header. + boot_ctrl->nb_slot = kMaxNumSlots; + std::string base_path = module->misc_device; + size_t last_path_sep = base_path.rfind('/'); + if (last_path_sep != std::string::npos) { + // We test the existence of the "boot" partition on each possible slot, + // which is a partition required by Android Bootloader Requirements. + base_path = base_path.substr(0, last_path_sep + 1) + "boot"; + int last_existing_slot = -1; + int first_missing_slot = -1; + for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { + std::string partition_path = base_path + kSlotSuffixes[slot]; + struct stat part_stat; + int err = stat(partition_path.c_str(), &part_stat); + if (!err) { + last_existing_slot = slot; + LOG(INFO) << "Found slot: " << kSlotSuffixes[slot]; + } else if (err < 0 && errno == ENOENT && first_missing_slot == -1) { + first_missing_slot = slot; + } + } + // We only declare that we found the actual number of slots if we found all + // the boot partitions up to the number of slots, and no boot partition + // after that. Not finding any of the boot partitions implies a problem so + // we just leave the number of slots in the maximum value. + if ((last_existing_slot != -1 && last_existing_slot + 1 == first_missing_slot) || + (first_missing_slot == -1 && last_existing_slot + 1 == kMaxNumSlots)) { + boot_ctrl->nb_slot = last_existing_slot + 1; + LOG(INFO) << "Found a system with " << last_existing_slot + 1 << " slots."; + } + } + + for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { + slot_metadata entry = {}; + + if (slot < boot_ctrl->nb_slot) { + entry.priority = 7; + entry.tries_remaining = kDefaultBootAttempts; + entry.successful_boot = 0; + } else { + entry.priority = 0; // Unbootable + } + + // When the boot_control stored on disk is invalid, we assume that the + // current slot is successful. The bootloader should repair this situation + // before booting and write a valid boot_control slot, so if we reach this + // stage it means that the misc partition was corrupted since boot. + if (module->current_slot == slot) { + entry.successful_boot = 1; + } + + boot_ctrl->slot_info[slot] = entry; + } + boot_ctrl->recovery_tries_remaining = 0; + + boot_ctrl->crc32_le = BootloaderControlLECRC(boot_ctrl); +} + +// Return the index of the slot suffix passed or -1 if not a valid slot suffix. +int SlotSuffixToIndex(const char* suffix) { + for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { + if (!strcmp(kSlotSuffixes[slot], suffix)) return slot; + } + return -1; +} + +// Initialize the boot_control_private struct with the information from +// the bootloader_message buffer stored in |boot_ctrl|. Returns whether the +// initialization succeeded. +bool BootControl_lazyInitialization(boot_control_private_t* module) { + if (module->initialized) return true; + + // Initialize the current_slot from the read-only property. If the property + // was not set (from either the command line or the device tree), we can later + // initialize it from the bootloader_control struct. + std::string suffix_prop = android::base::GetProperty("ro.boot.slot_suffix", ""); + module->current_slot = SlotSuffixToIndex(suffix_prop.c_str()); + + std::string err; + std::string device = get_bootloader_message_blk_device(&err); + if (device.empty()) return false; + + bootloader_control boot_ctrl; + if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) return false; + + // Note that since there isn't a module unload function this memory is leaked. + module->misc_device = strdup(device.c_str()); + module->initialized = true; + + // Validate the loaded data, otherwise we will destroy it and re-initialize it + // with the current information. + uint32_t computed_crc32 = BootloaderControlLECRC(&boot_ctrl); + if (boot_ctrl.crc32_le != computed_crc32) { + LOG(WARNING) << "Invalid boot control found, expected CRC-32 0x" << std::hex << computed_crc32 + << " but found 0x" << std::hex << boot_ctrl.crc32_le << ". Re-initializing."; + InitDefaultBootloaderControl(module, &boot_ctrl); + UpdateAndSaveBootloaderControl(device.c_str(), &boot_ctrl); + } + + module->num_slots = boot_ctrl.nb_slot; + return true; +} + +void BootControl_init(boot_control_module_t* module) { + BootControl_lazyInitialization(reinterpret_cast(module)); +} + +unsigned int BootControl_getNumberSlots(boot_control_module_t* module) { + return reinterpret_cast(module)->num_slots; +} + +unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) { + return reinterpret_cast(module)->current_slot; +} + +int BootControl_markBootSuccessful(boot_control_module_t* module) { + boot_control_private_t* const bootctrl_module = reinterpret_cast(module); + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + bootctrl.slot_info[bootctrl_module->current_slot].successful_boot = 1; + // tries_remaining == 0 means that the slot is not bootable anymore, make + // sure we mark the current slot as bootable if it succeeds in the last + // attempt. + bootctrl.slot_info[bootctrl_module->current_slot].tries_remaining = 1; + if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + return 0; +} + +int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) { + boot_control_private_t* const bootctrl_module = reinterpret_cast(module); + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + // Set every other slot with a lower priority than the new "active" slot. + const unsigned int kActivePriority = 15; + const unsigned int kActiveTries = 6; + for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) { + if (i != slot) { + if (bootctrl.slot_info[i].priority >= kActivePriority) + bootctrl.slot_info[i].priority = kActivePriority - 1; + } + } + + // Note that setting a slot as active doesn't change the successful bit. + // The successful bit will only be changed by setSlotAsUnbootable(). + bootctrl.slot_info[slot].priority = kActivePriority; + bootctrl.slot_info[slot].tries_remaining = kActiveTries; + + // Setting the current slot as active is a way to revert the operation that + // set *another* slot as active at the end of an updater. This is commonly + // used to cancel the pending update. We should only reset the verity_corrpted + // bit when attempting a new slot, otherwise the verity bit on the current + // slot would be flip. + if (slot != bootctrl_module->current_slot) bootctrl.slot_info[slot].verity_corrupted = 0; + + if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + return 0; +} + +int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) { + boot_control_private_t* const bootctrl_module = reinterpret_cast(module); + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + // The only way to mark a slot as unbootable, regardless of the priority is to + // set the tries_remaining to 0. + bootctrl.slot_info[slot].successful_boot = 0; + bootctrl.slot_info[slot].tries_remaining = 0; + if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + return 0; +} + +int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) { + boot_control_private_t* const bootctrl_module = reinterpret_cast(module); + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + return bootctrl.slot_info[slot].tries_remaining; +} + +int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) { + boot_control_private_t* const bootctrl_module = reinterpret_cast(module); + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + return bootctrl.slot_info[slot].successful_boot && bootctrl.slot_info[slot].tries_remaining; +} + +const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) { + if (slot >= kMaxNumSlots || slot >= reinterpret_cast(module)->num_slots) { + return NULL; + } + return kSlotSuffixes[slot]; +} + +static int BootControl_open(const hw_module_t* module __unused, const char* id __unused, + hw_device_t** device __unused) { + /* Nothing to do currently. */ + return 0; +} + +struct hw_module_methods_t BootControl_methods = { + .open = BootControl_open, +}; + +} // namespace + +boot_control_private_t HAL_MODULE_INFO_SYM = { + .base = + { + .common = + { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = BOOT_CONTROL_HARDWARE_MODULE_ID, + .name = "AOSP reference bootctrl HAL", + .author = "The Android Open Source Project", + .methods = &BootControl_methods, + }, + .init = BootControl_init, + .getNumberSlots = BootControl_getNumberSlots, + .getCurrentSlot = BootControl_getCurrentSlot, + .markBootSuccessful = BootControl_markBootSuccessful, + .setActiveBootSlot = BootControl_setActiveBootSlot, + .setSlotAsUnbootable = BootControl_setSlotAsUnbootable, + .isSlotBootable = BootControl_isSlotBootable, + .getSuffix = BootControl_getSuffix, + .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful, + }, + .initialized = false, + .misc_device = nullptr, + .num_slots = 0, + .current_slot = 0, +}; diff --git a/bootloader_message/Android.NObp b/bootloader_message/Android.NObp new file mode 100644 index 0000000000..f0d76e718f --- /dev/null +++ b/bootloader_message/Android.NObp @@ -0,0 +1,26 @@ +// +// 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. +// + +cc_library_static { + name: "libbootloader_message", + srcs: ["bootloader_message.cpp"], + cppflags: ["-Werror"], + static_libs: [ + "libbase", + "libfs_mgr", + ], + export_include_dirs: ["include"], +} diff --git a/bootloader_message/Android.mk b/bootloader_message/Android.mk index 1d5c85fe33..0d84713c32 100644 --- a/bootloader_message/Android.mk +++ b/bootloader_message/Android.mk @@ -14,32 +14,16 @@ LOCAL_PATH := $(call my-dir) -ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 25; echo $$?),0) - include $(CLEAR_VARS) - LOCAL_CLANG := true - LOCAL_SRC_FILES := bootloader_message.cpp - LOCAL_MODULE := libbootloader_message - LOCAL_STATIC_LIBRARIES := libfs_mgr - LOCAL_C_INCLUDES := $(LOCAL_PATH)/include - LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include - include $(BUILD_STATIC_LIBRARY) -endif - include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_SRC_FILES := bootloader_message.cpp LOCAL_MODULE := libbootloader_message -LOCAL_C_INCLUDES += bionic $(LOCAL_PATH)/include -ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) - LOCAL_C_INCLUDES += external/stlport/stlport - LOCAL_SHARED_LIBRARIES += libstlport -else - LOCAL_SHARED_LIBRARIES += libc++ -endif -LOCAL_CFLAGS := -DEXCLUDE_FS_MGR -# ignore bootloader's factory reset command even when written to /misc -ifeq ($(TW_IGNORE_MISC_WIPE_DATA), true) - LOCAL_CFLAGS += -DIGNORE_MISC_WIPE_DATA +LOCAL_STATIC_LIBRARIES := libbase libfs_mgr +LOCAL_CFLAGS := -Werror +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0) + TARGET_GLOBAL_CFLAGS += -DUSE_OLD_BOOTLOADER_MESSAGE + CLANG_TARGET_GLOBAL_CFLAGS += -DUSE_OLD_BOOTLOADER_MESSAGE endif +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include -include $(BUILD_SHARED_LIBRARY) +include $(BUILD_STATIC_LIBRARY) diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index 4d1ce5bb4e..6c237e620f 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -19,31 +19,18 @@ #include #include #include -#include -#include -#include -#include -#include #include #include -/* #include #include #include -*/ -#ifndef EXCLUDE_FS_MGR #include -#endif -static std::string misc_blkdev; - -void set_misc_device(std::string name) { - misc_blkdev = name; -} +#ifdef USE_OLD_BOOTLOADER_MESSAGE +#include -#ifndef EXCLUDE_FS_MGR static struct fstab* read_fstab(std::string* err) { // The fstab path is always "/fstab.${ro.hardware}". std::string fstab_path = "/fstab."; @@ -62,23 +49,28 @@ static struct fstab* read_fstab(std::string* err) { #endif static std::string get_misc_blk_device(std::string* err) { -#ifdef EXCLUDE_FS_MGR - return misc_blkdev; -#else +#ifdef USE_OLD_BOOTLOADER_MESSAGE struct fstab* fstab = read_fstab(err); - if (fstab == nullptr) { +#else + std::unique_ptr fstab(fs_mgr_read_fstab_default(), + fs_mgr_free_fstab); +#endif + if (!fstab) { + *err = "failed to read default fstab"; return ""; } +#ifdef USE_OLD_BOOTLOADER_MESSAGE fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab, "/misc"); +#else + fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc"); +#endif if (record == nullptr) { *err = "failed to find /misc partition"; return ""; } return record->blk_device; -#endif } - // In recovery mode, recovery can get started and try to access the misc // device before the kernel has actually created it. static bool wait_for_device(const std::string& blk_device, std::string* err) { @@ -90,127 +82,101 @@ static bool wait_for_device(const std::string& blk_device, std::string* err) { struct stat buf; ret = stat(blk_device.c_str(), &buf); if (ret == -1) { - char buffer[2048]; - sprintf(buffer, "failed to stat %s try %d: %s\n", - blk_device.c_str(), tries, strerror(errno)); - *err += buffer; - /* *err += android::base::StringPrintf("failed to stat %s try %d: %s\n", blk_device.c_str(), tries, strerror(errno)); - */ sleep(1); } } while (ret && tries < 10); if (ret) { - *err += "failed to stat " + blk_device + "\n"; - /* *err += android::base::StringPrintf("failed to stat %s\n", blk_device.c_str()); - */ } 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; } - int fd(open(misc_blk_device.c_str(), O_RDONLY)); - if (fd < 0) { - *err = "failed to open " + misc_blk_device + ": "; - *err += strerror(errno); - /* + android::base::unique_fd fd(open(misc_blk_device.c_str(), O_RDONLY)); + if (fd == -1) { *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno)); - */ return false; } if (lseek(fd, static_cast(offset), SEEK_SET) != static_cast(offset)) { - *err = "failed to lseek " + misc_blk_device + ": "; - *err += strerror(errno); - close(fd); - /* *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), strerror(errno)); - */ return false; } - if (read(fd, p, size) != size) { - *err = "failed to read " + misc_blk_device + ": "; - *err += strerror(errno); - close(fd); - /* + 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; } - close(fd); 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()) { - *err = "no misc device set"; - return false; - } - int fd = (open(misc_blk_device.c_str(), O_WRONLY | O_SYNC)); +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 = "failed to open " + misc_blk_device + ": "; - *err += strerror(errno); - /* *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno)); - */ return false; } if (lseek(fd, static_cast(offset), SEEK_SET) != static_cast(offset)) { - *err = "failed to lseek " + misc_blk_device + ": "; - *err += strerror(errno); - close(fd); - /* *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), strerror(errno)); - */ return false; } - if (write(fd, p, size) != size) { - *err = "failed to write " + misc_blk_device + ": "; - *err += strerror(errno); - close(fd); - /* + 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) == -1) { - *err = "failed to fsync " + misc_blk_device + ": "; - *err += strerror(errno); - close(fd); - /* *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(), strerror(errno)); - */ return false; } - close(fd); return true; } +std::string get_bootloader_message_blk_device(std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) return ""; + if (!wait_for_device(misc_blk_device, err)) return ""; + return misc_blk_device; +} + +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) { @@ -224,120 +190,72 @@ bool write_bootloader_message(const std::vector& options, std::stri strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); for (const auto& s : options) { strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery)); - if (s.substr(s.size() - 1) != "\n") { + if (s.back() != '\n') { + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + } + return write_bootloader_message(boot, err); +} + +bool update_bootloader_message(const std::vector& 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_bootloader_message(const char* options) { +extern "C" bool write_reboot_bootloader(void) { std::string err; - bootloader_message boot = {}; - memcpy(&boot, options, sizeof(boot)); - return write_bootloader_message(boot, &err); + return write_reboot_bootloader(&err); } -static const char *COMMAND_FILE = "/cache/recovery/command"; -static const int MAX_ARG_LENGTH = 4096; -static const int MAX_ARGS = 100; - -// command line args come from, in decreasing precedence: -// - the actual command line -// - the bootloader control block (one per line, after "recovery") -// - the contents of COMMAND_FILE (one per line) -void -get_args(int *argc, char ***argv) { - bootloader_message boot = {}; - std::string err; - if (!read_bootloader_message(&boot, &err)) { - printf("%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) { - printf("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command); - } - - if (boot.status[0] != 0 && boot.status[0] != 255) { - printf("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; - -// if the device does not have an own recovery key combo we just want to open TWRP after -// walking through the factory reset screen - without actually doing a factory reset -#ifdef IGNORE_MISC_WIPE_DATA - if (!strcmp(arg, "--wipe_data")) { - (*argv)[*argc] = ""; - *argc = *argc -1; - printf("Bootloader arg \"%s\" ignored because TWRP was compiled with TW_IGNORE_MISC_WIPE_DATA\n", arg); - continue; - } -#endif - (*argv)[*argc] = strdup(arg); - } - printf("Got arguments from boot message\n"); - } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) { - printf("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(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; - } - } - - fclose(fp); - printf("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)) { - printf("%s\n", err.c_str()); - } +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 e0fc2cd8af..4da1171cdd 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -17,23 +17,21 @@ #ifndef _BOOTLOADER_MESSAGE_H #define _BOOTLOADER_MESSAGE_H +#include #include +#include // 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. -#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET -static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = BOARD_RECOVERY_BLDRMSG_OFFSET; -static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024 + BOOTLOADER_MESSAGE_OFFSET_IN_MISC; -#else static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; -#endif -/* 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 @@ -44,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 @@ -56,18 +55,17 @@ 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]; char status[32]; char recovery[768]; +#ifdef USE_OLD_BOOTLOADER_MESSAGE // The 'recovery' field used to be 1024 bytes. It has only ever // been used to store the recovery command line, so 768 bytes // should be plenty. We carve off the last 256 bytes to store the @@ -76,23 +74,161 @@ struct bootloader_message { char stage[32]; char slot_suffix[32]; char reserved[192]; +#else + // The 'recovery' field used to be 1024 bytes. It has only ever + // been used to store the recovery command line, so 768 bytes + // should be plenty. We carve off the last 256 bytes to store the + // 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]; +#endif }; +/** + * We must be cautious when changing the bootloader_message struct size, + * because A/B-specific fields may end up with different offsets. + */ +#if !defined(USE_OLD_BOOTLOADER_MESSAGE) && ((__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]; + + // 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 !defined(USE_OLD_BOOTLOADER_MESSAGE) && ((__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 #include +// Return the block device name for the bootloader message partition and waits +// for the device for up to 10 seconds. In case of error returns the empty +// string. +std::string get_bootloader_message_blk_device(std::string* err); + +// 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& 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& 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); -void set_misc_device(std::string name); -void get_args(int *argc, char ***argv); +// 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 @@ -100,6 +236,7 @@ void get_args(int *argc, char ***argv); // C Interface. bool write_bootloader_message(const char* options); +bool write_reboot_bootloader(void); #endif // ifdef __cplusplus diff --git a/bootloader_message_twrp/Android.mk b/bootloader_message_twrp/Android.mk new file mode 100644 index 0000000000..e7a3ea0f8c --- /dev/null +++ b/bootloader_message_twrp/Android.mk @@ -0,0 +1,35 @@ +# 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_CLANG := true +LOCAL_SRC_FILES := bootloader_message.cpp +LOCAL_MODULE := libbootloader_message_twrp +LOCAL_C_INCLUDES += bionic $(LOCAL_PATH)/include +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 21; echo $$?),0) + LOCAL_C_INCLUDES += external/stlport/stlport + LOCAL_SHARED_LIBRARIES += libstlport +else + LOCAL_C_INCLUDES += external/libcxx/include + LOCAL_SHARED_LIBRARIES += libc++ +endif +LOCAL_CFLAGS := -Werror -std=c++11 +# ignore bootloader's factory reset command even when written to /misc +ifeq ($(TW_IGNORE_MISC_WIPE_DATA), true) + LOCAL_CFLAGS += -DIGNORE_MISC_WIPE_DATA +endif +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +include $(BUILD_SHARED_LIBRARY) diff --git a/bootloader_message_twrp/bootloader_message.cpp b/bootloader_message_twrp/bootloader_message.cpp new file mode 100644 index 0000000000..a06ad9a89f --- /dev/null +++ b/bootloader_message_twrp/bootloader_message.cpp @@ -0,0 +1,386 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static std::string misc_blkdev; + +void set_misc_device(const char* name) { + misc_blkdev = name; +} + +static std::string get_misc_blk_device(std::string* err) { + *err = ""; + return misc_blkdev; +} + +// In recovery mode, recovery can get started and try to access the misc +// device before the kernel has actually created it. +static bool wait_for_device(const std::string& blk_device, std::string* err) { + int tries = 0; + int ret; + err->clear(); + do { + ++tries; + struct stat buf; + ret = stat(blk_device.c_str(), &buf); + if (ret == -1) { + char buffer[2048]; + sprintf(buffer, "failed to stat %s try %d: %s\n", + blk_device.c_str(), tries, strerror(errno)); + *err += buffer; + /* + *err += android::base::StringPrintf("failed to stat %s try %d: %s\n", + blk_device.c_str(), tries, strerror(errno)); + */ + sleep(1); + } + } while (ret && tries < 10); + + if (ret) { + *err += "failed to stat " + blk_device + "\n"; + /* + *err += android::base::StringPrintf("failed to stat %s\n", blk_device.c_str()); + */ + } + return ret == 0; +} + +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; + } + + int fd(open(misc_blk_device.c_str(), O_RDONLY)); + if (fd < 0) { + *err = "failed to open " + misc_blk_device + ": "; + *err += strerror(errno); + /* + android::base::unique_fd fd(open(misc_blk_device.c_str(), O_RDONLY)); + if (fd == -1) { + *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), + strerror(errno)); + */ + return false; + } + if (lseek(fd, static_cast(offset), SEEK_SET) != static_cast(offset)) { + *err = "failed to lseek " + misc_blk_device + ": "; + *err += strerror(errno); + close(fd); + /* + + *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), + strerror(errno)); + */ + return false; + } + + if ((size_t)read(fd, p, size) != size) { + *err = "failed to read " + misc_blk_device + ": "; + *err += strerror(errno); + close(fd); + /* + 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; + } + close(fd); + 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()) { + *err = "no misc device set"; + return false; + } + int fd = (open(misc_blk_device.c_str(), O_WRONLY | O_SYNC)); + if (fd == -1) { + *err = "failed to open " + misc_blk_device + ": "; + *err += strerror(errno); + /* +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, static_cast(offset), SEEK_SET) != static_cast(offset)) { + *err = "failed to lseek " + misc_blk_device + ": "; + *err += strerror(errno); + close(fd); + /* + *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), + strerror(errno)); + */ + return false; + } + if ((size_t)write(fd, p, size) != size) { + *err = "failed to write " + misc_blk_device + ": "; + *err += strerror(errno); + close(fd); + /* + 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) == -1) { + *err = "failed to fsync " + misc_blk_device + ": "; + *err += strerror(errno); + close(fd); + /* + if (fsync(fd) == -1) { + *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(), + strerror(errno)); + */ + return false; + } + close(fd); + 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) { + 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, __unused const std::string& misc_blk_device, + std::string* err) { + return write_misc_partition(&boot, sizeof(boot), + BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); +} + +bool write_bootloader_message(const bootloader_message& boot, std::string* 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); +} + +// libc++ in 5.1 does not know how to handle a std::string* so this craziness is needed +bool clear_bootloader_message(void* err) { + std::string &s = *(static_cast(err)); + return clear_bootloader_message(&s); +} + +bool clear_bootloader_message(std::string* err) { + bootloader_message boot = {}; + return write_bootloader_message(boot, err); +} + +bool write_bootloader_message(const std::vector& options, std::string* err) { + bootloader_message boot = {}; + 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.substr(s.size() - 1) != "\n") { + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + } + return write_bootloader_message(boot, err); +} + +bool update_bootloader_message(const std::vector& 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, misc_blk_device, + WIPE_PACKAGE_OFFSET_IN_MISC, err); +} + +bool write_wipe_package(const std::string& package_data, std::string* err) { + 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(), + 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; + bootloader_message boot = {}; + memcpy(&boot, options, sizeof(boot)); + return write_bootloader_message(boot, &err); +} + +static const char *COMMAND_FILE = "/cache/recovery/command"; +static const int MAX_ARG_LENGTH = 4096; +static const int MAX_ARGS = 100; + +// command line args come from, in decreasing precedence: +// - the actual command line +// - the bootloader control block (one per line, after "recovery") +// - the contents of COMMAND_FILE (one per line) +void +get_args(int *argc, char ***argv) { + bootloader_message boot = {}; + std::string err; + if (!read_bootloader_message(&boot, &err)) { + printf("%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] != (char)255) { + printf("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command); + } + + if (boot.status[0] != 0 && boot.status[0] != (char)255) { + printf("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; + +// if the device does not have an own recovery key combo we just want to open TWRP after +// walking through the factory reset screen - without actually doing a factory reset +#ifdef IGNORE_MISC_WIPE_DATA + if (!strcmp(arg, "--wipe_data")) { + (*argv)[*argc] = NULL; + *argc = *argc -1; + printf("Bootloader arg \"%s\" ignored because TWRP was compiled with TW_IGNORE_MISC_WIPE_DATA\n", arg); + continue; + } +#endif + (*argv)[*argc] = strdup(arg); + } + printf("Got arguments from boot message\n"); + } else if (boot.recovery[0] != 0 && boot.recovery[0] != (char)255) { + printf("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(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; + } + } + + fclose(fp); + printf("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)) { + printf("%s\n", err.c_str()); + } +} diff --git a/bootloader_message_twrp/include/bootloader_message_twrp/bootloader_message.h b/bootloader_message_twrp/include/bootloader_message_twrp/bootloader_message.h new file mode 100644 index 0000000000..52c1b86f0e --- /dev/null +++ b/bootloader_message_twrp/include/bootloader_message_twrp/bootloader_message.h @@ -0,0 +1,236 @@ +/* + * 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. + */ + +#ifndef _BOOTLOADER_MESSAGE_TWRP_H +#define _BOOTLOADER_MESSAGE_TWRP_H + +#include +#include +#include + +// Spaces used by misc partition are as below: +// 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. +#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET +static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = BOARD_RECOVERY_BLDRMSG_OFFSET; +static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024 + BOOTLOADER_MESSAGE_OFFSET_IN_MISC; +#else +static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; +static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; +#endif + +/* 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 + * each other. + * + * The command field is updated by linux when it wants to + * reboot into recovery or to update radio or bootloader firmware. + * It is also updated by the bootloader when firmware update + * is complete (to boot into recovery for any final cleanup) + * + * 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 + * other way around. + * + * The stage field is written by packages which restart themselves + * multiple times, so that the UI can reflect which invocation of the + * package it is. If the value is of the format "#/#" (eg, "1/3"), + * the UI will add a simple indicator of that status. + * + * 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]; + char status[32]; + char recovery[768]; + + // The 'recovery' field used to be 1024 bytes. It has only ever + // been used to store the recovery command line, so 768 bytes + // should be plenty. We carve off the last 256 bytes to store the + // 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]; + + // 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 +#include + +// 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& 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& options, std::string* err); + +// Clear BCB. +bool clear_bootloader_message(void* err); +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); + +void set_misc_device(const char* name); +void get_args(int *argc, char ***argv); + +// 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 + +#include + +// C Interface. +bool write_bootloader_message(const char* options); +bool write_reboot_bootloader(void); + +#endif // ifdef __cplusplus + +#endif // _BOOTLOADER_MESSAGE_TWRP_H diff --git a/common.h b/common.h index 3afb633551..b0901f41a3 100644 --- a/common.h +++ b/common.h @@ -17,9 +17,9 @@ #ifndef RECOVERY_COMMON_H #define RECOVERY_COMMON_H -#include #include #include +#include #ifdef __cplusplus extern "C" { @@ -40,18 +40,29 @@ extern "C" { #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); void ui_print(const char* format, ...); -bool is_ro_debuggable(); +static bool is_ro_debuggable(); #ifdef __cplusplus } #endif +bool reboot(const std::string& command); + #endif // RECOVERY_COMMON_H diff --git a/crypto/ext4crypt/Android.mk b/crypto/ext4crypt/Android.mk index bcbcccfa2d..693b675186 100644 --- a/crypto/ext4crypt/Android.mk +++ b/crypto/ext4crypt/Android.mk @@ -5,15 +5,33 @@ include $(CLEAR_VARS) LOCAL_MODULE := libe4crypt LOCAL_MODULE_TAGS := eng optional LOCAL_CFLAGS := -LOCAL_SRC_FILES := Decrypt.cpp Ext4Crypt.cpp Keymaster.cpp KeyStorage.cpp ScryptParameters.cpp Utils.cpp HashPassword.cpp ext4_crypt.cpp +LOCAL_SRC_FILES := Decrypt.cpp Ext4Crypt.cpp ScryptParameters.cpp Utils.cpp HashPassword.cpp ext4_crypt.cpp LOCAL_SHARED_LIBRARIES := libselinux libc libc++ libext4_utils libsoftkeymaster libbase libcrypto libcutils libkeymaster_messages libhardware libprotobuf-cpp-lite LOCAL_STATIC_LIBRARIES := libscrypt_static -LOCAL_C_INCLUDES := system/extras/ext4_utils external/scrypt/lib/crypto system/security/keystore hardware/libhardware/include/hardware system/security/softkeymaster/include/keymaster system/keymaster/include +LOCAL_C_INCLUDES := system/extras/ext4_utils system/extras/ext4_utils/include/ext4_utils external/scrypt/lib/crypto system/security/keystore hardware/libhardware/include/hardware system/security/softkeymaster/include/keymaster system/keymaster/include ifneq ($(wildcard hardware/libhardware/include/hardware/keymaster0.h),) LOCAL_CFLAGS += -DTW_CRYPTO_HAVE_KEYMASTERX LOCAL_C_INCLUDES += external/boringssl/src/include endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0) + LOCAL_CFLAGS += -DUSE_KEYSTORAGE_3 -DHAVE_GATEKEEPER1 + LOCAL_SRC_FILES += Keymaster3.cpp KeyStorage3.cpp + LOCAL_SHARED_LIBRARIES += android.hardware.keymaster@3.0 libkeystore_binder libhidlbase libutils libbinder + LOCAL_SHARED_LIBRARIES += android.hardware.gatekeeper@1.0 + ifneq ($(wildcard hardware/interfaces/weaver/Android.bp),) + LOCAL_CFLAGS += -DHAVE_SYNTH_PWD_SUPPORT + LOCAL_SRC_FILES += Weaver1.cpp + LOCAL_SHARED_LIBRARIES += android.hardware.weaver@1.0 + endif + ifneq ($(wildcard system/core/libkeyutils/Android.bp),) + LOCAL_CFLAGS += -DHAVE_LIBKEYUTILS + LOCAL_SHARED_LIBRARIES += libkeyutils + endif + LOCAL_ADDITIONAL_DEPENDENCIES := keystore_auth +else + LOCAL_SRC_FILES += Keymaster.cpp KeyStorage.cpp +endif include $(BUILD_SHARED_LIBRARY) @@ -41,4 +59,15 @@ LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker64 include $(BUILD_EXECUTABLE) +include $(CLEAR_VARS) +LOCAL_MODULE := keystore_auth +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := keystore_auth.cpp +LOCAL_SHARED_LIBRARIES := libc libkeystore_binder libutils libbinder liblog +LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker64 + +include $(BUILD_EXECUTABLE) + endif diff --git a/crypto/ext4crypt/Decrypt.cpp b/crypto/ext4crypt/Decrypt.cpp index 3b69d46514..c062f8ae48 100644 --- a/crypto/ext4crypt/Decrypt.cpp +++ b/crypto/ext4crypt/Decrypt.cpp @@ -26,10 +26,50 @@ #include #include -#include "ext4_crypt.h" +#ifndef HAVE_LIBKEYUTILS #include "key_control.h" +#else +#include +#endif + +#ifdef HAVE_SYNTH_PWD_SUPPORT +#include "Weaver1.h" +#include "cutils/properties.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +extern "C" { +#include "crypto_scrypt.h" +} +#else +#include "ext4_crypt.h" +#endif //ifdef HAVE_SYNTH_PWD_SUPPORT + +#ifdef HAVE_GATEKEEPER1 +#include +#else #include +#endif #include "HashPassword.h" #include @@ -67,7 +107,7 @@ extern "C" bool lookup_ref_key(const char* policy, char* policy_type) { extern "C" bool lookup_ref_tar(const char* policy_type, char* policy) { if (strncmp(policy_type, "1", 1) != 0) { - printf("Unexpected version %c\n", policy_type); + printf("Unexpected version %c\n", policy_type[0]); return false; } const char* ptr = policy_type + 1; // skip past the version number @@ -94,6 +134,7 @@ extern "C" bool lookup_ref_tar(const char* policy_type, char* policy) { return true; } +#ifndef HAVE_GATEKEEPER1 int gatekeeper_device_initialize(gatekeeper_device_t **dev) { int ret; const hw_module_t *mod; @@ -110,8 +151,997 @@ int gatekeeper_device_initialize(gatekeeper_device_t **dev) { printf("failed to open gatekeeper\n"); return ret; } +#endif //ifndef HAVE_GATEKEEPER1 + +bool Decrypt_DE() { + if (!e4crypt_initialize_global_de()) { // this deals with the overarching device encryption + printf("e4crypt_initialize_global_de returned fail\n"); + return false; + } + if (!e4crypt_init_user0()) { + printf("e4crypt_init_user0 returned fail\n"); + return false; + } + return true; +} + +#ifdef HAVE_SYNTH_PWD_SUPPORT +// Crappy functions for debugging, please ignore unless you need to debug +/*void output_hex(const std::string& in) { + const char *buf = in.data(); + char hex[in.size() * 2 + 1]; + unsigned int index; + for (index = 0; index < in.size(); index++) + sprintf(&hex[2 * index], "%02X", buf[index]); + printf("%s", hex); +} + +void output_hex(const char* buf, const int size) { + char hex[size * 2 + 1]; + int index; + for (index = 0; index < size; index++) + sprintf(&hex[2 * index], "%02X", buf[index]); + printf("%s", hex); +} + +void output_hex(const unsigned char* buf, const int size) { + char hex[size * 2 + 1]; + int index; + for (index = 0; index < size; index++) + sprintf(&hex[2 * index], "%02X", buf[index]); + printf("%s", hex); +} + +void output_hex(std::vector* vec) { + char hex[3]; + unsigned int index; + for (index = 0; index < vec->size(); index++) { + sprintf(&hex[0], "%02X", vec->at(index)); + printf("%s", hex); + } +}*/ + +/* An alternative is to use: + * sqlite3 /data/system/locksettings.db "SELECT value FROM locksettings WHERE name='sp-handle' AND user=0;" + * but we really don't want to include the 1.1MB libsqlite in TWRP. We scan the spblob folder for the + * password data file (*.pwd) and get the handle from the filename instead. This is a replacement for + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/LockSettingsService.java#2017 + * We never use this data as an actual long. We always use it as a string. */ +bool Find_Handle(const std::string& spblob_path, std::string& handle_str) { + DIR* dir = opendir(spblob_path.c_str()); + if (!dir) { + printf("Error opening '%s'\n", spblob_path.c_str()); + return false; + } + + struct dirent* de = 0; + + while ((de = readdir(dir)) != 0) { + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + size_t len = strlen(de->d_name); + if (len <= 4) + continue; + char* p = de->d_name; + p += len - 4; + if (strncmp(p, ".pwd", 4) == 0) { + handle_str = de->d_name; + handle_str = handle_str.substr(0, len - 4); + //*handle = strtoull(handle_str.c_str(), 0 , 16); + closedir(dir); + return true; + } + } + closedir(dir); + return false; +} + +// The password data is stored in big endian and has to be swapped on little endian ARM +template +void endianswap(T *objp) { + unsigned char *memp = reinterpret_cast(objp); + std::reverse(memp, memp + sizeof(T)); +} + +/* This is the structure of the data in the password data (*.pwd) file which the structure can be found + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#187 */ +struct password_data_struct { + int password_type; + unsigned char scryptN; + unsigned char scryptR; + unsigned char scryptP; + int salt_len; + void* salt; + int handle_len; + void* password_handle; +}; + +/* C++ replacement for + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#764 */ +bool Get_Password_Data(const std::string& spblob_path, const std::string& handle_str, password_data_struct *pwd) { + std::string pwd_file = spblob_path + handle_str + ".pwd"; + std::string pwd_data; + if (!android::base::ReadFileToString(pwd_file, &pwd_data)) { + printf("Failed to read '%s'\n", pwd_file.c_str()); + return false; + } + //output_hex(pwd_data.data(), pwd_data.size());printf("\n"); + const int* intptr = (const int*)pwd_data.data(); + pwd->password_type = *intptr; + endianswap(&pwd->password_type); + //printf("password type %i\n", pwd->password_type); // 2 was PIN, 1 for pattern, 2 also for password, -1 for default password + const unsigned char* byteptr = (const unsigned char*)pwd_data.data() + sizeof(int); + pwd->scryptN = *byteptr; + byteptr++; + pwd->scryptR = *byteptr; + byteptr++; + pwd->scryptP = *byteptr; + byteptr++; + intptr = (const int*)byteptr; + pwd->salt_len = *intptr; + endianswap(&pwd->salt_len); + if (pwd->salt_len != 0) { + pwd->salt = malloc(pwd->salt_len); + if (!pwd->salt) { + printf("Get_Password_Data malloc salt\n"); + return false; + } + memcpy(pwd->salt, intptr + 1, pwd->salt_len); + intptr++; + byteptr = (const unsigned char*)intptr; + byteptr += pwd->salt_len; + } else { + printf("Get_Password_Data salt_len is 0\n"); + return false; + } + intptr = (const int*)byteptr; + pwd->handle_len = *intptr; + endianswap(&pwd->handle_len); + if (pwd->handle_len != 0) { + pwd->password_handle = malloc(pwd->handle_len); + if (!pwd->password_handle) { + printf("Get_Password_Data malloc password_handle\n"); + return false; + } + memcpy(pwd->password_handle, intptr + 1, pwd->handle_len); + } else { + printf("Get_Password_Data handle_len is 0\n"); + // Not an error if using weaver + } + return true; +} + +/* C++ replacement for + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#765 + * called here + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#1050 */ +bool Get_Password_Token(const password_data_struct *pwd, const std::string& Password, unsigned char* password_token) { + if (!password_token) { + printf("password_token is null\n"); + return false; + } + unsigned int N = 1 << pwd->scryptN; + unsigned int r = 1 << pwd->scryptR; + unsigned int p = 1 << pwd->scryptP; + //printf("N %i r %i p %i\n", N, r, p); + int ret = crypto_scrypt(reinterpret_cast(Password.data()), Password.size(), + reinterpret_cast(pwd->salt), pwd->salt_len, + N, r, p, + password_token, 32); + if (ret != 0) { + printf("scrypt error\n"); + return false; + } + return true; +} + +// Data structure for the *.weaver file, see Get_Weaver_Data below +struct weaver_data_struct { + unsigned char version; + int slot; +}; + +/* C++ replacement for + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#501 + * called here + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#768 */ +bool Get_Weaver_Data(const std::string& spblob_path, const std::string& handle_str, weaver_data_struct *wd) { + std::string weaver_file = spblob_path + handle_str + ".weaver"; + std::string weaver_data; + if (!android::base::ReadFileToString(weaver_file, &weaver_data)) { + printf("Failed to read '%s'\n", weaver_file.c_str()); + return false; + } + //output_hex(weaver_data.data(), weaver_data.size());printf("\n"); + const unsigned char* byteptr = (const unsigned char*)weaver_data.data(); + wd->version = *byteptr; + //printf("weaver version %i\n", wd->version); + const int* intptr = (const int*)weaver_data.data() + sizeof(unsigned char); + wd->slot = *intptr; + //endianswap(&wd->slot); not needed + //printf("weaver slot %i\n", wd->slot); + return true; +} + +namespace android { + +// On Android 8.0 for some reason init can't seem to completely stop keystore +// so we have to kill it too if it doesn't die on its own. +static void kill_keystore() { + DIR* dir = opendir("/proc"); + if (dir) { + struct dirent* de = 0; + + while ((de = readdir(dir)) != 0) { + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + + int pid = -1; + int ret = sscanf(de->d_name, "%d", &pid); + + if (ret == 1) { + char cmdpath[PATH_MAX]; + sprintf(cmdpath, "/proc/%d/cmdline", pid); + + FILE* file = fopen(cmdpath, "r"); + size_t task_size = PATH_MAX; + char task[PATH_MAX]; + char* p = task; + if (getline(&p, &task_size, file) > 0) { + if (strstr(task, "keystore") != 0) { + printf("keystore pid %d found, sending kill.\n", pid); + kill(pid, SIGINT); + usleep(5000); + kill(pid, SIGKILL); + } + } + fclose(file); + } + } + closedir(dir); + } +} + +// The keystore holds a file open on /data so we have to stop / kill it +// if we want to be able to unmount /data for things like formatting. +static void stop_keystore() { + printf("Stopping keystore...\n"); + property_set("ctl.stop", "keystore"); + usleep(5000); + kill_keystore(); +} + +/* These next 2 functions try to get the keystore service 50 times because + * the keystore is not always ready when TWRP boots */ +sp getKeystoreBinder() { + sp sm = defaultServiceManager(); + return sm->getService(String16("android.security.keystore")); +} + +sp getKeystoreBinderRetry() { + printf("Starting keystore...\n"); + property_set("ctl.start", "keystore"); + int retry_count = 50; + sp binder = getKeystoreBinder(); + while (binder == NULL && retry_count) { + printf("Waiting for keystore service... %i\n", retry_count--); + sleep(1); + binder = getKeystoreBinder(); + } + return binder; +} + +namespace keystore { + +#define SYNTHETIC_PASSWORD_VERSION_V1 1 +#define SYNTHETIC_PASSWORD_VERSION 2 +#define SYNTHETIC_PASSWORD_PASSWORD_BASED 0 +#define SYNTHETIC_PASSWORD_KEY_PREFIX "USRSKEY_synthetic_password_" + +/* The keystore alias subid is sometimes the same as the handle, but not always. + * In the case of handle 0c5303fd2010fe29, the alias subid used c5303fd2010fe29 + * without the leading 0. We could try to parse the data from a previous + * keystore request, but I think this is an easier solution because there + * is little to no documentation on the format of data we get back from + * the keystore in this instance. We also want to copy everything to a temp + * folder so that any key upgrades that might take place do not actually + * upgrade the keys on the data partition. We rename all 1000 uid files to 0 + * to pass the keystore permission checks. */ +bool Find_Keystore_Alias_SubID_And_Prep_Files(const userid_t user_id, std::string& keystoreid) { + char path_c[PATH_MAX]; + sprintf(path_c, "/data/misc/keystore/user_%d", user_id); + char user_dir[PATH_MAX]; + sprintf(user_dir, "user_%d", user_id); + std::string source_path = "/data/misc/keystore/"; + source_path += user_dir; + + mkdir("/tmp/misc", 0755); + mkdir("/tmp/misc/keystore", 0755); + std::string destination_path = "/tmp/misc/keystore/"; + destination_path += user_dir; + if (mkdir(destination_path.c_str(), 0755) && errno != EEXIST) { + printf("failed to mkdir '%s' %s\n", destination_path.c_str(), strerror(errno)); + return false; + } + destination_path += "/"; + + DIR* dir = opendir(source_path.c_str()); + if (!dir) { + printf("Error opening '%s'\n", source_path.c_str()); + return false; + } + source_path += "/"; + + struct dirent* de = 0; + size_t prefix_len = strlen(SYNTHETIC_PASSWORD_KEY_PREFIX); + bool found_subid = false; + + while ((de = readdir(dir)) != 0) { + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + if (!found_subid) { + size_t len = strlen(de->d_name); + if (len <= prefix_len) + continue; + if (!strstr(de->d_name, SYNTHETIC_PASSWORD_KEY_PREFIX)) + continue; + std::string file = de->d_name; + std::size_t found = file.find_last_of("_"); + if (found != std::string::npos) { + keystoreid = file.substr(found + 1); + printf("keystoreid: '%s'\n", keystoreid.c_str()); + found_subid = true; + } + } + std::string src = source_path; + src += de->d_name; + std::ifstream srcif(src.c_str(), std::ios::binary); + std::string dst = destination_path; + dst += de->d_name; + std::size_t source_uid = dst.find("1000"); + if (source_uid != std::string::npos) + dst.replace(source_uid, 4, "0"); + std::ofstream dstof(dst.c_str(), std::ios::binary); + printf("copying '%s' to '%s'\n", src.c_str(), dst.c_str()); + dstof << srcif.rdbuf(); + srcif.close(); + dstof.close(); + } + closedir(dir); + return found_subid; +} + +/* C++ replacement for function of the same name + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#867 + * returning an empty string indicates an error */ +std::string unwrapSyntheticPasswordBlob(const std::string& spblob_path, const std::string& handle_str, const userid_t user_id, const void* application_id, const size_t application_id_size, uint32_t auth_token_len) { + std::string disk_decryption_secret_key = ""; + + std::string keystore_alias_subid; + if (!Find_Keystore_Alias_SubID_And_Prep_Files(user_id, keystore_alias_subid)) { + printf("failed to scan keystore alias subid and prep keystore files\n"); + return disk_decryption_secret_key; + } + + // First get the keystore service + sp binder = getKeystoreBinderRetry(); + sp service = interface_cast(binder); + if (service == NULL) { + printf("error: could not connect to keystore service\n"); + return disk_decryption_secret_key; + } + + if (auth_token_len > 0) { + printf("Starting keystore_auth service...\n"); + property_set("ctl.start", "keystore_auth"); + } + + // Read the data from the .spblob file per: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#869 + std::string spblob_file = spblob_path + handle_str + ".spblob"; + std::string spblob_data; + if (!android::base::ReadFileToString(spblob_file, &spblob_data)) { + printf("Failed to read '%s'\n", spblob_file.c_str()); + return disk_decryption_secret_key; + } + unsigned char* byteptr = (unsigned char*)spblob_data.data(); + if (*byteptr != SYNTHETIC_PASSWORD_VERSION && *byteptr != SYNTHETIC_PASSWORD_VERSION_V1) { + printf("Unsupported synthetic password version %i\n", *byteptr); + return disk_decryption_secret_key; + } + const unsigned char* synthetic_password_version = byteptr; + byteptr++; + if (*byteptr != SYNTHETIC_PASSWORD_PASSWORD_BASED) { + printf("spblob data is not SYNTHETIC_PASSWORD_PASSWORD_BASED\n"); + return disk_decryption_secret_key; + } + byteptr++; // Now we're pointing to the blob data itself + if (*synthetic_password_version == SYNTHETIC_PASSWORD_VERSION_V1) { + printf("spblob v1\n"); + /* We're now going to handle decryptSPBlob: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#115 + * Called from https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#879 + * This small function ends up being quite a headache. The call to get data from the keystore basically is not needed in TWRP at this time. + * The keystore data seems to be the serialized data from an entire class in Java. Specifically I think it represents: + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java + * or perhaps + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java + * but the only things we "need" from this keystore are a user ID and the keyAlias which ends up being USRSKEY_synthetic_password_{handle_str} + * the latter of which we already have. We may need to figure out how to get the user ID if we ever support decrypting mulitple users. + * There are 2 calls to a Java decrypt funcion that is overloaded. These 2 calls go in completely different directions despite the seemingly + * similar use of decrypt() and decrypt parameters. To figure out where things were going, I added logging to: + * https://android.googlesource.com/platform/libcore/+/android-8.0.0_r23/ojluni/src/main/java/javax/crypto/Cipher.java#2575 + * Logger.global.severe("Cipher tryCombinations " + prov.getName() + " - " + prov.getInfo()); + * To make logging work in libcore, import java.util.logging.Logger; and either set a better logging level or modify the framework to log everything + * regardless of logging level. This will give you some strings that you can grep for and find the actual crypto provider in use. In our case there were + * 2 different providers in use. The first stage to get the intermediate key used: + * https://android.googlesource.com/platform/external/conscrypt/+/android-8.0.0_r23/common/src/main/java/org/conscrypt/OpenSSLProvider.java + * which is a pretty straight-forward OpenSSL implementation of AES/GCM/NoPadding. */ + // First we personalize as seen https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#102 + void* personalized_application_id = PersonalizedHashBinary(PERSONALISATION_APPLICATION_ID, (const char*)application_id, application_id_size); + if (!personalized_application_id) { + printf("malloc personalized_application_id\n"); + return disk_decryption_secret_key; + } + //printf("personalized application id: "); output_hex((unsigned char*)personalized_application_id, SHA512_DIGEST_LENGTH); printf("\n"); + // Now we'll decrypt using openssl AES/GCM/NoPadding + OpenSSL_add_all_ciphers(); + int actual_size=0, final_size=0; + EVP_CIPHER_CTX *d_ctx = EVP_CIPHER_CTX_new(); + const unsigned char* iv = (const unsigned char*)byteptr; // The IV is the first 12 bytes of the spblob + //printf("iv: "); output_hex((const unsigned char*)iv, 12); printf("\n"); + const unsigned char* cipher_text = (const unsigned char*)byteptr + 12; // The cipher text comes immediately after the IV + //printf("cipher_text: "); output_hex((const unsigned char*)cipher_text, spblob_data.size() - 2 - 12); printf("\n"); + const unsigned char* key = (const unsigned char*)personalized_application_id; // The key is the now personalized copy of the application ID + //printf("key: "); output_hex((const unsigned char*)key, 32); printf("\n"); + EVP_DecryptInit(d_ctx, EVP_aes_256_gcm(), key, iv); + std::vector intermediate_key; + intermediate_key.resize(spblob_data.size() - 2 - 12, '\0'); + EVP_DecryptUpdate(d_ctx, &intermediate_key[0], &actual_size, cipher_text, spblob_data.size() - 2 - 12); + unsigned char tag[AES_BLOCK_SIZE]; + EVP_CIPHER_CTX_ctrl(d_ctx, EVP_CTRL_GCM_SET_TAG, 16, tag); + EVP_DecryptFinal_ex(d_ctx, &intermediate_key[actual_size], &final_size); + EVP_CIPHER_CTX_free(d_ctx); + free(personalized_application_id); + //printf("spblob_data size: %lu actual_size %i, final_size: %i\n", spblob_data.size(), actual_size, final_size); + intermediate_key.resize(actual_size + final_size - 16, '\0');// not sure why we have to trim the size by 16 as I don't see where this is done in Java side + //printf("intermediate key: "); output_hex((const unsigned char*)intermediate_key.data(), intermediate_key.size()); printf("\n"); + + // When using secdis (aka not weaver) you must supply an auth token to the keystore prior to the begin operation + if (auth_token_len > 0) { + /*::keystore::KeyStoreServiceReturnCode auth_result = service->addAuthToken(auth_token, auth_token_len); + if (!auth_result.isOk()) { + // The keystore checks the uid of the calling process and will return a permission denied on this operation for user 0 + printf("keystore error adding auth token\n"); + return disk_decryption_secret_key; + }*/ + // The keystore refuses to allow the root user to supply auth tokens, so we write the auth token to a file earlier and + // run a separate service that runs user the system user to add the auth token. We wait for the auth token file to be + // deleted by the keymaster_auth service and check for a /auth_error file in case of errors. We quit after after a while if + // the /auth_token file never gets deleted. + int auth_wait_count = 20; + while (access("/auth_token", F_OK) == 0 && auth_wait_count-- > 0) + usleep(5000); + if (auth_wait_count == 0 || access("/auth_error", F_OK) == 0) { + printf("error during keymaster_auth service\n"); + /* If you are getting this error, make sure that you have the keymaster_auth service defined in your init scripts, preferrably in init.recovery.{ro.hardware}.rc + * service keystore_auth /sbin/keystore_auth + * disabled + * oneshot + * user system + * group root + * seclabel u:r:recovery:s0 + * + * And check dmesg for error codes regarding this service if needed. */ + return disk_decryption_secret_key; + } + } + + int32_t ret; + + /* We only need a keyAlias which is USRSKEY_synthetic_password_b6f71045af7bd042 which we find and a uid which is -1 or 1000, I forget which + * as the key data will be read again by the begin function later via the keystore. + * The data is in a hidl_vec format which consists of a type and a value. */ + /*::keystore::hidl_vec data; + std::string keystoreid = SYNTHETIC_PASSWORD_KEY_PREFIX; + keystoreid += handle_str; + + ret = service->get(String16(keystoreid.c_str()), user_id, &data); + if (ret < 0) { + printf("Could not connect to keystore service %i\n", ret); + return disk_decryption_secret_key; + } else if (ret != 1 /*android::keystore::ResponseCode::NO_ERROR*//*) { + printf("keystore error: (%d)\n", /*responses[ret],*//* ret); + return disk_decryption_secret_key; + } else { + printf("keystore returned: "); output_hex(&data[0], data.size()); printf("\n"); + }*/ + + // Now we'll break up the intermediate key into the IV (first 12 bytes) and the cipher text (the rest of it). + std::vector nonce = intermediate_key; + nonce.resize(12); + intermediate_key.erase (intermediate_key.begin(),intermediate_key.begin()+12); + //printf("nonce: "); output_hex((const unsigned char*)nonce.data(), nonce.size()); printf("\n"); + //printf("cipher text: "); output_hex((const unsigned char*)intermediate_key.data(), intermediate_key.size()); printf("\n"); + + /* Now we will begin the second decrypt call found in + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#122 + * This time we will use https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java + * and https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java + * First we set some algorithm parameters as seen in two places: + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java#297 + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java#216 */ + size_t maclen = 128; + ::keystore::AuthorizationSetBuilder begin_params; + begin_params.Authorization(::keystore::TAG_ALGORITHM, ::keystore::Algorithm::AES); + begin_params.Authorization(::keystore::TAG_BLOCK_MODE, ::keystore::BlockMode::GCM); + begin_params.Padding(::keystore::PaddingMode::NONE); + begin_params.Authorization(::keystore::TAG_NONCE, nonce); + begin_params.Authorization(::keystore::TAG_MAC_LENGTH, maclen); + //keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES); + //keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); + //keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + //keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits); + ::keystore::hidl_vec entropy; // No entropy is needed for decrypt + entropy.resize(0); + std::string keystore_alias = SYNTHETIC_PASSWORD_KEY_PREFIX; + keystore_alias += keystore_alias_subid; + String16 keystore_alias16(keystore_alias.c_str()); + ::keystore::KeyPurpose purpose = ::keystore::KeyPurpose::DECRYPT; + OperationResult begin_result; + // These parameters are mostly driven by the cipher.init call https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#63 + service->begin(binder, keystore_alias16, purpose, true, begin_params.hidl_data(), entropy, -1, &begin_result); + ret = begin_result.resultCode; + if (ret != 1 /*android::keystore::ResponseCode::NO_ERROR*/) { + printf("keystore begin error: (%d)\n", /*responses[ret],*/ ret); + return disk_decryption_secret_key; + } else { + //printf("keystore begin operation successful\n"); + } + ::keystore::hidl_vec<::keystore::KeyParameter> empty_params; + empty_params.resize(0); + OperationResult update_result; + // The cipher.doFinal call triggers an update to the keystore followed by a finish https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#64 + // See also https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java#208 + service->update(begin_result.token, empty_params, intermediate_key, &update_result); + ret = update_result.resultCode; + if (ret != 1 /*android::keystore::ResponseCode::NO_ERROR*/) { + printf("keystore update error: (%d)\n", /*responses[ret],*/ ret); + return disk_decryption_secret_key; + } else { + //printf("keystore update operation successful\n"); + //printf("keystore update returned: "); output_hex(&update_result.data[0], update_result.data.size()); printf("\n"); // this ends up being the synthetic password + } + // We must use the data in update_data.data before we call finish below or the data will be gone + // The payload data from the keystore update is further personalized at https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#153 + // We now have the disk decryption key! + disk_decryption_secret_key = PersonalizedHash(PERSONALIZATION_FBE_KEY, (const char*)&update_result.data[0], update_result.data.size()); + //printf("disk_decryption_secret_key: '%s'\n", disk_decryption_secret_key.c_str()); + ::keystore::hidl_vec signature; + OperationResult finish_result; + service->finish(begin_result.token, empty_params, signature, entropy, &finish_result); + ret = finish_result.resultCode; + if (ret != 1 /*android::keystore::ResponseCode::NO_ERROR*/) { + printf("keystore finish error: (%d)\n", /*responses[ret],*/ ret); + return disk_decryption_secret_key; + } else { + //printf("keystore finish operation successful\n"); + } + stop_keystore(); + return disk_decryption_secret_key; + } else if (*synthetic_password_version == SYNTHETIC_PASSWORD_VERSION) { + printf("spblob v2\n"); + /* Version 2 of the spblob is basically the same as version 1, but the order of getting the intermediate key and disk decryption key have been flip-flopped + * as seen in https://android.googlesource.com/platform/frameworks/base/+/5025791ac6d1538224e19189397de8d71dcb1a12 + */ + /* First decrypt call found in + * https://android.googlesource.com/platform/frameworks/base/+/android-8.1.0_r18/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#135 + * We will use https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java + * and https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java + * First we set some algorithm parameters as seen in two places: + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java#297 + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java#216 */ + // When using secdis (aka not weaver) you must supply an auth token to the keystore prior to the begin operation + if (auth_token_len > 0) { + /*::keystore::KeyStoreServiceReturnCode auth_result = service->addAuthToken(auth_token, auth_token_len); + if (!auth_result.isOk()) { + // The keystore checks the uid of the calling process and will return a permission denied on this operation for user 0 + printf("keystore error adding auth token\n"); + return disk_decryption_secret_key; + }*/ + // The keystore refuses to allow the root user to supply auth tokens, so we write the auth token to a file earlier and + // run a separate service that runs user the system user to add the auth token. We wait for the auth token file to be + // deleted by the keymaster_auth service and check for a /auth_error file in case of errors. We quit after after a while if + // the /auth_token file never gets deleted. + int auth_wait_count = 20; + while (access("/auth_token", F_OK) == 0 && auth_wait_count-- > 0) + usleep(5000); + if (auth_wait_count == 0 || access("/auth_error", F_OK) == 0) { + printf("error during keymaster_auth service\n"); + /* If you are getting this error, make sure that you have the keymaster_auth service defined in your init scripts, preferrably in init.recovery.{ro.hardware}.rc + * service keystore_auth /sbin/keystore_auth + * disabled + * oneshot + * user system + * group root + * seclabel u:r:recovery:s0 + * + * And check dmesg for error codes regarding this service if needed. */ + return disk_decryption_secret_key; + } + } + int32_t ret; + size_t maclen = 128; + unsigned char* iv = (unsigned char*)byteptr; // The IV is the first 12 bytes of the spblob + ::keystore::hidl_vec iv_hidlvec; + iv_hidlvec.setToExternal((unsigned char*)byteptr, 12); + //printf("iv: "); output_hex((const unsigned char*)iv, 12); printf("\n"); + unsigned char* cipher_text = (unsigned char*)byteptr + 12; // The cipher text comes immediately after the IV + ::keystore::hidl_vec cipher_text_hidlvec; + cipher_text_hidlvec.setToExternal(cipher_text, spblob_data.size() - 14 /* 1 each for version and SYNTHETIC_PASSWORD_PASSWORD_BASED and 12 for the iv */); + ::keystore::AuthorizationSetBuilder begin_params; + begin_params.Authorization(::keystore::TAG_ALGORITHM, ::keystore::Algorithm::AES); + begin_params.Authorization(::keystore::TAG_BLOCK_MODE, ::keystore::BlockMode::GCM); + begin_params.Padding(::keystore::PaddingMode::NONE); + begin_params.Authorization(::keystore::TAG_NONCE, iv_hidlvec); + begin_params.Authorization(::keystore::TAG_MAC_LENGTH, maclen); + ::keystore::hidl_vec entropy; // No entropy is needed for decrypt + entropy.resize(0); + std::string keystore_alias = SYNTHETIC_PASSWORD_KEY_PREFIX; + keystore_alias += keystore_alias_subid; + String16 keystore_alias16(keystore_alias.c_str()); + ::keystore::KeyPurpose purpose = ::keystore::KeyPurpose::DECRYPT; + OperationResult begin_result; + // These parameters are mostly driven by the cipher.init call https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#63 + service->begin(binder, keystore_alias16, purpose, true, begin_params.hidl_data(), entropy, -1, &begin_result); + ret = begin_result.resultCode; + if (ret != 1 /*android::keystore::ResponseCode::NO_ERROR*/) { + printf("keystore begin error: (%d)\n", /*responses[ret],*/ ret); + return disk_decryption_secret_key; + } /*else { + printf("keystore begin operation successful\n"); + }*/ + ::keystore::hidl_vec<::keystore::KeyParameter> empty_params; + empty_params.resize(0); + OperationResult update_result; + // The cipher.doFinal call triggers an update to the keystore followed by a finish https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#64 + // See also https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java#208 + service->update(begin_result.token, empty_params, cipher_text_hidlvec, &update_result); + ret = update_result.resultCode; + if (ret != 1 /*android::keystore::ResponseCode::NO_ERROR*/) { + printf("keystore update error: (%d)\n", /*responses[ret],*/ ret); + return disk_decryption_secret_key; + } /*else { + printf("keystore update operation successful\n"); + printf("keystore update returned: "); output_hex(&update_result.data[0], update_result.data.size()); printf("\n"); // this ends up being the synthetic password + }*/ + //printf("keystore resulting data: "); output_hex((unsigned char*)&update_result.data[0], update_result.data.size()); printf("\n"); + // We must copy the data in update_data.data before we call finish below or the data will be gone + size_t keystore_result_size = update_result.data.size(); + unsigned char* keystore_result = (unsigned char*)malloc(keystore_result_size); + if (!keystore_result) { + printf("malloc on keystore_result\n"); + return disk_decryption_secret_key; + } + memcpy(keystore_result, &update_result.data[0], update_result.data.size()); + //printf("keystore_result data: "); output_hex(keystore_result, keystore_result_size); printf("\n"); + ::keystore::hidl_vec signature; + OperationResult finish_result; + service->finish(begin_result.token, empty_params, signature, entropy, &finish_result); + ret = finish_result.resultCode; + if (ret != 1 /*android::keystore::ResponseCode::NO_ERROR*/) { + printf("keystore finish error: (%d)\n", /*responses[ret],*/ ret); + free(keystore_result); + return disk_decryption_secret_key; + } /*else { + printf("keystore finish operation successful\n"); + }*/ + stop_keystore(); + + /* Now we do the second decrypt call as seen in: + * https://android.googlesource.com/platform/frameworks/base/+/android-8.1.0_r18/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#136 + */ + const unsigned char* intermediate_iv = keystore_result; + //printf("intermediate_iv: "); output_hex((const unsigned char*)intermediate_iv, 12); printf("\n"); + const unsigned char* intermediate_cipher_text = (const unsigned char*)keystore_result + 12; // The cipher text comes immediately after the IV + int cipher_size = keystore_result_size - 12; + //printf("intermediate_cipher_text: "); output_hex((const unsigned char*)intermediate_cipher_text, cipher_size); printf("\n"); + // First we personalize as seen https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java#102 + void* personalized_application_id = PersonalizedHashBinary(PERSONALISATION_APPLICATION_ID, (const char*)application_id, application_id_size); + if (!personalized_application_id) { + printf("malloc personalized_application_id\n"); + free(keystore_result); + return disk_decryption_secret_key; + } + //printf("personalized application id: "); output_hex((unsigned char*)personalized_application_id, SHA512_DIGEST_LENGTH); printf("\n"); + // Now we'll decrypt using openssl AES/GCM/NoPadding + OpenSSL_add_all_ciphers(); + int actual_size=0, final_size=0; + EVP_CIPHER_CTX *d_ctx = EVP_CIPHER_CTX_new(); + const unsigned char* key = (const unsigned char*)personalized_application_id; // The key is the now personalized copy of the application ID + //printf("key: "); output_hex((const unsigned char*)key, 32); printf("\n"); + EVP_DecryptInit(d_ctx, EVP_aes_256_gcm(), key, intermediate_iv); + unsigned char* secret_key = (unsigned char*)malloc(cipher_size); + EVP_DecryptUpdate(d_ctx, secret_key, &actual_size, intermediate_cipher_text, cipher_size); + unsigned char tag[AES_BLOCK_SIZE]; + EVP_CIPHER_CTX_ctrl(d_ctx, EVP_CTRL_GCM_SET_TAG, 16, tag); + EVP_DecryptFinal_ex(d_ctx, secret_key + actual_size, &final_size); + EVP_CIPHER_CTX_free(d_ctx); + free(personalized_application_id); + free(keystore_result); + int secret_key_real_size = actual_size - 16; + //printf("secret key: "); output_hex((const unsigned char*)secret_key, secret_key_real_size); printf("\n"); + // The payload data from the keystore update is further personalized at https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#153 + // We now have the disk decryption key! + disk_decryption_secret_key = PersonalizedHash(PERSONALIZATION_FBE_KEY, (const char*)secret_key, secret_key_real_size); + //printf("disk_decryption_secret_key: '%s'\n", disk_decryption_secret_key.c_str()); + free(secret_key); + return disk_decryption_secret_key; + } + return disk_decryption_secret_key; +} + +}} + +#define PASSWORD_TOKEN_SIZE 32 + +/* C++ replacement for + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#992 + * called here + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#813 */ +bool Get_Secdis(const std::string& spblob_path, const std::string& handle_str, std::string& secdis_data) { + std::string secdis_file = spblob_path + handle_str + ".secdis"; + if (!android::base::ReadFileToString(secdis_file, &secdis_data)) { + printf("Failed to read '%s'\n", secdis_file.c_str()); + return false; + } + //output_hex(secdis_data.data(), secdis_data.size());printf("\n"); + return true; +} + +// C++ replacement for https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#1033 +userid_t fakeUid(const userid_t uid) { + return 100000 + uid; +} + +bool Is_Weaver(const std::string& spblob_path, const std::string& handle_str) { + std::string weaver_file = spblob_path + handle_str + ".weaver"; + struct stat st; + if (stat(weaver_file.c_str(), &st) == 0) + return true; + return false; +} + +bool Free_Return(bool retval, void* weaver_key, password_data_struct* pwd) { + if (weaver_key) + free(weaver_key); + if (pwd->salt) + free(pwd->salt); + if (pwd->password_handle) + free(pwd->password_handle); + return retval; +} + +/* Decrypt_User_Synth_Pass is the TWRP C++ equivalent to spBasedDoVerifyCredential + * https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/LockSettingsService.java#1998 */ +bool Decrypt_User_Synth_Pass(const userid_t user_id, const std::string& Password) { + bool retval = false; + void* weaver_key = NULL; + password_data_struct pwd; + pwd.salt = NULL; + pwd.salt_len = 0; + pwd.password_handle = NULL; + pwd.handle_len = 0; + char application_id[PASSWORD_TOKEN_SIZE + SHA512_DIGEST_LENGTH]; + + uint32_t auth_token_len = 0; + + std::string secret; // this will be the disk decryption key that is sent to vold + std::string token = "!"; // there is no token used for this kind of decrypt, key escrow is handled by weaver + int flags = FLAG_STORAGE_DE; + if (user_id == 0) + flags = FLAG_STORAGE_DE; + else + flags = FLAG_STORAGE_CE; + char spblob_path_char[PATH_MAX]; + sprintf(spblob_path_char, "/data/system_de/%d/spblob/", user_id); + std::string spblob_path = spblob_path_char; + long handle = 0; + std::string handle_str; + // Get the handle: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/LockSettingsService.java#2017 + if (!Find_Handle(spblob_path, handle_str)) { + printf("Error getting handle\n"); + return Free_Return(retval, weaver_key, &pwd); + } + printf("Handle is '%s'\n", handle_str.c_str()); + // Now we begin driving unwrapPasswordBasedSyntheticPassword from: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#758 + // First we read the password data which contains scrypt parameters + if (!Get_Password_Data(spblob_path, handle_str, &pwd)) { + printf("Failed to Get_Password_Data\n"); + return Free_Return(retval, weaver_key, &pwd); + } + //printf("pwd N %i R %i P %i salt ", pwd.scryptN, pwd.scryptR, pwd.scryptP); output_hex((char*)pwd.salt, pwd.salt_len); printf("\n"); + unsigned char password_token[PASSWORD_TOKEN_SIZE]; + //printf("Password: '%s'\n", Password.c_str()); + // The password token is the password scrypted with the parameters from the password data file + if (!Get_Password_Token(&pwd, Password, &password_token[0])) { + printf("Failed to Get_Password_Token\n"); + return Free_Return(retval, weaver_key, &pwd); + } + //output_hex(&password_token[0], PASSWORD_TOKEN_SIZE);printf("\n"); + if (Is_Weaver(spblob_path, handle_str)) { + printf("using weaver\n"); + // BEGIN PIXEL 2 WEAVER + // Get the weaver data from the .weaver file which tells us which slot to use when we ask weaver for the escrowed key + // https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#768 + weaver_data_struct wd; + if (!Get_Weaver_Data(spblob_path, handle_str, &wd)) { + printf("Failed to get weaver data\n"); + return Free_Return(retval, weaver_key, &pwd); + } + // The weaver key is the the password token prefixed with "weaver-key" padded to 128 with nulls with the password token appended then SHA512 + // https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#1059 + weaver_key = PersonalizedHashBinary(PERSONALISATION_WEAVER_KEY, (char*)&password_token[0], PASSWORD_TOKEN_SIZE); + if (!weaver_key) { + printf("malloc error getting weaver_key\n"); + return Free_Return(retval, weaver_key, &pwd); + } + // Now we start driving weaverVerify: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#343 + // Called from https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#776 + android::vold::Weaver weaver; + if (!weaver) { + printf("Failed to get weaver service\n"); + return Free_Return(retval, weaver_key, &pwd); + } + // Get the key size from weaver service + uint32_t weaver_key_size = 0; + if (!weaver.GetKeySize(&weaver_key_size)) { + printf("Failed to get weaver key size\n"); + return Free_Return(retval, weaver_key, &pwd); + } else { + //printf("weaver key size is %u\n", weaver_key_size); + } + //printf("weaver key: "); output_hex((unsigned char*)weaver_key, weaver_key_size); printf("\n"); + // Send the slot from the .weaver file, the computed weaver key, and get the escrowed key data + std::vector weaver_payload; + // TODO: we should return more information about the status including time delays before the next retry + if (!weaver.WeaverVerify(wd.slot, weaver_key, &weaver_payload)) { + printf("failed to weaver verify\n"); + return Free_Return(retval, weaver_key, &pwd); + } + //printf("weaver payload: "); output_hex(&weaver_payload); printf("\n"); + // Done with weaverVerify + // Now we will compute the application ID + // https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#964 + // Called from https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#780 + // The escrowed weaver key data is prefixed with "weaver-pwd" padded to 128 with nulls with the weaver payload appended then SHA512 + void* weaver_secret = PersonalizedHashBinary(PERSONALISATION_WEAVER_PASSWORD, (const char*)weaver_payload.data(), weaver_payload.size()); + //printf("weaver secret: "); output_hex((unsigned char*)weaver_secret, SHA512_DIGEST_LENGTH); printf("\n"); + // The application ID is the password token and weaver secret appended to each other + memcpy((void*)&application_id[0], (void*)&password_token[0], PASSWORD_TOKEN_SIZE); + memcpy((void*)&application_id[PASSWORD_TOKEN_SIZE], weaver_secret, SHA512_DIGEST_LENGTH); + //printf("application ID: "); output_hex((unsigned char*)application_id, PASSWORD_TOKEN_SIZE + SHA512_DIGEST_LENGTH); printf("\n"); + // END PIXEL 2 WEAVER + } else { + printf("using secdis\n"); + std::string secdis_data; + if (!Get_Secdis(spblob_path, handle_str, secdis_data)) { + printf("Failed to get secdis data\n"); + return Free_Return(retval, weaver_key, &pwd); + } + void* secdiscardable = PersonalizedHashBinary(PERSONALISATION_SECDISCARDABLE, (char*)secdis_data.data(), secdis_data.size()); + if (!secdiscardable) { + printf("malloc error getting secdiscardable\n"); + return Free_Return(retval, weaver_key, &pwd); + } + memcpy((void*)&application_id[0], (void*)&password_token[0], PASSWORD_TOKEN_SIZE); + memcpy((void*)&application_id[PASSWORD_TOKEN_SIZE], secdiscardable, SHA512_DIGEST_LENGTH); + + int ret = -1; + bool request_reenroll = false; + android::sp gk_device; + gk_device = ::android::hardware::gatekeeper::V1_0::IGatekeeper::getService(); + if (gk_device == nullptr) { + printf("failed to get gatekeeper service\n"); + return Free_Return(retval, weaver_key, &pwd); + } + if (pwd.handle_len <= 0) { + printf("no password handle supplied\n"); + return Free_Return(retval, weaver_key, &pwd); + } + android::hardware::hidl_vec pwd_handle_hidl; + pwd_handle_hidl.setToExternal(const_cast((const uint8_t *)pwd.password_handle), pwd.handle_len); + void* gk_pwd_token = PersonalizedHashBinary(PERSONALIZATION_USER_GK_AUTH, (char*)&password_token[0], PASSWORD_TOKEN_SIZE); + if (!gk_pwd_token) { + printf("malloc error getting gatekeeper_key\n"); + return Free_Return(retval, weaver_key, &pwd); + } + android::hardware::hidl_vec gk_pwd_token_hidl; + gk_pwd_token_hidl.setToExternal(const_cast((const uint8_t *)gk_pwd_token), SHA512_DIGEST_LENGTH); + android::hardware::Return hwRet = + gk_device->verify(fakeUid(user_id), 0 /* challange */, + pwd_handle_hidl, + gk_pwd_token_hidl, + [&ret, &request_reenroll, &auth_token_len] + (const android::hardware::gatekeeper::V1_0::GatekeeperResponse &rsp) { + ret = static_cast(rsp.code); // propagate errors + if (rsp.code >= android::hardware::gatekeeper::V1_0::GatekeeperStatusCode::STATUS_OK) { + auth_token_len = rsp.data.size(); + request_reenroll = (rsp.code == android::hardware::gatekeeper::V1_0::GatekeeperStatusCode::STATUS_REENROLL); + ret = 0; // all success states are reported as 0 + // The keystore refuses to allow the root user to supply auth tokens, so we write the auth token to a file here and later + // run a separate service that runs as the system user to add the auth token. We wait for the auth token file to be + // deleted by the keymaster_auth service and check for a /auth_error file in case of errors. We quit after a while seconds if + // the /auth_token file never gets deleted. + unlink("/auth_token"); + FILE* auth_file = fopen("/auth_token","wb"); + if (auth_file != NULL) { + fwrite(rsp.data.data(), sizeof(uint8_t), rsp.data.size(), auth_file); + fclose(auth_file); + } else { + printf("failed to open /auth_token for writing\n"); + ret = -2; + } + } else if (rsp.code == android::hardware::gatekeeper::V1_0::GatekeeperStatusCode::ERROR_RETRY_TIMEOUT && rsp.timeout > 0) { + ret = rsp.timeout; + } + } + ); + free(gk_pwd_token); + if (!hwRet.isOk() || ret != 0) { + printf("gatekeeper verification failed\n"); + return Free_Return(retval, weaver_key, &pwd); + } + } + // Now we will handle https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#816 + // Plus we will include the last bit that computes the disk decrypt key found in: + // https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#153 + secret = android::keystore::unwrapSyntheticPasswordBlob(spblob_path, handle_str, user_id, (const void*)&application_id[0], PASSWORD_TOKEN_SIZE + SHA512_DIGEST_LENGTH, auth_token_len); + if (!secret.size()) { + printf("failed to unwrapSyntheticPasswordBlob\n"); + return Free_Return(retval, weaver_key, &pwd); + } + if (!e4crypt_unlock_user_key(user_id, 0, token.c_str(), secret.c_str())) { + printf("e4crypt_unlock_user_key returned fail\n"); + return Free_Return(retval, weaver_key, &pwd); + } + if (!e4crypt_prepare_user_storage(nullptr, user_id, 0, flags)) { + printf("failed to e4crypt_prepare_user_storage\n"); + return Free_Return(retval, weaver_key, &pwd); + } + printf("Decrypted Successfully!\n"); + retval = true; + return Free_Return(retval, weaver_key, &pwd); +} +#endif //HAVE_SYNTH_PWD_SUPPORT int Get_Password_Type(const userid_t user_id, std::string& filename) { + struct stat st; + char spblob_path_char[PATH_MAX]; + sprintf(spblob_path_char, "/data/system_de/%d/spblob/", user_id); + if (stat(spblob_path_char, &st) == 0) { +#ifdef HAVE_SYNTH_PWD_SUPPORT + printf("Using synthetic password method\n"); + std::string spblob_path = spblob_path_char; + std::string handle_str; + if (!Find_Handle(spblob_path, handle_str)) { + printf("Error getting handle\n"); + return 0; + } + printf("Handle is '%s'\n", handle_str.c_str()); + password_data_struct pwd; + if (!Get_Password_Data(spblob_path, handle_str, &pwd)) { + printf("Failed to Get_Password_Data\n"); + return 0; + } + if (pwd.password_type == 1) // In Android this means pattern + return 2; // In TWRP this means pattern + else if (pwd.password_type == 2) // In Android this means PIN or password + return 1; // In TWRP this means PIN or password + return 0; // We'll try the default password +#else + printf("Synthetic password support not present in TWRP\n"); + return -1; +#endif + } std::string path; if (user_id == 0) { path = "/data/system/"; @@ -123,7 +1153,6 @@ int Get_Password_Type(const userid_t user_id, std::string& filename) { path += "/"; } filename = path + "gatekeeper.password.key"; - struct stat st; if (stat(filename.c_str(), &st) == 0 && st.st_size > 0) return 1; filename = path + "gatekeeper.pattern.key"; @@ -134,18 +1163,6 @@ int Get_Password_Type(const userid_t user_id, std::string& filename) { return 0; } -bool Decrypt_DE() { - if (!e4crypt_initialize_global_de()) { // this deals with the overarching device encryption - printf("e4crypt_initialize_global_de returned fail\n"); - return false; - } - if (!e4crypt_init_user0()) { - printf("e4crypt_init_user0 returned fail\n"); - return false; - } - return true; -} - bool Decrypt_User(const userid_t user_id, const std::string& Password) { uint8_t *auth_token; uint32_t auth_token_len; @@ -167,8 +1184,6 @@ bool Decrypt_User(const userid_t user_id, const std::string& Password) { flags = FLAG_STORAGE_DE; else flags = FLAG_STORAGE_CE; - gatekeeper_device_t *device; - ret = gatekeeper_device_initialize(&device); if (Default_Password) { if (!e4crypt_unlock_user_key(user_id, 0, "!", "!")) { printf("e4crypt_unlock_user_key returned fail\n"); @@ -181,8 +1196,15 @@ bool Decrypt_User(const userid_t user_id, const std::string& Password) { printf("Decrypted Successfully!\n"); return true; } - if (ret!=0) + if (stat("/data/system_de/0/spblob", &st) == 0) { +#ifdef HAVE_SYNTH_PWD_SUPPORT + printf("Using synthetic password method\n"); + return Decrypt_User_Synth_Pass(user_id, Password); +#else + printf("Synthetic password support not present in TWRP\n"); return false; +#endif + } printf("password filename is '%s'\n", filename.c_str()); if (stat(filename.c_str(), &st) != 0) { printf("error stat'ing key file: %s\n", strerror(errno)); @@ -194,13 +1216,51 @@ bool Decrypt_User(const userid_t user_id, const std::string& Password) { return false; } bool should_reenroll; - ret = device->verify(device, user_id, 0, (const uint8_t *)handle.c_str(), st.st_size, +#ifdef HAVE_GATEKEEPER1 + bool request_reenroll = false; + android::sp gk_device; + gk_device = ::android::hardware::gatekeeper::V1_0::IGatekeeper::getService(); + if (gk_device == nullptr) + return false; + android::hardware::hidl_vec curPwdHandle; + curPwdHandle.setToExternal(const_cast((const uint8_t *)handle.c_str()), st.st_size); + android::hardware::hidl_vec enteredPwd; + enteredPwd.setToExternal(const_cast((const uint8_t *)Password.c_str()), Password.size()); + + android::hardware::Return hwRet = + gk_device->verify(user_id, 0 /* challange */, + curPwdHandle, + enteredPwd, + [&ret, &request_reenroll, &auth_token, &auth_token_len] + (const android::hardware::gatekeeper::V1_0::GatekeeperResponse &rsp) { + ret = static_cast(rsp.code); // propagate errors + if (rsp.code >= android::hardware::gatekeeper::V1_0::GatekeeperStatusCode::STATUS_OK) { + auth_token = new uint8_t[rsp.data.size()]; + auth_token_len = rsp.data.size(); + memcpy(auth_token, rsp.data.data(), auth_token_len); + request_reenroll = (rsp.code == android::hardware::gatekeeper::V1_0::GatekeeperStatusCode::STATUS_REENROLL); + ret = 0; // all success states are reported as 0 + } else if (rsp.code == android::hardware::gatekeeper::V1_0::GatekeeperStatusCode::ERROR_RETRY_TIMEOUT && rsp.timeout > 0) { + ret = rsp.timeout; + } + } + ); + if (!hwRet.isOk()) { + return false; + } +#else + gatekeeper_device_t *gk_device; + ret = gatekeeper_device_initialize(&gk_device); + if (ret!=0) + return false; + ret = gk_device->verify(gk_device, user_id, 0, (const uint8_t *)handle.c_str(), st.st_size, (const uint8_t *)Password.c_str(), (uint32_t)Password.size(), &auth_token, &auth_token_len, &should_reenroll); if (ret !=0) { printf("failed to verify\n"); return false; } +#endif char token_hex[(auth_token_len*2)+1]; token_hex[(auth_token_len*2)] = 0; uint32_t i; diff --git a/crypto/ext4crypt/Ext4Crypt.cpp b/crypto/ext4crypt/Ext4Crypt.cpp index 8bc419992f..ea5b1cf885 100644 --- a/crypto/ext4crypt/Ext4Crypt.cpp +++ b/crypto/ext4crypt/Ext4Crypt.cpp @@ -41,8 +41,16 @@ #include +#ifdef HAVE_SYNTH_PWD_SUPPORT +#include +#else #include "ext4_crypt.h" +#endif +#ifndef HAVE_LIBKEYUTILS #include "key_control.h" +#else +#include +#endif #include #include "HashPassword.h" diff --git a/crypto/ext4crypt/HashPassword.cpp b/crypto/ext4crypt/HashPassword.cpp index 86e067ebb1..817c984bdf 100644 --- a/crypto/ext4crypt/HashPassword.cpp +++ b/crypto/ext4crypt/HashPassword.cpp @@ -28,16 +28,37 @@ #include #include +#include "HashPassword.h" + #define PASS_PADDING_SIZE 128 #define SHA512_HEX_SIZE SHA512_DIGEST_LENGTH * 2 -std::string HashPassword(const std::string& Password) { - size_t size = PASS_PADDING_SIZE + Password.size(); +void* PersonalizedHashBinary(const char* prefix, const char* key, const size_t key_size) { + size_t size = PASS_PADDING_SIZE + key_size; + unsigned char* buffer = (unsigned char*)calloc(1, size); + if (!buffer) return NULL; // failed to malloc + memcpy((void*)buffer, (void*)prefix, strlen(prefix)); + unsigned char* ptr = buffer + PASS_PADDING_SIZE; + memcpy((void*)ptr, key, key_size); + unsigned char hash[SHA512_DIGEST_LENGTH]; + SHA512_CTX sha512; + SHA512_Init(&sha512); + SHA512_Update(&sha512, buffer, size); + SHA512_Final(hash, &sha512); + free(buffer); + void* ret = malloc(SHA512_DIGEST_LENGTH); + if (!ret) return NULL; // failed to malloc + memcpy(ret, (void*)&hash[0], SHA512_DIGEST_LENGTH); + return ret; +} + +std::string PersonalizedHash(const char* prefix, const char* key, const size_t key_size) { + size_t size = PASS_PADDING_SIZE + key_size; unsigned char* buffer = (unsigned char*)calloc(1, size); - const char* prefix = "Android FBE credential hash"; + if (!buffer) return ""; // failed to malloc memcpy((void*)buffer, (void*)prefix, strlen(prefix)); unsigned char* ptr = buffer + PASS_PADDING_SIZE; - memcpy((void*)ptr, Password.c_str(), Password.size()); + memcpy((void*)ptr, key, key_size); unsigned char hash[SHA512_DIGEST_LENGTH]; SHA512_CTX sha512; SHA512_Init(&sha512); @@ -49,5 +70,15 @@ std::string HashPassword(const std::string& Password) { sprintf(hex_hash + (index * 2), "%02X", hash[index]); hex_hash[128] = 0; std::string ret = hex_hash; + free(buffer); return ret; } + +std::string PersonalizedHash(const char* prefix, const std::string& Password) { + return PersonalizedHash(prefix, Password.c_str(), Password.size()); +} + +std::string HashPassword(const std::string& Password) { + const char* prefix = FBE_PERSONALIZATION; + return PersonalizedHash(prefix, Password); +} diff --git a/crypto/ext4crypt/HashPassword.h b/crypto/ext4crypt/HashPassword.h index d9b5ce5f1f..4be107b510 100644 --- a/crypto/ext4crypt/HashPassword.h +++ b/crypto/ext4crypt/HashPassword.h @@ -19,6 +19,18 @@ #include +#define FBE_PERSONALIZATION "Android FBE credential hash" +#define PERSONALISATION_WEAVER_KEY "weaver-key" +#define PERSONALISATION_WEAVER_PASSWORD "weaver-pwd" +#define PERSONALISATION_APPLICATION_ID "application-id" +#define PERSONALIZATION_FBE_KEY "fbe-key" +#define PERSONALIZATION_USER_GK_AUTH "user-gk-authentication" +#define PERSONALISATION_SECDISCARDABLE "secdiscardable-transform" + +void* PersonalizedHashBinary(const char* prefix, const char* key, const size_t key_size); + +std::string PersonalizedHash(const char* prefix, const char* key, const size_t key_size); +std::string PersonalizedHash(const char* prefix, const std::string& Password); std::string HashPassword(const std::string& Password); #endif diff --git a/crypto/ext4crypt/KeyStorage3.cpp b/crypto/ext4crypt/KeyStorage3.cpp new file mode 100644 index 0000000000..a07212d9df --- /dev/null +++ b/crypto/ext4crypt/KeyStorage3.cpp @@ -0,0 +1,526 @@ +/* + * 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 "KeyStorage3.h" + +#include "Keymaster3.h" +#include "ScryptParameters.h" +#include "Utils.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +//#include + +#include + +#include + +#include +#include + +extern "C" { + +#include "crypto_scrypt.h" +} + +#include +#define ERROR 1 +#define LOG(x) std::cout +#define PLOG(x) std::cout + +namespace android { +namespace vold { +using namespace keystore; + +const KeyAuthentication kEmptyAuthentication{"", ""}; + +static constexpr size_t AES_KEY_BYTES = 32; +static constexpr size_t GCM_NONCE_BYTES = 12; +static constexpr size_t GCM_MAC_BYTES = 16; +static constexpr size_t SALT_BYTES = 1 << 4; +static constexpr size_t SECDISCARDABLE_BYTES = 1 << 14; +static constexpr size_t STRETCHED_BYTES = 1 << 6; + +static constexpr uint32_t AUTH_TIMEOUT = 30; // Seconds + +static const char* kCurrentVersion = "1"; +static const char* kRmPath = "/system/bin/rm"; +static const char* kSecdiscardPath = "/system/bin/secdiscard"; +static const char* kStretch_none = "none"; +static const char* kStretch_nopassword = "nopassword"; +static const std::string kStretchPrefix_scrypt = "scrypt "; +static const char* kHashPrefix_secdiscardable = "Android secdiscardable SHA512"; +static const char* kHashPrefix_keygen = "Android key wrapping key generation SHA512"; +static const char* kFn_encrypted_key = "encrypted_key"; +static const char* kFn_keymaster_key_blob = "keymaster_key_blob"; +static const char* kFn_keymaster_key_blob_upgraded = "keymaster_key_blob_upgraded"; +static const char* kFn_salt = "salt"; +static const char* kFn_secdiscardable = "secdiscardable"; +static const char* kFn_stretching = "stretching"; +static const char* kFn_version = "version"; + +static bool checkSize(const std::string& kind, size_t actual, size_t expected) { + if (actual != expected) { + LOG(ERROR) << "Wrong number of bytes in " << kind << ", expected " << expected << " got " + << actual; + return false; + } + return true; +} + +static std::string hashWithPrefix(char const* prefix, const std::string& tohash) { + SHA512_CTX c; + + SHA512_Init(&c); + // Personalise the hashing by introducing a fixed prefix. + // Hashing applications should use personalization except when there is a + // specific reason not to; see section 4.11 of https://www.schneier.com/skein1.3.pdf + std::string hashingPrefix = prefix; + hashingPrefix.resize(SHA512_CBLOCK); + SHA512_Update(&c, hashingPrefix.data(), hashingPrefix.size()); + SHA512_Update(&c, tohash.data(), tohash.size()); + std::string res(SHA512_DIGEST_LENGTH, '\0'); + SHA512_Final(reinterpret_cast(&res[0]), &c); + return res; +} + +/*static bool generateKeymasterKey(Keymaster& keymaster, const KeyAuthentication& auth, + const std::string& appId, std::string* key) { + auto paramBuilder = AuthorizationSetBuilder() + .AesEncryptionKey(AES_KEY_BYTES * 8) + .Authorization(TAG_BLOCK_MODE, BlockMode::GCM) + .Authorization(TAG_MIN_MAC_LENGTH, GCM_MAC_BYTES * 8) + .Authorization(TAG_PADDING, PaddingMode::NONE) + .Authorization(TAG_APPLICATION_ID, blob2hidlVec(appId)); + if (auth.token.empty()) { + LOG(DEBUG) << "Creating key that doesn't need auth token"; + paramBuilder.Authorization(TAG_NO_AUTH_REQUIRED); + } else { + LOG(DEBUG) << "Auth token required for key"; + if (auth.token.size() != sizeof(hw_auth_token_t)) { + LOG(ERROR) << "Auth token should be " << sizeof(hw_auth_token_t) << " bytes, was " + << auth.token.size() << " bytes"; + return false; + } + const hw_auth_token_t* at = reinterpret_cast(auth.token.data()); + paramBuilder.Authorization(TAG_USER_SECURE_ID, at->user_id); + paramBuilder.Authorization(TAG_USER_AUTH_TYPE, HardwareAuthenticatorType::PASSWORD); + paramBuilder.Authorization(TAG_AUTH_TIMEOUT, AUTH_TIMEOUT); + } + return keymaster.generateKey(paramBuilder, key); +}*/ + +static AuthorizationSet beginParams(const KeyAuthentication& auth, + const std::string& appId) { + auto paramBuilder = AuthorizationSetBuilder() + .Authorization(TAG_BLOCK_MODE, BlockMode::GCM) + .Authorization(TAG_MAC_LENGTH, GCM_MAC_BYTES * 8) + .Authorization(TAG_PADDING, PaddingMode::NONE) + .Authorization(TAG_APPLICATION_ID, blob2hidlVec(appId)); + if (!auth.token.empty()) { + LOG(DEBUG) << "Supplying auth token to Keymaster"; + paramBuilder.Authorization(TAG_AUTH_TOKEN, blob2hidlVec(auth.token)); + } + return paramBuilder; +} + +static bool readFileToString(const std::string& filename, std::string* result) { + if (!android::base::ReadFileToString(filename, result)) { + PLOG(ERROR) << "Failed to read from " << filename; + return false; + } + return true; +} + +static bool writeStringToFile(const std::string& payload, const std::string& filename) { + if (!android::base::WriteStringToFile(payload, filename)) { + PLOG(ERROR) << "Failed to write to " << filename; + return false; + } + return true; +} + +static KeymasterOperation begin(Keymaster& keymaster, const std::string& dir, + KeyPurpose purpose, + const AuthorizationSet &keyParams, + const AuthorizationSet &opParams, + AuthorizationSet* outParams) { + auto kmKeyPath = dir + "/" + kFn_keymaster_key_blob; + std::string kmKey; + if (!readFileToString(kmKeyPath, &kmKey)) return KeymasterOperation(); + AuthorizationSet inParams(keyParams); + inParams.append(opParams.begin(), opParams.end()); + for (;;) { + auto opHandle = keymaster.begin(purpose, kmKey, inParams, outParams); + if (opHandle) { + return opHandle; + } + if (opHandle.errorCode() != ErrorCode::KEY_REQUIRES_UPGRADE) return opHandle; + LOG(DEBUG) << "Upgrading key: " << dir; + std::string newKey; + if (!keymaster.upgradeKey(kmKey, keyParams, &newKey)) return KeymasterOperation(); + // Upgrade the key in memory but do not replace the key in storage + /*auto newKeyPath = dir + "/" + kFn_keymaster_key_blob_upgraded; + if (!writeStringToFile(newKey, newKeyPath)) return KeymasterOperation(); + if (rename(newKeyPath.c_str(), kmKeyPath.c_str()) != 0) { + PLOG(ERROR) << "Unable to move upgraded key to location: " << kmKeyPath; + return KeymasterOperation(); + } + if (!keymaster.deleteKey(kmKey)) { + LOG(ERROR) << "Key deletion failed during upgrade, continuing anyway: " << dir; + }*/ + kmKey = newKey; + LOG(INFO) << "Key upgraded: " << dir; + } +} + +/*static bool encryptWithKeymasterKey(Keymaster& keymaster, const std::string& dir, + const AuthorizationSet &keyParams, + const std::string& message, std::string* ciphertext) { + AuthorizationSet opParams; + AuthorizationSet outParams; + auto opHandle = begin(keymaster, dir, KeyPurpose::ENCRYPT, keyParams, opParams, &outParams); + if (!opHandle) return false; + auto nonceBlob = outParams.GetTagValue(TAG_NONCE); + if (!nonceBlob.isOk()) { + LOG(ERROR) << "GCM encryption but no nonce generated"; + return false; + } + // nonceBlob here is just a pointer into existing data, must not be freed + std::string nonce(reinterpret_cast(&nonceBlob.value()[0]), nonceBlob.value().size()); + if (!checkSize("nonce", nonce.size(), GCM_NONCE_BYTES)) return false; + std::string body; + if (!opHandle.updateCompletely(message, &body)) return false; + + std::string mac; + if (!opHandle.finish(&mac)) return false; + if (!checkSize("mac", mac.size(), GCM_MAC_BYTES)) return false; + *ciphertext = nonce + body + mac; + return true; +}*/ + +static bool decryptWithKeymasterKey(Keymaster& keymaster, const std::string& dir, + const AuthorizationSet &keyParams, + const std::string& ciphertext, std::string* message) { + auto nonce = ciphertext.substr(0, GCM_NONCE_BYTES); + auto bodyAndMac = ciphertext.substr(GCM_NONCE_BYTES); + auto opParams = AuthorizationSetBuilder() + .Authorization(TAG_NONCE, blob2hidlVec(nonce)); + auto opHandle = begin(keymaster, dir, KeyPurpose::DECRYPT, keyParams, opParams, nullptr); + if (!opHandle) return false; + if (!opHandle.updateCompletely(bodyAndMac, message)) return false; + if (!opHandle.finish(nullptr)) return false; + return true; +} + +static std::string getStretching(const KeyAuthentication& auth) { + if (!auth.usesKeymaster()) { + return kStretch_none; + } else if (auth.secret.empty()) { + return kStretch_nopassword; + } else { + char paramstr[PROPERTY_VALUE_MAX]; + + property_get(SCRYPT_PROP, paramstr, SCRYPT_DEFAULTS); + return std::string() + kStretchPrefix_scrypt + paramstr; + } +} + +static bool stretchingNeedsSalt(const std::string& stretching) { + return stretching != kStretch_nopassword && stretching != kStretch_none; +} + +static bool stretchSecret(const std::string& stretching, const std::string& secret, + const std::string& salt, std::string* stretched) { + if (stretching == kStretch_nopassword) { + if (!secret.empty()) { + LOG(WARNING) << "Password present but stretching is nopassword"; + // Continue anyway + } + stretched->clear(); + } else if (stretching == kStretch_none) { + *stretched = secret; + } else if (std::equal(kStretchPrefix_scrypt.begin(), kStretchPrefix_scrypt.end(), + stretching.begin())) { + int Nf, rf, pf; + if (!parse_scrypt_parameters(stretching.substr(kStretchPrefix_scrypt.size()).c_str(), &Nf, + &rf, &pf)) { + LOG(ERROR) << "Unable to parse scrypt params in stretching: " << stretching; + return false; + } + stretched->assign(STRETCHED_BYTES, '\0'); + if (crypto_scrypt(reinterpret_cast(secret.data()), secret.size(), + reinterpret_cast(salt.data()), salt.size(), + 1 << Nf, 1 << rf, 1 << pf, + reinterpret_cast(&(*stretched)[0]), stretched->size()) != 0) { + LOG(ERROR) << "scrypt failed with params: " << stretching; + return false; + } + } else { + LOG(ERROR) << "Unknown stretching type: " << stretching; + return false; + } + return true; +} + +static bool generateAppId(const KeyAuthentication& auth, const std::string& stretching, + const std::string& salt, const std::string& secdiscardable, + std::string* appId) { + std::string stretched; + if (!stretchSecret(stretching, auth.secret, salt, &stretched)) return false; + *appId = hashWithPrefix(kHashPrefix_secdiscardable, secdiscardable) + stretched; + return true; +} + +static bool readRandomBytesOrLog(size_t count, std::string* out) { + auto status = ReadRandomBytes(count, *out); + if (status != OK) { + LOG(ERROR) << "Random read failed with status: " << status; + return false; + } + return true; +} + +static void logOpensslError() { + LOG(ERROR) << "Openssl error: " << ERR_get_error(); +} + +static bool encryptWithoutKeymaster(const std::string& preKey, + const std::string& plaintext, std::string* ciphertext) { + auto key = hashWithPrefix(kHashPrefix_keygen, preKey); + key.resize(AES_KEY_BYTES); + if (!readRandomBytesOrLog(GCM_NONCE_BYTES, ciphertext)) return false; + auto ctx = std::unique_ptr( + EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + if (!ctx) { + logOpensslError(); + return false; + } + if (1 != EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), NULL, + reinterpret_cast(key.data()), + reinterpret_cast(ciphertext->data()))) { + logOpensslError(); + return false; + } + ciphertext->resize(GCM_NONCE_BYTES + plaintext.size() + GCM_MAC_BYTES); + int outlen; + if (1 != EVP_EncryptUpdate(ctx.get(), + reinterpret_cast(&(*ciphertext)[0] + GCM_NONCE_BYTES), &outlen, + reinterpret_cast(plaintext.data()), plaintext.size())) { + logOpensslError(); + return false; + } + if (outlen != static_cast(plaintext.size())) { + LOG(ERROR) << "GCM ciphertext length should be " << plaintext.size() << " was " << outlen; + return false; + } + if (1 != EVP_EncryptFinal_ex(ctx.get(), + reinterpret_cast(&(*ciphertext)[0] + GCM_NONCE_BYTES + plaintext.size()), &outlen)) { + logOpensslError(); + return false; + } + if (outlen != 0) { + LOG(ERROR) << "GCM EncryptFinal should be 0, was " << outlen; + return false; + } + if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, GCM_MAC_BYTES, + reinterpret_cast(&(*ciphertext)[0] + GCM_NONCE_BYTES + plaintext.size()))) { + logOpensslError(); + return false; + } + return true; +} + +static bool decryptWithoutKeymaster(const std::string& preKey, + const std::string& ciphertext, std::string* plaintext) { + if (ciphertext.size() < GCM_NONCE_BYTES + GCM_MAC_BYTES) { + LOG(ERROR) << "GCM ciphertext too small: " << ciphertext.size(); + return false; + } + auto key = hashWithPrefix(kHashPrefix_keygen, preKey); + key.resize(AES_KEY_BYTES); + auto ctx = std::unique_ptr( + EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + if (!ctx) { + logOpensslError(); + return false; + } + if (1 != EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(), NULL, + reinterpret_cast(key.data()), + reinterpret_cast(ciphertext.data()))) { + logOpensslError(); + return false; + } + plaintext->resize(ciphertext.size() - GCM_NONCE_BYTES - GCM_MAC_BYTES); + int outlen; + if (1 != EVP_DecryptUpdate(ctx.get(), + reinterpret_cast(&(*plaintext)[0]), &outlen, + reinterpret_cast(ciphertext.data() + GCM_NONCE_BYTES), plaintext->size())) { + logOpensslError(); + return false; + } + if (outlen != static_cast(plaintext->size())) { + LOG(ERROR) << "GCM plaintext length should be " << plaintext->size() << " was " << outlen; + return false; + } + if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, GCM_MAC_BYTES, + const_cast( + reinterpret_cast(ciphertext.data() + GCM_NONCE_BYTES + plaintext->size())))) { + logOpensslError(); + return false; + } + if (1 != EVP_DecryptFinal_ex(ctx.get(), + reinterpret_cast(&(*plaintext)[0] + plaintext->size()), &outlen)) { + logOpensslError(); + return false; + } + if (outlen != 0) { + LOG(ERROR) << "GCM EncryptFinal should be 0, was " << outlen; + return false; + } + return true; +} + +/*bool storeKey(const std::string& dir, const KeyAuthentication& auth, const std::string& key) { + if (TEMP_FAILURE_RETRY(mkdir(dir.c_str(), 0700)) == -1) { + PLOG(ERROR) << "key mkdir " << dir; + return false; + } + if (!writeStringToFile(kCurrentVersion, dir + "/" + kFn_version)) return false; + std::string secdiscardable; + if (!readRandomBytesOrLog(SECDISCARDABLE_BYTES, &secdiscardable)) return false; + if (!writeStringToFile(secdiscardable, dir + "/" + kFn_secdiscardable)) return false; + std::string stretching = getStretching(auth); + if (!writeStringToFile(stretching, dir + "/" + kFn_stretching)) return false; + std::string salt; + if (stretchingNeedsSalt(stretching)) { + if (ReadRandomBytes(SALT_BYTES, salt) != OK) { + LOG(ERROR) << "Random read failed"; + return false; + } + if (!writeStringToFile(salt, dir + "/" + kFn_salt)) return false; + } + std::string appId; + if (!generateAppId(auth, stretching, salt, secdiscardable, &appId)) return false; + std::string encryptedKey; + if (auth.usesKeymaster()) { + Keymaster keymaster; + if (!keymaster) return false; + std::string kmKey; + if (!generateKeymasterKey(keymaster, auth, appId, &kmKey)) return false; + if (!writeStringToFile(kmKey, dir + "/" + kFn_keymaster_key_blob)) return false; + auto keyParams = beginParams(auth, appId); + if (!encryptWithKeymasterKey(keymaster, dir, keyParams, key, &encryptedKey)) return false; + } else { + if (!encryptWithoutKeymaster(appId, key, &encryptedKey)) return false; + } + if (!writeStringToFile(encryptedKey, dir + "/" + kFn_encrypted_key)) return false; + return true; +}*/ + +bool retrieveKey(const std::string& dir, const KeyAuthentication& auth, std::string* key) { + std::string version; + if (!readFileToString(dir + "/" + kFn_version, &version)) return false; + if (version != kCurrentVersion) { + LOG(ERROR) << "Version mismatch, expected " << kCurrentVersion << " got " << version; + return false; + } + std::string secdiscardable; + if (!readFileToString(dir + "/" + kFn_secdiscardable, &secdiscardable)) return false; + std::string stretching; + if (!readFileToString(dir + "/" + kFn_stretching, &stretching)) return false; + std::string salt; + if (stretchingNeedsSalt(stretching)) { + if (!readFileToString(dir + "/" + kFn_salt, &salt)) return false; + } + std::string appId; + if (!generateAppId(auth, stretching, salt, secdiscardable, &appId)) return false; + std::string encryptedMessage; + if (!readFileToString(dir + "/" + kFn_encrypted_key, &encryptedMessage)) return false; + if (auth.usesKeymaster()) { + Keymaster keymaster; + if (!keymaster) return false; + auto keyParams = beginParams(auth, appId); + if (!decryptWithKeymasterKey(keymaster, dir, keyParams, encryptedMessage, key)) return false; + } else { + if (!decryptWithoutKeymaster(appId, encryptedMessage, key)) return false; + } + return true; +} + +static bool deleteKey(const std::string& dir) { + std::string kmKey; + if (!readFileToString(dir + "/" + kFn_keymaster_key_blob, &kmKey)) return false; + Keymaster keymaster; + if (!keymaster) return false; + if (!keymaster.deleteKey(kmKey)) return false; + return true; +} + +static bool runSecdiscard(const std::string& dir) { + if (ForkExecvp( + std::vector{kSecdiscardPath, "--", + dir + "/" + kFn_encrypted_key, + dir + "/" + kFn_keymaster_key_blob, + dir + "/" + kFn_secdiscardable, + }) != 0) { + LOG(ERROR) << "secdiscard failed"; + return false; + } + return true; +} + +bool runSecdiscardSingle(const std::string& file) { + if (ForkExecvp( + std::vector{kSecdiscardPath, "--", + file}) != 0) { + LOG(ERROR) << "secdiscard failed"; + return false; + } + return true; +} + +static bool recursiveDeleteKey(const std::string& dir) { + if (ForkExecvp(std::vector{kRmPath, "-rf", dir}) != 0) { + LOG(ERROR) << "recursive delete failed"; + return false; + } + return true; +} + +bool destroyKey(const std::string& dir) { + bool success = true; + // Try each thing, even if previous things failed. + success &= deleteKey(dir); + success &= runSecdiscard(dir); + success &= recursiveDeleteKey(dir); + return success; +} + +} // namespace vold +} // namespace android diff --git a/crypto/ext4crypt/KeyStorage3.h b/crypto/ext4crypt/KeyStorage3.h new file mode 100644 index 0000000000..bce6a99c5c --- /dev/null +++ b/crypto/ext4crypt/KeyStorage3.h @@ -0,0 +1,58 @@ +/* + * 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 ANDROID_VOLD_KEYSTORAGE_H +#define ANDROID_VOLD_KEYSTORAGE_H + +#include + +namespace android { +namespace vold { + +// Represents the information needed to decrypt a disk encryption key. +// If "token" is nonempty, it is passed in as a required Gatekeeper auth token. +// If "token" and "secret" are nonempty, "secret" is appended to the application-specific +// binary needed to unlock. +// If only "secret" is nonempty, it is used to decrypt in a non-Keymaster process. +class KeyAuthentication { + public: + KeyAuthentication(std::string t, std::string s) : token{t}, secret{s} {}; + + bool usesKeymaster() const { return !token.empty() || secret.empty(); }; + + const std::string token; + const std::string secret; +}; + +extern const KeyAuthentication kEmptyAuthentication; + +// Create a directory at the named path, and store "key" in it, +// in such a way that it can only be retrieved via Keymaster and +// can be securely deleted. +// It's safe to move/rename the directory after creation. +bool storeKey(const std::string& dir, const KeyAuthentication& auth, const std::string& key); + +// Retrieve the key from the named directory. +bool retrieveKey(const std::string& dir, const KeyAuthentication& auth, std::string* key); + +// Securely destroy the key stored in the named directory and delete the directory. +bool destroyKey(const std::string& dir); + +bool runSecdiscardSingle(const std::string& file); +} // namespace vold +} // namespace android + +#endif diff --git a/crypto/ext4crypt/Keymaster.h b/crypto/ext4crypt/Keymaster.h index 11b3532ad6..bd3f219851 100644 --- a/crypto/ext4crypt/Keymaster.h +++ b/crypto/ext4crypt/Keymaster.h @@ -22,6 +22,7 @@ #include #include +#include "Utils.h" namespace android { namespace vold { diff --git a/crypto/ext4crypt/Keymaster3.cpp b/crypto/ext4crypt/Keymaster3.cpp new file mode 100644 index 0000000000..c72ddd0c35 --- /dev/null +++ b/crypto/ext4crypt/Keymaster3.cpp @@ -0,0 +1,324 @@ +/* + * 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 "Keymaster3.h" + +//#include +#include +#include +#include + +#include +#define ERROR 1 +#define LOG(x) std::cout + +using namespace ::keystore; +using android::hardware::hidl_string; + +namespace android { +namespace vold { + +KeymasterOperation::~KeymasterOperation() { + if (mDevice.get()) mDevice->abort(mOpHandle); +} + +bool KeymasterOperation::updateCompletely(const std::string& input, std::string* output) { + if (output) + output->clear(); + auto it = input.begin(); + uint32_t inputConsumed; + + ErrorCode km_error; + auto hidlCB = [&] (ErrorCode ret, uint32_t _inputConsumed, + const hidl_vec& /*ignored*/, const hidl_vec& _output) { + km_error = ret; + if (km_error != ErrorCode::OK) return; + inputConsumed = _inputConsumed; + if (output) + output->append(reinterpret_cast(&_output[0]), _output.size()); + }; + + while (it != input.end()) { + size_t toRead = static_cast(input.end() - it); + auto inputBlob = blob2hidlVec(reinterpret_cast(&*it), toRead); + auto error = mDevice->update(mOpHandle, hidl_vec(), inputBlob, hidlCB); + if (!error.isOk()) { + LOG(ERROR) << "update failed: " << error.description(); + mDevice = nullptr; + return false; + } + if (km_error != ErrorCode::OK) { + LOG(ERROR) << "update failed, code " << int32_t(km_error); + mDevice = nullptr; + return false; + } + if (inputConsumed > toRead) { + LOG(ERROR) << "update reported too much input consumed"; + mDevice = nullptr; + return false; + } + it += inputConsumed; + } + return true; +} + +bool KeymasterOperation::finish(std::string* output) { + ErrorCode km_error; + auto hidlCb = [&] (ErrorCode ret, const hidl_vec& /*ignored*/, + const hidl_vec& _output) { + km_error = ret; + if (km_error != ErrorCode::OK) return; + if (output) + output->assign(reinterpret_cast(&_output[0]), _output.size()); + }; + auto error = mDevice->finish(mOpHandle, hidl_vec(), hidl_vec(), + hidl_vec(), hidlCb); + mDevice = nullptr; + if (!error.isOk()) { + LOG(ERROR) << "finish failed: " << error.description(); + return false; + } + if (km_error != ErrorCode::OK) { + LOG(ERROR) << "finish failed, code " << int32_t(km_error); + return false; + } + return true; +} + +Keymaster::Keymaster() { + mDevice = ::android::hardware::keymaster::V3_0::IKeymasterDevice::getService(); +} + +/*bool Keymaster::generateKey(const AuthorizationSet& inParams, std::string* key) { + ErrorCode km_error; + auto hidlCb = [&] (ErrorCode ret, const hidl_vec& keyBlob, + const KeyCharacteristics& /*ignored* /) { + km_error = ret; + if (km_error != ErrorCode::OK) return; + if (key) + key->assign(reinterpret_cast(&keyBlob[0]), keyBlob.size()); + }; + + auto error = mDevice->generateKey(inParams.hidl_data(), hidlCb); + if (!error.isOk()) { + LOG(ERROR) << "generate_key failed: " << error.description(); + return false; + } + if (km_error != ErrorCode::OK) { + LOG(ERROR) << "generate_key failed, code " << int32_t(km_error); + return false; + } + return true; +}*/ + +bool Keymaster::deleteKey(const std::string& key) { + LOG(ERROR) << "NOT deleting key in TWRP"; + return false; + /*auto keyBlob = blob2hidlVec(key); + auto error = mDevice->deleteKey(keyBlob); + if (!error.isOk()) { + LOG(ERROR) << "delete_key failed: " << error.description(); + return false; + } + if (ErrorCode(error) != ErrorCode::OK) { + LOG(ERROR) << "delete_key failed, code " << uint32_t(ErrorCode(error)); + return false; + } + return true;*/ +} + +bool Keymaster::upgradeKey(const std::string& oldKey, const AuthorizationSet& inParams, + std::string* newKey) { + auto oldKeyBlob = blob2hidlVec(oldKey); + ErrorCode km_error; + auto hidlCb = [&] (ErrorCode ret, const hidl_vec& upgradedKeyBlob) { + km_error = ret; + if (km_error != ErrorCode::OK) return; + if (newKey) + newKey->assign(reinterpret_cast(&upgradedKeyBlob[0]), + upgradedKeyBlob.size()); + }; + auto error = mDevice->upgradeKey(oldKeyBlob, inParams.hidl_data(), hidlCb); + if (!error.isOk()) { + LOG(ERROR) << "upgrade_key failed: " << error.description(); + return false; + } + if (km_error != ErrorCode::OK) { + LOG(ERROR) << "upgrade_key failed, code " << int32_t(km_error); + return false; + } + return true; +} + +KeymasterOperation Keymaster::begin(KeyPurpose purpose, const std::string& key, + const AuthorizationSet& inParams, + AuthorizationSet* outParams) { + auto keyBlob = blob2hidlVec(key); + uint64_t mOpHandle; + ErrorCode km_error; + + auto hidlCb = [&] (ErrorCode ret, const hidl_vec& _outParams, + uint64_t operationHandle) { + km_error = ret; + if (km_error != ErrorCode::OK) return; + if (outParams) + *outParams = _outParams; + mOpHandle = operationHandle; + }; + + auto error = mDevice->begin(purpose, keyBlob, inParams.hidl_data(), hidlCb); + if (!error.isOk()) { + LOG(ERROR) << "begin failed: " << error.description(); + return KeymasterOperation(ErrorCode::UNKNOWN_ERROR); + } + if (km_error != ErrorCode::OK) { + LOG(ERROR) << "begin failed, code " << int32_t(km_error); + return KeymasterOperation(km_error); + } + return KeymasterOperation(mDevice, mOpHandle); +} +bool Keymaster::isSecure() { + bool _isSecure = false; + auto rc = mDevice->getHardwareFeatures( + [&] (bool isSecure, bool, bool, bool, bool, const hidl_string&, const hidl_string&) { + _isSecure = isSecure; }); + return rc.isOk() && _isSecure; +} + +} // namespace vold +} // namespace android + +using namespace ::android::vold; + +int keymaster_compatibility_cryptfs_scrypt() { + Keymaster dev; + if (!dev) { + LOG(ERROR) << "Failed to initiate keymaster session"; + return -1; + } + return dev.isSecure(); +} + +/*int keymaster_create_key_for_cryptfs_scrypt(uint32_t rsa_key_size, + uint64_t rsa_exponent, + uint32_t ratelimit, + uint8_t* key_buffer, + uint32_t key_buffer_size, + uint32_t* key_out_size) +{ + Keymaster dev; + std::string key; + if (!dev) { + LOG(ERROR) << "Failed to initiate keymaster session"; + return -1; + } + if (!key_buffer || !key_out_size) { + LOG(ERROR) << __FILE__ << ":" << __LINE__ << ":Invalid argument"; + return -1; + } + if (key_out_size) { + *key_out_size = 0; + } + + auto paramBuilder = AuthorizationSetBuilder() + .Authorization(TAG_ALGORITHM, Algorithm::RSA) + .Authorization(TAG_KEY_SIZE, rsa_key_size) + .Authorization(TAG_RSA_PUBLIC_EXPONENT, rsa_exponent) + .Authorization(TAG_PURPOSE, KeyPurpose::SIGN) + .Authorization(TAG_PADDING, PaddingMode::NONE) + .Authorization(TAG_DIGEST, Digest::NONE) + .Authorization(TAG_BLOB_USAGE_REQUIREMENTS, + KeyBlobUsageRequirements::STANDALONE) + .Authorization(TAG_NO_AUTH_REQUIRED) + .Authorization(TAG_MIN_SECONDS_BETWEEN_OPS, ratelimit); + + if (!dev.generateKey(paramBuilder, &key)) { + return -1; + } + + if (key_out_size) { + *key_out_size = key.size(); + } + + if (key_buffer_size < key.size()) { + return -1; + } + + std::copy(key.data(), key.data() + key.size(), key_buffer); + return 0; +} + +int keymaster_sign_object_for_cryptfs_scrypt(const uint8_t* key_blob, + size_t key_blob_size, + uint32_t ratelimit, + const uint8_t* object, + const size_t object_size, + uint8_t** signature_buffer, + size_t* signature_buffer_size) +{ + Keymaster dev; + if (!dev) { + LOG(ERROR) << "Failed to initiate keymaster session"; + return -1; + } + if (!key_blob || !object || !signature_buffer || !signature_buffer_size) { + LOG(ERROR) << __FILE__ << ":" << __LINE__ << ":Invalid argument"; + return -1; + } + + AuthorizationSet outParams; + std::string key(reinterpret_cast(key_blob), key_blob_size); + std::string input(reinterpret_cast(object), object_size); + std::string output; + KeymasterOperation op; + + auto paramBuilder = AuthorizationSetBuilder() + .Authorization(TAG_PADDING, PaddingMode::NONE) + .Authorization(TAG_DIGEST, Digest::NONE); + + while (true) { + op = dev.begin(KeyPurpose::SIGN, key, paramBuilder, &outParams); + if (op.errorCode() == ErrorCode::KEY_RATE_LIMIT_EXCEEDED) { + sleep(ratelimit); + continue; + } else break; + } + + if (op.errorCode() != ErrorCode::OK) { + LOG(ERROR) << "Error starting keymaster signature transaction: " << int32_t(op.errorCode()); + return -1; + } + + if (!op.updateCompletely(input, &output)) { + LOG(ERROR) << "Error sending data to keymaster signature transaction: " + << uint32_t(op.errorCode()); + return -1; + } + + if (!op.finish(&output)) { + LOG(ERROR) << "Error finalizing keymaster signature transaction: " << int32_t(op.errorCode()); + return -1; + } + + *signature_buffer = reinterpret_cast(malloc(output.size())); + if (*signature_buffer == nullptr) { + LOG(ERROR) << "Error allocation buffer for keymaster signature"; + return -1; + } + *signature_buffer_size = output.size(); + std::copy(output.data(), output.data() + output.size(), *signature_buffer); + return 0; +}*/ diff --git a/crypto/ext4crypt/Keymaster3.h b/crypto/ext4crypt/Keymaster3.h new file mode 100644 index 0000000000..4db85519cf --- /dev/null +++ b/crypto/ext4crypt/Keymaster3.h @@ -0,0 +1,148 @@ +/* + * 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 ANDROID_VOLD_KEYMASTER_H +#define ANDROID_VOLD_KEYMASTER_H + +#ifdef __cplusplus + +#include +#include +#include + +#include +#include +#include "Utils.h" + +namespace android { +namespace vold { +using ::android::hardware::keymaster::V3_0::IKeymasterDevice; +using ::keystore::ErrorCode; +using ::keystore::KeyPurpose; +using ::keystore::AuthorizationSet; + +// C++ wrappers to the Keymaster hidl interface. +// This is tailored to the needs of KeyStorage, but could be extended to be +// a more general interface. + +// Wrapper for a Keymaster operation handle representing an +// ongoing Keymaster operation. Aborts the operation +// in the destructor if it is unfinished. Methods log failures +// to LOG(ERROR). +class KeymasterOperation { + public: + ~KeymasterOperation(); + // Is this instance valid? This is false if creation fails, and becomes + // false on finish or if an update fails. + explicit operator bool() { return mError == ErrorCode::OK; } + ErrorCode errorCode() { return mError; } + // Call "update" repeatedly until all of the input is consumed, and + // concatenate the output. Return true on success. + bool updateCompletely(const std::string& input, std::string* output); + // Finish and write the output to this string, unless pointer is null. + bool finish(std::string* output); + // Move constructor + KeymasterOperation(KeymasterOperation&& rhs) { + mDevice = std::move(rhs.mDevice); + mOpHandle = std::move(rhs.mOpHandle); + mError = std::move(rhs.mError); + } + // Construct an object in an error state for error returns + KeymasterOperation() + : mDevice{nullptr}, mOpHandle{0}, + mError {ErrorCode::UNKNOWN_ERROR} {} + // Move Assignment + KeymasterOperation& operator= (KeymasterOperation&& rhs) { + mDevice = std::move(rhs.mDevice); + mOpHandle = std::move(rhs.mOpHandle); + mError = std::move(rhs.mError); + rhs.mError = ErrorCode::UNKNOWN_ERROR; + rhs.mOpHandle = 0; + return *this; + } + + private: + KeymasterOperation(const sp& d, uint64_t h) + : mDevice{d}, mOpHandle{h}, mError {ErrorCode::OK} {} + KeymasterOperation(ErrorCode error) + : mDevice{nullptr}, mOpHandle{0}, + mError {error} {} + sp mDevice; + uint64_t mOpHandle; + ErrorCode mError; + DISALLOW_COPY_AND_ASSIGN(KeymasterOperation); + friend class Keymaster; +}; + +// Wrapper for a Keymaster device for methods that start a KeymasterOperation or are not +// part of one. +class Keymaster { + public: + Keymaster(); + // false if we failed to open the keymaster device. + explicit operator bool() { return mDevice.get() != nullptr; } + // Generate a key in the keymaster from the given params. + //bool generateKey(const AuthorizationSet& inParams, std::string* key); + // If the keymaster supports it, permanently delete a key. + bool deleteKey(const std::string& key); + // Replace stored key blob in response to KM_ERROR_KEY_REQUIRES_UPGRADE. + bool upgradeKey(const std::string& oldKey, const AuthorizationSet& inParams, + std::string* newKey); + // Begin a new cryptographic operation, collecting output parameters if pointer is non-null + KeymasterOperation begin(KeyPurpose purpose, const std::string& key, + const AuthorizationSet& inParams, AuthorizationSet* outParams); + bool isSecure(); + + private: + sp mDevice; + DISALLOW_COPY_AND_ASSIGN(Keymaster); +}; + +} // namespace vold +} // namespace android + +#endif // __cplusplus + + +/* + * The following functions provide C bindings to keymaster services + * needed by cryptfs scrypt. The compatibility check checks whether + * the keymaster implementation is considered secure, i.e., TEE backed. + * The create_key function generates an RSA key for signing. + * The sign_object function signes an object with the given keymaster + * key. + */ +__BEGIN_DECLS + +int keymaster_compatibility_cryptfs_scrypt(); +/*int keymaster_create_key_for_cryptfs_scrypt(uint32_t rsa_key_size, + uint64_t rsa_exponent, + uint32_t ratelimit, + uint8_t* key_buffer, + uint32_t key_buffer_size, + uint32_t* key_out_size); + +int keymaster_sign_object_for_cryptfs_scrypt(const uint8_t* key_blob, + size_t key_blob_size, + uint32_t ratelimit, + const uint8_t* object, + const size_t object_size, + uint8_t** signature_buffer, + size_t* signature_buffer_size);*/ + +__END_DECLS + +#endif diff --git a/crypto/ext4crypt/Utils.h b/crypto/ext4crypt/Utils.h index 8d0445d897..aede203410 100644 --- a/crypto/ext4crypt/Utils.h +++ b/crypto/ext4crypt/Utils.h @@ -24,6 +24,14 @@ #include #include +// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private: +// declarations in a class. +#if !defined(DISALLOW_COPY_AND_ASSIGN) +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete +#endif + namespace android { namespace vold { diff --git a/crypto/ext4crypt/Weaver1.cpp b/crypto/ext4crypt/Weaver1.cpp new file mode 100644 index 0000000000..6d09ec995c --- /dev/null +++ b/crypto/ext4crypt/Weaver1.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2017 Team Win Recovery 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. + */ + +/* To the best of my knowledge there is no native implementation for + * Weaver so I made this by looking at the IWeaver.h file that gets + * compiled by the build system. I took the information from this header + * file and looked at keymaster source to get an idea of the proper way + * to write the functions. + */ + +#include "Weaver1.h" + +//#include +#include +#include +#include + +#include + +#include +#define ERROR 1 +#define LOG(x) std::cout + +using namespace android::hardware::weaver; +using android::hardware::hidl_string; +using ::android::hardware::weaver::V1_0::IWeaver; +using ::android::hardware::weaver::V1_0::WeaverConfig; +using ::android::hardware::weaver::V1_0::WeaverReadStatus; +using ::android::hardware::weaver::V1_0::WeaverReadResponse; +using ::android::hardware::weaver::V1_0::WeaverStatus; +using ::android::hardware::Return; +using ::android::sp; + +namespace android { +namespace vold { + +Weaver::Weaver() { + mDevice = ::android::hardware::weaver::V1_0::IWeaver::getService(); + GottenConfig = false; +} + +bool Weaver::GetConfig() { + if (GottenConfig) + return true; + + WeaverStatus status; + WeaverConfig cfg; + + bool callbackCalled = false; + auto ret = mDevice->getConfig([&](WeaverStatus s, WeaverConfig c) { + callbackCalled = true; + status = s; + cfg = c; + }); + if (ret.isOk() && callbackCalled && status == WeaverStatus::OK) { + config = cfg; + GottenConfig = true; + return true; + } + return false; +} + +bool Weaver::GetSlots(uint32_t* slots) { + if (!GetConfig()) + return false; + *slots = config.slots; + return true; +} + +bool Weaver::GetKeySize(uint32_t* keySize) { + if (!GetConfig()) + return false; + *keySize = config.keySize; + return true; +} + +bool Weaver::GetValueSize(uint32_t* valueSize) { + if (!GetConfig()) + return false; + *valueSize = config.valueSize; + return true; +} + +// TODO: we should return more information about the status including time delays before the next retry +bool Weaver::WeaverVerify(const uint32_t slot, const void* weaver_key, std::vector* payload) { + bool callbackCalled = false; + WeaverReadStatus status; + std::vector readValue; + uint32_t timeout; + uint32_t keySize; + if (!GetKeySize(&keySize)) + return false; + std::vector key; + key.resize(keySize); + uint32_t index = 0; + unsigned char* ptr = (unsigned char*)weaver_key; + for (index = 0; index < keySize; index++) { + key[index] = *ptr; + ptr++; + } + const auto readRet = mDevice->read(slot, key, [&](WeaverReadStatus s, WeaverReadResponse r) { + callbackCalled = true; + status = s; + readValue = r.value; + timeout = r.timeout; + }); + if (readRet.isOk() && callbackCalled && status == WeaverReadStatus::OK && timeout == 0) { + *payload = readValue; + return true; + } + return false; +} + +} // namespace vold +} // namespace android diff --git a/crypto/ext4crypt/Weaver1.h b/crypto/ext4crypt/Weaver1.h new file mode 100644 index 0000000000..22f401e701 --- /dev/null +++ b/crypto/ext4crypt/Weaver1.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 Team Win Recovery 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. + */ + +/* To the best of my knowledge there is no native implementation for + * Weaver so I made this by looking at the IWeaver.h file that gets + * compiled by the build system. I took the information from this header + * file and looked at keymaster source to get an idea of the proper way + * to write the functions. + */ + +#ifndef TWRP_WEAVER_H +#define TWRP_WEAVER_H + +#include +#include +#include + +#include +#include "Utils.h" + +namespace android { +namespace vold { +using ::android::hardware::weaver::V1_0::IWeaver; + +// Wrapper for a Weaver device +class Weaver { + public: + Weaver(); + // false if we failed to open the weaver device. + explicit operator bool() { return mDevice.get() != nullptr; } + + bool GetSlots(uint32_t* slots); + bool GetKeySize(uint32_t* keySize); + bool GetValueSize(uint32_t* valueSize); + // TODO: we should return more information about the status including time delays before the next retry + bool WeaverVerify(const uint32_t slot, const void* weaver_key, std::vector* payload); + + private: + sp mDevice; + hardware::weaver::V1_0::WeaverConfig config; + bool GottenConfig; + + bool GetConfig(); + + DISALLOW_COPY_AND_ASSIGN(Weaver); +}; + +} // namespace vold +} // namespace android + +#endif diff --git a/crypto/ext4crypt/e4policyget.cpp b/crypto/ext4crypt/e4policyget.cpp index d217f18ee4..05de86fe70 100644 --- a/crypto/ext4crypt/e4policyget.cpp +++ b/crypto/ext4crypt/e4policyget.cpp @@ -28,13 +28,10 @@ int main(int argc, char *argv[]) { printf("Must specify a path\n"); return -1; } else { - char e4crypt_policy[EXT4_KEY_DESCRIPTOR_SIZE]; - if (e4crypt_policy_get(argv[1], e4crypt_policy, EXT4_KEY_DESCRIPTOR_SIZE, 0)) - { - char* ptr = tar_policy; - memset(tar_policy, 0, sizeof(tar_policy)); + ext4_encryption_policy eep; + if (e4crypt_policy_get_struct(argv[1], &eep, sizeof(eep))) { char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX]; - policy_to_hex(e4crypt_policy, policy_hex); + policy_to_hex(eep.master_key_descriptor, policy_hex); printf("%s\n", policy_hex); } else { printf("No policy set\n"); diff --git a/crypto/ext4crypt/ext4_crypt.cpp b/crypto/ext4crypt/ext4_crypt.cpp index 029db75672..5a3b4b20c1 100644 --- a/crypto/ext4crypt/ext4_crypt.cpp +++ b/crypto/ext4crypt/ext4_crypt.cpp @@ -22,6 +22,7 @@ */ #include "ext4_crypt.h" +#include "ext4crypt_tar.h" #include #include @@ -41,29 +42,13 @@ #define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy" #define EXT4_KEYREF_DELIMITER ((char)'.') -// ext4enc:TODO Include structure from somewhere sensible -// MUST be in sync with ext4_crypto.c in kernel -#define EXT4_KEY_DESCRIPTOR_SIZE 8 -#define EXT4_KEY_DESCRIPTOR_SIZE_HEX 17 - -struct ext4_encryption_policy { - char version; - char contents_encryption_mode; - char filenames_encryption_mode; - char flags; - char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE]; -} __attribute__((__packed__)); - #define EXT4_ENCRYPTION_MODE_AES_256_XTS 1 #define EXT4_ENCRYPTION_MODE_AES_256_CTS 4 +#define EXT4_ENCRYPTION_MODE_AES_256_HEH 126 #define EXT4_ENCRYPTION_MODE_PRIVATE 127 static int encryption_mode = EXT4_ENCRYPTION_MODE_PRIVATE; -// ext4enc:TODO Get value from somewhere sensible -#define EXT4_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct ext4_encryption_policy) -#define EXT4_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct ext4_encryption_policy) - #define HEX_LOOKUP "0123456789abcdef" extern "C" void policy_to_hex(const char* policy, char* hex) { @@ -146,6 +131,48 @@ extern "C" bool e4crypt_policy_get(const char *directory, char *policy, return true; } +extern "C" void e4crypt_policy_fill_default_struct(ext4_encryption_policy *eep) { + eep->version = 0; + eep->contents_encryption_mode = encryption_mode; + eep->filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS; + eep->flags = 0; + memset((void*)&eep->master_key_descriptor[0], 0, EXT4_KEY_DESCRIPTOR_SIZE); +} + +extern "C" bool e4crypt_policy_set_struct(const char *directory, const ext4_encryption_policy *eep) { + int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (fd == -1) { + printf("failed to open %s\n", directory); + PLOG(ERROR) << "Failed to open directory " << directory; + return false; + } + if (ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, eep)) { + printf("failed to set policy for '%s'\n", directory); + PLOG(ERROR) << "Failed to set encryption policy for " << directory; + close(fd); + return false; + } + close(fd); + return true; +} + +extern "C" bool e4crypt_policy_get_struct(const char *directory, ext4_encryption_policy *eep) { + int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (fd == -1) { + printf("Failed to open '%s'\n", directory); + PLOG(ERROR) << "Failed to open directory " << directory; + return false; + } + memset(eep, 0, sizeof(ext4_encryption_policy)); + if (ioctl(fd, EXT4_IOC_GET_ENCRYPTION_POLICY, eep) != 0) { + PLOG(ERROR) << "Failed to get encryption policy for " << directory; + close(fd); + return false; + } + close(fd); + return true; +} + extern "C" bool e4crypt_set_mode() { const char* mode_file = "/data/unencrypted/mode"; struct stat st; diff --git a/crypto/ext4crypt/ext4crypt_tar.h b/crypto/ext4crypt/ext4crypt_tar.h index 1c9cef0a54..c35d115711 100644 --- a/crypto/ext4crypt/ext4crypt_tar.h +++ b/crypto/ext4crypt/ext4crypt_tar.h @@ -21,8 +21,25 @@ #include #include +// ext4enc:TODO Include structure from somewhere sensible +// MUST be in sync with ext4_crypto.c in kernel +#define EXT4_KEY_DESCRIPTOR_SIZE 8 +#define EXT4_KEY_DESCRIPTOR_SIZE_HEX 17 + +// ext4enc:TODO Get value from somewhere sensible +#define EXT4_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct ext4_encryption_policy) +#define EXT4_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct ext4_encryption_policy) + __BEGIN_DECLS +struct ext4_encryption_policy { + char version; + char contents_encryption_mode; + char filenames_encryption_mode; + char flags; + char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE]; +} __attribute__((__packed__)); + bool lookup_ref_key(const char* policy, char* policy_type); bool lookup_ref_tar(const char* policy_type, char* policy); @@ -31,6 +48,9 @@ bool e4crypt_policy_set(const char *directory, const char *policy, size_t policy_length, int contents_encryption_mode); bool e4crypt_policy_get(const char *directory, char *policy, size_t policy_length, int contents_encryption_mode); +void e4crypt_policy_fill_default_struct(struct ext4_encryption_policy *eep); +bool e4crypt_policy_set_struct(const char *directory, const struct ext4_encryption_policy *eep); +bool e4crypt_policy_get_struct(const char *directory, struct ext4_encryption_policy *eep); bool e4crypt_set_mode(); __END_DECLS diff --git a/crypto/ext4crypt/keystore_auth.cpp b/crypto/ext4crypt/keystore_auth.cpp new file mode 100644 index 0000000000..7d6eb24bff --- /dev/null +++ b/crypto/ext4crypt/keystore_auth.cpp @@ -0,0 +1,90 @@ +/* + Copyright 2018 bigbiff/Dees_Troy TeamWin + This file is part of TWRP/TeamWin Recovery Project. + + TWRP is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + TWRP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TWRP. If not, see . +*/ + +/* The keystore refuses to allow the root user to supply auth tokens, so + * we write the auth token to a file in TWRP and run a separate service + * (this) that runs as the system user to add the auth token. TWRP waits + * for /auth_token to be deleted and also looks for /auth_error to check + * for errors. TWRP will error out after a while if /auth_token does not + * get deleted. */ + +#include +#include + +#include +#include +#include + +#include +#include + +#define LOG_TAG "keystore_auth" + +using namespace android; + +void create_error_file() { + FILE* error_file = fopen("/auth_error", "wb"); + if (error_file == NULL) { + printf("Failed to open /auth_error\n"); + ALOGE("Failed to open /auth_error\n"); + return; + } + fwrite("1", 1, 1, error_file); + fclose(error_file); + unlink("/auth_token"); +} + +int main(int argc, char *argv[]) { + unlink("/auth_error"); + FILE* auth_file = fopen("/auth_token", "rb"); + if (auth_file == NULL) { + printf("Failed to open /auth_token\n"); + ALOGE("Failed to open /auth_token\n"); + create_error_file(); + return -1; + } + // Get the file size + fseek(auth_file, 0, SEEK_END); + int size = ftell(auth_file); + fseek(auth_file, 0, SEEK_SET); + uint8_t auth_token[size]; + fread(auth_token , sizeof(uint8_t), size, auth_file); + fclose(auth_file); + // First get the keystore service + sp sm = defaultServiceManager(); + sp binder = sm->getService(String16("android.security.keystore")); + sp service = interface_cast(binder); + if (service == NULL) { + printf("error: could not connect to keystore service\n"); + ALOGE("error: could not connect to keystore service\n"); + create_error_file(); + return -2; + } + ::keystore::KeyStoreServiceReturnCode auth_result = service->addAuthToken(auth_token, size); + if (!auth_result.isOk()) { + // The keystore checks the uid of the calling process and will return a permission denied on this operation for user 0 + printf("keystore error adding auth token\n"); + ALOGE("keystore error adding auth token\n"); + create_error_file(); + return -3; + } + printf("successfully added auth token to keystore\n"); + ALOGD("successfully added auth token to keystore\n"); + unlink("/auth_token"); + return 0; +} diff --git a/crypto/scrypt/Scrypt-config.mk b/crypto/scrypt/Scrypt-config.mk index bbe10631ed..e33cf26bf8 100644 --- a/crypto/scrypt/Scrypt-config.mk +++ b/crypto/scrypt/Scrypt-config.mk @@ -82,7 +82,7 @@ target_src_files := $(common_src_files) $($(target_arch)_src_files) target_src_files := $(filter-out $($(target_arch)_exclude_files), $(target_src_files)) # Hacks for ARM NEON support -ifeq ($(target_arch),arm) +ifneq (,$(filter $(target_arch), arm arm64)) ifeq ($(ARCH_ARM_HAVE_NEON),true) target_c_flags += $(arm_neon_c_flags) target_src_files += $(arm_neon_src_files) diff --git a/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c b/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c index a3bf052b43..d05ee1bfe4 100644 --- a/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c +++ b/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c @@ -28,7 +28,6 @@ */ #include "scrypt_platform.h" -#include #include #include diff --git a/crypto/scrypt/patches/arm-neon.patch b/crypto/scrypt/patches/arm-neon.patch index 7197f99687..02ff357bee 100644 --- a/crypto/scrypt/patches/arm-neon.patch +++ b/crypto/scrypt/patches/arm-neon.patch @@ -129,7 +129,7 @@ new file mode 100644 index 0000000..a3bf052 --- /dev/null +++ b/lib/crypto/crypto_scrypt-neon.c -@@ -0,0 +1,305 @@ +@@ -0,0 +1,304 @@ +/*- + * Copyright 2009 Colin Percival + * All rights reserved. @@ -160,7 +160,6 @@ index 0000000..a3bf052 + */ +#include "scrypt_platform.h" + -+#include +#include + +#include diff --git a/crypto/scrypt/tests/Android.mk b/crypto/scrypt/tests/Android.mk index c20b41da9a..b49d8a1f26 100644 --- a/crypto/scrypt/tests/Android.mk +++ b/crypto/scrypt/tests/Android.mk @@ -8,9 +8,7 @@ LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_SRC_FILES:= \ scrypt_test.cpp -LOCAL_C_INCLUDES := \ - external/gtest/include \ - external/scrypt/lib/crypto +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../lib/crypto LOCAL_SHARED_LIBRARIES := \ libcrypto diff --git a/crypto/scrypt/tests/scrypt_test.cpp b/crypto/scrypt/tests/scrypt_test.cpp index ffb568df9f..7dfffe349e 100644 --- a/crypto/scrypt/tests/scrypt_test.cpp +++ b/crypto/scrypt/tests/scrypt_test.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "scrypt_test" -#include +#include #include #include diff --git a/crypto/vold_decrypt/Android.mk b/crypto/vold_decrypt/Android.mk index e371c24e78..19c2963c05 100644 --- a/crypto/vold_decrypt/Android.mk +++ b/crypto/vold_decrypt/Android.mk @@ -16,7 +16,6 @@ LOCAL_PATH := $(call my-dir) ifeq ($(TW_INCLUDE_CRYPTO), true) ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),) - ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),false) # Parse TW_CRYPTO_USE_SYSTEM_VOLD @@ -54,6 +53,17 @@ ifeq ($(TW_INCLUDE_CRYPTO), true) cp -f "$(LOCAL_PATH)/$(item)" "$(TARGET_ROOT_OUT)"/; \ fi; \ ) + + ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0) + # Truncate service_name to max 16 characters + LOCAL_POST_INSTALL_CMD += \ + $(foreach item, $(rc_files), \ + if [ -f "$(TARGET_ROOT_OUT)/$(item)" ]; then \ + sed -i 's/\([ \t]*service[ \t]*\)\(.\{16\}\).*\([ \t].*\)/\1\2\3/' "$(TARGET_ROOT_OUT)/$(item)"; \ + fi; \ + ) + endif + include $(BUILD_PREBUILT) @@ -66,16 +76,25 @@ ifeq ($(TW_INCLUDE_CRYPTO), true) endif ifneq ($(services),) - LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_SERVICES='"$(services)"' + ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0) + # Truncate service_name to max 12 characters due to the 4 character prefix + truncated_services := $(foreach item,$(services),$(shell echo -n "$(item)" | sed 's/\(.\{12\}\).*/\1/')) + LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_SERVICES='"$(truncated_services)"' + LOCAL_CFLAGS += -D_USING_SHORT_SERVICE_NAMES + else + LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_SERVICES='"$(services)"' + endif endif ifeq ($(TW_CRYPTO_SYSTEM_VOLD_DEBUG),true) # Enabling strace will expose the password in the strace logs!! LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_DEBUG - endif - - ifeq ($(TW_CRYPTO_SYSTEM_VOLD_DISABLE_TIMEOUT),true) - LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_DISABLE_TIMEOUT + else + ifneq ($(TW_CRYPTO_SYSTEM_VOLD_DEBUG),) + # Specify strace path + LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_DEBUG + LOCAL_CFLAGS += -DVD_STRACE_BIN=\"$(TW_CRYPTO_SYSTEM_VOLD_DEBUG)\" + endif endif LOCAL_SRC_FILES = vold_decrypt.cpp @@ -83,5 +102,4 @@ ifeq ($(TW_INCLUDE_CRYPTO), true) include $(BUILD_STATIC_LIBRARY) endif - endif endif diff --git a/crypto/vold_decrypt/init.recovery.vold_decrypt.hwservicemanager.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.hwservicemanager.rc new file mode 100755 index 0000000000..ab8b4ac4d7 --- /dev/null +++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.hwservicemanager.rc @@ -0,0 +1,18 @@ +# Service names must be less than 16 characters in android-7.1 and +# below. The makefile will truncate service names if needed in any +# init.recovery.vold_decryp.*.rc file found in the vold_decrypt +# directory. +# It cannot however do this for any .rc file(s) that may be +# overridden by the device tree files! + +# The seclabels are not needed when built in Android 8.0 tree +# in 7.1 however the below do not exist, so run them under vold +service sys_hwservicemanager /system/bin/hwservicemanager + user root + group root + setenv PATH /system/bin + setenv LD_LIBRARY_PATH /system/lib64:/system/lib + onrestart setprop hwservicemanager.ready false + disabled + oneshot + seclabel u:r:vold:s0 diff --git a/crypto/vold_decrypt/init.recovery.vold_decrypt.keymaster-3-0.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.keymaster-3-0.rc new file mode 100755 index 0000000000..e9f0b02411 --- /dev/null +++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.keymaster-3-0.rc @@ -0,0 +1,17 @@ +# Service names must be less than 16 characters in android-7.1 and +# below. The makefile will truncate service names if needed in any +# init.recovery.vold_decryp.*.rc file found in the vold_decrypt +# directory. +# It cannot however do this for any .rc file(s) that may be +# overridden by the device tree files! + +# The seclabels are not needed when built in Android 8.0 tree +# in 7.1 however the below do not exist, so run them under vold +service ven_keymaster-3-0 /vendor/bin/hw/android.hardware.keymaster@3.0-service + user root + group root + setenv PATH /vendor/bin:/system/bin + setenv LD_LIBRARY_PATH /vendor/lib64:/system/lib64:/vendor/lib:/system/lib + disabled + oneshot + seclabel u:r:vold:s0 diff --git a/crypto/vold_decrypt/init.recovery.vold_decrypt.qseecomd.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.qseecomd.rc index 06bdebcd3f..08666085d4 100755 --- a/crypto/vold_decrypt/init.recovery.vold_decrypt.qseecomd.rc +++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.qseecomd.rc @@ -5,6 +5,10 @@ on fs chmod 0664 /dev/ion chown system system /dev/ion + +# Oreo has qseecomd in /vendor/bin so add the additional +# service. Only an existing binary will be started, never both. + service sys_qseecomd /system/bin/qseecomd user root group root @@ -12,3 +16,11 @@ service sys_qseecomd /system/bin/qseecomd setenv LD_LIBRARY_PATH /system/lib64:/system/lib disabled oneshot + +service ven_qseecomd /vendor/bin/qseecomd + user root + group root + setenv PATH /vendor/bin:/system/bin + setenv LD_LIBRARY_PATH /vendor/lib64:/system/lib64:/vendor/lib:/system/lib + disabled + oneshot diff --git a/crypto/vold_decrypt/vold_decrypt.cpp b/crypto/vold_decrypt/vold_decrypt.cpp index d535a25142..ded4e7b676 100644 --- a/crypto/vold_decrypt/vold_decrypt.cpp +++ b/crypto/vold_decrypt/vold_decrypt.cpp @@ -16,28 +16,32 @@ along with TWRP. If not, see . */ - #include #include #include #include #include +#include +#include #include +#include +#include -#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG #include #include #include -#endif +#include #include #include #include -#include "../../twcommon.h" +#ifdef _USING_SHORT_SERVICE_NAMES +#include +#endif + #include "../../partitions.hpp" #include "../../twrp-functions.hpp" -#include "../../gui/gui.hpp" using namespace std; @@ -45,6 +49,10 @@ extern "C" { #include } +#include "vold_decrypt.h" + +namespace { + /* Timeouts as defined by ServiceManager */ /* The maximum amount of time to wait for a service to start or stop, @@ -55,51 +63,90 @@ extern "C" { #define SLEEP_MIN_USEC 200000 /* 200 msec */ -#define LOGDECRYPT(...) do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]" __VA_ARGS__); fflush(fp_kmsg); } } while (0) -#define LOGDECRYPT_KMSG(...) do { if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]" __VA_ARGS__); fflush(fp_kmsg); } } while (0) +/* vold response codes defined in ResponseCode.h */ +// 200 series - Requested action has been successfully completed +#define COMMAND_OKAY 200 +#define PASSWORD_TYPE_RESULT 213 -#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES -typedef struct { - string service_name; - string twrp_svc_name; - bool is_running; - bool resume; -} AdditionalService; -#endif + +#define LOGINFO(...) do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]I:" __VA_ARGS__); fflush(fp_kmsg); } } while (0) +#define LOGKMSG(...) do { if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]K:" __VA_ARGS__); fflush(fp_kmsg); } } while (0) +#define LOGERROR(...) do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]E:" __VA_ARGS__); fflush(fp_kmsg); } } while (0) FILE *fp_kmsg = NULL; -bool has_timeout = false; + +/* Debugging Functions */ #ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG + +#ifndef VD_STRACE_BIN +#define VD_STRACE_BIN "/sbin/strace" +#endif + bool has_strace = false; +pid_t pid_strace = 0; -pid_t strace_init(void) { - if (!has_strace) - return -1; +void Strace_init_Start(void) { + has_strace = TWFunc::Path_Exists(VD_STRACE_BIN); + if (!has_strace) { + LOGINFO("strace binary (%s) not found, disabling strace in vold_decrypt!\n", VD_STRACE_BIN); + return; + } pid_t pid; switch(pid = fork()) { case -1: - LOGDECRYPT_KMSG("forking strace_init failed: %d!\n", errno); - return -1; + LOGKMSG("forking strace_init failed: %d (%s)!\n", errno, strerror(errno)); + return; case 0: // child - execl("/sbin/strace", "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", "/tmp/strace_init.log", "-p", "1" , NULL); - LOGDECRYPT_KMSG("strace_init fork failed: %d!\n", errno); + execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", "/tmp/strace_init.log", "-p", "1" , NULL); + LOGKMSG("strace_init fork failed: %d (%s)!\n", errno, strerror(errno)); exit(-1); default: - LOGDECRYPT_KMSG("Starting strace_init (pid=%d)\n", pid); - return pid; + LOGKMSG("Starting strace_init (pid=%d)\n", pid); + pid_strace = pid; + return; } } -#endif + +void Strace_init_Stop(void) { + if (pid_strace > 0) { + LOGKMSG("Stopping strace_init (pid=%d)\n", pid_strace); + int timeout; + int status; + pid_t retpid = waitpid(pid_strace, &status, WNOHANG); + + kill(pid_strace, SIGTERM); + for (timeout = 5; retpid == 0 && timeout; --timeout) { + sleep(1); + retpid = waitpid(pid_strace, &status, WNOHANG); + } + if (retpid) + LOGKMSG("strace_init terminated successfully\n"); + else { + // SIGTERM didn't work, kill it instead + kill(pid_strace, SIGKILL); + for (timeout = 5; retpid == 0 && timeout; --timeout) { + sleep(1); + retpid = waitpid(pid_strace, &status, WNOHANG); + } + if (retpid) + LOGKMSG("strace_init killed successfully\n"); + else + LOGKMSG("strace_init took too long to kill, may be a zombie process\n"); + } + } +} +#endif // TW_CRYPTO_SYSTEM_VOLD_DEBUG + /* Convert a binary key of specified length into an ascii hex string equivalent, * without the leading 0x and with null termination * * Original code from cryptfs.c */ -string convert_key_to_hex_ascii(string master_key) { +string convert_key_to_hex_ascii(const string& master_key) { size_t i; unsigned char nibble; string master_key_ascii = ""; @@ -117,7 +164,46 @@ string convert_key_to_hex_ascii(string master_key) { return master_key_ascii; } -string wait_for_property(string property_name, int utimeout = SLEEP_MAX_USEC, string expected_value = "not_empty") { +/* Helper Functions */ +#define PATH_EXISTS(path) (access(path, F_OK) >= 0) + +int vrename(const string& oldname, const string& newname, bool verbose = false) { + const char *old_name = oldname.c_str(); + const char *new_name = newname.c_str(); + + if (!PATH_EXISTS(old_name)) + return 0; + + if (rename(old_name, new_name) < 0) { + LOGERROR("Moving %s to %s failed: %d (%s)\n", old_name, new_name, errno, strerror(errno)); + return -1; + } else if (verbose) + LOGINFO("Renamed %s to %s\n", old_name, new_name); + else + LOGKMSG("Renamed %s to %s\n", old_name, new_name); + return 0; +} + +int vsymlink(const string& oldname, const string& newname, bool verbose = false) { + const char *old_name = oldname.c_str(); + const char *new_name = newname.c_str(); + + if (!PATH_EXISTS(old_name)) + return 0; + + if (symlink(old_name, new_name) < 0) { + LOGERROR("Symlink %s -> %s failed: %d (%s)\n", new_name, old_name, errno, strerror(errno)); + return -1; + } else if (verbose) + LOGINFO("Symlinked %s -> %s\n", new_name, old_name); + else + LOGKMSG("Symlinked %s -> %s\n", new_name, old_name); + return 0; +} + + +/* Properties and Services Functions */ +string Wait_For_Property(const string& property_name, int utimeout = SLEEP_MAX_USEC, const string& expected_value = "not_empty") { char prop_value[PROPERTY_VALUE_MAX]; if (expected_value == "not_empty") { @@ -125,7 +211,7 @@ string wait_for_property(string property_name, int utimeout = SLEEP_MAX_USEC, st property_get(property_name.c_str(), prop_value, "error"); if (strcmp(prop_value, "error") != 0) break; - LOGDECRYPT_KMSG("waiting for %s to get set\n", property_name.c_str()); + LOGKMSG("waiting for %s to get set\n", property_name.c_str()); utimeout -= SLEEP_MIN_USEC; usleep(SLEEP_MIN_USEC);; } @@ -135,7 +221,7 @@ string wait_for_property(string property_name, int utimeout = SLEEP_MAX_USEC, st property_get(property_name.c_str(), prop_value, "error"); if (strcmp(prop_value, expected_value.c_str()) == 0) break; - LOGDECRYPT_KMSG("waiting for %s to change from '%s' to '%s'\n", property_name.c_str(), prop_value, expected_value.c_str()); + LOGKMSG("waiting for %s to change from '%s' to '%s'\n", property_name.c_str(), prop_value, expected_value.c_str()); utimeout -= SLEEP_MIN_USEC; usleep(SLEEP_MIN_USEC);; } @@ -145,179 +231,84 @@ string wait_for_property(string property_name, int utimeout = SLEEP_MAX_USEC, st return prop_value; } -bool Service_Exists(string initrc_svc) { +string Get_Service_State(const string& initrc_svc) { char prop_value[PROPERTY_VALUE_MAX]; string init_svc = "init.svc." + initrc_svc; property_get(init_svc.c_str(), prop_value, "error"); - return (strcmp(prop_value, "error") != 0); + return prop_value; } -bool Is_Service_Running(string initrc_svc) { - char prop_value[PROPERTY_VALUE_MAX]; - string init_svc = "init.svc." + initrc_svc; - property_get(init_svc.c_str(), prop_value, "error"); - return (strcmp(prop_value, "running") == 0); +bool Service_Exists(const string& initrc_svc) { + return (Get_Service_State(initrc_svc) != "error"); } -bool Is_Service_Stopped(string initrc_svc) { - char prop_value[PROPERTY_VALUE_MAX]; - string init_svc = "init.svc." + initrc_svc; - property_get(init_svc.c_str(), prop_value, "error"); - return (strcmp(prop_value, "stopped") == 0); +bool Is_Service_Running(const string& initrc_svc) { + return (Get_Service_State(initrc_svc) == "running"); } -bool Start_Service(string initrc_svc, int utimeout = SLEEP_MAX_USEC) { +bool Is_Service_Stopped(const string& initrc_svc) { + return (Get_Service_State(initrc_svc) == "stopped"); +} + +bool Start_Service(const string& initrc_svc, int utimeout = SLEEP_MAX_USEC) { string res = "error"; string init_svc = "init.svc." + initrc_svc; property_set("ctl.start", initrc_svc.c_str()); - res = wait_for_property(init_svc, utimeout, "running"); + res = Wait_For_Property(init_svc, utimeout, "running"); - LOGDECRYPT("Start service %s: %s.\n", initrc_svc.c_str(), res.c_str()); + LOGINFO("Start service %s: %s.\n", initrc_svc.c_str(), res.c_str()); return (res == "running"); } -bool Stop_Service(string initrc_svc, int utimeout = SLEEP_MAX_USEC) { +bool Stop_Service(const string& initrc_svc, int utimeout = SLEEP_MAX_USEC) { string res = "error"; if (Service_Exists(initrc_svc)) { string init_svc = "init.svc." + initrc_svc; property_set("ctl.stop", initrc_svc.c_str()); - res = wait_for_property(init_svc, utimeout, "stopped"); - LOGDECRYPT("Stop service %s: %s.\n", initrc_svc.c_str(), res.c_str()); + res = Wait_For_Property(init_svc, utimeout, "stopped"); + LOGINFO("Stop service %s: %s.\n", initrc_svc.c_str(), res.c_str()); } return (res == "stopped"); } -void output_dmesg_to_recoverylog(void) { - TWFunc::Exec_Cmd( - "echo \"---- DMESG LOG FOLLOWS ----\";" - "dmesg | grep 'DECRYPT\\|vold\\|qseecom\\|QSEECOM\\|keymaste\\|keystore\\|cmnlib';" - "echo \"---- DMESG LOG ENDS ----\"" - ); -} -void set_needed_props(void) { - // vold won't start without ro.storage_structure on Kitkat - string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk"); - int sdkver = 20; - if (!sdkverstr.empty()) { - sdkver = atoi(sdkverstr.c_str()); - } - if (sdkver <= 19) { - string ro_storage_structure = TWFunc::System_Property_Get("ro.storage_structure"); - if (!ro_storage_structure.empty()) - property_set("ro.storage_structure", ro_storage_structure.c_str()); - } +/* Vendor, Firmware and fstab symlink Functions */ +bool is_Vendor_Mounted(void) { + static int is_mounted = -1; + if (is_mounted < 0) + is_mounted = PartitionManager.Is_Mounted_By_Path("/vendor") ? 1 : 0; + return is_mounted; } -string vdc_cryptfs_cmd(string log_name) { - string cmd = "LD_LIBRARY_PATH=/system/lib64:/system/lib /system/bin/vdc cryptfs"; - -#ifndef TW_CRYPTO_SYSTEM_VOLD_DEBUG - (void)log_name; // do nothing, but get rid of compiler warning in non debug builds -#else - if (has_timeout && has_strace) - cmd = "/sbin/strace -q -tt -ff -v -y -s 1000 -o /tmp/strace_vdc_" + log_name + " /sbin/timeout -t 30 -s KILL env " + cmd; - else if (has_strace) - cmd = "/sbin/strace -q -tt -ff -v -y -s 1000 -o /tmp/strace_vdc_" + log_name + " -E " + cmd; - else -#endif - if (has_timeout) - cmd = "/sbin/timeout -t 30 -s KILL env " + cmd; - - return cmd; +bool is_Firmware_Mounted(void) { + static int is_mounted = -1; + if (is_mounted < 0) + is_mounted = PartitionManager.Is_Mounted_By_Path("/firmware") ? 1 : 0; + return is_mounted; } -int run_vdc(string Password) { - int res = -1; - struct timeval t1, t2; - string vdc_res; - int vdc_r1, vdc_r2, vdc_r3; - - LOGDECRYPT("About to run vdc...\n"); - - // Wait for vold connection - gettimeofday(&t1, NULL); - t2 = t1; - while ((t2.tv_sec - t1.tv_sec) < 5) { - vdc_res.clear(); - // cryptfs getpwtype returns: R1=213(PasswordTypeResult) R2=? R3="password", "pattern", "pin", "default" - res = TWFunc::Exec_Cmd(vdc_cryptfs_cmd("connect") + " getpwtype", vdc_res); - std::replace(vdc_res.begin(), vdc_res.end(), '\n', ' '); // remove newline(s) - vdc_r1 = vdc_r2 = vdc_r3 = -1; - sscanf(vdc_res.c_str(), "%d", &vdc_r1); - if (vdc_r1 == 213) { - char str_res[sizeof(int) + 1]; - snprintf(str_res, sizeof(str_res), "%d", res); - vdc_res += "ret="; - vdc_res += str_res; - res = 0; - break; - } - LOGDECRYPT("Retrying connection to vold\n"); - usleep(SLEEP_MIN_USEC); // vdc usually usleep(10000), but that causes too many unnecessary attempts - gettimeofday(&t2, NULL); - } - - if (res != 0) - return res; - - LOGDECRYPT("Connected to vold (%s)\n", vdc_res.c_str()); - - // Input password from GUI, or default password - vdc_res.clear(); - res = TWFunc::Exec_Cmd(vdc_cryptfs_cmd("passwd") + " checkpw '" + Password + "'", vdc_res); - std::replace(vdc_res.begin(), vdc_res.end(), '\n', ' '); // remove newline(s) - LOGDECRYPT("vdc cryptfs result (passwd): %s (ret=%d)\n", vdc_res.c_str(), res); - vdc_r1 = vdc_r2 = vdc_r3 = -1; - sscanf(vdc_res.c_str(), "%d %d %d", &vdc_r1, &vdc_r2, &vdc_r3); - - if (vdc_r3 != 0) { - // try falling back to Lollipop hex passwords - string hexPassword = convert_key_to_hex_ascii(Password); - vdc_res.clear(); - res = TWFunc::Exec_Cmd(vdc_cryptfs_cmd("hex_pw") + " checkpw '" + hexPassword + "'", vdc_res); - std::replace(vdc_res.begin(), vdc_res.end(), '\n', ' '); // remove newline(s) - LOGDECRYPT("vdc cryptfs result (hex_pw): %s (ret=%d)\n", vdc_res.c_str(), res); - vdc_r1 = vdc_r2 = vdc_r3 = -1; - sscanf(vdc_res.c_str(), "%d %d %d", &vdc_r1, &vdc_r2, &vdc_r3); - } - - // vdc's return value is dependant upon source origin, it will either - // return 0 or vdc_r1, so disregard and focus on decryption instead - if (vdc_r3 == 0) { - // Decryption successful wait for crypto blk dev - wait_for_property("ro.crypto.fs_crypto_blkdev"); - res = 0; - } else { - res = -1; - } - - return res; +bool will_VendorBin_Be_Symlinked(void) { + return (!is_Vendor_Mounted() && TWFunc::Path_Exists("/system/vendor")); } bool Symlink_Vendor_Folder(void) { bool is_vendor_symlinked = false; - if (PartitionManager.Is_Mounted_By_Path("/vendor")) { - LOGDECRYPT("vendor partition mounted, skipping /vendor substitution\n"); + if (is_Vendor_Mounted()) { + LOGINFO("vendor partition mounted, skipping /vendor substitution\n"); } else if (TWFunc::Path_Exists("/system/vendor")) { - LOGDECRYPT("Symlinking vendor folder...\n"); - if (TWFunc::Path_Exists("/vendor") && rename("/vendor", "/vendor-orig") != 0) { - LOGDECRYPT("Failed to rename original /vendor folder: %s\n", strerror(errno)); - } else { + LOGINFO("Symlinking vendor folder...\n"); + if (!TWFunc::Path_Exists("/vendor") || vrename("/vendor", "/vendor-orig") == 0) { TWFunc::Recursive_Mkdir("/vendor/firmware/keymaster"); - LOGDECRYPT_KMSG("Symlinking /system/vendor/lib64 to /vendor/lib64 (res=%d)\n", - symlink("/system/vendor/lib64", "/vendor/lib64") - ); - LOGDECRYPT_KMSG("Symlinking /system/vendor/lib to /vendor/lib (res=%d)\n", - symlink("/system/vendor/lib", "/vendor/lib") - ); + vsymlink("/system/vendor/lib64", "/vendor/lib64", true); + vsymlink("/system/vendor/lib", "/vendor/lib", true); + vsymlink("/system/vendor/bin", "/vendor/bin", true); is_vendor_symlinked = true; property_set("vold_decrypt.symlinked_vendor", "1"); } @@ -328,19 +319,18 @@ bool Symlink_Vendor_Folder(void) { void Restore_Vendor_Folder(void) { property_set("vold_decrypt.symlinked_vendor", "0"); TWFunc::removeDir("/vendor", false); - rename("/vendor-orig", "/vendor"); + vrename("/vendor-orig", "/vendor"); } bool Symlink_Firmware_Folder(void) { bool is_firmware_symlinked = false; - if (PartitionManager.Is_Mounted_By_Path("/firmware")) { - LOGDECRYPT("firmware partition mounted, skipping /firmware substitution\n"); - } else { - LOGDECRYPT("Symlinking firmware folder...\n"); - if (TWFunc::Path_Exists("/firmware") && rename("/firmware", "/firmware-orig") != 0) { - LOGDECRYPT("Failed to rename original /firmware folder: %s\n", strerror(errno)); - } else { + if (is_Firmware_Mounted()) { + LOGINFO("firmware partition mounted, skipping /firmware substitution\n"); + } + else { + LOGINFO("Symlinking firmware folder...\n"); + if (!TWFunc::Path_Exists("/firmware") || vrename("/firmware", "/firmware-orig") == 0) { TWFunc::Recursive_Mkdir("/firmware/image"); is_firmware_symlinked = true; property_set("vold_decrypt.symlinked_firmware", "1"); @@ -352,103 +342,701 @@ bool Symlink_Firmware_Folder(void) { void Restore_Firmware_Folder(void) { property_set("vold_decrypt.symlinked_firmware", "0"); TWFunc::removeDir("/firmware", false); - rename("/firmware-orig", "/firmware"); + vrename("/firmware-orig", "/firmware"); +} + +int Find_Firmware_Files(const string& Path, vector *FileList) { + int ret; + DIR* d; + struct dirent* de; + string FileName; + + d = opendir(Path.c_str()); + if (d == NULL) { + closedir(d); + return -1; + } + while ((de = readdir(d)) != NULL) { + if (de->d_type == DT_DIR) { + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + FileName = Path + "/" + de->d_name; + ret = Find_Firmware_Files(FileName, FileList); + if (ret < 0) + return -1; + } else if (de->d_type == DT_REG) { + if (fnmatch("keymaste*.*", de->d_name, 0) == 0 || fnmatch("cmnlib.*", de->d_name, 0) == 0) { + FileName = Path + "/" + de->d_name; + FileList->push_back(FileName); + } + } + } + closedir(d); + return 0; } void Symlink_Firmware_Files(bool is_vendor_symlinked, bool is_firmware_symlinked) { if (!is_vendor_symlinked && !is_firmware_symlinked) return; - LOGDECRYPT("Symlinking firmware files...\n"); - string result_of_find; - TWFunc::Exec_Cmd("find /system -name keymaste*.* -type f -o -name cmnlib.* -type f 2>/dev/null", result_of_find); + LOGINFO("Symlinking firmware files...\n"); - stringstream ss(result_of_find); - string line; - int count = 0; + vector FirmwareFiles; + Find_Firmware_Files("/system", &FirmwareFiles); - while(getline(ss, line)) { - const char *fwfile = line.c_str(); - string base_name = TWFunc::Get_Filename(line); - count++; + for (size_t i = 0; i < FirmwareFiles.size(); ++i) { + string base_name = TWFunc::Get_Filename(FirmwareFiles[i]); - if (is_firmware_symlinked) { - LOGDECRYPT_KMSG("Symlinking %s to /firmware/image/ (res=%d)\n", fwfile, - symlink(fwfile, ("/firmware/image/" + base_name).c_str()) - ); - } + if (is_firmware_symlinked) + vsymlink(FirmwareFiles[i], "/firmware/image/" + base_name); if (is_vendor_symlinked) { - LOGDECRYPT_KMSG("Symlinking %s to /vendor/firmware/ (res=%d)\n", fwfile, - symlink(fwfile, ("/vendor/firmware/" + base_name).c_str()) - ); + vsymlink(FirmwareFiles[i], "/vendor/firmware/" + base_name); + vsymlink(FirmwareFiles[i], "/vendor/firmware/keymaster/" + base_name); + } + } + LOGINFO("%d file(s) symlinked.\n", (int)FirmwareFiles.size()); +} - LOGDECRYPT_KMSG("Symlinking %s to /vendor/firmware/keymaster/ (res=%d)\n", fwfile, - symlink(fwfile, ("/vendor/firmware/keymaster/" + base_name).c_str()) - ); +// Android 8.0 fs_mgr checks for "/sbin/recovery", in which case it will +// use /etc/recovery.fstab -> symlink it temporarily. Reference: +// https://android.googlesource.com/platform/system/core/+/android-8.0.0_r17/fs_mgr/fs_mgr_fstab.cpp#693 +bool Symlink_Recovery_Fstab(void) { + bool is_fstab_symlinked = false; + + if (vrename("/etc/recovery.fstab", "/etc/recovery-fstab-orig") == 0) { + is_fstab_symlinked = true; + + // now attempt to symlink to /fstab.{ro.hardware}, but even if that + // fails, keep TWRP's fstab hidden since it cannot be parsed by fs_mgr + char prop_value[PROPERTY_VALUE_MAX]; + property_get("ro.hardware", prop_value, "error"); + if (strcmp(prop_value, "error")) { + string fstab_device = "/fstab."; fstab_device += prop_value; + vsymlink(fstab_device, "/etc/recovery.fstab"); } } - LOGDECRYPT("%d file(s) symlinked.\n", count); + return is_fstab_symlinked; } +void Restore_Recovery_Fstab(void) { + unlink("/etc/recovery.fstab"); + vrename("/etc/recovery-fstab-orig", "/etc/recovery.fstab"); +} + + +/* Additional Services Functions */ #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES -vector Get_List_Of_Additional_Services (void) { +typedef struct { + string Service_Name; + string Service_Path; + string Service_Binary; + + string VOLD_Service_Name; + string TWRP_Service_Name; + bool is_running; + bool resume; + bool bin_exists; + bool svc_exists; +} AdditionalService; + +typedef struct { + string Service_Name; + string Service_Path; + string Service_Binary; +} RC_Service; + +// expand_props() courtesy of platform_system_core_init_util.cpp +bool expand_props(const std::string& src, std::string* dst) { + const char* src_ptr = src.c_str(); + + if (!dst) { + return false; + } + + /* - variables can either be $x.y or ${x.y}, in case they are only part + * of the string. + * - will accept $$ as a literal $. + * - no nested property expansion, i.e. ${foo.${bar}} is not supported, + * bad things will happen + * - ${x.y:-default} will return default value if property empty. + */ + while (*src_ptr) { + const char* c; + + c = strchr(src_ptr, '$'); + if (!c) { + dst->append(src_ptr); + return true; + } + + dst->append(src_ptr, c); + c++; + + if (*c == '$') { + dst->push_back(*(c++)); + src_ptr = c; + continue; + } else if (*c == '\0') { + return true; + } + + std::string prop_name; + std::string def_val; + if (*c == '{') { + c++; + const char* end = strchr(c, '}'); + if (!end) { + // failed to find closing brace, abort. + return false; + } + prop_name = std::string(c, end); + c = end + 1; + size_t def = prop_name.find(":-"); + if (def < prop_name.size()) { + def_val = prop_name.substr(def + 2); + prop_name = prop_name.substr(0, def); + } + } else { + prop_name = c; + c += prop_name.size(); + } + + if (prop_name.empty()) { + return false; + } + + char prop_value[PROPERTY_VALUE_MAX]; + property_get(prop_name.c_str(), prop_value, ""); + std::string prop_val = prop_value; + if (prop_val.empty()) { + if (def_val.empty()) { + return false; + } + prop_val = def_val; + } + + dst->append(prop_val); + src_ptr = c; + } + + return true; +} + +string GetArgument(const string& line, size_t argument_number, bool expand_properties) { + size_t beg; + size_t end; + string argument; + + beg = line.find_first_not_of(" \t\r"); + if (beg == string::npos) + return ""; + + for (size_t i = 0; i < argument_number; ++i) { + end = line.find_first_of(" \t\r", beg); + if (end == string::npos) + return ""; + + beg = line.find_first_not_of(" \t\r", end); + if (beg == string::npos) + return ""; + } + + end = line.find_first_of(" \t\r", beg); + if (end == string::npos) + argument = line.substr(beg); + else + argument = line.substr(beg, end - beg); // exclude trailing whitespace + + if (expand_properties) { + string expanded_property_argument; + if (expand_props(argument, &expanded_property_argument)) + return expanded_property_argument; + else + return ""; + } else { + return argument; + } +} + +// Very simplified .rc parser to get services +void Parse_RC_File(const string& rc_file, vector& RC_Services) { + ifstream file; + + file.open(rc_file.c_str(), ios::in); + if (!file.is_open()) + return; + + size_t beg; // left trim + size_t end; // right trim + bool continuation = false; // backslash continuation + string line; // line + string real_line; // trimmed line with backslash continuation removal + vector imports; // file names of imports (we don't want to recursively do while the file is open) + + while (getline(file, line)) { + beg = line.find_first_not_of(" \t\r"); + end = line.find_last_not_of(" \t\r"); + if (end == string::npos) + end = line.length(); + + if (beg == string::npos) { + if (continuation) + continuation = false; + else + continue; + } else if (line[end] == '\\') { + continuation = true; + real_line += line.substr(beg, end - beg); // excluding backslash + continue; + } else if (continuation) { + continuation = false; + real_line += line.substr(beg, end - beg + 1); + } else { + real_line = line.substr(beg, end - beg + 1); + } + + if (GetArgument(real_line, 0, false) == "import") { + // handle: import + string file_name = GetArgument(real_line, 1, true); + if (file_name.empty()) { + // INVALID IMPORT + } else + imports.push_back(file_name); + } else if (GetArgument(real_line, 0, false) == "service") { + // handle: service + RC_Service svc; + + svc.Service_Name = GetArgument(real_line, 1, false); + svc.Service_Path = GetArgument(real_line, 2, true); + + if (svc.Service_Name.empty() || svc.Service_Path.empty()) { + // INVALID SERVICE ENTRY + } else { + beg = svc.Service_Path.find_last_of("/"); + if (beg == string::npos) + svc.Service_Binary = svc.Service_Path; + else + svc.Service_Binary = svc.Service_Path.substr(beg + 1); + +/* +#ifdef _USING_SHORT_SERVICE_NAMES + if (svc.Service_Name.length() > 16) { + LOGERROR("WARNING: Ignoring service %s (-> %s)\n" + " defined in %s is greater than 16 characters and will\n" + " not be able to be run by init on android-7.1 or below!\n", + svc.Service_Name.c_str(), svc.Service_Path.c_str(), rc_file.c_str() + ); + } + else +#endif +*/ + RC_Services.push_back(svc); + } + } + real_line.clear(); + } + file.close(); + + for (size_t i = 0; i < imports.size(); ++i) { + Parse_RC_File(imports[i], RC_Services); + } +} + +vector Get_List_Of_Additional_Services(void) { vector services; + // Additional Services needed by vold_decrypt (eg qseecomd, hwservicemanager, etc) vector service_names = TWFunc::Split_String(TW_CRYPTO_SYSTEM_VOLD_SERVICES, " "); - for (size_t i = 0; i < service_names.size(); ++i) { AdditionalService svc; - svc.service_name = service_names[i]; + svc.Service_Name = service_names[i]; + svc.bin_exists = false; + svc.svc_exists = false; services.push_back(svc); + +#ifdef _USING_SHORT_SERVICE_NAMES + // Fallback code for >16 character service names which + // allows for multiple definitions in custom .rc files + if (service_names[i].length() > 12) { + svc.Service_Name = service_names[i].substr(0, 12); // 16-4(prefix)=12 + svc.bin_exists = false; + svc.svc_exists = false; + services.push_back(svc); + } +#endif + } + + // Read list of all services defined in all .rc files + vector RC_Services; + Parse_RC_File("/init.rc", RC_Services); + + + // Cross reference Additional Services against the .rc Services and establish + // availability of the binaries, otherwise disable it to avoid unnecessary + // delays and log spam. + // Also check for duplicate entries between TWRP and vold_decrypt so we can + // stop and restart any conflicting services. + for (size_t i = 0; i < RC_Services.size(); ++i) { + string prefix = RC_Services[i].Service_Name.substr(0, 4); + +#ifdef _USING_SHORT_SERVICE_NAMES + map rc_indeces; +#endif + + if (prefix != "sys_" && prefix != "ven_") { +#ifdef _USING_SHORT_SERVICE_NAMES + if (RC_Services[i].Service_Name.length() > 12) { + // save this entry for potential binary name match + rc_indeces.insert(pair(RC_Services[i].Service_Binary, i)); + } +#endif + continue; + } + + for (size_t j = 0; j < services.size(); ++j) { + string path = RC_Services[i].Service_Path; + if (prefix == "ven_" && will_VendorBin_Be_Symlinked()) { + path = "/system" + path; // vendor is going to get symlinked to /system/vendor + } + + if (RC_Services[i].Service_Name == prefix + services[j].Service_Name) { + services[j].svc_exists = true; + + if (!services[j].VOLD_Service_Name.empty() && TWFunc::Path_Exists(path)) { + // Duplicate match, log but use previous definition + LOGERROR("Service %s: VOLD_Service_Name already defined as %s\n", RC_Services[i].Service_Name.c_str(), services[j].VOLD_Service_Name.c_str()); + } + else if (TWFunc::Path_Exists(path)) { + services[j].bin_exists = true; + services[j].VOLD_Service_Name = RC_Services[i].Service_Name; // prefix + service_name + services[j].Service_Path = RC_Services[i].Service_Path; + services[j].Service_Binary = RC_Services[i].Service_Binary; + + if (Service_Exists(services[j].Service_Name)) + services[j].TWRP_Service_Name = services[j].Service_Name; + else if (Service_Exists("sbin" + services[j].Service_Name)) + services[j].TWRP_Service_Name = "sbin" + services[j].Service_Name; + else + services[j].TWRP_Service_Name.clear(); + +#ifdef _USING_SHORT_SERVICE_NAMES + if (services[j].TWRP_Service_Name.empty()) { + // Try matching Service_Binary (due to 16 character service_name limit in 7.1 and below) + map::iterator it = rc_indeces.find(services[j].Service_Binary); + if (it != rc_indeces.end()) { + services[j].TWRP_Service_Name = RC_Services[it->second].Service_Name; + } + } +#endif + } + break; + } + } } + LOGINFO("List of additional services for vold_decrypt:\n"); + for (size_t i = 0; i < services.size(); ++i) { + if (!services[i].svc_exists) { + LOGINFO(" %s: Disabled due to lack of .rc service entry\n", services[i].Service_Name.c_str()); + } else if (services[i].bin_exists) { + if (services[i].TWRP_Service_Name.empty()) { + LOGINFO(" %s: Enabled as %s -> %s\n", + services[i].Service_Name.c_str(), + services[i].VOLD_Service_Name.c_str(), services[i].Service_Path.c_str() + ); + } else { + LOGINFO(" %s: Enabled as %s -> %s (temporarily replacing TWRP's %s service)\n", + services[i].Service_Name.c_str(), + services[i].VOLD_Service_Name.c_str(), services[i].Service_Path.c_str(), + services[i].TWRP_Service_Name.c_str() + ); + } + } + else { + LOGINFO(" %s: Disabled due to lack of matching binary\n", services[i].Service_Name.c_str()); + } + } return services; } #endif -int vold_decrypt(string Password) -{ + +/* Misc Functions */ +void Set_Needed_Properties(void) { + // vold won't start without ro.storage_structure on Kitkat + string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk"); + int sdkver = 20; + if (!sdkverstr.empty()) { + sdkver = atoi(sdkverstr.c_str()); + } + if (sdkver <= 19) { + string ro_storage_structure = TWFunc::System_Property_Get("ro.storage_structure"); + if (!ro_storage_structure.empty()) + property_set("ro.storage_structure", ro_storage_structure.c_str()); + } +} + + +/* vdc Functions */ +typedef struct { + string Output; // Entire line excluding \n + int ResponseCode; // ResponseCode.h (int) + int Sequence; // Sequence (int) + int Message; // Message (string) but we're only interested in int +} vdc_ReturnValues; + +int Exec_vdc_cryptfs(const string& command, const string& argument, vdc_ReturnValues* vdcResult) { + pid_t pid; + int status; + int pipe_fd[2][2]; + + vdcResult->Output.clear(); + vdcResult->ResponseCode = vdcResult->Sequence = vdcResult->Message = -1; + + for (int i = 0; i < 2; ++i) { + if (pipe(pipe_fd[i])) { + LOGERROR("exec_vdc_cryptfs: pipe() error!\n"); + return -1; + } + } + + const char *cmd[] = { "/system/bin/vdc", "cryptfs" }; + const char *env[] = { "LD_LIBRARY_PATH=/system/lib64:/system/lib", NULL }; + +#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG + string log_name = "/tmp/strace_vdc_" + command; +#endif + + switch(pid = fork()) + { + case -1: + LOGERROR("exec_vdc_cryptfs: fork failed: %d (%s)!\n", errno, strerror(errno)); + return -1; + + case 0: // child + fflush(stdout); fflush(stderr); + for (int i = 0; i < 2; ++i) { + close(pipe_fd[i][0]); + dup2(pipe_fd[i][1], ((i == 0) ? STDOUT_FILENO : STDERR_FILENO)); + close(pipe_fd[i][1]); + } + +#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG + if (has_strace) { + if (argument.empty()) + execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", log_name.c_str(), + "-E", env[0], cmd[0], cmd[1], command.c_str(), NULL); + else + execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", log_name.c_str(), + "-E", env[0], cmd[0], cmd[1], command.c_str(), argument.c_str(), NULL); + } else +#endif + if (argument.empty()) + execle(cmd[0], cmd[0], cmd[1], command.c_str(), NULL, env); + else + execle(cmd[0], cmd[0], cmd[1], command.c_str(), argument.c_str(), NULL, env); + _exit(127); + break; + + default: + { + int timeout = 30*100; + + for (int i = 0; i < 2; ++i) { + close(pipe_fd[i][1]); + + // Non-blocking read loop with timeout + int flags = fcntl(pipe_fd[i][0], F_GETFL, 0); + fcntl(pipe_fd[i][0], F_SETFL, flags | O_NONBLOCK); + } + + char buffer[128]; + ssize_t count; + string strout[2]; + pid_t retpid = waitpid(pid, &status, WNOHANG); + while (true) { + for (int i = 0; i < 2; ++i) { + count = read(pipe_fd[i][0], buffer, sizeof(buffer)); + if (count == -1) { + if (errno == EINTR) + continue; + else if (errno != EAGAIN) + LOGERROR("exec_vdc_cryptfs: read() error %d (%s)\n!", errno, strerror(errno)); + } else if (count > 0) { + strout[i].append(buffer, count); + } + } + + retpid = waitpid(pid, &status, WNOHANG); + if (retpid == 0 && --timeout) + usleep(10000); + else + break; + }; + + for (int i = 0; i < 2; ++i) { + close(pipe_fd[i][0]); + } + + if (!strout[0].empty()) { + sscanf(strout[0].c_str(), "%d %d %d", &vdcResult->ResponseCode, &vdcResult->Sequence, &vdcResult->Message); + vdcResult->Output = "I:" + strout[0]; + } + if (!strout[1].empty()) { + vdcResult->Output += "E:" + strout[1]; + } + std::replace(vdcResult->Output.begin(), vdcResult->Output.end(), '\n', '|'); + + if (!vdcResult->Output.empty() && vdcResult->Output[vdcResult->Output.length() - 1] != '|') + vdcResult->Output += "|"; + vdcResult->Output += "RC=" + TWFunc::to_string(WEXITSTATUS(status)); + + // Error handling + if (retpid == 0 && timeout == 0) { + LOGERROR("exec_vdc_cryptfs: took too long, killing process\n"); + kill(pid, SIGKILL); + for (timeout = 5; retpid == 0 && timeout; --timeout) { + sleep(1); + retpid = waitpid(pid, &status, WNOHANG); + } + if (retpid) + LOGINFO("exec_vdc_cryptfs: process killed successfully\n"); + else + LOGERROR("exec_vdc_cryptfs: process took too long to kill, may be a zombie process\n"); + return VD_ERR_VOLD_OPERATION_TIMEDOUT; + } else if (retpid > 0) { + if (WIFSIGNALED(status)) { + LOGERROR("exec_vdc_cryptfs: process ended with signal: %d\n", WTERMSIG(status)); // Seg fault or some other non-graceful termination + return -1; + } + } else if (retpid < 0) { // no PID returned + if (errno == ECHILD) + LOGINFO("exec_vdc_cryptfs: no child process exist\n"); + else { + LOGERROR("exec_vdc_cryptfs: Unexpected error %d (%s)\n", errno, strerror(errno)); + return -1; + } + } + return 0; + } + } +} + +int Run_vdc(const string& Password) { + int res; + struct timeval t1, t2; + vdc_ReturnValues vdcResult; + + LOGINFO("About to run vdc...\n"); + + // Wait for vold connection + gettimeofday(&t1, NULL); + t2 = t1; + while ((t2.tv_sec - t1.tv_sec) < 5) { + // cryptfs getpwtype returns: R1=213(PasswordTypeResult) R2=? R3="password", "pattern", "pin", "default" + res = Exec_vdc_cryptfs("getpwtype", "", &vdcResult); + if (vdcResult.ResponseCode == PASSWORD_TYPE_RESULT) { + res = 0; + break; + } + LOGINFO("Retrying connection to vold (Reason: %s)\n", vdcResult.Output.c_str()); + usleep(SLEEP_MIN_USEC); // vdc usually usleep(10000), but that causes too many unnecessary attempts + gettimeofday(&t2, NULL); + } + + if (res == 0 && (t2.tv_sec - t1.tv_sec) < 5) + LOGINFO("Connected to vold: %s\n", vdcResult.Output.c_str()); + else if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT) + return VD_ERR_VOLD_OPERATION_TIMEDOUT; // should never happen for getpwtype + else if (res) + return VD_ERR_FORK_EXECL_ERROR; + else if (vdcResult.ResponseCode != -1) + return VD_ERR_VOLD_UNEXPECTED_RESPONSE; + else + return VD_ERR_VDC_FAILED_TO_CONNECT; + + + // Input password from GUI, or default password + res = Exec_vdc_cryptfs("checkpw", Password, &vdcResult); + if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT) + return VD_ERR_VOLD_OPERATION_TIMEDOUT; + else if (res) + return VD_ERR_FORK_EXECL_ERROR; + + LOGINFO("vdc cryptfs result (passwd): %s\n", vdcResult.Output.c_str()); + /* + if (res == 0 && vdcResult.ResponseCode != COMMAND_OKAY) + return VD_ERR_VOLD_UNEXPECTED_RESPONSE; + */ + + if (vdcResult.Message != 0) { + // try falling back to Lollipop hex passwords + string hexPassword = convert_key_to_hex_ascii(Password); + res = Exec_vdc_cryptfs("checkpw", hexPassword, &vdcResult); + if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT) + return VD_ERR_VOLD_OPERATION_TIMEDOUT; + else if (res) + return VD_ERR_FORK_EXECL_ERROR; + + LOGINFO("vdc cryptfs result (hex_pw): %s\n", vdcResult.Output.c_str()); + /* + if (res == 0 && vdcResult.ResponseCode != COMMAND_OKAY) + return VD_ERR_VOLD_UNEXPECTED_RESPONSE; + */ + } + + // vdc's return value is dependant upon source origin, it will either + // return 0 or ResponseCode, so disregard and focus on decryption instead + if (vdcResult.Message == 0) { + // Decryption successful wait for crypto blk dev + Wait_For_Property("ro.crypto.fs_crypto_blkdev"); + res = VD_SUCCESS; + } else if (vdcResult.ResponseCode != COMMAND_OKAY) { + res = VD_ERR_VOLD_UNEXPECTED_RESPONSE; + } else { + res = VD_ERR_DECRYPTION_FAILED; + } + + return res; +} + +int Vold_Decrypt_Core(const string& Password) { int res; - bool output_dmesg_to_log = false; bool is_vendor_symlinked = false; bool is_firmware_symlinked = false; + bool is_fstab_symlinked = false; bool is_vold_running = false; if (Password.empty()) { - LOGDECRYPT("vold_decrypt: password is empty!\n"); - return -1; + LOGINFO("vold_decrypt: password is empty!\n"); + return VD_ERR_PASSWORD_EMPTY; } // Mount system and check for vold and vdc if (!PartitionManager.Mount_By_Path("/system", true)) { - return -1; + return VD_ERR_UNABLE_TO_MOUNT_SYSTEM; } else if (!TWFunc::Path_Exists("/system/bin/vold")) { - LOGDECRYPT("ERROR: /system/bin/vold not found, aborting.\n"); - gui_msg(Msg(msg::kError, "decrypt_data_vold_os_missing=Missing files needed for vold decrypt: {1}")("/system/bin/vold")); - return -1; + LOGINFO("ERROR: /system/bin/vold not found, aborting.\n"); + return VD_ERR_MISSING_VOLD; } else if (!TWFunc::Path_Exists("/system/bin/vdc")) { - LOGDECRYPT("ERROR: /system/bin/vdc not found, aborting.\n"); - gui_msg(Msg(msg::kError, "decrypt_data_vold_os_missing=Missing files needed for vold decrypt: {1}")("/system/bin/vdc")); - return -1; + LOGINFO("ERROR: /system/bin/vdc not found, aborting.\n"); + return VD_ERR_MISSING_VDC; } fp_kmsg = fopen("/dev/kmsg", "a"); - LOGDECRYPT("TW_CRYPTO_USE_SYSTEM_VOLD := true\n"); - LOGDECRYPT("Attempting to use system's vold for decryption...\n"); + LOGINFO("TW_CRYPTO_USE_SYSTEM_VOLD := true\n"); -#ifndef TW_CRYPTO_SYSTEM_VOLD_DISABLE_TIMEOUT - has_timeout = TWFunc::Path_Exists("/sbin/timeout"); - if (!has_timeout) - LOGDECRYPT("timeout binary not found, disabling timeout in vold_decrypt!\n"); -#endif + // just cache the result to avoid unneeded duplicates in recovery.log + LOGINFO("Checking existence of vendor and firmware partitions...\n"); + is_Vendor_Mounted(); + is_Firmware_Mounted(); + + LOGINFO("Attempting to use system's vold for decryption...\n"); #ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG - has_strace = TWFunc::Path_Exists("/sbin/strace"); - if (!has_strace) - LOGDECRYPT("strace binary not found, disabling strace in vold_decrypt!\n"); - pid_t pid_strace = strace_init(); + Strace_init_Start(); #endif #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES @@ -456,74 +1044,61 @@ int vold_decrypt(string Password) // Check if TWRP is running any of the services for (size_t i = 0; i < Services.size(); ++i) { - if (Service_Exists(Services[i].service_name)) - Services[i].twrp_svc_name = Services[i].service_name; - else if (Service_Exists("sbin" + Services[i].service_name)) - Services[i].twrp_svc_name = "sbin" + Services[i].service_name; - else - Services[i].twrp_svc_name.clear(); - - if (!Services[i].twrp_svc_name.empty() && !Is_Service_Stopped(Services[i].twrp_svc_name)) { + if (!Services[i].TWRP_Service_Name.empty() && !Is_Service_Stopped(Services[i].TWRP_Service_Name)) { Services[i].resume = true; - Stop_Service(Services[i].twrp_svc_name); + Stop_Service(Services[i].TWRP_Service_Name); } else Services[i].resume = false; - - // vold_decrypt system services have to be named sys_{service} in the .rc files - Services[i].service_name = "sys_" + Services[i].service_name; } #endif - LOGDECRYPT("Setting up folders and permissions...\n"); + LOGINFO("Setting up folders and permissions...\n"); + is_fstab_symlinked = Symlink_Recovery_Fstab(); is_vendor_symlinked = Symlink_Vendor_Folder(); is_firmware_symlinked = Symlink_Firmware_Folder(); Symlink_Firmware_Files(is_vendor_symlinked, is_firmware_symlinked); - set_needed_props(); + Set_Needed_Properties(); // Start services needed for vold decrypt - LOGDECRYPT("Starting services...\n"); + LOGINFO("Starting services...\n"); #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES for (size_t i = 0; i < Services.size(); ++i) { - Services[i].is_running = Start_Service(Services[i].service_name); + if (Services[i].bin_exists) + Services[i].is_running = Start_Service(Services[i].VOLD_Service_Name); } #endif is_vold_running = Start_Service("sys_vold"); if (is_vold_running) { - #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES for (size_t i = 0; i < Services.size(); ++i) { - if (!Is_Service_Running(Services[i].service_name) && Services[i].resume) { + if (Services[i].bin_exists && !Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume) { // if system_service has died restart the twrp_service - LOGDECRYPT("%s is not running, resuming %s!\n", Services[i].service_name.c_str(), Services[i].twrp_svc_name.c_str()); - Start_Service(Services[i].twrp_svc_name); + LOGINFO("%s is not running, resuming %s!\n", Services[i].VOLD_Service_Name.c_str(), Services[i].TWRP_Service_Name.c_str()); + Start_Service(Services[i].TWRP_Service_Name); } } #endif - - res = run_vdc(Password); + res = Run_vdc(Password); if (res != 0) { - // Decryption was unsuccessful - LOGDECRYPT("Decryption failed\n"); - output_dmesg_to_log = true; + LOGINFO("Decryption failed\n"); } } else { - LOGDECRYPT("Failed to start vold\n"); - TWFunc::Exec_Cmd("echo \"$(getprop | grep init.svc)\" >> /dev/kmsg"); - output_dmesg_to_log = true; + LOGINFO("Failed to start vold\n"); + res = VD_ERR_VOLD_FAILED_TO_START; } // Stop services needed for vold decrypt so /system can be unmounted - LOGDECRYPT("Stopping services...\n"); + LOGINFO("Stopping services...\n"); Stop_Service("sys_vold"); #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES for (size_t i = 0; i < Services.size(); ++i) { - if (!Is_Service_Running(Services[i].service_name) && Services[i].resume) - Stop_Service(Services[i].twrp_svc_name); - else - Stop_Service(Services[i].service_name); + if (!Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume) + Stop_Service(Services[i].TWRP_Service_Name); + else if (Services[i].bin_exists) + Stop_Service(Services[i].VOLD_Service_Name); } #endif @@ -531,52 +1106,28 @@ int vold_decrypt(string Password) Restore_Firmware_Folder(); if (is_vendor_symlinked) Restore_Vendor_Folder(); + if (is_fstab_symlinked) + Restore_Recovery_Fstab(); if (!PartitionManager.UnMount_By_Path("/system", true)) { // PartitionManager failed to unmount /system, this should not happen, // but in case it does, do a lazy unmount - LOGDECRYPT("WARNING: system could not be unmounted normally!\n"); - TWFunc::Exec_Cmd("umount -l /system"); + LOGINFO("WARNING: system could not be unmounted normally!\n"); + umount2("/system", MNT_DETACH); } - LOGDECRYPT("Finished.\n"); + LOGINFO("Finished.\n"); #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES // Restart previously running services for (size_t i = 0; i < Services.size(); ++i) { if (Services[i].resume) - Start_Service(Services[i].twrp_svc_name); + Start_Service(Services[i].TWRP_Service_Name); } #endif #ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG - if (pid_strace > 0) { - LOGDECRYPT_KMSG("Stopping strace_init (pid=%d)\n", pid_strace); - int timeout; - int status; - pid_t retpid = waitpid(pid_strace, &status, WNOHANG); - - kill(pid_strace, SIGTERM); - for (timeout = 5; retpid == 0 && timeout; --timeout) { - sleep(1); - retpid = waitpid(pid_strace, &status, WNOHANG); - } - if (retpid) - LOGDECRYPT_KMSG("strace_init terminated successfully\n"); - else { - // SIGTERM didn't work, kill it instead - kill(pid_strace, SIGKILL); - for (timeout = 5; retpid == 0 && timeout; --timeout) { - sleep(1); - retpid = waitpid(pid_strace, &status, WNOHANG); - } - if (retpid) - LOGDECRYPT_KMSG("strace_init killed successfully\n"); - else - LOGDECRYPT_KMSG("strace_init took too long to kill, may be a zombie process\n"); - } - } - output_dmesg_to_log = true; + Strace_init_Stop(); #endif // Finish up and exit @@ -585,14 +1136,18 @@ int vold_decrypt(string Password) fclose(fp_kmsg); } - if (output_dmesg_to_log) - output_dmesg_to_recoverylog(); + return res; +} - // Finally check if crypto device is up - if (wait_for_property("ro.crypto.fs_crypto_blkdev", 0) != "error") - res = 0; - else - res = -1; +} // namespace - return res; +/* + * Common vold Response Codes / Errors: + * 406 (OpFailedStorageNotFound) -> Problem reading or parsing fstab + * + */ + +/* Main function separated from core in case we want to return error info */ +int vold_decrypt(const string& Password) { + return Vold_Decrypt_Core(Password); } diff --git a/crypto/vold_decrypt/vold_decrypt.h b/crypto/vold_decrypt/vold_decrypt.h index 70db404e99..ba7a747205 100644 --- a/crypto/vold_decrypt/vold_decrypt.h +++ b/crypto/vold_decrypt/vold_decrypt.h @@ -21,6 +21,22 @@ #include -int vold_decrypt(std::string Password); +// -_- +enum { + VD_SUCCESS = 0, + VD_ERR_DECRYPTION_FAILED = -1, + VD_ERR_UNABLE_TO_MOUNT_SYSTEM = -2, + VD_ERR_MISSING_VOLD = -3, + VD_ERR_MISSING_VDC = -4, + VD_ERR_VDC_FAILED_TO_CONNECT = -5, + VD_ERR_VOLD_FAILED_TO_START = -6, + VD_ERR_VOLD_UNEXPECTED_RESPONSE = -7, + VD_ERR_VOLD_OPERATION_TIMEDOUT = -8, + VD_ERR_FORK_EXECL_ERROR = -9, + VD_ERR_PASSWORD_EMPTY = -10, +}; + + +int vold_decrypt(const std::string& Password); #endif // _VOLD_DECRYPT_H diff --git a/data.cpp b/data.cpp index 5d05d2badd..4dfbde2d0c 100644 --- a/data.cpp +++ b/data.cpp @@ -230,7 +230,7 @@ int DataManager::ResetDefaults() int DataManager::LoadValues(const string& filename) { - string str, dev_id; + string dev_id; if (!mInitialized) SetDefaultValues(); @@ -263,6 +263,44 @@ int DataManager::LoadValues(const string& filename) return 0; } +int DataManager::LoadPersistValues(void) +{ + static bool loaded = false; + string dev_id; + + // Only run this function once, and make sure normal settings file has not yet been read + if (loaded || !mBackingFile.empty() || !TWFunc::Path_Exists(PERSIST_SETTINGS_FILE)) + return -1; + + LOGINFO("Attempt to load settings from /persist settings file...\n"); + + if (!mInitialized) + SetDefaultValues(); + + GetValue("device_id", dev_id); + mPersist.SetFile(PERSIST_SETTINGS_FILE); + mPersist.SetFileVersion(FILE_VERSION); + + // Read in the file, if possible + pthread_mutex_lock(&m_valuesLock); + mPersist.LoadValues(); + +#ifndef TW_NO_SCREEN_TIMEOUT + blankTimer.setTime(mPersist.GetIntValue("tw_screen_timeout_secs")); +#endif + + update_tz_environment_variables(); + TWFunc::Set_Brightness(GetStrValue("tw_brightness")); + + pthread_mutex_unlock(&m_valuesLock); + + /* Don't set storage nor backup paths this early */ + + loaded = true; + + return 0; +} + int DataManager::Flush() { return SaveValues(); @@ -271,6 +309,15 @@ int DataManager::Flush() int DataManager::SaveValues() { #ifndef TW_OEM_BUILD + if (PartitionManager.Mount_By_Path("/persist", false)) { + mPersist.SetFile(PERSIST_SETTINGS_FILE); + mPersist.SetFileVersion(FILE_VERSION); + pthread_mutex_lock(&m_valuesLock); + mPersist.SaveValues(); + pthread_mutex_unlock(&m_valuesLock); + LOGINFO("Saved settings file values to %s\n", PERSIST_SETTINGS_FILE); + } + if (mBackingFile.empty()) return -1; @@ -284,7 +331,7 @@ int DataManager::SaveValues() pthread_mutex_unlock(&m_valuesLock); tw_set_default_metadata(mBackingFile.c_str()); - LOGINFO("Saved settings file values\n"); + LOGINFO("Saved settings file values to '%s'\n", mBackingFile.c_str()); #endif // ifdef TW_OEM_BUILD return 0; } @@ -353,7 +400,7 @@ int DataManager::GetValue(const string& varName, float& value) return 0; } -unsigned long long DataManager::GetValue(const string& varName, unsigned long long& value) +int DataManager::GetValue(const string& varName, unsigned long long& value) { string data; @@ -834,6 +881,10 @@ void DataManager::SetDefaultValues() mConst.SetValue("tw_has_boot_slots", "0"); #endif +#ifdef TW_NO_LEGACY_PROPS + LOGINFO("TW_NO_LEGACY_PROPS := true\n"); +#endif + #ifdef TW_OEM_BUILD LOGINFO("TW_OEM_BUILD := true\n"); mConst.SetValue("tw_oem_build", "1"); @@ -844,6 +895,8 @@ void DataManager::SetDefaultValues() mData.SetValue("tw_app_install_status", "0"); // 0 = no status, 1 = not installed, 2 = already installed #endif + mData.SetValue("tw_enable_adb_backup", "0"); + pthread_mutex_unlock(&m_valuesLock); } diff --git a/data.hpp b/data.hpp index 790efc9934..d61fe8e7c0 100644 --- a/data.hpp +++ b/data.hpp @@ -23,6 +23,8 @@ #include #include "infomanager.hpp" +#define PERSIST_SETTINGS_FILE "/persist/.twrps" + using namespace std; class DataManager @@ -30,13 +32,14 @@ class DataManager public: static int ResetDefaults(); static int LoadValues(const string& filename); + static int LoadPersistValues(void); static int Flush(); // Core get routines static int GetValue(const string& varName, string& value); static int GetValue(const string& varName, int& value); static int GetValue(const string& varName, float& value); - static unsigned long long GetValue(const string& varName, unsigned long long& value); + static int GetValue(const string& varName, unsigned long long& value); // Helper functions static string GetStrValue(const string& varName); diff --git a/device.cpp b/device.cpp index e717dddf74..61501869ef 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; } diff --git a/device.h b/device.h index 5017782e68..639e2bf57c 100644 --- a/device.h +++ b/device.h @@ -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 71cf7652a0..d8058c16fc 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 similarity index 99% rename from edify/README rename to edify/README.md index 810455ccaa..b3330e23a1 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 0000000000..f1b56284cf --- /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 +#include + +#include +#include + +#include + +#include "expr.h" + +static void ExprDump(int depth, const std::unique_ptr& 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 \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 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 cc14fbe931..54ab3325ca 100644 --- a/edify/expr.cpp +++ b/edify/expr.cpp @@ -14,201 +14,172 @@ * limitations under the License. */ -#include -#include +#include "expr.h" + +#include #include #include -#include +#include #include +#include #include +#include +#include +#include #include #include -#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, std::string* result) { + if (result == nullptr) { + return false; + } + + std::unique_ptr 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) { + return expr->fn(expr->name.c_str(), state, expr->argv); } -Value* StringValue(char* str) { - if (str == NULL) return NULL; - Value* v = reinterpret_cast(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(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>& argv) { + if (argv.empty()) { + return StringValue(""); } - - result = reinterpret_cast(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>& 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>& 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>& 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(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>& 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>& 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>& 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>& 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>& 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>& 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>& 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>& 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>& argv) { + std::unique_ptr 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>& 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 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>& 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 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(malloc(sizeof(Expr))); - e->fn = fn; - e->name = "(operator)"; - e->argc = count; - e->argv = reinterpret_cast(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>& 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(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 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(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(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>& argv, + std::vector* 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(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>& argv, + std::vector* 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>& argv, + std::vector>* 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>& argv, + std::vector>* 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 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 886347991c..4838d20c08 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -19,27 +19,26 @@ #include +#include +#include +#include + #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>& argv); struct Expr { - Function fn; - const char* name; - int argc; - Expr** argv; - int start, end; + Function fn; + std::string name; + std::vector> 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); -// 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, 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>& 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>& argv); +Value* LogicalAndFn(const char* name, State* state, const std::vector>& argv); +Value* LogicalOrFn(const char* name, State* state, const std::vector>& argv); +Value* LogicalNotFn(const char* name, State* state, const std::vector>& argv); +Value* SubstringFn(const char* name, State* state, const std::vector>& argv); +Value* EqualityFn(const char* name, State* state, const std::vector>& argv); +Value* InequalityFn(const char* name, State* state, const std::vector>& argv); +Value* SequenceFn(const char* name, State* state, const std::vector>& 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>& argv); +Value* AssertFn(const char* name, State* state, const std::vector>& argv); +Value* AbortFn(const char* name, State* state, const std::vector>& 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>& argv, + std::vector* args); +bool ReadArgs(State* state, const std::vector>& argv, + std::vector* 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>& argv, + std::vector>* args); +bool ReadValueArgs(State* state, const std::vector>& argv, + std::vector>* 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* root, int* error_count); #endif // _EXPRESSION_H diff --git a/edify/main.cpp b/edify/main.cpp deleted file mode 100644 index 6007a3d58c..0000000000 --- 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 -#include -#include - -#include - -#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 098a6370a6..b1685eb1f0 100644 --- a/edify/parser.yy +++ b/edify/parser.yy @@ -19,6 +19,12 @@ #include #include +#include +#include +#include + +#include + #include "expr.h" #include "yydefs.h" #include "parser.h" @@ -26,13 +32,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* root, int* error_count, const char* s); +int yyparse(std::unique_ptr* 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 +59,7 @@ struct yy_buffer_state* yy_scan_string(const char* yystr); %union { char* str; Expr* expr; - struct { - int argc; - Expr** argv; - } args; + std::vector>* args; } %token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF @@ -51,7 +67,10 @@ struct yy_buffer_state* yy_scan_string(const char* yystr); %type expr %type arglist -%parse-param {Expr** root} +%destructor { delete $$; } expr +%destructor { delete $$; } arglist + +%parse-param {std::unique_ptr* root} %parse-param {int* error_count} %error-verbose @@ -66,17 +85,11 @@ struct yy_buffer_state* yy_scan_string(const char* yystr); %% -input: expr { *root = $1; } +input: expr { root->reset($1); } ; expr: STRING { - $$ = reinterpret_cast(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 +104,33 @@ 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(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>; } | expr { - $$.argc = 1; - $$.argv = reinterpret_cast(malloc(sizeof(Expr*))); - $$.argv[0] = $1; + $$ = new std::vector>; + $$->emplace_back($1); } | arglist ',' expr { - $$.argc = $1.argc + 1; - $$.argv = reinterpret_cast(realloc($$.argv, $$.argc * sizeof(Expr*))); - $$.argv[$$.argc-1] = $3; + UNUSED($1); + $$->push_back(std::unique_ptr($3)); } ; %% -void yyerror(Expr** root, int* error_count, const char* s) { +void yyerror(std::unique_ptr* root, int* error_count, const char* s) { if (strlen(s) == 0) { s = "syntax error"; } @@ -133,7 +138,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* 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 dfea0eb382..9fe047c91b 100644 --- a/error_code.h +++ b/error_code.h @@ -18,52 +18,56 @@ #define _ERROR_CODE_H_ enum ErrorCode { - kNoError = -1, - kLowBattery = 20, - kZipVerificationFailure, - kZipOpenFailure, - kBootreasonInBlacklist + kNoError = -1, + kLowBattery = 20, + kZipVerificationFailure, + kZipOpenFailure, + kBootreasonInBlacklist, + kPackageCompatibilityFailure, + kScriptExecutionFailure, }; 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, + kPatchApplicationFailure, + 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/Android.mk b/etc/Android.mk index decd3e2d65..cda0f372ed 100644 --- a/etc/Android.mk +++ b/etc/Android.mk @@ -51,6 +51,26 @@ else include $(BUILD_PREBUILT) endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0) + include $(CLEAR_VARS) + LOCAL_MODULE := init.recovery.hlthchrg.rc + LOCAL_MODULE_TAGS := eng + LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES + LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) + + LOCAL_SRC_FILES := init.recovery.hlthchrg26.rc + include $(BUILD_PREBUILT) +else + include $(CLEAR_VARS) + LOCAL_MODULE := init.recovery.hlthchrg.rc + LOCAL_MODULE_TAGS := eng + LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES + LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) + + LOCAL_SRC_FILES := init.recovery.hlthchrg25.rc + include $(BUILD_PREBUILT) +endif + ifeq ($(TWRP_INCLUDE_LOGCAT), true) ifeq ($(TARGET_USES_LOGD), true) 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 b091b19276..0000000000 --- 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 3523181fa5..d1c4526d7a 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -16,7 +16,6 @@ on early-init restorecon /postinstall start ueventd - start healthd service set_permissive /sbin/permissive.sh oneshot @@ -46,7 +45,6 @@ on init on fs mount pstore pstore /sys/fs/pstore - 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 @@ -104,10 +102,6 @@ service ueventd /sbin/ueventd critical seclabel u:r:ueventd:s0 -service healthd /sbin/healthd -r - critical - seclabel u:r:healthd:s0 - service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery disabled socket adbd stream 660 system system diff --git a/etc/init.recovery.hlthchrg25.rc b/etc/init.recovery.hlthchrg25.rc new file mode 100644 index 0000000000..62b24894d9 --- /dev/null +++ b/etc/init.recovery.hlthchrg25.rc @@ -0,0 +1,5 @@ +# healthd for pre Android 8.0 + +service healthd /sbin/healthd -r + critical + seclabel u:r:healthd:s0 diff --git a/etc/init.recovery.hlthchrg26.rc b/etc/init.recovery.hlthchrg26.rc new file mode 100644 index 0000000000..8a49c86fd5 --- /dev/null +++ b/etc/init.recovery.hlthchrg26.rc @@ -0,0 +1,5 @@ +# charger for Android 8.0 and up + +service charger /charger -r + critical + seclabel u:r:charger:s0 diff --git a/etc/init.recovery.service22.rc b/etc/init.recovery.service22.rc index bb2853c1aa..43a3374b42 100644 --- a/etc/init.recovery.service22.rc +++ b/etc/init.recovery.service22.rc @@ -1,5 +1,11 @@ on boot + export LD_CONFIG_FILE /sbin/ld.config.txt # For starting recovery on 5.0 and newer service recovery /sbin/recovery seclabel u:r:recovery:s0 + +# This helps fix ld.config.txt errors from the linker on Android 8.1 +service ldconfigtxt /sbin/touch /sbin/ld.config.txt + oneshot + seclabel u:r:recovery:s0 diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp index df96312723..b0ecf96bee 100644 --- a/fuse_sdcard_provider.cpp +++ b/fuse_sdcard_provider.cpp @@ -23,6 +23,8 @@ #include #include +#include + #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/fuse_sideload.cpp b/fuse_sideload.cpp index f57d479b06..f667cd4a17 100644 --- a/fuse_sideload.cpp +++ b/fuse_sideload.cpp @@ -235,11 +235,13 @@ static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse return NO_STATUS; } -static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { +static int handle_flush(void* /* data */, struct fuse_data* /* fd */, + const struct fuse_in_header* /* hdr */) { return 0; } -static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { +static int handle_release(void* /* data */, struct fuse_data* /* fd */, + const struct fuse_in_header* /* hdr */) { return 0; } @@ -375,166 +377,167 @@ static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_he return NO_STATUS; } -int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, - uint64_t file_size, uint32_t block_size) -{ - int result; - - // If something's already mounted on our mountpoint, try to remove - // it. (Mostly in case of a previous abnormal exit.) - umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE); - - if (block_size < 1024) { - fprintf(stderr, "block size (%u) is too small\n", block_size); - return -1; - } - if (block_size > (1<<22)) { // 4 MiB - fprintf(stderr, "block size (%u) is too large\n", block_size); - return -1; - } - - struct fuse_data fd; - memset(&fd, 0, sizeof(fd)); - fd.vtab = vtab; - fd.cookie = cookie; - fd.file_size = file_size; - fd.block_size = block_size; - fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1); - - if (fd.file_blocks > (1<<18)) { - fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); - result = -1; - goto done; - } - - fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH); - if (fd.hashes == NULL) { - fprintf(stderr, "failed to allocate %d bites for hashes\n", - fd.file_blocks * SHA256_DIGEST_LENGTH); - result = -1; - goto done; - } - - fd.uid = getuid(); - fd.gid = getgid(); - - fd.curr_block = -1; - fd.block_data = (uint8_t*)malloc(block_size); - if (fd.block_data == NULL) { - fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); - result = -1; - goto done; - } - fd.extra_block = (uint8_t*)malloc(block_size); - if (fd.extra_block == NULL) { - fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); - result = -1; - goto done; - } - - fd.ffd = open("/dev/fuse", O_RDWR); - if (fd.ffd < 0) { - perror("open /dev/fuse"); - result = -1; - goto done; - } - +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size, + uint32_t block_size) { + // If something's already mounted on our mountpoint, try to remove it. (Mostly in case of a + // previous abnormal exit.) + umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE); + + // fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read. + if (block_size < 4096) { + fprintf(stderr, "block size (%u) is too small\n", block_size); + return -1; + } + if (block_size > (1 << 22)) { // 4 MiB + fprintf(stderr, "block size (%u) is too large\n", block_size); + return -1; + } + + struct fuse_data fd = {}; + fd.vtab = vtab; + fd.cookie = cookie; + fd.file_size = file_size; + fd.block_size = block_size; + fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1); + + int result; + if (fd.file_blocks > (1 << 18)) { + fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); + result = -1; + goto done; + } + + fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH); + if (fd.hashes == NULL) { + fprintf(stderr, "failed to allocate %d bites for hashes\n", + fd.file_blocks * SHA256_DIGEST_LENGTH); + result = -1; + goto done; + } + + fd.uid = getuid(); + fd.gid = getgid(); + + fd.curr_block = -1; + fd.block_data = (uint8_t*)malloc(block_size); + if (fd.block_data == NULL) { + fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); + result = -1; + goto done; + } + fd.extra_block = (uint8_t*)malloc(block_size); + if (fd.extra_block == NULL) { + fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); + result = -1; + goto done; + } + + fd.ffd = open("/dev/fuse", O_RDWR); + if (fd.ffd < 0) { + perror("open /dev/fuse"); + result = -1; + goto done; + } + + { char opts[256]; snprintf(opts, sizeof(opts), ("fd=%d,user_id=%d,group_id=%d,max_read=%u," "allow_other,rootmode=040000"), fd.ffd, fd.uid, fd.gid, block_size); - result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, - "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); + result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse", + MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); if (result < 0) { - perror("mount"); - goto done; + perror("mount"); + goto done; + } + } + + uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX * 8]; + for (;;) { + ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); + if (len == -1) { + perror("read request"); + if (errno == ENODEV) { + result = -1; + break; + } + continue; } - uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8]; - for (;;) { - ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); - if (len == -1) { - perror("read request"); - if (errno == ENODEV) { - result = -1; - break; - } - continue; - } - if ((size_t)len < sizeof(struct fuse_in_header)) { - fprintf(stderr, "request too short: len=%zu\n", (size_t)len); - continue; - } + if (static_cast(len) < sizeof(struct fuse_in_header)) { + fprintf(stderr, "request too short: len=%zd\n", len); + continue; + } - struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer; - void* data = request_buffer + sizeof(struct fuse_in_header); + struct fuse_in_header* hdr = reinterpret_cast(request_buffer); + void* data = request_buffer + sizeof(struct fuse_in_header); - result = -ENOSYS; + result = -ENOSYS; - switch (hdr->opcode) { - case FUSE_INIT: - result = handle_init(data, &fd, hdr); - break; + switch (hdr->opcode) { + case FUSE_INIT: + result = handle_init(data, &fd, hdr); + break; - case FUSE_LOOKUP: - result = handle_lookup(data, &fd, hdr); - break; + case FUSE_LOOKUP: + result = handle_lookup(data, &fd, hdr); + break; - case FUSE_GETATTR: - result = handle_getattr(data, &fd, hdr); - break; + case FUSE_GETATTR: + result = handle_getattr(data, &fd, hdr); + break; - case FUSE_OPEN: - result = handle_open(data, &fd, hdr); - break; + case FUSE_OPEN: + result = handle_open(data, &fd, hdr); + break; - case FUSE_READ: - result = handle_read(data, &fd, hdr); - break; + case FUSE_READ: + result = handle_read(data, &fd, hdr); + break; - case FUSE_FLUSH: - result = handle_flush(data, &fd, hdr); - break; + case FUSE_FLUSH: + result = handle_flush(data, &fd, hdr); + break; - case FUSE_RELEASE: - result = handle_release(data, &fd, hdr); - break; + case FUSE_RELEASE: + result = handle_release(data, &fd, hdr); + break; - default: - fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); - break; - } + default: + fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); + break; + } - if (result == NO_STATUS_EXIT) { - result = 0; - break; - } + if (result == NO_STATUS_EXIT) { + result = 0; + break; + } - if (result != NO_STATUS) { - struct fuse_out_header outhdr; - outhdr.len = sizeof(outhdr); - outhdr.error = result; - outhdr.unique = hdr->unique; - TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr))); - } + if (result != NO_STATUS) { + struct fuse_out_header outhdr; + outhdr.len = sizeof(outhdr); + outhdr.error = result; + outhdr.unique = hdr->unique; + TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr))); } + } - done: - fd.vtab->close(fd.cookie); +done: + fd.vtab->close(fd.cookie); - result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); - if (result < 0) { - printf("fuse_sideload umount failed: %s\n", strerror(errno)); - } + result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); + if (result < 0) { + printf("fuse_sideload umount failed: %s\n", strerror(errno)); + } - if (fd.ffd) close(fd.ffd); - free(fd.hashes); - free(fd.block_data); - free(fd.extra_block); + if (fd.ffd) close(fd.ffd); + free(fd.hashes); + free(fd.block_data); + free(fd.extra_block); - return result; + return result; } extern "C" int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, diff --git a/gpt/Android.mk b/gpt/Android.mk index 7369339b3b..3c2ec3b7a5 100644 --- a/gpt/Android.mk +++ b/gpt/Android.mk @@ -3,7 +3,9 @@ LOCAL_PATH := $(call my-dir) # Build libgpt_twrp library include $(CLEAR_VARS) +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0) LOCAL_CLANG := false +endif LOCAL_MODULE := libgpt_twrp LOCAL_MODULE_TAGS := optional diff --git a/gui/Android.mk b/gui/Android.mk index 78e93c61a2..909850fc4f 100644 --- a/gui/Android.mk +++ b/gui/Android.mk @@ -38,7 +38,13 @@ else LOCAL_SRC_FILES += hardwarekeyboard.cpp endif -LOCAL_SHARED_LIBRARIES += libminuitwrp libc libstdc++ libminzip libaosprecovery libselinux +LOCAL_SHARED_LIBRARIES += libminuitwrp libc libstdc++ libaosprecovery libselinux +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0) + LOCAL_SHARED_LIBRARIES += libziparchive +else + LOCAL_SHARED_LIBRARIES += libminzip + LOCAL_CFLAGS += -DUSE_MINZIP +endif LOCAL_MODULE := libguitwrp #TWRP_EVENT_LOGGING := true diff --git a/gui/action.cpp b/gui/action.cpp index a395158283..fb05d77b41 100644 --- a/gui/action.cpp +++ b/gui/action.cpp @@ -1,4 +1,4 @@ -/*update +/* Copyright 2013 bigbiff/Dees_Troy TeamWin This file is part of TWRP/TeamWin Recovery Project. @@ -43,11 +43,11 @@ #include "../adb_install.h" #include "../fuse_sideload.h" #include "blanktimer.hpp" +#include "../twinstall.h" extern "C" { #include "../twcommon.h" #include "../variables.h" -#include "../twinstall.h" #include "cutils/properties.h" #include "../adb_install.h" }; @@ -566,6 +566,7 @@ int GUIAction::reload(std::string arg __unused) int GUIAction::readBackup(std::string arg __unused) { string Restore_Name; + DataManager::GetValue("tw_restore", Restore_Name); PartitionManager.Set_Restore_Files(Restore_Name); return 0; @@ -1192,6 +1193,18 @@ int GUIAction::nandroid(std::string arg) string auto_gen = gui_lookup("auto_generate", "(Auto Generate)"); if (Backup_Name == auto_gen || Backup_Name == gui_lookup("curr_date", "(Current Date)") || Backup_Name == "0" || Backup_Name == "(" || PartitionManager.Check_Backup_Name(true) == 0) { ret = PartitionManager.Run_Backup(false); + DataManager::SetValue("tw_encrypt_backup", 0); // reset value so we don't encrypt every subsequent backup + if (!PartitionManager.stop_backup.get_value()) { + if (ret == false) + ret = 1; // 1 for failure + else + ret = 0; // 0 for success + DataManager::SetValue("tw_cancel_backup", 0); + } else { + DataManager::SetValue("tw_cancel_backup", 1); + gui_msg("backup_cancel=Backup Cancelled"); + ret = 0; + } } else { operation_end(1); return -1; @@ -1199,25 +1212,28 @@ int GUIAction::nandroid(std::string arg) DataManager::SetValue(TW_BACKUP_NAME, auto_gen); } else if (arg == "restore") { string Restore_Name; + int gui_adb_backup; + DataManager::GetValue("tw_restore", Restore_Name); - ret = PartitionManager.Run_Restore(Restore_Name); + DataManager::GetValue("tw_enable_adb_backup", gui_adb_backup); + if (gui_adb_backup) { + DataManager::SetValue("tw_operation_state", 1); + if (TWFunc::stream_adb_backup(Restore_Name) == 0) + ret = 0; // success + else + ret = 1; // failure + DataManager::SetValue("tw_enable_adb_backup", 0); + ret = 0; // assume success??? + } else { + if (PartitionManager.Run_Restore(Restore_Name)) + ret = 0; // success + else + ret = 1; // failure + } } else { - operation_end(1); + operation_end(1); // invalid arg specified, fail return -1; } - DataManager::SetValue("tw_encrypt_backup", 0); - if (!PartitionManager.stop_backup.get_value()) { - if (ret == false) - ret = 1; // 1 for failure - else - ret = 0; // 0 for success - DataManager::SetValue("tw_cancel_backup", 0); - } - else { - DataManager::SetValue("tw_cancel_backup", 1); - gui_msg("backup_cancel=Backup Cancelled"); - ret = 0; - } operation_end(ret); return ret; } @@ -1591,8 +1607,7 @@ int GUIAction::installsu(std::string arg __unused) if (simulate) { simulate_progress_bar(); } else { - if (!TWFunc::Install_SuperSU()) - op_status = 1; + LOGERR("Installing SuperSU was deprecated from TWRP.\n"); } operation_end(op_status); diff --git a/gui/fileselector.cpp b/gui/fileselector.cpp index a992b80eaa..c4aaab1685 100644 --- a/gui/fileselector.cpp +++ b/gui/fileselector.cpp @@ -30,6 +30,7 @@ extern "C" { #include "objects.hpp" #include "../data.hpp" #include "../twrp-functions.hpp" +#include "../adbbu/libtwadbbu.hpp" int GUIFileSelector::mSortOrder = 0; @@ -270,7 +271,10 @@ int GUIFileSelector::GetFileList(const std::string folder) mFolderList.push_back(data); } else if (data.fileType == DT_REG || data.fileType == DT_LNK || data.fileType == DT_BLK) { if (mExtn.empty() || (data.fileName.length() > mExtn.length() && data.fileName.substr(data.fileName.length() - mExtn.length()) == mExtn)) { - mFileList.push_back(data); + if (mExtn == ".ab" && twadbbu::Check_ADB_Backup_File(path)) + mFolderList.push_back(data); + else + mFileList.push_back(data); } } } @@ -354,8 +358,8 @@ void GUIFileSelector::NotifySelect(size_t item_selected) cwd += str; } - if (mShowNavFolders == 0 && mShowFiles == 0) { - // nav folders and files are disabled, this is probably the restore list and we need to save chosen location to mVariable instead of mPathVar + if (mShowNavFolders == 0 && (mShowFiles == 0 || mExtn == ".ab")) { + // this is probably the restore list and we need to save chosen location to mVariable instead of mPathVar DataManager::SetValue(mVariable, cwd); } else { // We are changing paths, so we need to set mPathVar diff --git a/gui/gui.cpp b/gui/gui.cpp index 369b6b897d..a270e365f7 100644 --- a/gui/gui.cpp +++ b/gui/gui.cpp @@ -33,7 +33,6 @@ #include #include #include -#include extern "C" { @@ -79,6 +78,8 @@ int gGuiRunning = 0; int g_pty_fd = -1; // set by terminal on init void terminal_pty_read(); +int select_fd = 0; + static int gRecorder = -1; extern "C" void gr_write_frame_to_file(int fd); @@ -395,9 +396,18 @@ void InputHandler::handleDrag() } } +void set_select_fd() { + select_fd = ors_read_fd + 1; + if (g_pty_fd >= select_fd) + select_fd = g_pty_fd + 1; + if (PartitionManager.uevent_pfd.fd >= select_fd) + select_fd = PartitionManager.uevent_pfd.fd + 1; +} + static void setup_ors_command() { ors_read_fd = -1; + set_select_fd(); unlink(ORS_INPUT_FILE); if (mkfifo(ORS_INPUT_FILE, 06660) != 0) { @@ -417,6 +427,7 @@ static void setup_ors_command() unlink(ORS_INPUT_FILE); unlink(ORS_OUTPUT_FILE); } + set_select_fd(); } // callback called after a CLI command was executed @@ -448,6 +459,7 @@ static void ors_command_read() if (!orsout) { close(ors_read_fd); ors_read_fd = -1; + set_select_fd(); LOGINFO("Unable to fopen %s\n", ORS_OUTPUT_FILE); unlink(ORS_INPUT_FILE); unlink(ORS_OUTPUT_FILE); @@ -462,14 +474,6 @@ static void ors_command_read() gui_set_FILE(orsout); PageManager::GetResources()->DumpStrings(); ors_command_done(); - //check to see if we should show backup page for parsing adbbackup partitions - } else if (strlen(command) == 23 && strncmp(command, "adbbackup", 9) == 0) { - gui_set_FILE(orsout); - DataManager::SetValue("tw_action", "twcmd"); - DataManager::SetValue("tw_action_param", command); - DataManager::SetValue("tw_enable_adb_backup", 1); - gui_changePage("backup"); - ors_command_done(); } else { // mirror output messages gui_set_FILE(orsout); @@ -562,29 +566,30 @@ static int runPages(const char *page_name, const int stop_on_page_done) for (;;) { loopTimer(input_timeout_ms); + FD_ZERO(&fdset); + timeout.tv_sec = 0; + timeout.tv_usec = 1; if (g_pty_fd > 0) { - // TODO: this is not nice, we should have one central select for input, pty, and ors - FD_ZERO(&fdset); FD_SET(g_pty_fd, &fdset); - timeout.tv_sec = 0; - timeout.tv_usec = 1; - has_data = select(g_pty_fd+1, &fdset, NULL, NULL, &timeout); - if (has_data > 0) { - terminal_pty_read(); - } + } + if (PartitionManager.uevent_pfd.fd > 0) { + FD_SET(PartitionManager.uevent_pfd.fd, &fdset); } #ifndef TW_OEM_BUILD if (ors_read_fd > 0 && !orsout) { // orsout is non-NULL if a command is still running - FD_ZERO(&fdset); FD_SET(ors_read_fd, &fdset); - timeout.tv_sec = 0; - timeout.tv_usec = 1; - has_data = select(ors_read_fd+1, &fdset, NULL, NULL, &timeout); - if (has_data > 0) { - ors_command_read(); - } } #endif + // TODO: combine this select with the poll done by input handling + has_data = select(select_fd, &fdset, NULL, NULL, &timeout); + if (has_data > 0) { + if (g_pty_fd > 0 && FD_ISSET(g_pty_fd, &fdset)) + terminal_pty_read(); + if (PartitionManager.uevent_pfd.fd > 0 && FD_ISSET(PartitionManager.uevent_pfd.fd, &fdset)) + PartitionManager.read_uevent(); + if (ors_read_fd > 0 && !orsout && FD_ISSET(ors_read_fd, &fdset)) + ors_command_read(); + } if (!gForceRender.get_value()) { @@ -644,6 +649,7 @@ static int runPages(const char *page_name, const int stop_on_page_done) if (ors_read_fd > 0) close(ors_read_fd); ors_read_fd = -1; + set_select_fd(); gGuiRunning = 0; return 0; } diff --git a/gui/gui.hpp b/gui/gui.hpp index afcd9b0a90..d5b9553d69 100644 --- a/gui/gui.hpp +++ b/gui/gui.hpp @@ -21,6 +21,8 @@ #include "twmsg.h" +void set_select_fd(); + void gui_msg(const char* text); void gui_warn(const char* text); void gui_err(const char* text); diff --git a/gui/pages.cpp b/gui/pages.cpp index b901526ff0..a3a1df3254 100644 --- a/gui/pages.cpp +++ b/gui/pages.cpp @@ -39,12 +39,17 @@ #include #include +#ifdef USE_MINZIP +#include "../minzip/SysUtil.h" +#else +#include "../otautil/SysUtil.h" +#endif + extern "C" { #include "../twcommon.h" -#include "../minzip/SysUtil.h" -#include "../minzip/Zip.h" #include "gui.h" } +#include "../zipwrap.hpp" #include "../minuitwrp/minui.h" #include "rapidxml.hpp" @@ -664,7 +669,7 @@ int Page::NotifyVarChange(std::string varName, std::string value) // transient data for loading themes struct LoadingContext { - ZipArchive* zip; // zip to load theme from, or NULL for the stock theme + ZipWrap* zip; // zip to load theme from, or NULL for the stock theme std::set filenames; // to detect cyclic includes std::string basepath; // if zip is NULL, base path to load includes from with trailing slash, otherwise empty std::vector*> xmldocs; // all loaded xml docs @@ -806,7 +811,7 @@ void PageSet::MakeEmergencyConsoleIfNeeded() } } -int PageSet::LoadLanguage(char* languageFile, ZipArchive* package) +int PageSet::LoadLanguage(char* languageFile, ZipWrap* package) { xml_document<> lang; xml_node<>* parent; @@ -1182,7 +1187,7 @@ void PageSet::AddStringResource(std::string resource_source, std::string resourc mResources->AddStringResource(resource_source, resource_name, value); } -char* PageManager::LoadFileToBuffer(std::string filename, ZipArchive* package) { +char* PageManager::LoadFileToBuffer(std::string filename, ZipWrap* package) { size_t len; char* buffer = NULL; @@ -1219,19 +1224,18 @@ char* PageManager::LoadFileToBuffer(std::string filename, ZipArchive* package) { close(fd); } else { LOGINFO("PageManager::LoadFileToBuffer loading filename: '%s' from zip\n", filename.c_str()); - const ZipEntry* zipentry = mzFindZipEntry(package, filename.c_str()); - if (zipentry == NULL) { + if (!package->EntryExists(filename)) { LOGERR("Unable to locate '%s' in zip file\n", filename.c_str()); return NULL; } // Allocate the buffer for the file - len = mzGetZipEntryUncompLen(zipentry); + len = package->GetUncompressedSize(filename); buffer = (char*) malloc(len + 1); if (!buffer) return NULL; - if (!mzExtractZipEntryToBuffer(package, zipentry, (unsigned char*) buffer)) { + if (!package->ExtractToBuffer(filename, (unsigned char*) buffer)) { LOGERR("Unable to extract '%s'\n", filename.c_str()); free(buffer); return NULL; @@ -1295,14 +1299,13 @@ void PageManager::LoadLanguageListDir(string dir) { closedir(d); } -void PageManager::LoadLanguageList(ZipArchive* package) { +void PageManager::LoadLanguageList(ZipWrap* package) { Language_List.clear(); if (TWFunc::Path_Exists(TWRES "customlanguages")) TWFunc::removeDir(TWRES "customlanguages", true); if (package) { TWFunc::Recursive_Mkdir(TWRES "customlanguages"); - struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default - mzExtractRecursive(package, "languages", TWRES "customlanguages/", ×tamp, NULL, NULL, NULL); + package->ExtractRecursive("languages", TWRES "customlanguages/"); LoadLanguageListDir(TWRES "customlanguages/"); } else { LoadLanguageListDir(TWRES "languages/"); @@ -1330,7 +1333,7 @@ void PageManager::LoadLanguage(string filename) { int PageManager::LoadPackage(std::string name, std::string package, std::string startpage) { std::string mainxmlfilename = package; - ZipArchive zip; + ZipWrap zip; char* languageFile = NULL; char* baseLanguageFile = NULL; PageSet* pageSet = NULL; @@ -1367,13 +1370,19 @@ int PageManager::LoadPackage(std::string name, std::string package, std::string tw_h_offset = 0; if (!TWFunc::Path_Exists(package)) return -1; +#ifdef USE_MINZIP if (sysMapFile(package.c_str(), &map) != 0) { +#else + if (!map.MapFile(package)) { +#endif LOGERR("Failed to map '%s'\n", package.c_str()); goto error; } - if (mzOpenZipArchive(map.addr, map.length, &zip)) { + if (!zip.Open(package.c_str(), &map)) { LOGERR("Unable to open zip archive '%s'\n", package.c_str()); +#ifdef USE_MINZIP sysReleaseMap(&map); +#endif goto error; } ctx.zip = &zip; @@ -1414,16 +1423,20 @@ int PageManager::LoadPackage(std::string name, std::string package, std::string mCurrentSet = pageSet; if (ctx.zip) { - mzCloseZipArchive(ctx.zip); + ctx.zip->Close(); +#ifdef USE_MINZIP sysReleaseMap(&map); +#endif } return ret; error: // Sometimes we get here without a real error if (ctx.zip) { - mzCloseZipArchive(ctx.zip); + ctx.zip->Close(); +#ifdef USE_MINZIP sysReleaseMap(&map); +#endif } return -1; } diff --git a/gui/pages.hpp b/gui/pages.hpp index 1e71b10e04..282b2d5b12 100644 --- a/gui/pages.hpp +++ b/gui/pages.hpp @@ -21,7 +21,7 @@ #ifndef _PAGES_HEADER_HPP #define _PAGES_HEADER_HPP -#include "../minzip/Zip.h" +#include "../zipwrap.hpp" #include #include #include @@ -116,7 +116,7 @@ class PageSet public: int Load(LoadingContext& ctx, const std::string& filename); - int LoadLanguage(char* languageFile, ZipArchive* package); + int LoadLanguage(char* languageFile, ZipWrap* package); void MakeEmergencyConsoleIfNeeded(); Page* FindPage(std::string name); @@ -155,8 +155,8 @@ class PageManager { public: // Used by GUI - static char* LoadFileToBuffer(std::string filename, ZipArchive* package); - static void LoadLanguageList(ZipArchive* package); + static char* LoadFileToBuffer(std::string filename, ZipWrap* package); + static void LoadLanguageList(ZipWrap* package); static void LoadLanguage(std::string filename); static int LoadPackage(std::string name, std::string package, std::string startpage); static PageSet* SelectPackage(std::string name); diff --git a/gui/resources.cpp b/gui/resources.cpp index bd71d50b06..59b8ed484e 100644 --- a/gui/resources.cpp +++ b/gui/resources.cpp @@ -30,7 +30,7 @@ #include #include -#include "../minzip/Zip.h" +#include "../zipwrap.hpp" extern "C" { #include "../twcommon.h" #include "gui.h" @@ -42,38 +42,24 @@ extern "C" { #define TMP_RESOURCE_NAME "/tmp/extract.bin" -Resource::Resource(xml_node<>* node, ZipArchive* pZip __unused) +Resource::Resource(xml_node<>* node, ZipWrap* pZip __unused) { if (node && node->first_attribute("name")) mName = node->first_attribute("name")->value(); } -int Resource::ExtractResource(ZipArchive* pZip, std::string folderName, std::string fileName, std::string fileExtn, std::string destFile) +int Resource::ExtractResource(ZipWrap* pZip, std::string folderName, std::string fileName, std::string fileExtn, std::string destFile) { if (!pZip) return -1; std::string src = folderName + "/" + fileName + fileExtn; - - const ZipEntry* binary = mzFindZipEntry(pZip, src.c_str()); - if (binary == NULL) { - return -1; - } - - unlink(destFile.c_str()); - int fd = creat(destFile.c_str(), 0666); - if (fd < 0) + if (!pZip->ExtractEntry(src, destFile, 0666)) return -1; - - int ret = 0; - if (!mzExtractZipEntryToFile(pZip, binary, fd)) - ret = -1; - - close(fd); - return ret; + return 0; } -void Resource::LoadImage(ZipArchive* pZip, std::string file, gr_surface* surface) +void Resource::LoadImage(ZipWrap* pZip, std::string file, gr_surface* surface) { int rc = 0; if (ExtractResource(pZip, "images", file, ".png", TMP_RESOURCE_NAME) == 0) @@ -119,7 +105,7 @@ void Resource::CheckAndScaleImage(gr_surface source, gr_surface* destination, in } } -FontResource::FontResource(xml_node<>* node, ZipArchive* pZip) +FontResource::FontResource(xml_node<>* node, ZipWrap* pZip) : Resource(node, pZip) { origFontSize = 0; @@ -127,7 +113,7 @@ FontResource::FontResource(xml_node<>* node, ZipArchive* pZip) LoadFont(node, pZip); } -void FontResource::LoadFont(xml_node<>* node, ZipArchive* pZip) +void FontResource::LoadFont(xml_node<>* node, ZipWrap* pZip) { std::string file; xml_attribute<>* attr; @@ -192,7 +178,7 @@ void FontResource::DeleteFont() { origFont = NULL; } -void FontResource::Override(xml_node<>* node, ZipArchive* pZip) { +void FontResource::Override(xml_node<>* node, ZipWrap* pZip) { if (!origFont) { origFont = mFont; } else if (mFont) { @@ -207,7 +193,7 @@ FontResource::~FontResource() DeleteFont(); } -ImageResource::ImageResource(xml_node<>* node, ZipArchive* pZip) +ImageResource::ImageResource(xml_node<>* node, ZipWrap* pZip) : Resource(node, pZip) { std::string file; @@ -238,7 +224,7 @@ ImageResource::~ImageResource() res_free_surface(mSurface); } -AnimationResource::AnimationResource(xml_node<>* node, ZipArchive* pZip) +AnimationResource::AnimationResource(xml_node<>* node, ZipWrap* pZip) : Resource(node, pZip) { std::string file; @@ -359,7 +345,7 @@ void ResourceManager::AddStringResource(std::string resource_source, std::string mStrings[resource_name] = res; } -void ResourceManager::LoadResources(xml_node<>* resList, ZipArchive* pZip, std::string resource_source) +void ResourceManager::LoadResources(xml_node<>* resList, ZipWrap* pZip, std::string resource_source) { if (!resList) return; diff --git a/gui/resources.hpp b/gui/resources.hpp index 2d30676677..de673184c5 100644 --- a/gui/resources.hpp +++ b/gui/resources.hpp @@ -25,8 +25,7 @@ #include #include #include "rapidxml.hpp" - -struct ZipArchive; +#include "../zipwrap.hpp" extern "C" { #include "../minuitwrp/minui.h" @@ -36,7 +35,7 @@ extern "C" { class Resource { public: - Resource(xml_node<>* node, ZipArchive* pZip); + Resource(xml_node<>* node, ZipWrap* pZip); virtual ~Resource() {} public: @@ -46,27 +45,27 @@ class Resource std::string mName; protected: - static int ExtractResource(ZipArchive* pZip, std::string folderName, std::string fileName, std::string fileExtn, std::string destFile); - static void LoadImage(ZipArchive* pZip, std::string file, gr_surface* surface); + static int ExtractResource(ZipWrap* pZip, std::string folderName, std::string fileName, std::string fileExtn, std::string destFile); + static void LoadImage(ZipWrap* pZip, std::string file, gr_surface* surface); static void CheckAndScaleImage(gr_surface source, gr_surface* destination, int retain_aspect); }; class FontResource : public Resource { public: - FontResource(xml_node<>* node, ZipArchive* pZip); + FontResource(xml_node<>* node, ZipWrap* pZip); virtual ~FontResource(); public: void* GetResource() { return this ? mFont : NULL; } int GetHeight() { return gr_ttf_getMaxFontHeight(this ? mFont : NULL); } - void Override(xml_node<>* node, ZipArchive* pZip); + void Override(xml_node<>* node, ZipWrap* pZip); protected: void* mFont; private: - void LoadFont(xml_node<>* node, ZipArchive* pZip); + void LoadFont(xml_node<>* node, ZipWrap* pZip); void DeleteFont(); private: @@ -77,7 +76,7 @@ class FontResource : public Resource class ImageResource : public Resource { public: - ImageResource(xml_node<>* node, ZipArchive* pZip); + ImageResource(xml_node<>* node, ZipWrap* pZip); virtual ~ImageResource(); public: @@ -92,7 +91,7 @@ class ImageResource : public Resource class AnimationResource : public Resource { public: - AnimationResource(xml_node<>* node, ZipArchive* pZip); + AnimationResource(xml_node<>* node, ZipWrap* pZip); virtual ~AnimationResource(); public: @@ -112,7 +111,7 @@ class ResourceManager ResourceManager(); virtual ~ResourceManager(); void AddStringResource(std::string resource_source, std::string resource_name, std::string value); - void LoadResources(xml_node<>* resList, ZipArchive* pZip, std::string resource_source); + void LoadResources(xml_node<>* resList, ZipWrap* pZip, std::string resource_source); public: FontResource* FindFont(const std::string& name) const; diff --git a/gui/terminal.cpp b/gui/terminal.cpp index 1744788da8..65ad2c0266 100644 --- a/gui/terminal.cpp +++ b/gui/terminal.cpp @@ -34,6 +34,7 @@ extern "C" { #include "../twcommon.h" } #include "../minuitwrp/minui.h" +#include "gui.hpp" #include "rapidxml.hpp" #include "objects.hpp" @@ -83,6 +84,7 @@ class Pseudoterminal // and write it to the terminal // this currently works through gui.cpp calling terminal_pty_read below g_pty_fd = fdMaster; + set_select_fd(); return true; } else { @@ -174,6 +176,7 @@ class Pseudoterminal } close(fdMaster); g_pty_fd = fdMaster = -1; + set_select_fd(); int status; waitpid(pid, &status, WNOHANG); // avoid zombies but don't hang if the child is still alive and we got here due to some error pid = 0; diff --git a/gui/theme/common/landscape.xml b/gui/theme/common/landscape.xml index c3d89fe2d4..5bbfe90295 100644 --- a/gui/theme/common/landscape.xml +++ b/gui/theme/common/landscape.xml @@ -2047,7 +2047,7 @@ {@restore_sel_pack_fs=Select Package to Restore:} - + @@ -2273,6 +2273,7 @@ + {@restore_enable_digest_chk=Enable Digest Verification of Backup Files} @@ -2736,7 +2737,7 @@ @@ -4725,51 +4726,6 @@ - -