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 bb22a3ebcd..f42f89119b 100644 --- a/Android.mk +++ b/Android.mk @@ -40,25 +40,29 @@ TWHTCD_PATH := $(TWRES_PATH)htcd/ TARGET_RECOVERY_GUI := true +ifneq ($(TW_DEVICE_VERSION),) + LOCAL_CFLAGS += -DTW_DEVICE_VERSION='"-$(TW_DEVICE_VERSION)"' +else + LOCAL_CFLAGS += -DTW_DEVICE_VERSION='"-0"' +endif + LOCAL_SRC_FILES := \ twrp.cpp \ fixContexts.cpp \ twrpTar.cpp \ - twrpDU.cpp \ - twrpDigest.cpp \ - digest/md5.c \ + exclude.cpp \ find_file.cpp \ - infomanager.cpp - -LOCAL_SRC_FILES += \ + infomanager.cpp \ data.cpp \ partition.cpp \ partitionmanager.cpp \ progresstracking.cpp \ twinstall.cpp \ 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) @@ -68,14 +72,17 @@ LOCAL_MODULE := recovery #LOCAL_FORCE_STATIC_EXECUTABLE := true +#ifeq ($(TARGET_USERIMAGES_USE_F2FS),true) #ifeq ($(HOST_OS),linux) #LOCAL_REQUIRED_MODULES := mkfs.f2fs #endif +#endif RECOVERY_API_VERSION := 3 RECOVERY_FSTAB_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CLANG := true #LOCAL_STATIC_LIBRARIES := \ # libext4_utils_static \ @@ -98,27 +105,47 @@ LOCAL_CFLAGS += -Wno-unused-parameter 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 external/openssl/include $(LOCAL_PATH)/libmincrypt/includes +LOCAL_C_INCLUDES += bionic ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) - LOCAL_C_INCLUDES += external/stlport/stlport + LOCAL_C_INCLUDES += external/stlport/stlport external/openssl/include +else + LOCAL_C_INCLUDES += external/boringssl/include external/libcxx/include endif LOCAL_STATIC_LIBRARIES := LOCAL_SHARED_LIBRARIES := LOCAL_STATIC_LIBRARIES += libguitwrp -LOCAL_SHARED_LIBRARIES += libz libc libcutils libstdc++ libtar libblkid libminuitwrp libminadbd libmtdutils libminzip libaosprecovery libtwadbbu -LOCAL_SHARED_LIBRARIES += libcrecovery +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 + LOCAL_CFLAGS += -DTW_NO_SHA2_LIBRARY +endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0) + LOCAL_SHARED_LIBRARIES += libmincrypttwrp + LOCAL_C_INCLUDES += $(LOCAL_PATH)/libmincrypt/includes + LOCAL_CFLAGS += -DUSE_OLD_VERIFIER else - LOCAL_SHARED_LIBRARIES += libc++ + 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),) @@ -132,34 +159,30 @@ ifeq ($(TW_OEM_BUILD),true) TW_EXCLUDE_SUPERSU := true TW_EXCLUDE_MTP := true endif + ifeq ($(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-static + #LOCAL_STATIC_LIBRARIES += liblz4 endif endif -ifneq ($(wildcard external/libselinux/Android.mk),) - TWHAVE_SELINUX := true -endif -ifeq ($(TWHAVE_SELINUX), true) - #LOCAL_C_INCLUDES += external/libselinux/include - #LOCAL_STATIC_LIBRARIES += libselinux - #LOCAL_CFLAGS += -DHAVE_SELINUX -g -endif # HAVE_SELINUX -ifeq ($(TWHAVE_SELINUX), true) - LOCAL_C_INCLUDES += external/libselinux/include - LOCAL_SHARED_LIBRARIES += libselinux - LOCAL_CFLAGS += -DHAVE_SELINUX -g - 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-static - endif - 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 + +ifeq ($(AB_OTA_UPDATER),true) + LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 + LOCAL_SHARED_LIBRARIES += libhardware + LOCAL_ADDITIONAL_DEPENDENCIES += libhardware endif LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin @@ -277,7 +300,20 @@ ifeq ($(TW_INCLUDE_CRYPTO), true) LOCAL_CFLAGS += -DTW_INCLUDE_CRYPTO LOCAL_SHARED_LIBRARIES += libcryptfslollipop libgpt_twrp LOCAL_C_INCLUDES += external/boringssl/src/include + ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?),0) + TW_INCLUDE_CRYPTO_FBE := true + LOCAL_CFLAGS += -DTW_INCLUDE_FBE + LOCAL_SHARED_LIBRARIES += libe4crypt + endif + ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),) + ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),false) + LOCAL_CFLAGS += -DTW_CRYPTO_USE_SYSTEM_VOLD + LOCAL_STATIC_LIBRARIES += libvolddecrypt + endif + endif endif +WITH_CRYPTO_UTILS := \ + $(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 @@ -305,7 +341,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) @@ -329,7 +367,7 @@ else LOCAL_CFLAGS += -DTW_DEFAULT_LANGUAGE=en endif -LOCAL_ADDITIONAL_DEPENDENCIES := \ +LOCAL_ADDITIONAL_DEPENDENCIES += \ dump_image \ erase_image \ flash_image \ @@ -338,12 +376,14 @@ LOCAL_ADDITIONAL_DEPENDENCIES := \ teamwin \ toolbox_symlinks \ twrp \ - unpigz_symlink \ fsck.fat \ fatlabel \ mkfs.fat \ permissive.sh \ - simg2img_twrp + simg2img_twrp \ + libbootloader_message_twrp \ + init.recovery.hlthchrg.rc \ + init.recovery.service.rc ifneq ($(TARGET_ARCH), arm64) ifneq ($(TARGET_ARCH), x86_64) @@ -356,6 +396,11 @@ else endif ifneq ($(TW_USE_TOOLBOX), true) LOCAL_ADDITIONAL_DEPENDENCIES += busybox_symlinks + ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0) + LOCAL_POST_INSTALL_CMD := \ + $(hide) mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/sbin && \ + ln -sf /sbin/busybox $(TARGET_RECOVERY_ROOT_OUT)/sbin/sh + endif else ifneq ($(wildcard external/toybox/Android.mk),) LOCAL_ADDITIONAL_DEPENDENCIES += toybox_symlinks @@ -367,6 +412,7 @@ else LOCAL_ADDITIONAL_DEPENDENCIES += unzip endif endif + ifneq ($(TW_NO_EXFAT), true) LOCAL_ADDITIONAL_DEPENDENCIES += mkexfatfs fsckexfat ifneq ($(TW_NO_EXFAT_FUSE), true) @@ -381,7 +427,7 @@ ifeq ($(BOARD_HAS_NO_REAL_SDCARD),) endif endif ifneq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS), true) - LOCAL_ADDITIONAL_DEPENDENCIES += openaes ../openaes/LICENSE + LOCAL_ADDITIONAL_DEPENDENCIES += openaes openaes_license endif ifeq ($(TW_INCLUDE_DUMLOCK), true) LOCAL_ADDITIONAL_DEPENDENCIES += \ @@ -448,8 +494,27 @@ ifeq ($(shell test $(CM_PLATFORM_SDK_VERSION) -ge 3; echo $$?),0) endif endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 25; echo $$?),0) + LOCAL_ADDITIONAL_DEPENDENCIES += file_contexts_text +endif + +ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),) +LOCAL_REQUIRED_MODULES := recovery-persist recovery-refresh +endif + include $(BUILD_EXECUTABLE) +# Symlink for file_contexts +include $(CLEAR_VARS) + +LOCAL_MODULE := file_contexts_text +LOCAL_MODULE_TAGS := optional +LOCAL_REQUIRED_MODULES := file_contexts.bin +LOCAL_POST_INSTALL_CMD := \ + $(hide) cp -f $(OUT)/obj/ETC/file_contexts.bin_intermediates/file_contexts.concat.tmp $(TARGET_RECOVERY_ROOT_OUT)/file_contexts + +include $(BUILD_PHONY_PACKAGE) + ifneq ($(TW_USE_TOOLBOX), true) include $(CLEAR_VARS) # Create busybox symlinks... gzip and gunzip are excluded because those need to link to pigz instead @@ -468,10 +533,8 @@ endif # If busybox does not have restorecon, assume it does not have SELinux support. # Then, let toolbox provide 'ls' so -Z is available to list SELinux contexts. -ifeq ($(TWHAVE_SELINUX), true) - ifeq ($(filter restorecon, $(notdir $(BUSYBOX_LINKS))),) - exclude += ls - endif +ifeq ($(filter restorecon, $(notdir $(BUSYBOX_LINKS))),) + exclude += ls endif RECOVERY_BUSYBOX_TOOLS := $(filter-out $(exclude), $(notdir $(BUSYBOX_LINKS))) @@ -494,81 +557,209 @@ include $(BUILD_PHONY_PACKAGE) RECOVERY_BUSYBOX_SYMLINKS := endif # !TW_USE_TOOLBOX -# All the APIs for testing +# recovery-persist (system partition dynamic executable run after /data mounts) +# =============================== +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?),0) + include $(CLEAR_VARS) + LOCAL_SRC_FILES := \ + recovery-persist.cpp \ + rotate_logs.cpp + LOCAL_MODULE := recovery-persist + LOCAL_SHARED_LIBRARIES := liblog libbase + LOCAL_CFLAGS := -Werror + LOCAL_INIT_RC := recovery-persist.rc + include $(BUILD_EXECUTABLE) +endif + +# recovery-refresh (system partition dynamic executable run at init) +# =============================== +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?),0) + include $(CLEAR_VARS) + LOCAL_SRC_FILES := \ + recovery-refresh.cpp \ + rotate_logs.cpp + LOCAL_MODULE := recovery-refresh + LOCAL_SHARED_LIBRARIES := liblog libbase + LOCAL_CFLAGS := -Werror + LOCAL_INIT_RC := recovery-refresh.rc + include $(BUILD_EXECUTABLE) +endif + +# shared libfusesideload +# =============================== include $(CLEAR_VARS) -LOCAL_MODULE := libverifier -LOCAL_MODULE_TAGS := tests -LOCAL_SRC_FILES := \ - asn1_decoder.cpp -include $(BUILD_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := fuse_sideload.c - -LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter +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_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes -LOCAL_SHARED_LIBRARIES := libcutils libc libmincrypttwrp +LOCAL_SHARED_LIBRARIES := libcutils libc +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0) + LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes + LOCAL_SHARED_LIBRARIES += libmincrypttwrp + LOCAL_CFLAGS += -DUSE_MINCRYPT +else + LOCAL_SHARED_LIBRARIES += libcrypto +endif include $(BUILD_SHARED_LIBRARY) +# static libfusesideload +# =============================== (required to fix build errors in 8.1 due to use by tests) include $(CLEAR_VARS) -LOCAL_MODULE := verifier_test -LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := fuse_sideload.cpp +LOCAL_CLANG := true +LOCAL_CFLAGS := -Wall -Werror +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE -LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes +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) -LOCAL_CFLAGS += -DNO_RECOVERY_MOUNT -LOCAL_CFLAGS += -Wno-unused-parameter +# 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 := \ - verifier_test.cpp \ - asn1_decoder.cpp \ - verifier.cpp \ - ui.cpp + 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 := \ - libmincrypttwrp \ libminui \ - libminzip \ - libcutils \ - libstdc++ \ - libc -include $(BUILD_EXECUTABLE) + libvintf_recovery \ + libcrypto_utils \ + libcrypto \ + libbase \ + libziparchive \ + +include $(BUILD_STATIC_LIBRARY) +# shared libaosprecovery for Apache code +# =============================== include $(CLEAR_VARS) LOCAL_MODULE := libaosprecovery LOCAL_MODULE_TAGS := eng optional -LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes -LOCAL_SRC_FILES := adb_install.cpp asn1_decoder.cpp bootloader.cpp legacy_property_service.c verifier.cpp set_metadata.c tw_atomic.cpp -LOCAL_SHARED_LIBRARIES += libc liblog libcutils libmtdutils libfusesideload libmincrypttwrp libselinux +LOCAL_CFLAGS := -std=gnu++0x +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) + LOCAL_SHARED_LIBRARIES += libstdc++ libstlport + LOCAL_C_INCLUDES += bionic external/stlport/stlport +else + LOCAL_SHARED_LIBRARIES += libc++ +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 verifier24/asn1_decoder.cpp + LOCAL_CFLAGS += -DUSE_OLD_VERIFIER +else + LOCAL_SHARED_LIBRARIES += libcrypto libbase + LOCAL_SRC_FILES += verifier.cpp asn1_decoder.cpp +endif -ifneq ($(BOARD_RECOVERY_BLDRMSG_OFFSET),) - LOCAL_CFLAGS += -DBOARD_RECOVERY_BLDRMSG_OFFSET=$(BOARD_RECOVERY_BLDRMSG_OFFSET) +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) +# All the APIs for testing +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_MODULE := libverifier +LOCAL_SRC_FILES := \ + asn1_decoder.cpp \ + verifier.cpp \ + ui.cpp +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)/tests/Android.mk \ + $(LOCAL_PATH)/tools/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ - $(LOCAL_PATH)/applypatch/Android.mk + $(LOCAL_PATH)/update_verifier/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 - TARGET_GLOBAL_CFLAGS += -DTW_USE_OLD_MINUI_H - include $(commands_recovery_local_path)/minadbd.old/Android.mk \ - $(commands_recovery_local_path)/minui.old/Android.mk + TARGET_GLOBAL_CFLAGS += -DTW_USE_MINUI_21 + include $(commands_recovery_local_path)/minadbd21/Android.mk \ + $(commands_recovery_local_path)/minui21/Android.mk endif #includes for TWRP @@ -585,9 +776,9 @@ 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)/libmincrypt/Android.mk \ $(commands_recovery_local_path)/twrpTarMain/Android.mk \ $(commands_recovery_local_path)/mtp/Android.mk \ $(commands_recovery_local_path)/minzip/Android.mk \ @@ -596,11 +787,25 @@ include $(commands_recovery_local_path)/injecttwrp/Android.mk \ $(commands_recovery_local_path)/toybox/Android.mk \ $(commands_recovery_local_path)/simg2img/Android.mk \ $(commands_recovery_local_path)/adbbu/Android.mk \ - $(commands_recovery_local_path)/libpixelflinger/Android.mk + $(commands_recovery_local_path)/libpixelflinger/Android.mk \ + $(commands_recovery_local_path)/twrpDigest/Android.mk \ + $(commands_recovery_local_path)/attr/Android.mk + +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0) + include $(commands_recovery_local_path)/libmincrypt/Android.mk +endif ifeq ($(TW_INCLUDE_CRYPTO), true) include $(commands_recovery_local_path)/crypto/lollipop/Android.mk include $(commands_recovery_local_path)/crypto/scrypt/Android.mk + ifeq ($(TW_INCLUDE_CRYPTO_FBE), true) + include $(commands_recovery_local_path)/crypto/ext4crypt/Android.mk + endif + ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),) + ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),false) + include $(commands_recovery_local_path)/crypto/vold_decrypt/Android.mk + endif + endif include $(commands_recovery_local_path)/gpt/Android.mk endif ifeq ($(BUILD_ID), GINGERBREAD) 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 5c54468d43..291708c69c 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -14,71 +14,120 @@ * 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 "fuse_sideload.h" -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)); +#include "common.h" +#include "fuse_sideload.h" +#ifdef USE_OLD_VERIFIER +#include "verifier24/verifier.h" +#else +#include "verifier.h" +#endif + +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 @@ -88,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; - 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 85e0acc5b6..efb5f9ae54 100644 --- a/adbbu/Android.mk +++ b/adbbu/Android.mk @@ -1,40 +1,19 @@ LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - twrpback.cpp \ - ../twrpDigest.cpp \ - ../digest/md5.c -LOCAL_SHARED_LIBRARIES += libstdc++ libz -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_C_INCLUDES += bionic external/zlib -LOCAL_CFLAGS:= -c -W -LOCAL_MODULE:= twrpbu -LOCAL_MODULE_STEM := bu -LOCAL_MODULE_TAGS:= eng -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_CFLAGS = -fno-strict-aliasing -D_LARGFILE_SOURCE 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 + libtwadbbu.cpp \ + twrpback.cpp -LOCAL_SHARED_LIBRARIES += libz libc libstdc++ +LOCAL_SHARED_LIBRARIES += libz libc libstdc++ libtwrpdigest ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) LOCAL_SHARED_LIBRARIES += libstlport @@ -43,3 +22,27 @@ else endif include $(BUILD_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + adbbumain.cpp + +LOCAL_SHARED_LIBRARIES += libstdc++ libz libtwadbbu + +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_C_INCLUDES += bionic external/zlib +LOCAL_CFLAGS:= -c -W +LOCAL_MODULE:= twrpbu +LOCAL_MODULE_STEM := bu +LOCAL_MODULE_TAGS:= eng +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +include $(BUILD_EXECUTABLE) + 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..64e688c374 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 (1) { + 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 4e76be165f..fd8eba9f44 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 10 //Max amount of errors for while loops +#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 2 //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 @@ -65,6 +71,11 @@ struct AdbBackupControlType { char type[16]; //stores the type of command, TWENDADB, TWCNT, TWEOF, TWMD5, TWDATA and TWERROR uint32_t crc; //stores the zlib 32 bit crc of the AdbBackupControlType struct to allow for making sure we are processing metadata char space[484]; //stores space to align the struct to 512 bytes + + //return a C++ string while not reading outside the type char array + std::string get_type() { + return std::string(type, strnlen(type, sizeof(type)-1)); + } }; //general info for file metadata stored in adb backup header diff --git a/adbbu/twrpback.cpp b/adbbu/twrpback.cpp index e22574b276..d88a9c9da5 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 @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -29,15 +28,18 @@ #include #include #include -#include #include +#include #include +#include +#include #include "twadbstream.h" #include "twrpback.hpp" -#include "../variables.h" -#include "../twcommon.h" -#include "../twrpDigest.hpp" +#include "libtwadbbu.hpp" +#include "../twrpDigest/twrpDigest.hpp" +#include "../twrpDigest/twrpMD5.hpp" +#include "../twrpAdbBuFifo.hpp" twrpback::twrpback(void) { read_fd = 0; @@ -49,11 +51,40 @@ twrpback::twrpback(void) { adb_write_fd = 0; ors_fd = 0; firstPart = true; + createFifos(); adbloginit(); } twrpback::~twrpback(void) { adblogfile.close(); + closeFifos(); +} + +void twrpback::createFifos(void) { + if (mkfifo(TW_ADB_BU_CONTROL, 0666) < 0) { + std::stringstream str; + str << strerror(errno); + adblogwrite("Unable to create TW_ADB_BU_CONTROL fifo: " + str.str() + "\n"); + } + if (mkfifo(TW_ADB_TWRP_CONTROL, 0666) < 0) { + std::stringstream str; + str << strerror(errno); + adblogwrite("Unable to create TW_ADB_TWRP_CONTROL fifo: " + str.str() + "\n"); + unlink(TW_ADB_BU_CONTROL); + } +} + +void twrpback::closeFifos(void) { + if (unlink(TW_ADB_BU_CONTROL) < 0) { + std::stringstream str; + str << strerror(errno); + adblogwrite("Unable to remove TW_ADB_BU_CONTROL: " + str.str()); + } + if (unlink(TW_ADB_TWRP_CONTROL) < 0) { + std::stringstream str; + str << strerror(errno); + adblogwrite("Unable to remove TW_ADB_TWRP_CONTROL: " + str.str()); + } } void twrpback::adbloginit(void) { @@ -94,13 +125,13 @@ void twrpback::close_restore_fds() { unlink(TW_ADB_RESTORE); } -int twrpback::backup(std::string command) { - twrpDigest adb_md5; +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; + int64_t count = false; // Count of how many blocks set uint64_t md5fnsize = 0; struct AdbBackupControlType endadb; @@ -113,43 +144,40 @@ 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"); + adblogwrite("Unable to open TW_ADB_FIFO\n"); close_backup_fds(); - return -1; + return false; } } - sprintf(operation, "adbbackup %s", command.c_str()); - if (write(write_fd, operation, sizeof(operation)) != sizeof(operation)) { - adblogwrite("Unable to write to ORS_INPUT_FILE\n"); + memset(operation, 0, sizeof(operation)); + 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; } - - 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"); + if (write(write_fd, operation, sizeof(operation)) != sizeof(operation)) { + adblogwrite("Unable to write to ORS_INPUT_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"); @@ -157,7 +185,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"); @@ -165,7 +193,7 @@ 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 @@ -174,22 +202,21 @@ int twrpback::backup(std::string command) { struct AdbBackupControlType structcmd; memcpy(&structcmd, cmd, sizeof(cmd)); - std::string cmdstr(structcmd.type); - std::string cmdtype = cmdstr.substr(0, sizeof(structcmd.type) - 1); + std::string cmdtype = structcmd.get_type(); //we received an error, exit and unlink if (cmdtype == TWERROR) { 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) { writedata = false; adblogwrite("Recieved TWENDADB\n"); memcpy(&endadb, cmd, sizeof(cmd)); - stringstream str; + std::stringstream str; str << totalbytes; adblogwrite(str.str() + " total bytes written\n"); breakloop = true; @@ -197,13 +224,13 @@ int twrpback::backup(std::string command) { //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)) { - stringstream str; + std::stringstream str; str << strerror(errno); adblogwrite("Error writing TWSTREAMHDR to adbd" + str.str() + "\n"); close_backup_fds(); - return -1; + return false; } fflush(adbd_fp); } @@ -211,17 +238,17 @@ int twrpback::backup(std::string command) { else if (cmdtype == TWIMG) { struct twfilehdr twimghdr; - adblogwrite("Writing TWIMG\n"); - adb_md5.initMD5(); - + adblogwrite("writing TWIMG\n"); + digest.init(); memset(&twimghdr, 0, sizeof(twimghdr)); memcpy(&twimghdr, cmd, sizeof(cmd)); md5fnsize = twimghdr.size; + compressed = false; - 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; @@ -230,8 +257,8 @@ int twrpback::backup(std::string command) { else if (cmdtype == TWFN) { struct twfilehdr twfilehdr; - adblogwrite("Writing TWFN\n"); - adb_md5.initMD5(); + adblogwrite("writing TWFN\n"); + digest.init(); ADBSTRUCT_STATIC_ASSERT(sizeof(twfilehdr) == MAX_ADB_READ); @@ -244,7 +271,7 @@ int twrpback::backup(std::string command) { 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; @@ -259,46 +286,50 @@ 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); - if (adb_md5.updateMD5stream((unsigned char *) writeresult, bytes) == -1) - adblogwrite("failed to update md5 stream\n"); - 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::stringstream str; + str << strerror(errno); + adblogwrite("Cannot write to adbd stream: " + str.str() + "\n"); } 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)) { + count = fileBytes / DATA_MAX_CHUNK_SIZE + 1; + count = count * DATA_MAX_CHUNK_SIZE; + + if (fileBytes % DATA_MAX_CHUNK_SIZE != 0) { + char padding[count - fileBytes]; + int paddingBytes = sizeof(padding); + std::stringstream paddingStr; + paddingStr << paddingBytes; + memset(padding, 0, 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 (adb_md5.updateMD5stream((unsigned char *) padding, sizeof(padding)) == -1) - adblogwrite("failed to update md5 stream\n"); + totalbytes += paddingBytes; + digest.update((unsigned char *) padding, paddingBytes); fflush(adbd_fp); - totalbytes = 0; } AdbBackupFileTrailer md5trailer; memset(&md5trailer, 0, sizeof(md5trailer)); - adb_md5.finalizeMD5stream(); - std::string md5string = adb_md5.createMD5string(); + std::string md5string = digest.return_digest_string(); strncpy(md5trailer.start_of_trailer, TWRP, sizeof(md5trailer.start_of_trailer)); strncpy(md5trailer.type, MD5TRAILER, sizeof(md5trailer.type)); @@ -314,11 +345,12 @@ 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)); } @@ -327,60 +359,76 @@ int twrpback::backup(std::string command) { //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))) == MAX_ADB_READ) { + 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; } 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); - if (adb_md5.updateMD5stream((unsigned char *) writeresult, bytes) == -1) - adblogwrite("failed to update md5 stream\n"); + 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; } 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::stringstream str; + str << strerror(errno); + adblogwrite("Cannot write to adbd stream: " + str.str() + "\n"); + close_restore_fds(); + return false; + } + fflush(adbd_fp); + delete [] writeAdbReadStream; + } + memset(&extraData, 0, bytesLeft); + if (bytesLeft == 0) { + break; + } } + fflush(adbd_fp); dataChunkBytes = 0; + firstDataPacket = true; } - } - compressed = false; } } @@ -388,23 +436,25 @@ 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; } -int twrpback::restore(void) { - twrpDigest adb_md5; +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; bool writedata, read_from_adb; bool breakloop, eofsent, md5trsent; + bool compressed; + bool md5TrailerReceived = false; breakloop = false; read_from_adb = true; @@ -415,61 +465,53 @@ int twrpback::restore(void) { 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"); + adblogwrite("Unable to open TW_ADB_FIFO\n"); 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"); + adblogwrite("Unable to write to TW_ADB_FIFO\n"); close_restore_fds(); - return -1; + return false; } - ors_fd = open(ORS_OUTPUT_FILE, O_RDONLY); - if (ors_fd < 0) { - stringstream str; - str << strerror(errno); - adblogwrite("Unable to write to ORS_OUTPUT_FILE: " + str.str() + "\n"); - close_restore_fds(); - return -1; - } - - 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) { - stringstream str; + std::stringstream str; str << strerror(errno); adblogwrite("Unable to open TW_ADB_BU_CONTROL for writing. " + str.str() + "\n"); 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) { - stringstream str; + std::stringstream str; str << strerror(errno); adblogwrite("Unable to open TW_ADB_TWRP_CONTROL for writing. " + str.str() + ". Retrying...\n"); while (adb_control_twrp_fd < 0) { @@ -479,7 +521,7 @@ 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; } } } @@ -490,8 +532,7 @@ int twrpback::restore(void) { if (read(adb_control_bu_fd, &cmd, sizeof(cmd)) > 0) { struct AdbBackupControlType structcmd; memcpy(&structcmd, cmd, sizeof(cmd)); - std::string cmdstr(structcmd.type); - std::string cmdtype = cmdstr.substr(0, sizeof(structcmd.type) - 1); + std::string cmdtype = structcmd.get_type(); //If we receive TWEOF from TWRP close adb data fifo if (cmdtype == TWEOF) { @@ -499,7 +540,7 @@ int twrpback::restore(void) { struct AdbBackupControlType tweof; memset(&tweof, 0, sizeof(tweof)); - memcpy(&tweof, result, sizeof(result)); + memcpy(&tweof, readAdbStream, sizeof(readAdbStream)); read_from_adb = true; } //Break when TWRP sends TWENDADB @@ -512,28 +553,23 @@ int twrpback::restore(void) { 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) { - std::string cmdstr; int readbytes; - if ((readbytes = fread(result, 1, sizeof(result), adbd_fp)) == sizeof(result)) { - totalbytes += readbytes; - memcpy(&structcmd, result, sizeof(result)); - cmdstr = structcmd.type; - std::string cmdtype = cmdstr.substr(0, sizeof(structcmd.type) - 1); + 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 if (cmdtype == TWENDADB) { struct AdbBackupControlType endadb; uint32_t crc, endadbcrc; - totalbytes -= sizeof(result); 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); @@ -542,17 +578,18 @@ int twrpback::restore(void) { if (crc == endadbcrc) { adblogwrite("Sending TWENDADB\n"); if (write(adb_control_twrp_fd, &endadb, sizeof(endadb)) < 1) { - stringstream str; + std::stringstream str; str << strerror(errno); adblogwrite("Cannot write to ADB_CONTROL_READ_FD: " + str.str() + "\n"); 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 @@ -561,10 +598,9 @@ int twrpback::restore(void) { uint32_t crc, cnthdrcrc; ADBSTRUCT_STATIC_ASSERT(sizeof(cnthdr) == MAX_ADB_READ); - totalbytes -= sizeof(result); 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); @@ -572,18 +608,18 @@ int twrpback::restore(void) { if (crc == cnthdrcrc) { adblogwrite("Restoring TWSTREAMHDR\n"); - if (write(adb_control_twrp_fd, result, sizeof(result)) < 0) { - stringstream str; + if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 0) { + std::stringstream str; str << strerror(errno); adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); 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 @@ -591,11 +627,10 @@ int twrpback::restore(void) { struct twfilehdr twimghdr; uint32_t crc, twimghdrcrc; - totalbytes -= sizeof(result); - adb_md5.initMD5(); + digest.init(); 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)); @@ -603,18 +638,18 @@ 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) { - stringstream str; + if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 1) { + std::stringstream str; str << strerror(errno); adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); close_restore_fds(); - return -1; + return false; } } else { adblogwrite("ADB TWIMG crc header doesn't match\n"); close_restore_fds(); - return -1; + return false; } adblogwrite("opening TW_ADB_RESTORE\n"); adb_write_fd = open(TW_ADB_RESTORE, O_WRONLY); @@ -624,11 +659,10 @@ int twrpback::restore(void) { struct twfilehdr twfilehdr; uint32_t crc, twfilehdrcrc; - totalbytes -= sizeof(result); - adb_md5.initMD5(); + 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)); @@ -637,187 +671,171 @@ 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) { - stringstream str; + if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 1) { + std::stringstream str; str << strerror(errno); adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); close_restore_fds(); - return -1; + return false; } } else { adblogwrite("ADB TWFN crc header doesn't match\n"); close_restore_fds(); - return -1; + return false; } adblogwrite("opening TW_ADB_RESTORE\n"); adb_write_fd = open(TW_ADB_RESTORE, O_WRONLY); } + else if (cmdtype == MD5TRAILER) { + read_from_adb = false; //don't read from adb until TWRP sends TWEOF + close(adb_write_fd); + md5TrailerReceived = true; + if (!checkMD5Trailer(readAdbStream, md5fnsize, &digest)) { + close_restore_fds(); + return false; + } + } //Send the tar or partition image md5 to TWRP else if (cmdtype == TWDATA) { - totalbytes -= sizeof(result); + dataChunkBytes += sizeof(readAdbStream); while (1) { - if ((readbytes = fread(result, 1, sizeof(result), adbd_fp)) != sizeof(result)) { + if ((readbytes = fread(readAdbStream, 1, sizeof(readAdbStream), adbd_fp)) != sizeof(readAdbStream)) { close_restore_fds(); - return -1; + return false; } + + memcpy(&structcmd, readAdbStream, sizeof(readAdbStream)); + + char *readAdbReadStream = new char [readbytes]; + memcpy(readAdbReadStream, readAdbStream, readbytes); + std::string cmdtype = structcmd.get_type(); + dataChunkBytes += readbytes; + delete [] readAdbReadStream; totalbytes += readbytes; - memcpy(&structcmd, result, sizeof(result)); - cmdstr = structcmd.type; - - if (cmdstr.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)); - - if (md5identmatch == md5ident) { - totalbytes -= sizeof(result); - 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) { - 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; - } - adblogwrite("md5 finalize stream\n"); - adb_md5.finalizeMD5stream(); - - 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 = adb_md5.createMD5string(); - strncpy(md5.md5, md5string.c_str(), sizeof(md5.md5)); - - adblogwrite("Sending MD5Check\n"); - if (write(adb_control_twrp_fd, &md5, sizeof(md5)) < 1) { - stringstream str; - str << strerror(errno); - adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); - close_restore_fds(); - return -1; - } - read_from_adb = false; //don't read from adb until TWRP sends TWEOF - break; + digest.update((unsigned char*)readAdbReadStream, readbytes); + + if (cmdtype == MD5TRAILER) { + read_from_adb = false; //don't read from adb until TWRP sends TWEOF + close(adb_write_fd); + if (!checkMD5Trailer(readAdbStream, md5fnsize, &digest)) { + close_restore_fds(); + return false; } + break; } - if (adb_md5.updateMD5stream((unsigned char*)result, sizeof(result)) == -1) - adblogwrite("failed to update md5 stream\n"); - dataChunkBytes += readbytes; - if (write(adb_write_fd, result, sizeof(result)) < 0) { - 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; - } + + if (write(adb_write_fd, readAdbStream, sizeof(readAdbStream)) < 0) { + adblogwrite("end of stream reached.\n"); + break; } - if (dataChunkBytes == ((DATA_MAX_CHUNK_SIZE) - sizeof(result))) { + if (dataChunkBytes == DATA_MAX_CHUNK_SIZE) { dataChunkBytes = 0; break; } - memset(&result, 0, sizeof(result)); + memset(&readAdbStream, 0, sizeof(readAdbStream)); } } + else { + if (!md5TrailerReceived) { + char *readAdbReadStream = new char [readbytes]; + memcpy(readAdbReadStream, readAdbStream, readbytes); + digest.update((unsigned char*)readAdbReadStream, readbytes); + totalbytes += readbytes; + delete [] readAdbReadStream; + } + + } } } } - stringstream str; + std::stringstream str; str << totalbytes; 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; +void twrpback::streamFileForTWRP(void) { + adblogwrite("streamFileForTwrp" + streamFn + "\n"); +} - tw.adblogwrite("Starting adb backup and restore\n"); - if (mkfifo(TW_ADB_BU_CONTROL, 0666) < 0) { - 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) { - 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::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"); +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); + memset(&md5tr, 0, sizeof(md5tr)); + 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::stringstream str; + str << strerror(errno); + adblogwrite("Cannot write to adb_control_twrp_fd: " + str.str() + "\n"); + close_restore_fds(); + return false; + } + } + else { + adblogwrite("ADB MD5TRAILER crc header doesn't match\n"); + close_restore_fds(); + return false; + } - if (unlink(TW_ADB_BU_CONTROL) < 0) { - stringstream str; - str << strerror(errno); - tw.adblogwrite("Unable to remove TW_ADB_BU_CONTROL: " + str.str()); + 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\n"); + std::stringstream dstr; + dstr << adb_control_twrp_fd; + 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 false; + } + return true; } - unlink(TW_ADB_TWRP_CONTROL); - return ret; + return false; } diff --git a/adbbu/twrpback.hpp b/adbbu/twrpback.hpp index 752d35ea12..c52da3ea7e 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 @@ -45,5 +47,13 @@ class twrpback { 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 }; + +#endif // _TWRPBACK_HPP diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 2cce81f6d6..9cbe3e2a9d 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -13,6 +13,13 @@ # limitations under the License. LOCAL_PATH := $(call my-dir) + +ifdef project-path-for + RECOVERY_PATH := $(call project-path-for,recovery) +else + RECOVERY_PATH := bootable/recovery +endif + include $(CLEAR_VARS) BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY @@ -23,47 +30,191 @@ $(foreach board_define,$(BOARD_RECOVERY_DEFINES), \ ) \ ) -LOCAL_SRC_FILES := applypatch.c bspatch.c freecache.c imgpatch.c utils.c -LOCAL_MODULE := libapplypatch -LOCAL_MODULE_TAGS := eng LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/include \ external/bzip2 \ external/zlib \ $(commands_recovery_local_path) -LOCAL_STATIC_LIBRARIES += libmtdutils libmincrypttwrp libbz libz +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 + +# 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_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + $(commands_recovery_local_path) +LOCAL_STATIC_LIBRARIES := \ + libotafault \ + libbase \ + libcrypto \ + libbspatch \ + libbz \ + libz +LOCAL_WHOLE_STATIC_LIBRARIES += libmtdutils +LOCAL_CFLAGS := \ + -DZLIB_CONST \ + -Werror include $(BUILD_STATIC_LIBRARY) +# libimgpatch (static library) +# =============================== include $(CLEAR_VARS) -LOCAL_SRC_FILES := main.c -LOCAL_MODULE := applypatch -LOCAL_MODULE_TAGS := optional -LOCAL_C_INCLUDES += $(commands_recovery_local_path) -LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz -LOCAL_SHARED_LIBRARIES += libz libcutils libstdc++ libc +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) -include $(BUILD_EXECUTABLE) +# libimgpatch (host static library) +# =============================== +include $(CLEAR_VARS) +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 \ + libbspatch \ + libbase \ + libbz \ + libz +LOCAL_CFLAGS := \ + -DZLIB_CONST \ + -Werror +include $(BUILD_HOST_STATIC_LIBRARY) + +# 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) +# 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_SRC_FILES := main.c -LOCAL_MODULE := applypatch_static -LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_MODULE_TAGS := optional eng -LOCAL_C_INCLUDES += $(commands_recovery_local_path) -LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz -LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc +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) -LOCAL_SRC_FILES := imgdiff.c utils.c bsdiff.c -LOCAL_MODULE := imgdiff -LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_C_INCLUDES += external/zlib external/bzip2 -LOCAL_STATIC_LIBRARIES += libz libbz -LOCAL_MODULE_TAGS := eng +# libimgdiff (host static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + $(libimgdiff_src_files) +LOCAL_MODULE := libimgdiff +LOCAL_CFLAGS := \ + $(libimgdiff_cflags) +LOCAL_STATIC_LIBRARIES := \ + $(libimgdiff_static_libraries) +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +include $(BUILD_HOST_STATIC_LIBRARY) +# imgdiff (host static executable) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := imgdiff_main.cpp +LOCAL_MODULE := imgdiff +LOCAL_CFLAGS := -Werror +LOCAL_STATIC_LIBRARIES := \ + libimgdiff \ + $(libimgdiff_static_libraries) \ + libbz include $(BUILD_HOST_EXECUTABLE) diff --git a/applypatch/Makefile b/applypatch/Makefile new file mode 100644 index 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.c b/applypatch/applypatch.c deleted file mode 100644 index bc45e3c45d..0000000000 --- a/applypatch/applypatch.c +++ /dev/null @@ -1,1082 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mincrypt/sha.h" -#include "applypatch.h" -#include "bmlutils/bmlutils.h" -#include "mtdutils/mtdutils.h" -#include "edify/expr.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_SIZE], - size_t target_size, - const Value* bonus_data); - -static int mtd_partitions_scanned = 0; - -// 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) { - file->data = NULL; - - // 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; - } - - file->size = file->st.st_size; - file->data = malloc(file->size); - - FILE* f = fopen(filename, "rb"); - if (f == NULL) { - printf("failed to open \"%s\": %s\n", filename, strerror(errno)); - free(file->data); - file->data = NULL; - return -1; - } - - ssize_t bytes_read = fread(file->data, 1, file->size, f); - if (bytes_read != file->size) { - printf("short read of \"%s\" (%ld bytes of %ld)\n", - filename, (long)bytes_read, (long)file->size); - free(file->data); - file->data = NULL; - return -1; - } - fclose(f); - - SHA_hash(file->data, file->size, file->sha1); - return 0; -} - -static size_t* size_array; -// comparison function for qsort()ing an int array of indexes into -// size_array[]. -static int compare_size_indices(const void* a, const void* b) { - int aa = *(int*)a; - int bb = *(int*)b; - if (size_array[aa] < size_array[bb]) { - return -1; - } else if (size_array[aa] > size_array[bb]) { - return 1; - } else { - return 0; - } -} - -// Load the contents of an MTD or EMMC partition into the provided -// FileContents. filename should be a string of the form -// "MTD::::::..." (or -// "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. -// -// This complexity is needed because if an OTA installation is -// interrupted, the partition might contain either the source or the -// target data, which might be of different lengths. We need to know -// the length in order to read from a partition (there is no -// "end-of-file" marker), so the caller must specify the possible -// lengths and the hash of the data, and we'll do the load expecting -// to find one of those hashes. -enum PartitionType { MTD, EMMC }; - -static int LoadPartitionContents(const char* filename, FileContents* file) { - char* copy = strdup(filename); - const char* magic = strtok(copy, ":"); - - enum PartitionType type; - - if (strcmp(magic, "MTD") == 0) { - type = MTD; - } else if (strcmp(magic, "EMMC") == 0) { - type = EMMC; - } else if (strcmp(magic, "BML") == 0) { - type = EMMC; - } else { - printf("LoadPartitionContents called with bad filename (%s)\n", - filename); - return -1; - } - const char* partition = strtok(NULL, ":"); - - if (strcmp(magic, "BML") == 0) { - if (strcmp(partition, "boot") == 0) { - partition = BOARD_BML_BOOT; - } else if (strcmp(partition, "recovery") == 0) { - partition = BOARD_BML_RECOVERY; - } - } - - int i; - int colons = 0; - for (i = 0; filename[i] != '\0'; ++i) { - if (filename[i] == ':') { - ++colons; - } - } - if (colons < 3 || colons%2 == 0) { - printf("LoadPartitionContents called with bad filename (%s)\n", - filename); - } - - int pairs = (colons-1)/2; // # of (size,sha1) pairs in filename - int* index = malloc(pairs * sizeof(int)); - size_t* size = malloc(pairs * sizeof(size_t)); - char** sha1sum = malloc(pairs * sizeof(char*)); - - for (i = 0; i < pairs; ++i) { - const char* size_str = strtok(NULL, ":"); - size[i] = strtol(size_str, NULL, 10); - if (size[i] == 0) { - printf("LoadPartitionContents called with bad size (%s)\n", filename); - return -1; - } - sha1sum[i] = strtok(NULL, ":"); - index[i] = i; - } - - // sort the index[] array so it indexes the pairs in order of - // increasing size. - size_array = size; - qsort(index, pairs, sizeof(int), compare_size_indices); - - MtdReadContext* ctx = NULL; - FILE* dev = NULL; - - switch (type) { - case MTD: - if (!mtd_partitions_scanned) { - mtd_scan_partitions(); - mtd_partitions_scanned = 1; - } - - 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 = fopen(partition, "rb"); - if (dev == NULL) { - printf("failed to open emmc partition \"%s\": %s\n", - partition, strerror(errno)); - return -1; - } - } - - SHA_CTX sha_ctx; - SHA_init(&sha_ctx); - uint8_t parsed_sha[SHA_DIGEST_SIZE]; - - // allocate enough memory to hold the largest size. - file->data = malloc(size[index[pairs-1]]); - char* p = (char*)file->data; - file->size = 0; // # bytes read so far - - for (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]] - file->size; - size_t read = 0; - if (next > 0) { - switch (type) { - case MTD: - read = mtd_read_data(ctx, p, next); - break; - - case EMMC: - read = fread(p, 1, next, dev); - break; - } - if (next != read) { - printf("short read (%zu bytes of %zu) for partition \"%s\"\n", - read, next, partition); - free(file->data); - file->data = NULL; - return -1; - } - SHA_update(&sha_ctx, p, read); - file->size += 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)); - const uint8_t* sha_so_far = SHA_final(&temp_ctx); - - if (ParseSha1(sha1sum[index[i]], parsed_sha) != 0) { - printf("failed to parse sha1 %s in %s\n", - sha1sum[index[i]], filename); - free(file->data); - file->data = NULL; - return -1; - } - - if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_SIZE) == 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]]); - break; - } - - p += read; - } - - switch (type) { - case MTD: - mtd_read_close(ctx); - break; - - case EMMC: - fclose(dev); - break; - } - - - if (i == pairs) { - // 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); - free(file->data); - file->data = NULL; - return -1; - } - - const uint8_t* sha_final = SHA_final(&sha_ctx); - for (i = 0; i < SHA_DIGEST_SIZE; ++i) { - file->sha1[i] = sha_final[i]; - } - - // Fake some stat() info. - file->st.st_mode = 0644; - file->st.st_uid = 0; - file->st.st_gid = 0; - - free(copy); - free(index); - free(size); - free(sha1sum); - - 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 = 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; - } - - ssize_t bytes_written = FileSink(file->data, file->size, &fd); - if (bytes_written != file->size) { - printf("short write of \"%s\" (%ld bytes of %ld) (%s)\n", - filename, (long)bytes_written, (long)file->size, - strerror(errno)); - close(fd); - return -1; - } - if (fsync(fd) != 0) { - printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } - if (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; - } - - return 0; -} - -// Write a memory buffer to 'target' partition, a string of the form -// "MTD:[:...]" or "EMMC::". Return 0 on -// success. -int WriteToPartition(unsigned char* data, size_t len, - const char* target) { - char* copy = strdup(target); - const char* magic = strtok(copy, ":"); - - enum PartitionType type; - if (strcmp(magic, "MTD") == 0) { - type = MTD; - } else if (strcmp(magic, "EMMC") == 0) { - type = EMMC; - } else if (strcmp(magic, "BML") == 0) { - type = EMMC; - } else { - printf("WriteToPartition called with bad target (%s)\n", target); - return -1; - } - const char* partition = strtok(NULL, ":"); - - if (strcmp(magic, "BML") == 0) { - if (strcmp(partition, "boot") == 0) { - partition = BOARD_BML_BOOT; - } else if (strcmp(partition, "recovery") == 0) { - partition = BOARD_BML_RECOVERY; - } - - int bmlpartition = open(partition, O_RDWR | O_LARGEFILE); - if (bmlpartition < 0) - return -1; - if (ioctl(bmlpartition, BML_UNLOCK_ALL, 0)) { - printf("failed to unlock BML partition: (%s)\n", partition); - return -1; - } - close(bmlpartition); - } - - if (partition == NULL) { - printf("bad partition target name \"%s\"\n", target); - return -1; - } - - switch (type) { - case MTD: - if (!mtd_partitions_scanned) { - mtd_scan_partitions(); - mtd_partitions_scanned = 1; - } - - const MtdPartition* mtd = mtd_find_partition_by_name(partition); - if (mtd == NULL) { - printf("mtd partition \"%s\" not found for writing\n", - partition); - return -1; - } - - MtdWriteContext* ctx = mtd_write_partition(mtd); - if (ctx == NULL) { - printf("failed to init mtd partition \"%s\" for writing\n", - partition); - return -1; - } - - size_t written = mtd_write_data(ctx, (char*)data, len); - if (written != len) { - printf("only wrote %zu of %zu bytes to MTD %s\n", - written, len, partition); - mtd_write_close(ctx); - return -1; - } - - if (mtd_erase_blocks(ctx, -1) < 0) { - printf("error finishing mtd write of %s\n", partition); - mtd_write_close(ctx); - return -1; - } - - if (mtd_write_close(ctx)) { - printf("error closing mtd write of %s\n", partition); - return -1; - } - break; - - case EMMC: - { - size_t start = 0; - int success = 0; - int fd = open(partition, O_RDWR | O_SYNC); - if (fd < 0) { - printf("failed to open %s: %s\n", partition, strerror(errno)); - return -1; - } - int attempt; - - for (attempt = 0; attempt < 2; ++attempt) { - if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { - printf("failed seek on %s: %s\n", - partition, strerror(errno)); - return -1; - } - while (start < len) { - size_t to_write = len - start; - if (to_write > 1<<20) to_write = 1<<20; - - ssize_t written = TEMP_FAILURE_RETRY(write(fd, data+start, to_write)); - if (written == -1) { - printf("failed write writing to %s: %s\n", partition, strerror(errno)); - return -1; - } - start += written; - } - if (fsync(fd) != 0) { - printf("failed to sync to %s (%s)\n", - partition, strerror(errno)); - return -1; - } - if (close(fd) != 0) { - printf("failed to close %s (%s)\n", - partition, strerror(errno)); - return -1; - } - fd = open(partition, O_RDONLY); - if (fd < 0) { - printf("failed to reopen %s for verify (%s)\n", - partition, strerror(errno)); - return -1; - } - - // drop caches so our subsequent verification read - // won't just be reading the cache. - sync(); - int dc = open("/proc/sys/vm/drop_caches", O_WRONLY); - if (TEMP_FAILURE_RETRY(write(dc, "3\n", 2)) == -1) { - printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); - } else { - printf(" caches dropped\n"); - } - close(dc); - sleep(1); - - // verify - if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { - printf("failed to seek back to beginning of %s: %s\n", - partition, strerror(errno)); - return -1; - } - unsigned char buffer[4096]; - start = len; - size_t p; - for (p = 0; p < len; p += sizeof(buffer)) { - size_t to_read = len - p; - if (to_read > sizeof(buffer)) to_read = sizeof(buffer); - - size_t so_far = 0; - while (so_far < to_read) { - ssize_t read_count = - TEMP_FAILURE_RETRY(read(fd, buffer+so_far, to_read-so_far)); - if (read_count == -1) { - printf("verify read error %s at %zu: %s\n", - partition, p, strerror(errno)); - return -1; - } - if ((size_t)read_count < to_read) { - printf("short verify read %s at %zu: %zd %zu %s\n", - partition, p, read_count, to_read, strerror(errno)); - } - so_far += read_count; - } - - if (memcmp(buffer, data+p, to_read)) { - printf("verification failed starting at %zu\n", p); - start = p; - break; - } - } - - if (start == len) { - printf("verification read succeeded (attempt %d)\n", attempt+1); - success = true; - break; - } - } - - if (!success) { - printf("failed to verify after all attempts\n"); - return -1; - } - - if (close(fd) != 0) { - printf("error closing %s (%s)\n", partition, strerror(errno)); - return -1; - } - sync(); - break; - } - } - - free(copy); - 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 -// error. -int ParseSha1(const char* str, uint8_t* digest) { - int i; - const char* ps = str; - uint8_t* pd = digest; - for (i = 0; i < SHA_DIGEST_SIZE * 2; ++i, ++ps) { - int digit; - if (*ps >= '0' && *ps <= '9') { - digit = *ps - '0'; - } else if (*ps >= 'a' && *ps <= 'f') { - digit = *ps - 'a' + 10; - } else if (*ps >= 'A' && *ps <= 'F') { - digit = *ps - 'A' + 10; - } else { - return -1; - } - if (i % 2 == 0) { - *pd = digit << 4; - } else { - *pd |= digit; - ++pd; - } - } - if (*ps != '\0') return -1; - return 0; -} - -// 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) { - int i; - uint8_t patch_sha1[SHA_DIGEST_SIZE]; - for (i = 0; i < num_patches; ++i) { - if (ParseSha1(patch_sha1_str[i], patch_sha1) == 0 && - memcmp(patch_sha1, sha1, SHA_DIGEST_SIZE) == 0) { - return i; - } - } - 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; - file.data = NULL; - - // 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); - - free(file.data); - file.data = NULL; - - // 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); - free(file.data); - return 1; - } - } - - free(file.data); - return 0; -} - -int ShowLicenses() { - ShowBSDiffLicense(); - return 0; -} - -ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) { - int fd = *(int *)token; - ssize_t done = 0; - ssize_t wrote; - while (done < (ssize_t) len) { - wrote = TEMP_FAILURE_RETRY(write(fd, data+done, len-done)); - if (wrote == -1) { - printf("error writing %d bytes: %s\n", (int)(len-done), strerror(errno)); - return done; - } - done += wrote; - } - return done; -} - -typedef struct { - unsigned char* buffer; - ssize_t size; - ssize_t pos; -} MemorySinkInfo; - -ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { - MemorySinkInfo* msi = (MemorySinkInfo*)token; - if (msi->size - msi->pos < len) { - return -1; - } - memcpy(msi->buffer + msi->pos, data, len); - msi->pos += len; - return len; -} - -// Return the amount of free space (in bytes) on the filesystem -// containing filename. filename must exist. Return -1 on error. -size_t FreeSpaceForFile(const char* filename) { - struct statfs sf; - if (statfs(filename, &sf) != 0) { - printf("failed to statfs %s: %s\n", filename, strerror(errno)); - return -1; - } - return sf.f_bsize * sf.f_bavail; -} - -int CacheSizeCheck(size_t bytes) { - if (MakeFreeSpaceOnCache(bytes) < 0) { - printf("unable to make %ld bytes available on /cache\n", (long)bytes); - return 1; - } else { - return 0; - } -} - -static void print_short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]) { - int i; - const char* hex = "0123456789abcdef"; - for (i = 0; i < 4; ++i) { - putchar(hex[(sha1[i]>>4) & 0xf]); - putchar(hex[sha1[i] & 0xf]); - } -} - -// 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). -// -// - if the sha1 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 daat). 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, 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 LoadPartition Contents() 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_SIZE]; - 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; - copy_file.data = NULL; - source_file.data = NULL; - 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_SIZE) == 0) { - // The early-exit case: the patch was already applied, this file - // has the desired hash, nothing for us to do. - printf("already "); - print_short_sha1(target_sha1); - putchar('\n'); - free(source_file.data); - return 0; - } - } - - if (source_file.data == NULL || - (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. - free(source_file.data); - source_file.data = NULL; - LoadFileContents(source_filename, &source_file); - } - - if (source_file.data != NULL) { - 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) { - free(source_file.data); - source_file.data = NULL; - 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"); - free(copy_file.data); - return 1; - } - } - - int result = GenerateTarget(&source_file, source_patch_value, - ©_file, copy_patch_value, - source_filename, target_filename, - target_sha1, target_size, bonus_data); - free(source_file.data); - free(copy_file.data); - - return result; -} - -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_SIZE], - size_t target_size, - const Value* bonus_data) { - int retry = 1; - SHA_CTX ctx; - int output; - MemorySinkInfo msi; - FileContents* source_to_use; - char* outname; - int made_copy = 0; - - // 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(). - char target_fs[strlen(target_filename)+1]; - char* slash = strchr(target_filename+1, '/'); - if (slash != NULL) { - int count = slash - target_filename; - strncpy(target_fs, target_filename, count); - target_fs[count] = '\0'; - } else { - strcpy(target_fs, target_filename); - } - - do { - // Is there enough room in the target filesystem to hold the patched - // file? - - if (strncmp(target_filename, "MTD:", 4) == 0 || - strncmp(target_filename, "EMMC:", 5) == 0 || - strncmp(target_filename, "BML:", 4) == 0) { - // 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->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); - 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 %ld bytes; free space %ld bytes; retry %d; enough %d\n", - (long)target_size, (long)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->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); - printf("(now %ld bytes free for target) ", (long)free_space); - } - } - - 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; - } - - SinkFn sink = NULL; - void* token = NULL; - output = -1; - outname = NULL; - if (strncmp(target_filename, "MTD:", 4) == 0 || - strncmp(target_filename, "EMMC:", 5) == 0 || - strncmp(target_filename, "BML:", 4) == 0) { - // We store the decoded output in memory. - msi.buffer = malloc(target_size); - if (msi.buffer == NULL) { - printf("failed to alloc %ld bytes for output\n", - (long)target_size); - return 1; - } - msi.pos = 0; - msi.size = target_size; - sink = MemorySink; - token = &msi; - } else { - // We write the decoded output to ".patch". - outname = (char*)malloc(strlen(target_filename) + 10); - strcpy(outname, target_filename); - strcat(outname, ".patch"); - - output = open(outname, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, - S_IRUSR | S_IWUSR); - if (output < 0) { - printf("failed to open output file %s: %s\n", - outname, strerror(errno)); - return 1; - } - sink = FileSink; - token = &output; - } - - char* header = patch->data; - ssize_t header_bytes_read = patch->size; - - SHA_init(&ctx); - - int result; - - if (header_bytes_read >= 8 && - memcmp(header, "BSDIFF40", 8) == 0) { - result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size, - patch, 0, sink, token, &ctx); - } else if (header_bytes_read >= 8 && - memcmp(header, "IMGDIFF2", 8) == 0) { - result = ApplyImagePatch(source_to_use->data, source_to_use->size, - patch, sink, token, &ctx, bonus_data); - } else { - printf("Unknown patch file format\n"); - return 1; - } - - if (output >= 0) { - if (fsync(output) != 0) { - printf("failed to fsync file \"%s\" (%s)\n", outname, strerror(errno)); - result = 1; - } - if (close(output) != 0) { - printf("failed to close file \"%s\" (%s)\n", outname, 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 (outname != NULL) { - unlink(outname); - } - } else { - // succeeded; no need to retry - break; - } - } while (retry-- > 0); - - const uint8_t* current_target_sha1 = SHA_final(&ctx); - if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE) != 0) { - printf("patch did not produce expected sha1\n"); - return 1; - } else { - printf("now "); - print_short_sha1(target_sha1); - putchar('\n'); - } - - if (output < 0) { - // Copy the temp file to the partition. - if (WriteToPartition(msi.buffer, msi.pos, target_filename) != 0) { - printf("write of patched data to %s failed\n", target_filename); - return 1; - } - free(msi.buffer); - } else { - // Give the .patch file the same owner, group, and mode of the - // original source file. - if (chmod(outname, source_to_use->st.st_mode) != 0) { - printf("chmod of \"%s\" failed: %s\n", outname, strerror(errno)); - return 1; - } - if (chown(outname, source_to_use->st.st_uid, - source_to_use->st.st_gid) != 0) { - printf("chown of \"%s\" failed: %s\n", outname, strerror(errno)); - return 1; - } - - // Finally, rename the .patch file to replace the target file. - if (rename(outname, 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; -} diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp new file mode 100644 index 0000000000..43e9b80dca --- /dev/null +++ b/applypatch/applypatch.cpp @@ -0,0 +1,785 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "applypatch/applypatch.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "bmlutils/bmlutils.h" +#include "mtdutils/mtdutils.h" + +#include "edify/expr.h" +#include "ota_io.h" +#include "print_sha1.h" + +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. +// 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) == -1) { + printf("failed to stat \"%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.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 EMMC partition into the provided +// FileContents. filename should be a string of the form +// "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. +// +// This complexity is needed because if an OTA installation is +// interrupted, the partition might contain either the source or the +// target data, which might be of different lengths. We need to know +// the length in order to read from a partition (there is no +// "end-of-file" marker), so the caller must specify the possible +// lengths and the hash of the data, and we'll do the load expecting +// to find one of those hashes. +enum PartitionType { MTD, EMMC }; + +static int LoadPartitionContents(const 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; + } + + if (pieces[0] == "BML") { + if (strcmp(partition, "boot") == 0) { + partition = BOARD_BML_BOOT; + } else if (strcmp(partition, "recovery") == 0) { + partition = BOARD_BML_RECOVERY; + } + } + + // 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); + + uint8_t parsed_sha[SHA_DIGEST_LENGTH]; + if (ParseSha1(current_sha1.c_str(), parsed_sha) != 0) { + printf("failed to parse SHA-1 %s in %s\n", current_sha1.c_str(), filename.c_str()); + return -1; + } + + 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; + } + + SHA1_Final(file->sha1, &sha_ctx); + + 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; +} + +// Save the contents of the given FileContents object under the given +// filename. Return 0 on success. +int SaveFileContents(const char* filename, const FileContents* file) { + unique_fd fd(ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR)); + if (fd == -1) { + printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); + return -1; + } + + 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; + } + + return 0; +} + +// Write a memory buffer to 'target' partition, a string of the form +// "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 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.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("WriteToPartition called with bad target (%s)\n", target.c_str()); + return -1; + } + + 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; + } + + int bmlpartition = open(partition, O_RDWR | O_LARGEFILE); + if (bmlpartition < 0) + return -1; + if (ioctl(bmlpartition, BML_UNLOCK_ALL, 0)) { + printf("failed to unlock BML partition: (%s)\n", partition); + return -1; + } + close(bmlpartition); + } + + if (partition == NULL) { + printf("bad partition target name \"%s\"\n", target.c_str()); + return -1; + } + + switch (type) { + case MTD: { + if (!mtd_partitions_scanned) { + mtd_scan_partitions(); + mtd_partitions_scanned = true; + } + + const MtdPartition* mtd = mtd_find_partition_by_name(partition); + if (mtd == NULL) { + printf("mtd partition \"%s\" not found for writing\n", partition); + return -1; + } + + MtdWriteContext* ctx = mtd_write_partition(mtd); + if (ctx == NULL) { + printf("failed to init mtd partition \"%s\" for writing\n", partition); + return -1; + } + + size_t written = mtd_write_data(ctx, reinterpret_cast(data), len); + if (written != len) { + printf("only wrote %zu of %zu bytes to MTD %s\n", written, len, partition); + mtd_write_close(ctx); + return -1; + } + + if (mtd_erase_blocks(ctx, -1) < 0) { + printf("error finishing mtd write of %s\n", partition); + mtd_write_close(ctx); + return -1; + } + + if (mtd_write_close(ctx)) { + printf("error closing mtd write of %s\n", partition); + return -1; + } + break; + } + + case EMMC: { + size_t start = 0; + bool success = false; + 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; + } + + for (size_t attempt = 0; attempt < 2; ++attempt) { + if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { + printf("failed seek on %s: %s\n", partition, strerror(errno)); + return -1; + } + while (start < len) { + size_t to_write = len - start; + if (to_write > 1<<20) to_write = 1<<20; + + ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data+start, to_write)); + if (written == -1) { + printf("failed write writing to %s: %s\n", partition, strerror(errno)); + return -1; + } + start += written; + } + if (ota_fsync(fd) != 0) { + printf("failed to sync to %s (%s)\n", partition, strerror(errno)); + return -1; + } + if (ota_close(fd) != 0) { + printf("failed to close %s (%s)\n", partition, strerror(errno)); + return -1; + } + 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; + } + + // Drop caches so our subsequent verification read + // won't just be reading the cache. + sync(); + unique_fd dc(ota_open("/proc/sys/vm/drop_caches", O_WRONLY)); + if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) { + printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); + } else { + printf(" caches dropped\n"); + } + ota_close(dc); + sleep(1); + + // verify + if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { + printf("failed to seek back to beginning of %s: %s\n", + partition, strerror(errno)); + return -1; + } + unsigned char buffer[4096]; + start = len; + for (size_t p = 0; p < len; p += sizeof(buffer)) { + size_t to_read = len - p; + if (to_read > sizeof(buffer)) { + to_read = sizeof(buffer); + } + + size_t so_far = 0; + while (so_far < to_read) { + ssize_t read_count = + TEMP_FAILURE_RETRY(ota_read(fd, buffer+so_far, to_read-so_far)); + if (read_count == -1) { + printf("verify read error %s at %zu: %s\n", + partition, p, strerror(errno)); + return -1; + } + if (static_cast(read_count) < to_read) { + printf("short verify read %s at %zu: %zd %zu %s\n", + partition, p, read_count, to_read, strerror(errno)); + } + so_far += read_count; + } + + if (memcmp(buffer, data+p, to_read) != 0) { + printf("verification failed starting at %zu\n", p); + start = p; + break; + } + } + + if (start == len) { + printf("verification read succeeded (attempt %zu)\n", attempt+1); + success = true; + break; + } + } + + if (!success) { + printf("failed to verify after all attempts\n"); + return -1; + } + + if (ota_close(fd) != 0) { + printf("error closing %s (%s)\n", partition, strerror(errno)); + return -1; + } + sync(); + break; + } + } + + 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 +// error. +int ParseSha1(const char* str, uint8_t* digest) { + const char* ps = str; + uint8_t* pd = digest; + for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) { + int digit; + if (*ps >= '0' && *ps <= '9') { + digit = *ps - '0'; + } else if (*ps >= 'a' && *ps <= 'f') { + digit = *ps - 'a' + 10; + } else if (*ps >= 'A' && *ps <= 'F') { + digit = *ps - 'A' + 10; + } else { + return -1; + } + if (i % 2 == 0) { + *pd = digit << 4; + } else { + *pd |= digit; + ++pd; + } + } + if (*ps != '\0') return -1; + return 0; +} + +// 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. +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]; + if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 && + memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) { + return i; + } + } + 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, 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() { + ShowBSDiffLicense(); + return 0; +} + +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 +// containing filename. filename must exist. Return -1 on error. +size_t FreeSpaceForFile(const char* filename) { + struct statfs sf; + if (statfs(filename, &sf) != 0) { + printf("failed to statfs %s: %s\n", filename, strerror(errno)); + return -1; + } + return sf.f_bsize * sf.f_bavail; +} + +int CacheSizeCheck(size_t bytes) { + if (MakeFreeSpaceOnCache(bytes) < 0) { + printf("unable to make %zu bytes available on /cache\n", bytes); + return 1; + } else { + return 0; + } +} + +// 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 SHA-1 hash of is , does nothing and exits +// successfully. +// +// - 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. +// +// 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); +} + +/* + * This function flashes a given image to the target partition. It verifies + * the target cheksum first, and will return if target has the desired hash. + * It checks the checksum of the given source image before flashing, and + * verifies the target partition afterwards. The function is idempotent. + * Returns zero on success. + */ +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; + } + + 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(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/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp new file mode 100644 index 0000000000..aa32d57ef4 --- /dev/null +++ b/applypatch/applypatch_modes.cpp @@ -0,0 +1,188 @@ +/* + * 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 "applypatch_modes.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "applypatch/applypatch.h" +#include "edify/expr.h" + +static int CheckMode(int argc, const char** argv) { + if (argc < 3) { + return 2; + } + std::vector sha1; + for (int i = 3; i < argc; i++) { + sha1.push_back(argv[i]); + } + + 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, const char** argv, std::vector* sha1s, + std::vector* files) { + if (sha1s == nullptr) { + return false; + } + for (int i = 0; i < argc; ++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; + } + + 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(pieces[0]); + FileContents fc; + if (LoadFileContents(pieces[1].c_str(), &fc) != 0) { + return false; + } + files->push_back(std::move(fc)); + } + return true; +} + +static int FlashMode(const char* src_filename, const char* tgt_filename, + const char* tgt_sha1, size_t tgt_size) { + return applypatch_flash(src_filename, tgt_filename, tgt_sha1, tgt_size); +} + +static int PatchMode(int argc, const char** argv) { + FileContents bonusFc; + 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.type = VAL_BLOB; + bonus.data = std::string(bonusFc.data.cbegin(), bonusFc.data.cend()); + argc -= 2; + argv += 2; + } + + if (argc < 4) { + return 2; + } + + 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.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 files; + if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) { + printf("failed to parse patch args\n"); + return 1; + } + + std::vector> patches; + for (size_t i = 0; i < files.size(); ++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, sha1s, patches, &bonus); +} + +// 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). +// +// - if the sha1 hash of is , does nothing and exits +// successfully. +// +// - otherwise, if no : is provided, flashes with +// . must be a partition name, while must +// be a regular image file. will not be deleted on success. +// +// - otherwise, if the sha1 hash of is , applies the +// bsdiff to to produce a new file (the type of patch +// is automatically detected from the file header). 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 src-file". +// +// - otherwise, or if any error is encountered, exits with non-zero +// status. +// +// (or in check mode) may refer to an EMMC partition +// to read the source data. See the comments for the +// LoadPartitionContents() function for the format of such a filename. + +int applypatch_modes(int argc, const char** argv) { + if (argc < 2) { + usage: + printf( + "usage: %s [-b ] " + "[: ...]\n" + " or %s -c [ ...]\n" + " or %s -l\n" + "\n" + "Filenames may be of the form\n" + " EMMC::::::...\n" + "to specify reading from or writing to an EMMC partition.\n\n", + argv[0], argv[0], argv[0]); + return 2; + } + + int result; + + if (strncmp(argv[1], "-l", 3) == 0) { + result = ShowLicenses(); + } else if (strncmp(argv[1], "-c", 3) == 0) { + result = CheckMode(argc, argv); + } else { + result = PatchMode(argc, argv); + } + + if (result == 2) { + goto usage; + } + return result; +} 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.c b/applypatch/bsdiff.c deleted file mode 100644 index b6d342b7a8..0000000000 --- a/applypatch/bsdiff.c +++ /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],new,MIN(oldsize-I[x],newsize))<0) { - return search(I,old,oldsize,new,newsize,x,en,pos); - } else { - return search(I,old,oldsize,new,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, new, newsize are arguments; we don't load this -// data from files. old and new 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* new, 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 = malloc((oldsize+1) * sizeof(off_t)); - V = malloc((oldsize+1) * sizeof(off_t)); - qsufsort(*IP, V, old, oldsize); - free(V); - } - I = *IP; - - if(((db=malloc(newsize+1))==NULL) || - ((eb=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]==new[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 "mincrypt/sha.h" -#include "applypatch.h" - -void ShowBSDiffLicense() { - puts("The bsdiff library used herein is:\n" - "\n" - "Copyright 2003-2005 Colin Percival\n" - "All rights reserved\n" - "\n" - "Redistribution and use in source and binary forms, with or without\n" - "modification, are permitted providing that the following conditions\n" - "are met:\n" - "1. Redistributions of source code must retain the above copyright\n" - " notice, this list of conditions and the following disclaimer.\n" - "2. Redistributions in binary form must reproduce the above copyright\n" - " notice, this list of conditions and the following disclaimer in the\n" - " documentation and/or other materials provided with the distribution.\n" - "\n" - "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" - "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n" - "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n" - "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n" - "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n" - "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n" - "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n" - "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n" - "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n" - "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n" - "POSSIBILITY OF SUCH DAMAGE.\n" - "\n------------------\n\n" - "This program uses Julian R Seward's \"libbzip2\" library, available\n" - "from http://www.bzip.org/.\n" - ); -} - -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) { - - unsigned char* new_data; - ssize_t new_size; - if (ApplyBSDiffPatchMem(old_data, old_size, patch, patch_offset, - &new_data, &new_size) != 0) { - return -1; - } - - if (sink(new_data, new_size, token) < new_size) { - printf("short write of output: %d (%s)\n", errno, strerror(errno)); - return 1; - } - if (ctx) SHA_update(ctx, new_data, new_size); - free(new_data); - - return 0; -} - -int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - unsigned char** new_data, ssize_t* new_size) { - // Patch data format: - // 0 8 "BSDIFF40" - // 8 8 X - // 16 8 Y - // 24 8 sizeof(newfile) - // 32 X bzip2(control block) - // 32+X Y bzip2(diff block) - // 32+X+Y ??? bzip2(extra block) - // with control block a set of triples (x,y,z) meaning "add x bytes - // from oldfile to x bytes from the diff block; copy y bytes from the - // extra block; seek forwards in oldfile by z bytes". - - unsigned char* header = (unsigned char*) patch->data + patch_offset; - if (memcmp(header, "BSDIFF40", 8) != 0) { - printf("corrupt bsdiff patch file header (magic number)\n"); - return 1; - } - - ssize_t ctrl_len, data_len; - 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 = malloc(*new_size); - if (*new_data == NULL) { - printf("failed to allocate %ld bytes of memory for output file\n", - (long)*new_size); - return 1; - } - - 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 + 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 + 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; -} diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp new file mode 100644 index 0000000000..65ee614ef2 --- /dev/null +++ b/applypatch/bspatch.cpp @@ -0,0 +1,94 @@ +/* + * 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. + */ + +// This file is a nearly line-for-line copy of bspatch.c from the +// bsdiff-4.3 distribution; the primary differences being how the +// input and output data are read and the error handling. Running +// applypatch with the -l option will display the bsdiff license +// notice. + +#include +#include + +#include + +#include +#include +#include + +#include "applypatch/applypatch.h" +#include "print_sha1.h" + +void ShowBSDiffLicense() { + puts("The bsdiff library used herein is:\n" + "\n" + "Copyright 2003-2005 Colin Percival\n" + "All rights reserved\n" + "\n" + "Redistribution and use in source and binary forms, with or without\n" + "modification, are permitted providing that the following conditions\n" + "are met:\n" + "1. Redistributions of source code must retain the above copyright\n" + " notice, this list of conditions and the following disclaimer.\n" + "2. Redistributions in binary form must reproduce the above copyright\n" + " notice, this list of conditions and the following disclaimer in the\n" + " documentation and/or other materials provided with the distribution.\n" + "\n" + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n" + "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n" + "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n" + "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n" + "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n" + "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n" + "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n" + "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n" + "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n" + "POSSIBILITY OF SUCH DAMAGE.\n" + "\n------------------\n\n" + "This program uses Julian R Seward's \"libbzip2\" library, available\n" + "from http://www.bzip.org/.\n" + ); +} + +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; + } + } + return result; +} \ No newline at end of file diff --git a/applypatch/freecache.c b/applypatch/freecache.c deleted file mode 100644 index 9827fda06a..0000000000 --- a/applypatch/freecache.c +++ /dev/null @@ -1,172 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "applypatch.h" - -static int EliminateOpenFiles(char** files, int file_count) { - DIR* d; - struct dirent* de; - d = opendir("/proc"); - if (d == NULL) { - printf("error opening /proc: %s\n", strerror(errno)); - return -1; - } - while ((de = readdir(d)) != 0) { - int i; - for (i = 0; de->d_name[i] != '\0' && isdigit(de->d_name[i]); ++i); - if (de->d_name[i]) continue; - - // de->d_name[i] is numeric - - char path[FILENAME_MAX]; - strcpy(path, "/proc/"); - strcat(path, de->d_name); - strcat(path, "/fd/"); - - DIR* fdd; - struct dirent* fdde; - fdd = opendir(path); - if (fdd == NULL) { - printf("error opening %s: %s\n", path, strerror(errno)); - continue; - } - while ((fdde = readdir(fdd)) != 0) { - char fd_path[FILENAME_MAX]; - char link[FILENAME_MAX]; - strcpy(fd_path, path); - strcat(fd_path, fdde->d_name); - - int count; - count = readlink(fd_path, link, sizeof(link)-1); - if (count >= 0) { - link[count] = '\0'; - - // This is inefficient, but it should only matter if there are - // lots of files in /cache, and lots of them are open (neither - // of which should be true, especially in recovery). - if (strncmp(link, "/cache/", 7) == 0) { - int j; - for (j = 0; j < file_count; ++j) { - if (files[j] && strcmp(files[j], link) == 0) { - printf("%s is open by %s\n", link, de->d_name); - free(files[j]); - files[j] = NULL; - } - } - } - } - } - closedir(fdd); - } - closedir(d); - - return 0; -} - -int FindExpendableFiles(char*** names, int* entries) { - DIR* d; - struct dirent* de; - int size = 32; - *entries = 0; - *names = malloc(size * sizeof(char*)); - - char path[FILENAME_MAX]; - - // We're allowed to delete unopened regular files in any of these - // directories. - const char* dirs[2] = {"/cache", "/cache/recovery/otatest"}; - - unsigned int i; - for (i = 0; i < sizeof(dirs)/sizeof(dirs[0]); ++i) { - d = opendir(dirs[i]); - if (d == NULL) { - printf("error opening %s: %s\n", dirs[i], strerror(errno)); - continue; - } - - // Look for regular files in the directory (not in any subdirectories). - while ((de = readdir(d)) != 0) { - strcpy(path, dirs[i]); - strcat(path, "/"); - strcat(path, de->d_name); - - // We can't delete CACHE_TEMP_SOURCE; if it's there we might have - // restarted during installation and could be depending on it to - // be there. - if (strcmp(path, CACHE_TEMP_SOURCE) == 0) continue; - - struct stat st; - if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) { - if (*entries >= size) { - size *= 2; - *names = realloc(*names, size * sizeof(char*)); - } - (*names)[(*entries)++] = strdup(path); - } - } - - closedir(d); - } - - printf("%d regular files in deletable directories\n", *entries); - - if (EliminateOpenFiles(*names, *entries) < 0) { - return -1; - } - - return 0; -} - -int MakeFreeSpaceOnCache(size_t bytes_needed) { - size_t free_now = FreeSpaceForFile("/cache"); - printf("%ld bytes free on /cache (%ld needed)\n", - (long)free_now, (long)bytes_needed); - - if (free_now >= bytes_needed) { - return 0; - } - - char** names; - int entries; - - if (FindExpendableFiles(&names, &entries) < 0) { - return -1; - } - - if (entries == 0) { - // nothing we can delete to free up space! - printf("no files can be deleted to free space on /cache\n"); - return -1; - } - - // We could try to be smarter about which files to delete: the - // biggest ones? the smallest ones that will free up enough space? - // the oldest? the newest? - // - // Instead, we'll be dumb. - - int i; - for (i = 0; i < entries && free_now < bytes_needed; ++i) { - if (names[i]) { - unlink(names[i]); - free_now = FreeSpaceForFile("/cache"); - printf("deleted %s; now %ld bytes free\n", names[i], (long)free_now); - free(names[i]); - } - } - - for (; i < entries; ++i) { - free(names[i]); - } - free(names); - - return (free_now >= bytes_needed) ? 0 : -1; -} diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp new file mode 100644 index 0000000000..331cae265f --- /dev/null +++ b/applypatch/freecache.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2010 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 + +#include +#include + +#include "applypatch/applypatch.h" + +static int EliminateOpenFiles(std::set* files) { + std::unique_ptr d(opendir("/proc"), closedir); + if (!d) { + printf("error opening /proc: %s\n", strerror(errno)); + return -1; + } + struct dirent* de; + while ((de = readdir(d.get())) != 0) { + unsigned int pid; + if (!android::base::ParseUint(de->d_name, &pid)) { + continue; + } + std::string path = android::base::StringPrintf("/proc/%s/fd/", de->d_name); + + struct dirent* fdde; + std::unique_ptr fdd(opendir(path.c_str()), closedir); + if (!fdd) { + printf("error opening %s: %s\n", path.c_str(), strerror(errno)); + continue; + } + while ((fdde = readdir(fdd.get())) != 0) { + std::string fd_path = path + fdde->d_name; + char link[FILENAME_MAX]; + + int count = readlink(fd_path.c_str(), link, sizeof(link)-1); + if (count >= 0) { + link[count] = '\0'; + if (strncmp(link, "/cache/", 7) == 0) { + if (files->erase(link) > 0) { + printf("%s is open by %s\n", link, de->d_name); + } + } + } + } + } + return 0; +} + +static std::set FindExpendableFiles() { + std::set files; + // We're allowed to delete unopened regular files in any of these + // directories. + const char* dirs[2] = {"/cache", "/cache/recovery/otatest"}; + + for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); ++i) { + std::unique_ptr d(opendir(dirs[i]), closedir); + if (!d) { + printf("error opening %s: %s\n", dirs[i], strerror(errno)); + continue; + } + + // Look for regular files in the directory (not in any subdirectories). + struct dirent* de; + while ((de = readdir(d.get())) != 0) { + std::string path = std::string(dirs[i]) + "/" + de->d_name; + + // We can't delete CACHE_TEMP_SOURCE; if it's there we might have + // restarted during installation and could be depending on it to + // be there. + if (path == CACHE_TEMP_SOURCE) { + continue; + } + + struct stat st; + if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { + files.insert(path); + } + } + } + + printf("%zu regular files in deletable directories\n", files.size()); + if (EliminateOpenFiles(&files) < 0) { + return std::set(); + } + return files; +} + +int MakeFreeSpaceOnCache(size_t bytes_needed) { + size_t free_now = FreeSpaceForFile("/cache"); + printf("%zu bytes free on /cache (%zu needed)\n", free_now, bytes_needed); + + if (free_now >= bytes_needed) { + return 0; + } + std::set files = FindExpendableFiles(); + if (files.empty()) { + // nothing we can delete to free up space! + printf("no files can be deleted to free space on /cache\n"); + return -1; + } + + // We could try to be smarter about which files to delete: the + // biggest ones? the smallest ones that will free up enough space? + // the oldest? the newest? + // + // Instead, we'll be dumb. + + for (const auto& file : files) { + unlink(file.c_str()); + free_now = FreeSpaceForFile("/cache"); + printf("deleted %s; now %zu bytes free\n", file.c_str(), free_now); + if (free_now < bytes_needed) { + break; + } + } + return (free_now >= bytes_needed) ? 0 : -1; +} diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c deleted file mode 100644 index 3bac8be916..0000000000 --- a/applypatch/imgdiff.c +++ /dev/null @@ -1,1060 +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. - */ - -/* - * This program constructs binary patches for images -- such as boot.img - * and recovery.img -- that consist primarily of large chunks of gzipped - * data interspersed with uncompressed data. Doing a naive bsdiff of - * these files is not useful because small changes in the data lead to - * large changes in the compressed bitstream; bsdiff patches of gzipped - * data are typically as large as the data itself. - * - * To patch these usefully, we break the source and target images up into - * chunks of two types: "normal" and "gzip". Normal chunks are simply - * patched using a plain bsdiff. Gzip chunks are first expanded, then a - * bsdiff is applied to the uncompressed data, then the patched data is - * gzipped using the same encoder parameters. Patched chunks are - * concatenated together to create the output file; the output image - * should be *exactly* the same series of bytes as the target image used - * originally to generate the patch. - * - * To work well with this tool, the gzipped sections of the target - * image must have been generated using the same deflate encoder that - * is available in applypatch, namely, the one in the zlib library. - * In practice this means that images should be compressed using the - * "minigzip" tool included in the zlib distribution, not the GNU gzip - * program. - * - * An "imgdiff" patch consists of a header describing the chunk structure - * of the file and any encoding parameters needed for the gzipped - * chunks, followed by N bsdiff patches, one per chunk. - * - * For a diff to be generated, the source and target images must have the - * same "chunk" structure: that is, the same number of gzipped and normal - * chunks in the same order. Android boot and recovery images currently - * consist of five chunks: a small normal header, a gzipped kernel, a - * small normal section, a gzipped ramdisk, and finally a small normal - * footer. - * - * Caveats: we locate gzipped sections within the source and target - * images by searching for the byte sequence 1f8b0800: 1f8b is the gzip - * magic number; 08 specifies the "deflate" encoding [the only encoding - * supported by the gzip standard]; and 00 is the flags byte. We do not - * currently support any extra header fields (which would be indicated by - * a nonzero flags byte). We also don't handle the case when that byte - * sequence appears spuriously in the file. (Note that it would have to - * occur spuriously within a normal chunk to be a problem.) - * - * - * The imgdiff patch header looks like this: - * - * "IMGDIFF1" (8) [magic number and version] - * chunk count (4) - * for each chunk: - * chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}] - * if chunk type == CHUNK_NORMAL: - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * if chunk type == CHUNK_GZIP: (version 1 only) - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * source expanded len (8) [size of uncompressed source] - * target expected len (8) [size of uncompressed target] - * gzip level (4) - * method (4) - * windowBits (4) - * memLevel (4) - * strategy (4) - * gzip header len (4) - * gzip header (gzip header len) - * gzip footer (8) - * if chunk type == CHUNK_DEFLATE: (version 2 only) - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * source expanded len (8) [size of uncompressed source] - * target expected len (8) [size of uncompressed target] - * gzip level (4) - * method (4) - * windowBits (4) - * memLevel (4) - * strategy (4) - * if chunk type == RAW: (version 2 only) - * target len (4) - * data (target len) - * - * All integers are little-endian. "source start" and "source len" - * specify the section of the input image that comprises this chunk, - * including the gzip header and footer for gzip chunks. "source - * expanded len" is the size of the uncompressed source data. "target - * expected len" is the size of the uncompressed data after applying - * the bsdiff patch. The next five parameters specify the zlib - * parameters to be used when compressing the patched data, and the - * next three specify the header and footer to be wrapped around the - * compressed data to create the output chunk (so that header contents - * like the timestamp are recreated exactly). - * - * After the header there are 'chunk count' bsdiff patches; the offset - * of each from the beginning of the file is specified in the header. - * - * This tool can take an optional file of "bonus data". This is an - * extra file of data that is appended to chunk #1 after it is - * compressed (it must be a CHUNK_DEFLATE chunk). The same file must - * be available (and passed to applypatch with -b) when applying the - * patch. This is used to reduce the size of recovery-from-boot - * patches by combining the boot image with recovery ramdisk - * information that is stored on the system partition. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "zlib.h" -#include "imgdiff.h" -#include "utils.h" - -typedef struct { - int type; // CHUNK_NORMAL, CHUNK_DEFLATE - size_t start; // offset of chunk in original image file - - size_t len; - unsigned char* data; // data to be patched (uncompressed, for deflate chunks) - - size_t source_start; - size_t source_len; - - off_t* I; // used by bsdiff - - // --- for CHUNK_DEFLATE chunks only: --- - - // original (compressed) deflate data - size_t deflate_len; - unsigned char* deflate_data; - - char* filename; // 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; - } -} - -// from bsdiff.c -int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* new, off_t newsize, - const char* patch_filename); - -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; - } - - unsigned char* img = malloc(st.st_size); - FILE* f = fopen(filename, "rb"); - if (fread(img, 1, st.st_size, f) != st.st_size) { - printf("failed to read \"%s\" %s\n", filename, strerror(errno)); - fclose(f); - return NULL; - } - fclose(f); - - // look for the end-of-central-directory record. - - 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; - } - - int cdcount = Read2(img+i+8); - int cdoffset = Read4(img+i+16); - - ZipFileEntry* temp_entries = malloc(cdcount * sizeof(ZipFileEntry)); - int entrycount = 0; - - 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; - } - - 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 - - char* filename = malloc(nlen+1); - memcpy(filename, cd+46, nlen); - filename[nlen] = '\0'; - - int method = Read2(cd+10); - - cd += 46 + nlen + xlen + mlen; - - if (method != 8) { // 8 == deflate - free(filename); - continue; - } - - unsigned char* lh = img + hoffset; - - if (!(lh[0] == 0x50 && lh[1] == 0x4b && lh[2] == 0x03 && lh[3] == 0x04)) { - printf("bad local file header entry %d\n", i); - return NULL; - } - - if (Read2(lh+26) != nlen || memcmp(lh+30, filename, nlen) != 0) { - printf("central dir filename doesn't match local header\n"); - return NULL; - } - - xlen = Read2(lh+28); // extra field len; might be different from CD entry? - - 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; - } - - 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); - } -#endif - - *num_chunks = 0; - *chunks = malloc((entrycount*2+2) * sizeof(ImageChunk)); - ImageChunk* curr = *chunks; - - 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; - } - - int pos = 0; - int nextentry = 0; - - 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; - - curr->len = temp_entries[nextentry].uncomp_len; - curr->data = malloc(curr->len); - - 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; - - // -15 means we are decoding a 'raw' deflate stream; zlib will - // not expect zlib headers. - int ret = inflateInit2(&strm, -15); - - 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; - } - - inflateEnd(&strm); - - pos += curr->deflate_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; - } else { - curr->len = st.st_size - pos; - } - curr->data = img + pos; - curr->filename = NULL; - curr->I = NULL; - pos += curr->len; - - ++*num_chunks; - ++curr; - } - - free(temp_entries); - return img; -} - -/* - * 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) { - struct stat st; - if (stat(filename, &st) != 0) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return NULL; - } - - unsigned char* img = malloc(st.st_size + 4); - FILE* f = fopen(filename, "rb"); - if (fread(img, 1, st.st_size, f) != st.st_size) { - printf("failed to read \"%s\" %s\n", filename, strerror(errno)); - fclose(f); - return NULL; - } - 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+st.st_size, 0, 4); - - size_t pos = 0; - - *num_chunks = 0; - *chunks = NULL; - - while (pos < st.st_size) { - unsigned char* p = img+pos; - - if (st.st_size - pos >= 4 && - p[0] == 0x1f && p[1] == 0x8b && - p[2] == 0x08 && // deflate compression - p[3] == 0x00) { // no header flags - // 'pos' is the offset of the start of a gzip chunk. - size_t chunk_offset = pos; - - *num_chunks += 3; - *chunks = 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; - - // 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. - - size_t allocated = 32768; - curr->len = 0; - curr->data = malloc(allocated); - curr->start = pos; - curr->deflate_data = p; - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = st.st_size - pos; - strm.next_in = p; - - // -15 means we are decoding a 'raw' deflate stream; zlib will - // not expect zlib headers. - int ret = inflateInit2(&strm, -15); - - do { - strm.avail_out = allocated - curr->len; - strm.next_out = curr->data + curr->len; - ret = inflate(&strm, Z_NO_FLUSH); - if (ret < 0) { - printf("Error: inflate failed [%s] at file offset [%zu]\n" - "imgdiff only supports gzip kernel compression," - " did you try CONFIG_KERNEL_LZO?\n", - strm.msg, chunk_offset); - free(img); - return NULL; - } - curr->len = allocated - strm.avail_out; - if (strm.avail_out == 0) { - allocated *= 2; - curr->data = realloc(curr->data, allocated); - } - } while (ret != Z_STREAM_END); - - curr->deflate_len = st.st_size - strm.avail_in - pos; - inflateEnd(&strm); - pos += curr->deflate_len; - p += curr->deflate_len; - ++curr; - - // create a normal chunk for the footer - - 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 %d != decompressed size %d\n", - footer_size, curr[-2].len); - free(img); - return NULL; - } - } 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 = 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 < (st.st_size - pos); ++curr->len) { - if (p[curr->len] == 0x1f && - p[curr->len+1] == 0x8b && - p[curr->len+2] == 0x08 && - p[curr->len+3] == 0x00) { - break; - } - } - 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; - - 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; -} - -/* - * Verify that we can reproduce exactly the same compressed data that - * we started with. Sets the level, method, windowBits, memLevel, and - * strategy fields in the chunk to the encoding parameters needed to - * produce the right output. Returns 0 on success. - */ -int ReconstructDeflateChunk(ImageChunk* chunk) { - if (chunk->type != CHUNK_DEFLATE) { - printf("attempt to reconstruct non-deflate chunk\n"); - return -1; - } - - size_t p = 0; - unsigned char* out = malloc(BUFFER_SIZE); - - // We only check two combinations of encoder parameters: level 6 - // (the default) and level 9 (the maximum). - for (chunk->level = 6; chunk->level <= 9; chunk->level += 3) { - chunk->windowBits = -15; // 32kb window; negative to indicate a raw stream. - chunk->memLevel = 8; // the default value. - chunk->method = Z_DEFLATED; - chunk->strategy = Z_DEFAULT_STRATEGY; - - if (TryReconstruction(chunk, out) == 0) { - free(out); - return 0; - } - } - - free(out); - return -1; -} - -/* - * 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. - */ -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; - } - } - - char ptemp[] = "/tmp/imgdiff-patch-XXXXXX"; - mkstemp(ptemp); - - int r = bsdiff(src->data, src->len, &(src->I), tgt->data, tgt->len, ptemp); - if (r != 0) { - printf("bsdiff() failed: %d\n", r); - return NULL; - } - - struct stat st; - if (stat(ptemp, &st) != 0) { - printf("failed to stat patch file %s: %s\n", - ptemp, strerror(errno)); - return NULL; - } - - unsigned char* data = malloc(st.st_size); - - if (tgt->type == CHUNK_NORMAL && tgt->len <= st.st_size) { - unlink(ptemp); - - tgt->type = CHUNK_RAW; - *size = tgt->len; - return tgt->data; - } - - *size = st.st_size; - - FILE* f = fopen(ptemp, "rb"); - if (f == NULL) { - printf("failed to open patch %s: %s\n", ptemp, strerror(errno)); - return NULL; - } - if (fread(data, 1, st.st_size, f) != st.st_size) { - printf("failed to read patch %s: %s\n", ptemp, strerror(errno)); - return NULL; - } - fclose(f); - - unlink(ptemp); - - 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; - } -} - -/* - * Look for runs of adjacent normal chunks and compress them down into - * 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); - } - - 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); - } - - ++out; - in_start = in_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; - } - } - return NULL; -} - -void DumpChunks(ImageChunk* chunks, int num_chunks) { - int i; - for (i = 0; i < num_chunks; ++i) { - printf("chunk %d: type %d start %d len %d\n", - i, chunks[i].type, chunks[i].start, chunks[i].len); - } -} - -int main(int argc, char** argv) { - int zip_mode = 0; - - if (argc >= 2 && strcmp(argv[1], "-z") == 0) { - zip_mode = 1; - --argc; - ++argv; - } - - size_t bonus_size = 0; - unsigned char* bonus_data = NULL; - 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)); - return 1; - } - bonus_size = st.st_size; - bonus_data = 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)); - return 1; - } - if (fread(bonus_data, 1, bonus_size, f) != 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; - - if (zip_mode) { - if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) { - printf("failed to break apart source zip file\n"); - return 1; - } - if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) { - printf("failed to break apart target zip file\n"); - return 1; - } - } else { - if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) { - printf("failed to break apart source image\n"); - return 1; - } - if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) { - printf("failed to break apart target image\n"); - return 1; - } - - // 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); - } - - if (num_src_chunks != num_tgt_chunks) { - printf("source and target don't have same number of chunks!\n"); - printf("source chunks:\n"); - DumpChunks(src_chunks, num_src_chunks); - printf("target chunks:\n"); - DumpChunks(tgt_chunks, num_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); - printf("source chunks:\n"); - DumpChunks(src_chunks, num_src_chunks); - printf("target chunks:\n"); - DumpChunks(tgt_chunks, num_tgt_chunks); - return 1; - } - } - } - - for (i = 0; i < num_tgt_chunks; ++i) { - if (tgt_chunks[i].type == 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 (zip_mode) { - ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); - if (src) { - ChangeDeflateChunkToNormal(src); - } - } else { - ChangeDeflateChunkToNormal(src_chunks+i); - } - continue; - } - - // If two deflate chunks are identical (eg, the kernel has not - // changed between two builds), treat them as normal chunks. - // This makes applypatch much faster -- it can apply a trivial - // patch to the compressed data, rather than uncompressing and - // recompressing to apply the trivial patch to the uncompressed - // data. - ImageChunk* src; - if (zip_mode) { - src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); - } else { - src = src_chunks+i; - } - - if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) { - ChangeDeflateChunkToNormal(tgt_chunks+i); - if (src) { - ChangeDeflateChunkToNormal(src); - } - } - } - } - - // Merging neighboring normal chunks. - if (zip_mode) { - // 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); - } 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) { - // This shouldn't happen. - printf("merging normal chunks went awry\n"); - return 1; - } - } - - // Compute bsdiff patches for each chunk's data (the uncompressed - // data, in the case of deflate chunks). - - DumpChunks(src_chunks, num_src_chunks); - - printf("Construct patches for %d chunks...\n", num_tgt_chunks); - unsigned char** patch_data = malloc(num_tgt_chunks * sizeof(unsigned char*)); - size_t* patch_size = malloc(num_tgt_chunks * sizeof(size_t)); - for (i = 0; i < num_tgt_chunks; ++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); - } else { - patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i); - } - } else { - if (i == 1 && bonus_data) { - printf(" using %d bytes of bonus data for chunk %d\n", bonus_size, i); - src_chunks[i].data = 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); - } - printf("patch %3d is %d bytes (of %d)\n", - i, patch_size[i], tgt_chunks[i].source_len); - } - - // Figure out how big the imgdiff file header is going to be, so - // that we can correctly compute the offset of each bsdiff patch - // 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; - } - } - - size_t offset = total_header_size; - - FILE* f = fopen(argv[3], "wb"); - - // 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 (%10d, %10d) %10d\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 (%10d, %10d) %10d %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 (%10d, %10d)\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; - } - } - - // 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); - } - } - - fclose(f); - - return 0; -} diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp new file mode 100644 index 0000000000..fc240644f2 --- /dev/null +++ b/applypatch/imgdiff.cpp @@ -0,0 +1,1087 @@ +/* + * 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 program constructs binary patches for images -- such as boot.img + * and recovery.img -- that consist primarily of large chunks of gzipped + * data interspersed with uncompressed data. Doing a naive bsdiff of + * these files is not useful because small changes in the data lead to + * large changes in the compressed bitstream; bsdiff patches of gzipped + * data are typically as large as the data itself. + * + * To patch these usefully, we break the source and target images up into + * chunks of two types: "normal" and "gzip". Normal chunks are simply + * patched using a plain bsdiff. Gzip chunks are first expanded, then a + * bsdiff is applied to the uncompressed data, then the patched data is + * gzipped using the same encoder parameters. Patched chunks are + * concatenated together to create the output file; the output image + * should be *exactly* the same series of bytes as the target image used + * originally to generate the patch. + * + * To work well with this tool, the gzipped sections of the target + * image must have been generated using the same deflate encoder that + * is available in applypatch, namely, the one in the zlib library. + * In practice this means that images should be compressed using the + * "minigzip" tool included in the zlib distribution, not the GNU gzip + * program. + * + * An "imgdiff" patch consists of a header describing the chunk structure + * of the file and any encoding parameters needed for the gzipped + * chunks, followed by N bsdiff patches, one per chunk. + * + * For a diff to be generated, the source and target images must have the + * same "chunk" structure: that is, the same number of gzipped and normal + * chunks in the same order. Android boot and recovery images currently + * consist of five chunks: a small normal header, a gzipped kernel, a + * small normal section, a gzipped ramdisk, and finally a small normal + * footer. + * + * Caveats: we locate gzipped sections within the source and target + * images by searching for the byte sequence 1f8b0800: 1f8b is the gzip + * magic number; 08 specifies the "deflate" encoding [the only encoding + * supported by the gzip standard]; and 00 is the flags byte. We do not + * currently support any extra header fields (which would be indicated by + * a nonzero flags byte). We also don't handle the case when that byte + * sequence appears spuriously in the file. (Note that it would have to + * occur spuriously within a normal chunk to be a problem.) + * + * + * The imgdiff patch header looks like this: + * + * "IMGDIFF1" (8) [magic number and version] + * chunk count (4) + * for each chunk: + * chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}] + * if chunk type == CHUNK_NORMAL: + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * if chunk type == CHUNK_GZIP: (version 1 only) + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * source expanded len (8) [size of uncompressed source] + * target expected len (8) [size of uncompressed target] + * gzip level (4) + * method (4) + * windowBits (4) + * memLevel (4) + * strategy (4) + * gzip header len (4) + * gzip header (gzip header len) + * gzip footer (8) + * if chunk type == CHUNK_DEFLATE: (version 2 only) + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * source expanded len (8) [size of uncompressed source] + * target expected len (8) [size of uncompressed target] + * gzip level (4) + * method (4) + * windowBits (4) + * memLevel (4) + * strategy (4) + * if chunk type == RAW: (version 2 only) + * target len (4) + * data (target len) + * + * All integers are little-endian. "source start" and "source len" + * specify the section of the input image that comprises this chunk, + * including the gzip header and footer for gzip chunks. "source + * expanded len" is the size of the uncompressed source data. "target + * expected len" is the size of the uncompressed data after applying + * the bsdiff patch. The next five parameters specify the zlib + * parameters to be used when compressing the patched data, and the + * next three specify the header and footer to be wrapped around the + * compressed data to create the output chunk (so that header contents + * like the timestamp are recreated exactly). + * + * After the header there are 'chunk count' bsdiff patches; the offset + * of each from the beginning of the file is specified in the header. + * + * This tool can take an optional file of "bonus data". This is an + * extra file of data that is appended to chunk #1 after it is + * compressed (it must be a CHUNK_DEFLATE chunk). The same file must + * be available (and passed to applypatch with -b) when applying the + * patch. This is used to reduce the size of recovery-from-boot + * patches by combining the boot image with recovery ramdisk + * information that is stored on the system partition. + */ + +#include "applypatch/imgdiff.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +using android::base::get_unaligned; + +static constexpr auto BUFFER_SIZE = 0x8000; + +// 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()); + } + + void SetSourceInfo(const ImageChunk& other); + void SetEntryName(std::string entryname); + void SetUncompressedData(std::vector data); + bool SetBonusData(const std::vector& bonus_data); + + 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 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(); +} + +size_t ImageChunk::DataLengthForPatch() const { + if (type_ == CHUNK_DEFLATE) { + return uncompressed_data_.size(); + } + return raw_data_len_; +} + +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); +} + +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(); + } +} + +void ImageChunk::SetEntryName(std::string entryname) { + entry_name_ = std::move(entryname); +} + +void ImageChunk::SetUncompressedData(std::vector data) { + uncompressed_data_ = std::move(data); +} + +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; +} + +// 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; +} + +void ImageChunk::ChangeDeflateChunkToNormal() { + if (type_ != CHUNK_DEFLATE) return; + type_ = CHUNK_NORMAL; + entry_name_.clear(); + uncompressed_data_.clear(); +} + +// 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; + } +} + +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; + } +} + +bool ImageChunk::IsAdjacentNormal(const ImageChunk& other) const { + if (type_ != CHUNK_NORMAL || other.type_ != CHUNK_NORMAL) { + return false; + } + return (other.start_ == start_ + raw_data_len_); +} + +void ImageChunk::MergeAdjacentNormal(const ImageChunk& other) { + CHECK(IsAdjacentNormal(other)); + raw_data_len_ = raw_data_len_ + other.raw_data_len_; +} + +bool ImageChunk::ReconstructDeflateChunk() { + if (type_ != CHUNK_DEFLATE) { + printf("attempt to reconstruct non-deflate chunk\n"); + return false; + } + + // We only check two combinations of encoder parameters: level 6 + // (the default) and level 9 (the maximum). + for (int level = 6; level <= 9; level += 3) { + if (TryReconstruction(level)) { + compress_level_ = level; + return true; + } + } + + return false; +} + +/* + * 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; + } + + 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; + } + + 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); + + if (offset != raw_data_len_) { + // mismatch; ran out of data before we should have. + return false; + } + return true; +} + +// 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; + } + + // 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; + } + } + } + + // EOCD not found, this file is likely not a valid zip file. + return false; +} + +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; + } + + 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(); + + // 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; + } + + // 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; + } + + 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)); + } + } + + 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); + + 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; + } + + 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 += compressed_len; + ++nextentry; + continue; + } + + // 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 { + raw_data_len = zip_file->size() - pos; + } + chunks->emplace_back(CHUNK_NORMAL, pos, zip_file, raw_data_len); + + pos += raw_data_len; + } + + CloseArchive(handle); + return true; +} + +// 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 (fstat(fd, &st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return false; + } + + size_t sz = static_cast(st.st_size); + img->resize(sz); + if (!android::base::ReadFully(fd, img->data(), sz)) { + printf("failed to read \"%s\" %s\n", filename, strerror(errno)); + return false; + } + + size_t pos = 0; + + while (pos < sz) { + // 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; + + // 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 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; + + // 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 = 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 - uncompressed_len; + strm.next_out = uncompressed_data.data() + uncompressed_len; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret < 0) { + printf("Warning: inflate failed [%s] at offset [%zu], treating as a normal chunk\n", + strm.msg, chunk_offset); + break; + } + uncompressed_len = allocated - strm.avail_out; + if (strm.avail_out == 0) { + allocated *= 2; + uncompressed_data.resize(allocated); + } + } while (ret != Z_STREAM_END); + + raw_data_len = sz - strm.avail_in - pos; + inflateEnd(&strm); + + if (ret < 0) { + continue; + } + + // 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; + } + 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; + } + + ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len); + uncompressed_data.resize(uncompressed_len); + body.SetUncompressedData(std::move(uncompressed_data)); + chunks->push_back(body); + + pos += raw_data_len; + + // create a normal chunk for the footer + chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN); + + 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); + + pos += data_len; + } + } + + return true; +} + +/* + * 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. + */ +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"; +#endif + + int fd = mkstemp(ptemp); + if (fd == -1) { + printf("MakePatch failed to create a temporary file: %s\n", strerror(errno)); + return false; + } + close(fd); + + 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 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 (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); + // Change the chunk type to raw if the patch takes less space that way. + if (tgt->ChangeChunkToRaw(sz)) { + unlink(ptemp); + size_t patch_size = tgt->DataLengthForPatch(); + patch_data->resize(patch_size); + std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin()); + return true; + } + 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; + } + + unlink(ptemp); + tgt->SetSourceInfo(*src); + + return true; +} + +/* + * Look for runs of adjacent normal chunks and compress them down into + * a single chunk. (Such runs can be produced when deflate chunks are + * changed to normal chunks.) + */ +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 (merged_last != cur) { + chunks->at(merged_last) = std::move(chunks->at(cur)); + } + merged_last++; + cur = to_check; + } + if (merged_last < chunks->size()) { + chunks->erase(chunks->begin() + merged_last, chunks->end()); + } +} + +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 nullptr; +} + +static void DumpChunks(const std::vector& chunks) { + for (size_t i = 0; i < chunks.size(); ++i) { + printf("chunk %zu: ", i); + chunks[i].Dump(); + } +} + +int imgdiff(int argc, const char** argv) { + bool zip_mode = false; + + if (argc >= 2 && strcmp(argv[1], "-z") == 0) { + zip_mode = true; + --argc; + ++argv; + } + + std::vector bonus_data; + if (argc >= 3 && strcmp(argv[1], "-b") == 0) { + 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; + } + struct stat st; + if (fstat(fd, &st) != 0) { + printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + + 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; + } + + argc -= 2; + argv += 2; + } + + if (argc != 4) { + printf("usage: %s [-z] [-b ] \n", + argv[0]); + return 2; + } + + std::vector src_chunks; + std::vector tgt_chunks; + std::vector src_file; + std::vector tgt_file; + + if (zip_mode) { + if (!ReadZip(argv[1], &src_chunks, &src_file, true)) { + printf("failed to break apart source zip file\n"); + return 1; + } + 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], &src_chunks, &src_file)) { + printf("failed to break apart source image\n"); + return 1; + } + if (!ReadImage(argv[2], &tgt_chunks, &tgt_file)) { + printf("failed to break apart target image\n"); + return 1; + } + + // Verify that the source and target images have the same chunk + // structure (ie, the same sequence of deflate and normal chunks). + + // Merge the gzip header and footer in with any adjacent normal chunks. + MergeAdjacentNormalChunks(&tgt_chunks); + MergeAdjacentNormalChunks(&src_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); + printf("target chunks:\n"); + DumpChunks(tgt_chunks); + return 1; + } + 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); + printf("target chunks:\n"); + DumpChunks(tgt_chunks); + return 1; + } + } + } + + 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 (!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].GetEntryName(), src_chunks); + if (src != nullptr) { + src->ChangeDeflateChunkToNormal(); + } + } else { + src_chunks[i].ChangeDeflateChunkToNormal(); + } + continue; + } + + // If two deflate chunks are identical (eg, the kernel has not + // changed between two builds), treat them as normal chunks. + // This makes applypatch much faster -- it can apply a trivial + // patch to the compressed data, rather than uncompressing and + // recompressing to apply the trivial patch to the uncompressed + // data. + ImageChunk* src; + if (zip_mode) { + src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks); + } else { + src = &src_chunks[i]; + } + + if (src == nullptr) { + tgt_chunks[i].ChangeDeflateChunkToNormal(); + } else if (tgt_chunks[i] == *src) { + tgt_chunks[i].ChangeDeflateChunkToNormal(); + src->ChangeDeflateChunkToNormal(); + } + } + } + + // Merging neighboring normal chunks. + if (zip_mode) { + // 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); + + } 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); + MergeAdjacentNormalChunks(&src_chunks); + if (src_chunks.size() != tgt_chunks.size()) { + // This shouldn't happen. + printf("merging normal chunks went awry\n"); + return 1; + } + } + + // Compute bsdiff patches for each chunk's data (the uncompressed + // data, in the case of deflate chunks). + + DumpChunks(src_chunks); + + 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].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 { + 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.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 %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 + // that we can correctly compute the offset of each bsdiff patch + // within the file. + + size_t total_header_size = 12; + 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; + + 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. + 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 (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; + } + } + } + + 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.c b/applypatch/imgpatch.c deleted file mode 100644 index 09b0a7397b..0000000000 --- a/applypatch/imgpatch.c +++ /dev/null @@ -1,234 +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. - */ - -// See imgdiff.c in this directory for a description of the patch file -// format. - -#include -#include -#include -#include -#include -#include -#include - -#include "zlib.h" -#include "mincrypt/sha.h" -#include "applypatch.h" -#include "imgdiff.h" -#include "utils.h" - -/* - * Apply the patch given in 'patch_filename' to the source data given - * by (old_data, old_size). Write the patched output to the 'output' - * 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 __unused, - 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; - } - - // 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"); - return -1; - } - - int num_chunks = Read4(header+8); - - 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; - } - 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); - - 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) SHA_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); - size_t target_len = Read8(deflate_header+32); - int level = Read4(deflate_header+40); - int method = Read4(deflate_header+44); - int windowBits = Read4(deflate_header+48); - int memLevel = Read4(deflate_header+52); - int strategy = Read4(deflate_header+56); - - // 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; - - unsigned char* expanded_source = malloc(expanded_len); - if (expanded_source == NULL) { - printf("failed to allocate %zu bytes for expanded_source\n", - expanded_len); - return -1; - } - - 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; - - 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 + (expanded_len - bonus_size), - bonus_data->data, bonus_size); - } - - // Next, apply the bsdiff patch (in memory) to the uncompressed - // data. - unsigned char* uncompressed_target_data; - ssize_t uncompressed_target_size; - if (ApplyBSDiffPatchMem(expanded_source, expanded_len, - patch, patch_offset, - &uncompressed_target_data, - &uncompressed_target_size) != 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. - unsigned char* temp_data = expanded_source; - ssize_t temp_size = expanded_len; - if (temp_size < 32768) { - // ... unless the buffer is too small, in which case we'll - // allocate a fresh one. - free(temp_data); - temp_data = malloc(32768); - temp_size = 32768; - } - - // now the deflate stream - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = uncompressed_target_size; - strm.next_in = uncompressed_target_data; - ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy); - do { - strm.avail_out = temp_size; - strm.next_out = temp_data; - ret = deflate(&strm, Z_FINISH); - ssize_t have = temp_size - strm.avail_out; - - if (sink(temp_data, have, token) != have) { - printf("failed to write %ld compressed bytes to output\n", - (long)have); - return -1; - } - if (ctx) SHA_update(ctx, temp_data, have); - } while (ret != Z_STREAM_END); - deflateEnd(&strm); - - free(temp_data); - free(uncompressed_target_data); - } else { - printf("patch chunk %d is unknown type %d\n", i, type); - return -1; - } - } - - return 0; -} diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp new file mode 100644 index 0000000000..df75f98d49 --- /dev/null +++ b/applypatch/imgpatch.cpp @@ -0,0 +1,291 @@ +/* + * 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. + */ + +// 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 +#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); +} + +/* + * Apply the patch given in 'patch_filename' to the source data given + * by (old_data, old_size). Write the patched output to the 'output' + * file, and update the SHA context with the output data as well. + * Return 0 on success. + */ +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; + + 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)); + + 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; + } + + // Because we've provided enough room to accommodate the output + // data, we expect one call to inflate() to suffice. + ret = inflate(&strm, Z_SYNC_FLUSH); + if (ret != Z_STREAM_END) { + printf("source inflation returned %d\n", ret); + return -1; + } + // We should have filled the output buffer exactly, except + // for the bonus_size. + if (strm.avail_out != bonus_size) { + printf("source inflation short by %zu bytes\n", strm.avail_out - bonus_size); + return -1; + } + inflateEnd(&strm); + + if (bonus_size) { + memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0], + bonus_size); + } + } + + 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; +} diff --git a/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h similarity index 55% rename from applypatch/applypatch.h rename to applypatch/include/applypatch/applypatch.h index edec848129..581360ef1f 100644 --- a/applypatch/applypatch.h +++ b/applypatch/include/applypatch/applypatch.h @@ -17,21 +17,23 @@ #ifndef _APPLYPATCH_H #define _APPLYPATCH_H +#include #include -#include "mincrypt/sha.h" -#include "edify/expr.h" -typedef struct _Patch { - uint8_t sha1[SHA_DIGEST_SIZE]; - const char* patch_filename; -} Patch; +#include +#include +#include +#include + +#include + +#include "edify/expr.h" -typedef struct _FileContents { - uint8_t sha1[SHA_DIGEST_SIZE]; - unsigned char* data; - ssize_t size; +struct FileContents { + uint8_t sha1[SHA_DIGEST_LENGTH]; + std::vector data; struct stat st; -} FileContents; +}; // When there isn't enough room on the target filesystem to hold the // patched version of the file, we copy the original here and delete @@ -40,9 +42,9 @@ typedef 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); @@ -52,36 +54,27 @@ 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.c +// 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, - unsigned char** new_data, ssize_t* new_size); +int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch, + size_t patch_offset, SinkFn sink, SHA_CTX* ctx); -// imgpatch.c -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); +// imgpatch.cpp +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink, + SHA_CTX* ctx, const Value* bonus_data); -// freecache.c +// freecache.cpp int MakeFreeSpaceOnCache(size_t bytes_needed); #endif 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 new file mode 100644 index 0000000000..07c66094ff --- /dev/null +++ b/applypatch/include/applypatch/imgpatch.h @@ -0,0 +1,29 @@ +/* + * 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_IMGPATCH_H +#define _APPLYPATCH_IMGPATCH_H + +#include + +#include + +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/main.c b/applypatch/main.c deleted file mode 100644 index 8e9fe80ef6..0000000000 --- a/applypatch/main.c +++ /dev/null @@ -1,214 +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 "applypatch.h" -#include "edify/expr.h" -#include "mincrypt/sha.h" - -int CheckMode(int argc, char** argv) { - if (argc < 3) { - return 2; - } - return applypatch_check(argv[2], argc-3, argv+3); -} - -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; - } - return CacheSizeCheck(bytes); -} - -// Parse arguments (which should be of the form "" or -// ":" into the new parallel arrays *sha1s and -// *patches (loading file contents into the patches). Returns 0 on -// success. -static int ParsePatchArgs(int argc, char** argv, - char*** sha1s, Value*** patches, int* num_patches) { - *num_patches = argc; - *sha1s = malloc(*num_patches * sizeof(char*)); - *patches = malloc(*num_patches * sizeof(Value*)); - memset(*patches, 0, *num_patches * sizeof(Value*)); - - uint8_t digest[SHA_DIGEST_SIZE]; - - int i; - for (i = 0; i < *num_patches; ++i) { - char* colon = strchr(argv[i], ':'); - if (colon != NULL) { - *colon = '\0'; - ++colon; - } - - if (ParseSha1(argv[i], digest) != 0) { - printf("failed to parse sha1 \"%s\"\n", argv[i]); - return -1; - } - - (*sha1s)[i] = argv[i]; - if (colon == NULL) { - (*patches)[i] = NULL; - } else { - FileContents fc; - if (LoadFileContents(colon, &fc) != 0) { - goto abort; - } - (*patches)[i] = malloc(sizeof(Value)); - (*patches)[i]->type = VAL_BLOB; - (*patches)[i]->size = fc.size; - (*patches)[i]->data = (char*)fc.data; - } - } - - return 0; - - abort: - for (i = 0; i < *num_patches; ++i) { - Value* p = (*patches)[i]; - if (p != NULL) { - free(p->data); - free(p); - } - } - free(*sha1s); - free(*patches); - return -1; -} - -int PatchMode(int argc, char** argv) { - Value* bonus = NULL; - if (argc >= 3 && strcmp(argv[1], "-b") == 0) { - FileContents fc; - if (LoadFileContents(argv[2], &fc) != 0) { - printf("failed to load bonus file %s\n", argv[2]); - return 1; - } - bonus = malloc(sizeof(Value)); - bonus->type = VAL_BLOB; - bonus->size = fc.size; - bonus->data = (char*)fc.data; - argc -= 2; - argv += 2; - } - - if (argc < 6) { - return 2; - } - - char* endptr; - size_t target_size = strtol(argv[4], &endptr, 10); - if (target_size == 0 && endptr == argv[4]) { - printf("can't parse \"%s\" as byte count\n\n", argv[4]); - return 1; - } - - char** sha1s; - Value** patches; - int num_patches; - if (ParsePatchArgs(argc-5, argv+5, &sha1s, &patches, &num_patches) != 0) { - printf("failed to parse patch args\n"); - return 1; - } - - int result = applypatch(argv[1], argv[2], argv[3], target_size, - num_patches, sha1s, patches, bonus); - - int i; - for (i = 0; i < num_patches; ++i) { - Value* p = patches[i]; - if (p != NULL) { - free(p->data); - free(p); - } - } - if (bonus) { - free(bonus->data); - free(bonus); - } - free(sha1s); - free(patches); - - return result; -} - -// This program 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). -// -// - if the sha1 hash of is , does nothing and exits -// successfully. -// -// - otherwise, if the sha1 hash of is , applies the -// bsdiff to to produce a new file (the type of patch -// is automatically detected from the file header). 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 src-file". -// -// - otherwise, or if any error is encountered, exits with non-zero -// status. -// -// (or in check mode) may refer to an MTD partition -// to read the source data. See the comments for the -// LoadMTDContents() function above for the format of such a filename. - -int main(int argc, 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]); - return 2; - } - - int result; - - if (strncmp(argv[1], "-l", 3) == 0) { - 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); - } - - if (result == 2) { - goto usage; - } - return result; -} diff --git a/applypatch/utils.c b/applypatch/utils.c deleted file mode 100644 index 41ff676dc1..0000000000 --- a/applypatch/utils.c +++ /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 = pv; - return (int)(((unsigned int)p[1] << 8) | - (unsigned int)p[0]); -} - -int Read4(void* pv) { - unsigned char* p = 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 = 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/attr/Android.mk b/attr/Android.mk new file mode 100644 index 0000000000..2986ab50bf --- /dev/null +++ b/attr/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := listxattr.c +LOCAL_CFLAGS := -c -W +LOCAL_MODULE := listxattr +LOCAL_MODULE_TAGS := eng +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_PACK_MODULE_RELOCATIONS := false + +ifneq ($(TARGET_ARCH), arm64) + ifneq ($(TARGET_ARCH), x86_64) + LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker + else + LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker64 + endif +else + LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker64 +endif + +include $(BUILD_EXECUTABLE) diff --git a/attr/listxattr.c b/attr/listxattr.c new file mode 100644 index 0000000000..1aa8e14cf4 --- /dev/null +++ b/attr/listxattr.c @@ -0,0 +1,202 @@ +/**************************************************************************** + | (C) Copyright 2008 Novell, Inc. All Rights Reserved. + | + | GPLv2: This program is free software; you can redistribute it + | and/or modify it under the terms of version 2 of the GNU General + | Public License as published by the Free Software Foundation. + | + | This program 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. + +-------------------------------------------------------------------------*/ +/* + * NOTE from Dees_Troy: modified source to display values along with xattr names + * Below code comments about usage are no longer accurate but came from the + * original source code from the chromium project and combine features of + * listxattr and getfattr: + * https://chromium.googlesource.com/chromiumos/platform/punybench/+/factory-1235.B/file.m/listxattr.c + * https://chromium.googlesource.com/chromiumos/platform/punybench/+/factory-1235.B/file.m/getxattr.c + */ +/* + * LISTXATTR(2) Linux Programmer's Manual LISTXATTR(2) + * + * + * + * NAME + * listxattr, llistxattr, flistxattr - list extended attribute names + * + * SYNOPSIS + * #include + * #include + * + * ssize_t listxattr (const char *path, + * char *list, size_t size); + * ssize_t llistxattr (const char *path, + * char *list, size_t size); + * ssize_t flistxattr (int filedes, + * char *list, size_t size); + * + * DESCRIPTION + * Extended attributes are name:value pairs associated with inodes + * (files, directories, symlinks, etc). They are extensions to the + * normal attributes which are associated with all inodes in the sys- + * tem (i.e. the stat(2) data). A complete overview of extended + * attributes concepts can be found in attr(5). + * + * listxattr retrieves the list of extended attribute names associated + * with the given path in the filesystem. The list is the set of + * (NULL-terminated) names, one after the other. Names of extended + * attributes to which the calling process does not have access may be + * omitted from the list. The length of the attribute name list is + * returned. + * + * llistxattr is identical to listxattr, except in the case of a sym- + * bolic link, where the list of names of extended attributes associ- + * ated with the link itself is retrieved, not the file that it refers + * to. + * + * flistxattr is identical to listxattr, only the open file pointed to + * by filedes (as returned by open(2)) is interrogated in place of + * path. + * + * A single extended attribute name is a simple NULL-terminated + * string. The name includes a namespace prefix; there may be sev- + * eral, disjoint namespaces associated with an individual inode. + * + * An empty buffer of size zero can be passed into these calls to + * return the current size of the list of extended attribute names, + * which can be used to estimate the size of a buffer which is suffi- + * ciently large to hold the list of names. + * + * EXAMPLES + * The list of names is returned as an unordered array of NULL-termi- + * nated character strings (attribute names are separated by NULL + * characters), like this: + * user.name1\0system.name1\0user.name2\0 + * + * Filesystems like ext2, ext3 and XFS which implement POSIX ACLs + * using extended attributes, might return a list like this: + * system.posix_acl_access\0system.posix_acl_default\0 + * + * RETURN VALUE + * On success, a positive number is returned indicating the size of + * the extended attribute name list. On failure, -1 is returned and + * errno is set appropriately. + * + * If the size of the list buffer is too small to hold the result, + * errno is set to ERANGE. + * + * If extended attributes are not supported by the filesystem, or are + * disabled, errno is set to ENOTSUP. + * + * The errors documented for the stat(2) system call are also applica- + * ble here. + * + * AUTHORS + * Andreas Gruenbacher, and the SGI XFS + * development team, . Please send any bug + * reports or comments to these addresses. + * + * SEE ALSO + * getfattr(1), setfattr(1), getxattr(2), open(2), removexattr(2), + * setxattr(2), stat(2), attr(5) + * + * + * + * Dec 2001 Extended Attributes LISTXATTR(2) + */ +#include +#include +#include +#include +#include +//#include +//#include + +/* dumpmem: dumps an n byte area of memory to screen */ +void dumpmem (const void *mem, unsigned int n) +{ + const char *c = mem; + unsigned i; + int all_text = 1; + if (n == 0) { + printf(""); + return; + } + for (i = 0; i < n - 1; i++, c++) { + if (!isprint(*c)) { + all_text = 0; + break; + } + } + c = mem; + if (all_text) { + for (i = 0; i < n - 1; i++, c++) { + putchar(*c); + } + return; + } else { + char hex[(n * 2) + 1]; + for(i = 0; i < n; i++, c++) + sprintf(hex + (i * 2), "%02X", *c); + hex[n] = 0; + printf("0x%s", hex); + return; + } +} + +void dump_list (char *file, char *list, ssize_t size) +{ + int c; + int i; + int first = 1; + int j = 0; + char xattr[1024]; + char value[1024]; + ssize_t size2; + for (i = 0; i < size; i++) { + c = list[i]; + if (c) { + if (first) { + putchar('\t'); + first = 0; + j = 0; + } + putchar(c); + xattr[j++] = list[i]; + } else { + xattr[j] = '\0'; + size2 = getxattr(file, xattr, value, sizeof(value)); + if (size2 < 0) { + printf("file=%s xattr=%s returned:", file, xattr); + } else { + putchar('='); + dumpmem(value, size2); + } + putchar('\n'); + first = 1; + } + } +} +void usage (void) +{ + printf("listxattr "); +} +char List[1<<17]; +int main (int argc, char *argv[]) +{ + ssize_t size; + if (argc < 2) { + usage(); + exit(2); + } + size = listxattr(argv[1], List, sizeof(List)); + if (size == -1) { + perror(argv[1]); + exit(2); + } + printf("xattrs for %s:\n", argv[1]); + dump_list(argv[1], List, size); + return 0; +} 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.cpp b/bootloader.cpp deleted file mode 100644 index b5b20e9c90..0000000000 --- a/bootloader.cpp +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* -#include -*/ -#include "bootloader.h" -#include "common.h" -extern "C" { -#include "mtdutils/mtdutils.h" -} -/* -#include "roots.h" -*/ -#include -#include -#include -#include -#include -#include - -// fake Volume struct that allows us to use the AOSP code easily -struct Volume -{ - char fs_type[8]; - char blk_device[256]; -}; - -static Volume misc; - -void set_misc_device(const char* type, const char* name) { - strlcpy(misc.fs_type, type, sizeof(misc.fs_type)); - if (strlen(name) >= sizeof(misc.blk_device)) { - LOGE("New device name of '%s' is too large for bootloader.cpp\n", name); - } else { - strcpy(misc.blk_device, name); - } -} - -static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v); -static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v); -static int get_bootloader_message_block(struct bootloader_message *out, const Volume* v); -static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v); - -int get_bootloader_message(struct bootloader_message *out) { -#if 0 - Volume* v = volume_for_path("/misc"); - if (v == NULL) { - LOGE("Cannot load volume /misc!\n"); - return -1; - } -#else - Volume* v = &misc; - if (v->fs_type[0] == 0) { - LOGI("Not using /misc, not defined in fstab.\n"); - return -1; - } -#endif - if (strcmp(v->fs_type, "mtd") == 0) { - return get_bootloader_message_mtd(out, v); - } else if (strcmp(v->fs_type, "emmc") == 0) { - return get_bootloader_message_block(out, v); - } - LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); - return -1; -} - -int set_bootloader_message(const struct bootloader_message *in) { -#if 0 - Volume* v = volume_for_path("/misc"); - if (v == NULL) { - LOGE("Cannot load volume /misc!\n"); - return -1; - } -#else - Volume* v = &misc; - if (v->fs_type[0] == 0) { - LOGI("Not using /misc, not defined in fstab.\n"); - return -1; - } -#endif - if (strcmp(v->fs_type, "mtd") == 0) { - return set_bootloader_message_mtd(in, v); - } else if (strcmp(v->fs_type, "emmc") == 0) { - return set_bootloader_message_block(in, v); - } - LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); - return -1; -} - -// ------------------------------ -// for misc partitions on MTD -// ------------------------------ - -static const int MISC_PAGES = 3; // number of pages to save -static const int MISC_COMMAND_PAGE = 1; // bootloader command is this page - -static int get_bootloader_message_mtd(struct bootloader_message *out, - const Volume* v) { - size_t write_size; - mtd_scan_partitions(); - const MtdPartition *part = mtd_find_partition_by_name(v->blk_device); - if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) { - LOGE("Can't find %s\n", v->blk_device); - return -1; - } - - MtdReadContext *read = mtd_read_partition(part); - if (read == NULL) { - LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } - - const ssize_t size = write_size * MISC_PAGES; - char data[size]; - ssize_t r = mtd_read_data(read, data, size); - if (r != size) LOGE("Can't read %s\n(%s)\n", v->blk_device, strerror(errno)); - mtd_read_close(read); - if (r != size) return -1; - - memcpy(out, &data[write_size * MISC_COMMAND_PAGE], sizeof(*out)); - return 0; -} -static int set_bootloader_message_mtd(const struct bootloader_message *in, - const Volume* v) { - size_t write_size; - mtd_scan_partitions(); - const MtdPartition *part = mtd_find_partition_by_name(v->blk_device); - if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) { - LOGE("Can't find %s\n", v->blk_device); - return -1; - } - - MtdReadContext *read = mtd_read_partition(part); - if (read == NULL) { - LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } - - ssize_t size = write_size * MISC_PAGES; - char data[size]; - ssize_t r = mtd_read_data(read, data, size); - if (r != size) LOGE("Can't read %s\n(%s)\n", v->blk_device, strerror(errno)); - mtd_read_close(read); - if (r != size) return -1; - - memcpy(&data[write_size * MISC_COMMAND_PAGE], in, sizeof(*in)); - - MtdWriteContext *write = mtd_write_partition(part); - if (write == NULL) { - LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } - if (mtd_write_data(write, data, size) != size) { - LOGE("Can't write %s\n(%s)\n", v->blk_device, strerror(errno)); - mtd_write_close(write); - return -1; - } - if (mtd_write_close(write)) { - LOGE("Can't finish %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } - - LOGI("Set boot command \"%s\"\n", in->command[0] != 255 ? in->command : ""); - return 0; -} - - -// ------------------------------------ -// for misc partitions on block devices -// ------------------------------------ - -static void wait_for_device(const char* fn) { - int tries = 0; - int ret; - struct stat buf; - do { - ++tries; - ret = stat(fn, &buf); - if (ret) { - printf("stat %s try %d: %s\n", fn, tries, strerror(errno)); - sleep(1); - } - } while (ret && tries < 10); - if (ret) { - printf("failed to stat %s\n", fn); - } -} - -static int get_bootloader_message_block(struct bootloader_message *out, - const Volume* v) { - wait_for_device(v->blk_device); - FILE* f = fopen(v->blk_device, "rb"); - if (f == NULL) { - LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } -#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET - fseek(f, BOARD_RECOVERY_BLDRMSG_OFFSET, SEEK_SET); -#endif - struct bootloader_message temp; - int count = fread(&temp, sizeof(temp), 1, f); - if (count != 1) { - LOGE("Failed reading %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } - if (fclose(f) != 0) { - LOGE("Failed closing %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } - memcpy(out, &temp, sizeof(temp)); - return 0; -} - -static int set_bootloader_message_block(const struct bootloader_message *in, - const Volume* v) { - wait_for_device(v->blk_device); - FILE* f = fopen(v->blk_device, "rb+"); - if (f == NULL) { - LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } -#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET - fseek(f, BOARD_RECOVERY_BLDRMSG_OFFSET, SEEK_SET); -#endif - int count = fwrite(in, sizeof(*in), 1, f); - if (count != 1) { - LOGE("Failed writing %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } - if (fclose(f) != 0) { - LOGE("Failed closing %s\n(%s)\n", v->blk_device, strerror(errno)); - return -1; - } - return 0; -} - - -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) { - struct bootloader_message boot; - memset(&boot, 0, sizeof(boot)); - get_bootloader_message(&boot); // this may fail, leaving a zeroed structure - - if (boot.command[0] != 0 && boot.command[0] != 255) { - LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command); - } - - if (boot.status[0] != 0 && boot.status[0] != 255) { - LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status); - } - - // --- if arguments weren't supplied, look in the bootloader control block - if (*argc <= 1) { - boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination - const char *arg = strtok(boot.recovery, "\n"); - if (arg != NULL && !strcmp(arg, "recovery")) { - *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); - (*argv)[0] = strdup(arg); - for (*argc = 1; *argc < MAX_ARGS; ++*argc) { - if ((arg = strtok(NULL, "\n")) == NULL) break; - (*argv)[*argc] = strdup(arg); - } - LOGI("Got arguments from boot message\n"); - } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) { - LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery); - } - } - - // --- if that doesn't work, try the command file - if (*argc <= 1) { - 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; - } - } - - fflush(fp); - if (ferror(fp)) LOGE("Error in %s\n(%s)\n", COMMAND_FILE, strerror(errno)); - fclose(fp); - LOGI("Got arguments from %s\n", COMMAND_FILE); - } - } - - // --> write the arguments we have back into the bootloader control block - // always boot into recovery after this (until finish_recovery() is called) - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - int i; - for (i = 1; i < *argc; ++i) { - strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery)); - strlcat(boot.recovery, "\n", sizeof(boot.recovery)); - } - set_bootloader_message(&boot); -} diff --git a/bootloader.h b/bootloader.h index b9d70edaaf..9c84a1cf9b 100644 --- a/bootloader.h +++ b/bootloader.h @@ -14,61 +14,5 @@ * limitations under the License. */ -#ifndef _RECOVERY_BOOTLOADER_H -#define _RECOVERY_BOOTLOADER_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* Bootloader Message - * - * 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 is written by the bootloader after the - * completion of an "update-radio" or "update-hboot" command. - * - * 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. - */ -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]; - char reserved[224]; -}; - -/* Read and write the bootloader command from the "misc" partition. - * These return zero on success. - */ -int get_bootloader_message(struct bootloader_message *out); -int set_bootloader_message(const struct bootloader_message *in); - -void set_misc_device(const char* type, const char* name); -void get_args(int *argc, char ***argv); - -#ifdef __cplusplus -} -#endif - -#endif +// TODO: Remove this file once we remove all places that include this file. +#include "bootloader_message/include/bootloader_message/bootloader_message.h" 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 new file mode 100644 index 0000000000..0d84713c32 --- /dev/null +++ b/bootloader_message/Android.mk @@ -0,0 +1,29 @@ +# 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 +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_STATIC_LIBRARY) diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp new file mode 100644 index 0000000000..6c237e620f --- /dev/null +++ b/bootloader_message/bootloader_message.cpp @@ -0,0 +1,261 @@ +/* + * 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 + +#ifdef USE_OLD_BOOTLOADER_MESSAGE +#include + +static struct fstab* read_fstab(std::string* err) { + // The fstab path is always "/fstab.${ro.hardware}". + std::string fstab_path = "/fstab."; + char value[PROP_VALUE_MAX]; + if (__system_property_get("ro.hardware", value) == 0) { + *err = "failed to get ro.hardware"; + return nullptr; + } + fstab_path += value; + struct fstab* fstab = fs_mgr_read_fstab(fstab_path.c_str()); + if (fstab == nullptr) { + *err = "failed to read " + fstab_path; + } + return fstab; +} +#endif + +static std::string get_misc_blk_device(std::string* err) { +#ifdef USE_OLD_BOOTLOADER_MESSAGE + struct fstab* fstab = read_fstab(err); +#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; +} + +// 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) { + *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 += 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; + } + 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 = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + 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; + } + return true; +} + +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 = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + 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; + } + if (fsync(fd) == -1) { + *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + 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) { + 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) { + 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) { + 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.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, 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(), misc_blk_device, + WIPE_PACKAGE_OFFSET_IN_MISC, err); +} + +extern "C" bool write_reboot_bootloader(void) { + std::string err; + return write_reboot_bootloader(&err); +} + +extern "C" bool write_bootloader_message(const char* options) { + std::string err; + return write_bootloader_message({options}, &err); +} diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h new file mode 100644 index 0000000000..4da1171cdd --- /dev/null +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -0,0 +1,243 @@ +/* + * 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_H +#define _BOOTLOADER_MESSAGE_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. +static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; +static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; + +/* 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]; + +#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 + // stage string (for multistage packages) and possible future + // expansion. + 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); + +// 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_H 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 new file mode 100644 index 0000000000..693b675186 --- /dev/null +++ b/crypto/ext4crypt/Android.mk @@ -0,0 +1,73 @@ +LOCAL_PATH := $(call my-dir) +ifeq ($(TW_INCLUDE_CRYPTO), true) +include $(CLEAR_VARS) + +LOCAL_MODULE := libe4crypt +LOCAL_MODULE_TAGS := eng optional +LOCAL_CFLAGS := +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 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) + + + +include $(CLEAR_VARS) +LOCAL_MODULE := twrpfbe +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := main.cpp +LOCAL_SHARED_LIBRARIES := libe4crypt +#LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker64 + +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_MODULE := e4policyget +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := e4policyget.cpp +LOCAL_SHARED_LIBRARIES := libe4crypt +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 new file mode 100644 index 0000000000..c062f8ae48 --- /dev/null +++ b/crypto/ext4crypt/Decrypt.cpp @@ -0,0 +1,1282 @@ +/* + * Copyright (C) 2016 The 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. + */ + +#include "Decrypt.h" +#include "Ext4Crypt.h" + +#include +#include + +#include +#include +#include +#include +#include + +#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 + +// Store main DE raw ref / policy +extern std::string de_raw_ref; +extern std::map s_de_key_raw_refs; +extern std::map s_ce_key_raw_refs; + +static bool lookup_ref_key_internal(std::map& key_map, const char* policy, userid_t* user_id) { + for (std::map::iterator it=key_map.begin(); it!=key_map.end(); ++it) { + if (strncmp(it->second.c_str(), policy, it->second.size()) == 0) { + *user_id = it->first; + return true; + } + } + return false; +} + +extern "C" bool lookup_ref_key(const char* policy, char* policy_type) { + userid_t user_id = 0; + if (strncmp(de_raw_ref.c_str(), policy, de_raw_ref.size()) == 0) { + strcpy(policy_type, "1DK"); + return true; + } + if (!lookup_ref_key_internal(s_de_key_raw_refs, policy, &user_id)) { + if (!lookup_ref_key_internal(s_ce_key_raw_refs, policy, &user_id)) { + return false; + } else + sprintf(policy_type, "1CE%d", user_id); + } else + sprintf(policy_type, "1DE%d", user_id); + return true; +} + +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[0]); + return false; + } + const char* ptr = policy_type + 1; // skip past the version number + if (strncmp(ptr, "DK", 2) == 0) { + strncpy(policy, de_raw_ref.data(), de_raw_ref.size()); + return true; + } + userid_t user_id = atoi(ptr + 2); + std::string raw_ref; + if (*ptr == 'D') { + if (lookup_key_ref(s_de_key_raw_refs, user_id, &raw_ref)) { + strncpy(policy, raw_ref.data(), raw_ref.size()); + } else + return false; + } else if (*ptr == 'C') { + if (lookup_key_ref(s_ce_key_raw_refs, user_id, &raw_ref)) { + strncpy(policy, raw_ref.data(), raw_ref.size()); + } else + return false; + } else { + printf("unknown policy type '%s'\n", policy_type); + return false; + } + return true; +} + +#ifndef HAVE_GATEKEEPER1 +int gatekeeper_device_initialize(gatekeeper_device_t **dev) { + int ret; + const hw_module_t *mod; + ret = hw_get_module_by_class(GATEKEEPER_HARDWARE_MODULE_ID, NULL, &mod); + + if (ret!=0) { + printf("failed to get hw module\n"); + return ret; + } + + ret = gatekeeper_open(mod, dev); + + if (ret!=0) + 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/"; + } else { + char user_id_str[5]; + sprintf(user_id_str, "%i", user_id); + path = "/data/system/users/"; + path += user_id_str; + path += "/"; + } + filename = path + "gatekeeper.password.key"; + if (stat(filename.c_str(), &st) == 0 && st.st_size > 0) + return 1; + filename = path + "gatekeeper.pattern.key"; + if (stat(filename.c_str(), &st) == 0 && st.st_size > 0) + return 2; + printf("Unable to locate gatekeeper password file '%s'\n", filename.c_str()); + filename = ""; + return 0; +} + +bool Decrypt_User(const userid_t user_id, const std::string& Password) { + uint8_t *auth_token; + uint32_t auth_token_len; + int ret; + + struct stat st; + if (user_id > 9999) { + printf("user_id is too big\n"); + return false; + } + std::string filename; + bool Default_Password = (Password == "!"); + if (Get_Password_Type(user_id, filename) == 0 && !Default_Password) { + printf("Unknown password type\n"); + return false; + } + int flags = FLAG_STORAGE_DE; + if (user_id == 0) + flags = FLAG_STORAGE_DE; + else + flags = FLAG_STORAGE_CE; + if (Default_Password) { + if (!e4crypt_unlock_user_key(user_id, 0, "!", "!")) { + printf("e4crypt_unlock_user_key returned fail\n"); + return false; + } + if (!e4crypt_prepare_user_storage(nullptr, user_id, 0, flags)) { + printf("failed to e4crypt_prepare_user_storage\n"); + return false; + } + printf("Decrypted Successfully!\n"); + return true; + } + 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)); + return false; + } + std::string handle; + if (!android::base::ReadFileToString(filename, &handle)) { + printf("Failed to read '%s'\n", filename.c_str()); + return false; + } + bool should_reenroll; +#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; + for (i=0;i +#include + +#include + +#include + +__BEGIN_DECLS + +// NOTE: keep in sync with StorageManager +static constexpr int FLAG_STORAGE_DE = 1 << 0; +static constexpr int FLAG_STORAGE_CE = 1 << 1; + +int Get_Password_Type(const userid_t user_id, std::string& filename); +bool Decrypt_DE(); +bool Decrypt_User(const userid_t user_id, const std::string& Password); + +__END_DECLS diff --git a/crypto/ext4crypt/Ext4Crypt.cpp b/crypto/ext4crypt/Ext4Crypt.cpp new file mode 100644 index 0000000000..ea5b1cf885 --- /dev/null +++ b/crypto/ext4crypt/Ext4Crypt.cpp @@ -0,0 +1,533 @@ +/* + * 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 "Ext4Crypt.h" +#include "Decrypt.h" + +#include "KeyStorage.h" +#include "Utils.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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" + +#define EMULATED_USES_SELINUX 0 +#define MANAGE_MISC_DIRS 0 + +#include + +#include +//#include +#include + +#define LOG(x) std::cout +#define PLOG(x) std::cout +#define DATA_MNT_POINT "/data" + +using android::base::StringPrintf; +using android::vold::kEmptyAuthentication; + +// NOTE: keep in sync with StorageManager +//static constexpr int FLAG_STORAGE_DE = 1 << 0; // moved to Decrypt.h +//static constexpr int FLAG_STORAGE_CE = 1 << 1; + +// Store main DE raw ref / policy +std::string de_raw_ref; +// Map user ids to key references +std::map s_de_key_raw_refs; +std::map s_ce_key_raw_refs; + +namespace { +const std::string device_key_dir = std::string() + DATA_MNT_POINT + e4crypt_unencrypted_folder; +const std::string device_key_path = device_key_dir + "/key"; +const std::string device_key_temp = device_key_dir + "/temp"; + +const std::string user_key_dir = std::string() + DATA_MNT_POINT + "/misc/vold/user_keys"; +const std::string user_key_temp = user_key_dir + "/temp"; + +bool s_global_de_initialized = false; + +// Some users are ephemeral, don't try to wipe their keys from disk +std::set s_ephemeral_users; + +// TODO abolish this map. Keys should not be long-lived in user memory, only kernel memory. +// See b/26948053 +std::map s_ce_keys; + +// ext4enc:TODO get this const from somewhere good +const int EXT4_KEY_DESCRIPTOR_SIZE = 8; + +// ext4enc:TODO Include structure from somewhere sensible +// MUST be in sync with ext4_crypto.c in kernel +constexpr int EXT4_ENCRYPTION_MODE_AES_256_XTS = 1; +constexpr int EXT4_AES_256_XTS_KEY_SIZE = 64; +constexpr int EXT4_MAX_KEY_SIZE = 64; +struct ext4_encryption_key { + uint32_t mode; + char raw[EXT4_MAX_KEY_SIZE]; + uint32_t size; +}; +} + +static bool e4crypt_is_emulated() { + return false; //property_get_bool("persist.sys.emulate_fbe", false); +} + +static const char* escape_null(const char* value) { + return (value == nullptr) ? "null" : value; +} + +// Get raw keyref - used to make keyname and to pass to ioctl +static std::string generate_key_ref(const char* key, int length) { + SHA512_CTX c; + + SHA512_Init(&c); + SHA512_Update(&c, key, length); + unsigned char key_ref1[SHA512_DIGEST_LENGTH]; + SHA512_Final(key_ref1, &c); + + SHA512_Init(&c); + SHA512_Update(&c, key_ref1, SHA512_DIGEST_LENGTH); + unsigned char key_ref2[SHA512_DIGEST_LENGTH]; + SHA512_Final(key_ref2, &c); + + static_assert(EXT4_KEY_DESCRIPTOR_SIZE <= SHA512_DIGEST_LENGTH, + "Hash too short for descriptor"); + return std::string((char*)key_ref2, EXT4_KEY_DESCRIPTOR_SIZE); +} + +static bool fill_key(const std::string& key, ext4_encryption_key* ext4_key) { + if (key.size() != EXT4_AES_256_XTS_KEY_SIZE) { + LOG(ERROR) << "Wrong size key " << key.size(); + return false; + } + static_assert(EXT4_AES_256_XTS_KEY_SIZE <= sizeof(ext4_key->raw), "Key too long!"); + ext4_key->mode = EXT4_ENCRYPTION_MODE_AES_256_XTS; + ext4_key->size = key.size(); + memset(ext4_key->raw, 0, sizeof(ext4_key->raw)); + memcpy(ext4_key->raw, key.data(), key.size()); + return true; +} + +static std::string keyname(const std::string& raw_ref) { + std::ostringstream o; + o << "ext4:"; + for (auto i : raw_ref) { + o << std::hex << std::setw(2) << std::setfill('0') << (int)i; + } + LOG(INFO) << "keyname is " << o.str() << "\n"; + return o.str(); +} + +// Get the keyring we store all keys in +static bool e4crypt_keyring(key_serial_t* device_keyring) { + *device_keyring = keyctl_search(KEY_SPEC_SESSION_KEYRING, "keyring", "e4crypt", 0); + if (*device_keyring == -1) { + PLOG(ERROR) << "Unable to find device keyring\n"; + return false; + } + return true; +} + +// Install password into global keyring +// Return raw key reference for use in policy +static bool install_key(const std::string& key, std::string* raw_ref) { + ext4_encryption_key ext4_key; + if (!fill_key(key, &ext4_key)) return false; + *raw_ref = generate_key_ref(ext4_key.raw, ext4_key.size); + auto ref = keyname(*raw_ref); + key_serial_t device_keyring; + if (!e4crypt_keyring(&device_keyring)) return false; + key_serial_t key_id = + add_key("logon", ref.c_str(), (void*)&ext4_key, sizeof(ext4_key), device_keyring); + if (key_id == -1) { + PLOG(ERROR) << "Failed to insert key into keyring " << device_keyring << "\n"; + return false; + } + LOG(DEBUG) << "Added key " << key_id << " (" << ref << ") to keyring " << device_keyring + << " in process " << getpid() << "\n"; + return true; +} + +static std::string get_de_key_path(userid_t user_id) { +LOG(INFO) << "get_de_key_path " << user_id << " " << StringPrintf("%s/de/%d", user_key_dir.c_str(), user_id) << "\n"; + return StringPrintf("%s/de/%d", user_key_dir.c_str(), user_id); +} + +static std::string get_ce_key_directory_path(userid_t user_id) { +LOG(INFO) << "get_ce_key_directory_path " << user_id << ": " << StringPrintf("%s/ce/%d", user_key_dir.c_str(), user_id) << "\n"; + return StringPrintf("%s/ce/%d", user_key_dir.c_str(), user_id); +} + +// Returns the keys newest first +static std::vector get_ce_key_paths(const std::string& directory_path) { + auto dirp = std::unique_ptr(opendir(directory_path.c_str()), closedir); + if (!dirp) { + PLOG(ERROR) << "Unable to open ce key directory: " + directory_path; + return std::vector(); + } + std::vector result; + for (;;) { + errno = 0; + auto const entry = readdir(dirp.get()); + if (!entry) { + if (errno) { + PLOG(ERROR) << "Unable to read ce key directory: " + directory_path; + return std::vector(); + } + break; + } + if (entry->d_type != DT_DIR || entry->d_name[0] != 'c') { + LOG(DEBUG) << "Skipping non-key " << entry->d_name; + continue; + } + result.emplace_back(directory_path + "/" + entry->d_name); + LOG(INFO) << "get_ce_key_paths adding: " << directory_path + "/" + entry->d_name << "\n"; + } + std::sort(result.begin(), result.end()); + std::reverse(result.begin(), result.end()); + return result; +} + +static std::string get_ce_key_current_path(const std::string& directory_path) { +LOG(INFO) << "get_ce_key_current_path: " << directory_path + "/current\n"; + return directory_path + "/current"; +} + +// Discard all keys but the named one; rename it to canonical name. +// No point in acting on errors in this; ignore them. +static void fixate_user_ce_key(const std::string& directory_path, const std::string &to_fix, + const std::vector& paths) { + for (auto const other_path: paths) { + if (other_path != to_fix) { + android::vold::destroyKey(other_path); + } + } + auto const current_path = get_ce_key_current_path(directory_path); + if (to_fix != current_path) { + LOG(DEBUG) << "Renaming " << to_fix << " to " << current_path; + if (rename(to_fix.c_str(), current_path.c_str()) != 0) { + PLOG(WARNING) << "Unable to rename " << to_fix << " to " << current_path; + } + } +} + +static bool read_and_fixate_user_ce_key(userid_t user_id, + const android::vold::KeyAuthentication& auth, + std::string *ce_key) { + auto const directory_path = get_ce_key_directory_path(user_id); + auto const paths = get_ce_key_paths(directory_path); + for (auto const ce_key_path: paths) { + LOG(DEBUG) << "Trying user CE key " << ce_key_path; + if (android::vold::retrieveKey(ce_key_path, auth, ce_key)) { + LOG(DEBUG) << "Successfully retrieved key"; + fixate_user_ce_key(directory_path, ce_key_path, paths); + return true; + } + } + LOG(ERROR) << "Failed to find working ce key for user " << user_id; + return false; +} + +static bool read_and_install_user_ce_key(userid_t user_id, + const android::vold::KeyAuthentication& auth) { + if (s_ce_key_raw_refs.count(user_id) != 0) return true; + std::string ce_key; + if (!read_and_fixate_user_ce_key(user_id, auth, &ce_key)) return false; + std::string ce_raw_ref; + if (!install_key(ce_key, &ce_raw_ref)) return false; + s_ce_keys[user_id] = ce_key; + s_ce_key_raw_refs[user_id] = ce_raw_ref; + LOG(DEBUG) << "Installed ce key for user " << user_id; + return true; +} + +static bool prepare_dir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid) { + LOG(DEBUG) << "Preparing: " << dir << "\n"; + return true; + return access(dir.c_str(), F_OK) == 0; // we don't want recovery creating directories or changing permissions at this point, so we will just return true if the path already exists + if (fs_prepare_dir(dir.c_str(), mode, uid, gid) != 0) { + PLOG(ERROR) << "Failed to prepare " << dir; + return false; + } + return true; +} + +static bool path_exists(const std::string& path) { + return access(path.c_str(), F_OK) == 0; +} + +bool lookup_key_ref(const std::map& key_map, userid_t user_id, + std::string* raw_ref) { + auto refi = key_map.find(user_id); + if (refi == key_map.end()) { + LOG(ERROR) << "Cannot find key for " << user_id; + return false; + } + *raw_ref = refi->second; + return true; +} + +static bool ensure_policy(const std::string& raw_ref, const std::string& path) { + LOG(INFO) << "ensure_policy '" << path << "'\n"; + return true; + return access(path.c_str(), F_OK) == 0; // ensure policy will set a policy if one is not set on an empty folder - we don't want to do this in recovery + /*if (e4crypt_policy_ensure(path.c_str(), raw_ref.data(), raw_ref.size()) != 0) { + LOG(ERROR) << "Failed to set policy on: " << path << "\n"; + return false; + } + return true;*/ +} + +static bool is_numeric(const char* name) { + for (const char* p = name; *p != '\0'; p++) { + if (!isdigit(*p)) return false; + } + return true; +} + +static bool load_all_de_keys() { + auto de_dir = user_key_dir + "/de"; + auto dirp = std::unique_ptr(opendir(de_dir.c_str()), closedir); + if (!dirp) { + PLOG(ERROR) << "Unable to read de key directory"; + return false; + } + for (;;) { + errno = 0; + auto entry = readdir(dirp.get()); + if (!entry) { + if (errno) { + PLOG(ERROR) << "Unable to read de key directory"; + return false; + } + break; + } + if (entry->d_type != DT_DIR || !is_numeric(entry->d_name)) { + LOG(DEBUG) << "Skipping non-de-key " << entry->d_name; + continue; + } + userid_t user_id = atoi(entry->d_name); + if (s_de_key_raw_refs.count(user_id) == 0) { + auto key_path = de_dir + "/" + entry->d_name; + std::string key; + if (!android::vold::retrieveKey(key_path, kEmptyAuthentication, &key)) return false; + std::string raw_ref; + if (!install_key(key, &raw_ref)) return false; + s_de_key_raw_refs[user_id] = raw_ref; + LOG(DEBUG) << "Installed de key for user " << user_id; + } + } + // ext4enc:TODO: go through all DE directories, ensure that all user dirs have the + // correct policy set on them, and that no rogue ones exist. + return true; +} + +bool e4crypt_initialize_global_de() { + + if (s_global_de_initialized) { + LOG(INFO) << "Already initialized\n"; + return true; + } + + std::string device_key; + if (path_exists(device_key_path)) { + if (!android::vold::retrieveKey(device_key_path, + kEmptyAuthentication, &device_key)) return false; + } else { + LOG(INFO) << "NOT Creating new key\n"; + return false; + } + + std::string device_key_ref; + if (!install_key(device_key, &device_key_ref)) { + LOG(ERROR) << "Failed to install device key\n"; + return false; + } + + s_global_de_initialized = true; + de_raw_ref = device_key_ref; + return true; +} + +bool e4crypt_init_user0() { + if (e4crypt_is_native()) { + if (!prepare_dir(user_key_dir, 0700, AID_ROOT, AID_ROOT)) return false; + if (!prepare_dir(user_key_dir + "/ce", 0700, AID_ROOT, AID_ROOT)) return false; + if (!prepare_dir(user_key_dir + "/de", 0700, AID_ROOT, AID_ROOT)) return false; + if (!path_exists(get_de_key_path(0))) { + //if (!create_and_install_user_keys(0, false)) return false; + printf("de key path not found\n"); + return false; + } + // TODO: switch to loading only DE_0 here once framework makes + // explicit calls to install DE keys for secondary users + if (!load_all_de_keys()) return false; + } + // We can only safely prepare DE storage here, since CE keys are probably + // entangled with user credentials. The framework will always prepare CE + // storage once CE keys are installed. + if (!e4crypt_prepare_user_storage(nullptr, 0, 0, FLAG_STORAGE_DE)) { + LOG(ERROR) << "Failed to prepare user 0 storage"; + return false; + } + + // If this is a non-FBE device that recently left an emulated mode, + // restore user data directories to known-good state. + if (!e4crypt_is_native() && !e4crypt_is_emulated()) { + e4crypt_unlock_user_key(0, 0, "!", "!"); + } + + return true; +} + +static bool parse_hex(const char* hex, std::string* result) { + if (strcmp("!", hex) == 0) { + *result = ""; + return true; + } + if (android::vold::HexToStr(hex, *result) != 0) { + LOG(ERROR) << "Invalid FBE hex string"; // Don't log the string for security reasons + return false; + } + return true; +} + +// TODO: rename to 'install' for consistency, and take flags to know which keys to install +bool e4crypt_unlock_user_key(userid_t user_id, int serial, const char* token_hex, + const char* secret_hex) { + if (e4crypt_is_native()) { + if (s_ce_key_raw_refs.count(user_id) != 0) { + LOG(WARNING) << "Tried to unlock already-unlocked key for user " << user_id; + return true; + } + std::string token, secret; + if (!parse_hex(token_hex, &token)) return false; + if (!parse_hex(secret_hex, &secret)) return false; + android::vold::KeyAuthentication auth(token, secret); + if (!read_and_install_user_ce_key(user_id, auth)) { + LOG(ERROR) << "Couldn't read key for " << user_id; + return false; + } + } else { + printf("Emulation mode not supported in TWRP\n"); + // When in emulation mode, we just use chmod. However, we also + // unlock directories when not in emulation mode, to bring devices + // back into a known-good state. + /*if (!emulated_unlock(android::vold::BuildDataSystemCePath(user_id), 0771) || + !emulated_unlock(android::vold::BuildDataMiscCePath(user_id), 01771) || + !emulated_unlock(android::vold::BuildDataMediaCePath(nullptr, user_id), 0770) || + !emulated_unlock(android::vold::BuildDataUserCePath(nullptr, user_id), 0771)) { + LOG(ERROR) << "Failed to unlock user " << user_id; + return false; + }*/ + } + return true; +} + +bool e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id, int serial, + int flags) { + + if (flags & FLAG_STORAGE_DE) { + // DE_sys key + auto system_legacy_path = android::vold::BuildDataSystemLegacyPath(user_id); + auto misc_legacy_path = android::vold::BuildDataMiscLegacyPath(user_id); + auto profiles_de_path = android::vold::BuildDataProfilesDePath(user_id); + auto foreign_de_path = android::vold::BuildDataProfilesForeignDexDePath(user_id); + + // DE_n key + auto system_de_path = android::vold::BuildDataSystemDePath(user_id); + auto misc_de_path = android::vold::BuildDataMiscDePath(user_id); + auto user_de_path = android::vold::BuildDataUserDePath(volume_uuid, user_id); + + if (!prepare_dir(system_legacy_path, 0700, AID_SYSTEM, AID_SYSTEM)) return false; +#if MANAGE_MISC_DIRS + if (!prepare_dir(misc_legacy_path, 0750, multiuser_get_uid(user_id, AID_SYSTEM), + multiuser_get_uid(user_id, AID_EVERYBODY))) return false; +#endif + if (!prepare_dir(profiles_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false; + if (!prepare_dir(foreign_de_path, 0773, AID_SYSTEM, AID_SYSTEM)) return false; + + if (!prepare_dir(system_de_path, 0770, AID_SYSTEM, AID_SYSTEM)) return false; + if (!prepare_dir(misc_de_path, 01771, AID_SYSTEM, AID_MISC)) return false; + if (!prepare_dir(user_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false; + + // For now, FBE is only supported on internal storage + if (e4crypt_is_native() && volume_uuid == nullptr) { + std::string de_raw_ref; + if (!lookup_key_ref(s_de_key_raw_refs, user_id, &de_raw_ref)) return false; + if (!ensure_policy(de_raw_ref, system_de_path)) return false; + if (!ensure_policy(de_raw_ref, misc_de_path)) return false; + if (!ensure_policy(de_raw_ref, user_de_path)) return false; + } + } + + if (flags & FLAG_STORAGE_CE) { + // CE_n key + auto system_ce_path = android::vold::BuildDataSystemCePath(user_id); + auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id); + auto media_ce_path = android::vold::BuildDataMediaCePath(volume_uuid, user_id); + auto user_ce_path = android::vold::BuildDataUserCePath(volume_uuid, user_id); + + if (!prepare_dir(system_ce_path, 0770, AID_SYSTEM, AID_SYSTEM)) return false; + if (!prepare_dir(misc_ce_path, 01771, AID_SYSTEM, AID_MISC)) return false; + if (!prepare_dir(media_ce_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW)) return false; + if (!prepare_dir(user_ce_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false; + + // For now, FBE is only supported on internal storage + if (e4crypt_is_native() && volume_uuid == nullptr) { + std::string ce_raw_ref; + if (!lookup_key_ref(s_ce_key_raw_refs, user_id, &ce_raw_ref)) return false; + if (!ensure_policy(ce_raw_ref, system_ce_path)) return false; + if (!ensure_policy(ce_raw_ref, misc_ce_path)) return false; + if (!ensure_policy(ce_raw_ref, media_ce_path)) return false; + if (!ensure_policy(ce_raw_ref, user_ce_path)) return false; + } + } + + return true; +} diff --git a/crypto/ext4crypt/Ext4Crypt.h b/crypto/ext4crypt/Ext4Crypt.h new file mode 100644 index 0000000000..57623e35c2 --- /dev/null +++ b/crypto/ext4crypt/Ext4Crypt.h @@ -0,0 +1,47 @@ +/* + * 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 + +__BEGIN_DECLS + +// General functions +bool e4crypt_is_native(); +bool e4crypt_initialize_global_de(); + +bool e4crypt_init_user0(); +//bool e4crypt_vold_create_user_key(userid_t user_id, int serial, bool ephemeral); +//bool e4crypt_destroy_user_key(userid_t user_id); +//bool e4crypt_add_user_key_auth(userid_t user_id, int serial, const char* token, +// const char* secret); +//bool e4crypt_fixate_newest_user_key_auth(userid_t user_id); + +bool e4crypt_unlock_user_key(userid_t user_id, int serial, const char* token, const char* secret); +//bool e4crypt_lock_user_key(userid_t user_id); + +bool e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id, int serial, int flags); +//bool e4crypt_destroy_user_storage(const char* volume_uuid, userid_t user_id, int flags); + +bool lookup_key_ref(const std::map& key_map, userid_t user_id, + std::string* raw_ref); + +__END_DECLS diff --git a/crypto/ext4crypt/HashPassword.cpp b/crypto/ext4crypt/HashPassword.cpp new file mode 100644 index 0000000000..817c984bdf --- /dev/null +++ b/crypto/ext4crypt/HashPassword.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 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. + */ + +/* + * This computes the "secret" used by Android as one of the parameters + * to decrypt File Based Encryption. The secret is prefixed with + * "Android FBE credential hash" padded with 0s to 128 bytes then the + * user's password is appended to the end of the 128 bytes. This string + * is then hashed with sha512 and the sha512 value is then converted to + * hex with upper-case characters. + */ + +#include +#include +#include +#include + +#include "HashPassword.h" + +#define PASS_PADDING_SIZE 128 +#define SHA512_HEX_SIZE SHA512_DIGEST_LENGTH * 2 + +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); + if (!buffer) return ""; // 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); + int index = 0; + char hex_hash[SHA512_HEX_SIZE + 1]; + for(index = 0; index < SHA512_DIGEST_LENGTH; index++) + 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 new file mode 100644 index 0000000000..4be107b510 --- /dev/null +++ b/crypto/ext4crypt/HashPassword.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef __HASH_PASSWORD_H +#define __HASH_PASSWORD_H + +#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/KeyStorage.cpp b/crypto/ext4crypt/KeyStorage.cpp new file mode 100644 index 0000000000..199520e9db --- /dev/null +++ b/crypto/ext4crypt/KeyStorage.cpp @@ -0,0 +1,349 @@ +/* + * 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 "KeyStorage.h" + +#include "Keymaster.h" +#include "ScryptParameters.h" +#include "Utils.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +//#include + +#include + +#include + +#include + +extern "C" { + +#include "crypto_scrypt.h" +} + +#define ERROR 1 +#define LOG(x) std::cout +#define PLOG(x) std::cout + +namespace android { +namespace vold { + +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* kFn_encrypted_key = "encrypted_key"; +static const char* kFn_keymaster_key_blob = "keymaster_key_blob"; +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 hashSecdiscardable(const std::string& secdiscardable) { + 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 secdiscardableHashingPrefix = "Android secdiscardable SHA512"; + secdiscardableHashingPrefix.resize(SHA512_CBLOCK); + SHA512_Update(&c, secdiscardableHashingPrefix.data(), secdiscardableHashingPrefix.size()); + SHA512_Update(&c, secdiscardable.data(), secdiscardable.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 = keymaster::AuthorizationSetBuilder() + .AesEncryptionKey(AES_KEY_BYTES * 8) + .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_GCM) + .Authorization(keymaster::TAG_MIN_MAC_LENGTH, GCM_MAC_BYTES * 8) + .Authorization(keymaster::TAG_PADDING, KM_PAD_NONE); + addStringParam(¶mBuilder, keymaster::TAG_APPLICATION_ID, appId); + if (auth.token.empty()) { + LOG(DEBUG) << "Creating key that doesn't need auth token"; + paramBuilder.Authorization(keymaster::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(keymaster::TAG_USER_SECURE_ID, at->user_id); + paramBuilder.Authorization(keymaster::TAG_USER_AUTH_TYPE, HW_AUTH_PASSWORD); + paramBuilder.Authorization(keymaster::TAG_AUTH_TIMEOUT, AUTH_TIMEOUT); + } + return keymaster.generateKey(paramBuilder.build(), key); +}*/ + +static keymaster::AuthorizationSetBuilder beginParams(const KeyAuthentication& auth, + const std::string& appId) { + auto paramBuilder = keymaster::AuthorizationSetBuilder() + .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_GCM) + .Authorization(keymaster::TAG_MAC_LENGTH, GCM_MAC_BYTES * 8) + .Authorization(keymaster::TAG_PADDING, KM_PAD_NONE); + addStringParam(¶mBuilder, keymaster::TAG_APPLICATION_ID, appId); + if (!auth.token.empty()) { + LOG(DEBUG) << "Supplying auth token to Keymaster"; + addStringParam(¶mBuilder, keymaster::TAG_AUTH_TOKEN, auth.token); + } + return paramBuilder; +} + +/*static bool encryptWithKeymasterKey(Keymaster& keymaster, const std::string& key, + const KeyAuthentication& auth, const std::string& appId, + const std::string& message, std::string* ciphertext) { + auto params = beginParams(auth, appId).build(); + keymaster::AuthorizationSet outParams; + auto opHandle = keymaster.begin(KM_PURPOSE_ENCRYPT, key, params, &outParams); + if (!opHandle) return false; + keymaster_blob_t nonceBlob; + if (!outParams.GetTagValue(keymaster::TAG_NONCE, &nonceBlob)) { + 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.data), nonceBlob.data_length); + 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.finishWithOutput(&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& key, + const KeyAuthentication& auth, const std::string& appId, + const std::string& ciphertext, std::string* message) { + auto nonce = ciphertext.substr(0, GCM_NONCE_BYTES); + auto bodyAndMac = ciphertext.substr(GCM_NONCE_BYTES); + auto params = addStringParam(beginParams(auth, appId), keymaster::TAG_NONCE, nonce).build(); + auto opHandle = keymaster.begin(KM_PURPOSE_DECRYPT, key, params); + if (!opHandle) return false; + if (!opHandle.updateCompletely(bodyAndMac, message)) return false; + if (!opHandle.finish()) return false; + return true; +} + +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 std::string getStretching() { + 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 = hashSecdiscardable(secdiscardable) + stretched; + 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 (ReadRandomBytes(SECDISCARDABLE_BYTES, secdiscardable) != OK) { + // TODO status_t plays badly with PLOG, fix it. + LOG(ERROR) << "Random read failed"; + return false; + } + if (!writeStringToFile(secdiscardable, dir + "/" + kFn_secdiscardable)) return false; + std::string stretching = auth.secret.empty() ? kStretch_nopassword : getStretching(); + 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; + 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; + std::string encryptedKey; + if (!encryptWithKeymasterKey(keymaster, kmKey, auth, 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 kmKey; + if (!readFileToString(dir + "/" + kFn_keymaster_key_blob, &kmKey)) return false; + std::string encryptedMessage; + if (!readFileToString(dir + "/" + kFn_encrypted_key, &encryptedMessage)) return false; + Keymaster keymaster; + if (!keymaster) return false; + return decryptWithKeymasterKey(keymaster, kmKey, auth, appId, encryptedMessage, key); +} + +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 secdiscardSecdiscardable(const std::string& dir) { + if (ForkExecvp( + std::vector{kSecdiscardPath, "--", dir + "/" + kFn_secdiscardable}) != 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 &= secdiscardSecdiscardable(dir); + success &= recursiveDeleteKey(dir); + return success; +} + +} // namespace vold +} // namespace android diff --git a/crypto/ext4crypt/KeyStorage.h b/crypto/ext4crypt/KeyStorage.h new file mode 100644 index 0000000000..63d38da25f --- /dev/null +++ b/crypto/ext4crypt/KeyStorage.h @@ -0,0 +1,53 @@ +/* + * 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 "secret" is nonempty, it is appended to the application-specific +// binary needed to unlock. +class KeyAuthentication { + public: + KeyAuthentication(std::string t, std::string s) : token{t}, secret{s} {}; + 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); + +} // namespace vold +} // namespace android + +#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.cpp b/crypto/ext4crypt/Keymaster.cpp new file mode 100644 index 0000000000..3c21aa26d6 --- /dev/null +++ b/crypto/ext4crypt/Keymaster.cpp @@ -0,0 +1,254 @@ +/* + * 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 "Keymaster.h" + +//#include +#include +#include +#include + +#include +#define ERROR 1 +#define LOG(x) std::cout + +namespace android { +namespace vold { + +class IKeymasterDevice { + public: + IKeymasterDevice() {} + virtual ~IKeymasterDevice() {} + /*virtual keymaster_error_t generate_key(const keymaster_key_param_set_t* params, + keymaster_key_blob_t* key_blob) const = 0;*/ + virtual keymaster_error_t delete_key(const keymaster_key_blob_t* key) const = 0; + virtual keymaster_error_t begin(keymaster_purpose_t purpose, const keymaster_key_blob_t* key, + const keymaster_key_param_set_t* in_params, + keymaster_key_param_set_t* out_params, + keymaster_operation_handle_t* operation_handle) const = 0; + virtual keymaster_error_t update(keymaster_operation_handle_t operation_handle, + const keymaster_key_param_set_t* in_params, + const keymaster_blob_t* input, size_t* input_consumed, + keymaster_key_param_set_t* out_params, + keymaster_blob_t* output) const = 0; + virtual keymaster_error_t finish(keymaster_operation_handle_t operation_handle, + const keymaster_key_param_set_t* in_params, + const keymaster_blob_t* signature, + keymaster_key_param_set_t* out_params, + keymaster_blob_t* output) const = 0; + virtual keymaster_error_t abort(keymaster_operation_handle_t operation_handle) const = 0; + + protected: + DISALLOW_COPY_AND_ASSIGN(IKeymasterDevice); +}; + +template class KeymasterDevice : public IKeymasterDevice { + public: + KeymasterDevice(T* d) : mDevice{d} {} + /*keymaster_error_t generate_key(const keymaster_key_param_set_t* params, + keymaster_key_blob_t* key_blob) const override final { + return mDevice->generate_key(mDevice, params, key_blob, nullptr); + }*/ + keymaster_error_t delete_key(const keymaster_key_blob_t* key) const override final { + if (mDevice->delete_key == nullptr) return KM_ERROR_OK; + return mDevice->delete_key(mDevice, key); + } + keymaster_error_t begin(keymaster_purpose_t purpose, const keymaster_key_blob_t* key, + const keymaster_key_param_set_t* in_params, + keymaster_key_param_set_t* out_params, + keymaster_operation_handle_t* operation_handle) const override final { + return mDevice->begin(mDevice, purpose, key, in_params, out_params, operation_handle); + } + keymaster_error_t update(keymaster_operation_handle_t operation_handle, + const keymaster_key_param_set_t* in_params, + const keymaster_blob_t* input, size_t* input_consumed, + keymaster_key_param_set_t* out_params, + keymaster_blob_t* output) const override final { + return mDevice->update(mDevice, operation_handle, in_params, input, input_consumed, + out_params, output); + } + keymaster_error_t abort(keymaster_operation_handle_t operation_handle) const override final { + return mDevice->abort(mDevice, operation_handle); + } + + protected: + T* const mDevice; +}; + +class Keymaster1Device : public KeymasterDevice { + public: + Keymaster1Device(keymaster1_device_t* d) : KeymasterDevice{d} {} + ~Keymaster1Device() override final { keymaster1_close(mDevice); } + keymaster_error_t finish(keymaster_operation_handle_t operation_handle, + const keymaster_key_param_set_t* in_params, + const keymaster_blob_t* signature, + keymaster_key_param_set_t* out_params, + keymaster_blob_t* output) const override final { + return mDevice->finish(mDevice, operation_handle, in_params, signature, out_params, output); + } +}; + +class Keymaster2Device : public KeymasterDevice { + public: + Keymaster2Device(keymaster2_device_t* d) : KeymasterDevice{d} {} + ~Keymaster2Device() override final { keymaster2_close(mDevice); } + keymaster_error_t finish(keymaster_operation_handle_t operation_handle, + const keymaster_key_param_set_t* in_params, + const keymaster_blob_t* signature, + keymaster_key_param_set_t* out_params, + keymaster_blob_t* output) const override final { + return mDevice->finish(mDevice, operation_handle, in_params, nullptr, signature, out_params, + output); + } +}; + +KeymasterOperation::~KeymasterOperation() { + if (mDevice) mDevice->abort(mOpHandle); +} + +bool KeymasterOperation::updateCompletely(const std::string& input, std::string* output) { + output->clear(); + auto it = input.begin(); + while (it != input.end()) { + size_t toRead = static_cast(input.end() - it); + keymaster_blob_t inputBlob{reinterpret_cast(&*it), toRead}; + keymaster_blob_t outputBlob; + size_t inputConsumed; + auto error = + mDevice->update(mOpHandle, nullptr, &inputBlob, &inputConsumed, nullptr, &outputBlob); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "update failed, code " << error; + mDevice = nullptr; + return false; + } + output->append(reinterpret_cast(outputBlob.data), outputBlob.data_length); + free(const_cast(outputBlob.data)); + if (inputConsumed > toRead) { + LOG(ERROR) << "update reported too much input consumed"; + mDevice = nullptr; + return false; + } + it += inputConsumed; + } + return true; +} + +bool KeymasterOperation::finish() { + auto error = mDevice->finish(mOpHandle, nullptr, nullptr, nullptr, nullptr); + mDevice = nullptr; + if (error != KM_ERROR_OK) { + LOG(ERROR) << "finish failed, code " << error; + return false; + } + return true; +} + +bool KeymasterOperation::finishWithOutput(std::string* output) { + keymaster_blob_t outputBlob; + auto error = mDevice->finish(mOpHandle, nullptr, nullptr, nullptr, &outputBlob); + mDevice = nullptr; + if (error != KM_ERROR_OK) { + LOG(ERROR) << "finish failed, code " << error; + return false; + } + output->assign(reinterpret_cast(outputBlob.data), outputBlob.data_length); + free(const_cast(outputBlob.data)); + return true; +} + +Keymaster::Keymaster() { + mDevice = nullptr; + const hw_module_t* module; + int ret = hw_get_module_by_class(KEYSTORE_HARDWARE_MODULE_ID, NULL, &module); + if (ret != 0) { + LOG(ERROR) << "hw_get_module_by_class returned " << ret; + return; + } + if (module->module_api_version == KEYMASTER_MODULE_API_VERSION_1_0) { + keymaster1_device_t* device; + ret = keymaster1_open(module, &device); + if (ret != 0) { + LOG(ERROR) << "keymaster1_open returned " << ret; + return; + } + mDevice = std::make_shared(device); + } else if (module->module_api_version == KEYMASTER_MODULE_API_VERSION_2_0) { + keymaster2_device_t* device; + ret = keymaster2_open(module, &device); + if (ret != 0) { + LOG(ERROR) << "keymaster2_open returned " << ret; + return; + } + mDevice = std::make_shared(device); + } else { + LOG(ERROR) << "module_api_version is " << module->module_api_version; + return; + } +} + +/*bool Keymaster::generateKey(const keymaster::AuthorizationSet& inParams, std::string* key) { + keymaster_key_blob_t keyBlob; + auto error = mDevice->generate_key(&inParams, &keyBlob); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "generate_key failed, code " << error; + return false; + } + key->assign(reinterpret_cast(keyBlob.key_material), keyBlob.key_material_size); + free(const_cast(keyBlob.key_material)); + return true; +}*/ + +bool Keymaster::deleteKey(const std::string& key) { + keymaster_key_blob_t keyBlob{reinterpret_cast(key.data()), key.size()}; + auto error = mDevice->delete_key(&keyBlob); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "delete_key failed, code " << error; + return false; + } + return true; +} + +KeymasterOperation Keymaster::begin(keymaster_purpose_t purpose, const std::string& key, + const keymaster::AuthorizationSet& inParams, + keymaster::AuthorizationSet* outParams) { + keymaster_key_blob_t keyBlob{reinterpret_cast(key.data()), key.size()}; + keymaster_operation_handle_t mOpHandle; + keymaster_key_param_set_t outParams_set; + auto error = mDevice->begin(purpose, &keyBlob, &inParams, &outParams_set, &mOpHandle); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "begin failed, code " << error; + return KeymasterOperation(nullptr, mOpHandle); + } + outParams->Clear(); + outParams->push_back(outParams_set); + keymaster_free_param_set(&outParams_set); + return KeymasterOperation(mDevice, mOpHandle); +} + +KeymasterOperation Keymaster::begin(keymaster_purpose_t purpose, const std::string& key, + const keymaster::AuthorizationSet& inParams) { + keymaster_key_blob_t keyBlob{reinterpret_cast(key.data()), key.size()}; + keymaster_operation_handle_t mOpHandle; + auto error = mDevice->begin(purpose, &keyBlob, &inParams, nullptr, &mOpHandle); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "begin failed, code " << error; + return KeymasterOperation(nullptr, mOpHandle); + } + return KeymasterOperation(mDevice, mOpHandle); +} + +} // namespace vold +} // namespace android diff --git a/crypto/ext4crypt/Keymaster.h b/crypto/ext4crypt/Keymaster.h new file mode 100644 index 0000000000..bd3f219851 --- /dev/null +++ b/crypto/ext4crypt/Keymaster.h @@ -0,0 +1,111 @@ +/* + * 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 + +#include +#include +#include + +#include +#include "Utils.h" + +namespace android { +namespace vold { + +using namespace keymaster; + +// C++ wrappers to the Keymaster C interface. +// This is tailored to the needs of KeyStorage, but could be extended to be +// a more general interface. + +// Class that wraps a keymaster1_device_t or keymaster2_device_t and provides methods +// they have in common. Also closes the device on destruction. +class IKeymasterDevice; + +// Wrapper for a keymaster_operation_handle_t 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 mDevice != nullptr; } + // 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; pass nullptr for the "output" param. + bool finish(); + // Finish and write the output to this string. + bool finishWithOutput(std::string* output); + // Move constructor + KeymasterOperation(KeymasterOperation&& rhs) { + mOpHandle = std::move(rhs.mOpHandle); + mDevice = std::move(rhs.mDevice); + } + + private: + KeymasterOperation(std::shared_ptr d, keymaster_operation_handle_t h) + : mDevice{d}, mOpHandle{h} {} + std::shared_ptr mDevice; + keymaster_operation_handle_t mOpHandle; + 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 != 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); + // Begin a new cryptographic operation, collecting output parameters. + KeymasterOperation begin(keymaster_purpose_t purpose, const std::string& key, + const AuthorizationSet& inParams, AuthorizationSet* outParams); + // Begin a new cryptographic operation; don't collect output parameters. + KeymasterOperation begin(keymaster_purpose_t purpose, const std::string& key, + const AuthorizationSet& inParams); + + private: + std::shared_ptr mDevice; + DISALLOW_COPY_AND_ASSIGN(Keymaster); +}; + +template +inline AuthorizationSetBuilder& addStringParam(AuthorizationSetBuilder&& params, + TypedTag tag, + const std::string& val) { + return params.Authorization(tag, val.data(), val.size()); +} + +template +inline void addStringParam(AuthorizationSetBuilder* params, TypedTag tag, + const std::string& val) { + params->Authorization(tag, val.data(), val.size()); +} + +} // namespace vold +} // namespace android + +#endif 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/ScryptParameters.cpp b/crypto/ext4crypt/ScryptParameters.cpp new file mode 100644 index 0000000000..669809b9ff --- /dev/null +++ b/crypto/ext4crypt/ScryptParameters.cpp @@ -0,0 +1,50 @@ +/* + * 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 "ScryptParameters.h" + +#include +#include + +bool parse_scrypt_parameters(const char* paramstr, int *Nf, int *rf, int *pf) { + int params[3]; + char *token; + char *saveptr; + int i; + + /* + * The token we're looking for should be three integers separated by + * colons (e.g., "12:8:1"). Scan the property to make sure it matches. + */ + for (i = 0, token = strtok_r(const_cast(paramstr), ":", &saveptr); + token != nullptr && i < 3; + i++, token = strtok_r(nullptr, ":", &saveptr)) { + char *endptr; + params[i] = strtol(token, &endptr, 10); + + /* + * Check that there was a valid number and it's 8-bit. + */ + if ((*token == '\0') || (*endptr != '\0') || params[i] < 0 || params[i] > 255) { + return false; + } + } + if (token != nullptr) { + return false; + } + *Nf = params[0]; *rf = params[1]; *pf = params[2]; + return true; +} diff --git a/applypatch/utils.h b/crypto/ext4crypt/ScryptParameters.h similarity index 59% rename from applypatch/utils.h rename to crypto/ext4crypt/ScryptParameters.h index bc97f1720f..1b43ea5741 100644 --- a/applypatch/utils.h +++ b/crypto/ext4crypt/ScryptParameters.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,19 @@ * limitations under the License. */ -#ifndef _BUILD_TOOLS_APPLYPATCH_UTILS_H -#define _BUILD_TOOLS_APPLYPATCH_UTILS_H +#ifndef ANDROID_VOLD_SCRYPT_PARAMETERS_H +#define ANDROID_VOLD_SCRYPT_PARAMETERS_H -#include +#include +#include -// Read and write little-endian values of various sizes. +#define SCRYPT_PROP "ro.crypto.scrypt_params" +#define SCRYPT_DEFAULTS "15:3:1" -void Write4(int value, FILE* f); -void Write8(long long value, FILE* f); -int Read2(void* p); -int Read4(void* p); -long long Read8(void* p); +__BEGIN_DECLS -#endif // _BUILD_TOOLS_APPLYPATCH_UTILS_H +bool parse_scrypt_parameters(const char* paramstr, int *Nf, int *rf, int *pf); + +__END_DECLS + +#endif diff --git a/crypto/ext4crypt/Utils.cpp b/crypto/ext4crypt/Utils.cpp new file mode 100644 index 0000000000..f0bf029b95 --- /dev/null +++ b/crypto/ext4crypt/Utils.cpp @@ -0,0 +1,297 @@ +/* + * 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 "Utils.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using android::base::ReadFileToString; +using android::base::StringPrintf; + +namespace android { +namespace vold { + +static const char* kKeyPath = "/data/misc/vold"; + +status_t ForkExecvp(const std::vector& args) { + return ForkExecvp(args, nullptr); +} + +status_t ForkExecvp(const std::vector& args, security_context_t context) { + size_t argc = args.size(); + char** argv = (char**) calloc(argc, sizeof(char*)); + for (size_t i = 0; i < argc; i++) { + argv[i] = (char*) args[i].c_str(); + if (i == 0) { + LOG(VERBOSE) << args[i]; + } else { + LOG(VERBOSE) << " " << args[i]; + } + } + + if (setexeccon(context)) { + LOG(ERROR) << "Failed to setexeccon"; + abort(); + } + abort(); + status_t res = 1;//android_fork_execvp(argc, argv, NULL, false, true); + if (setexeccon(nullptr)) { + LOG(ERROR) << "Failed to setexeccon"; + abort(); + } + + free(argv); + return res; +} + +status_t ForkExecvp(const std::vector& args, + std::vector& output) { + return ForkExecvp(args, output, nullptr); +} + +status_t ForkExecvp(const std::vector& args, + std::vector& output, security_context_t context) { + std::string cmd; + for (size_t i = 0; i < args.size(); i++) { + cmd += args[i] + " "; + if (i == 0) { + LOG(VERBOSE) << args[i]; + } else { + LOG(VERBOSE) << " " << args[i]; + } + } + output.clear(); + + if (setexeccon(context)) { + LOG(ERROR) << "Failed to setexeccon"; + abort(); + } + FILE* fp = popen(cmd.c_str(), "r"); + if (setexeccon(nullptr)) { + LOG(ERROR) << "Failed to setexeccon"; + abort(); + } + + if (!fp) { + PLOG(ERROR) << "Failed to popen " << cmd; + return -errno; + } + char line[1024]; + while (fgets(line, sizeof(line), fp) != nullptr) { + LOG(VERBOSE) << line; + output.push_back(std::string(line)); + } + if (pclose(fp) != 0) { + PLOG(ERROR) << "Failed to pclose " << cmd; + return -errno; + } + + return OK; +} + +pid_t ForkExecvpAsync(const std::vector& args) { + size_t argc = args.size(); + char** argv = (char**) calloc(argc + 1, sizeof(char*)); + for (size_t i = 0; i < argc; i++) { + argv[i] = (char*) args[i].c_str(); + if (i == 0) { + LOG(VERBOSE) << args[i]; + } else { + LOG(VERBOSE) << " " << args[i]; + } + } + + pid_t pid = fork(); + if (pid == 0) { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + if (execvp(argv[0], argv)) { + PLOG(ERROR) << "Failed to exec"; + } + + _exit(1); + } + + if (pid == -1) { + PLOG(ERROR) << "Failed to exec"; + } + + free(argv); + return pid; +} + +status_t ReadRandomBytes(size_t bytes, std::string& out) { + out.clear(); + + int fd = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); + if (fd == -1) { + return -errno; + } + + char buf[BUFSIZ]; + size_t n; + while ((n = TEMP_FAILURE_RETRY(read(fd, &buf[0], std::min(sizeof(buf), bytes)))) > 0) { + out.append(buf, n); + bytes -= n; + } + close(fd); + + if (bytes == 0) { + return OK; + } else { + return -EIO; + } +} + +status_t HexToStr(const std::string& hex, std::string& str) { + str.clear(); + bool even = true; + char cur = 0; + for (size_t i = 0; i < hex.size(); i++) { + int val = 0; + switch (hex[i]) { + case ' ': case '-': case ':': continue; + case 'f': case 'F': val = 15; break; + case 'e': case 'E': val = 14; break; + case 'd': case 'D': val = 13; break; + case 'c': case 'C': val = 12; break; + case 'b': case 'B': val = 11; break; + case 'a': case 'A': val = 10; break; + case '9': val = 9; break; + case '8': val = 8; break; + case '7': val = 7; break; + case '6': val = 6; break; + case '5': val = 5; break; + case '4': val = 4; break; + case '3': val = 3; break; + case '2': val = 2; break; + case '1': val = 1; break; + case '0': val = 0; break; + default: return -EINVAL; + } + + if (even) { + cur = val << 4; + } else { + cur += val; + str.push_back(cur); + cur = 0; + } + even = !even; + } + return even ? OK : -EINVAL; +} + +static bool isValidFilename(const std::string& name) { + if (name.empty() || (name == ".") || (name == "..") + || (name.find('/') != std::string::npos)) { + return false; + } else { + return true; + } +} + +std::string BuildKeyPath(const std::string& partGuid) { + return StringPrintf("%s/expand_%s.key", kKeyPath, partGuid.c_str()); +} + +std::string BuildDataSystemLegacyPath(userid_t userId) { + return StringPrintf("%s/system/users/%u", BuildDataPath(nullptr).c_str(), userId); +} + +std::string BuildDataSystemCePath(userid_t userId) { + return StringPrintf("%s/system_ce/%u", BuildDataPath(nullptr).c_str(), userId); +} + +std::string BuildDataSystemDePath(userid_t userId) { + return StringPrintf("%s/system_de/%u", BuildDataPath(nullptr).c_str(), userId); +} + +std::string BuildDataMiscLegacyPath(userid_t userId) { + return StringPrintf("%s/misc/user/%u", BuildDataPath(nullptr).c_str(), userId); +} + +std::string BuildDataMiscCePath(userid_t userId) { + return StringPrintf("%s/misc_ce/%u", BuildDataPath(nullptr).c_str(), userId); +} + +std::string BuildDataMiscDePath(userid_t userId) { + return StringPrintf("%s/misc_de/%u", BuildDataPath(nullptr).c_str(), userId); +} + +// Keep in sync with installd (frameworks/native/cmds/installd/utils.h) +std::string BuildDataProfilesDePath(userid_t userId) { + return StringPrintf("%s/misc/profiles/cur/%u", BuildDataPath(nullptr).c_str(), userId); +} + +std::string BuildDataProfilesForeignDexDePath(userid_t userId) { + std::string profiles_path = BuildDataProfilesDePath(userId); + return StringPrintf("%s/foreign-dex", profiles_path.c_str()); +} + +std::string BuildDataPath(const char* volumeUuid) { + // TODO: unify with installd path generation logic + if (volumeUuid == nullptr) { + return "/data"; + } else { + CHECK(isValidFilename(volumeUuid)); + return StringPrintf("/mnt/expand/%s", volumeUuid); + } +} + +std::string BuildDataMediaCePath(const char* volumeUuid, userid_t userId) { + // TODO: unify with installd path generation logic + std::string data(BuildDataPath(volumeUuid)); + return StringPrintf("%s/media/%u", data.c_str(), userId); +} + +std::string BuildDataUserCePath(const char* volumeUuid, userid_t userId) { + // TODO: unify with installd path generation logic + std::string data(BuildDataPath(volumeUuid)); + if (volumeUuid == nullptr) { + if (userId == 0) { + return StringPrintf("%s/data", data.c_str()); + } else { + return StringPrintf("%s/user/%u", data.c_str(), userId); + } + } else { + return StringPrintf("%s/user/%u", data.c_str(), userId); + } +} + +std::string BuildDataUserDePath(const char* volumeUuid, userid_t userId) { + // TODO: unify with installd path generation logic + std::string data(BuildDataPath(volumeUuid)); + return StringPrintf("%s/user_de/%u", data.c_str(), userId); +} + +} // namespace vold +} // namespace android diff --git a/crypto/ext4crypt/Utils.h b/crypto/ext4crypt/Utils.h new file mode 100644 index 0000000000..aede203410 --- /dev/null +++ b/crypto/ext4crypt/Utils.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TWRP_VOLD_UTILS_H +#define TWRP_VOLD_UTILS_H + +#include +#include +#include + +#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 { + +/* Returns either WEXITSTATUS() status, or a negative errno */ +status_t ForkExecvp(const std::vector& args); +status_t ForkExecvp(const std::vector& args, security_context_t context); + +status_t ForkExecvp(const std::vector& args, + std::vector& output); +status_t ForkExecvp(const std::vector& args, + std::vector& output, security_context_t context); + +pid_t ForkExecvpAsync(const std::vector& args); + +status_t ReadRandomBytes(size_t bytes, std::string& out); + +/* Converts hex string to raw bytes, ignoring [ :-] */ +status_t HexToStr(const std::string& hex, std::string& str); + +std::string BuildKeyPath(const std::string& partGuid); + +std::string BuildDataSystemLegacyPath(userid_t userid); +std::string BuildDataSystemCePath(userid_t userid); +std::string BuildDataSystemDePath(userid_t userid); +std::string BuildDataMiscLegacyPath(userid_t userid); +std::string BuildDataMiscCePath(userid_t userid); +std::string BuildDataMiscDePath(userid_t userid); +std::string BuildDataProfilesDePath(userid_t userid); +std::string BuildDataProfilesForeignDexDePath(userid_t userid); + +std::string BuildDataPath(const char* volumeUuid); +std::string BuildDataMediaCePath(const char* volumeUuid, userid_t userid); +std::string BuildDataUserCePath(const char* volumeUuid, userid_t userid); +std::string BuildDataUserDePath(const char* volumeUuid, userid_t userid); + +} // namespace vold +} // namespace android + +#endif 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 new file mode 100644 index 0000000000..05de86fe70 --- /dev/null +++ b/crypto/ext4crypt/e4policyget.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 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. + */ + +#include +#include +#include +#include "ext4crypt_tar.h" + +#define EXT4_KEY_DESCRIPTOR_SIZE 8 +#define EXT4_KEY_DESCRIPTOR_SIZE_HEX 17 + +int main(int argc, char *argv[]) { + bool ret = false; + if (argc != 2) { + printf("Must specify a path\n"); + return -1; + } else { + 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(eep.master_key_descriptor, policy_hex); + printf("%s\n", policy_hex); + } else { + printf("No policy set\n"); + } + } + return 0; +} diff --git a/crypto/ext4crypt/ext4_crypt.cpp b/crypto/ext4crypt/ext4_crypt.cpp new file mode 100644 index 0000000000..5a3b4b20c1 --- /dev/null +++ b/crypto/ext4crypt/ext4_crypt.cpp @@ -0,0 +1,207 @@ +/* + * 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. + */ + +/* TWRP NOTE: Kanged from system/extras/ext4_utils/ext4_crypt.cpp + * because policy_to_hex, e4crypt_policy_set, and e4crypt_policy_get + * are not exposed to be used. There was also a bug in e4crypt_policy_get + * that may or may not be fixed in the user's local repo: + * https://android.googlesource.com/platform/system/extras/+/30b93dd5715abcabd621235733733c0503f9c552 + */ + +#include "ext4_crypt.h" +#include "ext4crypt_tar.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy" +#define EXT4_KEYREF_DELIMITER ((char)'.') + +#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; + +#define HEX_LOOKUP "0123456789abcdef" + +extern "C" void policy_to_hex(const char* policy, char* hex) { + for (size_t i = 0, j = 0; i < EXT4_KEY_DESCRIPTOR_SIZE; i++) { + hex[j++] = HEX_LOOKUP[(policy[i] & 0xF0) >> 4]; + hex[j++] = HEX_LOOKUP[policy[i] & 0x0F]; + } + hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX - 1] = '\0'; +} + +extern "C" bool e4crypt_policy_set(const char *directory, const char *policy, + size_t policy_length, int contents_encryption_mode) { + if (contents_encryption_mode == 0) + contents_encryption_mode = encryption_mode; + if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) { + printf("policy wrong length\n"); + LOG(ERROR) << "Policy wrong length: " << policy_length; + return false; + } + 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; + } + + ext4_encryption_policy eep; + eep.version = 0; + eep.contents_encryption_mode = contents_encryption_mode; + eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS; + eep.flags = 0; + memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE); + if (ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep)) { + printf("failed to set policy for '%s' '%s'\n", directory, policy); + PLOG(ERROR) << "Failed to set encryption policy for " << directory; + close(fd); + return false; + } + close(fd); + + char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX]; + policy_to_hex(policy, policy_hex); + LOG(INFO) << "Policy for " << directory << " set to " << policy_hex; + return true; +} + +extern "C" bool e4crypt_policy_get(const char *directory, char *policy, + size_t policy_length, int contents_encryption_mode) { + if (contents_encryption_mode == 0) + contents_encryption_mode = encryption_mode; + if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) { + LOG(ERROR) << "Policy wrong length: " << policy_length; + return false; + } + + int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (fd == -1) { + PLOG(ERROR) << "Failed to open directory " << directory; + return false; + } + + ext4_encryption_policy eep; + 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); + + if ((eep.version != 0) + || (eep.contents_encryption_mode != contents_encryption_mode) + || (eep.filenames_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_CTS) + || (eep.flags != 0)) { + LOG(ERROR) << "Failed to find matching encryption policy for " << directory; + return false; + } + memcpy(policy, eep.master_key_descriptor, EXT4_KEY_DESCRIPTOR_SIZE); + + 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; + if (stat(mode_file, &st) != 0 || st.st_size <= 0) { + printf("Invalid encryption mode file %s\n", mode_file); + return false; + } + size_t mode_size = st.st_size; + char contents_encryption_mode[mode_size + 1]; + memset((void*)contents_encryption_mode, 0, mode_size + 1); + int fd = open(mode_file, O_RDONLY); + if (fd < 0) { + printf("error opening '%s': %s\n", mode_file, strerror(errno)); + return false; + } + if (read(fd, contents_encryption_mode, mode_size) != mode_size) { + printf("read error on '%s': %s\n", mode_file, strerror(errno)); + close(fd); + return false; + } + close(fd); + if (!strcmp(contents_encryption_mode, "software")) { + encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS; + } else if (!strcmp(contents_encryption_mode, "ice")) { + encryption_mode = EXT4_ENCRYPTION_MODE_PRIVATE; + } else { + printf("Invalid encryption mode '%s'\n", contents_encryption_mode); + return false; + } + printf("set encryption mode to %i\n", encryption_mode); + return true; +} diff --git a/crypto/ext4crypt/ext4crypt_tar.h b/crypto/ext4crypt/ext4crypt_tar.h new file mode 100644 index 0000000000..c35d115711 --- /dev/null +++ b/crypto/ext4crypt/ext4crypt_tar.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef __EXT4CRYPT_TAR_H +#define __EXT4CRYPT_TAR_H + +#include +#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); + +void policy_to_hex(const char* policy, char* hex); +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 + +#endif 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/ext4crypt/main.cpp b/crypto/ext4crypt/main.cpp new file mode 100644 index 0000000000..f0266ae106 --- /dev/null +++ b/crypto/ext4crypt/main.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 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. + */ + +#include +#include +#include +#include "Decrypt.h" + +int main(int argc, char *argv[]) { + bool ret = false; + if (argc < 2) { + Decrypt_DE(); + ret = Decrypt_User(0, "0000"); + } else if (argc < 3) { + Decrypt_DE(); + ret = Decrypt_User(0, argv[1]); + } else { + ret = Decrypt_User(atoi(argv[1]), argv[2]); + } + if (!ret) + printf("Failed to decrypt\n"); + return 0; +} diff --git a/crypto/lollipop/cryptfs.c b/crypto/lollipop/cryptfs.c index 0c7848d8f4..80f433bf6d 100644 --- a/crypto/lollipop/cryptfs.c +++ b/crypto/lollipop/cryptfs.c @@ -713,13 +713,13 @@ static void get_device_scrypt_params(struct crypt_mnt_ftr *ftr) { static unsigned int get_blkdev_size(int fd) { - unsigned int nr_sec; + unsigned long nr_sec; if ( (ioctl(fd, BLKGETSIZE, &nr_sec)) == -1) { nr_sec = 0; } - return nr_sec; + return (unsigned int) nr_sec; } static int get_crypt_ftr_info(char **metadata_fname, off64_t *off) @@ -1099,6 +1099,13 @@ static int create_crypto_blk_dev(struct crypt_mnt_ftr *crypt_ftr, const unsigned fd, extra_params); if (load_count < 0) { printf("Cannot load dm-crypt mapping table.\n"); + + // Remove the dm-crypt device, otherwise it cannot be used later on by other + // processes (eg vold_decrypt) or further testing/debugging in recovery + ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0); + if (ioctl(fd, DM_DEV_REMOVE, io)) { + printf("Cannot remove dm-crypt device %i\n", errno); + } goto errout; } else if (load_count > 1) { printf("Took %d tries to load dmcrypt table.\n", load_count); @@ -1109,6 +1116,13 @@ static int create_crypto_blk_dev(struct crypt_mnt_ftr *crypt_ftr, const unsigned if (ioctl(fd, DM_DEV_SUSPEND, io)) { printf("Cannot resume the dm-crypt device\n"); + + // Remove the dm-crypt device, otherwise it cannot be used later on by other + // processes (eg vold_decrypt) or further testing/debugging in recovery + ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0); + if (ioctl(fd, DM_DEV_REMOVE, io)) { + printf("Cannot remove dm-crypt device %i\n", errno); + } goto errout; } @@ -1328,6 +1342,21 @@ static int decrypt_master_key(char *passwd, unsigned char *decrypted_master_key, return ret; } +static int try_mount_multiple_fs(const char *crypto_blkdev, + const char *mount_point, + const char *file_system) +{ + if (!mount(crypto_blkdev, mount_point, file_system, 0, NULL)) + return 0; + if (strcmp(file_system, "ext4") && + !mount(crypto_blkdev, mount_point, "ext4", 0, NULL)) + return 0; + if (strcmp(file_system, "f2fs") && + !mount(crypto_blkdev, mount_point, "f2fs", 0, NULL)) + return 0; + return 1; +} + static int test_mount_encrypted_fs(struct crypt_mnt_ftr* crypt_ftr, char *passwd, char *mount_point, char *label) { @@ -1426,7 +1455,7 @@ static int test_mount_encrypted_fs(struct crypt_mnt_ftr* crypt_ftr, * the footer, not the key. */ sprintf(tmp_mount_point, "%s/tmp_mnt", mount_point); mkdir(tmp_mount_point, 0755); - if (mount(crypto_blkdev, tmp_mount_point, file_system, 0, NULL) != 0) { + if (try_mount_multiple_fs(crypto_blkdev, tmp_mount_point, file_system)) { printf("Error temp mounting decrypted block device '%s'\n", crypto_blkdev); delete_crypto_blk_dev(label); rc = 1; diff --git a/crypto/vold_decrypt/Android.mk b/crypto/vold_decrypt/Android.mk new file mode 100644 index 0000000000..ddfad03271 --- /dev/null +++ b/crypto/vold_decrypt/Android.mk @@ -0,0 +1,99 @@ +# Copyright (C) 2017 TeamWin 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. + +LOCAL_PATH := $(call my-dir) + +ifeq ($(TW_INCLUDE_CRYPTO), true) + ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),) + + + # Parse TW_CRYPTO_USE_SYSTEM_VOLD + ifeq ($(TW_CRYPTO_USE_SYSTEM_VOLD),true) + # Just enabled, so only vold + vdc + services := + else + # Additional services needed by vold + services := $(TW_CRYPTO_USE_SYSTEM_VOLD) + endif + + # List of .rc files for each additional service + rc_files := $(foreach item,$(services),init.recovery.vold_decrypt.$(item).rc) + + + include $(CLEAR_VARS) + LOCAL_MODULE := init.recovery.vold_decrypt.rc + LOCAL_MODULE_TAGS := eng + LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES + + # Cannot send to TARGET_RECOVERY_ROOT_OUT since build system wipes init*.rc + # during ramdisk creation and only allows init.recovery.*.rc files to be copied + # from TARGET_ROOT_OUT thereafter + LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) + + LOCAL_SRC_FILES := $(LOCAL_MODULE) + + # Add additional .rc files and imports into init.recovery.vold_decrypt.rc + # Note: any init.recovery.vold_decrypt.{service}.rc that are not default + # in crypto/vold_decrypt should be in the device tree + LOCAL_POST_INSTALL_CMD := $(hide) \ + $(foreach item, $(rc_files), \ + sed -i '1iimport \/$(item)' "$(TARGET_ROOT_OUT)/$(LOCAL_MODULE)"; \ + if [ -f "$(LOCAL_PATH)/$(item)" ]; then \ + 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) + + + include $(CLEAR_VARS) + LOCAL_MODULE := libvolddecrypt + LOCAL_MODULE_TAGS := eng optional + LOCAL_CFLAGS := -Wall + ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) + LOCAL_C_INCLUDES += external/stlport/stlport bionic bionic/libstdc++/include + endif + + ifneq ($(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 + + LOCAL_SRC_FILES = vold_decrypt.cpp + LOCAL_SHARED_LIBRARIES := libcutils + include $(BUILD_STATIC_LIBRARY) + + 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 new file mode 100755 index 0000000000..08666085d4 --- /dev/null +++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.qseecomd.rc @@ -0,0 +1,26 @@ +on fs + # needed to make qseecomd work in recovery + chmod 0660 /dev/qseecom + chown system drmrpc /dev/qseecom + 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 + setenv PATH /system/bin + 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/init.recovery.vold_decrypt.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.rc new file mode 100755 index 0000000000..65983eb257 --- /dev/null +++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.rc @@ -0,0 +1,10 @@ + +service sys_vold /system/bin/vold \ + --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \ + --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0 + socket vold stream 0660 root mount + socket cryptd stream 0660 root mount + setenv PATH /system/bin + setenv LD_LIBRARY_PATH /system/lib64:/system/lib + disabled + oneshot diff --git a/crypto/vold_decrypt/vold_decrypt.cpp b/crypto/vold_decrypt/vold_decrypt.cpp new file mode 100644 index 0000000000..f6e6e34823 --- /dev/null +++ b/crypto/vold_decrypt/vold_decrypt.cpp @@ -0,0 +1,1126 @@ +/* + Copyright 2017 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef _USING_SHORT_SERVICE_NAMES +#include +#endif + +#include "../../partitions.hpp" +#include "../../twrp-functions.hpp" + +using namespace std; + +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, + * in micro-seconds (really an approximation) */ +#define SLEEP_MAX_USEC 2000000 /* 2 seconds */ +/* The minimal sleeping interval between checking for the service's state + * when looping for SLEEP_MAX_USEC */ +#define SLEEP_MIN_USEC 200000 /* 200 msec */ + + +/* vold response codes defined in ResponseCode.h */ +// 200 series - Requested action has been successfully completed +#define COMMAND_OKAY 200 +#define PASSWORD_TYPE_RESULT 213 + + +#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; + + +/* 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; + +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: + LOGKMSG("forking strace_init failed: %d (%s)!\n", errno, strerror(errno)); + return; + case 0: // child + 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: + LOGKMSG("Starting strace_init (pid=%d)\n", pid); + pid_strace = pid; + return; + } +} + +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(const string& master_key) { + size_t i; + unsigned char nibble; + string master_key_ascii = ""; + + for (i = 0; i < master_key.size(); ++i) { + nibble = (master_key[i] >> 4) & 0xf; + nibble += nibble > 9 ? 0x57 : 0x30; + master_key_ascii += nibble; + + nibble = master_key[i] & 0xf; + nibble += nibble > 9 ? 0x57 : 0x30; + master_key_ascii += nibble; + } + + return master_key_ascii; +} + +/* 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") { + while (utimeout > 0) { + property_get(property_name.c_str(), prop_value, "error"); + if (strcmp(prop_value, "error") != 0) + break; + LOGKMSG("waiting for %s to get set\n", property_name.c_str()); + utimeout -= SLEEP_MIN_USEC; + usleep(SLEEP_MIN_USEC);; + } + } + else { + while (utimeout > 0) { + property_get(property_name.c_str(), prop_value, "error"); + if (strcmp(prop_value, expected_value.c_str()) == 0) + break; + 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);; + } + } + property_get(property_name.c_str(), prop_value, "error"); + + return prop_value; +} + +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 prop_value; +} + +bool Service_Exists(const string& initrc_svc) { + return (Get_Service_State(initrc_svc) != "error"); +} + +bool Is_Service_Running(const string& initrc_svc) { + return (Get_Service_State(initrc_svc) == "running"); +} + +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"); + + LOGINFO("Start service %s: %s.\n", initrc_svc.c_str(), res.c_str()); + + return (res == "running"); +} + +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"); + LOGINFO("Stop service %s: %s.\n", initrc_svc.c_str(), res.c_str()); + } + + return (res == "stopped"); +} + + +/* 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; +} + +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; +} + +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 (is_Vendor_Mounted()) { + LOGINFO("vendor partition mounted, skipping /vendor substitution\n"); + } + else if (TWFunc::Path_Exists("/system/vendor")) { + LOGINFO("Symlinking vendor folder...\n"); + if (!TWFunc::Path_Exists("/vendor") || vrename("/vendor", "/vendor-orig") == 0) { + TWFunc::Recursive_Mkdir("/vendor/firmware/keymaster"); + vsymlink("/system/vendor/lib64", "/vendor/lib64"); + vsymlink("/system/vendor/lib", "/vendor/lib"); + vsymlink("/system/vendor/bin", "/vendor/bin"); + is_vendor_symlinked = true; + property_set("vold_decrypt.symlinked_vendor", "1"); + } + } + return is_vendor_symlinked; +} + +void Restore_Vendor_Folder(void) { + property_set("vold_decrypt.symlinked_vendor", "0"); + TWFunc::removeDir("/vendor", false); + vrename("/vendor-orig", "/vendor"); +} + +bool Symlink_Firmware_Folder(void) { + bool is_firmware_symlinked = false; + + 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"); + } + } + return is_firmware_symlinked; +} + +void Restore_Firmware_Folder(void) { + property_set("vold_decrypt.symlinked_firmware", "0"); + TWFunc::removeDir("/firmware", false); + 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; + + LOGINFO("Symlinking firmware files...\n"); + + vector FirmwareFiles; + Find_Firmware_Files("/system", &FirmwareFiles); + + for (size_t i = 0; i < FirmwareFiles.size(); ++i) { + string base_name = TWFunc::Get_Filename(FirmwareFiles[i]); + + if (is_firmware_symlinked) + vsymlink(FirmwareFiles[i], "/firmware/image/" + base_name); + + if (is_vendor_symlinked) { + vsymlink(FirmwareFiles[i], "/vendor/firmware/" + base_name); + vsymlink(FirmwareFiles[i], "/vendor/firmware/keymaster/" + base_name); + } + } + LOGINFO("%d file(s) symlinked.\n", (int)FirmwareFiles.size()); +} + +// 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"); + } + } + 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 +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 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.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.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) { + 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].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].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 + + +/* 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]; + vdcResult->Output.clear(); + + if (pipe(pipe_fd)) { + 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); + close(pipe_fd[0]); + dup2(pipe_fd[1], STDOUT_FILENO); + dup2(pipe_fd[1], STDERR_FILENO); + close(pipe_fd[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; + vdcResult->Output.clear(); + close(pipe_fd[1]); + + // Non-blocking read loop with timeout + int flags = fcntl(pipe_fd[0], F_GETFL, 0); + fcntl(pipe_fd[0], F_SETFL, flags | O_NONBLOCK); + + char buffer[128]; + ssize_t count; + pid_t retpid = waitpid(pid, &status, WNOHANG); + while (true) { + count = read(pipe_fd[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) { + vdcResult->Output.append(buffer, count); + } + + retpid = waitpid(pid, &status, WNOHANG); + if (retpid == 0 && --timeout) + usleep(10000); + else + break; + }; + close(pipe_fd[0]); + if (vdcResult->Output.length() > 0 && vdcResult->Output[vdcResult->Output.length() - 1] == '\n') + vdcResult->Output.erase(vdcResult->Output.length() - 1); + vdcResult->ResponseCode = vdcResult->Sequence = vdcResult->Message = -1; + sscanf(vdcResult->Output.c_str(), "%d %d %d", &vdcResult->ResponseCode, &vdcResult->Sequence, &vdcResult->Message); + + // 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) { + char str_res[4 + sizeof(int) + 1]; + snprintf(str_res, sizeof(str_res), "ret=%d", res); + vdcResult.Output += str_res; + 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 (ret=%d)\n", vdcResult.Output.c_str(), res); + /* + 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 (ret=%d)\n", vdcResult.Output.c_str(), res); + /* + 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 is_vendor_symlinked = false; + bool is_firmware_symlinked = false; + bool is_fstab_symlinked = false; + bool is_vold_running = false; + + if (Password.empty()) { + 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 VD_ERR_UNABLE_TO_MOUNT_SYSTEM; + } else if (!TWFunc::Path_Exists("/system/bin/vold")) { + LOGINFO("ERROR: /system/bin/vold not found, aborting.\n"); + return VD_ERR_MISSING_VOLD; + } else if (!TWFunc::Path_Exists("/system/bin/vdc")) { + LOGINFO("ERROR: /system/bin/vdc not found, aborting.\n"); + return VD_ERR_MISSING_VDC; + } + + fp_kmsg = fopen("/dev/kmsg", "a"); + + LOGINFO("TW_CRYPTO_USE_SYSTEM_VOLD := true\n"); + + // 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 + Strace_init_Start(); +#endif + +#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES + vector Services = Get_List_Of_Additional_Services(); + + // Check if TWRP is running any of the services + for (size_t i = 0; i < Services.size(); ++i) { + if (!Services[i].TWRP_Service_Name.empty() && !Is_Service_Stopped(Services[i].TWRP_Service_Name)) { + Services[i].resume = true; + Stop_Service(Services[i].TWRP_Service_Name); + } else + Services[i].resume = false; + } +#endif + + 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_Properties(); + + // Start services needed for vold decrypt + LOGINFO("Starting services...\n"); +#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES + for (size_t i = 0; i < Services.size(); ++i) { + if (Services[i].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 (Services[i].exists && !Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume) { + // if system_service has died restart the twrp_service + 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); + + if (res != 0) { + LOGINFO("Decryption failed\n"); + } + } else { + LOGINFO("Failed to start vold\n"); + res = VD_ERR_VOLD_FAILED_TO_START; + } + + // Stop services needed for vold decrypt so /system can be unmounted + 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].VOLD_Service_Name) && Services[i].resume) + Stop_Service(Services[i].TWRP_Service_Name); + else if (Services[i].exists) + Stop_Service(Services[i].VOLD_Service_Name); + } +#endif + + if (is_firmware_symlinked) + 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 + LOGINFO("WARNING: system could not be unmounted normally!\n"); + umount2("/system", MNT_DETACH); + } + + 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_Service_Name); + } +#endif + +#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG + Strace_init_Stop(); +#endif + + // Finish up and exit + if (fp_kmsg) { + fflush(fp_kmsg); + fclose(fp_kmsg); + } + + return res; +} + +} // namespace + +/* + * 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 new file mode 100644 index 0000000000..ba7a747205 --- /dev/null +++ b/crypto/vold_decrypt/vold_decrypt.h @@ -0,0 +1,42 @@ +/* + Copyright 2017 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 . +*/ + +#ifndef _VOLD_DECRYPT_H +#define _VOLD_DECRYPT_H + +#include + +// -_- +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 553c9a4cc5..4dfbde2d0c 100644 --- a/data.cpp +++ b/data.cpp @@ -20,8 +20,10 @@ #include #include #include +#include #include #include +#include #include "variables.h" #include "data.hpp" @@ -89,7 +91,6 @@ void DataManager::sanitize_device_id(char* device_id) { void DataManager::get_device_id(void) { FILE *fp; - size_t i; char line[2048]; char hardware_id[HWID_MAX] = { 0 }; char device_id[DEVID_MAX] = { 0 }; @@ -102,7 +103,7 @@ void DataManager::get_device_id(void) { if (strcmp(model_id, "error") != 0) { LOGINFO("=> product model: '%s'\n", model_id); // Replace spaces with underscores - for (i = 0; i < strlen(model_id); i++) { + for (size_t i = 0; i < strlen(model_id); i++) { if (model_id[i] == ' ') model_id[i] = '_'; } @@ -229,7 +230,7 @@ int DataManager::ResetDefaults() int DataManager::LoadValues(const string& filename) { - string str, dev_id; + string dev_id; if (!mInitialized) SetDefaultValues(); @@ -251,7 +252,7 @@ int DataManager::LoadValues(const string& filename) pthread_mutex_unlock(&m_valuesLock); string current = GetCurrentStoragePath(); TWPartition* Part = PartitionManager.Find_Partition_By_Path(current); - if(!Part) + if (!Part) Part = PartitionManager.Get_Default_Storage_Partition(); if (Part && current != Part->Storage_Path && Part->Mount(false)) { LOGINFO("LoadValues setting storage path to '%s'\n", Part->Storage_Path.c_str()); @@ -262,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(); @@ -270,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; @@ -283,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; } @@ -352,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; @@ -534,7 +582,7 @@ void DataManager::SetDefaultValues() mPersist.SetValue("tw_action_vibrate", "160"); TWPartition *store = PartitionManager.Get_Default_Storage_Partition(); - if(store) + if (store) mPersist.SetValue("tw_storage_path", store->Storage_Path); else mPersist.SetValue("tw_storage_path", "/"); @@ -678,14 +726,14 @@ void DataManager::SetDefaultValues() mPersist.SetValue(TW_INSTALL_REBOOT_VAR, "0"); mPersist.SetValue(TW_SIGNED_ZIP_VERIFY_VAR, "0"); - mPersist.SetValue(TW_FORCE_MD5_CHECK_VAR, "0"); mPersist.SetValue(TW_DISABLE_FREE_SPACE_VAR, "0"); + mPersist.SetValue(TW_FORCE_DIGEST_CHECK_VAR, "0"); mPersist.SetValue(TW_USE_COMPRESSION_VAR, "0"); mPersist.SetValue(TW_TIME_ZONE_VAR, "CST6CDT,M3.2.0,M11.1.0"); mPersist.SetValue(TW_GUI_SORT_ORDER, "1"); mPersist.SetValue(TW_RM_RF_VAR, "0"); - mPersist.SetValue(TW_SKIP_MD5_CHECK_VAR, "0"); - mPersist.SetValue(TW_SKIP_MD5_GENERATE_VAR, "0"); + mPersist.SetValue(TW_SKIP_DIGEST_CHECK_VAR, "0"); + mPersist.SetValue(TW_SKIP_DIGEST_GENERATE_VAR, "0"); mPersist.SetValue(TW_SDEXT_SIZE, "0"); mPersist.SetValue(TW_SWAP_SIZE, "0"); mPersist.SetValue(TW_SDPART_FILE_SYSTEM, "ext3"); @@ -707,6 +755,14 @@ void DataManager::SetDefaultValues() mData.SetValue("tw_background_thread_running", "0"); mData.SetValue(TW_RESTORE_FILE_DATE, "0"); mPersist.SetValue("tw_military_time", "0"); + +#ifdef TW_INCLUDE_CRYPTO + mConst.SetValue(TW_USE_SHA2, "1"); + mConst.SetValue(TW_NO_SHA2, "0"); +#else + mConst.SetValue(TW_NO_SHA2, "1"); +#endif + #ifdef TW_NO_SCREEN_TIMEOUT mConst.SetValue("tw_screen_timeout_secs", "0"); mConst.SetValue("tw_no_screen_timeout", "1"); @@ -751,7 +807,7 @@ void DataManager::SetDefaultValues() string maxbrightpath = findbright.insert(findbright.rfind('/') + 1, "max_"); if (TWFunc::Path_Exists(maxbrightpath)) { ifstream maxVal(maxbrightpath.c_str()); - if(maxVal >> maxBrightness) { + if (maxVal >> maxBrightness) { LOGINFO("Got max brightness %s from '%s'\n", maxBrightness.c_str(), maxbrightpath.c_str()); } else { // Something went wrong, set that to indicate error @@ -818,6 +874,29 @@ void DataManager::SetDefaultValues() mData.SetValue("tw_has_adopted_storage", "0"); +#ifdef AB_OTA_UPDATER + LOGINFO("AB_OTA_UPDATER := true\n"); + mConst.SetValue("tw_has_boot_slots", "1"); +#else + 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"); +#else + mConst.SetValue("tw_oem_build", "0"); + mPersist.SetValue("tw_app_prompt", "1"); + mPersist.SetValue("tw_app_install_system", "1"); + 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); } @@ -854,38 +933,37 @@ int DataManager::GetMagicValue(const string& varName, string& value) } else if (varName == "tw_cpu_temp") { - int tw_no_cpu_temp; - GetValue("tw_no_cpu_temp", tw_no_cpu_temp); - if (tw_no_cpu_temp == 1) return -1; - - string cpu_temp_file; - static unsigned long convert_temp = 0; - static time_t cpuSecCheck = 0; - int divisor = 0; - struct timeval curTime; - string results; - - gettimeofday(&curTime, NULL); - if (curTime.tv_sec > cpuSecCheck) - { + int tw_no_cpu_temp; + GetValue("tw_no_cpu_temp", tw_no_cpu_temp); + if (tw_no_cpu_temp == 1) return -1; + + string cpu_temp_file; + static unsigned long convert_temp = 0; + static time_t cpuSecCheck = 0; + struct timeval curTime; + string results; + + gettimeofday(&curTime, NULL); + if (curTime.tv_sec > cpuSecCheck) + { #ifdef TW_CUSTOM_CPU_TEMP_PATH - cpu_temp_file = EXPAND(TW_CUSTOM_CPU_TEMP_PATH); - if (TWFunc::read_file(cpu_temp_file, results) != 0) - return -1; + cpu_temp_file = EXPAND(TW_CUSTOM_CPU_TEMP_PATH); + if (TWFunc::read_file(cpu_temp_file, results) != 0) + return -1; #else - cpu_temp_file = "/sys/class/thermal/thermal_zone0/temp"; - if (TWFunc::read_file(cpu_temp_file, results) != 0) - return -1; + cpu_temp_file = "/sys/class/thermal/thermal_zone0/temp"; + if (TWFunc::read_file(cpu_temp_file, results) != 0) + return -1; #endif - convert_temp = strtoul(results.c_str(), NULL, 0) / 1000; - if (convert_temp <= 0) - convert_temp = strtoul(results.c_str(), NULL, 0); - if (convert_temp >= 150) - convert_temp = strtoul(results.c_str(), NULL, 0) / 10; - cpuSecCheck = curTime.tv_sec + 5; - } - value = TWFunc::to_string(convert_temp); - return 0; + convert_temp = strtoul(results.c_str(), NULL, 0) / 1000; + if (convert_temp <= 0) + convert_temp = strtoul(results.c_str(), NULL, 0); + if (convert_temp >= 150) + convert_temp = strtoul(results.c_str(), NULL, 0) / 10; + cpuSecCheck = curTime.tv_sec + 5; + } + value = TWFunc::to_string(convert_temp); + return 0; } else if (varName == "tw_battery") { @@ -905,7 +983,7 @@ int DataManager::GetMagicValue(const string& varName, string& value) #else FILE * cap = fopen("/sys/class/power_supply/battery/capacity","rt"); #endif - if (cap){ + if (cap) { fgets(cap_s, 4, cap); fclose(cap); lastVal = atoi(cap_s); @@ -978,7 +1056,7 @@ void DataManager::ReadSettingsFile(void) #ifndef TW_OEM_BUILD // Load up the values for TWRP - Sleep to let the card be ready char mkdir_path[255], settings_file[255]; - int is_enc, has_dual, use_ext, has_data_media, has_ext; + int is_enc, has_data_media; GetValue(TW_IS_ENCRYPTED, is_enc); GetValue(TW_HAS_DATA_MEDIA, has_data_media); 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 fd1a9875b7..61501869ef 100644 --- a/device.cpp +++ b/device.cpp @@ -22,33 +22,45 @@ static const char* MENU_ITEMS[] = { "Apply update from ADB", "Apply update from SD card", "Wipe data/factory reset", +#ifndef AB_OTA_UPDATER "Wipe cache partition", +#endif // !AB_OTA_UPDATER "Mount /system", "View recovery logs", + "Run graphics test", "Power off", - NULL + NULL, }; +static const Device::BuiltinAction MENU_ACTIONS[] = { + Device::REBOOT, + Device::REBOOT_BOOTLOADER, + Device::APPLY_ADB_SIDELOAD, + Device::APPLY_SDCARD, + Device::WIPE_DATA, +#ifndef AB_OTA_UPDATER + Device::WIPE_CACHE, +#endif // !AB_OTA_UPDATER + Device::MOUNT_SYSTEM, + Device::VIEW_RECOVERY_LOGS, + Device::RUN_GRAPHICS_TEST, + Device::SHUTDOWN, +}; + +static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) == + sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1, + "MENU_ITEMS and MENU_ACTIONS should have the same length, " + "except for the extra NULL entry in MENU_ITEMS."); + const char* const* Device::GetMenuItems() { return MENU_ITEMS; } Device::BuiltinAction Device::InvokeMenuItem(int menu_position) { - switch (menu_position) { - case 0: return REBOOT; - case 1: return REBOOT_BOOTLOADER; - case 2: return APPLY_ADB_SIDELOAD; - case 3: return APPLY_SDCARD; - case 4: return WIPE_DATA; - case 5: return WIPE_CACHE; - case 6: return MOUNT_SYSTEM; - case 7: return VIEW_RECOVERY_LOGS; - case 8: return SHUTDOWN; - default: return NO_ACTION; - } + 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 f74b6b0478..639e2bf57c 100644 --- a/device.h +++ b/device.h @@ -20,95 +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, - }; + 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 03c04e432d..d8058c16fc 100644 --- a/edify/Android.mk +++ b/edify/Android.mk @@ -1,42 +1,58 @@ # 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.l \ - parser.y \ - expr.c - -# "-x c" forces the lex/yacc files to be compiled as c the build system -# otherwise forces them to be c++. Need to also add an explicit -std because the -# build system will soon default C++ to -std=c++11. -edify_cflags := -x c -std=gnu89 + 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.c + $(edify_src_files) \ + edify_parser.cpp -LOCAL_CFLAGS := $(edify_cflags) -g -O0 -LOCAL_MODULE := edify +LOCAL_CFLAGS := -Werror +LOCAL_CPPFLAGS := -g -O0 +LOCAL_MODULE := edify_parser LOCAL_YACCFLAGS := -v -LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CPPFLAGS += -Wno-unused-parameter +LOCAL_CPPFLAGS += -Wno-deprecated-register +LOCAL_CLANG := true +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. +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 := $(edify_cflags) -LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CFLAGS := -Werror +LOCAL_CPPFLAGS := -Wno-unused-parameter +LOCAL_CPPFLAGS += -Wno-deprecated-register LOCAL_MODULE := libedify +LOCAL_CLANG := true +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. +LOCAL_STATIC_LIBRARIES += libbase include $(BUILD_STATIC_LIBRARY) 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.c b/edify/expr.c deleted file mode 100644 index 79f6282d81..0000000000 --- a/edify/expr.c +++ /dev/null @@ -1,505 +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 -#include - -#include "expr.h" - -// Functions should: -// -// - return a malloc()'d string -// - if Evaluate() on any argument returns NULL, return NULL. - -int BooleanString(const char* s) { - return s[0] != '\0'; -} - -char* Evaluate(State* state, Expr* expr) { - Value* v = expr->fn(expr->name, state, expr->argc, expr->argv); - if (v == NULL) return NULL; - if (v->type != VAL_STRING) { - ErrorAbort(state, "expecting string, got value type %d", v->type); - FreeValue(v); - return NULL; - } - char* result = v->data; - free(v); - return result; -} - -Value* EvaluateValue(State* state, Expr* expr) { - return expr->fn(expr->name, state, expr->argc, expr->argv); -} - -Value* StringValue(char* str) { - if (str == NULL) return NULL; - Value* v = malloc(sizeof(Value)); - v->type = VAL_STRING; - v->size = strlen(str); - v->data = str; - return v; -} - -void FreeValue(Value* v) { - if (v == NULL) return; - free(v->data); - free(v); -} - -Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc == 0) { - return StringValue(strdup("")); - } - char** strings = 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]); - } - - result = malloc(length+1); - int p = 0; - for (i = 0; i < argc; ++i) { - strcpy(result+p, strings[i]); - p += strlen(strings[i]); - } - 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; - } - char* cond = Evaluate(state, argv[0]); - if (cond == NULL) { - return NULL; - } - - if (BooleanString(cond) == true) { - free(cond); - return EvaluateValue(state, argv[1]); - } else { - if (argc == 3) { - free(cond); - return EvaluateValue(state, argv[2]); - } else { - return StringValue(cond); - } - } -} - -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) { - state->errmsg = msg; - } else { - state->errmsg = strdup("called abort()"); - } - return NULL; -} - -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; - } - int b = BooleanString(v); - free(v); - if (!b) { - int prefix_len; - int len = argv[i]->end - argv[i]->start; - char* err_src = 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; - } - } - return StringValue(strdup("")); -} - -Value* SleepFn(const char* name, State* state, int argc, Expr* argv[]) { - char* val = Evaluate(state, argv[0]); - if (val == NULL) { - return NULL; - } - 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; - } - fputs(v, stdout); - free(v); - } - return StringValue(strdup("")); -} - -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); - return EvaluateValue(state, argv[1]); - } else { - return StringValue(left); - } -} - -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); - return EvaluateValue(state, argv[1]); - } else { - return StringValue(left); - } -} - -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")); -} - -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); - 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; - } - - char* result = strdup(strcmp(left, right) == 0 ? "t" : ""); - free(left); - free(right); - 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; - } - - char* result = strdup(strcmp(left, right) != 0 ? "t" : ""); - free(left); - free(right); - 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); - 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; - } - - 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; - } - - long r_int = strtol(right, &end, 10); - if (right[0] == '\0' || *end != '\0') { - goto done; - } - - result = l_int < r_int; - - done: - free(left); - free(right); - return StringValue(strdup(result ? "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; - } - - Expr* temp[2]; - temp[0] = argv[1]; - temp[1] = argv[0]; - - return LessThanIntFn(name, state, 2, temp); -} - -Value* Literal(const char* name, State* state, int argc, Expr* argv[]) { - return StringValue(strdup(name)); -} - -Expr* Build(Function fn, YYLTYPE loc, int count, ...) { - va_list v; - va_start(v, count); - Expr* e = malloc(sizeof(Expr)); - e->fn = fn; - e->name = "(operator)"; - e->argc = count; - e->argv = 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; -} - -// ----------------------------------------------------------------- -// 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 = 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); -} - -void FinishRegistration() { - qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare); -} - -Function FindFunction(const char* name) { - NamedFunction key; - key.name = name; - NamedFunction* nf = bsearch(&key, fn_table, fn_entries, - sizeof(NamedFunction), fn_entry_compare); - if (nf == NULL) { - return NULL; - } - return nf->fn; -} - -void RegisterBuiltins() { - RegisterFunction("ifelse", IfElseFn); - RegisterFunction("abort", AbortFn); - RegisterFunction("assert", AssertFn); - RegisterFunction("concat", ConcatFn); - RegisterFunction("is_substring", SubstringFn); - RegisterFunction("stdout", StdoutFn); - RegisterFunction("sleep", SleepFn); - - RegisterFunction("less_than_int", LessThanIntFn); - RegisterFunction("greater_than_int", GreaterThanIntFn); -} - - -// ----------------------------------------------------------------- -// 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 = 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, 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 = 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; - } - *(va_arg(v, Value**)) = args[i]; - } - va_end(v); - free(args); - return 0; -} - -// 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, 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; - } - } - return args; -} - -// Use printf-style arguments to compose an error message to put into -// *state. Returns NULL. -Value* ErrorAbort(State* state, const char* format, ...) { - char* buffer = malloc(4096); - va_list v; - va_start(v, format); - vsnprintf(buffer, 4096, format, v); - va_end(v); - free(state->errmsg); - state->errmsg = buffer; - return NULL; -} diff --git a/edify/expr.cpp b/edify/expr.cpp new file mode 100644 index 0000000000..54ab3325ca --- /dev/null +++ b/edify/expr.cpp @@ -0,0 +1,423 @@ +/* + * 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 "expr.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +// Functions should: +// +// - return a malloc()'d string +// - if Evaluate() on any argument returns nullptr, return nullptr. + +static bool BooleanString(const std::string& s) { + return !s.empty(); +} + +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); + return false; + } + + *result = v->data; + return true; +} + +Value* EvaluateValue(State* state, const std::unique_ptr& expr) { + return expr->fn(expr->name.c_str(), state, expr->argv); +} + +Value* StringValue(const char* str) { + if (str == nullptr) { + return nullptr; + } + return new Value(VAL_STRING, str); +} + +Value* StringValue(const std::string& str) { + return StringValue(str.c_str()); +} + +Value* ConcatFn(const char* name, State* state, const std::vector>& argv) { + if (argv.empty()) { + return StringValue(""); + } + 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; + } + + return StringValue(result); +} + +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; + } + + std::string cond; + if (!Evaluate(state, argv[0], &cond)) { + return nullptr; + } + + if (!cond.empty()) { + return EvaluateValue(state, argv[1]); + } else if (argv.size() == 3) { + return EvaluateValue(state, argv[2]); + } + + return StringValue(""); +} + +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 = "called abort()"; + } + return nullptr; +} + +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; + } + if (result.empty()) { + int len = argv[i]->end - argv[i]->start; + state->errmsg = "assert failed: " + state->script.substr(argv[i]->start, len); + return nullptr; + } + } + return StringValue(""); +} + +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; + } + sleep(v); + + return StringValue(val); +} + +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.c_str(), stdout); + } + return StringValue(""); +} + +Value* LogicalAndFn(const char* name, State* state, + 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(""); + } +} + +Value* LogicalOrFn(const char* name, State* state, + 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); + } +} + +Value* LogicalNotFn(const char* name, State* state, + 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, + 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, 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; + } + + const char* result = (left == right) ? "t" : ""; + return StringValue(result); +} + +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; + } + + const char* result = (left != right) ? "t" : ""; + return StringValue(result); +} + +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, + const std::vector>& argv) { + if (argv.size() != 2) { + state->errmsg = "less_than_int expects 2 arguments"; + return nullptr; + } + + std::vector args; + if (!ReadArgs(state, argv, &args)) { + return nullptr; + } + + // 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; + } + + return StringValue(l_int < r_int ? "t" : ""); +} + +Value* GreaterThanIntFn(const char* name, State* state, + const std::vector>& argv) { + if (argv.size() != 2) { + state->errmsg = "greater_than_int expects 2 arguments"; + return nullptr; + } + + std::vector args; + if (!ReadArgs(state, argv, &args)) { + return nullptr; + } + + // 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; + } + + return StringValue(l_int > r_int ? "t" : ""); +} + +Value* Literal(const char* name, State* state, const std::vector>& argv) { + return StringValue(name); +} + +// ----------------------------------------------------------------- +// the function table +// ----------------------------------------------------------------- + +static std::unordered_map fn_table; + +void RegisterFunction(const std::string& name, Function fn) { + fn_table[name] = fn; +} + +Function FindFunction(const std::string& name) { + if (fn_table.find(name) == fn_table.end()) { + return nullptr; + } else { + return fn_table[name]; + } +} + +void RegisterBuiltins() { + RegisterFunction("ifelse", IfElseFn); + RegisterFunction("abort", AbortFn); + RegisterFunction("assert", AssertFn); + RegisterFunction("concat", ConcatFn); + RegisterFunction("is_substring", SubstringFn); + RegisterFunction("stdout", StdoutFn); + RegisterFunction("sleep", SleepFn); + + RegisterFunction("less_than_int", LessThanIntFn); + RegisterFunction("greater_than_int", GreaterThanIntFn); +} + + +// ----------------------------------------------------------------- +// convenience methods for functions +// ----------------------------------------------------------------- + +// 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()); +} + +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; + } + args->push_back(var); + } + return true; +} + +// 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()); +} + +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 true; +} + +// Use printf-style arguments to compose an error message to put into +// *state. Returns nullptr. +Value* ErrorAbort(State* state, const char* format, ...) { + va_list ap; + va_start(ap, format); + android::base::StringAppendV(&state->errmsg, format, ap); + va_end(ap); + return nullptr; +} + +Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) { + va_list ap; + va_start(ap, format); + 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 a9ed2f9c5b..4838d20c08 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -19,155 +19,141 @@ #include -#include "yydefs.h" +#include +#include +#include -#ifdef __cplusplus -extern "C" { -#endif +#include "error_code.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; -} State; + // 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. + ErrorCode error_code = kNoError; + + // cause code provides more detailed reason of an OTA failure (e.g. fsync error) + // in addition to the error code. + CauseCode cause_code = kNoCause; + + bool is_retry = false; +}; -#define VAL_STRING 1 // data will be NULL-terminated; size doesn't count null -#define VAL_BLOB 2 +enum ValueType { + VAL_INVALID = -1, + VAL_STRING = 1, + VAL_BLOB = 2, +}; + +struct Value { + ValueType type; + std::string data; -typedef struct { - int type; - ssize_t size; - char* data; -} Value; + Value(ValueType type, const std::string& str) : + type(type), + data(str) {} +}; -typedef Value* (*Function)(const char* name, State* state, - int argc, Expr* argv[]); +struct Expr; + +using Function = Value* (*)(const char* name, State* state, + const std::vector>& argv); struct Expr { - Function fn; - 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. -Value* ErrorAbort(State* state, const char* format, ...) __attribute__((format(printf, 2, 3))); +Value* ErrorAbort(State* state, const char* format, ...) + __attribute__((format(printf, 2, 3), deprecated)); -// Wrap a string into a Value, taking ownership of the string. -Value* StringValue(char* str); +// ErrorAbort has an optional (but recommended) argument 'cause_code'. If the cause code +// is set, it will be logged into last_install and provides reason of OTA failures. +Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) + __attribute__((format(printf, 3, 4))); -// Free a Value object. -void FreeValue(Value* v); +// Copying the string into a Value. +Value* StringValue(const char* str); -int parse_string(const char* str, Expr** root, int* error_count); +Value* StringValue(const std::string& str); -#ifdef __cplusplus -} // extern "C" -#endif +int parse_string(const char* str, std::unique_ptr* root, int* error_count); #endif // _EXPRESSION_H diff --git a/edify/lexer.l b/edify/lexer.ll similarity index 76% rename from edify/lexer.l rename to edify/lexer.ll index fb2933bee7..b764d16992 100644 --- a/edify/lexer.l +++ b/edify/lexer.ll @@ -16,6 +16,7 @@ */ #include +#include #include "expr.h" #include "yydefs.h" @@ -25,9 +26,7 @@ int gLine = 1; int gColumn = 1; int gPos = 0; -// TODO: enforce MAX_STRING_LEN during lexing -char string_buffer[MAX_STRING_LEN]; -char* string_pos; +std::string string_buffer; #define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \ gColumn+=yyleng; gPos+=yyleng;} while(0) @@ -43,7 +42,7 @@ char* string_pos; \" { BEGIN(STR); - string_pos = string_buffer; + string_buffer.clear(); yylloc.start = gPos; ++gColumn; ++gPos; @@ -54,36 +53,35 @@ char* string_pos; ++gColumn; ++gPos; BEGIN(INITIAL); - *string_pos = '\0'; - yylval.str = strdup(string_buffer); + yylval.str = strdup(string_buffer.c_str()); yylloc.end = gPos; return STRING; } - \\n { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\n'; } - \\t { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\t'; } - \\\" { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\"'; } - \\\\ { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\\'; } + \\n { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\n'); } + \\t { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\t'); } + \\\" { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\"'); } + \\\\ { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\\'); } \\x[0-9a-fA-F]{2} { gColumn += yyleng; gPos += yyleng; int val; sscanf(yytext+2, "%x", &val); - *string_pos++ = val; + string_buffer.push_back(static_cast(val)); } \n { ++gLine; ++gPos; gColumn = 1; - *string_pos++ = yytext[0]; + string_buffer.push_back(yytext[0]); } . { ++gColumn; ++gPos; - *string_pos++ = yytext[0]; + string_buffer.push_back(yytext[0]); } } diff --git a/edify/main.c b/edify/main.c deleted file mode 100644 index b1baa0b132..0000000000 --- a/edify/main.c +++ /dev/null @@ -1,214 +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 "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); - - 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.y b/edify/parser.yy similarity index 64% rename from edify/parser.y rename to edify/parser.yy index f8fb2d12f3..b1685eb1f0 100644 --- a/edify/parser.y +++ 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 { - $$ = 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 ')' { - $$ = 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 = malloc(sizeof(Expr*)); - $$.argv[0] = $1; + $$ = new std::vector>; + $$->emplace_back($1); } | arglist ',' expr { - $$.argc = $1.argc + 1; - $$.argv = 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 new file mode 100644 index 0000000000..9fe047c91b --- /dev/null +++ b/error_code.h @@ -0,0 +1,73 @@ +/* + * 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 _ERROR_CODE_H_ +#define _ERROR_CODE_H_ + +enum ErrorCode { + 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, + 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, +}; + +#endif // _ERROR_CODE_H_ diff --git a/etc/Android.mk b/etc/Android.mk index 5e612b63e8..cda0f372ed 100644 --- a/etc/Android.mk +++ b/etc/Android.mk @@ -31,6 +31,46 @@ include $(BUILD_PREBUILT) endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 22; echo $$?),0) + include $(CLEAR_VARS) + LOCAL_MODULE := init.recovery.service.rc + LOCAL_MODULE_TAGS := eng + LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES + LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) + + LOCAL_SRC_FILES := init.recovery.service22.rc + include $(BUILD_PREBUILT) +else + include $(CLEAR_VARS) + LOCAL_MODULE := init.recovery.service.rc + LOCAL_MODULE_TAGS := eng + LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES + LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) + + LOCAL_SRC_FILES := init.recovery.service21.rc + 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 dc8b6e3f6b..d1c4526d7a 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -1,5 +1,7 @@ import /init.recovery.logd.rc import /init.recovery.usb.rc +import /init.recovery.service.rc +import /init.recovery.vold_decrypt.rc import /init.recovery.${ro.hardware}.rc on early-init @@ -10,8 +12,10 @@ on early-init # This should occur before anything else (e.g. ueventd) is started. setcon u:r:init:s0 + # Set the security context of /postinstall if present. + restorecon /postinstall + start ueventd - start healthd service set_permissive /sbin/permissive.sh oneshot @@ -19,7 +23,8 @@ service set_permissive /sbin/permissive.sh on init export PATH /sbin:/system/bin - export LD_LIBRARY_PATH .:/sbin + export LD_LIBRARY_PATH /sbin + export ANDROID_ROOT /system export ANDROID_DATA /data export EXTERNAL_STORAGE /sdcard @@ -40,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 @@ -77,7 +81,9 @@ on late-init # Load properties, pre-Android 6.0 trigger load_all_props_action - # Load properties, Android 6.0+ + # Load properties from /system/ + /factory after fs mount. Place + # this in another action so that the load will be scheduled after the prior + # issued fs triggers have completed. trigger load_system_props_action # Load properties, Android 6.0+, vendor init lives here @@ -96,12 +102,6 @@ service ueventd /sbin/ueventd critical seclabel u:r:ueventd:s0 -service healthd /sbin/healthd -r - critical - seclabel u:r:healthd:s0 - -service recovery /sbin/recovery - 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.logd.rc b/etc/init.recovery.logd.rc index 4bc21ad0c3..7bdbfb8bc9 100644 --- a/etc/init.recovery.logd.rc +++ b/etc/init.recovery.logd.rc @@ -22,7 +22,9 @@ service logd /sbin/logd socket logdr seqpacket 0666 logd logd socket logdw dgram 0222 logd logd group root system + seclabel u:r:logd:s0 service logd-reinit /sbin/logd --reinit oneshot disabled + seclabel u:r:logd:s0 diff --git a/etc/init.recovery.service21.rc b/etc/init.recovery.service21.rc new file mode 100644 index 0000000000..892b226ffe --- /dev/null +++ b/etc/init.recovery.service21.rc @@ -0,0 +1,4 @@ +on boot + +# For starting recovery on 4.4 and older +service recovery /sbin/recovery diff --git a/etc/init.recovery.service22.rc b/etc/init.recovery.service22.rc new file mode 100644 index 0000000000..bb2853c1aa --- /dev/null +++ b/etc/init.recovery.service22.rc @@ -0,0 +1,5 @@ +on boot + +# For starting recovery on 5.0 and newer +service recovery /sbin/recovery + seclabel u:r:recovery:s0 diff --git a/etc/init.recovery.usb.rc b/etc/init.recovery.usb.rc index 801973671c..8ef20e0ec9 100644 --- a/etc/init.recovery.usb.rc +++ b/etc/init.recovery.usb.rc @@ -1,9 +1,9 @@ on fs write /sys/class/android_usb/android0/enable 0 write /sys/class/android_usb/android0/idVendor 18D1 - write /sys/class/android_usb/android0/idProduct 4EE2 + write /sys/class/android_usb/android0/idProduct D001 write /sys/class/android_usb/android0/f_ffs/aliases adb - write /sys/class/android_usb/android0/functions mtp,adb + write /sys/class/android_usb/android0/functions adb write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer} write /sys/class/android_usb/android0/iProduct ${ro.product.model} write /sys/class/android_usb/android0/iSerial ${ro.serialno} diff --git a/twrpDU.cpp b/exclude.cpp similarity index 74% rename from twrpDU.cpp rename to exclude.cpp index 08dfdcbea5..8ce138ad7f 100644 --- a/twrpDU.cpp +++ b/exclude.cpp @@ -1,5 +1,5 @@ /* - Copyright 2013 TeamWin + Copyright 2013 to 2016 TeamWin This file is part of TWRP/TeamWin Recovery Project. TWRP is free software: you can redistribute it and/or modify @@ -21,33 +21,30 @@ extern "C" { } #include #include -#include +#include #include -#include -#include #include #include -#include -#include "twrpDU.hpp" +#include "exclude.hpp" #include "twrp-functions.hpp" #include "gui/gui.hpp" +#include "twcommon.h" using namespace std; extern bool datamedia; -twrpDU::twrpDU() { +TWExclude::TWExclude() { add_relative_dir("."); add_relative_dir(".."); add_relative_dir("lost+found"); - add_absolute_dir("/data/data/com.google.android.music/files"); } -void twrpDU::add_relative_dir(const string& dir) { +void TWExclude::add_relative_dir(const string& dir) { relativedir.push_back(dir); } -void twrpDU::clear_relative_dir(string dir) { +void TWExclude::clear_relative_dir(string dir) { vector::iterator iter = relativedir.begin(); while (iter != relativedir.end()) { if (*iter == dir) @@ -57,15 +54,11 @@ void twrpDU::clear_relative_dir(string dir) { } } -void twrpDU::add_absolute_dir(const string& dir) { +void TWExclude::add_absolute_dir(const string& dir) { absolutedir.push_back(TWFunc::Remove_Trailing_Slashes(dir)); } -vector twrpDU::get_absolute_dirs(void) { - return absolutedir; -} - -uint64_t twrpDU::Get_Folder_Size(const string& Path) { +uint64_t TWExclude::Get_Folder_Size(const string& Path) { DIR* d; struct dirent* de; struct stat st; @@ -88,7 +81,7 @@ uint64_t twrpDU::Get_Folder_Size(const string& Path) { } if ((st.st_mode & S_IFDIR) && !check_skip_dirs(FullPath) && de->d_type != DT_SOCK) { dusize += Get_Folder_Size(FullPath); - } else if (st.st_mode & S_IFREG) { + } else if (st.st_mode & S_IFREG || st.st_mode & S_IFLNK) { dusize += (uint64_t)(st.st_size); } } @@ -96,19 +89,19 @@ uint64_t twrpDU::Get_Folder_Size(const string& Path) { return dusize; } -bool twrpDU::check_relative_skip_dirs(const string& dir) { +bool TWExclude::check_relative_skip_dirs(const string& dir) { return std::find(relativedir.begin(), relativedir.end(), dir) != relativedir.end(); } -bool twrpDU::check_absolute_skip_dirs(const string& path) { +bool TWExclude::check_absolute_skip_dirs(const string& path) { return std::find(absolutedir.begin(), absolutedir.end(), path) != absolutedir.end(); } -bool twrpDU::check_skip_dirs(const string& path) { +bool TWExclude::check_skip_dirs(const string& path) { string normalized = TWFunc::Remove_Trailing_Slashes(path); size_t slashIdx = normalized.find_last_of('/'); - if(slashIdx != std::string::npos && slashIdx+1 < normalized.size()) { - if(check_relative_skip_dirs(normalized.substr(slashIdx+1))) + if (slashIdx != std::string::npos && slashIdx+1 < normalized.size()) { + if (check_relative_skip_dirs(normalized.substr(slashIdx+1))) return true; } return check_absolute_skip_dirs(normalized); diff --git a/twrpDU.hpp b/exclude.hpp similarity index 78% rename from twrpDU.hpp rename to exclude.hpp index e947de9c28..5cdc416098 100644 --- a/twrpDU.hpp +++ b/exclude.hpp @@ -1,5 +1,5 @@ /* - Copyright 2013 TeamWin + Copyright 2013 to 2016 TeamWin This file is part of TWRP/TeamWin Recovery Project. TWRP is free software: you can redistribute it and/or modify @@ -16,39 +16,28 @@ along with TWRP. If not, see . */ -#ifndef TWRPDU_HPP -#define TWRPDU_HPP - -#include -#include -#include -#include -#include -#include -#include -#include +#ifndef TWEXCLUDE_HPP +#define TWEXCLUDE_HPP + #include #include -#include "twcommon.h" using namespace std; -class twrpDU { +class TWExclude { public: - twrpDU(); + TWExclude(); uint64_t Get_Folder_Size(const string& Path); // Gets the folder's size using stat void add_absolute_dir(const string& Path); void add_relative_dir(const string& Path); bool check_relative_skip_dirs(const string& dir); bool check_absolute_skip_dirs(const string& path); bool check_skip_dirs(const string& path); - vector get_absolute_dirs(void); void clear_relative_dir(string dir); private: vector absolutedir; vector relativedir; }; -extern twrpDU du; #endif diff --git a/fb2png/fb.h b/fb2png/fb.h index 25922c83bc..b82acba24a 100644 --- a/fb2png/fb.h +++ b/fb2png/fb.h @@ -37,6 +37,7 @@ struct fb { void* data; }; +void fb_dump(const struct fb* fb); int fb_save_png(const struct fb *fb, const char *path); #endif diff --git a/fb2png/fb2png.c b/fb2png/fb2png.c index a357b7fee8..1123b053b3 100644 --- a/fb2png/fb2png.c +++ b/fb2png/fb2png.c @@ -17,13 +17,15 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include +#include +#include #include #include -#include #include #include -#include -#include +#include +#include #include "log.h" #include "fb2png.h" diff --git a/fb2png/log.h b/fb2png/log.h index 96e90d7967..2b7cf8cecc 100644 --- a/fb2png/log.h +++ b/fb2png/log.h @@ -21,6 +21,7 @@ #define __KYAN_LOG_H__ #include +#include #ifdef ANDROID_XXX diff --git a/fb2png/main.c b/fb2png/main.c index 84e08a968b..235e958f98 100644 --- a/fb2png/main.c +++ b/fb2png/main.c @@ -17,9 +17,12 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include + #include +#include #include +#include + #include "fb2png.h" #ifdef ANDROID diff --git a/fixContexts.cpp b/fixContexts.cpp index 64429446e6..7e650c2091 100644 --- a/fixContexts.cpp +++ b/fixContexts.cpp @@ -25,16 +25,13 @@ #include "fixContexts.hpp" #include "twrp-functions.hpp" #include "twcommon.h" -#ifdef HAVE_SELINUX -#include "selinux/selinux.h" -#include "selinux/label.h" -#include "selinux/android.h" -#include "selinux/label.h" -#endif +#include +#include +#include +#include using namespace std; -#ifdef HAVE_SELINUX struct selabel_handle *sehandle; struct selinux_opt selinux_options[] = { { SELABEL_OPT_PATH, "/file_contexts" } @@ -146,18 +143,3 @@ int fixContexts::fixDataMediaContexts(string Mount_Point) { selabel_close(sehandle); return 0; } - -#else - -int fixContexts::restorecon(string entry __unused, struct stat *sb __unused) { - return -1; -} - -int fixContexts::fixContextsRecursively(string name __unused, int level __unused) { - return -1; -} - -int fixContexts::fixDataMediaContexts(string Mount_Point __unused) { - return -1; -} -#endif diff --git a/flashutils/flashutils.c b/flashutils/flashutils.c index 5a5e0094f2..6401939692 100644 --- a/flashutils/flashutils.c +++ b/flashutils/flashutils.c @@ -22,10 +22,12 @@ int device_flash_type() if (the_flash_type == UNKNOWN) { if (access(BOARD_BML_BOOT, F_OK) == 0) { the_flash_type = BML; - } else if (access("/proc/emmc", F_OK) == 0) { - the_flash_type = MMC; } else if (access("/proc/mtd", F_OK) == 0) { the_flash_type = MTD; + } else if (access("/proc/emmc", F_OK) == 0 || + access("/dev/block/mmcblk0", F_OK) == 0 || + access("/dev/block/sda", F_OK) == 0) { + the_flash_type = MMC; } else { the_flash_type = UNSUPPORTED; } diff --git a/fuse_sdcard_provider.c b/fuse_sdcard_provider.c deleted file mode 100644 index 4565c7b5bf..0000000000 --- a/fuse_sdcard_provider.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2014 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 "fuse_sideload.h" - -struct file_data { - int fd; // the underlying sdcard file - - uint64_t file_size; - uint32_t block_size; -}; - -static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) { - struct file_data* fd = (struct file_data*)cookie; - - off64_t offset = ((off64_t) block) * fd->block_size; - if (TEMP_FAILURE_RETRY(lseek64(fd->fd, offset, SEEK_SET)) == -1) { - fprintf(stderr, "seek on sdcard failed: %s\n", strerror(errno)); - 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; - } - - return 0; -} - -static void close_file(void* cookie) { - struct file_data* fd = (struct file_data*)cookie; - close(fd->fd); -} - -struct token { - pthread_t th; - const char* path; - int result; -}; - -static void* run_sdcard_fuse(void* cookie) { - struct token* t = (struct token*)cookie; - - struct stat sb; - if (stat(t->path, &sb) < 0) { - fprintf(stderr, "failed to stat %s: %s\n", t->path, strerror(errno)); - t->result = -1; - return NULL; - } - - struct file_data fd; - struct provider_vtab vtab; - - fd.fd = open(t->path, O_RDONLY); - if (fd.fd < 0) { - fprintf(stderr, "failed to open %s: %s\n", t->path, strerror(errno)); - t->result = -1; - return NULL; - } - fd.file_size = sb.st_size; - fd.block_size = 65536; - - vtab.read_block = read_block_file; - vtab.close = close_file; - - t->result = run_fuse_sideload(&vtab, &fd, fd.file_size, fd.block_size); - return NULL; -} - -// How long (in seconds) we wait for the fuse-provided package file to -// appear, before timing out. -#define SDCARD_INSTALL_TIMEOUT 10 - -void* start_sdcard_fuse(const char* path) { - struct token* t = malloc(sizeof(struct token)); - - t->path = path; - pthread_create(&(t->th), NULL, run_sdcard_fuse, t); - - struct stat st; - int i; - for (i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { - if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { - if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT-1) { - sleep(1); - continue; - } else { - return NULL; - } - } - } - - // The installation process expects to find the sdcard unmounted. - // Unmount it with MNT_DETACH so that our open file continues to - // work but new references see it as unmounted. - umount2("/sdcard", MNT_DETACH); - - return t; -} - -void finish_sdcard_fuse(void* cookie) { - if (cookie == NULL) return; - struct token* t = (struct token*)cookie; - - // Calling stat() on this magic filename signals the fuse - // filesystem to shut down. - struct stat st; - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); - - pthread_join(t->th, NULL); - free(t); -} diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp new file mode 100644 index 0000000000..b0ecf96bee --- /dev/null +++ b/fuse_sdcard_provider.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 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 "fuse_sideload.h" + +struct file_data { + int fd; // the underlying sdcard file + + uint64_t file_size; + uint32_t block_size; +}; + +static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) { + file_data* fd = reinterpret_cast(cookie); + + off64_t offset = ((off64_t) block) * fd->block_size; + if (TEMP_FAILURE_RETRY(lseek64(fd->fd, offset, SEEK_SET)) == -1) { + fprintf(stderr, "seek on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } + + if (!android::base::ReadFully(fd->fd, buffer, fetch_size)) { + fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } + + return 0; +} + +static void close_file(void* cookie) { + file_data* fd = reinterpret_cast(cookie); + close(fd->fd); +} + +bool start_sdcard_fuse(const char* path) { + struct stat sb; + if (stat(path, &sb) == -1) { + fprintf(stderr, "failed to stat %s: %s\n", path, strerror(errno)); + return false; + } + + file_data fd; + fd.fd = open(path, O_RDONLY); + if (fd.fd == -1) { + fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno)); + return false; + } + fd.file_size = sb.st_size; + fd.block_size = 65536; + + provider_vtab vtab; + vtab.read_block = read_block_file; + vtab.close = close_file; + + // The installation process expects to find the sdcard unmounted. + // Unmount it with MNT_DETACH so that our open file continues to + // work but new references see it as unmounted. + umount2("/sdcard", MNT_DETACH); + + return run_fuse_sideload(&vtab, &fd, fd.file_size, fd.block_size) == 0; +} diff --git a/fuse_sdcard_provider.h b/fuse_sdcard_provider.h index dbfbcd5213..bdc60f2ba2 100644 --- a/fuse_sdcard_provider.h +++ b/fuse_sdcard_provider.h @@ -17,13 +17,6 @@ #ifndef __FUSE_SDCARD_PROVIDER_H #define __FUSE_SDCARD_PROVIDER_H -#include - -__BEGIN_DECLS - -void* start_sdcard_fuse(const char* path); -void finish_sdcard_fuse(void* token); - -__END_DECLS +bool start_sdcard_fuse(const char* path); #endif diff --git a/fuse_sideload.c b/fuse_sideload.cpp similarity index 69% rename from fuse_sideload.c rename to fuse_sideload.cpp index f09b026018..f667cd4a17 100644 --- a/fuse_sideload.c +++ b/fuse_sideload.cpp @@ -61,7 +61,13 @@ #include #include +#ifdef USE_MINCRYPT #include "mincrypt/sha256.h" +#define SHA256_DIGEST_LENGTH SHA256_DIGEST_SIZE +#else +#include +#endif + #include "fuse_sideload.h" #define PACKAGE_FILE_ID (FUSE_ROOT_ID+1) @@ -120,7 +126,7 @@ static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, siz } static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { - const struct fuse_init_in* req = data; + const struct fuse_init_in* req = reinterpret_cast(data); struct fuse_init_out out; size_t fuse_struct_size; @@ -174,7 +180,7 @@ static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd, attr->mode = mode; } -static int handle_getattr(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { +static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { struct fuse_attr_out out; memset(&out, 0, sizeof(out)); out.attr_valid = 10; @@ -200,12 +206,12 @@ static int handle_lookup(void* data, struct fuse_data* fd, out.entry_valid = 10; out.attr_valid = 10; - if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, data, + if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, reinterpret_cast(data), sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) { out.nodeid = PACKAGE_FILE_ID; out.generation = PACKAGE_FILE_ID; fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); - } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, data, + } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, reinterpret_cast(data), sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) { out.nodeid = EXIT_FLAG_ID; out.generation = EXIT_FLAG_ID; @@ -218,7 +224,7 @@ static int handle_lookup(void* data, struct fuse_data* fd, return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; } -static int handle_open(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { +static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; @@ -229,11 +235,13 @@ static int handle_open(void* data, struct fuse_data* fd, const struct fuse_in_he 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; } @@ -273,27 +281,31 @@ static int fetch_block(struct fuse_data* fd, uint32_t block) { // block). // - Otherwise, return -EINVAL for the read. - uint8_t hash[SHA256_DIGEST_SIZE]; + uint8_t hash[SHA256_DIGEST_LENGTH]; +#ifdef USE_MINCRYPT SHA256_hash(fd->block_data, fd->block_size, hash); - uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_SIZE; - if (memcmp(hash, blockhash, SHA256_DIGEST_SIZE) == 0) { +#else + SHA256(fd->block_data, fd->block_size, hash); +#endif + uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH; + if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) { return 0; } int i; - for (i = 0; i < SHA256_DIGEST_SIZE; ++i) { + for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { if (blockhash[i] != 0) { fd->curr_block = -1; return -EIO; } } - memcpy(blockhash, hash, SHA256_DIGEST_SIZE); + memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); return 0; } static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { - const struct fuse_read_in* req = data; + const struct fuse_read_in* req = reinterpret_cast(data); struct fuse_out_header outhdr; struct iovec vec[3]; int vec_used; @@ -365,164 +377,171 @@ 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_SIZE); - if (fd.hashes == NULL) { - fprintf(stderr, "failed to allocate %d bites for hashes\n", - fd.file_blocks * SHA256_DIGEST_SIZE); - 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, + uint64_t file_size, uint32_t block_size) +{ + return run_fuse_sideload(vtab, cookie, file_size, block_size); } diff --git a/fuse_sideload.h b/fuse_sideload.h index f9e3bf0d32..2a8ebfc5e5 100644 --- a/fuse_sideload.h +++ b/fuse_sideload.h @@ -17,10 +17,6 @@ #ifndef __FUSE_SIDELOAD_H #define __FUSE_SIDELOAD_H -#include - -__BEGIN_DECLS - // define the filenames created by the sideload FUSE filesystem #define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload" #define FUSE_SIDELOAD_HOST_FILENAME "package.zip" @@ -39,6 +35,13 @@ struct provider_vtab { int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size, uint32_t block_size); -__END_DECLS +#ifdef __cplusplus +extern "C" { +#endif +int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size); +#ifdef __cplusplus +} +#endif #endif 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 98d562944d..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 +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 @@ -55,9 +61,6 @@ endif ifneq ($(TW_NO_SCREEN_TIMEOUT),) LOCAL_CFLAGS += -DTW_NO_SCREEN_TIMEOUT endif -ifeq ($(HAVE_SELINUX), true) - LOCAL_CFLAGS += -DHAVE_SELINUX -endif ifeq ($(TW_OEM_BUILD), true) LOCAL_CFLAGS += -DTW_OEM_BUILD endif @@ -67,11 +70,21 @@ endif ifneq ($(TW_Y_OFFSET),) LOCAL_CFLAGS += -DTW_Y_OFFSET=$(TW_Y_OFFSET) endif +ifneq ($(TW_W_OFFSET),) + LOCAL_CFLAGS += -DTW_W_OFFSET=$(TW_W_OFFSET) +endif +ifneq ($(TW_H_OFFSET),) + LOCAL_CFLAGS += -DTW_H_OFFSET=$(TW_H_OFFSET) +endif ifeq ($(TW_ROUND_SCREEN), true) LOCAL_CFLAGS += -DTW_ROUND_SCREEN endif -LOCAL_C_INCLUDES += bionic system/core/libpixelflinger/include +LOCAL_C_INCLUDES += \ + bionic \ + system/core/include \ + system/core/libpixelflinger/include + ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) LOCAL_C_INCLUDES += external/stlport/stlport endif @@ -86,98 +99,96 @@ LOCAL_MODULE := twrp LOCAL_MODULE_TAGS := eng LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWRES_PATH) -TWRP_RES := $(commands_recovery_local_path)/gui/devices/common/res/* -# enable this to use new themes: -TWRP_NEW_THEME := true + +# The extra blank line before *** is intentional to ensure it ends up on its own line +define TW_THEME_WARNING_MSG + +**************************************************************************** + Could not find ui.xml for TW_THEME: $(TW_THEME) + Set TARGET_SCREEN_WIDTH and TARGET_SCREEN_HEIGHT to automatically select + an appropriate theme, or set TW_THEME to one of the following: + $(notdir $(wildcard $(commands_recovery_local_path)/gui/theme/*_*)) +**************************************************************************** +endef +define TW_CUSTOM_THEME_WARNING_MSG + +**************************************************************************** + Could not find ui.xml for TW_CUSTOM_THEME: $(TW_CUSTOM_THEME) + Expected to find custom theme's ui.xml at: + $(TWRP_THEME_LOC)/ui.xml + Please fix this or set TW_THEME to one of the following: + $(notdir $(wildcard $(commands_recovery_local_path)/gui/theme/*_*)) +**************************************************************************** +endef + +TWRP_RES := $(commands_recovery_local_path)/gui/theme/common/fonts +TWRP_RES += $(commands_recovery_local_path)/gui/theme/common/languages +ifeq ($(TW_EXTRA_LANGUAGES),true) + TWRP_RES += $(commands_recovery_local_path)/gui/theme/extra-languages/fonts + TWRP_RES += $(commands_recovery_local_path)/gui/theme/extra-languages/languages +endif ifeq ($(TW_CUSTOM_THEME),) ifeq ($(TW_THEME),) - # This converts the old DEVICE_RESOLUTION flag to the new TW_THEME flag - PORTRAIT_MDPI := 320x480 480x800 480x854 540x960 - PORTRAIT_HDPI := 720x1280 800x1280 1080x1920 1200x1920 1440x2560 1600x2560 - WATCH_MDPI := 240x240 280x280 320x320 - LANDSCAPE_MDPI := 800x480 1024x600 1024x768 - LANDSCAPE_HDPI := 1280x800 1920x1200 2560x1600 - ifneq ($(filter $(DEVICE_RESOLUTION), $(PORTRAIT_MDPI)),) - TW_THEME := portrait_mdpi - else ifneq ($(filter $(DEVICE_RESOLUTION), $(PORTRAIT_HDPI)),) - TW_THEME := portrait_hdpi - else ifneq ($(filter $(DEVICE_RESOLUTION), $(WATCH_MDPI)),) - TW_THEME := watch_mdpi - else ifneq ($(filter $(DEVICE_RESOLUTION), $(LANDSCAPE_MDPI)),) - TW_THEME := landscape_mdpi - else ifneq ($(filter $(DEVICE_RESOLUTION), $(LANDSCAPE_HDPI)),) - TW_THEME := landscape_hdpi + ifeq ($(DEVICE_RESOLUTION),) + GUI_WIDTH := $(TARGET_SCREEN_WIDTH) + GUI_HEIGHT := $(TARGET_SCREEN_HEIGHT) + else + SPLIT_DEVICE_RESOLUTION := $(subst x, ,$(DEVICE_RESOLUTION)) + GUI_WIDTH := $(word 1, $(SPLIT_DEVICE_RESOLUTION)) + GUI_HEIGHT := $(word 2, $(SPLIT_DEVICE_RESOLUTION)) + endif + + # Minimum resolution of 100x100 + # This also ensures GUI_WIDTH and GUI_HEIGHT are numbers + ifeq ($(shell test $(GUI_WIDTH) -ge 100; echo $$?),0) + ifeq ($(shell test $(GUI_HEIGHT) -ge 100; echo $$?),0) + ifeq ($(shell test $(GUI_WIDTH) -gt $(GUI_HEIGHT); echo $$?),0) + ifeq ($(shell test $(GUI_WIDTH) -ge 1280; echo $$?),0) + TW_THEME := landscape_hdpi + else + TW_THEME := landscape_mdpi + endif + else ifeq ($(shell test $(GUI_WIDTH) -lt $(GUI_HEIGHT); echo $$?),0) + ifeq ($(shell test $(GUI_WIDTH) -ge 720; echo $$?),0) + TW_THEME := portrait_hdpi + else + TW_THEME := portrait_mdpi + endif + else ifeq ($(shell test $(GUI_WIDTH) -eq $(GUI_HEIGHT); echo $$?),0) + # watch_hdpi does not yet exist + TW_THEME := watch_mdpi + endif + endif endif endif -ifeq ($(TWRP_NEW_THEME),true) TWRP_THEME_LOC := $(commands_recovery_local_path)/gui/theme/$(TW_THEME) - TWRP_RES := $(commands_recovery_local_path)/gui/theme/common/fonts - TWRP_RES += $(commands_recovery_local_path)/gui/theme/common/languages - TWRP_RES += $(commands_recovery_local_path)/gui/theme/common/$(word 1,$(subst _, ,$(TW_THEME))).xml -ifeq ($(TW_EXTRA_LANGUAGES),true) - TWRP_RES += $(commands_recovery_local_path)/gui/theme/extra-languages/fonts - TWRP_RES += $(commands_recovery_local_path)/gui/theme/extra-languages/languages -endif -# for future copying of used include xmls and fonts: -# UI_XML := $(TWRP_THEME_LOC)/ui.xml -# TWRP_INCLUDE_XMLS := $(shell xmllint --xpath '/recovery/include/xmlfile/@name' $(UI_XML)|sed -n 's/[^\"]*\"\([^\"]*\)\"[^\"]*/\1\n/gp'|sort|uniq) -# TWRP_FONTS_TTF := $(shell xmllint --xpath '/recovery/resources/font/@filename' $(UI_XML)|sed -n 's/[^\"]*\"\([^\"]*\)\"[^\"]*/\1\n/gp'|sort|uniq)niq) -ifeq ($(wildcard $(TWRP_THEME_LOC)/ui.xml),) - $(warning ****************************************************************************) - $(warning * TW_THEME is not valid: '$(TW_THEME)') - $(warning * Please choose an appropriate TW_THEME or create a new one for your device.) - $(warning * Available themes:) - $(warning * $(notdir $(wildcard $(commands_recovery_local_path)/gui/theme/*_*))) - $(warning ****************************************************************************) - $(error stopping) -endif -else - TWRP_RES += $(commands_recovery_local_path)/gui/devices/$(word 1,$(subst _, ,$(TW_THEME)))/res/* - ifeq ($(TW_THEME), portrait_mdpi) - TWRP_THEME_LOC := $(commands_recovery_local_path)/gui/devices/480x800/res - else ifeq ($(TW_THEME), portrait_hdpi) - TWRP_THEME_LOC := $(commands_recovery_local_path)/gui/devices/1080x1920/res - else ifeq ($(TW_THEME), watch_mdpi) - TWRP_THEME_LOC := $(commands_recovery_local_path)/gui/devices/320x320/res - else ifeq ($(TW_THEME), landscape_mdpi) - TWRP_THEME_LOC := $(commands_recovery_local_path)/gui/devices/800x480/res - else ifeq ($(TW_THEME), landscape_hdpi) - TWRP_THEME_LOC := $(commands_recovery_local_path)/gui/devices/1920x1200/res - else - $(warning ****************************************************************************) - $(warning * TW_THEME ($(TW_THEME)) is not valid.) - $(warning * Please choose an appropriate TW_THEME or create a new one for your device.) - $(warning * Valid options are portrait_mdpi portrait_hdpi watch_mdpi) - $(warning * landscape_mdpi landscape_hdpi) - $(warning ****************************************************************************) - $(error stopping) + ifeq ($(wildcard $(TWRP_THEME_LOC)/ui.xml),) + $(warning $(TW_THEME_WARNING_MSG)) + $(error Theme selection failed; exiting) endif -endif + + TWRP_RES += $(commands_recovery_local_path)/gui/theme/common/$(word 1,$(subst _, ,$(TW_THEME))).xml + # for future copying of used include xmls and fonts: + # UI_XML := $(TWRP_THEME_LOC)/ui.xml + # TWRP_INCLUDE_XMLS := $(shell xmllint --xpath '/recovery/include/xmlfile/@name' $(UI_XML)|sed -n 's/[^\"]*\"\([^\"]*\)\"[^\"]*/\1\n/gp'|sort|uniq) + # TWRP_FONTS_TTF := $(shell xmllint --xpath '/recovery/resources/font/@filename' $(UI_XML)|sed -n 's/[^\"]*\"\([^\"]*\)\"[^\"]*/\1\n/gp'|sort|uniq)niq) else TWRP_THEME_LOC := $(TW_CUSTOM_THEME) + ifeq ($(wildcard $(TWRP_THEME_LOC)/ui.xml),) + $(warning $(TW_CUSTOM_THEME_WARNING_MSG)) + $(error Theme selection failed; exiting) + endif endif + TWRP_RES += $(TW_ADDITIONAL_RES) TWRP_RES_GEN := $(intermediates)/twrp -ifneq ($(TW_USE_TOOLBOX), true) - TWRP_SH_TARGET := /sbin/busybox -else - TWRP_SH_TARGET := /sbin/mksh -endif - $(TWRP_RES_GEN): mkdir -p $(TARGET_RECOVERY_ROOT_OUT)$(TWRES_PATH) cp -fr $(TWRP_RES) $(TARGET_RECOVERY_ROOT_OUT)$(TWRES_PATH) cp -fr $(TWRP_THEME_LOC)/* $(TARGET_RECOVERY_ROOT_OUT)$(TWRES_PATH) - mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/sbin/ -ifneq ($(TW_USE_TOOLBOX), true) - ln -sf $(TWRP_SH_TARGET) $(TARGET_RECOVERY_ROOT_OUT)/sbin/sh -endif - ln -sf /sbin/pigz $(TARGET_RECOVERY_ROOT_OUT)/sbin/gzip - ln -sf /sbin/unpigz $(TARGET_RECOVERY_ROOT_OUT)/sbin/gunzip - LOCAL_GENERATED_SOURCES := $(TWRP_RES_GEN) LOCAL_SRC_FILES := twrp $(TWRP_RES_GEN) diff --git a/gui/action.cpp b/gui/action.cpp index 328fc2c528..3d497ca5c9 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. @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include @@ -43,15 +43,15 @@ #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" -#include "../set_metadata.h" }; +#include "../set_metadata.h" #include "../minuitwrp/minui.h" #include "rapidxml.hpp" @@ -62,7 +62,6 @@ GUIAction::mapFunc GUIAction::mf; std::set GUIAction::setActionsRunningInCallerThread; static string zip_queue[10]; static int zip_queue_index; -static pthread_t terminal_command; pid_t sideload_child_pid; static void *ActionThread_work_wrapper(void *data); @@ -107,7 +106,7 @@ ActionThread::ActionThread() ActionThread::~ActionThread() { pthread_mutex_lock(&m_act_lock); - if(m_thread_running) { + if (m_thread_running) { pthread_mutex_unlock(&m_act_lock); pthread_join(m_thread, NULL); } else { @@ -198,6 +197,8 @@ GUIAction::GUIAction(xml_node<>* node) ADD_ACTION(checkpartitionlifetimewrites); ADD_ACTION(mountsystemtoggle); ADD_ACTION(setlanguage); + ADD_ACTION(checkforapp); + ADD_ACTION(togglebacklight); // remember actions that run in the caller thread for (mapFunc::const_iterator it = mf.begin(); it != mf.end(); ++it) @@ -228,6 +229,8 @@ GUIAction::GUIAction(xml_node<>* node) ADD_ACTION(changefilesystem); ADD_ACTION(flashimage); ADD_ACTION(twcmd); + ADD_ACTION(setbootslot); + ADD_ACTION(installapp); } // First, get the action @@ -259,7 +262,7 @@ GUIAction::GUIAction(xml_node<>* node) if (attr) { std::vector keys = TWFunc::Split_String(attr->value(), "+"); - for(size_t i = 0; i < keys.size(); ++i) + for (size_t i = 0; i < keys.size(); ++i) { const int key = getKeyByName(keys[i]); mKeys[key] = false; @@ -294,7 +297,7 @@ int GUIAction::NotifyTouch(TOUCH_STATE state, int x __unused, int y __unused) int GUIAction::NotifyKey(int key, bool down) { std::map::iterator itr = mKeys.find(key); - if(itr == mKeys.end()) + if (itr == mKeys.end()) return 1; bool prevState = itr->second; @@ -304,20 +307,20 @@ int GUIAction::NotifyKey(int key, bool down) // doesn't trigger with multi-key actions. // Else, check if all buttons are pressed, then consume their release events // so they don't trigger one-button actions and reset mKeys pressed status - if(mKeys.size() == 1) { - if(!down && prevState) { + if (mKeys.size() == 1) { + if (!down && prevState) { doActions(); return 0; } - } else if(down) { - for(itr = mKeys.begin(); itr != mKeys.end(); ++itr) { - if(!itr->second) + } else if (down) { + for (itr = mKeys.begin(); itr != mKeys.end(); ++itr) { + if (!itr->second) return 1; } // Passed, all req buttons are pressed, reset them and consume release events HardwareKeyboard *kb = PageManager::GetHardwareKeyboard(); - for(itr = mKeys.begin(); itr != mKeys.end(); ++itr) { + for (itr = mKeys.begin(); itr != mKeys.end(); ++itr) { kb->ConsumeKeyRelease(itr->first); itr->second = false; } @@ -335,7 +338,7 @@ int GUIAction::NotifyVarChange(const std::string& varName, const std::string& va if (varName.empty() && !isConditionValid() && mKeys.empty() && !mActionW) doActions(); - else if((varName.empty() || IsConditionVariable(varName)) && isConditionValid() && isConditionTrue()) + else if ((varName.empty() || IsConditionVariable(varName)) && isConditionValid() && isConditionTrue()) doActions(); return 0; @@ -563,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; @@ -639,13 +643,19 @@ int GUIAction::copylog(std::string arg __unused) operation_start("Copy Log"); if (!simulate) { - string dst; + string dst, curr_storage; + int copy_kernel_log = 0; + + DataManager::GetValue("tw_include_kernel_log", copy_kernel_log); PartitionManager.Mount_Current_Storage(true); - dst = DataManager::GetCurrentStoragePath() + "/recovery.log"; + curr_storage = DataManager::GetCurrentStoragePath(); + dst = curr_storage + "/recovery.log"; TWFunc::copy_file("/tmp/recovery.log", dst.c_str(), 0755); tw_set_default_metadata(dst.c_str()); + if (copy_kernel_log) + TWFunc::copy_kernel_log(curr_storage); sync(); - gui_msg(Msg("copy_log=Copied recovery log to {1}.")(DataManager::GetCurrentStoragePath())); + gui_msg(Msg("copy_log=Copied recovery log to {1}")(dst)); } else simulate_progress_bar(); operation_end(0); @@ -698,7 +708,7 @@ int GUIAction::compute(std::string arg) int divide_by = atoi(divide_by_str.c_str()); int value; - if(divide_by != 0) + if (divide_by != 0) { DataManager::GetValue(varName, value); DataManager::SetValue(varName, value/divide_by); @@ -846,13 +856,12 @@ int GUIAction::checkpartitionlist(std::string arg) } else { DataManager::SetValue("tw_check_partition_list", 0); } - return 0; + return 0; } int GUIAction::getpartitiondetails(std::string arg) { string List, part_path; - int count = 0; if (arg.empty()) arg = "tw_wipe_list"; @@ -930,23 +939,17 @@ int GUIAction::screenshot(std::string arg __unused) time_t tm; char path[256]; int path_len; - uid_t uid = -1; - gid_t gid = -1; - - struct passwd *pwd = getpwnam("media_rw"); - if(pwd) { - uid = pwd->pw_uid; - gid = pwd->pw_gid; - } + uid_t uid = AID_MEDIA_RW; + gid_t gid = AID_MEDIA_RW; const std::string storage = DataManager::GetCurrentStoragePath(); - if(PartitionManager.Is_Mounted_By_Path(storage)) { + if (PartitionManager.Is_Mounted_By_Path(storage)) { snprintf(path, sizeof(path), "%s/Pictures/Screenshots/", storage.c_str()); } else { strcpy(path, "/tmp/"); } - if(!TWFunc::Create_Dir_Recursive(path, 0775, uid, gid)) + if (!TWFunc::Create_Dir_Recursive(path, 0775, uid, gid)) return 0; tm = time(NULL); @@ -956,7 +959,7 @@ int GUIAction::screenshot(std::string arg __unused) strftime(path+path_len, sizeof(path)-path_len, "Screenshot_%Y-%m-%d-%H-%M-%S.png", localtime(&tm)); int res = gr_save_screenshot(path); - if(res == 0) { + if (res == 0) { chmod(path, 0666); chown(path, uid, gid); @@ -1069,7 +1072,7 @@ int GUIAction::wipe(std::string arg) else if (arg == "DATAMEDIA") { ret_val = PartitionManager.Format_Data(); } else if (arg == "INTERNAL") { - int has_datamedia, dual_storage; + int has_datamedia; DataManager::GetValue(TW_HAS_DATA_MEDIA, has_datamedia); if (has_datamedia) { @@ -1088,7 +1091,6 @@ int GUIAction::wipe(std::string arg) string Wipe_List, wipe_path; bool skip = false; ret_val = true; - TWPartition* wipe_part = NULL; DataManager::GetValue("tw_wipe_list", Wipe_List); LOGINFO("wipe list '%s'\n", Wipe_List.c_str()); @@ -1191,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; @@ -1198,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; } @@ -1375,11 +1392,11 @@ int GUIAction::terminalcommand(std::string arg) if (fp == NULL) { LOGERR("Error opening command to run (%s).\n", strerror(errno)); } else { - int fd = fileno(fp), has_data = 0, check = 0, keep_going = -1, bytes_read = 0; + int fd = fileno(fp), has_data = 0, check = 0, keep_going = -1; struct timeval timeout; fd_set fdset; - while(keep_going) + while (keep_going) { FD_ZERO(&fdset); FD_SET(fd, &fdset); @@ -1397,7 +1414,7 @@ int GUIAction::terminalcommand(std::string arg) keep_going = 0; } else { // Try to read output - if(fgets(line, sizeof(line), fp) != NULL) + if (fgets(line, sizeof(line), fp) != NULL) gui_print("%s", line); // Display output else keep_going = 0; // Done executing @@ -1416,8 +1433,6 @@ int GUIAction::terminalcommand(std::string arg) int GUIAction::killterminal(std::string arg __unused) { - int op_status = 0; - LOGINFO("Sending kill command...\n"); operation_start("KillCommand"); DataManager::SetValue("tw_operation_status", 0); @@ -1737,16 +1752,11 @@ int GUIAction::flashimage(std::string arg __unused) { int op_status = 0; - PartitionSettings part_settings; operation_start("Flash Image"); - DataManager::GetValue("tw_zip_location", part_settings.Restore_Name); - DataManager::GetValue("tw_file", part_settings.Backup_FileName); - unsigned long long total_bytes = TWFunc::Get_File_Size(part_settings.Restore_Name + "/" + part_settings.Backup_FileName); - ProgressTracking progress(total_bytes); - part_settings.progress = &progress; - part_settings.adbbackup = false; - part_settings.PM_Method = PM_RESTORE; - if (PartitionManager.Flash_Image(&part_settings)) + string path, filename; + DataManager::GetValue("tw_zip_location", path); + DataManager::GetValue("tw_file", filename); + if (PartitionManager.Flash_Image(path, filename)) op_status = 0; // success else op_status = 1; // fail @@ -1865,3 +1875,177 @@ int GUIAction::setlanguage(std::string arg __unused) operation_end(op_status); return 0; } + +int GUIAction::togglebacklight(std::string arg __unused) +{ + blankTimer.toggleBlank(); + return 0; +} + +int GUIAction::setbootslot(std::string arg) +{ + operation_start("Set Boot Slot"); + if (!simulate) + PartitionManager.Set_Active_Slot(arg); + else + simulate_progress_bar(); + operation_end(0); + return 0; +} + +int GUIAction::checkforapp(std::string arg __unused) +{ + operation_start("Check for TWRP App"); + if (!simulate) + { + string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk"); + int sdkver = 0; + if (!sdkverstr.empty()) { + sdkver = atoi(sdkverstr.c_str()); + } + if (sdkver <= 13) { + if (sdkver == 0) + LOGINFO("Unable to read sdk version from build prop\n"); + else + LOGINFO("SDK version too low for TWRP app (%i < 14)\n", sdkver); + DataManager::SetValue("tw_app_install_status", 1); // 0 = no status, 1 = not installed, 2 = already installed or do not install + goto exit; + } + if (PartitionManager.Mount_By_Path("/system", false)) { + string base_path = "/system"; + if (TWFunc::Path_Exists("/system/system")) + base_path += "/system"; // For devices with system as a root file system (e.g. Pixel) + string install_path = base_path + "/priv-app"; + if (!TWFunc::Path_Exists(install_path)) + install_path = base_path + "/app"; + install_path += "/twrpapp"; + if (TWFunc::Path_Exists(install_path)) { + LOGINFO("App found at '%s'\n", install_path.c_str()); + DataManager::SetValue("tw_app_install_status", 2); // 0 = no status, 1 = not installed, 2 = already installed or do not install + goto exit; + } + } + if (PartitionManager.Mount_By_Path("/data", false)) { + const char parent_path[] = "/data/app"; + const char app_prefix[] = "me.twrp.twrpapp-"; + DIR *d = opendir(parent_path); + if (d) { + struct dirent *p; + while ((p = readdir(d))) { + if (p->d_type != DT_DIR || strlen(p->d_name) < strlen(app_prefix) || strncmp(p->d_name, app_prefix, strlen(app_prefix))) + continue; + closedir(d); + LOGINFO("App found at '%s/%s'\n", parent_path, p->d_name); + DataManager::SetValue("tw_app_install_status", 2); // 0 = no status, 1 = not installed, 2 = already installed or do not install + goto exit; + } + closedir(d); + } + } else { + LOGINFO("Data partition cannot be mounted during app check\n"); + DataManager::SetValue("tw_app_install_status", 2); // 0 = no status, 1 = not installed, 2 = already installed or do not install + } + } else + simulate_progress_bar(); + LOGINFO("App not installed\n"); + DataManager::SetValue("tw_app_install_status", 1); // 0 = no status, 1 = not installed, 2 = already installed +exit: + operation_end(0); + return 0; +} + +int GUIAction::installapp(std::string arg __unused) +{ + int op_status = 1; + operation_start("Install TWRP App"); + if (!simulate) + { + if (DataManager::GetIntValue("tw_mount_system_ro") > 0 || DataManager::GetIntValue("tw_app_install_system") == 0) { + if (PartitionManager.Mount_By_Path("/data", true)) { + string install_path = "/data/app"; + string context = "u:object_r:apk_data_file:s0"; + if (!TWFunc::Path_Exists(install_path)) { + if (mkdir(install_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + LOGERR("Error making %s directory: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + if (chown(install_path.c_str(), 1000, 1000)) { + LOGERR("chown %s error: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + if (setfilecon(install_path.c_str(), (security_context_t)context.c_str()) < 0) { + LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + } + install_path += "/me.twrp.twrpapp-1"; + if (mkdir(install_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + LOGERR("Error making %s directory: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + if (chown(install_path.c_str(), 1000, 1000)) { + LOGERR("chown %s error: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + if (setfilecon(install_path.c_str(), (security_context_t)context.c_str()) < 0) { + LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + install_path += "/base.apk"; + if (TWFunc::copy_file("/sbin/me.twrp.twrpapp.apk", install_path, 0644)) { + LOGERR("Error copying apk file\n"); + goto exit; + } + if (chown(install_path.c_str(), 1000, 1000)) { + LOGERR("chown %s error: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + if (setfilecon(install_path.c_str(), (security_context_t)context.c_str()) < 0) { + LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + sync(); + sync(); + } + } else { + if (PartitionManager.Mount_By_Path("/system", true)) { + string base_path = "/system"; + if (TWFunc::Path_Exists("/system/system")) + base_path += "/system"; // For devices with system as a root file system (e.g. Pixel) + string install_path = base_path + "/priv-app"; + string context = "u:object_r:system_file:s0"; + if (!TWFunc::Path_Exists(install_path)) + install_path = base_path + "/app"; + if (TWFunc::Path_Exists(install_path)) { + install_path += "/twrpapp"; + LOGINFO("Installing app to '%s'\n", install_path.c_str()); + if (mkdir(install_path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0) { + if (setfilecon(install_path.c_str(), (security_context_t)context.c_str()) < 0) { + LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + install_path += "/me.twrp.twrpapp.apk"; + if (TWFunc::copy_file("/sbin/me.twrp.twrpapp.apk", install_path, 0644)) { + LOGERR("Error copying apk file\n"); + goto exit; + } + if (setfilecon(install_path.c_str(), (security_context_t)context.c_str()) < 0) { + LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno)); + goto exit; + } + sync(); + sync(); + PartitionManager.UnMount_By_Path("/system", true); + op_status = 0; + } else { + LOGERR("Error making app directory '%s': %s\n", strerror(errno)); + } + } + } + } + } else + simulate_progress_bar(); +exit: + operation_end(0); + return 0; +} diff --git a/gui/animation.cpp b/gui/animation.cpp index d45373d81a..2244503e84 100644 --- a/gui/animation.cpp +++ b/gui/animation.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // animation.cpp - GUIAnimation object #include @@ -88,7 +106,7 @@ GUIAnimation::GUIAnimation(xml_node<>* node) : GUIObject(node) int GUIAnimation::Render(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (!mAnimation || !mAnimation->GetResource(mFrame)) return -1; @@ -99,7 +117,7 @@ int GUIAnimation::Render(void) int GUIAnimation::Update(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (!mAnimation) return -1; diff --git a/gui/blanktimer.cpp b/gui/blanktimer.cpp index 4662b1fab5..220525387c 100644 --- a/gui/blanktimer.cpp +++ b/gui/blanktimer.cpp @@ -116,3 +116,37 @@ void blanktimer::resetTimerAndUnblank(void) { pthread_mutex_unlock(&mutex); #endif } + +void blanktimer::blank(void) { +/* 1) No need for timer handling since checkForTimeout() verifies + * state of screen before performing screen-off + * 2) Assume screen-off causes issues for devices that set + * TW_NO_SCREEN_TIMEOUT and do not blank screen here either + */ + +#ifndef TW_NO_SCREEN_TIMEOUT + pthread_mutex_lock(&mutex); + if (state == kOn) { + orig_brightness = getBrightness(); + state = kOff; + TWFunc::Set_Brightness("0"); + TWFunc::check_and_run_script("/sbin/postscreenblank.sh", "blank"); + } +#ifndef TW_NO_SCREEN_BLANK + if (state == kOff) { + gr_fb_blank(true); + state = kBlanked; + } +#endif + pthread_mutex_unlock(&mutex); +#endif +} + +void blanktimer::toggleBlank(void) { + if (state == kOn) { + blank(); + PageManager::ChangeOverlay("lock"); + } else { + resetTimerAndUnblank(); + } +} diff --git a/gui/blanktimer.hpp b/gui/blanktimer.hpp index 5e617901fb..fe7b77c174 100644 --- a/gui/blanktimer.hpp +++ b/gui/blanktimer.hpp @@ -37,11 +37,15 @@ class blanktimer // call this when an input event is received or when an operation is finished void resetTimerAndUnblank(); + // call this when power button is pressed + void toggleBlank(void); + bool isScreenOff(); private: void setTimer(void); string getBrightness(void); + void blank(void); pthread_mutex_t mutex; enum State { kOn = 0, kDim = 1, kOff = 2, kBlanked = 3 }; diff --git a/gui/button.cpp b/gui/button.cpp index 45614e461d..a9b02a3054 100644 --- a/gui/button.cpp +++ b/gui/button.cpp @@ -85,7 +85,7 @@ GUIButton::GUIButton(xml_node<>* node) mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlightColor); - int x, y, w, h; + int x = 0, y = 0, w = 0, h = 0; TextPlacement = TOP_LEFT; if (mButtonImg) { mButtonImg->GetRenderPos(x, y, w, h); diff --git a/gui/checkbox.cpp b/gui/checkbox.cpp index e79f0cdcd8..a2958db991 100644 --- a/gui/checkbox.cpp +++ b/gui/checkbox.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // checkbox.cpp - GUICheckbox object #include diff --git a/gui/console.cpp b/gui/console.cpp index 6f375ff288..03628ec308 100644 --- a/gui/console.cpp +++ b/gui/console.cpp @@ -23,15 +23,9 @@ #include #include #include -#include -#include -#include -#include -#include -#include #include #include -#include +#include #include @@ -47,15 +41,30 @@ extern "C" { #define GUI_CONSOLE_BUFFER_SIZE 512 -size_t last_message_count = 0; -std::vector gMessages; +static pthread_mutex_t console_lock; +static size_t last_message_count = 0; +static std::vector gMessages; + +static std::vector gConsole; +static std::vector gConsoleColor; +static FILE* ors_file = NULL; -std::vector gConsole; -std::vector gConsoleColor; -static FILE* ors_file; +struct InitMutex +{ + InitMutex() { pthread_mutex_init(&console_lock, NULL); } +} initMutex; -extern "C" void __gui_print(const char *color, char *buf) +static void internal_gui_print(const char *color, char *buf) { + // make sure to flush any outstanding messages first to preserve order of outputs + GUIConsole::Translate_Now(); + + fputs(buf, stdout); + if (ors_file) { + fprintf(ors_file, "%s", buf); + fflush(ors_file); + } + char *start, *next; if (buf[0] == '\n' && strlen(buf) < 2) { @@ -63,6 +72,7 @@ extern "C" void __gui_print(const char *color, char *buf) return; } + pthread_mutex_lock(&console_lock); for (start = next = buf; *next != '\0';) { if (*next == '\n') @@ -78,10 +88,11 @@ extern "C" void __gui_print(const char *color, char *buf) } // The text after last \n (or whole string if there is no \n) - if(*start) { + if (*start) { gConsole.push_back(start); gConsoleColor.push_back(color); } + pthread_mutex_unlock(&console_lock); } extern "C" void gui_print(const char *fmt, ...) @@ -93,14 +104,7 @@ extern "C" void gui_print(const char *fmt, ...) vsnprintf(buf, GUI_CONSOLE_BUFFER_SIZE, fmt, ap); va_end(ap); - fputs(buf, stdout); - if (ors_file) { - fprintf(ors_file, "%s", buf); - fflush(ors_file); - } - - __gui_print("normal", buf); - return; + internal_gui_print("normal", buf); } extern "C" void gui_print_color(const char *color, const char *fmt, ...) @@ -112,14 +116,7 @@ extern "C" void gui_print_color(const char *color, const char *fmt, ...) vsnprintf(buf, GUI_CONSOLE_BUFFER_SIZE, fmt, ap); va_end(ap); - fputs(buf, stdout); - if (ors_file) { - fprintf(ors_file, "%s", buf); - fflush(ors_file); - } - - __gui_print(color, buf); - return; + internal_gui_print(color, buf); } extern "C" void gui_set_FILE(FILE* f) @@ -168,13 +165,20 @@ void gui_msg(Message msg) fprintf(ors_file, "%s", output.c_str()); fflush(ors_file); } + pthread_mutex_lock(&console_lock); gMessages.push_back(msg); + pthread_mutex_unlock(&console_lock); } -void GUIConsole::Translate_Now() { +void GUIConsole::Translate_Now() +{ + pthread_mutex_lock(&console_lock); size_t message_count = gMessages.size(); if (message_count <= last_message_count) + { + pthread_mutex_unlock(&console_lock); return; + } for (size_t m = last_message_count; m < message_count; m++) { std::string message = gMessages[m]; @@ -189,6 +193,16 @@ void GUIConsole::Translate_Now() { gConsoleColor.push_back(color); } last_message_count = message_count; + pthread_mutex_unlock(&console_lock); +} + +void GUIConsole::Clear_For_Retranslation() +{ + pthread_mutex_lock(&console_lock); + last_message_count = 0; + gConsole.clear(); + gConsoleColor.clear(); + pthread_mutex_unlock(&console_lock); } GUIConsole::GUIConsole(xml_node<>* node) : GUIScrollList(node) @@ -205,7 +219,10 @@ GUIConsole::GUIConsole(xml_node<>* node) : GUIScrollList(node) if (!node) { - mRenderX = 0; mRenderY = 0; mRenderW = gr_fb_width(); mRenderH = gr_fb_height(); + mRenderX = 0; + mRenderY = 0; + mRenderW = gr_fb_width(); + mRenderH = gr_fb_height(); } else { @@ -253,7 +270,9 @@ int GUIConsole::RenderSlideout(void) int GUIConsole::RenderConsole(void) { Translate_Now(); + pthread_mutex_lock(&console_lock); AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor); + pthread_mutex_unlock(&console_lock); GUIScrollList::Render(); // if last line is fully visible, keep tracking the last line when new lines are added @@ -276,7 +295,7 @@ int GUIConsole::RenderConsole(void) int GUIConsole::Render(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (mSlideout && mSlideoutState == hidden) @@ -304,7 +323,10 @@ int GUIConsole::Update(void) scrollToEnd = true; } - if (AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor)) { + pthread_mutex_lock(&console_lock); + bool addedNewText = AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor); + pthread_mutex_unlock(&console_lock); + if (addedNewText) { // someone added new text // at least the scrollbar must be updated, even if the new lines are currently not visible mUpdate = 1; @@ -346,7 +368,7 @@ int GUIConsole::IsInRegion(int x, int y) // Return 0 on success, >0 to ignore remainder of touch, and <0 on error int GUIConsole::NotifyTouch(TOUCH_STATE state, int x, int y) { - if(!isConditionTrue()) + if (!isConditionTrue()) return -1; if (mSlideout && x >= mSlideoutX && x < mSlideoutX + mSlideoutW && y >= mSlideoutY && y < mSlideoutY + mSlideoutH) { diff --git a/gui/devices/1080x1920/res/images/back-icon.png b/gui/devices/1080x1920/res/images/back-icon.png deleted file mode 100644 index 08e13b0f5c..0000000000 Binary files a/gui/devices/1080x1920/res/images/back-icon.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/checkbox_checked.png b/gui/devices/1080x1920/res/images/checkbox_checked.png deleted file mode 100644 index c1173de514..0000000000 Binary files a/gui/devices/1080x1920/res/images/checkbox_checked.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/checkbox_empty.png b/gui/devices/1080x1920/res/images/checkbox_empty.png deleted file mode 100644 index be5301f4fe..0000000000 Binary files a/gui/devices/1080x1920/res/images/checkbox_empty.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/cursor.png b/gui/devices/1080x1920/res/images/cursor.png deleted file mode 100644 index 39a7d6cdcd..0000000000 Binary files a/gui/devices/1080x1920/res/images/cursor.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/curtain.jpg b/gui/devices/1080x1920/res/images/curtain.jpg deleted file mode 100644 index 269941d5e9..0000000000 Binary files a/gui/devices/1080x1920/res/images/curtain.jpg and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/file.png b/gui/devices/1080x1920/res/images/file.png deleted file mode 100644 index 419ec10a58..0000000000 Binary files a/gui/devices/1080x1920/res/images/file.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/folder.png b/gui/devices/1080x1920/res/images/folder.png deleted file mode 100644 index 8fa307749b..0000000000 Binary files a/gui/devices/1080x1920/res/images/folder.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/home-icon.png b/gui/devices/1080x1920/res/images/home-icon.png deleted file mode 100644 index 23a02948cb..0000000000 Binary files a/gui/devices/1080x1920/res/images/home-icon.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/indeterminate001.png b/gui/devices/1080x1920/res/images/indeterminate001.png deleted file mode 100644 index 73918eeed9..0000000000 Binary files a/gui/devices/1080x1920/res/images/indeterminate001.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/indeterminate002.png b/gui/devices/1080x1920/res/images/indeterminate002.png deleted file mode 100644 index fbf230433e..0000000000 Binary files a/gui/devices/1080x1920/res/images/indeterminate002.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/indeterminate003.png b/gui/devices/1080x1920/res/images/indeterminate003.png deleted file mode 100644 index 8edb400d73..0000000000 Binary files a/gui/devices/1080x1920/res/images/indeterminate003.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/indeterminate004.png b/gui/devices/1080x1920/res/images/indeterminate004.png deleted file mode 100644 index 2810f8c1b4..0000000000 Binary files a/gui/devices/1080x1920/res/images/indeterminate004.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/indeterminate005.png b/gui/devices/1080x1920/res/images/indeterminate005.png deleted file mode 100644 index fe9bfa38fe..0000000000 Binary files a/gui/devices/1080x1920/res/images/indeterminate005.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/indeterminate006.png b/gui/devices/1080x1920/res/images/indeterminate006.png deleted file mode 100644 index e43aab7d11..0000000000 Binary files a/gui/devices/1080x1920/res/images/indeterminate006.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/keyboard1.png b/gui/devices/1080x1920/res/images/keyboard1.png deleted file mode 100644 index 5c273378af..0000000000 Binary files a/gui/devices/1080x1920/res/images/keyboard1.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/keyboard2.png b/gui/devices/1080x1920/res/images/keyboard2.png deleted file mode 100644 index fd3b628caa..0000000000 Binary files a/gui/devices/1080x1920/res/images/keyboard2.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/keyboard3.png b/gui/devices/1080x1920/res/images/keyboard3.png deleted file mode 100644 index 400e8e7286..0000000000 Binary files a/gui/devices/1080x1920/res/images/keyboard3.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/keyboard4.png b/gui/devices/1080x1920/res/images/keyboard4.png deleted file mode 100644 index 14c53fff39..0000000000 Binary files a/gui/devices/1080x1920/res/images/keyboard4.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/medium-button.png b/gui/devices/1080x1920/res/images/medium-button.png deleted file mode 100644 index 9eabb3b2f0..0000000000 Binary files a/gui/devices/1080x1920/res/images/medium-button.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/menu-button.png b/gui/devices/1080x1920/res/images/menu-button.png deleted file mode 100644 index 01fd37baac..0000000000 Binary files a/gui/devices/1080x1920/res/images/menu-button.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/minus-button.png b/gui/devices/1080x1920/res/images/minus-button.png deleted file mode 100644 index 2e4be8fa38..0000000000 Binary files a/gui/devices/1080x1920/res/images/minus-button.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/plus-button.png b/gui/devices/1080x1920/res/images/plus-button.png deleted file mode 100644 index 33ca71e65a..0000000000 Binary files a/gui/devices/1080x1920/res/images/plus-button.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/progress_empty.png b/gui/devices/1080x1920/res/images/progress_empty.png deleted file mode 100644 index 87735f935a..0000000000 Binary files a/gui/devices/1080x1920/res/images/progress_empty.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/progress_fill.png b/gui/devices/1080x1920/res/images/progress_fill.png deleted file mode 100644 index e359ea2231..0000000000 Binary files a/gui/devices/1080x1920/res/images/progress_fill.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/radio_empty.png b/gui/devices/1080x1920/res/images/radio_empty.png deleted file mode 100644 index 1b76f14602..0000000000 Binary files a/gui/devices/1080x1920/res/images/radio_empty.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/radio_selected.png b/gui/devices/1080x1920/res/images/radio_selected.png deleted file mode 100644 index f16f629569..0000000000 Binary files a/gui/devices/1080x1920/res/images/radio_selected.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/slideout.png b/gui/devices/1080x1920/res/images/slideout.png deleted file mode 100644 index 6fb41303d2..0000000000 Binary files a/gui/devices/1080x1920/res/images/slideout.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/slider-touch.png b/gui/devices/1080x1920/res/images/slider-touch.png deleted file mode 100644 index df02672f0e..0000000000 Binary files a/gui/devices/1080x1920/res/images/slider-touch.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/slider-used.png b/gui/devices/1080x1920/res/images/slider-used.png deleted file mode 100644 index a30331bfad..0000000000 Binary files a/gui/devices/1080x1920/res/images/slider-used.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/slider.png b/gui/devices/1080x1920/res/images/slider.png deleted file mode 100644 index e73cbf28d5..0000000000 Binary files a/gui/devices/1080x1920/res/images/slider.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/sort-button.png b/gui/devices/1080x1920/res/images/sort-button.png deleted file mode 100644 index e28c58c9e2..0000000000 Binary files a/gui/devices/1080x1920/res/images/sort-button.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/twrplogo.png b/gui/devices/1080x1920/res/images/twrplogo.png deleted file mode 100644 index a3f433a945..0000000000 Binary files a/gui/devices/1080x1920/res/images/twrplogo.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/images/unlock.png b/gui/devices/1080x1920/res/images/unlock.png deleted file mode 100644 index e582e1cf28..0000000000 Binary files a/gui/devices/1080x1920/res/images/unlock.png and /dev/null differ diff --git a/gui/devices/1080x1920/res/ui.xml b/gui/devices/1080x1920/res/ui.xml deleted file mode 100644 index 79bf5025d6..0000000000 --- a/gui/devices/1080x1920/res/ui.xml +++ /dev/null @@ -1,506 +0,0 @@ - - - -
- - TeamWin - Backup Naowz - Default basic theme - preview.jpg -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/gui/devices/1920x1200/res/images/back-icon.png b/gui/devices/1920x1200/res/images/back-icon.png deleted file mode 100644 index 5213597094..0000000000 Binary files a/gui/devices/1920x1200/res/images/back-icon.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/button.png b/gui/devices/1920x1200/res/images/button.png deleted file mode 100644 index b785f9969d..0000000000 Binary files a/gui/devices/1920x1200/res/images/button.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/checkbox_checked.png b/gui/devices/1920x1200/res/images/checkbox_checked.png deleted file mode 100644 index 2b7ad59195..0000000000 Binary files a/gui/devices/1920x1200/res/images/checkbox_checked.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/checkbox_empty.png b/gui/devices/1920x1200/res/images/checkbox_empty.png deleted file mode 100644 index 5c0b7bab3a..0000000000 Binary files a/gui/devices/1920x1200/res/images/checkbox_empty.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/console-icon.png b/gui/devices/1920x1200/res/images/console-icon.png deleted file mode 100644 index 1e800e6ac4..0000000000 Binary files a/gui/devices/1920x1200/res/images/console-icon.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/console-toggle.png b/gui/devices/1920x1200/res/images/console-toggle.png deleted file mode 100644 index 76d0e4888f..0000000000 Binary files a/gui/devices/1920x1200/res/images/console-toggle.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/cursor.png b/gui/devices/1920x1200/res/images/cursor.png deleted file mode 100644 index 39a7d6cdcd..0000000000 Binary files a/gui/devices/1920x1200/res/images/cursor.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/curtain.jpg b/gui/devices/1920x1200/res/images/curtain.jpg deleted file mode 100644 index 409664c6cc..0000000000 Binary files a/gui/devices/1920x1200/res/images/curtain.jpg and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/file.png b/gui/devices/1920x1200/res/images/file.png deleted file mode 100644 index 9d645770fc..0000000000 Binary files a/gui/devices/1920x1200/res/images/file.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/folder.png b/gui/devices/1920x1200/res/images/folder.png deleted file mode 100644 index 04017d2725..0000000000 Binary files a/gui/devices/1920x1200/res/images/folder.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/home-icon.png b/gui/devices/1920x1200/res/images/home-icon.png deleted file mode 100644 index a326d3005f..0000000000 Binary files a/gui/devices/1920x1200/res/images/home-icon.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/indeterminate001.png b/gui/devices/1920x1200/res/images/indeterminate001.png deleted file mode 100644 index 93916fc257..0000000000 Binary files a/gui/devices/1920x1200/res/images/indeterminate001.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/indeterminate002.png b/gui/devices/1920x1200/res/images/indeterminate002.png deleted file mode 100644 index 087bafe99c..0000000000 Binary files a/gui/devices/1920x1200/res/images/indeterminate002.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/indeterminate003.png b/gui/devices/1920x1200/res/images/indeterminate003.png deleted file mode 100644 index 881de5bd53..0000000000 Binary files a/gui/devices/1920x1200/res/images/indeterminate003.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/indeterminate004.png b/gui/devices/1920x1200/res/images/indeterminate004.png deleted file mode 100644 index 757a0cd1c1..0000000000 Binary files a/gui/devices/1920x1200/res/images/indeterminate004.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/indeterminate005.png b/gui/devices/1920x1200/res/images/indeterminate005.png deleted file mode 100644 index 3092415cac..0000000000 Binary files a/gui/devices/1920x1200/res/images/indeterminate005.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/indeterminate006.png b/gui/devices/1920x1200/res/images/indeterminate006.png deleted file mode 100644 index 25bbb3d1c8..0000000000 Binary files a/gui/devices/1920x1200/res/images/indeterminate006.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/keyboard1.png b/gui/devices/1920x1200/res/images/keyboard1.png deleted file mode 100644 index 7d42276e96..0000000000 Binary files a/gui/devices/1920x1200/res/images/keyboard1.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/keyboard2.png b/gui/devices/1920x1200/res/images/keyboard2.png deleted file mode 100644 index ff0600d559..0000000000 Binary files a/gui/devices/1920x1200/res/images/keyboard2.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/keyboard3.png b/gui/devices/1920x1200/res/images/keyboard3.png deleted file mode 100644 index 8c90d0c6d6..0000000000 Binary files a/gui/devices/1920x1200/res/images/keyboard3.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/keyboard4.png b/gui/devices/1920x1200/res/images/keyboard4.png deleted file mode 100644 index d0f3e414a7..0000000000 Binary files a/gui/devices/1920x1200/res/images/keyboard4.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/medium-button.png b/gui/devices/1920x1200/res/images/medium-button.png deleted file mode 100644 index 51593feff2..0000000000 Binary files a/gui/devices/1920x1200/res/images/medium-button.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/mediumwide-button.png b/gui/devices/1920x1200/res/images/mediumwide-button.png deleted file mode 100644 index b18b65334f..0000000000 Binary files a/gui/devices/1920x1200/res/images/mediumwide-button.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/minus-button.png b/gui/devices/1920x1200/res/images/minus-button.png deleted file mode 100644 index 7e86486a5c..0000000000 Binary files a/gui/devices/1920x1200/res/images/minus-button.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/plus-button.png b/gui/devices/1920x1200/res/images/plus-button.png deleted file mode 100644 index ccd96d9913..0000000000 Binary files a/gui/devices/1920x1200/res/images/plus-button.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/progress_empty.png b/gui/devices/1920x1200/res/images/progress_empty.png deleted file mode 100644 index dab79ea3a6..0000000000 Binary files a/gui/devices/1920x1200/res/images/progress_empty.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/progress_fill.png b/gui/devices/1920x1200/res/images/progress_fill.png deleted file mode 100644 index 1999130636..0000000000 Binary files a/gui/devices/1920x1200/res/images/progress_fill.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/radio_empty.png b/gui/devices/1920x1200/res/images/radio_empty.png deleted file mode 100644 index 10d0b94aa7..0000000000 Binary files a/gui/devices/1920x1200/res/images/radio_empty.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/radio_selected.png b/gui/devices/1920x1200/res/images/radio_selected.png deleted file mode 100644 index eabd5c4c58..0000000000 Binary files a/gui/devices/1920x1200/res/images/radio_selected.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/slider-touch.png b/gui/devices/1920x1200/res/images/slider-touch.png deleted file mode 100644 index 9f8b4f54b7..0000000000 Binary files a/gui/devices/1920x1200/res/images/slider-touch.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/slider-used.png b/gui/devices/1920x1200/res/images/slider-used.png deleted file mode 100644 index 5117203b3d..0000000000 Binary files a/gui/devices/1920x1200/res/images/slider-used.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/slider.png b/gui/devices/1920x1200/res/images/slider.png deleted file mode 100644 index ea2a272c85..0000000000 Binary files a/gui/devices/1920x1200/res/images/slider.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/sort-button.png b/gui/devices/1920x1200/res/images/sort-button.png deleted file mode 100644 index e10a3863b5..0000000000 Binary files a/gui/devices/1920x1200/res/images/sort-button.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/twrplogo.png b/gui/devices/1920x1200/res/images/twrplogo.png deleted file mode 100644 index 77c7712478..0000000000 Binary files a/gui/devices/1920x1200/res/images/twrplogo.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/images/unlock.png b/gui/devices/1920x1200/res/images/unlock.png deleted file mode 100644 index 11109bb061..0000000000 Binary files a/gui/devices/1920x1200/res/images/unlock.png and /dev/null differ diff --git a/gui/devices/1920x1200/res/ui.xml b/gui/devices/1920x1200/res/ui.xml deleted file mode 100644 index ae75f8de0c..0000000000 --- a/gui/devices/1920x1200/res/ui.xml +++ /dev/null @@ -1,491 +0,0 @@ - - - -
- - TeamWin - Backup Naowz - Default basic theme - preview.jpg -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/gui/devices/320x320/res/images/back-icon.png b/gui/devices/320x320/res/images/back-icon.png deleted file mode 100644 index 85b05e6642..0000000000 Binary files a/gui/devices/320x320/res/images/back-icon.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/checkbox_checked.png b/gui/devices/320x320/res/images/checkbox_checked.png deleted file mode 100644 index 6a130f2c95..0000000000 Binary files a/gui/devices/320x320/res/images/checkbox_checked.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/checkbox_empty.png b/gui/devices/320x320/res/images/checkbox_empty.png deleted file mode 100644 index 67fe20b3d3..0000000000 Binary files a/gui/devices/320x320/res/images/checkbox_empty.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/cursor.png b/gui/devices/320x320/res/images/cursor.png deleted file mode 100644 index 39a7d6cdcd..0000000000 Binary files a/gui/devices/320x320/res/images/cursor.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/curtain.jpg b/gui/devices/320x320/res/images/curtain.jpg deleted file mode 100644 index 0368a6574f..0000000000 Binary files a/gui/devices/320x320/res/images/curtain.jpg and /dev/null differ diff --git a/gui/devices/320x320/res/images/file.png b/gui/devices/320x320/res/images/file.png deleted file mode 100644 index 4e77eab636..0000000000 Binary files a/gui/devices/320x320/res/images/file.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/folder.png b/gui/devices/320x320/res/images/folder.png deleted file mode 100644 index 7797226d7b..0000000000 Binary files a/gui/devices/320x320/res/images/folder.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/home-icon.png b/gui/devices/320x320/res/images/home-icon.png deleted file mode 100644 index c14b53b564..0000000000 Binary files a/gui/devices/320x320/res/images/home-icon.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/indeterminate001.png b/gui/devices/320x320/res/images/indeterminate001.png deleted file mode 100644 index 63117b4a66..0000000000 Binary files a/gui/devices/320x320/res/images/indeterminate001.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/indeterminate002.png b/gui/devices/320x320/res/images/indeterminate002.png deleted file mode 100644 index 428caa01b9..0000000000 Binary files a/gui/devices/320x320/res/images/indeterminate002.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/indeterminate003.png b/gui/devices/320x320/res/images/indeterminate003.png deleted file mode 100644 index 0ce3504334..0000000000 Binary files a/gui/devices/320x320/res/images/indeterminate003.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/indeterminate004.png b/gui/devices/320x320/res/images/indeterminate004.png deleted file mode 100644 index 8372804655..0000000000 Binary files a/gui/devices/320x320/res/images/indeterminate004.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/indeterminate005.png b/gui/devices/320x320/res/images/indeterminate005.png deleted file mode 100644 index 9985f085bd..0000000000 Binary files a/gui/devices/320x320/res/images/indeterminate005.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/indeterminate006.png b/gui/devices/320x320/res/images/indeterminate006.png deleted file mode 100644 index 5f7ee99228..0000000000 Binary files a/gui/devices/320x320/res/images/indeterminate006.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/keyboard1.png b/gui/devices/320x320/res/images/keyboard1.png deleted file mode 100644 index 43d588a2da..0000000000 Binary files a/gui/devices/320x320/res/images/keyboard1.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/keyboard2.png b/gui/devices/320x320/res/images/keyboard2.png deleted file mode 100644 index 31807abbee..0000000000 Binary files a/gui/devices/320x320/res/images/keyboard2.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/keyboard3.png b/gui/devices/320x320/res/images/keyboard3.png deleted file mode 100644 index cbb1cfbb48..0000000000 Binary files a/gui/devices/320x320/res/images/keyboard3.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/keyboard4.png b/gui/devices/320x320/res/images/keyboard4.png deleted file mode 100644 index a6173baf2f..0000000000 Binary files a/gui/devices/320x320/res/images/keyboard4.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/medium-button.png b/gui/devices/320x320/res/images/medium-button.png deleted file mode 100644 index dec6945a22..0000000000 Binary files a/gui/devices/320x320/res/images/medium-button.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/menu-button.png b/gui/devices/320x320/res/images/menu-button.png deleted file mode 100644 index 80670123a4..0000000000 Binary files a/gui/devices/320x320/res/images/menu-button.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/minus-button.png b/gui/devices/320x320/res/images/minus-button.png deleted file mode 100644 index 8e74969773..0000000000 Binary files a/gui/devices/320x320/res/images/minus-button.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/plus-button.png b/gui/devices/320x320/res/images/plus-button.png deleted file mode 100644 index de8e3339bf..0000000000 Binary files a/gui/devices/320x320/res/images/plus-button.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/progress_empty.png b/gui/devices/320x320/res/images/progress_empty.png deleted file mode 100644 index cc65c388f6..0000000000 Binary files a/gui/devices/320x320/res/images/progress_empty.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/progress_fill.png b/gui/devices/320x320/res/images/progress_fill.png deleted file mode 100644 index bdce31f852..0000000000 Binary files a/gui/devices/320x320/res/images/progress_fill.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/radio_empty.png b/gui/devices/320x320/res/images/radio_empty.png deleted file mode 100644 index 00451f7984..0000000000 Binary files a/gui/devices/320x320/res/images/radio_empty.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/radio_selected.png b/gui/devices/320x320/res/images/radio_selected.png deleted file mode 100644 index 6db0504e32..0000000000 Binary files a/gui/devices/320x320/res/images/radio_selected.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/slideout.png b/gui/devices/320x320/res/images/slideout.png deleted file mode 100644 index a5a5fcbd0a..0000000000 Binary files a/gui/devices/320x320/res/images/slideout.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/slider-touch.png b/gui/devices/320x320/res/images/slider-touch.png deleted file mode 100644 index d06e99f6fb..0000000000 Binary files a/gui/devices/320x320/res/images/slider-touch.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/slider-used.png b/gui/devices/320x320/res/images/slider-used.png deleted file mode 100644 index 1d81962262..0000000000 Binary files a/gui/devices/320x320/res/images/slider-used.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/slider.png b/gui/devices/320x320/res/images/slider.png deleted file mode 100644 index f05205955f..0000000000 Binary files a/gui/devices/320x320/res/images/slider.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/sort-button.png b/gui/devices/320x320/res/images/sort-button.png deleted file mode 100644 index 7b4a535ab4..0000000000 Binary files a/gui/devices/320x320/res/images/sort-button.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/twrplogo.png b/gui/devices/320x320/res/images/twrplogo.png deleted file mode 100644 index d6a400ad73..0000000000 Binary files a/gui/devices/320x320/res/images/twrplogo.png and /dev/null differ diff --git a/gui/devices/320x320/res/images/unlock.png b/gui/devices/320x320/res/images/unlock.png deleted file mode 100644 index 4007e0d708..0000000000 Binary files a/gui/devices/320x320/res/images/unlock.png and /dev/null differ diff --git a/gui/devices/320x320/res/ui.xml b/gui/devices/320x320/res/ui.xml deleted file mode 100644 index 345a81c6aa..0000000000 --- a/gui/devices/320x320/res/ui.xml +++ /dev/null @@ -1,469 +0,0 @@ - - - -
- - - TeamWin - Backup Naowz - Default basic theme - preview.jpg -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/gui/devices/480x800/res/images/back-icon.png b/gui/devices/480x800/res/images/back-icon.png deleted file mode 100644 index 987d859cf0..0000000000 Binary files a/gui/devices/480x800/res/images/back-icon.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/checkbox_checked.png b/gui/devices/480x800/res/images/checkbox_checked.png deleted file mode 100644 index a9ccc1afae..0000000000 Binary files a/gui/devices/480x800/res/images/checkbox_checked.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/checkbox_empty.png b/gui/devices/480x800/res/images/checkbox_empty.png deleted file mode 100644 index 209100fcb4..0000000000 Binary files a/gui/devices/480x800/res/images/checkbox_empty.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/cursor.png b/gui/devices/480x800/res/images/cursor.png deleted file mode 100644 index 39a7d6cdcd..0000000000 Binary files a/gui/devices/480x800/res/images/cursor.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/curtain.jpg b/gui/devices/480x800/res/images/curtain.jpg deleted file mode 100644 index dfd59d96bc..0000000000 Binary files a/gui/devices/480x800/res/images/curtain.jpg and /dev/null differ diff --git a/gui/devices/480x800/res/images/file.png b/gui/devices/480x800/res/images/file.png deleted file mode 100644 index 5b06f74aab..0000000000 Binary files a/gui/devices/480x800/res/images/file.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/folder.png b/gui/devices/480x800/res/images/folder.png deleted file mode 100644 index a6def97dce..0000000000 Binary files a/gui/devices/480x800/res/images/folder.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/home-icon.png b/gui/devices/480x800/res/images/home-icon.png deleted file mode 100644 index a7594a7ace..0000000000 Binary files a/gui/devices/480x800/res/images/home-icon.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/indeterminate001.png b/gui/devices/480x800/res/images/indeterminate001.png deleted file mode 100644 index d3fe951703..0000000000 Binary files a/gui/devices/480x800/res/images/indeterminate001.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/indeterminate002.png b/gui/devices/480x800/res/images/indeterminate002.png deleted file mode 100644 index 08ed1ed31a..0000000000 Binary files a/gui/devices/480x800/res/images/indeterminate002.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/indeterminate003.png b/gui/devices/480x800/res/images/indeterminate003.png deleted file mode 100644 index cbe0bd6b35..0000000000 Binary files a/gui/devices/480x800/res/images/indeterminate003.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/indeterminate004.png b/gui/devices/480x800/res/images/indeterminate004.png deleted file mode 100644 index 0ec2357624..0000000000 Binary files a/gui/devices/480x800/res/images/indeterminate004.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/indeterminate005.png b/gui/devices/480x800/res/images/indeterminate005.png deleted file mode 100644 index 36ac401359..0000000000 Binary files a/gui/devices/480x800/res/images/indeterminate005.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/indeterminate006.png b/gui/devices/480x800/res/images/indeterminate006.png deleted file mode 100644 index a46811d8ac..0000000000 Binary files a/gui/devices/480x800/res/images/indeterminate006.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/keyboard1.png b/gui/devices/480x800/res/images/keyboard1.png deleted file mode 100644 index d40b65adac..0000000000 Binary files a/gui/devices/480x800/res/images/keyboard1.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/keyboard2.png b/gui/devices/480x800/res/images/keyboard2.png deleted file mode 100644 index 398e31fda3..0000000000 Binary files a/gui/devices/480x800/res/images/keyboard2.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/keyboard3.png b/gui/devices/480x800/res/images/keyboard3.png deleted file mode 100644 index 3667657911..0000000000 Binary files a/gui/devices/480x800/res/images/keyboard3.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/keyboard4.png b/gui/devices/480x800/res/images/keyboard4.png deleted file mode 100644 index 18244e2691..0000000000 Binary files a/gui/devices/480x800/res/images/keyboard4.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/medium-button.png b/gui/devices/480x800/res/images/medium-button.png deleted file mode 100644 index 7befcc9b2a..0000000000 Binary files a/gui/devices/480x800/res/images/medium-button.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/menu-button.png b/gui/devices/480x800/res/images/menu-button.png deleted file mode 100644 index 4f70b89df4..0000000000 Binary files a/gui/devices/480x800/res/images/menu-button.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/minus-button.png b/gui/devices/480x800/res/images/minus-button.png deleted file mode 100644 index 265cb85406..0000000000 Binary files a/gui/devices/480x800/res/images/minus-button.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/plus-button.png b/gui/devices/480x800/res/images/plus-button.png deleted file mode 100644 index 6d1434fdae..0000000000 Binary files a/gui/devices/480x800/res/images/plus-button.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/progress_empty.png b/gui/devices/480x800/res/images/progress_empty.png deleted file mode 100644 index 1a7471e448..0000000000 Binary files a/gui/devices/480x800/res/images/progress_empty.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/progress_fill.png b/gui/devices/480x800/res/images/progress_fill.png deleted file mode 100644 index a67e8ff2b4..0000000000 Binary files a/gui/devices/480x800/res/images/progress_fill.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/radio_empty.png b/gui/devices/480x800/res/images/radio_empty.png deleted file mode 100644 index e161e398f6..0000000000 Binary files a/gui/devices/480x800/res/images/radio_empty.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/radio_selected.png b/gui/devices/480x800/res/images/radio_selected.png deleted file mode 100644 index 57c0242ee2..0000000000 Binary files a/gui/devices/480x800/res/images/radio_selected.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/slideout.png b/gui/devices/480x800/res/images/slideout.png deleted file mode 100644 index 6f6cde5026..0000000000 Binary files a/gui/devices/480x800/res/images/slideout.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/slider-touch.png b/gui/devices/480x800/res/images/slider-touch.png deleted file mode 100644 index b4d25dad8d..0000000000 Binary files a/gui/devices/480x800/res/images/slider-touch.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/slider-used.png b/gui/devices/480x800/res/images/slider-used.png deleted file mode 100644 index 085307f37e..0000000000 Binary files a/gui/devices/480x800/res/images/slider-used.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/slider.png b/gui/devices/480x800/res/images/slider.png deleted file mode 100644 index 246eb03768..0000000000 Binary files a/gui/devices/480x800/res/images/slider.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/sort-button.png b/gui/devices/480x800/res/images/sort-button.png deleted file mode 100644 index e079700f9e..0000000000 Binary files a/gui/devices/480x800/res/images/sort-button.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/twrplogo.png b/gui/devices/480x800/res/images/twrplogo.png deleted file mode 100644 index 08acdddf2b..0000000000 Binary files a/gui/devices/480x800/res/images/twrplogo.png and /dev/null differ diff --git a/gui/devices/480x800/res/images/unlock.png b/gui/devices/480x800/res/images/unlock.png deleted file mode 100644 index 88e27bffe2..0000000000 Binary files a/gui/devices/480x800/res/images/unlock.png and /dev/null differ diff --git a/gui/devices/480x800/res/ui.xml b/gui/devices/480x800/res/ui.xml deleted file mode 100644 index ec9df7ed80..0000000000 --- a/gui/devices/480x800/res/ui.xml +++ /dev/null @@ -1,469 +0,0 @@ - - - -
- - TeamWin - Backup Naowz - Default basic theme - preview.jpg -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/gui/devices/800x480/res/images/back-icon.png b/gui/devices/800x480/res/images/back-icon.png deleted file mode 100644 index 5b093deb95..0000000000 Binary files a/gui/devices/800x480/res/images/back-icon.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/button.png b/gui/devices/800x480/res/images/button.png deleted file mode 100644 index 97b82b249c..0000000000 Binary files a/gui/devices/800x480/res/images/button.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/checkbox_checked.png b/gui/devices/800x480/res/images/checkbox_checked.png deleted file mode 100644 index b8d709f392..0000000000 Binary files a/gui/devices/800x480/res/images/checkbox_checked.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/checkbox_empty.png b/gui/devices/800x480/res/images/checkbox_empty.png deleted file mode 100644 index 9d84cfa5d5..0000000000 Binary files a/gui/devices/800x480/res/images/checkbox_empty.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/console-icon.png b/gui/devices/800x480/res/images/console-icon.png deleted file mode 100644 index da9536e8c3..0000000000 Binary files a/gui/devices/800x480/res/images/console-icon.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/console-toggle.png b/gui/devices/800x480/res/images/console-toggle.png deleted file mode 100644 index 6f6cde5026..0000000000 Binary files a/gui/devices/800x480/res/images/console-toggle.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/cursor.png b/gui/devices/800x480/res/images/cursor.png deleted file mode 100644 index 39a7d6cdcd..0000000000 Binary files a/gui/devices/800x480/res/images/cursor.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/curtain.jpg b/gui/devices/800x480/res/images/curtain.jpg deleted file mode 100644 index 1f03eb5e56..0000000000 Binary files a/gui/devices/800x480/res/images/curtain.jpg and /dev/null differ diff --git a/gui/devices/800x480/res/images/file.png b/gui/devices/800x480/res/images/file.png deleted file mode 100644 index 5b06f74aab..0000000000 Binary files a/gui/devices/800x480/res/images/file.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/folder.png b/gui/devices/800x480/res/images/folder.png deleted file mode 100644 index a6def97dce..0000000000 Binary files a/gui/devices/800x480/res/images/folder.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/home-icon.png b/gui/devices/800x480/res/images/home-icon.png deleted file mode 100644 index 30bdc8aa73..0000000000 Binary files a/gui/devices/800x480/res/images/home-icon.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/indeterminate001.png b/gui/devices/800x480/res/images/indeterminate001.png deleted file mode 100644 index d3fe951703..0000000000 Binary files a/gui/devices/800x480/res/images/indeterminate001.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/indeterminate002.png b/gui/devices/800x480/res/images/indeterminate002.png deleted file mode 100644 index 08ed1ed31a..0000000000 Binary files a/gui/devices/800x480/res/images/indeterminate002.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/indeterminate003.png b/gui/devices/800x480/res/images/indeterminate003.png deleted file mode 100644 index cbe0bd6b35..0000000000 Binary files a/gui/devices/800x480/res/images/indeterminate003.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/indeterminate004.png b/gui/devices/800x480/res/images/indeterminate004.png deleted file mode 100644 index 0ec2357624..0000000000 Binary files a/gui/devices/800x480/res/images/indeterminate004.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/indeterminate005.png b/gui/devices/800x480/res/images/indeterminate005.png deleted file mode 100644 index 36ac401359..0000000000 Binary files a/gui/devices/800x480/res/images/indeterminate005.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/indeterminate006.png b/gui/devices/800x480/res/images/indeterminate006.png deleted file mode 100644 index a46811d8ac..0000000000 Binary files a/gui/devices/800x480/res/images/indeterminate006.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/keyboard1.png b/gui/devices/800x480/res/images/keyboard1.png deleted file mode 100644 index 906e539f39..0000000000 Binary files a/gui/devices/800x480/res/images/keyboard1.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/keyboard2.png b/gui/devices/800x480/res/images/keyboard2.png deleted file mode 100644 index 8b25311dce..0000000000 Binary files a/gui/devices/800x480/res/images/keyboard2.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/keyboard3.png b/gui/devices/800x480/res/images/keyboard3.png deleted file mode 100644 index bb8a34709b..0000000000 Binary files a/gui/devices/800x480/res/images/keyboard3.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/keyboard4.png b/gui/devices/800x480/res/images/keyboard4.png deleted file mode 100644 index 0c1cb6638a..0000000000 Binary files a/gui/devices/800x480/res/images/keyboard4.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/medium-button.png b/gui/devices/800x480/res/images/medium-button.png deleted file mode 100644 index 6dbafb389f..0000000000 Binary files a/gui/devices/800x480/res/images/medium-button.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/mediumwide-button.png b/gui/devices/800x480/res/images/mediumwide-button.png deleted file mode 100644 index 69355e0b17..0000000000 Binary files a/gui/devices/800x480/res/images/mediumwide-button.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/minus-button.png b/gui/devices/800x480/res/images/minus-button.png deleted file mode 100644 index 63c58699c1..0000000000 Binary files a/gui/devices/800x480/res/images/minus-button.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/plus-button.png b/gui/devices/800x480/res/images/plus-button.png deleted file mode 100644 index da3e3f553d..0000000000 Binary files a/gui/devices/800x480/res/images/plus-button.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/progress_empty.png b/gui/devices/800x480/res/images/progress_empty.png deleted file mode 100644 index 1a7471e448..0000000000 Binary files a/gui/devices/800x480/res/images/progress_empty.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/progress_fill.png b/gui/devices/800x480/res/images/progress_fill.png deleted file mode 100644 index a67e8ff2b4..0000000000 Binary files a/gui/devices/800x480/res/images/progress_fill.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/radio_empty.png b/gui/devices/800x480/res/images/radio_empty.png deleted file mode 100644 index f3e2810b0a..0000000000 Binary files a/gui/devices/800x480/res/images/radio_empty.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/radio_selected.png b/gui/devices/800x480/res/images/radio_selected.png deleted file mode 100644 index c4900795ff..0000000000 Binary files a/gui/devices/800x480/res/images/radio_selected.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/slider-touch.png b/gui/devices/800x480/res/images/slider-touch.png deleted file mode 100644 index 4944fa772f..0000000000 Binary files a/gui/devices/800x480/res/images/slider-touch.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/slider-used.png b/gui/devices/800x480/res/images/slider-used.png deleted file mode 100644 index 5b2c462787..0000000000 Binary files a/gui/devices/800x480/res/images/slider-used.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/slider.png b/gui/devices/800x480/res/images/slider.png deleted file mode 100644 index 445d923f95..0000000000 Binary files a/gui/devices/800x480/res/images/slider.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/sort-button.png b/gui/devices/800x480/res/images/sort-button.png deleted file mode 100644 index 310cb4a375..0000000000 Binary files a/gui/devices/800x480/res/images/sort-button.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/twrplogo.png b/gui/devices/800x480/res/images/twrplogo.png deleted file mode 100644 index a362980d31..0000000000 Binary files a/gui/devices/800x480/res/images/twrplogo.png and /dev/null differ diff --git a/gui/devices/800x480/res/images/unlock.png b/gui/devices/800x480/res/images/unlock.png deleted file mode 100644 index c53181e697..0000000000 Binary files a/gui/devices/800x480/res/images/unlock.png and /dev/null differ diff --git a/gui/devices/800x480/res/ui.xml b/gui/devices/800x480/res/ui.xml deleted file mode 100644 index a561a3cc41..0000000000 --- a/gui/devices/800x480/res/ui.xml +++ /dev/null @@ -1,500 +0,0 @@ - - - -
- - TeamWin - Backup Naowz - Default basic theme - preview.jpg -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/gui/devices/common/res/fonts/DroidSansMono.ttf b/gui/devices/common/res/fonts/DroidSansMono.ttf deleted file mode 100644 index 4085cee84c..0000000000 Binary files a/gui/devices/common/res/fonts/DroidSansMono.ttf and /dev/null differ diff --git a/gui/devices/common/res/fonts/RobotoCondensed-Regular.ttf b/gui/devices/common/res/fonts/RobotoCondensed-Regular.ttf deleted file mode 100644 index b9fc49c95b..0000000000 Binary files a/gui/devices/common/res/fonts/RobotoCondensed-Regular.ttf and /dev/null differ diff --git a/gui/devices/common/res/images/progress_empty.png b/gui/devices/common/res/images/progress_empty.png deleted file mode 100644 index 1a7471e448..0000000000 Binary files a/gui/devices/common/res/images/progress_empty.png and /dev/null differ diff --git a/gui/devices/common/res/images/progress_fill.png b/gui/devices/common/res/images/progress_fill.png deleted file mode 100644 index 7f4121fe5f..0000000000 Binary files a/gui/devices/common/res/images/progress_fill.png and /dev/null differ diff --git a/gui/devices/common/res/languages/en.xml b/gui/devices/common/res/languages/en.xml deleted file mode 100644 index e47ee70509..0000000000 --- a/gui/devices/common/res/languages/en.xml +++ /dev/null @@ -1,235 +0,0 @@ - - - - English - - - - - - - - - - System - System Image - Vendor - Vendor Image - Boot - Recovery - Cache - Data - SDCard - Internal Storage - Micro SDCard - USB OTG - Android Secure - Dalvik / ART Cache - - SD-EXT - - - Install - - - Kernel does not have support for reading SELinux contexts. - Full SELinux support is present. - No SELinux support (no libselinux). - MTP Enabled - MTP Crashed, not starting MTP on boot. - Rebooting... - Successfully decrypted with default password. - Unable to decrypt with default password. You may need to perform a Format Data. - Generating MD5 - - * Generating md5... - * MD5 Created. - * MD5 Error! - * Error computing MD5. - (Current Date) - (Auto Generate) - Unable to locate '{1}' partition for backup calculations. - No partitions selected for backup. - * Total number of partitions to back up: {1} - * Total size of all data: {1}MB - * Available space: {1}MB - Unable to locate storage device. - Not enough free space on storage. - [BACKUP STARTED] - * Backup Folder: {1} - Failed to make backup folder. - Average backup rate for file systems: {1} MB/sec - Average backup rate for imaged drives: {1} MB/sec - [{1} MB TOTAL BACKED UP] - [BACKUP COMPLETED IN {1} SECONDS] - [RESTORE STARTED] - Restore folder: '{1}' - - [{1} done ({2} seconds)] - Verifying MD5 - Skipping MD5 check based on user setting. - Calculating restore details... - Cannot restore {1} -- mounted read only. - Unable to locate '{1}' partition for restoring. - No partitions selected for restore. - Restoring {1} partitions... - Total restore size is {1}MB - Updating System Details - Restore Complete - [RESTORE COMPLETED IN {1} SECONDS] - - Error opening: '{1}' ({2}) - Unable to locate partition by backup name: '{1}' - Unable to find partition for path '{1}' - Updating partition details... - ...done - Wiping Dalvik Cache Directories... - Cleaned: {1}... - -- Dalvik Cache Directories Wipe Complete! - No android secure partitions found. - Unable to locate {1}. - Wiping internal storage -- /data/media... - Unable to mount {1} - Unable to mount internal_storage - Unable to mount storage - Failed to decrypt data. - No crypto support was compiled into this build. - Data successfully decrypted, new block device: '{1}' - Done. - Partitioning SD Card... - Unable to locate device to partition. - EXT + Swap size is larger than sdcard size. - Removing partition table... - Unable to remove partition table. - Creating {1} partition... - Unable to create {1} partition. - Formatting sd-ext as {1}... - Partitioning complete. - Unable to open '{1}'. - MTP already enabled - Failed to enable MTP - MTP support not included - [IMAGE FLASH STARTED] - Image to flash: '{1}' - Unable to locate '{1}' partition for flashing. - No partitions selected for flashing. - Too many partitions selected for flashing. - Invalid flash partition specified. - [IMAGE FLASH COMPLETED] - Wiping {1} - {1} does not exist! Cannot repair! - Repairing {1} using {2}... - Unable to repair {1}. - Could not mount /data and unable to find crypto footer. - - Can not create '{1}' folder ({2}). - - Failed to mount '{1}' ({2}) - - Failed to unmount '{1}' ({2}) - Cannot resize {1}. - Repairing {1} before resizing. - Unable to resize {1}. - No md5 file found for '{1}'. Please unselect Enable MD5 verification to restore. - MD5 failed to match on '{1}'. - Failed to decrypt tar file '{1}' - You may need to reboot recovery to be able to use /data again. - Unable to format to remove encryption. - Formatting {1} using {2}... - Unable to wipe {1}. - Partition {1} cannot be wiped. - Removing all files under '{1}' - Wiping data without wiping /data/media ... - Backing up {1}... - Backing Up - Backup file size for '{1}' is 0 bytes. - WARNING: This /data backup was made with {1} file system! The backup may not boot unless you change back to {1}. - Restoring {1}... - Restoring - Unable to recreate {1} folder. - Size of image is larger than target device - Flashing {1}... - Backup folder set to '{1}' - Unable to locate backup '{1}' - Setting restore options: '{1}': - MD5 check skip is on - Unable to use OpenRecoveryScript to restore an encrypted backup. - Mounting - Unmounting - Mounted '{1}' - Unmounted '{1}' - Setting '{1}' to '{2}' - Setting '{1}' to empty - Making Directory - Making directory: '{1}' - Running Command - ADB Sideload - Starting ADB sideload feature... - You need adb 1.0.32 or newer to sideload to this device. - No password provided. - Done processing script file - Injecting TWRP into boot image... - Error installing zip file '{1}' - Installing zip file '{1}' - Setting backup options: - Compression is on - MD5 Generation is off - Backup Failed - Backup Failed. Cleaning Backup Folder. - Running Recovery Commands - Recovery Commands Complete - Running OpenRecoveryScript - OpenRecoveryScript Complete - Could not find '{1}' in the zip file. - Checking for MD5 file... - Failed to map file '{1}' - Verifying zip signature... - Zip signature verification failed! - Zip signature verified successfully. - Zip file is corrupt! - Skipping MD5 check: no MD5 file found - MD5 does not match - MD5 matched - {1} process ended with signal: {2} - {1} process ended with ERROR: {2} - Installing HTC Dumlock to system... - Restoring original boot... - Reflashing recovery to boot... - Running {1} script... - Renamed stock recovery file in /system to prevent the stock ROM from replacing TWRP. - Breaking backup file into multiple archives... - Error creating backup. - Error during restore process. - Splitting thread ID {1} into archive {2} - - %llu of %llu files, %i%% - %lluMB of %lluMB, %i%% - Attempting to decrypt data partition via command line. - Failed to load base packages. - Simulating actions... - Backup Cancelled - Configuring TWRP... - Unable to configure TWRP with this kernel. - Copied recovery log to {1}. - Maximum zip queue reached! - Minimum zip queue reached! - Failed to take a screenshot! - One or more zip requested a cache wipe -- Wiping cache now. - Unable to wipe android secure - Failed to wipe dalvik - (Auto Generate) - (Current Date) - Backup name is too long. - Backup name '{1}' contains invalid character: '{1}' - A backup with that name already exists! - This device does not have a real SD Card! Aborting! - Cancelling ADB sideload... - Error changing file system. - Custom theme version does not match TWRP version. Using stock theme. - Rebooting in 5 seconds - Error with ADB Backup. Quitting..." - Cannot write to adb control channel - --twrp option is required to enable twrp adb backup - path: {1} not found in partititon list - - diff --git a/gui/devices/common/res/languages/es.xml b/gui/devices/common/res/languages/es.xml deleted file mode 100644 index ad5c54a7bc..0000000000 --- a/gui/devices/common/res/languages/es.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - Español - - - - - - - Instalar - - diff --git a/gui/devices/common/res/languages/fi.xml b/gui/devices/common/res/languages/fi.xml deleted file mode 100644 index 0c4f5bc873..0000000000 --- a/gui/devices/common/res/languages/fi.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - Suomi - - - - - - - Asenna - - diff --git a/gui/devices/landscape/res/landscape.xml b/gui/devices/landscape/res/landscape.xml deleted file mode 100644 index c2a117dfa2..0000000000 --- a/gui/devices/landscape/res/landscape.xml +++ /dev/null @@ -1,3721 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - - - - Install - - - install - - - - - - Backup - backup - - - - - Restore - restore - - - - - Wipe - wipe - - - - - Mount - mount - - - - - Settings - settings - - - - - Advanced - advanced - - - - - Reboot - reboot - - - - - - - - - - - Select Zip to Install - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=install - selectstorage - - - - - - Folders: - - - - - - - - %tw_zip_location% - - - - - - - - - - - Images... - - install_image - - - - - - - - flash_confirm - - - - - - main - - - - - main - - - - - - - - - - - WARNING: This operation may install incompatible software and render your device unusable. - - - - - Folder: - - - - - %tw_zip_location% - - - - - File to flash: - - - - - %tw_file% - - - - - Press back to cancel adding this zip. - - - - - Zip file signature verification? - - - - - - File %tw_zip_queue_count% of max of 10 - - - - - - Add More Zips - install - - - - - Clear Queue - - - install - - - - - Swipe to Confirm Flash - flash_zip - - - - - - - install - - - - - - main - - - - - - - - - - - - - Flashing file %tw_zip_index% of %tw_zip_queue_count% - - - - - %tw_filename% - - - - - - - flash_done - - - - - - - - - - - - - Failed - - - - - - - Successful - - - - - Wipe Cache/Dalvik - - tw_back=flash_done - tw_action=wipe - tw_action_param=/cache - tw_has_action2=1 - tw_action2=wipe - tw_action2_param=dalvik - tw_text1=Wipe Cache & Dalvik? - tw_action_text1=Wiping Cache & Dalvik... - tw_complete_text1=Cache & Dalvik Wipe Complete - tw_slider_text=Swipe to Wipe - confirm_action - - - - - - - - - - Home - - tw_clear_destination=main2 - clear_vars - - - - - - - tw_clear_destination=install - clear_vars - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - - - - Select Image to Flash - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=install_image - selectstorage - - - - - - Folders: - - - - - - - - %tw_zip_location% - - - - - - - - - - - Zips... - - install - - - - - - flashimage_confirm - - - - - install - - - - - main - - - - - - - - - - - Select Partition to Flash Image: - - - - - - - - Folder: - - - - - %tw_zip_location% - - - - - File to flash: - - - - - %tw_file% - - - - Swipe to Confirm Flash - - tw_back=flashimage_confirm - tw_action=flashimage - tw_has_action2=0 - tw_action_text1=Flashing Image... - tw_action_text2= - tw_complete_text1=Image Flashed - action_page - - - - - - - - tw_clear_destination=install_image - clear_vars - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - - - tw_operation_state=0 - tw_text1= - tw_text2= - tw_text3= - tw_text4= - tw_action_text1= - tw_action_text2= - tw_action_param= - tw_has_action2=0 - tw_action2= - tw_action2_param= - tw_has_cancel=0 - tw_cancel_action= - tw_cancel_param= - tw_show_exclamation=0 - tw_show_reboot=0 - %tw_clear_destination% - - - - - - - - - %tw_text1% - - - - - %tw_text2% - - - - - %tw_text3% - - - - - %tw_text4% - - - - - Press back button to cancel. - - - - %tw_slider_text% - action_page - - - - - - tw_clear_destination=%tw_back% - clear_vars - - - - - - main - - - - - - - - - - - %tw_action_text1% - - - - - %tw_action_text2% - - - - - - - - Cancel - %tw_cancel_param% - - - - - - - - action_complete - - - - - - - %tw_action_param% - - - - - - - %tw_action_param% - %tw_action2_param% - - - - - - - - - - %tw_action_text1% - - - - - %tw_action_text2% - - - - - - - - - - tw_page_done=1 - - - - - - - %tw_action_param% - - - - - - - %tw_action_param% - %tw_action2_param% - - - - - - - - - - %tw_complete_text1% - - - - - - - Failed - - - - - - - Successful - - - - - - - - Back - - tw_clear_destination=%tw_back% - clear_vars - - - - - - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - tw_clear_destination=%tw_back% - clear_vars - - - - - - - %tw_filecheck% - - - - - - - - - tw_fileexists=1 - %tw_existpage% - - - - - - - - - - tw_fileexists=0 - %tw_notexistpage% - - - - - - - - %tw_action_param% - - - - - confirm_action - - - - - - - - - Reboot Menu - - - - - System - - - - - - Power Off - - tw_back=reboot - tw_action=reboot - tw_action_param=poweroff - tw_has_action2=0 - tw_text1=No OS Installed! Are you - tw_text2=sure you wish to power off? - tw_action_text1=Turning Off... - tw_complete_text1=Turning Off... - tw_slider_text=Swipe to Power Off - rebootcheck - - - - - - - Recovery - - tw_back=reboot - tw_action=reboot - tw_action_param=recovery - tw_has_action2=0 - tw_text1=No OS Installed! Are you - tw_text2=sure you wish to reboot? - tw_action_text1=Rebooting... - tw_complete_text1=Rebooting... - tw_slider_text=Swipe to Reboot - rebootcheck - - - - - - - Bootloader - - tw_back=reboot - tw_action=reboot - tw_action_param=bootloader - tw_has_action2=0 - tw_text1=No OS Installed! Are you - tw_text2=sure you wish to reboot? - tw_action_text1=Rebooting... - tw_complete_text1=Rebooting... - tw_slider_text=Swipe to Reboot - rebootcheck - - - - - - - Download - - tw_back=reboot - tw_action=reboot - tw_action_param=download - tw_has_action2=0 - tw_text1=No OS Installed! Are you - tw_text2=sure you wish to reboot? - tw_action_text1=Rebooting... - tw_complete_text1=Rebooting... - tw_slider_text=Swipe to Reboot - rebootcheck - - - - - - main - - - - - main - - - - - - - - - - - Select Storage: - - - - - - - - OK - - tw_clear_destination=%tw_back% - clear_vars - - - - - - main - - - - - - %tw_back% - - - - - - - - - - - - Mount Menu - - - - - Select Partitions to Mount: - - - - - - - - - - Decrypt Data - decrypt - - - - - - Mount USB Storage - usb_mount - - - - - - - - - Enable MTP - - - - - - - - - - Disable MTP - - - - - - - - - - Decrypt Data - decrypt - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=mount - selectstorage - - - - - - - - Only mount system read-only - - 1 - - - - - - - Only mount system read-only - - - tw_lifetime_writes=2 - system_readonly_check - - - - - - main - - - - - main - - - - - - - - - - - USB Storage Mounted -- Be sure to safely remove your device from your computer before unmounting! - - - - - Unmount - usb_umount - - - - usb - - - - - - - - usb - - - - mount - - - - - - - - /system - - - - - - 0 - mount - - - - - - - tw_back=mount - system_readonly - - - - - - - - - - Factory Reset: Wipes Data, Cache, and Dalvik - - - - - - (not including internal storage) - - - - - - - - - Android Secure - - - - - - SD-EXT - - - - - Most of the time this is the only wipe that you need. - - - - - Advanced Wipe - advancedwipe - - - - - - Format Data - - formatdata - - - - - - - - - - Wipe Encryption - - tw_back=wipe - tw_action=wipe - tw_action_param=DATAMEDIA - tw_has_action2=0 - tw_text1=Wipe Encryption from Data? - tw_text2= - tw_action_text1=Formatting Data... - tw_complete_text1=Data Format Complete - tw_slider_text=Swipe to Format Data - confirm_action - - - - - - Press back button to cancel. - - - - Swipe to Factory Reset - - tw_back=wipe - tw_action=wipe - tw_action_param=data - tw_action_text1=Factory Reset... - tw_complete_text1=Factory Reset Complete - action_page - - - - - - main - - - - - main - - - - - - - - - - - Select Partitions to Wipe: - - - - - - - Repair or Change File System - - - checkpartitionlist - - - - - - - - Invalid partition selection - - - - Swipe to Wipe - - tw_back=advancedwipe - tw_action=wipe - tw_action_param=LIST - tw_text1=Wipe Selected Partition(s)? - tw_action_text1=Wiping Partition(s)... - tw_complete_text1=Wipe Complete - action_page - - - - - - main - - - - - wipe - - - - - - - - - - - Format Data will wipe all of your apps, backups, pictures, - - - - - videos, media, and removes encryption on internal storage. - - - - - This cannot be undone. Press back to cancel. - - - - - Type yes to continue. - - - - - %tw_confirm_formatdata% - - - formatdata_confirm - - - - - - - main - - - - - wipe - - - - - - - - - - tw_back=formatdata - tw_action=wipe - tw_action_param=DATAMEDIA - tw_action_text1=Formatting Data... - tw_complete_text1=Data Format Complete - action_page - - - - - - formatdata - - - - - - - - partitionlisterror=0 - - partitionoptions - - - - - - - partitionlisterror=1 - tw_wipe_list= - advancedwipe - - - - - - - - - - Partition Options for: %tw_partition_name% - - - - - Mount Point: %tw_partition_mount_point% - - - - - Current file system: %tw_partition_file_system% - - - - - - Present: Yes - - - - - - Present: No - - - - - - Removable: Yes - - - - - - Removable: No - - - - - Size: %tw_partition_size%MB - - - - - Used: %tw_partition_used%MB - - - - - Free: %tw_partition_free%MB - - - - - Backup Size: %tw_partition_backup_size%MB - - - - - - Resize - - tw_back=partitionoptions - tw_action=resize - tw_action_param=%tw_partition_mount_point% - tw_has_action2=1 - tw_action2=getpartitiondetails - tw_text1=Resize %tw_partition_name%? - tw_text2= - tw_action_text1=Resizing... - tw_complete_text1=Resize Complete - tw_slider_text=Swipe to Resize - confirm_action - - - - - - - Repair - - tw_back=partitionoptions - tw_action=repair - tw_action_param=%tw_partition_mount_point% - tw_has_action2=0 - tw_text1=Repair %tw_partition_name%? - tw_text2= - tw_action_text1=Repairing... - tw_complete_text1=Repair Complete - tw_slider_text=Swipe to Repair - confirm_action - - - - - - Change File System - selectfilesystem - - - - - main - - - - - advancedwipe - - - - - - - - - - partitionlisterror=0 - - selectfilesystem - - - - - - - partitionlisterror=1 - tw_wipe_list= - advancedwipe - - - - - - - - - - Change file system for: %tw_partition_name% - - - - - Mount Point: %tw_partition_mount_point% - - - - - Current file system: %tw_partition_file_system% - - - - - Some ROMs or kernels may not support some file systems. Proceed with caution! - - - - - - EXT2 - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=ext2 - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to EXT2? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - EXT3 - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=ext3 - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to EXT3? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - EXT4 - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=ext4 - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to EXT4? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - F2FS - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=f2fs - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to F2FS? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - FAT - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=vfat - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to FAT? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - exFAT - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=exfat - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to exFAT? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - main - - - - - partitionoptions - - - - - - - - - - - Back Up Device - - - - - Select Partitions to Back Up: - - - - - - - Backup Name: %tw_backup_name% - - - - - Refresh Sizes - - - backup - - - - - - Set Backup Name - - tw_fileexists=0 - backupname1 - - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=backup - selectstorage - - - - - - - - - - No Encryption - - backupencryption - - - - - - - - - - Using Encryption - - backupencryption - - - - - - Enable Compression (Requires more time) - - - - - - Skip MD5 generation on backups - - - - - - - Disable Free Space Check. - - - - - - Swipe to Back Up - - tw_operation_state=0 - backup_run - - - - - - main - - - - - main - - - - - - - - - - - - - backupname2 - - - - - - - - - Please Enter a Backup Name - - - - - %tw_backup_name% - - - - tw_filecheck=%tw_backups_folder%/%tw_backup_name% - tw_existpage=backupname2 - tw_notexistpage=backup - filecheck - - - - - - - - A backup with that name already exists! - - - - - Append Date - - - - - - Cancel / Clear - - tw_backup_name=(Auto Generate) - backup - - - - - - - - - tw_backup_name=(Auto Generate) - main - - - - - - - tw_backup_name=(Auto Generate) - backup - - - - - - - - - - - - Encrypt your backup? Please enter a password: - - - - - %tw_backup_encrypt_display% - - - backupencryption2 - - - - - - - Passwords Do Not Match - - - - - Cancel - - tw_encrypt_backup=0 - tw_backup_password= - tw_backup_password2= - tw_backup_encrypt_display= - tw_backup_encrypt_display2= - backup - - - - - - - - main - - - - - backup - - - - - - - - - - - Encrypt your backup? Please Enter Password Again: - - - - - %tw_backup_encrypt_display2% - - - - checkbackuppassword - - - - - - Cancel - - tw_encrypt_backup=0 - tw_backup_password= - tw_backup_password2= - tw_backup_encrypt_display= - tw_backup_encrypt_display2= - backup - - - - - - - - main - - - - - backup - - - - - - - - - - tw_encrypt_backup=1 - backup - - - - - - - tw_encrypt_backup=0 - tw_password_not_match=1 - tw_backup_password= - tw_backup_password2= - tw_backup_encrypt_display= - tw_backup_encrypt_display2= - backupencryption - - - - - - - - - - %tw_operation% %tw_partition% - - - - - %tw_file_progress% - - - - - %tw_size_progress% - - - - - - - Cancel - - - - - - - - - backup - - - - - - - tw_back=backup - tw_complete_text1=Backup Complete - tw_show_reboot=1 - action_complete - - - - - - - - tw_back=backup - tw_complete_text1=Backup Cancelled - tw_show_reboot=1 - action_complete - - - - - - - - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=restore - selectstorage - - - - - - Select Package to Restore: - - - - - - - - - - - - - restore_read - - - - - - main - - - - - main - - - - - - - - - - tw_password_fail=0 - restore_decrypt - - - - - - - restore_select - - - - - - - - - - Backup encrypted. Please enter your password: - - - - - %tw_restore_display% - - - - try_restore_decrypt - - - - - - - - Password Failed, Please Try Again - - - - - Cancel - - tw_page_done=1 - restore - - - - - - Delete - - tw_back=restore - tw_action=cmd - tw_action_param=cd %tw_backups_folder% && rm -rf "%tw_restore_name%" - tw_text1=Delete Backup? %tw_restore_name% - tw_text2=This cannot be undone! - tw_action_text1=Deleting Backup... - tw_complete_text1=Backup Delete Complete - tw_slider_text=Swipe to Delete - confirm_action - - - - - - - - main - - - - - restore - - - - - - - - - - - Trying Decryption with Your Password - - - - - - - - - - - - - - - - - tw_password_fail=1 - restore_decrypt - - - - - - - - - - tw_page_done=1 - restore_select - - - - - - - - - - Package to Restore: %tw_restore_name% - - - - - Package Date: %tw_restore_file_date% - - - - - Select Partitions to Restore: - - - - - - - Enable MD5 checking of backup files - - - - - - Rename Backup - - tw_backup_rename= - tw_fileexists=0 - renamebackup - - - - - - Delete Backup - - tw_back=restore - tw_action=cmd - tw_action_param=cd %tw_backups_folder% && rm -rf "%tw_restore_name%" - tw_text1=Delete Backup? %tw_restore_name% - tw_text2=This cannot be undone! - tw_action_text1=Deleting Backup... - tw_complete_text1=Backup Delete Complete - tw_slider_text=Swipe to Delete - confirm_action - - - - - Swipe to Restore - restore_run - - - - - main - - - - - restore - - - - - - - - - - - Please Enter a New Backup Name - - - - - %tw_backup_rename% - - - - tw_back=restore - tw_action=cmd - tw_action_param=cd %tw_backups_folder% && mv "%tw_restore_name%" "%tw_backup_rename%" - tw_text1=Rename Backup? - tw_text2=This cannot be undone! - tw_action_text1=Renaming Backup... - tw_complete_text1=Backup Rename Complete - tw_slider_text=Swipe to Rename - tw_filecheck=%tw_backups_folder%/%tw_backup_rename% - tw_existpage=renamebackup - tw_notexistpage=confirm_action - filecheck - - - - - - - - A backup with that name already exists! - - - - - Cancel - restore_select - - - - - - - main - - - - - restore_select - - - - - - - - - - - %tw_operation% %tw_partition% - - - - - %tw_size_progress% - - - - - - - - - - tw_back=restore_select - tw_complete_text1=Restore Complete - tw_show_reboot=1 - action_complete - - - - - restore - - - - - - - - - Settings - - - - - Zip file signature verification? - > - - - - - Use rm -rf instead of formatting? - - - - - - Skip MD5 generation on backups - - - - - - Enable MD5 checking of backup files - - - - - - Use 24-hour clock - - - - - - Simulate most actions for theme testing - - - - - - - Simulate failure for actions - - - - - - Time Zone - timezone - - - - - Restore Defaults - - - - - - Vibration Duration - Vibrate - - - - - Screen - screen - - - - - main - - - - - main - - - - - - - - - - - Select Time Zone - - - - - Select Time Zone: - - - BST11;BDT - HST10;HDT - AST9;ADT - PST8;PDT,M3.2.0,M11.1.0 - MST7;MDT,M3.2.0,M11.1.0 - CST6;CDT,M3.2.0,M11.1.0 - EST5;EDT,M3.2.0,M11.1.0 - AST4;ADT - GRNLNDST3;GRNLNDDT - FALKST2;FALKDT - AZOREST1;AZOREDT - GMT0;BST,M3.5.0,M10.5.0 - CET-1;CEST,M3.5.0,M10.5.0 - WET-2;WET,M3.5.0,M10.5.0 - SAUST-3;SAUDT - WST-4;WDT - PAKST-5;PAKDT - TASHST-6;TASHDT - THAIST-7;THAIDT - TAIST-8;TAIDT - JST-9;JSTDT - EET-10;EETDT - MET-11;METDT - NZST-12;NZDT - - - - - Do you use daylight savings time (DST)? - - - - - - Offset (usually 0): %tw_time_zone_guioffset% - - - - - 0 - tw_time_zone_guioffset=0 - - - - - 15 - tw_time_zone_guioffset=15 - - - - - 30 - tw_time_zone_guioffset=30 - - - - - 45 - tw_time_zone_guioffset=45 - - - - - Set Time Zone - - - - - - Current Time Zone: %tw_time_zone% - - - - - main - - - - - settings - - - - - - - - - - - Screen Settings - - - - - - - Enable screen timeout. - - tw_screen_timeout_secs=60 - - - - - - - Enable screen timeout. - - tw_screen_timeout_secs=0 - - - - - - - - - Screen timeout in seconds: - - - - - - - Brightness: %tw_brightness_pct%% - - - tw_brightness=%tw_brightness_max% - tw_brightness*%tw_brightness_pct% - tw_brightness/100 - %tw_brightness% - - - - - - main - - - - - settings - - - - - - - - - - - Vibration Settings : - - - - - Button Vibration: - - - - - - - Keyboard Vibration: - - - - - - - Action Vibration: - - - - - - - main - - - - - settings - - - - - - - - - - - Advanced - - - - - Copy Log to SD - - tw_back=advanced - tw_action=copylog - tw_text1=Copy Log to SD Card? - tw_action_text1=Copying Log to SD Card... - tw_complete_text1=Log Copy Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - Fix Permissions - fixperms - - - - - Terminal Command - terminalfolder - - - - - ADB Sideload - sideload - - - - - - Partition SD Card - partsdcard - - - - - File Manager - filemanagerlist - - - - - Reload Theme - - - - - - - HTC Dumlock - htcdumlock - - - - - main - - - - - main - - - - - - - - - - - Partition SD Card - - - - - - - tw_sdext_size-128 - - - - - - - tw_sdext_size+128 - - - - - EXT Size: %tw_sdext_size% - - - - - - - tw_swap_size-32 - - - - - - - tw_swap_size+32 - - - - - Swap Size: %tw_swap_size% - - - - - File system: %tw_sdpart_file_system% - - - - - EXT3 - tw_sdpart_file_system=ext3 - - - - - - EXT4 - tw_sdpart_file_system=ext4 - - - - - You will lose all files on your SD card! - - - - - This action cannot be undone! - - - - Swipe to Confirm Partition - - tw_back=partsdcard - tw_action=partitionsd - tw_has_action2=1 - tw_action2=set - tw_action2_param=tw_zip_location=/sdcard - tw_action_text1=Partitioning SD Card... - tw_action_text2=This will take a few minutes. - tw_complete_text1=Partitioning Complete - action_page - - - - - - main - - - - - advanced - - - - - - - - - - - - HTC Dumlock - - - - - Restore Original Boot - - tw_back=htcdumlock - tw_action=htcdumlockrestoreboot - tw_text1=Restore original boot image? - tw_action_text1=Restoring Original Boot... - tw_complete_text1=Restore Original Boot Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - Reflash Recovery->Boot - - tw_back=htcdumlock - tw_action=htcdumlockreflashrecovery - tw_text1=Reflash recovery to boot? - tw_action_text1=Flashing recovery to boot... - tw_complete_text1=Recovery Flash to Boot Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - Install HTC Dumlock - - tw_back=htcdumlock - tw_action=installhtcdumlock - tw_text1=Install HTC dumlock files to ROM? - tw_action_text1=Installing HTC Dumlock... - tw_complete_text1=HTC Dumlock Install Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - main - - - - - advanced - - - - - - - - - - - - - - - Swipe to Unlock - - - - - - - - - - - File Manager: Select a File or Folder - - - - - Folders: - - - - - - - - - %tw_file_location1% - - - - - - - - - - - tw_fm_type=File - tw_fm_isfolder=0 - - - - - - main - - - - - advanced - - - - - - filemanageroptions - - - - - - Select Folder - - tw_filename1=tw_file_location1 - tw_fm_isfolder=1 - tw_fm_type=Folder - filemanageroptions - - - - - - - - - - - - %tw_fm_type% Selected: - - - - - %tw_filename1% - - - - - - Copy File - - tw_filemanager_command=cp - tw_fm_text1=Copying - choosedestinationfolder - - - - - - - Copy Folder - - tw_filemanager_command=cd "%tw_file_location1%" && cd .. && cp -R - tw_fm_text1=Copying - choosedestinationfolder - - - - - - Move - - tw_filemanager_command=mv - tw_fm_text1=Moving - choosedestinationfolder - - - - - - chmod 755 - - tw_filemanager_command=chmod 755 - tw_fm_text1=chmod 755 - tw_fm_text2= - tw_fm_text3= - tw_include_text3=0 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - chmod - - tw_filemanager_rename=0000 - tw_fm_text2= - tw_fm_text3= - tw_include_text3=0 - tw_back=filemanageroptions - filemanagerchmod - - - - - - Delete - - tw_filemanager_command=rm -rf - tw_fm_text1=Deleting - tw_fm_text2= - tw_fm_text3= - tw_include_text3=0 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - - Rename File - - tw_filemanager_rename=tw_selection1 - tw_fm_text1=Renaming - tw_filemanager_command=mv - filemanagerrenamefile - - - - - - - Rename Folder - - tw_filemanager_rename=tw_selection1 - tw_fm_text1=Renaming - tw_filemanager_command=cd "%tw_file_location1%" && cd .. && mv - filemanagerrenamefolder - - - - - - main - - - - - filemanagerlist - - - - - - - - - - - Browse to Destination Folder & Press Select - - - - - %tw_file_location2% - - - - - - - - - - - main - - - - - filemanageroptions - - - - - Select Folder - - tw_fm_text2=to - tw_fm_text3=%tw_file_location2% - tw_include_text3=1 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - - - - - - - - Please Enter a New %tw_fm_type% Name - - - - - %tw_filemanager_rename% - - - - tw_fm_text2=to - tw_fm_text3="%tw_file_location1%/%tw_filemanager_rename%" - tw_include_text3=1 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - Cancel - filemanageroptions - - - - - - - main - - - - - filemanageroptions - - - - - - - - - - - Please Enter a New %tw_fm_type% Name - - - - - %tw_filemanager_rename% - - - - tw_fm_text2=to - tw_fm_text3=%tw_filemanager_rename% - tw_include_text3=1 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - Cancel - filemanageroptions - - - - - - - main - - - - - filemanageroptions - - - - - - - - - - - Please Enter New Permissions - - - - - %tw_filemanager_rename% - - - - tw_filemanager_command=chmod %tw_filemanager_rename% - tw_fm_text1=chmod %tw_filemanager_rename% - tw_back=filemanageroptions - filemanagerconfirm - - - - - - Cancel - filemanageroptions - - - - - - - main - - - - - filemanageroptions - - - - - - - - - - - %tw_fm_text1% - - - - - %tw_filename1% - - - - - %tw_fm_text2% - - - - - %tw_fm_text3% - - - - - Press back button to cancel. - - - - filemanageracction - - - - - %tw_back% - - - - - main - - - - - - - - - - - %tw_fm_text1% - - - - - - - - - - - - - tw_back=filemanagerlist - tw_complete_text1=File Operation Complete - action_complete - - - - - - - - - - tw_complete_text1=File Operation Complete - action_complete - - - - - - - %tw_filemanager_command% "%tw_filename1%" - - - - - - - %tw_filemanager_command% "%tw_filename1%" "%tw_fm_text3%" - - - - - - - - - - decrypt_pattern - - - - - Please Enter Your Password - - - - - %tw_crypto_display% - - - - trydecrypt - - - - - - - - Password Failed, Please Try Again - - - - - Cancel - - tw_page_done=1 - main - - - - - - - - - - - - - - Please Enter Your Pattern - - - - - - - Pattern Failed, Please Try Again - - - - - - - - - - trydecrypt - - - - - Cancel - - tw_page_done=1 - main - - - - - - - - - - - - Trying Decryption with Your Password - - - - - - - - - - - - - - - - - tw_password_fail=1 - decrypt - - - - - - - - - - tw_page_done=1 - main - - - - - - - - - - Browse to Starting Folder - - - - - %tw_terminal_location% - - - - - - - - - - - main - - - - - advanced - - - - - Select Folder - - terminalcommand - - - - - - - - - - - - - - - - - Starting Path: %tw_terminal_location% - - - - - - KILL - - - - - - - - - home - - - - - - - - back - - - - - lock - - - - - - - - - - - - main - - - - - terminalfolder - - - - - - - - - ADB Sideload - - - - - Wipe Dalvik Cache. - - - - - - Wipe Cache. - - - - - Swipe to Start Sideload - - tw_back=advanced - tw_action=adbsideload - tw_action_text1=ADB Sideload - tw_action_text2=Usage: adb sideload filename.zip - tw_complete_text1=ADB Sideload Complete - tw_has_cancel=1 - tw_show_reboot=1 - tw_cancel_action=adbsideloadcancel - action_page - - - - - - main - - - - - advanced - - - - - - - - - - - Fix Permissions - - - - - Note: Fixing permissions is rarely needed. - - - - - Also fix SELinux contexts - - - - - - Fixing SELinux contexts may cause your device to not boot properly. - - - - Swipe to Fix Permissions - - tw_back=advanced - tw_action=fixpermissions - tw_action_text1=Fixing Permissions... - tw_complete_text1=Fix Permissions Complete - tw_slider_text=Swipe to Confirm - tw_show_reboot=1 - action_page - - - - - - main - - - - - advanced - - - - - - - - - - - Install SuperSU? - - - - - Your device does not appear to be rooted. - - - - - Install SuperSU now? This will root your device. - - - - - Do Not Install - tw_page_done=1 - - - - Swipe to Install - - tw_action=installsu - tw_action_text1=Installing SuperSU - tw_action_text2= - singleaction_page - - - - - - - - - - Keep System Read Only? - - - - - TWRP can leave your system partition unmodified to make it easier for you to take official updates. - - - - - TWRP will be unable to prevent the stock ROM from replacing TWRP and will not offer to root your device. - - - - - Installing zips or performing adb operations may still modify the system partition. - - - - - - Never show this screen during boot again - - - - - - Keep Read Only - - 1 - tw_page_done=1 - %tw_back% - - - - - Swipe to Allow Modifications - - 0 - tw_page_done=1 - %tw_back% - - - - - - - diff --git a/gui/devices/portrait/res/portrait.xml b/gui/devices/portrait/res/portrait.xml deleted file mode 100644 index 8d21777db3..0000000000 --- a/gui/devices/portrait/res/portrait.xml +++ /dev/null @@ -1,3769 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - - - - Install - - - install - - - - - - Wipe - wipe - - - - - Backup - backup - - - - - Restore - restore - - - - - Mount - mount - - - - - Settings - settings - - - - - Advanced - advanced - - - - - Reboot - reboot - - - - - - - - - - - Select Zip to Install - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=install - selectstorage - - - - - - %tw_zip_location% - - - - - - - - - - - Images... - - install_image - - - - - - - - flash_confirm - - - - - - main - - - - - main - - - - - - - - - - - This operation may install incompatible - - - - - software and render your device unusable. - - - - - Folder: - - - - - - %tw_zip_location% - - - - - File to flash: - - - - - - %tw_file% - - - - - Press back to cancel adding this zip. - - - - - Zip file signature verification. - - - - - - - Inject TWRP after install. - - - - - - File %tw_zip_queue_count% of max of 10 - - - - Swipe to Confirm Flash - flash_zip - - - - - - Add More Zips - install - - - - - Clear Zip Queue - - - install - - - - - - - - install - - - - - - main - - - - - - - - - - - - - - - Flashing file %tw_zip_index% of %tw_zip_queue_count% - - - - - - %tw_filename% - - - - - - - flash_done - - - - - - - - - Zip Install Complete - - - - - - - - - Wipe cache/dalvik - - tw_back=flash_done - tw_action=wipe - tw_action_param=/cache - tw_has_action2=1 - tw_action2=wipe - tw_action2_param=dalvik - tw_text1=Wipe Cache & Dalvik? - tw_action_text1=Wiping Cache & Dalvik... - tw_complete_text1=Cache & Dalvik Wipe Complete - tw_slider_text=Swipe to Wipe - confirm_action - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - - - - - - Install - install_select - - - - - Wipe - wipe - - - - - Backup - backup - - - - - Restore - restore - - - - - Mount - mount - - - - - Settings - settings - - - - - - Advanced - advanced - - - - - Reboot - reboot - - - - - - - - - - - - - Install Zips - - - install - - - - - - Install Images - install_image - - - - - main - - - - - main - - - - - - - - - - - Select Zip to Install - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=install - selectstorage - - - - - - %tw_zip_location% - - - - - - - - - - - - - flash_confirm - - - - - - install_select - - - - - main - - - - - - - - - - - This operation may install incompatible - - - - - software and render your device unusable. - - - - - Folder and File: - - - - - - %tw_zip_location% - - - - - - %tw_file% - - - - - Press back to cancel adding this zip. - - - - - Zip file signature verification. - - - - - - - Inject TWRP after install. - - - - - - File %tw_zip_queue_count% of max of 10 - - - - Swipe to Confirm Flash - flash_zip - - - - - - Add More Zips - install - - - - - Clear Zip Queue - - - install - - - - - - - - install - - - - - - main - - - - - - - - - - - - - Flashing file %tw_zip_index% of %tw_zip_queue_count% - - - - - - %tw_filename% - - - - - - - flash_done - - - - - - - - - Zip Install Complete - - - - - - - - - Wipe cache/dalvik - - tw_back=flash_done - tw_action=wipe - tw_action_param=/cache - tw_has_action2=1 - tw_action2=wipe - tw_action2_param=dalvik - tw_text1=Wipe Cache & Dalvik? - tw_action_text1=Wiping Cache & Dalvik... - tw_complete_text1=Cache & Dalvik Wipe Complete - tw_slider_text=Swipe to Wipe - confirm_action - - - - - - - - - - - - Failed - - - - - - - Successful - - - - - - tw_clear_destination=install - clear_vars - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - - - - Select Image to Install - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=install_image - selectstorage - - - - - - %tw_zip_location% - - - - - - - - - - - flashimage_confirm - - - - - install_select - - - - - main - - - - - - - - - - - Select Partition to Flash Image: - - - - - - - - Folder: - - - - - - %tw_zip_location% - - - - - File to flash: - - - - - - %tw_file% - - - - Swipe to Confirm Flash - - tw_back=flashimage_confirm - tw_action=flashimage - tw_has_action2=0 - tw_action_text1=Flashing Image... - tw_action_text2= - tw_complete_text1=Image Flashed - action_page - - - - - - - - tw_clear_destination=install_image - clear_vars - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - tw_operation_state=0 - tw_text1= - tw_text2= - tw_text3= - tw_text4= - tw_action_text1= - tw_action_text2= - tw_action_param= - tw_has_action2=0 - tw_action2= - tw_action2_param= - tw_has_cancel=0 - tw_cancel_action= - tw_cancel_param= - tw_show_exclamation=0 - tw_show_reboot=0 - %tw_clear_destination% - - - - - - - - - %tw_text1% - - - - - %tw_text2% - - - - - %tw_text3% - - - - - %tw_text4% - - - - - Press back button to cancel. - - - - %tw_slider_text% - action_page - - - - - - tw_clear_destination=%tw_back% - clear_vars - - - - - - main - - - - - - - - - %tw_action_text1% - - - - - %tw_action_text2% - - - - - - - - Cancel - %tw_cancel_param% - - - - - - - - action_complete - - - - - - - %tw_action_param% - - - - - - - %tw_action_param% - %tw_action2_param% - - - - - - - - - - %tw_action_text1% - - - - - %tw_action_text2% - - - - - - - - - - tw_page_done=1 - - - - - - - %tw_action_param% - - - - - - - %tw_action_param% - %tw_action2_param% - - - - - - - - - - %tw_complete_text1% - - - - - - - Failed - - - - - - - Successful - - - - - - - - tw_clear_destination=main2 - clear_vars - - - - - - - tw_clear_destination=%tw_back% - clear_vars - - - - - - - %tw_filecheck% - - - - - - - - - tw_fileexists=1 - %tw_existpage% - - - - - - - - - - tw_fileexists=0 - %tw_notexistpage% - - - - - - - - %tw_action_param% - - - - - confirm_action - - - - - - - - - Factory Reset - - - - - Wipes Data, Cache, and Dalvik - - - - - - (not including internal storage) - - - - - - - - - Android Secure - - - - - - SD-EXT - - - - - Most of the time this is - - - - - the only wipe that you need. - - - - - Press back button to cancel. - - - - - Advanced Wipe - - partitionlisterror=0 - advancedwipe - - - - - - - Format Data - - formatdata - - - - - - - - - - Wipe Encryption - - tw_back=wipe - tw_action=wipe - tw_action_param=DATAMEDIA - tw_has_action2=0 - tw_text1=Wipe Encryption from Data? - tw_text2= - tw_action_text1=Formatting Data... - tw_complete_text1=Data Format Complete - tw_slider_text=Swipe to Format Data - confirm_action - - - - - Swipe to Factory Reset - - tw_back=wipe - tw_action=wipe - tw_action_param=data - tw_action_text1=Factory Reset... - tw_complete_text1=Factory Reset Complete - action_page - - - - - - main - - - - - main - - - - - - - - tw_wipe_list= - - - - - Wipe Menu - - - - - Select Partitions to Wipe: - - - - - - Swipe to Wipe - - tw_back=advancedwipe - tw_action=wipe - tw_action_param=LIST - tw_text1=Wipe Selected Partition(s)? - tw_action_text1=Wiping Partition(s)... - tw_complete_text1=Wipe Complete - action_page - - - - - - Repair or Change File System - - - checkpartitionlist - - - - - - - - Invalid partition selection - - - - - main - - - - - wipe - - - - - - - - - Format Data will wipe all of your apps, - - - - - backups, pictures, videos, media, and - - - - - removes encryption on internal storage. - - - - - This cannot be undone. Press back to cancel. - - - - - Type yes to continue. - - - - - %tw_confirm_formatdata% - - - formatdata_confirm - - - - - - - main - - - - - wipe - - - - - - - - - - tw_back=formatdata - tw_action=wipe - tw_action_param=DATAMEDIA - tw_action_text1=Formatting Data... - tw_complete_text1=Data Format Complete - action_page - - - - - - formatdata - - - - - - - - partitionlisterror=0 - - partitionoptions - - - - - - - partitionlisterror=1 - tw_wipe_list= - advancedwipe - - - - - - - - - - Partition Options for: %tw_partition_name% - - - - - Mount Point: %tw_partition_mount_point% - - - - - Current file system: %tw_partition_file_system% - - - - - - Present: Yes - - - - - - Present: No - - - - - - Removable: Yes - - - - - - Removable: No - - - - - Size: %tw_partition_size%MB - - - - - Used: %tw_partition_used%MB - - - - - Free: %tw_partition_free%MB - - - - - Backup Size: %tw_partition_backup_size%MB - - - - - - Resize - - tw_back=partitionoptions - tw_action=resize - tw_action_param=%tw_partition_mount_point% - tw_has_action2=1 - tw_action2=getpartitiondetails - tw_text1=Resize %tw_partition_name%? - tw_text2= - tw_action_text1=Resizing... - tw_complete_text1=Resize Complete - tw_slider_text=Swipe to Resize - confirm_action - - - - - - - Repair - - tw_back=partitionoptions - tw_action=repair - tw_action_param=%tw_partition_mount_point% - tw_has_action2=0 - tw_text1=Repair %tw_partition_name%? - tw_text2= - tw_action_text1=Repairing... - tw_complete_text1=Repair Complete - tw_slider_text=Swipe to Repair - confirm_action - - - - - - Change File Sys - selectfilesystem - - - - - main - - - - - advancedwipe - - - - - - - - - - partitionlisterror=0 - - selectfilesystem - - - - - - - partitionlisterror=1 - tw_wipe_list= - advancedwipe - - - - - - - - - - Change file system for: %tw_partition_name% - - - - - Mount Point: %tw_partition_mount_point% - - - - - Current file system: %tw_partition_file_system% - - - - - Proceed with caution! - - - - - - EXT2 - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=ext2 - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to EXT2? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - EXT3 - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=ext3 - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to EXT3? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - EXT4 - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=ext4 - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to EXT4? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - F2FS - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=f2fs - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to F2FS? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - FAT - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=vfat - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to FAT? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - - exFAT - - tw_back=refreshfilesystem - tw_action=changefilesystem - tw_action_param=%tw_partition_mount_point% - tw_action_new_file_system=exfat - tw_has_action2=0 - tw_text1=Change %tw_partition_name% to exFAT? - tw_text2= - tw_action_text1=Formatting... - tw_complete_text1=Format Complete - tw_slider_text=Swipe to Change - confirm_action - - - - - - main - - - - - partitionoptions - - - - - - - - - - - Backup Name: %tw_backup_name% - - tw_fileexists=0 - backupname1 - - - - - - Select Partitions to Back Up: - - - - - - - More... - backupoptions - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=backup - selectstorage - - - - - - Compression - - - - - Swipe to Back Up - backup_run - - - - - main - - - - - main - - - - - - - - - More Backup Options - - - - - - - - - No Encryption - - backupencryption - - - - - - - - - - Using Encryption - - tw_password_not_match=0 - backupencryption - - - - - - Refresh Sizes - - - backupoptions - - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=backupotions - selectstorage - - - - - - Enable compression. - - - - - - Skip MD5 generation during backup. - - - - - - - Disable Free Space Check. - - - - - - - main - - - - - backup - - - - - - - - - - - backupname2 - - - - - - - - - Please Enter a Backup Name - - - - - %tw_backup_name% - - - - tw_filecheck=%tw_backups_folder%/%tw_backup_name% - tw_existpage=backupname2 - tw_notexistpage=backup - filecheck - - - - - - - - A backup with that name already exists! - - - - - Append Date - - - - - - Cancel - - tw_backup_name=(Auto Generate) - backup - - - - - - - - - tw_backup_name=(Auto Generate) - main - - - - - - - tw_backup_name=(Auto Generate) - backup - - - - - - - - - - - - Encrypt your backup? - - - - - Please Enter A Password: - - - - - %tw_backup_encrypt_display% - - - backupencryption2 - - - - - - - Passwords Do Not Match - - - - - Cancel - - tw_encrypt_backup=0 - tw_backup_password= - tw_backup_password2= - tw_backup_encrypt_display= - tw_backup_encrypt_display2= - backupoptions - - - - - - - - main - - - - - backupoptions - - - - - - - - - - - Encrypt your backup? - - - - - Please Enter Password Again: - - - - - %tw_backup_encrypt_display2% - - - - checkbackuppassword - - - - - - Cancel - - tw_encrypt_backup=0 - tw_backup_password= - tw_backup_password2= - tw_backup_encrypt_display= - tw_backup_encrypt_display2= - backupoptions - - - - - - - - main - - - - - backupoptions - - - - - - - - - - tw_encrypt_backup=1 - backupoptions - - - - - - - tw_encrypt_backup=0 - tw_password_not_match=1 - tw_backup_password= - tw_backup_password2= - tw_backup_encrypt_display= - tw_backup_encrypt_display2= - backupencryption - - - - - - - - - - %tw_operation% %tw_partition% - - - - - %tw_file_progress% - - - - - %tw_size_progress% - - - - - - - - - - - Cancel - - - - - - - - - backup - - - - - - - tw_back=backup - tw_complete_text1=Backup Complete - tw_show_reboot=1 - action_complete - - - - - - - - tw_back=backup - tw_complete_text1=Backup Cancelled - tw_show_reboot=1 - action_complete - - - - - - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=restore - selectstorage - - - - - - Select Package to Restore: - - - - - - - - - - - - - restore_read - - - - - - main - - - - - main - - - - - - - - - - tw_password_fail=0 - restore_decrypt - - - - - - - restore_select - - - - - - - - - - Backup Encrypted - - - - - Please Enter Your Password: - - - - - %tw_restore_display% - - - - try_restore_decrypt - - - - - - - - Password Failed, Please Try Again - - - - - Cancel - - tw_page_done=1 - restore - - - - - - Delete - - tw_back=restore - tw_action=cmd - tw_action_param=cd %tw_backups_folder% && rm -rf "%tw_restore_name%" - tw_text1=Delete Backup? %tw_restore_name% - tw_text2=This cannot be undone! - tw_action_text1=Deleting Backup... - tw_complete_text1=Backup Delete Complete - tw_slider_text=Swipe to Delete - confirm_action - - - - - - - - main - - - - - restore - - - - - - - - - - - Trying Decryption with Your Password - - - - - - - - - - - - - - - - - tw_password_fail=1 - restore_decrypt - - - - - - - - - - tw_page_done=1 - restore_select - - - - - - - - - - Restoring: %tw_restore_name% - - - - - - - Rename Backup - - tw_backup_rename= - tw_fileexists=0 - renamebackup - - - - - - Delete Backup - - tw_back=restore - tw_action=cmd - tw_action_param=cd %tw_backups_folder% && rm -rf "%tw_restore_name%" - tw_text1=Delete Backup? %tw_restore_name% - tw_text2=This cannot be undone! - tw_action_text1=Deleting Backup... - tw_complete_text1=Backup Delete Complete - tw_slider_text=Swipe to Delete - confirm_action - - - - - - Enable MD5 verification of backup. - - - - - Swipe to Restore - restore_run - - - - - main - - - - - restore - - - - - - - - - Please Enter a New Backup Name - - - - - %tw_backup_rename% - - - - tw_back=restore - tw_action=cmd - tw_action_param=cd %tw_backups_folder% && mv "%tw_restore_name%" "%tw_backup_rename%" - tw_text1=Rename Backup? - tw_text2=This cannot be undone! - tw_action_text1=Renaming Backup... - tw_complete_text1=Backup Rename Complete - tw_slider_text=Swipe to Rename - tw_filecheck=%tw_backups_folder%/%tw_backup_rename% - tw_existpage=renamebackup - tw_notexistpage=confirm_action - filecheck - - - - - - - - A backup with that name already exists! - - - - - Cancel - restore_select - - - - - - - main - - - - - restore_select - - - - - - - - - - - %tw_operation% %tw_partition% - - - - - %tw_size_progress% - - - - - - - - - - tw_back=restore_select - tw_complete_text1=Restore Complete - tw_show_reboot=1 - action_complete - - - - - restore - - - - - - - - - Select Storage: - - - - - - - - OK - - tw_clear_destination=%tw_back% - clear_vars - - - - - - main - - - - - - %tw_back% - - - - - - - - - - - - Select Partitions to Mount: - - - - - - Storage: %tw_storage_display_name% (%tw_storage_free_size% MB) - - tw_back=mount - selectstorage - - - - - - - USB Storage - usb_mount - - - - - - - - - Enable MTP - - - - - - - - - - Disable MTP - - - - - - - - - - Decrypt Data - decrypt - - - - - - - Only mount system read-only - - 1 - - - - - - - Only mount system read-only - - - tw_lifetime_writes=2 - system_readonly_check - - - - - - main - - - - - main - - - - - - - - - - - USB Storage Mounted - - - - - - Be sure to safely remove your device - - - - - from your computer before unmounting! - - - - - Unmount - usb_umount - - - - usb - - - - - - - - usb - - - - mount - - - - - - /system - - - - - - 0 - mount - - - - - - - tw_back=mount - system_readonly - - - - - - - - - - Reboot Menu - - - - - System - - - - - - Power Off - - tw_back=reboot - tw_action=reboot - tw_action_param=poweroff - tw_has_action2=0 - tw_text1=No OS Installed! Are you - tw_text2=sure you wish to power off? - tw_action_text1=Turning Off... - tw_complete_text1=Turning Off... - tw_slider_text=Swipe to Power Off - rebootcheck - - - - - - - Recovery - - tw_back=reboot - tw_action=reboot - tw_action_param=recovery - tw_has_action2=0 - tw_text1=No OS Installed! Are you - tw_text2=sure you wish to reboot? - tw_action_text1=Rebooting... - tw_complete_text1=Rebooting... - tw_slider_text=Swipe to Reboot - rebootcheck - - - - - - - Bootloader - - tw_back=reboot - tw_action=reboot - tw_action_param=bootloader - tw_has_action2=0 - tw_text1=No OS Installed! Are you - tw_text2=sure you wish to reboot? - tw_action_text1=Rebooting... - tw_complete_text1=Rebooting... - tw_slider_text=Swipe to Reboot - rebootcheck - - - - - - - Download - - tw_back=reboot - tw_action=reboot - tw_action_param=download - tw_has_action2=0 - tw_text1=No OS Installed! Are you - tw_text2=sure you wish to reboot? - tw_action_text1=Rebooting... - tw_complete_text1=Rebooting... - tw_slider_text=Swipe to Reboot - rebootcheck - - - - - - main - - - - - main - - - - - - - - - - - Zip file signature verification. - - - - - - Use rm -rf instead of formatting. - - - - - - Skip MD5 generation during backup. - - - - - - Enable MD5 verification of backup files. - - - - - - Use 24-hour clock. - - - - - - Simulate actions for theme testing. - - - - - - - Simulate failure for actions. - - - - - - Time Zone - timezone - - - - - Screen - screen - - - - - Restore Defaults - - - - - - Vibration - vibrate - - - - - main - - - - - main - - - - - - - - - - - Select Time Zone: - - - BST11;BDT - HST10;HDT - AST9;ADT - PST8;PDT,M3.2.0,M11.1.0 - MST7;MDT,M3.2.0,M11.1.0 - CST6;CDT,M3.2.0,M11.1.0 - EST5;EDT,M3.2.0,M11.1.0 - AST4;ADT - GRNLNDST3;GRNLNDDT - FALKST2;FALKDT - AZOREST1;AZOREDT - GMT0;BST,M3.5.0,M10.5.0 - CET-1;CEST,M3.5.0,M10.5.0 - WET-2;WET,M3.5.0,M10.5.0 - SAUST-3;SAUDT - WST-4;WDT - PAKST-5;PAKDT - TASHST-6;TASHDT - THAIST-7;THAIDT - TAIST-8;TAIDT - JST-9;JSTDT - EET-10;EETDT - MET-11;METDT - NZST-12;NZDT - - - - - Do you use daylight savings time (DST)? - - - - - - Offset (usually 0): %tw_time_zone_guioffset% - - - - - None - tw_time_zone_guioffset=0 - - - - - 15 - tw_time_zone_guioffset=15 - - - - - 30 - tw_time_zone_guioffset=30 - - - - - 45 - tw_time_zone_guioffset=45 - - - - - Set Time Zone - - - - - - Current Time Zone: %tw_time_zone% - - - - - main - - - - - settings - - - - - - - - - Screen Settings - - - - - - - - Enable screen timeout. - - tw_screen_timeout_secs=60 - - - - - - - - Enable screen timeout. - - tw_screen_timeout_secs=0 - - - - - - - - - Screen timeout in seconds: - - - - - - - Brightness: %tw_brightness_pct%% - - - tw_brightness=%tw_brightness_max% - tw_brightness*%tw_brightness_pct% - tw_brightness/100 - %tw_brightness% - - - - - - main - - - - - settings - - - - - - - - - - - Vibration Settings : - - - - - Button Vibration: - - - - - - Keyboard Vibration: - - - - - - Action Vibration: - - - - - - main - - - - - settings - - - - - - - - - - - Advanced - - - - - Copy Log to SD - - tw_back=advanced - tw_action=copylog - tw_text1=Copy Log to SD Card? - tw_action_text1=Copying Log to SD Card... - tw_complete_text1=Log Copy Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - Fix Permissions - fixperms - - - - - - Partition SD Card - partsdcard - - - - - File Manager - filemanagerlist - - - - - Terminal Command - terminalfolder - - - - - Reload Theme - - - - - - ADB Sideload - sideload - - - - - - HTC Dumlock - htcdumlock - - - - - - Re-Inject TWRP - - tw_back=advanced - tw_action=reinjecttwrp - tw_text1=Re-Inject TWRP? - tw_action_text1=Re-Injecting TWRP... - tw_complete_text1=TWRP Injection Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - main - - - - - main - - - - - - - - - - - Partition SD Card - - - - - - - tw_sdext_size-128 - - - - - - - tw_sdext_size+128 - - - - - EXT Size: %tw_sdext_size% - - - - - - - tw_swap_size-32 - - - - - - - tw_swap_size+32 - - - - - Swap Size: %tw_swap_size% - - - - - File system: %tw_sdpart_file_system% - - - - - - EXT3 - tw_sdpart_file_system=ext3 - - - - - - EXT4 - - tw_sdpart_file_system=ext4 - - - - - You will lose all files on your SD card! - - - - - This action cannot be undone! - - - - Swipe to Partition - - tw_back=partsdcard - tw_action=partitionsd - tw_has_action2=1 - tw_action2=set - tw_action2_param=tw_zip_location=/sdcard - tw_action_text1=Partitioning SD Card... - tw_action_text2=This will take a few minutes. - tw_complete_text1=Partitioning Complete - action_page - - - - - - main - - - - - advanced - - - - - - - - - HTC Dumlock - - - - - Restore Original Boot - - tw_back=htcdumlock - tw_action=htcdumlockrestoreboot - tw_text1=Restore original boot image? - tw_action_text1=Restoring Original Boot... - tw_complete_text1=Restore Original Boot Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - Reflash Recovery - - tw_back=htcdumlock - tw_action=htcdumlockreflashrecovery - tw_text1=Reflash recovery to boot? - tw_action_text1=Flashing recovery to boot... - tw_complete_text1=Recovery Flash to Boot Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - Install HTC Dumlock - - tw_back=htcdumlock - tw_action=installhtcdumlock - tw_text1=Install HTC dumlock files to ROM? - tw_action_text1=Installing HTC Dumlock... - tw_complete_text1=HTC Dumlock Install Complete - tw_slider_text=Swipe to Confirm - confirm_action - - - - - - main - - - - - advanced - - - - - - - - - - - - - - - Swipe to Unlock - - - - - - - - - - File Manager: Select a File or Folder - - - - - %tw_file_location1% - - - - - - - - - - - tw_fm_type=File - tw_fm_isfolder=0 - - - - - - main - - - - - advanced - - - - - - filemanageroptions - - - - - - Select - - tw_filename1=tw_file_location1 - tw_fm_isfolder=1 - tw_fm_type=Folder - filemanageroptions - - - - - - - - - - %tw_fm_type% Selected: - - - - - %tw_filename1% - - - - - - Copy File - - tw_filemanager_command=cp - tw_fm_text1=Copying - choosedestinationfolder - - - - - - - Copy Folder - - tw_filemanager_command=cd "%tw_file_location1%" && cd .. && cp -R - tw_fm_text1=Copying - choosedestinationfolder - - - - - - Move - - tw_filemanager_command=mv - tw_fm_text1=Moving - choosedestinationfolder - - - - - - chmod 755 - - tw_filemanager_command=chmod 755 - tw_fm_text1=chmod 755 - tw_fm_text2= - tw_fm_text3= - tw_include_text3=0 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - chmod - - tw_filemanager_rename=0000 - tw_fm_text2= - tw_fm_text3= - tw_include_text3=0 - tw_back=filemanageroptions - filemanagerchmod - - - - - - Delete - - tw_filemanager_command=rm -rf - tw_fm_text1=Deleting - tw_fm_text2= - tw_fm_text3= - tw_include_text3=0 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - - Rename File - - tw_filemanager_rename=tw_selection1 - tw_fm_text1=Renaming - tw_filemanager_command=mv - filemanagerrenamefile - - - - - - - Rename Folder - - tw_filemanager_rename=tw_selection1 - tw_fm_text1=Renaming - tw_filemanager_command=cd "%tw_file_location1%" && cd .. && mv - filemanagerrenamefolder - - - - - - main - - - - - filemanagerlist - - - - - - - - - - - Browse to Destination & Press Select - - - - - %tw_file_location2% - - - - - - - - - - - main - - - - - filemanageroptions - - - - - Select - - tw_fm_text2=to - tw_fm_text3=%tw_file_location2% - tw_include_text3=1 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - - - - - Please Enter a New %tw_fm_type% Name - - - - - %tw_filemanager_rename% - - - - tw_fm_text2=to - tw_fm_text3="%tw_file_location1%/%tw_filemanager_rename%" - tw_include_text3=1 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - Cancel - filemanageroptions - - - - - - - main - - - - - filemanageroptions - - - - - - - - - - - Please Enter a New %tw_fm_type% Name - - - - - %tw_filemanager_rename% - - - - tw_fm_text2=to - tw_fm_text3=%tw_filemanager_rename% - tw_include_text3=1 - tw_back=filemanageroptions - filemanagerconfirm - - - - - - Cancel - filemanageroptions - - - - - - - main - - - - - filemanageroptions - - - - - - - - - - - - Please Enter New Permissions - - - - - %tw_filemanager_rename% - - - - tw_filemanager_command=chmod %tw_filemanager_rename% - tw_fm_text1=chmod %tw_filemanager_rename% - tw_back=filemanageroptions - filemanagerconfirm - - - - - - Cancel - filemanageroptions - - - - - - - main - - - - - filemanageroptions - - - - - - - - - - - %tw_fm_text1% - - - - - %tw_filename1% - - - - - %tw_fm_text2% - - - - - %tw_fm_text3% - - - - - Press back button to cancel. - - - - filemanageracction - - - - - %tw_back% - - - - - main - - - - - - - - - %tw_fm_text1% - - - - - - - - - - - - - tw_back=filemanagerlist - tw_complete_text1=File Operation Complete - action_complete - - - - - - - - - - tw_complete_text1=File Operation Complete - action_complete - - - - - - - %tw_filemanager_command% "%tw_filename1%" - - - - - - - %tw_filemanager_command% "%tw_filename1%" "%tw_fm_text3%" - - - - - - - - - - decrypt_pattern - - - - - Please Enter Your Password - - - - - %tw_crypto_display% - - - - trydecrypt - - - - - - - - Password Failed, Please Try Again - - - - - Cancel - - tw_page_done=1 - main - - - - - - - - - - - - - - Please Enter Your Pattern - - - - - - - Pattern Failed, Please Try Again - - - - - - - - - - trydecrypt - - - - - Cancel - - tw_page_done=1 - main - - - - - - - - - - - - Trying Decryption with Your Password - - - - - - - - - - - - - - - - - tw_password_fail=1 - decrypt - - - - - - - - - - tw_page_done=1 - main - - - - - - - - - - Browse to Starting Folder - - - - - %tw_terminal_location% - - - - - - - - - - - main - - - - - advanced - - - - - Select - - terminalcommand - - - - - - - - - - - - - - Starting Path: %tw_terminal_location% - - - - - - %tw_terminal_command% - - - - %tw_terminal_command% - - - - - - KILL - - - - - - - - main - - - - - terminalfolder - - - - - - - - - ADB Sideload - - - - - Wipe Dalvik Cache. - - - - - - Wipe Cache. - - - - - Swipe to Start Sideload - - tw_back=advanced - tw_action=adbsideload - tw_action_text1=ADB Sideload - tw_action_text2=Usage: adb sideload filename.zip - tw_complete_text1=ADB Sideload Complete - tw_has_cancel=1 - tw_show_reboot=1 - tw_cancel_action=adbsideloadcancel - action_page - - - - - - main - - - - - advanced - - - - - - - - - Fix Permissions - - - - - Note: Fixing permissions is rarely needed. - - - - - Also fix SELinux contexts - - - - - - Fixing SELinux contexts may cause - - - - - your device to not boot properly. - - - - Swipe to Fix Permissions - - tw_back=advanced - tw_action=fixpermissions - tw_action_text1=Fixing Permissions... - tw_complete_text1=Fix Permissions Complete - tw_slider_text=Swipe to Confirm - tw_show_reboot=1 - action_page - - - - - - main - - - - - advanced - - - - - - - - - - - Install SuperSU? - - - - - Your device does not appear to be rooted. - - - - - Install SuperSU now? - - - - - This will root your device. - - - - - Do Not Install - tw_page_done=1 - - - - Swipe to Install - - tw_action=installsu - tw_action_text1=Installing SuperSU - tw_action_text2= - singleaction_page - - - - - - - - - - Keep System Read Only? - - - - - TWRP can leave your system partition unmodified - - - - - to make it easier for you to take official updates. - - - - - TWRP will be unable to prevent the stock ROM from - - - - - replacing TWRP and will not offer to root your device. - - - - - Installing zips or performing adb operations may still - - - - - modify the system partition. - - - - - - Never show this screen during boot again - - - - - - Keep Read Only - - 1 - tw_page_done=1 - %tw_back% - - - - - Swipe to Allow Modifications - - 0 - tw_page_done=1 - %tw_back% - - - - - diff --git a/gui/fileselector.cpp b/gui/fileselector.cpp index 9275d976d6..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; @@ -127,7 +128,7 @@ GUIFileSelector::~GUIFileSelector() int GUIFileSelector::Update(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; GUIScrollList::Update(); @@ -155,7 +156,7 @@ int GUIFileSelector::NotifyVarChange(const std::string& varName, const std::stri { GUIScrollList::NotifyVarChange(varName, value); - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (varName.empty()) { @@ -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); } } } @@ -305,7 +309,6 @@ size_t GUIFileSelector::GetItemCount() void GUIFileSelector::RenderItem(size_t itemindex, int yPos, bool selected) { size_t folderSize = mShowFolders ? mFolderList.size() : 0; - size_t fileSize = mShowFiles ? mFileList.size() : 0; ImageResource* icon; std::string text; @@ -355,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/fill.cpp b/gui/fill.cpp index d0a1cfda72..3cfbe6ed4a 100644 --- a/gui/fill.cpp +++ b/gui/fill.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // fill.cpp - GUIFill object #include @@ -42,7 +60,7 @@ GUIFill::GUIFill(xml_node<>* node) : GUIObject(node) int GUIFill::Render(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; gr_color(mColor.red, mColor.green, mColor.blue, mColor.alpha); diff --git a/gui/gui.cpp b/gui/gui.cpp index df41939fb6..a270e365f7 100644 --- a/gui/gui.cpp +++ b/gui/gui.cpp @@ -33,7 +33,6 @@ #include #include #include -#include extern "C" { @@ -79,11 +78,13 @@ 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); -void flip(void) +static void flip(void) { if (gRecorder != -1) { @@ -205,7 +206,9 @@ bool InputHandler::processInput(int timeout_ms) break; } - blankTimer.resetTimerAndUnblank(); + if (ev.code != KEY_POWER && ev.code > KEY_RESERVED) + blankTimer.resetTimerAndUnblank(); + return true; // we got an event, so there might be more in the queue } @@ -300,15 +303,15 @@ void InputHandler::process_EV_KEY(input_event& ev) // Handle key-press here LOGEVENT("TOUCH_KEY: %d\n", ev.code); // Left mouse button is treated as a touch - if(ev.code == BTN_LEFT) + if (ev.code == BTN_LEFT) { MouseCursor *cursor = PageManager::GetMouseCursor(); - if(ev.value == 1) + if (ev.value == 1) { cursor->GetPos(x, y); doTouchStart(); } - else if(touch_status) + else if (touch_status) { // Left mouse button was previously pressed and now is // being released so send a TOUCH_RELEASE @@ -323,9 +326,9 @@ void InputHandler::process_EV_KEY(input_event& ev) } } // side mouse button, often used for "back" function - else if(ev.code == BTN_SIDE) + else if (ev.code == BTN_SIDE) { - if(ev.value == 1) + if (ev.value == 1) kb->KeyDown(KEY_BACK); else kb->KeyUp(KEY_BACK); @@ -366,12 +369,12 @@ void InputHandler::process_EV_REL(input_event& ev) // Mouse movement MouseCursor *cursor = PageManager::GetMouseCursor(); LOGEVENT("EV_REL %d %d\n", ev.code, ev.value); - if(ev.code == REL_X) + if (ev.code == REL_X) cursor->Move(ev.value, 0); - else if(ev.code == REL_Y) + else if (ev.code == REL_Y) cursor->Move(0, ev.value); - if(touch_status) { + if (touch_status) { cursor->GetPos(x, y); LOGEVENT("Mouse TOUCH_DRAG: %d, %d\n", x, y); key_status = KS_NONE; @@ -393,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) { @@ -415,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 @@ -446,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); @@ -460,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); @@ -560,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()) { @@ -642,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/hardwarekeyboard.cpp b/gui/hardwarekeyboard.cpp index 9ca607c0d6..68be9065da 100644 --- a/gui/hardwarekeyboard.cpp +++ b/gui/hardwarekeyboard.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // hardwarekeyboard.cpp - HardwareKeyboard object #include @@ -19,6 +37,7 @@ extern "C" { #include "../common.h" } +#include "../twcommon.h" #include "objects.hpp" #include @@ -343,7 +362,7 @@ static int KeyCodeToChar(int key_code, bool shiftkey, bool ctrlkey) #ifdef _EVENT_LOGGING default: - LOGE("Unmapped keycode: %i\n", key_code); + LOGERR("Unmapped keycode: %i\n", key_code); break; #endif } @@ -366,7 +385,7 @@ bool HardwareKeyboard::IsKeyDown(int key_code) int HardwareKeyboard::KeyDown(int key_code) { #ifdef _EVENT_LOGGING - LOGE("HardwareKeyboard::KeyDown %i\n", key_code); + LOGERR("HardwareKeyboard::KeyDown %i\n", key_code); #endif key_code = TranslateKeyCode(key_code); mPressedKeys.insert(key_code); @@ -392,7 +411,7 @@ int HardwareKeyboard::KeyDown(int key_code) int HardwareKeyboard::KeyUp(int key_code) { #ifdef _EVENT_LOGGING - LOGE("HardwareKeyboard::KeyUp %i\n", key_code); + LOGERR("HardwareKeyboard::KeyUp %i\n", key_code); #endif key_code = TranslateKeyCode(key_code); std::set::iterator itr = mPressedKeys.find(key_code); @@ -406,7 +425,7 @@ int HardwareKeyboard::KeyUp(int key_code) int HardwareKeyboard::KeyRepeat() { #ifdef _EVENT_LOGGING - LOGE("HardwareKeyboard::KeyRepeat: %i\n", mLastKeyChar); + LOGERR("HardwareKeyboard::KeyRepeat: %i\n", mLastKeyChar); #endif if (mLastKeyChar) PageManager::NotifyCharInput(mLastKeyChar); diff --git a/gui/image.cpp b/gui/image.cpp index 2107d55060..2b5c541976 100644 --- a/gui/image.cpp +++ b/gui/image.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // image.cpp - GUIImage object #include diff --git a/gui/input.cpp b/gui/input.cpp index ec370abe59..8dd981c24b 100644 --- a/gui/input.cpp +++ b/gui/input.cpp @@ -236,7 +236,7 @@ void GUIInput::HandleCursorByTouch(int x) { unsigned index = 0, displaySize = displayValue.size(); int prevX = mRenderX + scrollingX; - for(index = 0; index <= displaySize; index++) { + for (index = 0; index <= displaySize; index++) { cursorString = displayValue.substr(0, index); cursorX = gr_ttf_measureEx(cursorString.c_str(), fontResource) + mRenderX + scrollingX; if (cursorX > x) { @@ -263,7 +263,7 @@ void GUIInput::HandleCursorByTouch(int x) { } void GUIInput::HandleCursorByText() { -// Uses mCursorLocation to find cursorX +// Uses mCursorLocation to find cursorX if (!DrawCursor) return; diff --git a/gui/keyboard.cpp b/gui/keyboard.cpp index 4a78ad7986..849cf19d8f 100644 --- a/gui/keyboard.cpp +++ b/gui/keyboard.cpp @@ -346,7 +346,6 @@ void GUIKeyboard::DrawKey(Key& key, int keyX, int keyY, int keyW, int keyH) void* fontResource = mLongpressFont->GetResource(); gr_color(mLongpressFontColor.red, mLongpressFontColor.green, mLongpressFontColor.blue, mLongpressFontColor.alpha); string text(1, keychar); - int textH = mLongpressFont->GetHeight(); int textW = gr_ttf_measureEx(text.c_str(), fontResource); int textX = keyX + keyW - longpressOffsetX - textW; int textY = keyY + longpressOffsetY; diff --git a/gui/listbox.cpp b/gui/listbox.cpp index e10fee72a6..05276e8acb 100644 --- a/gui/listbox.cpp +++ b/gui/listbox.cpp @@ -123,7 +123,7 @@ GUIListBox::~GUIListBox() int GUIListBox::Update(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; GUIScrollList::Update(); @@ -140,7 +140,7 @@ int GUIListBox::NotifyVarChange(const std::string& varName, const std::string& v { GUIScrollList::NotifyVarChange(varName, value); - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; // Check to see if the variable that we are using to store the list selected value has been updated diff --git a/gui/mousecursor.cpp b/gui/mousecursor.cpp index bd730df6be..b87cac4149 100644 --- a/gui/mousecursor.cpp +++ b/gui/mousecursor.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + #include #include #include @@ -52,15 +70,15 @@ void MouseCursor::LoadData(xml_node<>* node) xml_node<>* child; child = FindNode(node, "placement"); - if(child) + if (child) LoadPlacement(child, &mRenderX, &mRenderY, &mRenderW, &mRenderH); child = FindNode(node, "background"); - if(child) + if (child) { m_color = LoadAttrColor(child, "color", m_color); m_image = LoadAttrImage(child, "resource"); - if(m_image) + if (m_image) { mRenderW = m_image->GetWidth(); mRenderH = m_image->GetHeight(); @@ -68,20 +86,20 @@ void MouseCursor::LoadData(xml_node<>* node) } child = FindNode(node, "speed"); - if(child) + if (child) { attr = child->first_attribute("multiplier"); - if(attr) + if (attr) m_speedMultiplier = atof(attr->value()); } } int MouseCursor::Render(void) { - if(!m_present) + if (!m_present) return 0; - if(m_image) + if (m_image) { gr_blit(m_image->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY); } @@ -95,15 +113,15 @@ int MouseCursor::Render(void) int MouseCursor::Update(void) { - if(m_present != ev_has_mouse()) + if (m_present != ev_has_mouse()) { m_present = ev_has_mouse(); - if(m_present) + if (m_present) SetRenderPos(m_resX/2, m_resY/2); return 2; } - if(m_present && m_moved) + if (m_present && m_moved) { m_moved = false; return 2; @@ -113,7 +131,7 @@ int MouseCursor::Update(void) int MouseCursor::SetRenderPos(int x, int y, int w, int h) { - if(x == mRenderX && y == mRenderY) + if (x == mRenderX && y == mRenderY) m_moved = true; return RenderObject::SetRenderPos(x, y, w, h); @@ -121,7 +139,7 @@ int MouseCursor::SetRenderPos(int x, int y, int w, int h) void MouseCursor::Move(int deltaX, int deltaY) { - if(deltaX != 0) + if (deltaX != 0) { mRenderX += deltaX*m_speedMultiplier; mRenderX = (std::min)(mRenderX, m_resX); @@ -130,7 +148,7 @@ void MouseCursor::Move(int deltaX, int deltaY) m_moved = true; } - if(deltaY != 0) + if (deltaY != 0) { mRenderY += deltaY*m_speedMultiplier; mRenderY = (std::min)(mRenderY, m_resY); diff --git a/gui/object.cpp b/gui/object.cpp index 14df14fc13..0cf2ccea68 100644 --- a/gui/object.cpp +++ b/gui/object.cpp @@ -1,8 +1,26 @@ +/* + Copyright 2017 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 . +*/ + // object.cpp - GUIObject base class #include #include - +#include #include extern "C" { @@ -159,7 +177,7 @@ bool GUIObject::UpdateConditions(std::vector& conditions, const std:: std::vector::iterator iter; for (iter = conditions.begin(); iter != conditions.end(); ++iter) { - if(varNameEmpty && iter->mCompareOp == "modified") + if (varNameEmpty && iter->mCompareOp == "modified") { string val; @@ -172,10 +190,10 @@ bool GUIObject::UpdateConditions(std::vector& conditions, const std:: iter->mLastVal = val; } - if(varNameEmpty || iter->mVar1 == varName || iter->mVar2 == varName) + if (varNameEmpty || iter->mVar1 == varName || iter->mVar2 == varName) iter->mLastResult = isConditionTrue(&(*iter)); - if(!iter->mLastResult) + if (!iter->mLastResult) result = false; } return result; diff --git a/gui/objects.hpp b/gui/objects.hpp index 0d969279ac..630cf7102c 100644 --- a/gui/objects.hpp +++ b/gui/objects.hpp @@ -42,6 +42,12 @@ using namespace rapidxml; #ifndef TW_Y_OFFSET #define TW_Y_OFFSET 0 #endif +#ifndef TW_W_OFFSET +#define TW_W_OFFSET 0 +#endif +#ifndef TW_H_OFFSET +#define TW_H_OFFSET 0 +#endif class RenderObject { @@ -320,6 +326,7 @@ class GUIAction : public GUIObject, public ActionObject int getpartitiondetails(std::string arg); int screenshot(std::string arg); int setbrightness(std::string arg); + int checkforapp(std::string arg); // (originally) threaded actions int fileexists(std::string arg); @@ -356,7 +363,10 @@ class GUIAction : public GUIObject, public ActionObject int checkpartitionlifetimewrites(std::string arg); int mountsystemtoggle(std::string arg); int setlanguage(std::string arg); + int togglebacklight(std::string arg); int twcmd(std::string arg); + int setbootslot(std::string arg); + int installapp(std::string arg); int simulate; }; @@ -736,7 +746,9 @@ class GUIConsole : public GUIScrollList virtual size_t GetItemCount(); virtual void RenderItem(size_t itemindex, int yPos, bool selected); virtual void NotifySelect(size_t item_selected); + static void Translate_Now(); + static void Clear_For_Retranslation(); protected: enum SlideoutState { @@ -1225,8 +1237,6 @@ COLOR LoadAttrColor(xml_node<>* element, const char* attrname, COLOR defaultvalu FontResource* LoadAttrFont(xml_node<>* element, const char* attrname); ImageResource* LoadAttrImage(xml_node<>* element, const char* attrname); AnimationResource* LoadAttrAnimation(xml_node<>* element, const char* attrname); - bool LoadPlacement(xml_node<>* node, int* x, int* y, int* w = NULL, int* h = NULL, Placement* placement = NULL); #endif // _OBJECTS_HEADER - diff --git a/gui/pages.cpp b/gui/pages.cpp index 115d6b35df..a3a1df3254 100644 --- a/gui/pages.cpp +++ b/gui/pages.cpp @@ -32,34 +32,37 @@ #include #include #include +#include #include "../twrp-functions.hpp" #include "../partitions.hpp" #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" #include "objects.hpp" #include "blanktimer.hpp" -#define TW_THEME_VERSION 1 +// version 2 requires theme to handle power button as action togglebacklight +#define TW_THEME_VERSION 3 + #define TW_THEME_VER_ERR -2 extern int gGuiRunning; -// From console.cpp -extern size_t last_message_count; -extern std::vector gConsole; -extern std::vector gConsoleColor; - std::map PageManager::mPageSets; PageSet* PageManager::mCurrentSet; MouseCursor *PageManager::mMouseCursor = NULL; @@ -70,6 +73,8 @@ std::vector Language_List; int tw_x_offset = 0; int tw_y_offset = 0; +int tw_w_offset = 0; +int tw_h_offset = 0; // Helper routine to convert a string to a color declaration int ConvertStrToColor(std::string str, COLOR* color) @@ -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; @@ -894,16 +899,17 @@ int PageSet::LoadDetails(LoadingContext& ctx, xml_node<>* root) } #endif if (width != 0 && height != 0) { - float scale_w = ((float)gr_fb_width() - ((float)offx * 2.0)) / (float)width; - float scale_h = ((float)gr_fb_height() - ((float)offy * 2.0)) / (float)height; + float scale_w = (((float)gr_fb_width() + (float)tw_w_offset) - ((float)offx * 2.0)) / (float)width; + float scale_h = (((float)gr_fb_height() + (float)tw_h_offset) - ((float)offy * 2.0)) / (float)height; #ifdef TW_ROUND_SCREEN - float scale_off_w = (float)gr_fb_width() / (float)width; - float scale_off_h = (float)gr_fb_height() / (float)height; + float scale_off_w = ((float)gr_fb_width() + (float)tw_w_offset) / (float)width; + float scale_off_h = ((float)gr_fb_height() + (float)tw_h_offset) / (float)height; tw_x_offset = offx * scale_off_w; tw_y_offset = offy * scale_off_h; #endif if (scale_w != 1 || scale_h != 1) { - LOGINFO("Scaling theme width %fx and height %fx, offsets x: %i y: %i\n", scale_w, scale_h, tw_x_offset, tw_y_offset); + LOGINFO("Scaling theme width %fx and height %fx, offsets x: %i y: %i w: %i h: %i\n", + scale_w, scale_h, tw_x_offset, tw_y_offset, tw_w_offset, tw_h_offset); set_scale_values(scale_w, scale_h); } } @@ -1005,7 +1011,7 @@ int PageSet::LoadVariables(xml_node<>* vars) name = child->first_attribute("name"); value = child->first_attribute("value"); persist = child->first_attribute("persist"); - if(name && value) + if (name && value) { if (strcmp(name->value(), "tw_x_offset") == 0) { tw_x_offset = atoi(value->value()); @@ -1017,6 +1023,16 @@ int PageSet::LoadVariables(xml_node<>* vars) child = child->next_sibling("variable"); continue; } + if (strcmp(name->value(), "tw_w_offset") == 0) { + tw_w_offset = atoi(value->value()); + child = child->next_sibling("variable"); + continue; + } + if (strcmp(name->value(), "tw_h_offset") == 0) { + tw_h_offset = atoi(value->value()); + child = child->next_sibling("variable"); + continue; + } p = persist ? atoi(persist->value()) : 0; string temp = value->value(); string valstr = gui_parse_text(temp); @@ -1171,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; @@ -1179,7 +1195,7 @@ char* PageManager::LoadFileToBuffer(std::string filename, ZipArchive* package) { // We can try to load the XML directly... LOGINFO("PageManager::LoadFileToBuffer loading filename: '%s' directly\n", filename.c_str()); struct stat st; - if(stat(filename.c_str(),&st) != 0) { + if (stat(filename.c_str(),&st) != 0) { // This isn't always an error, sometimes we request files that don't exist. return NULL; } @@ -1208,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; @@ -1284,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/"); @@ -1319,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; @@ -1339,6 +1353,8 @@ int PageManager::LoadPackage(std::string name, std::string package, std::string LOGINFO("Load XML directly\n"); tw_x_offset = TW_X_OFFSET; tw_y_offset = TW_Y_OFFSET; + tw_w_offset = TW_W_OFFSET; + tw_h_offset = TW_H_OFFSET; if (name != "splash") { LoadLanguageList(NULL); languageFile = LoadFileToBuffer(TWRES "languages/en.xml", NULL); @@ -1350,15 +1366,23 @@ int PageManager::LoadPackage(std::string name, std::string package, std::string LOGINFO("Loading zip theme\n"); tw_x_offset = 0; tw_y_offset = 0; + tw_w_offset = 0; + 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; @@ -1399,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; } @@ -1453,7 +1481,7 @@ int PageManager::ReloadPackage(std::string name, std::string package) if (iter == mPageSets.end()) return -1; - if(mMouseCursor) + if (mMouseCursor) mMouseCursor->ResetData(gr_fb_width(), gr_fb_height()); PageSet* set = (*iter).second; @@ -1521,9 +1549,7 @@ int PageManager::RunReload() { } // This makes the console re-translate - last_message_count = 0; - gConsole.clear(); - gConsoleColor.clear(); + GUIConsole::Clear_For_Retranslation(); return ret_val; } @@ -1571,18 +1597,18 @@ int PageManager::IsCurrentPage(Page* page) int PageManager::Render(void) { - if(blankTimer.isScreenOff()) + if (blankTimer.isScreenOff()) return 0; int res = (mCurrentSet ? mCurrentSet->Render() : -1); - if(mMouseCursor) + if (mMouseCursor) mMouseCursor->Render(); return res; } HardwareKeyboard *PageManager::GetHardwareKeyboard() { - if(!mHardwareKeyboard) + if (!mHardwareKeyboard) mHardwareKeyboard = new HardwareKeyboard(); return mHardwareKeyboard; } @@ -1612,14 +1638,14 @@ xml_node<>* PageManager::FindStyle(std::string name) MouseCursor *PageManager::GetMouseCursor() { - if(!mMouseCursor) + if (!mMouseCursor) mMouseCursor = new MouseCursor(gr_fb_width(), gr_fb_height()); return mMouseCursor; } void PageManager::LoadCursorData(xml_node<>* node) { - if(!mMouseCursor) + if (!mMouseCursor) mMouseCursor = new MouseCursor(gr_fb_width(), gr_fb_height()); mMouseCursor->LoadData(node); @@ -1627,7 +1653,7 @@ void PageManager::LoadCursorData(xml_node<>* node) int PageManager::Update(void) { - if(blankTimer.isScreenOff()) + if (blankTimer.isScreenOff()) return 0; if (RunReload()) @@ -1635,10 +1661,10 @@ int PageManager::Update(void) int res = (mCurrentSet ? mCurrentSet->Update() : -1); - if(mMouseCursor) + if (mMouseCursor) { int c_res = mMouseCursor->Update(); - if(c_res > res) + if (c_res > res) res = c_res; } return res; diff --git a/gui/pages.hpp b/gui/pages.hpp index 4bfd5b0e5b..282b2d5b12 100644 --- a/gui/pages.hpp +++ b/gui/pages.hpp @@ -1,9 +1,27 @@ +/* + Copyright 2017 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 . +*/ + // pages.hpp - Base classes for page manager of GUI #ifndef _PAGES_HEADER_HPP #define _PAGES_HEADER_HPP -#include "../minzip/Zip.h" +#include "../zipwrap.hpp" #include #include #include @@ -98,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); @@ -137,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/partitionlist.cpp b/gui/partitionlist.cpp index ff207fa9a2..c85339152b 100644 --- a/gui/partitionlist.cpp +++ b/gui/partitionlist.cpp @@ -79,7 +79,7 @@ GUIPartitionList::~GUIPartitionList() int GUIPartitionList::Update(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; // Check for changes in mount points if the list type is mount and update the list and render if needed @@ -99,10 +99,8 @@ int GUIPartitionList::Update(void) GUIScrollList::Update(); if (updateList) { - int listSize = 0; - - // Completely update the list if needed -- Used primarily for - // restore as the list for restore will change depending on what + // Completely update the list if needed -- Used primarily for + // restore as the list for restore will change depending on what // partitions were backed up mList.clear(); PartitionManager.Get_Partition_List(ListType, &mList); @@ -126,7 +124,7 @@ int GUIPartitionList::NotifyVarChange(const std::string& varName, const std::str { GUIScrollList::NotifyVarChange(varName, value); - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (varName == mVariable && !mUpdate) diff --git a/gui/patternpassword.cpp b/gui/patternpassword.cpp index ab446b9f0f..c31737f329 100644 --- a/gui/patternpassword.cpp +++ b/gui/patternpassword.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + #include #include #include @@ -54,7 +72,7 @@ GUIPatternPassword::GUIPatternPassword(xml_node<>* node) mAction = new GUIAction(node); child = FindNode(node, "dot"); - if(child) + if (child) { mDotColor = LoadAttrColor(child, "color", mDotColor); mActiveDotColor = LoadAttrColor(child, "activecolor", mActiveDotColor); @@ -65,18 +83,18 @@ GUIPatternPassword::GUIPatternPassword(xml_node<>* node) } child = FindNode(node, "line"); - if(child) + if (child) { mLineColor = LoadAttrColor(child, "color", mLineColor); mLineWidth = LoadAttrIntScaleX(child, "width", mLineWidth); } child = FindNode(node, "data"); - if(child) + if (child) mPassVar = LoadAttrString(child, "name", ""); child = FindNode(node, "size"); - if(child) { + if (child) { mSizeVar = LoadAttrString(child, "name", ""); // Use the configured default, if set. @@ -84,7 +102,7 @@ GUIPatternPassword::GUIPatternPassword(xml_node<>* node) Resize(size); } - if(!mDotImage || !mDotImage->GetResource() || !mActiveDotImage || !mActiveDotImage->GetResource()) + if (!mDotImage || !mDotImage->GetResource() || !mActiveDotImage || !mActiveDotImage->GetResource()) { mDotCircle = gr_render_circle(mDotRadius, mDotColor.red, mDotColor.green, mDotColor.blue, mDotColor.alpha); mActiveDotCircle = gr_render_circle(mDotRadius/2, mActiveDotColor.red, mActiveDotColor.green, mActiveDotColor.blue, mActiveDotColor.alpha); @@ -104,10 +122,10 @@ GUIPatternPassword::~GUIPatternPassword() delete[] mDots; delete[] mConnectedDots; - if(mDotCircle) + if (mDotCircle) gr_free_surface(mDotCircle); - if(mActiveDotCircle) + if (mActiveDotCircle) gr_free_surface(mActiveDotCircle); } @@ -115,7 +133,7 @@ void GUIPatternPassword::ResetActiveDots() { mConnectedDotsLen = 0; mCurLineX = mCurLineY = -1; - for(size_t i = 0; i < mGridSize * mGridSize; ++i) + for (size_t i = 0; i < mGridSize * mGridSize; ++i) mDots[i].active = false; } @@ -155,9 +173,9 @@ void GUIPatternPassword::CalculateDotPositions(void) * n*n-1 */ - for(size_t r = 0; r < mGridSize; ++r) + for (size_t r = 0; r < mGridSize; ++r) { - for(size_t c = 0; c < mGridSize; ++c) + for (size_t c = 0; c < mGridSize; ++c) { mDots[mGridSize*r + c].x = x; mDots[mGridSize*r + c].y = y; @@ -170,29 +188,29 @@ void GUIPatternPassword::CalculateDotPositions(void) int GUIPatternPassword::Render(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; gr_color(mLineColor.red, mLineColor.green, mLineColor.blue, mLineColor.alpha); - for(size_t i = 1; i < mConnectedDotsLen; ++i) { + for (size_t i = 1; i < mConnectedDotsLen; ++i) { const Dot& dp = mDots[mConnectedDots[i-1]]; const Dot& dc = mDots[mConnectedDots[i]]; gr_line(dp.x + mDotRadius, dp.y + mDotRadius, dc.x + mDotRadius, dc.y + mDotRadius, mLineWidth); } - if(mConnectedDotsLen > 0 && mTrackingTouch) { + if (mConnectedDotsLen > 0 && mTrackingTouch) { const Dot& dc = mDots[mConnectedDots[mConnectedDotsLen-1]]; gr_line(dc.x + mDotRadius, dc.y + mDotRadius, mCurLineX, mCurLineY, mLineWidth); } - for(size_t i = 0; i < mGridSize * mGridSize; ++i) { - if(mDotCircle) { + for (size_t i = 0; i < mGridSize * mGridSize; ++i) { + if (mDotCircle) { gr_blit(mDotCircle, 0, 0, gr_get_width(mDotCircle), gr_get_height(mDotCircle), mDots[i].x, mDots[i].y); - if(mDots[i].active) { + if (mDots[i].active) { gr_blit(mActiveDotCircle, 0, 0, gr_get_width(mActiveDotCircle), gr_get_height(mActiveDotCircle), mDots[i].x + mDotRadius/2, mDots[i].y + mDotRadius/2); } } else { - if(mDots[i].active) { + if (mDots[i].active) { gr_blit(mActiveDotImage->GetResource(), 0, 0, mActiveDotImage->GetWidth(), mActiveDotImage->GetHeight(), mDots[i].x + (mDotRadius - mActiveDotImage->GetWidth()/2), mDots[i].y + (mDotRadius - mActiveDotImage->GetHeight()/2)); } else { @@ -205,7 +223,7 @@ int GUIPatternPassword::Render(void) int GUIPatternPassword::Update(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; int res = mNeedRender ? 2 : 1; @@ -214,7 +232,7 @@ int GUIPatternPassword::Update(void) } void GUIPatternPassword::Resize(size_t n) { - if(mGridSize == n) + if (mGridSize == n) return; delete[] mDots; @@ -247,8 +265,8 @@ static bool IsInCircle(int x, int y, int ox, int oy, int r) int GUIPatternPassword::InDot(int x, int y) { - for(size_t i = 0; i < mGridSize * mGridSize; ++i) { - if(IsInCircle(x, y, mDots[i].x + mDotRadius, mDots[i].y + mDotRadius, mDotRadius*3)) + for (size_t i = 0; i < mGridSize * mGridSize; ++i) { + if (IsInCircle(x, y, mDots[i].x + mDotRadius, mDots[i].y + mDotRadius, mDotRadius*3)) return i; } return -1; @@ -256,8 +274,8 @@ int GUIPatternPassword::InDot(int x, int y) bool GUIPatternPassword::DotUsed(int dot_idx) { - for(size_t i = 0; i < mConnectedDotsLen; ++i) { - if(mConnectedDots[i] == dot_idx) + for (size_t i = 0; i < mConnectedDotsLen; ++i) { + if (mConnectedDots[i] == dot_idx) return true; } return false; @@ -265,7 +283,7 @@ bool GUIPatternPassword::DotUsed(int dot_idx) void GUIPatternPassword::ConnectDot(int dot_idx) { - if(mConnectedDotsLen >= mGridSize * mGridSize) + if (mConnectedDotsLen >= mGridSize * mGridSize) { LOGERR("mConnectedDots in GUIPatternPassword has overflown!\n"); return; @@ -277,7 +295,7 @@ void GUIPatternPassword::ConnectDot(int dot_idx) void GUIPatternPassword::ConnectIntermediateDots(int next_dot_idx) { - if(mConnectedDotsLen == 0) + if (mConnectedDotsLen == 0) return; const int prev_dot_idx = mConnectedDots[mConnectedDotsLen-1]; @@ -311,15 +329,15 @@ void GUIPatternPassword::ConnectIntermediateDots(int next_dot_idx) int Dy = (ny > py) ? 1 : -1; // Vertical lines. - if(px == nx) + if (px == nx) Dx = 0; // Horizontal lines. - else if(py == ny) + else if (py == ny) Dy = 0; // Diagonal lines (|∆x| = |∆y|). - else if(abs(px - nx) == abs(py - ny)) + else if (abs(px - nx) == abs(py - ny)) ; // No valid intermediate dots. @@ -327,19 +345,19 @@ void GUIPatternPassword::ConnectIntermediateDots(int next_dot_idx) return; // Iterate along axis, adding dots in the correct order. - while((Dy == 0 || y != ny - Dy) && (Dx == 0 || x != nx - Dx)) { + while ((Dy == 0 || y != ny - Dy) && (Dx == 0 || x != nx - Dx)) { x += Dx; y += Dy; int idx = mGridSize * y + x; - if(!DotUsed(idx)) + if (!DotUsed(idx)) ConnectDot(idx); } } int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y) { - if(!isConditionTrue()) + if (!isConditionTrue()) return -1; switch (state) @@ -347,7 +365,7 @@ int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y) case TOUCH_START: { const int dot_idx = InDot(x, y); - if(dot_idx == -1) + if (dot_idx == -1) break; mTrackingTouch = true; @@ -361,11 +379,11 @@ int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y) } case TOUCH_DRAG: { - if(!mTrackingTouch) + if (!mTrackingTouch) break; const int dot_idx = InDot(x, y); - if(dot_idx != -1 && !DotUsed(dot_idx)) + if (dot_idx != -1 && !DotUsed(dot_idx)) { ConnectIntermediateDots(dot_idx); ConnectDot(dot_idx); @@ -379,7 +397,7 @@ int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y) } case TOUCH_RELEASE: { - if(!mTrackingTouch) + if (!mTrackingTouch) break; mNeedRender = true; @@ -396,10 +414,10 @@ int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y) int GUIPatternPassword::NotifyVarChange(const std::string& varName, const std::string& value) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; - if(varName == mSizeVar) { + if (varName == mSizeVar) { Resize(atoi(value.c_str())); mUpdate = true; } @@ -420,8 +438,8 @@ static unsigned int getSDKVersion(void) { std::string GUIPatternPassword::GeneratePassphrase() { char pattern[mConnectedDotsLen]; - for(size_t i = 0; i < mConnectedDotsLen; i++) { - pattern[i] = (char) mConnectedDots[i]; + for (size_t i = 0; i < mConnectedDotsLen; i++) { + pattern[i] = (char) mConnectedDots[i]; } std::stringstream pass; @@ -461,9 +479,9 @@ std::string GUIPatternPassword::GeneratePassphrase() void GUIPatternPassword::PatternDrawn() { - if(!mPassVar.empty() && mConnectedDotsLen > 0) + if (!mPassVar.empty() && mConnectedDotsLen > 0) DataManager::SetValue(mPassVar, GeneratePassphrase()); - if(mAction) + if (mAction) mAction->doActions(); } diff --git a/gui/progressbar.cpp b/gui/progressbar.cpp index fc2a4976cb..56ce48091d 100644 --- a/gui/progressbar.cpp +++ b/gui/progressbar.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // progressbar.cpp - GUIProgressBar object #include @@ -67,7 +85,7 @@ GUIProgressBar::GUIProgressBar(xml_node<>* node) : GUIObject(node) int GUIProgressBar::Render(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; // This handles making sure timing updates occur @@ -90,7 +108,7 @@ int GUIProgressBar::RenderInternal(void) int GUIProgressBar::Update(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; std::string str; @@ -165,7 +183,7 @@ int GUIProgressBar::NotifyVarChange(const std::string& varName, const std::strin { GUIObject::NotifyVarChange(varName, value); - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; static int nextPush = 0; diff --git a/gui/resources.cpp b/gui/resources.cpp index 9c97dad189..59b8ed484e 100644 --- a/gui/resources.cpp +++ b/gui/resources.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // resource.cpp - Source to manage GUI resources #include @@ -10,8 +28,9 @@ #include #include #include +#include -#include "../minzip/Zip.h" +#include "../zipwrap.hpp" extern "C" { #include "../twcommon.h" #include "gui.h" @@ -23,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) { + if (!pZip->ExtractEntry(src, destFile, 0666)) return -1; - } - - unlink(destFile.c_str()); - int fd = creat(destFile.c_str(), 0666); - if (fd < 0) - 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) @@ -100,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; @@ -108,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; @@ -123,7 +128,7 @@ void FontResource::LoadFont(xml_node<>* node, ZipArchive* pZip) file = attr->value(); - if(file.size() >= 4 && file.compare(file.size()-4, 4, ".ttf") == 0) + if (file.size() >= 4 && file.compare(file.size()-4, 4, ".ttf") == 0) { int font_size = 0; @@ -143,7 +148,7 @@ void FontResource::LoadFont(xml_node<>* node, ZipArchive* pZip) int dpi = 300; attr = node->first_attribute("dpi"); - if(attr) + if (attr) dpi = atoi(attr->value()); // we can't use TMP_RESOURCE_NAME here because the ttf subsystem is caching the name and scaling needs to reload the font @@ -165,15 +170,15 @@ void FontResource::LoadFont(xml_node<>* node, ZipArchive* pZip) } void FontResource::DeleteFont() { - if(mFont) + if (mFont) gr_ttf_freeFont(mFont); mFont = NULL; - if(origFont) + if (origFont) gr_ttf_freeFont(origFont); 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) { @@ -188,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; @@ -219,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; @@ -340,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 528fecc141..de673184c5 100644 --- a/gui/resources.hpp +++ b/gui/resources.hpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // resources.hpp - Base classes for resource management of GUI #ifndef _RESOURCE_HEADER @@ -7,8 +25,7 @@ #include #include #include "rapidxml.hpp" - -struct ZipArchive; +#include "../zipwrap.hpp" extern "C" { #include "../minuitwrp/minui.h" @@ -18,7 +35,7 @@ extern "C" { class Resource { public: - Resource(xml_node<>* node, ZipArchive* pZip); + Resource(xml_node<>* node, ZipWrap* pZip); virtual ~Resource() {} public: @@ -28,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: @@ -59,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: @@ -74,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: @@ -94,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/scrolllist.cpp b/gui/scrolllist.cpp index 291b3820bf..7540356bfd 100644 --- a/gui/scrolllist.cpp +++ b/gui/scrolllist.cpp @@ -190,7 +190,7 @@ void GUIScrollList::SetVisibleListLocation(size_t list_index) int GUIScrollList::Render(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; // First step, fill background @@ -352,7 +352,7 @@ void GUIScrollList::RenderStdItem(int yPos, bool selected, ImageResource* icon, int GUIScrollList::Update(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (!mHeaderIsStatic) { @@ -421,7 +421,7 @@ size_t GUIScrollList::HitTestItem(int x __unused, int y) int GUIScrollList::NotifyTouch(TOUCH_STATE state, int x, int y) { - if(!isConditionTrue()) + if (!isConditionTrue()) return -1; switch (state) @@ -524,7 +524,7 @@ void GUIScrollList::HandleScrolling() { // handle dragging downward, scrolling upward // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed - while(firstDisplayedItem && y_offset > 0) { + while (firstDisplayedItem && y_offset > 0) { firstDisplayedItem--; y_offset -= actualItemHeight; } @@ -569,7 +569,7 @@ int GUIScrollList::NotifyVarChange(const std::string& varName, const std::string { GUIObject::NotifyVarChange(varName, value); - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (!mHeaderIsStatic) { @@ -624,7 +624,7 @@ bool GUIScrollList::AddLines(std::vector* origText, std::vectorat(i); - for(;;) { + for (;;) { size_t line_char_width = gr_ttf_maxExW(curr_line.c_str(), mFont->GetResource(), mRenderW); if (line_char_width < curr_line.size()) { //string left = curr_line.substr(0, line_char_width); diff --git a/gui/slider.cpp b/gui/slider.cpp index 59b295bd06..ed5c615ceb 100644 --- a/gui/slider.cpp +++ b/gui/slider.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // slider.cpp - GUISlider object // Pulled & ported from https://raw.github.com/agrabren/RecoverWin/master/gui/slider.cpp @@ -108,7 +126,7 @@ GUISlider::~GUISlider() int GUISlider::Render(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (!sSlider || !sSlider->GetResource()) @@ -136,7 +154,7 @@ int GUISlider::Render(void) int GUISlider::Update(void) { - if(!isConditionTrue()) + if (!isConditionTrue()) return 0; if (sUpdate) @@ -146,7 +164,7 @@ int GUISlider::Update(void) int GUISlider::NotifyTouch(TOUCH_STATE state, int x, int y) { - if(!isConditionTrue()) + if (!isConditionTrue()) return -1; static bool dragging = false; diff --git a/gui/slidervalue.cpp b/gui/slidervalue.cpp index 088620a21d..6f007e23b1 100644 --- a/gui/slidervalue.cpp +++ b/gui/slidervalue.cpp @@ -1,3 +1,21 @@ +/* + Copyright 2017 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 . +*/ + // slidervalue.cpp - GUISliderValue object #include @@ -62,7 +80,7 @@ GUISliderValue::GUISliderValue(xml_node<>* node) : GUIObject(node) } mLabel = new GUIText(node); - if(mLabel->Render() < 0) + if (mLabel->Render() < 0) { delete mLabel; mLabel = NULL; @@ -156,7 +174,7 @@ GUISliderValue::GUISliderValue(xml_node<>* node) : GUIObject(node) mFontHeight = mFont->GetHeight(); - if(mShowCurr) + if (mShowCurr) { int maxLen = std::max(strlen(mMinStr.c_str()), strlen(mMaxStr.c_str())); mValueStr = new char[maxLen+1]; @@ -182,10 +200,10 @@ GUISliderValue::~GUISliderValue() void GUISliderValue::loadValue(bool force) { - if(!mVariable.empty()) + if (!mVariable.empty()) { int value = DataManager::GetIntValue(mVariable); - if(mValue == value && !force) + if (mValue == value && !force) return; mValue = value; @@ -208,7 +226,7 @@ int GUISliderValue::SetRenderPos(int x, int y, int w, int h) } mRenderH = mSliderH; - if(mShowCurr) + if (mShowCurr) mRenderH += mFontHeight; if (mLabel) @@ -230,7 +248,7 @@ int GUISliderValue::SetRenderPos(int x, int y, int w, int h) mActionW = mRenderW; mActionH = mRenderH; - if(mBackgroundImage && mBackgroundImage->GetResource()) + if (mBackgroundImage && mBackgroundImage->GetResource()) { mLineW = mBackgroundImage->GetWidth(); mLineH = mBackgroundImage->GetHeight(); @@ -260,7 +278,7 @@ int GUISliderValue::Render(void) return 0; } - if(mLabel) + if (mLabel) { int w, h; mLabel->GetCurrentBounds(w, h); @@ -270,12 +288,12 @@ int GUISliderValue::Render(void) mLabel->SetRenderPos(textX, mRenderY); } int res = mLabel->Render(); - if(res < 0) + if (res < 0) return res; } // line - if(mBackgroundImage && mBackgroundImage->GetResource()) + if (mBackgroundImage && mBackgroundImage->GetResource()) { gr_blit(mBackgroundImage->GetResource(), 0, 0, mLineW, mLineH, mLineX, mLineY); } @@ -288,10 +306,10 @@ int GUISliderValue::Render(void) // slider uint32_t sliderX = mLineX + (mValuePct*(mLineW - mSliderW))/100; - if(mHandleImage && mHandleImage->GetResource()) + if (mHandleImage && mHandleImage->GetResource()) { gr_surface s = mHandleImage->GetResource(); - if(mDragging && mHandleHoverImage && mHandleHoverImage->GetResource()) + if (mDragging && mHandleHoverImage && mHandleHoverImage->GetResource()) s = mHandleHoverImage->GetResource(); gr_blit(s, 0, 0, mSliderW, mSliderH, sliderX, mLineY + (mLineH/2 - mSliderH/2)); } @@ -302,16 +320,16 @@ int GUISliderValue::Render(void) } void *fontResource = NULL; - if(mFont) fontResource = mFont->GetResource(); + if (mFont) fontResource = mFont->GetResource(); gr_color(mTextColor.red, mTextColor.green, mTextColor.blue, mTextColor.alpha); - if(mShowRange) + if (mShowRange) { int rangeY = (mLineY - mLineH/2) - mFontHeight/2; gr_textEx_scaleW(mRenderX + mPadding/2, rangeY, mMinStr.c_str(), fontResource, mRenderW, TOP_LEFT, 0); gr_textEx_scaleW(mLineX + mLineW + mPadding/2, rangeY, mMaxStr.c_str(), fontResource, mRenderW, TOP_LEFT, 0); } - if(mValueStr && mShowCurr) + if (mValueStr && mShowCurr) { sprintf(mValueStr, "%d", mValue); int textW = measureText(mValueStr); @@ -329,7 +347,7 @@ int GUISliderValue::Update(void) if (!mRendered) return 2; - if(mLabel) + if (mLabel) return mLabel->Update(); return 0; } @@ -406,7 +424,7 @@ int GUISliderValue::NotifyVarChange(const std::string& varName, const std::strin mLabel->NotifyVarChange(varName, value); if (varName == mVariable) { int newVal = atoi(value.c_str()); - if(newVal != mValue) { + if (newVal != mValue) { mValue = newVal; mValuePct = pctFromValue(mValue); mRendered = false; diff --git a/gui/terminal.cpp b/gui/terminal.cpp index b4194d0fb0..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; @@ -389,6 +392,7 @@ class TerminalEngine lines.clear(); setY(0); unpackLine(0); + linewrap = false; ++updateCounter; } @@ -465,6 +469,7 @@ class TerminalEngine { x = min(width, max(x, 0)); cursorX = x; + linewrap = false; ++updateCounter; } @@ -473,6 +478,7 @@ class TerminalEngine //y = min(height, max(y, 0)); y = max(y, 0); cursorY = y; + linewrap = false; while (lines.size() <= (size_t) y) lines.push_back(Line()); ++updateCounter; @@ -500,7 +506,7 @@ class TerminalEngine uint32_t u8state = 0, u8cp = 0; std::string& s = lines[y].text; unpackedLine.cells.clear(); - for(size_t i = 0; i < s.size(); ++i) { + for (size_t i = 0; i < s.size(); ++i) { uint32_t rc = utf8decode(&u8state, &u8cp, (unsigned char)s[i]); if (rc == UTF8_ACCEPT) unpackedLine.cells.push_back(Cell(u8cp)); @@ -581,21 +587,20 @@ class TerminalEngine void processChar(CodePoint cp) { + if (linewrap) { + down(); + setX(0); + } ensureUnpacked(cursorY); // extend unpackedLine if needed, write ch into cell if (unpackedLine.cells.size() <= (size_t)cursorX) unpackedLine.cells.resize(cursorX+1); unpackedLine.cells[cursorX].cp = cp; - right(); + right(); // also bumps updateCounter + if (cursorX >= width) - { - // TODO: configurable line wrapping - // TODO: don't go down immediately but only on next char? - down(); - setX(0); - } - // TODO: update all GUI objects that display this terminal engine + linewrap = true; } void processEsc(CodePoint cp) @@ -744,6 +749,7 @@ class TerminalEngine private: int cursorX, cursorY; // 0-based, char based. TODO: decide how to handle scrollback + bool linewrap; // true to put next character into next line int width, height; // window size in chars std::vector lines; // the text buffer UnpackedLine unpackedLine; // current line for editing @@ -776,7 +782,10 @@ GUITerminal::GUITerminal(xml_node<>* node) : GUIScrollList(node) lastCondition = false; if (!node) { - mRenderX = 0; mRenderY = 0; mRenderW = gr_fb_width(); mRenderH = gr_fb_height(); + mRenderX = 0; + mRenderY = 0; + mRenderW = gr_fb_width(); + mRenderH = gr_fb_height(); } engine = &gEngine; @@ -785,7 +794,7 @@ GUITerminal::GUITerminal(xml_node<>* node) : GUIScrollList(node) int GUITerminal::Update(void) { - if(!isConditionTrue()) { + if (!isConditionTrue()) { lastCondition = false; return 0; } @@ -816,7 +825,7 @@ int GUITerminal::Update(void) // Return 0 on success, >0 to ignore remainder of touch, and <0 on error int GUITerminal::NotifyTouch(TOUCH_STATE state, int x, int y) { - if(!isConditionTrue()) + if (!isConditionTrue()) return -1; // TODO: grab focus correctly diff --git a/gui/text.cpp b/gui/text.cpp index 49e27a1042..61940ff9fd 100644 --- a/gui/text.cpp +++ b/gui/text.cpp @@ -114,9 +114,6 @@ int GUIText::Render(void) mVarChanged = 0; - int x = mRenderX, y = mRenderY; - int width = gr_ttf_measureEx(mLastValue.c_str(), fontResource); - if (isHighlighted) gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha); else diff --git a/gui/textbox.cpp b/gui/textbox.cpp index 2e2512c50f..2c7d09f993 100644 --- a/gui/textbox.cpp +++ b/gui/textbox.cpp @@ -98,7 +98,7 @@ int GUITextBox::NotifyVarChange(const std::string& varName, const std::string& v { GUIScrollList::NotifyVarChange(varName, value); - if(!isConditionTrue() || mIsStatic) + if (!isConditionTrue() || mIsStatic) return 0; // Check to see if the variable exists in mText diff --git a/gui/theme/common/landscape.xml b/gui/theme/common/landscape.xml index 5c92fccd31..f460e6fee7 100644 --- a/gui/theme/common/landscape.xml +++ b/gui/theme/common/landscape.xml @@ -139,6 +139,7 @@