diff --git a/.clang-format b/.clang-format index 0e0f4d143e..6fa717cfb9 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,33 @@ +# bootable/recovery project uses repohook to apply `clang-format` to the changed lines, with the +# local style file in `.clang-format`. This will be triggered automatically with `repo upload`. +# Alternatively, one can stage and format a change with `git clang-format` directly. +# +# $ git add +# $ git clang-format --style file +# +# Or to format a committed change. +# +# $ git clang-format --style file HEAD~1 +# +# `--style file` will pick up the local style file in `.clang-format`. This can be configured as the +# default behavior for bootable/recovery project. +# +# $ git config --local clangFormat.style file +# +# Note that `repo upload` calls the `clang-format` binary in Android repo (i.e. +# `$ANDROID_BUILD_TOP/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format`), which might +# give slightly different results from the one installed in host machine (e.g. +# `/usr/bin/clang-format`). Specifying the file with `--binary` will ensure consistent results. +# +# $ git clang-format --binary \ +# /path/to/aosp-master/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format +# +# Or to do one-time setup to make it default. +# +# $ git config --local clangFormat.binary \ +# /path/to/aosp-master/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format +# + BasedOnStyle: Google AllowShortBlocksOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty @@ -6,6 +36,7 @@ AllowShortIfStatementsOnASingleLine: true ColumnLimit: 100 CommentPragmas: NOLINT:.* DerivePointerAlignment: false +IncludeBlocks: Preserve IndentWidth: 2 PointerAlignment: Left TabWidth: 2 diff --git a/Android.bp b/Android.bp index f8c6a4b71f..8e29dc8fde 100644 --- a/Android.bp +++ b/Android.bp @@ -1,8 +1,203 @@ -subdirs = [ - "applypatch", - "bootloader_message", - "edify", - "otafault", - "otautil", - "uncrypt", -] +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_defaults { + name: "recovery_defaults", + + cflags: [ + "-D_FILE_OFFSET_BITS=64", + + // Must be the same as RECOVERY_API_VERSION. + "-DRECOVERY_API_VERSION=3", + + "-Wall", + "-Werror", + ], +} + +cc_library_static { + name: "librecovery_fastboot", + recovery_available: true, + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "fastboot/fastboot.cpp", + ], + + shared_libs: [ + "libbase", + "libbootloader_message", + "libcutils", + "liblog", + "librecovery_ui", + ], + + static_libs: [ + "librecovery_ui_default", + ], +} + +cc_defaults { + name: "librecovery_defaults", + + defaults: [ + "recovery_defaults", + ], + + shared_libs: [ + "android.hardware.boot@1.0", + "android.hardware.boot@1.1", + "libbase", + "libbootloader_message", + "libcrypto", + "libcutils", + "libfs_mgr", + "liblp", + "liblog", + "libprotobuf-cpp-lite", + "libziparchive", + ], + + static_libs: [ + "libc++fs", + "libinstall", + "librecovery_fastboot", + "libminui", + "librecovery_utils", + "libotautil", + "libsnapshot_nobinder", + ], +} + +cc_library_static { + name: "librecovery", + recovery_available: true, + + defaults: [ + "librecovery_defaults", + ], + + srcs: [ + "recovery.cpp", + ], + + shared_libs: [ + "librecovery_ui", + ], +} + +prebuilt_etc { + name: "init_recovery.rc", + filename: "init.rc", + src: "etc/init.rc", + sub_dir: "init/hw", + recovery: true, +} + +cc_binary { + name: "recovery", + recovery: true, + + defaults: [ + "libinstall_defaults", + "librecovery_defaults", + "librecovery_utils_defaults", + ], + + srcs: [ + "recovery_main.cpp", + ], + + shared_libs: [ + "librecovery_ui", + ], + + static_libs: [ + "librecovery", + "librecovery_ui_default", + ], + + required: [ + "e2fsdroid.recovery", + "init_recovery.rc", + "librecovery_ui_ext", + "minadbd", + "mke2fs.conf.recovery", + "mke2fs.recovery", + "recovery_deps", + "ueventd.rc.recovery", + ], +} + +// The dynamic executable that runs after /data mounts. +cc_binary { + name: "recovery-persist", + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "recovery-persist.cpp", + ], + + shared_libs: [ + "libbase", + "liblog", + ], + + static_libs: [ + "librecovery_utils", + ], + + init_rc: [ + "recovery-persist.rc", + ], +} + +// The dynamic executable that runs at init. +cc_binary { + name: "recovery-refresh", + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "recovery-refresh.cpp", + ], + + shared_libs: [ + "libbase", + "liblog", + ], + + static_libs: [ + "librecovery_utils", + ], + + init_rc: [ + "recovery-refresh.rc", + ], +} + +filegroup { + name: "res-testdata", + + srcs: [ + "res-*/images/*_text.png", + ], +} diff --git a/Android.mk b/Android.mk index 7e0ad122e8..9f691531c9 100644 --- a/Android.mk +++ b/Android.mk @@ -14,267 +14,65 @@ LOCAL_PATH := $(call my-dir) -# Needed by build/make/core/Makefile. +# Needed by build/make/core/Makefile. Must be consistent with the value in Android.bp. RECOVERY_API_VERSION := 3 RECOVERY_FSTAB_VERSION := 2 -# libfusesideload (static library) -# =============================== -include $(CLEAR_VARS) -LOCAL_SRC_FILES := fuse_sideload.cpp -LOCAL_CFLAGS := -Wall -Werror -LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE -LOCAL_MODULE := libfusesideload -LOCAL_STATIC_LIBRARIES := \ - libcrypto \ - libbase -include $(BUILD_STATIC_LIBRARY) - -# libmounts (static library) -# =============================== -include $(CLEAR_VARS) -LOCAL_SRC_FILES := mounts.cpp -LOCAL_CFLAGS := \ - -Wall \ - -Werror -LOCAL_MODULE := libmounts -LOCAL_STATIC_LIBRARIES := libbase -include $(BUILD_STATIC_LIBRARY) - -# librecovery (static library) -# =============================== -include $(CLEAR_VARS) -LOCAL_SRC_FILES := \ - install.cpp -LOCAL_CFLAGS := -Wall -Werror -LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) - -ifeq ($(AB_OTA_UPDATER),true) - LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 -endif - -LOCAL_MODULE := librecovery -LOCAL_STATIC_LIBRARIES := \ - libminui \ - libotautil \ - libvintf_recovery \ - libcrypto_utils \ - libcrypto \ - libbase \ - libziparchive \ - -include $(BUILD_STATIC_LIBRARY) +# TARGET_RECOVERY_UI_LIB should be one of librecovery_ui_{default,wear,vr,ethernet} or a +# device-specific module that defines make_device() and the exact RecoveryUI class for the +# target. It defaults to librecovery_ui_default, which uses ScreenRecoveryUI. +TARGET_RECOVERY_UI_LIB ?= librecovery_ui_default -# recovery (static executable) -# =============================== +# librecovery_ui_ext (shared library) +# =================================== include $(CLEAR_VARS) -LOCAL_SRC_FILES := \ - adb_install.cpp \ - device.cpp \ - fuse_sdcard_provider.cpp \ - recovery.cpp \ - roots.cpp \ - rotate_logs.cpp \ - screen_ui.cpp \ - ui.cpp \ - vr_ui.cpp \ - wear_ui.cpp \ - -LOCAL_MODULE := recovery - -LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE := librecovery_ui_ext -LOCAL_REQUIRED_MODULES := e2fsdroid_static mke2fs_static mke2fs.conf +# LOCAL_MODULE_PATH for shared libraries is unsupported in multiarch builds. +LOCAL_MULTILIB := first -ifeq ($(TARGET_USERIMAGES_USE_F2FS),true) -ifeq ($(HOST_OS),linux) -LOCAL_REQUIRED_MODULES += sload.f2fs mkfs.f2fs -endif -endif - -LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) -LOCAL_CFLAGS += -Wall -Werror - -ifneq ($(TARGET_RECOVERY_UI_MARGIN_HEIGHT),) -LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=$(TARGET_RECOVERY_UI_MARGIN_HEIGHT) -else -LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=0 -endif - -ifneq ($(TARGET_RECOVERY_UI_MARGIN_WIDTH),) -LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=$(TARGET_RECOVERY_UI_MARGIN_WIDTH) +ifeq ($(TARGET_IS_64_BIT),true) +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib64 else -LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=0 +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib endif -ifneq ($(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD),) -LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD) -else -LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=50 -endif +LOCAL_WHOLE_STATIC_LIBRARIES := \ + $(TARGET_RECOVERY_UI_LIB) -ifneq ($(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD),) -LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD) -else -LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=90 -endif - -ifneq ($(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE),) -LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=$(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE) -else -LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=259 -endif - -ifneq ($(TARGET_RECOVERY_UI_ANIMATION_FPS),) -LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=$(TARGET_RECOVERY_UI_ANIMATION_FPS) -else -LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=30 -endif - -ifneq ($(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS),) -LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=$(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS) -else -LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=9 -endif - -ifneq ($(TARGET_RECOVERY_UI_VR_STEREO_OFFSET),) -LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=$(TARGET_RECOVERY_UI_VR_STEREO_OFFSET) -else -LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=0 -endif - -LOCAL_C_INCLUDES += \ - system/vold \ - -# Health HAL dependency -LOCAL_STATIC_LIBRARIES := \ - android.hardware.health@2.0-impl \ - android.hardware.health@2.0 \ - android.hardware.health@1.0 \ - android.hardware.health@1.0-convert \ - libhealthstoragedefault \ - libhidltransport \ - libhidlbase \ - libhwbinder_noltopgo \ - libvndksupport \ - libbatterymonitor - -LOCAL_STATIC_LIBRARIES += \ - librecovery \ - libverifier \ - libbootloader_message \ - libfs_mgr \ - libext4_utils \ - libsparse \ - libziparchive \ - libotautil \ - libmounts \ - libminadbd \ - libasyncio \ - libfusesideload \ - libminui \ - libpng \ - libcrypto_utils \ - libcrypto \ - libvintf_recovery \ - libvintf \ - libhidl-gen-utils \ - libtinyxml2 \ +LOCAL_SHARED_LIBRARIES := \ libbase \ - libutils \ - libcutils \ liblog \ - libselinux \ - libz + librecovery_ui.recovery -LOCAL_HAL_STATIC_LIBRARIES := libhealthd +include $(BUILD_SHARED_LIBRARY) -ifeq ($(AB_OTA_UPDATER),true) - LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 -endif +# recovery_deps: A phony target that's depended on by `recovery`, which +# builds additional modules conditionally based on Makefile variables. +# ====================================================================== +include $(CLEAR_VARS) -LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_MODULE := recovery_deps -ifeq ($(TARGET_RECOVERY_UI_LIB),) - LOCAL_SRC_FILES += default_device.cpp -else - LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) +ifeq ($(TARGET_USERIMAGES_USE_F2FS),true) +ifeq ($(HOST_OS),linux) +LOCAL_REQUIRED_MODULES += \ + make_f2fs.recovery \ + sload_f2fs.recovery +endif endif +# On A/B devices recovery-persist reads the recovery related file from the persist storage and +# copies them into /data/misc/recovery. Then, for both A/B and non-A/B devices, recovery-persist +# parses the last_install file and reports the embedded update metrics. Also, the last_install file +# will be deteleted after the report. +LOCAL_REQUIRED_MODULES += recovery-persist ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),) -LOCAL_REQUIRED_MODULES += recovery-persist recovery-refresh +LOCAL_REQUIRED_MODULES += recovery-refresh endif -include $(BUILD_EXECUTABLE) - -# recovery-persist (system partition dynamic executable run after /data mounts) -# =============================== -include $(CLEAR_VARS) -LOCAL_SRC_FILES := \ - recovery-persist.cpp \ - rotate_logs.cpp -LOCAL_MODULE := recovery-persist -LOCAL_SHARED_LIBRARIES := liblog libbase -LOCAL_CFLAGS := -Wall -Werror -LOCAL_INIT_RC := recovery-persist.rc -include $(BUILD_EXECUTABLE) - -# recovery-refresh (system partition dynamic executable run at init) -# =============================== -include $(CLEAR_VARS) -LOCAL_SRC_FILES := \ - recovery-refresh.cpp \ - rotate_logs.cpp -LOCAL_MODULE := recovery-refresh -LOCAL_SHARED_LIBRARIES := liblog libbase -LOCAL_CFLAGS := -Wall -Werror -LOCAL_INIT_RC := recovery-refresh.rc -include $(BUILD_EXECUTABLE) - -# libverifier (static library) -# =============================== -include $(CLEAR_VARS) -LOCAL_MODULE := libverifier -LOCAL_SRC_FILES := \ - asn1_decoder.cpp \ - verifier.cpp -LOCAL_STATIC_LIBRARIES := \ - libotautil \ - libcrypto_utils \ - libcrypto \ - libbase -LOCAL_CFLAGS := -Wall -Werror -include $(BUILD_STATIC_LIBRARY) - -# Wear default device -# =============================== -include $(CLEAR_VARS) -LOCAL_SRC_FILES := wear_device.cpp -LOCAL_CFLAGS := -Wall -Werror - -# 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 -LOCAL_CFLAGS := -Wall -Werror - -# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk -LOCAL_MODULE := librecovery_ui_vr - -include $(BUILD_STATIC_LIBRARY) +include $(BUILD_PHONY_PACKAGE) include \ - $(LOCAL_PATH)/boot_control/Android.mk \ - $(LOCAL_PATH)/minadbd/Android.mk \ - $(LOCAL_PATH)/minui/Android.mk \ - $(LOCAL_PATH)/tests/Android.mk \ - $(LOCAL_PATH)/tools/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ - $(LOCAL_PATH)/update_verifier/Android.mk \ diff --git a/CleanSpec.mk b/CleanSpec.mk index e2d97d42ba..d4e9e437b7 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -44,8 +44,31 @@ #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/sbin) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinstall.recovery_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/system/lib64/libinstall.so) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest/recovery_component_test) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest64/recovery_component_test) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/testcases/recovery_component_test) + +$(call add-clean-step, find $(OUT_DIR) -type f -name "SystemUpdaterSample*" -print0 | xargs -0 rm -f) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/SystemUpdaterSample) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib*/libbrotli.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib*/libbz.so) + +# Move recovery resources from /system to /vendor. +$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/bin/applypatch) +$(call add-clean-step, rm -r $(PRODUCT_OUT)/symbols/system/bin/applypatch) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/bin/applypatch) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/bin/install-recovery.sh) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/etc/recovery-resource.dat) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/recovery-from-boot.p) + # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ -$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates) -$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes) diff --git a/OWNERS b/OWNERS index b3f11dcd6c..79dd9f7768 100644 --- a/OWNERS +++ b/OWNERS @@ -1,3 +1,5 @@ +elsk@google.com enh@google.com -tbao@google.com +nhdo@google.com xunchang@google.com +zhaojiac@google.com diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index b5f5f03628..28aa06f455 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -4,3 +4,7 @@ clang_format = true [Builtin Hooks Options] # Handle native codes only. clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp + +[Hook Scripts] +checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} + --file_whitelist tools/ updater_sample/ diff --git a/README.md b/README.md index 0aeadaeb46..bd1cf7deaf 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,41 @@ The Recovery Image Quick turn-around testing ------------------------- - mm -j && m ramdisk-nodeps && m recoveryimage-nodeps +* Devices using recovery-as-boot (e.g. Pixels, which set BOARD\_USES\_RECOVERY\_AS\_BOOT) - # To boot into the new recovery image - # without flashing the recovery partition: - adb reboot bootloader - fastboot boot $ANDROID_PRODUCT_OUT/recovery.img + # After setting up environment and lunch. + m -j bootimage + adb reboot bootloader + + # Pixel devices don't support booting into recovery mode with `fastboot boot`. + fastboot flash boot + + # Manually choose `Recovery mode` from bootloader menu. + +* Devices with a separate recovery image (e.g. Nexus) + + # After setting up environment and lunch. + mm -j && m ramdisk-nodeps && m recoveryimage-nodeps + adb reboot bootloader + + # To boot into the new recovery image without flashing the recovery partition: + fastboot boot $ANDROID_PRODUCT_OUT/recovery.img Running the tests ----------------- + # After setting up environment and lunch. mmma -j bootable/recovery - # Running the tests on device. + # Running the tests on device (under normal boot). adb root adb sync data # 32-bit device adb shell /data/nativetest/recovery_unit_test/recovery_unit_test - adb shell /data/nativetest/recovery_component_test/recovery_component_test # Or 64-bit device adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test - adb shell /data/nativetest64/recovery_component_test/recovery_component_test Running the manual tests ------------------------ @@ -41,13 +53,6 @@ Running the manual tests contents of pmsg buffer into /data/misc/recovery/inject.txt. Test will pass if this file has expected contents. -`ResourceTest` validates whether the png files are qualified as background text -image under recovery. - - 1. `adb sync data` to make sure the test-dir has the images to test. - 2. The test will automatically pickup and verify all `_text.png` files in - the test dir. - Using `adb` under recovery -------------------------- @@ -60,10 +65,10 @@ allows `adb` communication. A device should be listed under `adb devices`, eithe List of devices attached 1234567890abcdef recovery -Although `/sbin/adbd` shares the same binary between normal boot and recovery images, only a subset -of `adb` commands are meaningful under recovery, such as `adb root`, `adb shell`, `adb push`, `adb -pull` etc. `adb shell` works only after manually mounting `/system` from recovery menu (assuming a -valid system image on device). +Although `/system/bin/adbd` is built from the same code base as the one in the normal boot, only a +subset of `adb` commands are meaningful under recovery, such as `adb root`, `adb shell`, `adb push`, +`adb pull` etc. Since Android Q, `adb shell` no longer requires manually mounting `/system` from +recovery menu. ## Troubleshooting @@ -74,8 +79,8 @@ valid system image on device). * Ensure `adbd` is built and running. -By default, `adbd` is always included into recovery image, as `/sbin/adbd`. `init` starts `adbd` -service automatically only in debuggable builds. This behavior is controlled by the recovery +By default, `adbd` is always included into recovery image, as `/system/bin/adbd`. `init` starts +`adbd` service automatically only in debuggable builds. This behavior is controlled by the recovery specific `/init.rc`, whose source code is at `bootable/recovery/etc/init.rc`. The best way to confirm a running `adbd` is by checking the serial output, which shows a service diff --git a/TEST_MAPPING b/TEST_MAPPING new file mode 100644 index 0000000000..a3045828ef --- /dev/null +++ b/TEST_MAPPING @@ -0,0 +1,14 @@ +{ + "presubmit": [ + { + "name": "minadbd_test" + }, + { + "name": "recovery_unit_test" + }, + { + "name": "recovery_host_test", + "host": true + } + ] +} diff --git a/adb_install.cpp b/adb_install.cpp deleted file mode 100644 index ac0130651b..0000000000 --- a/adb_install.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2012 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 "adb_install.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "common.h" -#include "fuse_sideload.h" -#include "install.h" -#include "ui.h" - -static void set_usb_driver(bool enabled) { - // USB configfs doesn't use /s/c/a/a/enable. - if (android::base::GetBoolProperty("sys.usb.configfs", false)) { - return; - } - - static constexpr const char* USB_DRIVER_CONTROL = "/sys/class/android_usb/android0/enable"; - android::base::unique_fd fd(open(USB_DRIVER_CONTROL, O_WRONLY)); - if (fd == -1) { - PLOG(ERROR) << "Failed to open driver control"; - return; - } - // Not using android::base::WriteStringToFile since that will open with O_CREAT and give EPERM - // when USB_DRIVER_CONTROL doesn't exist. When it gives EPERM, we don't know whether that's due - // to non-existent USB_DRIVER_CONTROL or indeed a permission issue. - if (!android::base::WriteStringToFd(enabled ? "1" : "0", fd)) { - PLOG(ERROR) << "Failed to set driver control"; - } -} - -static void stop_adbd() { - ui->Print("Stopping adbd...\n"); - android::base::SetProperty("ctl.stop", "adbd"); - set_usb_driver(false); -} - -static void maybe_restart_adbd() { - if (is_ro_debuggable()) { - ui->Print("Restarting adbd...\n"); - set_usb_driver(true); - android::base::SetProperty("ctl.start", "adbd"); - } -} - -int apply_from_adb(bool* wipe_cache, const char* install_file) { - modified_flash = 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", nullptr); - _exit(EXIT_FAILURE); - } - - // How long (in seconds) we wait for the host to start sending us a package, before timing out. - static constexpr int ADB_INSTALL_TIMEOUT = 300; - - // 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; - for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { - if (waitpid(child, &status, WNOHANG) != 0) { - result = INSTALL_ERROR; - waited = true; - break; - } - - struct stat st; - if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { - if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) { - sleep(1); - continue; - } else { - ui->Print("\nTimed out waiting for package.\n\n"); - result = INSTALL_ERROR; - kill(child, SIGKILL); - break; - } - } - result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false, 0); - break; - } - - if (!waited) { - // Calling stat() on this magic filename signals the minadbd subprocess to shut down. - struct stat st; - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); - - // TODO: 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) { - ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); - } else if (!WIFSIGNALED(status)) { - ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status)); - } - } - - set_usb_driver(false); - maybe_restart_adbd(); - - return result; -} diff --git a/applypatch/Android.bp b/applypatch/Android.bp index cb0b367461..13a9625844 100644 --- a/applypatch/Android.bp +++ b/applypatch/Android.bp @@ -31,6 +31,7 @@ cc_library_static { name: "libapplypatch", host_supported: true, + vendor_available: true, defaults: [ "applypatch_defaults", @@ -51,13 +52,15 @@ cc_library_static { "libbase", "libbspatch", "libbz", - "libcrypto", "libedify", - "libotafault", "libotautil", "libz", ], + shared_libs: [ + "libcrypto", + ], + target: { darwin: { enabled: false, @@ -67,6 +70,7 @@ cc_library_static { cc_library_static { name: "libapplypatch_modes", + vendor_available: true, defaults: [ "applypatch_defaults", @@ -79,14 +83,18 @@ cc_library_static { static_libs: [ "libapplypatch", "libbase", - "libcrypto", "libedify", "libotautil", ], + + shared_libs: [ + "libcrypto", + ], } cc_binary { name: "applypatch", + vendor: true, defaults: [ "applypatch_defaults", @@ -100,27 +108,30 @@ cc_binary { "libapplypatch_modes", "libapplypatch", "libedify", - "libotafault", "libotautil", + + // External dependencies. "libbspatch", + "libbrotli", + "libbz", ], shared_libs: [ "libbase", - "libbrotli", - "libbz", "libcrypto", "liblog", "libz", "libziparchive", ], + + init_rc: [ + "vendor_flash_recovery.rc", + ], } -cc_library_static { +cc_library_host_static { name: "libimgdiff", - host_supported: true, - defaults: [ "applypatch_defaults", ], @@ -172,35 +183,3 @@ cc_binary_host { "libz", ], } - -cc_library_static { - name: "libimgpatch", - - // The host module is for recovery_host_test (Linux only). - host_supported: true, - - defaults: [ - "applypatch_defaults", - ], - - srcs: [ - "bspatch.cpp", - "imgpatch.cpp", - ], - - static_libs: [ - "libbase", - "libbspatch", - "libbz", - "libcrypto", - "libedify", - "libotautil", - "libz", - ], - - target: { - darwin: { - enabled: false, - }, - }, -} diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 7645a40053..adda6976df 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -23,261 +23,167 @@ #include #include #include -#include #include #include +#include #include #include #include #include #include +#include #include #include #include +#include #include #include "edify/expr.h" -#include "otafault/ota_io.h" -#include "otautil/cache_location.h" +#include "otautil/paths.h" #include "otautil/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); +using namespace std::string_literals; -// 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 "EMMC:" means to load the contents of a partition. - if (strncmp(filename, "EMMC:", 5) == 0) { - return LoadPartitionContents(filename, file); - } +static bool GenerateTarget(const Partition& target, const FileContents& source_file, + const Value& patch, const Value* bonus_data, bool backup_source); - struct stat sb; - if (stat(filename, &sb) == -1) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return -1; +bool LoadFileContents(const std::string& filename, FileContents* file) { + // No longer allow loading contents from eMMC partitions. + if (android::base::StartsWith(filename, "EMMC:")) { + return false; } - std::vector data(sb.st_size); - unique_file f(ota_fopen(filename, "rb")); - if (!f) { - printf("failed to open \"%s\": %s\n", filename, strerror(errno)); - return -1; + std::string data; + if (!android::base::ReadFileToString(filename, &data)) { + PLOG(ERROR) << "Failed to read \"" << filename << "\""; + return false; } - 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); + file->data = std::vector(data.begin(), data.end()); SHA1(file->data.data(), file->data.size(), file->sha1); - return 0; + return true; } -// 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. -static int LoadPartitionContents(const std::string& filename, FileContents* file) { - std::vector pieces = android::base::Split(filename, ":"); - if (pieces.size() < 4 || pieces.size() % 2 != 0 || pieces[0] != "EMMC") { - printf("LoadPartitionContents called with bad filename \"%s\"\n", filename.c_str()); - return -1; - } - - size_t pair_count = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename - std::vector> 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; +// Reads the contents of a Partition to the given FileContents buffer. +static bool ReadPartitionToBuffer(const Partition& partition, FileContents* out, + bool check_backup) { + uint8_t expected_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(partition.hash, expected_sha1) != 0) { + LOG(ERROR) << "Failed to parse target hash \"" << partition.hash << "\""; + return false; } - 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; + android::base::unique_fd dev(open(partition.name.c_str(), O_RDONLY)); + if (dev == -1) { + PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\""; + } else { + std::vector buffer(partition.size); + if (!android::base::ReadFully(dev, buffer.data(), buffer.size())) { + PLOG(ERROR) << "Failed to read " << buffer.size() << " bytes of data for partition " + << partition; + } else { + SHA1(buffer.data(), buffer.size(), out->sha1); + if (memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) { + out->data = std::move(buffer); + return true; } - SHA1_Update(&sha_ctx, buffer_ptr, read); - buffer_size += read; - buffer_ptr += read; - } - - // Duplicate the SHA context and finalize the duplicate so we can - // check it against this pair's expected hash. - SHA_CTX temp_ctx; - memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX)); - uint8_t sha_so_far[SHA_DIGEST_LENGTH]; - SHA1_Final(sha_so_far, &temp_ctx); - - 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; + if (!check_backup) { + LOG(ERROR) << "Partition contents don't have the expected checksum"; + return false; } - SHA1_Final(file->sha1, &sha_ctx); - - buffer.resize(buffer_size); - file->data = std::move(buffer); + if (LoadFileContents(Paths::Get().cache_temp_source(), out) && + memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) { + return true; + } - return 0; + LOG(ERROR) << "Both of partition contents and backup don't have the expected checksum"; + return false; } -// 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)); +bool SaveFileContents(const std::string& filename, const FileContents* file) { + android::base::unique_fd fd( + open(filename.c_str(), 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; + PLOG(ERROR) << "Failed to open \"" << filename << "\" for write"; + return false; } - 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 (!android::base::WriteFully(fd, file->data.data(), file->data.size())) { + PLOG(ERROR) << "Failed to write " << file->data.size() << " bytes of data to " << filename; + return false; } - 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::vector pieces = android::base::Split(target, ":"); - if (pieces.size() < 2 || pieces[0] != "EMMC") { - printf("WriteToPartition called with bad target (%s)\n", target.c_str()); - return -1; + if (fsync(fd) != 0) { + PLOG(ERROR) << "Failed to fsync \"" << filename << "\""; + return false; } - const char* partition = pieces[1].c_str(); - unique_fd fd(ota_open(partition, O_RDWR)); - if (fd == -1) { - printf("failed to open %s: %s\n", partition, strerror(errno)); - return -1; + if (close(fd.release()) != 0) { + PLOG(ERROR) << "Failed to close \"" << filename << "\""; + return false; } + return true; +} + +// Writes a memory buffer to 'target' Partition. +static bool WriteBufferToPartition(const FileContents& file_contents, const Partition& partition) { + const unsigned char* data = file_contents.data.data(); + size_t len = file_contents.data.size(); size_t start = 0; bool success = false; for (size_t attempt = 0; attempt < 2; ++attempt) { + android::base::unique_fd fd(open(partition.name.c_str(), O_RDWR)); + if (fd == -1) { + PLOG(ERROR) << "Failed to open \"" << partition << "\""; + return false; + } + if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { - printf("failed seek on %s: %s\n", partition, strerror(errno)); - return -1; + PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\""; + return false; } - 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 (!android::base::WriteFully(fd, data + start, len - start)) { + PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\""; + return false; } - if (ota_fsync(fd) != 0) { - printf("failed to sync to %s: %s\n", partition, strerror(errno)); - return -1; + if (fsync(fd) != 0) { + PLOG(ERROR) << "Failed to sync \"" << partition << "\""; + return false; } - if (ota_close(fd) != 0) { - printf("failed to close %s: %s\n", partition, strerror(errno)); - return -1; + if (close(fd.release()) != 0) { + PLOG(ERROR) << "Failed to close \"" << partition << "\""; + return false; } - fd.reset(ota_open(partition, O_RDONLY)); + fd.reset(open(partition.name.c_str(), O_RDONLY)); if (fd == -1) { - printf("failed to reopen %s for verify: %s\n", partition, strerror(errno)); - return -1; + PLOG(ERROR) << "Failed to reopen \"" << partition << "\" for verification"; + return false; } // 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)); + std::string drop_cache = "/proc/sys/vm/drop_caches"; + if (!android::base::WriteStringToFile("3\n", drop_cache)) { + PLOG(ERROR) << "Failed to write to " << drop_cache; } else { - printf(" caches dropped\n"); + LOG(INFO) << " caches dropped"; } - 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; + PLOG(ERROR) << "Failed to seek to 0 on " << partition; + return false; } unsigned char buffer[4096]; @@ -288,386 +194,264 @@ int WriteToPartition(const unsigned char* data, size_t len, const std::string& t to_read = sizeof(buffer); } - size_t so_far = 0; - while (so_far < to_read) { - ssize_t read_count = TEMP_FAILURE_RETRY(ota_read(fd, buffer + so_far, to_read - so_far)); - if (read_count == -1) { - printf("verify read error %s at %zu: %s\n", partition, p, strerror(errno)); - return -1; - } else if (read_count == 0) { - printf("verify read reached unexpected EOF, %s at %zu\n", partition, p); - return -1; - } - if (static_cast(read_count) < to_read) { - printf("short verify read %s at %zu: %zd %zu\n", partition, p, read_count, to_read); - } - so_far += read_count; + if (!android::base::ReadFully(fd, buffer, to_read)) { + PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p; + return false; } if (memcmp(buffer, data + p, to_read) != 0) { - printf("verification failed starting at %zu\n", p); + LOG(ERROR) << "Verification failed starting at " << p; start = p; break; } } if (start == len) { - printf("verification read succeeded (attempt %zu)\n", attempt + 1); + LOG(INFO) << "Verification read succeeded (attempt " << attempt + 1 << ")"; success = true; break; } - if (ota_close(fd) != 0) { - printf("failed to close %s: %s\n", partition, strerror(errno)); - return -1; - } - - fd.reset(ota_open(partition, O_RDWR)); - if (fd == -1) { - printf("failed to reopen %s for retry write && verify: %s\n", partition, strerror(errno)); - return -1; + if (close(fd.release()) != 0) { + PLOG(ERROR) << "Failed to close " << partition; + return false; } } if (!success) { - printf("failed to verify after all attempts\n"); - return -1; + LOG(ERROR) << "Failed to verify after all attempts"; + return false; } - if (ota_close(fd) == -1) { - printf("error closing %s: %s\n", partition, strerror(errno)); - return -1; - } sync(); - return 0; -} - -// Take a string 'str' of 40 hex digits and parse it into the 20 -// byte array 'digest'. 'str' may contain only the digest or be of -// 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; + return true; } -// 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(CacheLocation::location().cache_temp_source().c_str(), &file) != 0) { - printf("failed to load cache file\n"); - return 1; +int ParseSha1(const std::string& str, uint8_t* digest) { + const char* ps = str.c_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 (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) { - printf("cache bits don't match any sha1 for \"%s\"\n", filename); - return 1; + if (i % 2 == 0) { + *pd = digit << 4; + } else { + *pd |= digit; + ++pd; } } + if (*ps != '\0') 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; +bool PatchPartitionCheck(const Partition& target, const Partition& source) { + FileContents target_file; + FileContents source_file; + return (ReadPartitionToBuffer(target, &target_file, false) || + ReadPartitionToBuffer(source, &source_file, true)); } -// 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 ShowLicenses() { + ShowBSDiffLicense(); + return 0; } -int CacheSizeCheck(size_t bytes) { - if (MakeFreeSpaceOnCache(bytes) < 0) { - printf("unable to make %zu bytes available on /cache\n", bytes); - return 1; - } - return 0; -} +bool PatchPartition(const Partition& target, const Partition& source, const Value& patch, + const Value* bonus, bool backup_source) { + LOG(INFO) << "Patching " << target.name; -// 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 */, - 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; - } + // We try to load and check against the target hash first. + FileContents target_file; + if (ReadPartitionToBuffer(target, &target_file, false)) { + // The early-exit case: the patch was already applied, this file has the desired hash, nothing + // for us to do. + LOG(INFO) << " already " << target.hash.substr(0, 8); + return true; } - 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); + FileContents source_file; + if (ReadPartitionToBuffer(source, &source_file, backup_source)) { + return GenerateTarget(target, source_file, patch, bonus, backup_source); } - 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); - } - } + LOG(ERROR) << "Failed to find any match"; + return false; +} - printf("source file is bad; trying copy\n"); +bool FlashPartition(const Partition& partition, const std::string& source_filename) { + LOG(INFO) << "Flashing " << partition; - FileContents copy_file; - if (LoadFileContents(CacheLocation::location().cache_temp_source().c_str(), ©_file) < 0) { - printf("failed to read copy file\n"); - return 1; + // We try to load and check against the target hash first. + FileContents target_file; + if (ReadPartitionToBuffer(partition, &target_file, false)) { + // The early-exit case: the patch was already applied, this file has the desired hash, nothing + // for us to do. + LOG(INFO) << " already " << partition.hash.substr(0, 8); + return true; } - 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; + FileContents source_file; + if (!LoadFileContents(source_filename, &source_file)) { + LOG(ERROR) << "Failed to load source file"; + return false; } - 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; + uint8_t expected_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(partition.hash, expected_sha1) != 0) { + LOG(ERROR) << "Failed to parse source hash \"" << partition.hash << "\""; + return false; } - 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; + if (memcmp(source_file.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) { + // The source doesn't have desired checksum. + LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum"; + LOG(ERROR) << "expected: " << partition.hash.substr(0, 8) + << ", found: " << short_sha1(source_file.sha1); + return false; } - - // 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 (!WriteBufferToPartition(source_file, partition)) { + LOG(ERROR) << "Failed to write to " << partition; + return false; } + return true; +} - if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) { - printf("write of copied data to %s failed\n", target_filename); - return 1; +static bool GenerateTarget(const Partition& target, const FileContents& source_file, + const Value& patch, const Value* bonus_data, bool backup_source) { + uint8_t expected_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(target.hash, expected_sha1) != 0) { + LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\""; + return false; } - 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; + if (patch.type != Value::Type::BLOB) { + LOG(ERROR) << "patch is not a blob"; + return false; } - const char* header = &patch->data[0]; - size_t header_bytes_read = patch->data.size(); + const char* header = patch.data.data(); + 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; + LOG(ERROR) << "Unknown patch file format"; + return false; } - 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; + // We write the original source to cache, in case the partition write is interrupted. + if (backup_source && !CheckAndFreeSpaceOnCache(source_file.data.size())) { + LOG(ERROR) << "Not enough free space on /cache"; + return false; } - if (SaveFileContents(CacheLocation::location().cache_temp_source().c_str(), &source_file) < 0) { - printf("failed to back up source file\n"); - return 1; + if (backup_source && !SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) { + LOG(ERROR) << "Failed to back up source file"; + return false; } // 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; - }; - + FileContents patched; SHA_CTX ctx; SHA1_Init(&ctx); + SinkFn sink = [&patched, &ctx](const unsigned char* data, size_t len) { + SHA1_Update(&ctx, data, len); + patched.data.insert(patched.data.end(), data, data + len); + return len; + }; int result; if (use_bsdiff) { - result = - ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), *patch, 0, sink, &ctx); + result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch, 0, sink); } else { - result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), *patch, sink, &ctx, - bonus_data); + result = + ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch, sink, bonus_data); } if (result != 0) { - printf("applying patch failed\n"); - return 1; + LOG(ERROR) << "Failed to apply the patch: " << result; + return false; } - 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()); + SHA1_Final(patched.sha1, &ctx); + if (memcmp(patched.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) { + LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(expected_sha1); + + LOG(ERROR) << "target size " << patched.data.size() << " SHA-1 " << short_sha1(patched.sha1); + LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 " + << short_sha1(source_file.sha1); + + uint8_t patch_digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast(patch.data.data()), patch.data.size(), patch_digest); + LOG(ERROR) << "patch size " << patch.data.size() << " SHA-1 " << short_sha1(patch_digest); + + if (bonus_data != nullptr) { + uint8_t bonus_digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast(bonus_data->data.data()), bonus_data->data.size(), + bonus_digest); + LOG(ERROR) << "bonus size " << bonus_data->data.size() << " SHA-1 " + << short_sha1(bonus_digest); + } + + return false; } + LOG(INFO) << " now " << short_sha1(expected_sha1); + // 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; + if (!WriteBufferToPartition(patched, target)) { + LOG(ERROR) << "Failed to write patched data to " << target.name; + return false; } // Delete the backup copy of the source. - unlink(CacheLocation::location().cache_temp_source().c_str()); + if (backup_source) { + unlink(Paths::Get().cache_temp_source().c_str()); + } // Success! - return 0; + return true; +} + +bool CheckPartition(const Partition& partition) { + FileContents target_file; + return ReadPartitionToBuffer(partition, &target_file, false); +} + +Partition Partition::Parse(const std::string& input_str, std::string* err) { + std::vector pieces = android::base::Split(input_str, ":"); + if (pieces.size() != 4 || pieces[0] != "EMMC") { + *err = "Invalid number of tokens or non-eMMC target"; + return {}; + } + + size_t size; + if (!android::base::ParseUint(pieces[2], &size) || size == 0) { + *err = "Failed to parse \"" + pieces[2] + "\" as byte count"; + return {}; + } + + return Partition(pieces[1], size, pieces[3]); +} + +std::string Partition::ToString() const { + if (*this) { + return "EMMC:"s + name + ":" + std::to_string(size) + ":" + hash; + } + return ""; +} + +std::ostream& operator<<(std::ostream& os, const Partition& partition) { + os << partition.ToString(); + return os; } diff --git a/applypatch/applypatch_main.cpp b/applypatch/applypatch_main.cpp index 197077c938..92d2b3fa9a 100644 --- a/applypatch/applypatch_main.cpp +++ b/applypatch/applypatch_main.cpp @@ -16,13 +16,10 @@ #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. +#include +// See the comments for applypatch() function. int main(int argc, char** argv) { - return applypatch_modes(argc, const_cast(argv)); + android::base::InitLogging(argv); + return applypatch_modes(argc, argv); } diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp index aa32d57ef4..bb5eeae9d5 100644 --- a/applypatch/applypatch_modes.cpp +++ b/applypatch/applypatch_modes.cpp @@ -16,6 +16,7 @@ #include "applypatch_modes.h" +#include #include #include #include @@ -25,6 +26,8 @@ #include #include +#include +#include #include #include #include @@ -32,157 +35,153 @@ #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]); - } +static int CheckMode(const std::string& target_emmc) { + std::string err; + auto target = Partition::Parse(target_emmc, &err); + if (!target) { + LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err; + return 2; + } + return CheckPartition(target) ? 0 : 1; +} - return applypatch_check(argv[2], sha1); +static int FlashMode(const std::string& target_emmc, const std::string& source_file) { + std::string err; + auto target = Partition::Parse(target_emmc, &err); + if (!target) { + LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err; + return 2; + } + return FlashPartition(target, source_file) ? 0 : 1; } -// 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; +static int PatchMode(const std::string& target_emmc, const std::string& source_emmc, + const std::string& patch_file, const std::string& bonus_file) { + std::string err; + auto target = Partition::Parse(target_emmc, &err); + if (!target) { + LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err; + return 2; + } + + auto source = Partition::Parse(source_emmc, &err); + if (!source) { + LOG(ERROR) << "Failed to parse source \"" << source_emmc << "\": " << err; + return 2; + } + + std::string patch_contents; + if (!android::base::ReadFileToString(patch_file, &patch_contents)) { + PLOG(ERROR) << "Failed to read patch file \"" << patch_file << "\""; + return 1; + } + + Value patch(Value::Type::BLOB, std::move(patch_contents)); + std::unique_ptr bonus; + if (!bonus_file.empty()) { + std::string bonus_contents; + if (!android::base::ReadFileToString(bonus_file, &bonus_contents)) { + PLOG(ERROR) << "Failed to read bonus file \"" << bonus_file << "\""; + return 1; } - 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; - } + bonus = std::make_unique(Value::Type::BLOB, std::move(bonus_contents)); + } - 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; + return PatchPartition(target, source, patch, bonus.get(), false) ? 0 : 1; } -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 void Usage() { + printf( + "Usage: \n" + "check mode\n" + " applypatch --check EMMC:::\n\n" + "flash mode\n" + " applypatch --flash \n" + " --target EMMC:::\n\n" + "patch mode\n" + " applypatch [--bonus ]\n" + " --patch \n" + " --target EMMC:::\n" + " --source EMMC:::\n\n" + "show license\n" + " applypatch --license\n" + "\n\n"); } -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; +int applypatch_modes(int argc, char* argv[]) { + static constexpr struct option OPTIONS[]{ + // clang-format off + { "bonus", required_argument, nullptr, 0 }, + { "check", required_argument, nullptr, 0 }, + { "flash", required_argument, nullptr, 0 }, + { "license", no_argument, nullptr, 0 }, + { "patch", required_argument, nullptr, 0 }, + { "source", required_argument, nullptr, 0 }, + { "target", required_argument, nullptr, 0 }, + { nullptr, 0, nullptr, 0 }, + // clang-format on + }; + + std::string check_target; + std::string source; + std::string target; + std::string patch; + std::string bonus; + + bool check_mode = false; + bool flash_mode = false; + bool patch_mode = false; + + optind = 1; + + int arg; + int option_index; + while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { + switch (arg) { + case 0: { + std::string option = OPTIONS[option_index].name; + if (option == "bonus") { + bonus = optarg; + } else if (option == "check") { + check_target = optarg; + check_mode = true; + } else if (option == "flash") { + source = optarg; + flash_mode = true; + } else if (option == "license") { + return ShowLicenses(); + } else if (option == "patch") { + patch = optarg; + patch_mode = true; + } else if (option == "source") { + source = optarg; + } else if (option == "target") { + target = optarg; } - bonus.type = VAL_BLOB; - bonus.data = std::string(bonusFc.data.cbegin(), bonusFc.data.cend()); - argc -= 2; - argv += 2; - } - - if (argc < 4) { + break; + } + case '?': + default: + LOG(ERROR) << "Invalid argument"; + Usage(); 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; + } + + if (check_mode) { + return CheckMode(check_target); + } + if (flash_mode) { + if (!bonus.empty()) { + LOG(ERROR) << "bonus file not supported in flash mode"; + return 1; } - return result; + return FlashMode(target, source); + } + if (patch_mode) { + return PatchMode(target, source, patch, bonus); + } + + Usage(); + return 2; } diff --git a/applypatch/applypatch_modes.h b/applypatch/applypatch_modes.h index 3d9d08df57..aa60a431f8 100644 --- a/applypatch/applypatch_modes.h +++ b/applypatch/applypatch_modes.h @@ -17,6 +17,6 @@ #ifndef _APPLYPATCH_MODES_H #define _APPLYPATCH_MODES_H -int applypatch_modes(int argc, const char** argv); +int applypatch_modes(int argc, char* argv[]); #endif // _APPLYPATCH_MODES_H diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp index 912dbbdd8e..ba33c3a9c6 100644 --- a/applypatch/bspatch.cpp +++ b/applypatch/bspatch.cpp @@ -66,18 +66,12 @@ void ShowBSDiffLicense() { } 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; - }; - + size_t patch_offset, SinkFn sink) { 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); + patch.data.size() - patch_offset, sink); if (result != 0) { LOG(ERROR) << "bspatch failed, result: " << result; // print SHA1 of the patch in the case of a data error. diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp index ea364d8e63..3868ef2306 100644 --- a/applypatch/freecache.cpp +++ b/applypatch/freecache.cpp @@ -14,31 +14,35 @@ * limitations under the License. */ +#include #include -#include +#include #include #include #include #include #include #include -#include -#include +#include +#include #include #include #include +#include +#include #include #include +#include #include "applypatch/applypatch.h" -#include "otautil/cache_location.h" +#include "otautil/paths.h" -static int EliminateOpenFiles(std::set* files) { +static int EliminateOpenFiles(const std::string& dirname, std::set* files) { std::unique_ptr d(opendir("/proc"), closedir); if (!d) { - printf("error opening /proc: %s\n", strerror(errno)); + PLOG(ERROR) << "Failed to open /proc"; return -1; } struct dirent* de; @@ -52,7 +56,7 @@ static int EliminateOpenFiles(std::set* files) { 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)); + PLOG(ERROR) << "Failed to open " << path; continue; } while ((fdde = readdir(fdd.get())) != 0) { @@ -62,9 +66,9 @@ static int EliminateOpenFiles(std::set* files) { int count = readlink(fd_path.c_str(), link, sizeof(link)-1); if (count >= 0) { link[count] = '\0'; - if (strncmp(link, "/cache/", 7) == 0) { + if (android::base::StartsWith(link, dirname)) { if (files->erase(link) > 0) { - printf("%s is open by %s\n", link, de->d_name); + LOG(INFO) << link << " is open by " << de->d_name; } } } @@ -73,77 +77,171 @@ static int EliminateOpenFiles(std::set* files) { return 0; } -static std::set FindExpendableFiles() { +static std::vector FindExpendableFiles( + const std::string& dirname, const std::function& name_filter) { + std::unique_ptr d(opendir(dirname.c_str()), closedir); + if (!d) { + PLOG(ERROR) << "Failed to open " << dirname; + return {}; + } + + // Look for regular files in the directory (not in any subdirectories). 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)); + struct dirent* de; + while ((de = readdir(d.get())) != 0) { + std::string path = dirname + "/" + 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 == Paths::Get().cache_temp_source()) { 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 == CacheLocation::location().cache_temp_source()) { - continue; - } + // Do not delete the file if it doesn't have the expected format. + if (name_filter != nullptr && !name_filter(de->d_name)) { + continue; + } - struct stat st; - if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { - files.insert(path); - } + 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(); + LOG(INFO) << files.size() << " regular files in deletable directory"; + if (EliminateOpenFiles(dirname, &files) < 0) { + return {}; } - return files; + + return std::vector(files.begin(), files.end()); } -int MakeFreeSpaceOnCache(size_t bytes_needed) { +// Parses the index of given log file, e.g. 3 for last_log.3; returns max number if the log name +// doesn't have the expected format so that we'll delete these ones first. +static unsigned int GetLogIndex(const std::string& log_name) { + if (log_name == "last_log" || log_name == "last_kmsg") { + return 0; + } + + unsigned int index; + if (sscanf(log_name.c_str(), "last_log.%u", &index) == 1 || + sscanf(log_name.c_str(), "last_kmsg.%u", &index) == 1) { + return index; + } + + return std::numeric_limits::max(); +} + +// Returns the amount of free space (in bytes) on the filesystem containing filename, or -1 on +// error. +static int64_t FreeSpaceForFile(const std::string& filename) { + struct statfs sf; + if (statfs(filename.c_str(), &sf) == -1) { + PLOG(ERROR) << "Failed to statfs " << filename; + return -1; + } + + auto f_bsize = static_cast(sf.f_bsize); + auto free_space = sf.f_bsize * sf.f_bavail; + if (f_bsize == 0 || free_space / f_bsize != static_cast(sf.f_bavail)) { + LOG(ERROR) << "Invalid block size or overflow (sf.f_bsize " << sf.f_bsize << ", sf.f_bavail " + << sf.f_bavail << ")"; + return -1; + } + return free_space; +} + +bool CheckAndFreeSpaceOnCache(size_t bytes) { #ifndef __ANDROID__ - // TODO (xunchang) implement a heuristic cache size check during host simulation. - printf("Skip making (%zu) bytes free space on cache; program is running on host\n", bytes_needed); - return 0; + // TODO(xunchang): Implement a heuristic cache size check during host simulation. + LOG(WARNING) << "Skipped making (" << bytes + << ") bytes free space on /cache; program is running on host"; + return true; #endif - size_t free_now = FreeSpaceForFile("/cache"); - printf("%zu bytes free on /cache (%zu needed)\n", free_now, bytes_needed); + std::vector dirs{ "/cache", Paths::Get().cache_log_directory() }; + for (const auto& dirname : dirs) { + if (RemoveFilesInDirectory(bytes, dirname, FreeSpaceForFile)) { + return true; + } + } - if (free_now >= bytes_needed) { - return 0; + return false; +} + +bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname, + const std::function& space_checker) { + // The requested size cannot exceed max int64_t. + if (static_cast(bytes_needed) > + static_cast(std::numeric_limits::max())) { + LOG(ERROR) << "Invalid arg of bytes_needed: " << bytes_needed; + return false; } - 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; + + struct stat st; + if (stat(dirname.c_str(), &st) == -1) { + PLOG(ERROR) << "Failed to stat " << dirname; + return false; + } + if (!S_ISDIR(st.st_mode)) { + LOG(ERROR) << dirname << " is not a directory"; + return false; } - // 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. + int64_t free_now = space_checker(dirname); + if (free_now == -1) { + LOG(ERROR) << "Failed to check free space for " << dirname; + return false; + } + LOG(INFO) << free_now << " bytes free on " << dirname << " (" << bytes_needed << " needed)"; + + if (free_now >= static_cast(bytes_needed)) { + return true; + } + + std::vector files; + if (dirname == Paths::Get().cache_log_directory()) { + // Deletes the log files only. + auto log_filter = [](const std::string& file_name) { + return android::base::StartsWith(file_name, "last_log") || + android::base::StartsWith(file_name, "last_kmsg"); + }; + + files = FindExpendableFiles(dirname, log_filter); + + // Older logs will come to the top of the queue. + auto comparator = [](const std::string& name1, const std::string& name2) -> bool { + unsigned int index1 = GetLogIndex(android::base::Basename(name1)); + unsigned int index2 = GetLogIndex(android::base::Basename(name2)); + if (index1 == index2) { + return name1 < name2; + } + + return index1 > index2; + }; + + std::sort(files.begin(), files.end(), comparator); + } else { + // We're allowed to delete unopened regular files in the directory. + files = FindExpendableFiles(dirname, nullptr); + } 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; + if (unlink(file.c_str()) == -1) { + PLOG(ERROR) << "Failed to delete " << file; + continue; + } + + free_now = space_checker(dirname); + if (free_now == -1) { + LOG(ERROR) << "Failed to check free space for " << dirname; + return false; + } + LOG(INFO) << "Deleted " << file << "; now " << free_now << " bytes free"; + if (free_now >= static_cast(bytes_needed)) { + return true; } } - return (free_now >= bytes_needed) ? 0 : -1; + + return false; } diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp index 674cc2b16b..6ad4a6105d 100644 --- a/applypatch/imgdiff.cpp +++ b/applypatch/imgdiff.cpp @@ -462,12 +462,12 @@ PatchChunk::PatchChunk(const ImageChunk& tgt) target_len_(tgt.GetRawDataLength()), target_uncompressed_len_(tgt.DataLengthForPatch()), target_compress_level_(tgt.GetCompressLevel()), - data_(tgt.DataForPatch(), tgt.DataForPatch() + tgt.DataLengthForPatch()) {} + data_(tgt.GetRawData(), tgt.GetRawData() + tgt.GetRawDataLength()) {} // Return true if raw data is smaller than the patch size. bool PatchChunk::RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size) { size_t target_len = tgt.GetRawDataLength(); - return (tgt.GetType() == CHUNK_NORMAL && (target_len <= 160 || target_len < patch_size)); + return target_len < patch_size || (tgt.GetType() == CHUNK_NORMAL && target_len <= 160); } void PatchChunk::UpdateSourceOffset(const SortedRangeSet& src_range) { @@ -675,7 +675,7 @@ bool ZipModeImage::Initialize(const std::string& filename) { // Iterate the zip entries and compose the image chunks accordingly. bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandle handle) { void* cookie; - int ret = StartIteration(handle, &cookie, nullptr, nullptr); + int ret = StartIteration(handle, &cookie); if (ret != 0) { LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret); return false; @@ -683,12 +683,11 @@ bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandl // Create a list of deflated zip entries, sorted by offset. std::vector> temp_entries; - ZipString name; + std::string name; ZipEntry entry; while ((ret = Next(cookie, &entry, &name)) == 0) { if (entry.method == kCompressDeflated || limit_ > 0) { - std::string entry_name(name.name, name.name + name.name_length); - temp_entries.emplace_back(entry_name, entry); + temp_entries.emplace_back(name, entry); } } diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp index 3682d61157..f4c33e5a31 100644 --- a/applypatch/imgpatch.cpp +++ b/applypatch/imgpatch.cpp @@ -38,6 +38,7 @@ #include #include "edify/expr.h" +#include "otautil/print_sha1.h" static inline int64_t Read8(const void *address) { return android::base::get_unaligned(address); @@ -51,8 +52,9 @@ static inline int32_t Read4(const void *address) { // 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) { + const char* deflate_header, SinkFn sink) { size_t expected_target_length = static_cast(Read8(deflate_header + 32)); + CHECK_GT(expected_target_length, static_cast(0)); int level = Read4(deflate_header + 40); int method = Read4(deflate_header + 44); int window_bits = Read4(deflate_header + 48); @@ -77,7 +79,7 @@ static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_ size_t total_written = 0; static constexpr size_t buffer_size = 32768; auto compression_sink = [&strm, &actual_target_length, &expected_target_length, &total_written, - &ret, &ctx, &sink](const uint8_t* data, size_t len) -> size_t { + &ret, &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; @@ -102,15 +104,13 @@ static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_ 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; }; - int bspatch_result = - ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr); + int bspatch_result = ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink); deflateEnd(&strm); if (bspatch_result != 0) { @@ -127,19 +127,20 @@ static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_ << actual_target_length; return false; } - LOG(DEBUG) << "bspatch writes " << total_written << " bytes in total to streaming output."; + LOG(DEBUG) << "bspatch wrote " << 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); + Value patch(Value::Type::BLOB, + std::string(reinterpret_cast(patch_data), patch_size)); + return ApplyImagePatch(old_data, old_size, patch, sink, nullptr); } int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink, - SHA_CTX* ctx, const Value* bonus_data) { + const Value* bonus_data) { if (patch.data.size() < 12) { printf("patch too short to contain header\n"); return -1; @@ -180,10 +181,12 @@ int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& printf("source data too short\n"); return -1; } - if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) { + if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink) != 0) { printf("Failed to apply bsdiff patch.\n"); return -1; } + + LOG(DEBUG) << "Processed chunk type normal"; } else if (type == CHUNK_RAW) { const char* raw_header = patch_header + pos; pos += 4; @@ -198,14 +201,13 @@ int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& printf("failed to read chunk %d raw data\n", i); return -1; } - if (ctx) { - SHA1_Update(ctx, patch_header + pos, data_len); - } if (sink(reinterpret_cast(patch_header + pos), data_len) != data_len) { printf("failed to write chunk %d raw data\n", i); return -1; } pos += data_len; + + LOG(DEBUG) << "Processed chunk type raw"; } else if (type == CHUNK_DEFLATE) { // deflate chunks have an additional 60 bytes in their chunk header. const char* deflate_header = patch_header + pos; @@ -228,11 +230,10 @@ int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& // 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; + // 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 != nullptr) ? bonus_data->data.size() : 0; std::vector expanded_source(expanded_len); @@ -270,17 +271,18 @@ int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& inflateEnd(&strm); if (bonus_size) { - memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0], + memcpy(expanded_source.data() + (expanded_len - bonus_size), bonus_data->data.data(), bonus_size); } } if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch, - patch_offset, deflate_header, sink, ctx)) { + patch_offset, deflate_header, sink)) { LOG(ERROR) << "Fail to apply streaming bspatch."; return -1; } + LOG(DEBUG) << "Processed chunk type deflate"; } else { printf("patch chunk %d is unknown type %d\n", i, type); return -1; diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h index 912ead1fa0..799f4b2d79 100644 --- a/applypatch/include/applypatch/applypatch.h +++ b/applypatch/include/applypatch/applypatch.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -39,45 +40,92 @@ using SinkFn = std::function; // applypatch.cpp int ShowLicenses(); -size_t FreeSpaceForFile(const char* filename); -int CacheSizeCheck(size_t bytes); -int ParseSha1(const char* str, uint8_t* digest); - -int applypatch(const char* source_filename, - const char* target_filename, - const char* target_sha1_str, - size_t target_size, - const std::vector& patch_sha1_str, - const std::vector>& patch_data, - const Value* bonus_data); -int applypatch_check(const char* filename, - 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); + +// Parses a given string of 40 hex digits into 20-byte array 'digest'. 'str' may contain only the +// digest or be of the form ":". Returns 0 on success, or -1 on any error. +int ParseSha1(const std::string& str, uint8_t* digest); + +struct Partition { + Partition() = default; + + Partition(const std::string& name, size_t size, const std::string& hash) + : name(name), size(size), hash(hash) {} + + // Parses and returns the given string into a Partition object. The input string is of the form + // "EMMC:::". Returns the parsed Partition, or an empty object on error. + static Partition Parse(const std::string& partition, std::string* err); + + std::string ToString() const; + + // Returns whether the current Partition object is valid. + explicit operator bool() const { + return !name.empty(); + } + + std::string name; + size_t size; + std::string hash; +}; + +std::ostream& operator<<(std::ostream& os, const Partition& partition); + +// Applies the given 'patch' to the 'source' Partition, verifies then writes the patching result to +// the 'target' Partition. While patching, it will backup the data on the source partition to +// /cache, so that the patching could be resumed on interruption even if both of the source and +// target partitions refer to the same device. The function is idempotent if called multiple times. +// 'bonus' can be provided if the patch was generated with a bonus output, or nullptr. +// 'backup_source' indicates whether the source partition should be backed up prior to the update +// (e.g. when doing in-place update). Returns the patching result. +bool PatchPartition(const Partition& target, const Partition& source, const Value& patch, + const Value* bonus, bool backup_source); + +// Returns whether the contents of the eMMC target or the cached file match the embedded hash. +// It will look for the backup on /cache if the given partition doesn't match the checksum. +bool PatchPartitionCheck(const Partition& target, const Partition& source); + +// Checks whether the contents of the given partition has the desired hash. It will NOT look for +// the backup on /cache if the given partition doesn't have the expected checksum. +bool CheckPartition(const Partition& target); + +// Flashes a given image in 'source_filename' to the eMMC target partition. It verifies the target +// checksum first, and will return if target already has the desired hash. Otherwise it checks the +// checksum of the given source image, flashes, and verifies the target partition afterwards. The +// function is idempotent. Returns the flashing result. +bool FlashPartition(const Partition& target, const std::string& source_filename); + +// Reads a file into memory; stores the file contents and associated metadata in *file. +bool LoadFileContents(const std::string& filename, FileContents* file); + +// Saves the given FileContents object to the given filename. +bool SaveFileContents(const std::string& filename, const FileContents* file); // bspatch.cpp void ShowBSDiffLicense(); // Applies the bsdiff-patch given in 'patch' (from offset 'patch_offset' to the end) to the source -// data given by (old_data, old_size). Writes the patched output through the given 'sink', and -// updates the SHA-1 context with the output data. Returns 0 on success. +// data given by (old_data, old_size). Writes the patched output through the given 'sink'. Returns +// 0 on success. int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch, - size_t patch_offset, SinkFn sink, SHA_CTX* ctx); + size_t patch_offset, SinkFn sink); // imgpatch.cpp // Applies the imgdiff-patch given in 'patch' to the source data given by (old_data, old_size), with -// the optional bonus data. Writes the patched output through the given 'sink', and updates the -// SHA-1 context with the output data. Returns 0 on success. +// the optional bonus data. Writes the patched output through the given 'sink'. Returns 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); + const Value* bonus_data); // freecache.cpp -int MakeFreeSpaceOnCache(size_t bytes_needed); +// Checks whether /cache partition has at least 'bytes'-byte free space. Returns true immediately +// if so. Otherwise, it will try to free some space by removing older logs, checks again and +// returns the checking result. +bool CheckAndFreeSpaceOnCache(size_t bytes); +// Removes the files in |dirname| until we have at least |bytes_needed| bytes of free space on the +// partition. |space_checker| should return the size of the free space, or -1 on error. +bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname, + const std::function& space_checker); #endif diff --git a/applypatch/include/applypatch/imgdiff_image.h b/applypatch/include/applypatch/imgdiff_image.h index 0848072374..671605160a 100644 --- a/applypatch/include/applypatch/imgdiff_image.h +++ b/applypatch/include/applypatch/imgdiff_image.h @@ -44,6 +44,8 @@ class ImageChunk { int GetType() const { return type_; } + + const uint8_t* GetRawData() const; size_t GetRawDataLength() const { return raw_data_len_; } @@ -99,7 +101,6 @@ class ImageChunk { bsdiff::SuffixArrayIndexInterface** bsdiff_cache); private: - const uint8_t* GetRawData() const; bool TryReconstruction(int level); int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW diff --git a/applypatch/vendor_flash_recovery.rc b/applypatch/vendor_flash_recovery.rc new file mode 100644 index 0000000000..37a7c2be71 --- /dev/null +++ b/applypatch/vendor_flash_recovery.rc @@ -0,0 +1,3 @@ +service vendor_flash_recovery /vendor/bin/install-recovery.sh + class main + oneshot diff --git a/boot_control/Android.mk b/boot_control/Android.mk deleted file mode 100644 index 9814d71222..0000000000 --- a/boot_control/Android.mk +++ /dev/null @@ -1,33 +0,0 @@ -# -# 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 -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 deleted file mode 100644 index ec97b6ced3..0000000000 --- a/boot_control/boot_control.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -struct boot_control_private_t { - // The base struct needs to be first in the list. - boot_control_module_t base; - - // Whether this struct was initialized with data from the bootloader message - // that doesn't change until next reboot. - bool initialized; - - // The path to the misc_device as reported in the fstab. - const char* misc_device; - - // The number of slots present on the device. - unsigned int num_slots; - - // The slot where we are running from. - unsigned int current_slot; -}; - -namespace { - -// The number of boot attempts that should be made from a new slot before -// rolling back to the previous slot. -constexpr unsigned int kDefaultBootAttempts = 7; -static_assert(kDefaultBootAttempts < 8, "tries_remaining field only has 3 bits"); - -constexpr unsigned int kMaxNumSlots = - sizeof(bootloader_control::slot_info) / sizeof(bootloader_control::slot_info[0]); -constexpr const char* kSlotSuffixes[kMaxNumSlots] = { "_a", "_b", "_c", "_d" }; -constexpr off_t kBootloaderControlOffset = offsetof(bootloader_message_ab, slot_suffix); - -static uint32_t CRC32(const uint8_t* buf, size_t size) { - static uint32_t crc_table[256]; - - // Compute the CRC-32 table only once. - if (!crc_table[1]) { - for (uint32_t i = 0; i < 256; ++i) { - uint32_t crc = i; - for (uint32_t j = 0; j < 8; ++j) { - uint32_t mask = -(crc & 1); - crc = (crc >> 1) ^ (0xEDB88320 & mask); - } - crc_table[i] = crc; - } - } - - uint32_t ret = -1; - for (size_t i = 0; i < size; ++i) { - ret = (ret >> 8) ^ crc_table[(ret ^ buf[i]) & 0xFF]; - } - - return ~ret; -} - -// Return the little-endian representation of the CRC-32 of the first fields -// in |boot_ctrl| up to the crc32_le field. -uint32_t BootloaderControlLECRC(const bootloader_control* boot_ctrl) { - return htole32( - CRC32(reinterpret_cast(boot_ctrl), offsetof(bootloader_control, crc32_le))); -} - -bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer) { - android::base::unique_fd fd(open(misc_device, O_RDONLY)); - if (fd.get() == -1) { - PLOG(ERROR) << "failed to open " << misc_device; - return false; - } - if (lseek(fd, kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) { - PLOG(ERROR) << "failed to lseek " << misc_device; - return false; - } - if (!android::base::ReadFully(fd.get(), buffer, sizeof(bootloader_control))) { - PLOG(ERROR) << "failed to read " << misc_device; - return false; - } - return true; -} - -bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control* buffer) { - buffer->crc32_le = BootloaderControlLECRC(buffer); - android::base::unique_fd fd(open(misc_device, O_WRONLY | O_SYNC)); - if (fd.get() == -1) { - PLOG(ERROR) << "failed to open " << misc_device; - return false; - } - if (lseek(fd.get(), kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) { - PLOG(ERROR) << "failed to lseek " << misc_device; - return false; - } - if (!android::base::WriteFully(fd.get(), buffer, sizeof(bootloader_control))) { - PLOG(ERROR) << "failed to write " << misc_device; - return false; - } - return true; -} - -void InitDefaultBootloaderControl(const boot_control_private_t* module, - bootloader_control* boot_ctrl) { - memset(boot_ctrl, 0, sizeof(*boot_ctrl)); - - if (module->current_slot < kMaxNumSlots) { - strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[module->current_slot], - sizeof(boot_ctrl->slot_suffix)); - } - boot_ctrl->magic = BOOT_CTRL_MAGIC; - boot_ctrl->version = BOOT_CTRL_VERSION; - - // Figure out the number of slots by checking if the partitions exist, - // otherwise assume the maximum supported by the header. - boot_ctrl->nb_slot = kMaxNumSlots; - std::string base_path = module->misc_device; - size_t last_path_sep = base_path.rfind('/'); - if (last_path_sep != std::string::npos) { - // We test the existence of the "boot" partition on each possible slot, - // which is a partition required by Android Bootloader Requirements. - base_path = base_path.substr(0, last_path_sep + 1) + "boot"; - int last_existing_slot = -1; - int first_missing_slot = -1; - for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { - std::string partition_path = base_path + kSlotSuffixes[slot]; - struct stat part_stat; - int err = stat(partition_path.c_str(), &part_stat); - if (!err) { - last_existing_slot = slot; - LOG(INFO) << "Found slot: " << kSlotSuffixes[slot]; - } else if (err < 0 && errno == ENOENT && first_missing_slot == -1) { - first_missing_slot = slot; - } - } - // We only declare that we found the actual number of slots if we found all - // the boot partitions up to the number of slots, and no boot partition - // after that. Not finding any of the boot partitions implies a problem so - // we just leave the number of slots in the maximum value. - if ((last_existing_slot != -1 && last_existing_slot + 1 == first_missing_slot) || - (first_missing_slot == -1 && last_existing_slot + 1 == kMaxNumSlots)) { - boot_ctrl->nb_slot = last_existing_slot + 1; - LOG(INFO) << "Found a system with " << last_existing_slot + 1 << " slots."; - } - } - - for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { - slot_metadata entry = {}; - - if (slot < boot_ctrl->nb_slot) { - entry.priority = 7; - entry.tries_remaining = kDefaultBootAttempts; - entry.successful_boot = 0; - } else { - entry.priority = 0; // Unbootable - } - - // When the boot_control stored on disk is invalid, we assume that the - // current slot is successful. The bootloader should repair this situation - // before booting and write a valid boot_control slot, so if we reach this - // stage it means that the misc partition was corrupted since boot. - if (module->current_slot == slot) { - entry.successful_boot = 1; - } - - boot_ctrl->slot_info[slot] = entry; - } - boot_ctrl->recovery_tries_remaining = 0; - - boot_ctrl->crc32_le = BootloaderControlLECRC(boot_ctrl); -} - -// Return the index of the slot suffix passed or -1 if not a valid slot suffix. -int SlotSuffixToIndex(const char* suffix) { - for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { - if (!strcmp(kSlotSuffixes[slot], suffix)) return slot; - } - return -1; -} - -// Initialize the boot_control_private struct with the information from -// the bootloader_message buffer stored in |boot_ctrl|. Returns whether the -// initialization succeeded. -bool BootControl_lazyInitialization(boot_control_private_t* module) { - if (module->initialized) return true; - - // Initialize the current_slot from the read-only property. If the property - // was not set (from either the command line or the device tree), we can later - // initialize it from the bootloader_control struct. - std::string suffix_prop = android::base::GetProperty("ro.boot.slot_suffix", ""); - module->current_slot = SlotSuffixToIndex(suffix_prop.c_str()); - - std::string err; - std::string device = get_bootloader_message_blk_device(&err); - if (device.empty()) return false; - - bootloader_control boot_ctrl; - if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) return false; - - // Note that since there isn't a module unload function this memory is leaked. - module->misc_device = strdup(device.c_str()); - module->initialized = true; - - // Validate the loaded data, otherwise we will destroy it and re-initialize it - // with the current information. - uint32_t computed_crc32 = BootloaderControlLECRC(&boot_ctrl); - if (boot_ctrl.crc32_le != computed_crc32) { - LOG(WARNING) << "Invalid boot control found, expected CRC-32 0x" << std::hex << computed_crc32 - << " but found 0x" << std::hex << boot_ctrl.crc32_le << ". Re-initializing."; - InitDefaultBootloaderControl(module, &boot_ctrl); - UpdateAndSaveBootloaderControl(device.c_str(), &boot_ctrl); - } - - module->num_slots = boot_ctrl.nb_slot; - return true; -} - -void BootControl_init(boot_control_module_t* module) { - BootControl_lazyInitialization(reinterpret_cast(module)); -} - -unsigned int BootControl_getNumberSlots(boot_control_module_t* module) { - return reinterpret_cast(module)->num_slots; -} - -unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) { - return reinterpret_cast(module)->current_slot; -} - -int BootControl_markBootSuccessful(boot_control_module_t* module) { - boot_control_private_t* const bootctrl_module = reinterpret_cast(module); - - bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - - bootctrl.slot_info[bootctrl_module->current_slot].successful_boot = 1; - // tries_remaining == 0 means that the slot is not bootable anymore, make - // sure we mark the current slot as bootable if it succeeds in the last - // attempt. - bootctrl.slot_info[bootctrl_module->current_slot].tries_remaining = 1; - if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - return 0; -} - -int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) { - boot_control_private_t* const bootctrl_module = reinterpret_cast(module); - - if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { - // Invalid slot number. - return -1; - } - - bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - - // Set every other slot with a lower priority than the new "active" slot. - const unsigned int kActivePriority = 15; - const unsigned int kActiveTries = 6; - for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) { - if (i != slot) { - if (bootctrl.slot_info[i].priority >= kActivePriority) - bootctrl.slot_info[i].priority = kActivePriority - 1; - } - } - - // Note that setting a slot as active doesn't change the successful bit. - // The successful bit will only be changed by setSlotAsUnbootable(). - bootctrl.slot_info[slot].priority = kActivePriority; - bootctrl.slot_info[slot].tries_remaining = kActiveTries; - - // Setting the current slot as active is a way to revert the operation that - // set *another* slot as active at the end of an updater. This is commonly - // used to cancel the pending update. We should only reset the verity_corrpted - // bit when attempting a new slot, otherwise the verity bit on the current - // slot would be flip. - if (slot != bootctrl_module->current_slot) bootctrl.slot_info[slot].verity_corrupted = 0; - - if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - return 0; -} - -int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) { - boot_control_private_t* const bootctrl_module = reinterpret_cast(module); - - if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { - // Invalid slot number. - return -1; - } - - bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - - // The only way to mark a slot as unbootable, regardless of the priority is to - // set the tries_remaining to 0. - bootctrl.slot_info[slot].successful_boot = 0; - bootctrl.slot_info[slot].tries_remaining = 0; - if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - return 0; -} - -int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) { - boot_control_private_t* const bootctrl_module = reinterpret_cast(module); - - if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { - // Invalid slot number. - return -1; - } - - bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - - return bootctrl.slot_info[slot].tries_remaining; -} - -int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) { - boot_control_private_t* const bootctrl_module = reinterpret_cast(module); - - if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { - // Invalid slot number. - return -1; - } - - bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - - return bootctrl.slot_info[slot].successful_boot && bootctrl.slot_info[slot].tries_remaining; -} - -const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) { - if (slot >= kMaxNumSlots || slot >= reinterpret_cast(module)->num_slots) { - return NULL; - } - return kSlotSuffixes[slot]; -} - -static int BootControl_open(const hw_module_t* module __unused, const char* id __unused, - hw_device_t** device __unused) { - /* Nothing to do currently. */ - return 0; -} - -struct hw_module_methods_t BootControl_methods = { - .open = BootControl_open, -}; - -} // namespace - -boot_control_private_t HAL_MODULE_INFO_SYM = { - .base = - { - .common = - { - .tag = HARDWARE_MODULE_TAG, - .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1, - .hal_api_version = HARDWARE_HAL_API_VERSION, - .id = BOOT_CONTROL_HARDWARE_MODULE_ID, - .name = "AOSP reference bootctrl HAL", - .author = "The Android Open Source Project", - .methods = &BootControl_methods, - }, - .init = BootControl_init, - .getNumberSlots = BootControl_getNumberSlots, - .getCurrentSlot = BootControl_getCurrentSlot, - .markBootSuccessful = BootControl_markBootSuccessful, - .setActiveBootSlot = BootControl_setActiveBootSlot, - .setSlotAsUnbootable = BootControl_setSlotAsUnbootable, - .isSlotBootable = BootControl_isSlotBootable, - .getSuffix = BootControl_getSuffix, - .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful, - }, - .initialized = false, - .misc_device = nullptr, - .num_slots = 0, - .current_slot = 0, -}; diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp index c81c67bdbd..6443a077ce 100644 --- a/bootloader_message/Android.bp +++ b/bootloader_message/Android.bp @@ -14,16 +14,47 @@ // limitations under the License. // -cc_library_static { - name: "libbootloader_message", +cc_defaults { + name: "libbootloader_message_defaults", srcs: ["bootloader_message.cpp"], cflags: [ "-Wall", "-Werror", ], - static_libs: [ + shared_libs: [ "libbase", - "libfs_mgr", + ], + static_libs: [ + "libfstab", ], export_include_dirs: ["include"], } + +cc_library { + name: "libbootloader_message", + defaults: [ + "libbootloader_message_defaults", + ], + recovery_available: true, + host_supported: true, + + target: { + host: { + shared_libs: [ + "libcutils", // for strlcpy + ], + }, + darwin: { + enabled: false, + }, + } +} + +cc_library_static { + name: "libbootloader_message_vendor", + defaults: [ + "libbootloader_message_defaults", + ], + vendor: true, + recovery_available: true, +} diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index aaeffdc5cc..b70d54e5c4 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -20,28 +20,48 @@ #include #include +#include #include +#include #include #include #include #include #include -#include +#include -static std::string get_misc_blk_device(std::string* err) { - std::unique_ptr fstab(fs_mgr_read_fstab_default(), - fs_mgr_free_fstab); - if (!fstab) { +#ifndef __ANDROID__ +#include // for strlcpy +#endif + +using android::fs_mgr::Fstab; +using android::fs_mgr::ReadDefaultFstab; + +static std::optional g_misc_device_for_test; + +// Exposed for test purpose. +void SetMiscBlockDeviceForTest(std::string_view misc_device) { + g_misc_device_for_test = misc_device; +} + +std::string get_misc_blk_device(std::string* err) { + if (g_misc_device_for_test.has_value() && !g_misc_device_for_test->empty()) { + return *g_misc_device_for_test; + } + Fstab fstab; + if (!ReadDefaultFstab(&fstab)) { *err = "failed to read default fstab"; return ""; } - fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc"); - if (record == nullptr) { - *err = "failed to find /misc partition"; - return ""; + for (const auto& entry : fstab) { + if (entry.mount_point == "/misc") { + return entry.blk_device; + } } - return record->blk_device; + + *err = "failed to find /misc partition"; + return ""; } // In recovery mode, recovery can get started and try to access the misc @@ -91,8 +111,8 @@ static bool read_misc_partition(void* p, size_t size, const std::string& misc_bl 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) { +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(), @@ -164,6 +184,14 @@ bool write_bootloader_message(const std::vector& options, std::stri return write_bootloader_message(boot, err); } +bool write_bootloader_message_to(const std::vector& options, + const std::string& misc_blk_device, std::string* err) { + bootloader_message boot = {}; + update_bootloader_message_in_struct(&boot, options); + + return write_bootloader_message_to(boot, misc_blk_device, err); +} + bool update_bootloader_message(const std::vector& options, std::string* err) { bootloader_message boot; if (!read_bootloader_message(&boot, err)) { @@ -182,13 +210,15 @@ bool update_bootloader_message_in_struct(bootloader_message* boot, memset(boot->recovery, 0, sizeof(boot->recovery)); strlcpy(boot->command, "boot-recovery", sizeof(boot->command)); - strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery)); + + std::string recovery = "recovery\n"; for (const auto& s : options) { - strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery)); + recovery += s; if (s.back() != '\n') { - strlcat(boot->recovery, "\n", sizeof(boot->recovery)); + recovery += '\n'; } } + strlcpy(boot->recovery, recovery.c_str(), sizeof(boot->recovery)); return true; } @@ -220,10 +250,60 @@ bool write_wipe_package(const std::string& package_data, std::string* err) { if (misc_blk_device.empty()) { return false; } + static constexpr size_t kMaximumWipePackageSize = + SYSTEM_SPACE_OFFSET_IN_MISC - WIPE_PACKAGE_OFFSET_IN_MISC; + if (package_data.size() > kMaximumWipePackageSize) { + *err = "Wipe package size " + std::to_string(package_data.size()) + " exceeds " + + std::to_string(kMaximumWipePackageSize) + " bytes"; + return false; + } return write_misc_partition(package_data.data(), package_data.size(), misc_blk_device, WIPE_PACKAGE_OFFSET_IN_MISC, err); } +static bool ValidateSystemSpaceRegion(size_t offset, size_t size, std::string* err) { + if (size <= SYSTEM_SPACE_SIZE_IN_MISC && offset <= (SYSTEM_SPACE_SIZE_IN_MISC - size)) { + return true; + } + *err = android::base::StringPrintf("Out of bound access (offset %zu size %zu)", offset, size); + return false; +} + +static bool ReadMiscPartitionSystemSpace(void* data, size_t size, size_t offset, std::string* err) { + if (!ValidateSystemSpaceRegion(offset, size, err)) { + return false; + } + auto misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return read_misc_partition(data, size, misc_blk_device, SYSTEM_SPACE_OFFSET_IN_MISC + offset, + err); +} + +static bool WriteMiscPartitionSystemSpace(const void* data, size_t size, size_t offset, + std::string* err) { + if (!ValidateSystemSpaceRegion(offset, size, err)) { + return false; + } + auto misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return write_misc_partition(data, size, misc_blk_device, SYSTEM_SPACE_OFFSET_IN_MISC + offset, + err); +} + +bool ReadMiscVirtualAbMessage(misc_virtual_ab_message* message, std::string* err) { + return ReadMiscPartitionSystemSpace(message, sizeof(*message), + offsetof(misc_system_space_layout, virtual_ab_message), err); +} + +bool WriteMiscVirtualAbMessage(const misc_virtual_ab_message& message, std::string* err) { + return WriteMiscPartitionSystemSpace(&message, sizeof(message), + offsetof(misc_system_space_layout, virtual_ab_message), err); +} + extern "C" bool write_reboot_bootloader(void) { std::string err; return write_reboot_bootloader(&err); diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index 95c19ae54d..e4cf09b22e 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -25,11 +25,15 @@ // 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 +// 16K - 32K Used by uncrypt and recovery to store wipe_package for A/B devices +// 32K - 64K System space, used for miscellanious AOSP features. See below. // 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; +constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; +constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024; +constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; +constexpr size_t SYSTEM_SPACE_OFFSET_IN_MISC = 32 * 1024; +constexpr size_t SYSTEM_SPACE_SIZE_IN_MISC = 32 * 1024; /* Bootloader Message (2-KiB) * @@ -79,116 +83,47 @@ struct bootloader_message { 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. - * - * The update_channel field is used to store the Omaha update channel - * if update_engine is compiled with Omaha support. - */ -struct bootloader_message_ab { - struct bootloader_message message; - char slot_suffix[32]; - char update_channel[128]; +// Holds Virtual A/B merge status information. Current version is 1. New fields +// must be added to the end. +struct misc_virtual_ab_message { + uint8_t version; + uint32_t magic; + uint8_t merge_status; // IBootControl 1.1, MergeStatus enum. + uint8_t source_slot; // Slot number when merge_status was written. + uint8_t reserved[57]; +} __attribute__((packed)); - // Round up the entire struct to 4096-byte. - char reserved[1888]; -}; +#define MISC_VIRTUAL_AB_MESSAGE_VERSION 2 +#define MISC_VIRTUAL_AB_MAGIC_HEADER 0x56740AB0 -/** - * 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"); +static_assert(sizeof(struct misc_virtual_ab_message) == 64, + "struct misc_virtual_ab_message has wrong size"); #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; +// This struct is not meant to be used directly, rather, it is to make +// computation of offsets easier. New fields must be added to the end. +struct misc_system_space_layout { + misc_virtual_ab_message virtual_ab_message; } __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 +// Gets the block device name of /misc partition. +std::string get_misc_blk_device(std::string* err); // 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); +// Writes |size| bytes of data from buffer |p| to |misc_blk_device| at |offset|. If the write fails, +// sets the error message in |err|. +bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device, + size_t offset, 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); @@ -207,6 +142,11 @@ bool write_bootloader_message_to(const bootloader_message& boot, // set the command and recovery fields, and reset the rest. bool write_bootloader_message(const std::vector& options, std::string* err); +// Write bootloader message (boots into recovery with the options) to the specific BCB device. Will +// set the command and recovery fields, and reset the rest. +bool write_bootloader_message_to(const std::vector& options, + const std::string& misc_blk_device, 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); @@ -228,6 +168,10 @@ 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); +// Read or write the Virtual A/B message from system space in /misc. +bool ReadMiscVirtualAbMessage(misc_virtual_ab_message* message, std::string* err); +bool WriteMiscVirtualAbMessage(const misc_virtual_ab_message& message, std::string* err); + #else #include diff --git a/common.h b/common.h deleted file mode 100644 index 8b336f8068..0000000000 --- a/common.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef RECOVERY_COMMON_H -#define RECOVERY_COMMON_H - -#include -#include - -#include - -// Not using the command-line defined macro here because this header could be included by -// device-specific recovery libraries. We static assert the value consistency in recovery.cpp. -static constexpr int kRecoveryApiVersion = 3; - -class RecoveryUI; - -extern RecoveryUI* ui; -extern bool modified_flash; - -// 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(); - -bool reboot(const std::string& command); - -#endif // RECOVERY_COMMON_H diff --git a/device.cpp b/device.cpp deleted file mode 100644 index f881daff63..0000000000 --- a/device.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "device.h" - -static const char* MENU_ITEMS[] = { - "Reboot system now", - "Reboot to bootloader", - "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", - "Run locale test", - "Power off", - nullptr, -}; - -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::RUN_LOCALE_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) { - return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position]; -} - -int Device::HandleMenuKey(int key, bool visible) { - if (!visible) { - return kNoAction; - } - - switch (key) { - case KEY_DOWN: - case KEY_VOLUMEDOWN: - return kHighlightDown; - - case KEY_UP: - case KEY_VOLUMEUP: - return kHighlightUp; - - case KEY_ENTER: - case KEY_POWER: - return kInvokeItem; - - default: - // If you have all of the above buttons, any other buttons - // are ignored. Otherwise, any button cycles the highlight. - return ui_->HasThreeButtons() ? kNoAction : kHighlightDown; - } -} diff --git a/device.h b/device.h deleted file mode 100644 index 74745b36c3..0000000000 --- a/device.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _RECOVERY_DEVICE_H -#define _RECOVERY_DEVICE_H - -#include "ui.h" - -class 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 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. - // - // Returns one of the defined constants below in order to: - // - // - move the menu highlight (kHighlight{Up,Down}) - // - invoke the highlighted item (kInvokeItem) - // - do nothing (kNoAction) - // - invoke a specific action (a menu position: any non-negative number) - virtual int HandleMenuKey(int key, bool visible); - - enum BuiltinAction { - NO_ACTION = 0, - REBOOT = 1, - APPLY_SDCARD = 2, - // APPLY_CACHE was 3. - APPLY_ADB_SIDELOAD = 4, - WIPE_DATA = 5, - WIPE_CACHE = 6, - REBOOT_BOOTLOADER = 7, - SHUTDOWN = 8, - VIEW_RECOVERY_LOGS = 9, - MOUNT_SYSTEM = 10, - RUN_GRAPHICS_TEST = 11, - RUN_LOCALE_TEST = 12, - }; - - // 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 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; - - // 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; - } - - 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. -Device* make_device(); - -#endif // _DEVICE_H diff --git a/edify/Android.bp b/edify/Android.bp index 42947eb4ef..0ab53d6dd0 100644 --- a/edify/Android.bp +++ b/edify/Android.bp @@ -16,6 +16,8 @@ cc_library_static { name: "libedify", host_supported: true, + vendor_available: true, + recovery_available: true, srcs: [ "expr.cpp", diff --git a/edify/expr.cpp b/edify/expr.cpp index 6823b73392..e5e0e240af 100644 --- a/edify/expr.cpp +++ b/edify/expr.cpp @@ -51,9 +51,9 @@ bool Evaluate(State* state, const std::unique_ptr& expr, std::string* resu if (!v) { return false; } - if (v->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type); - return false; + if (v->type != Value::Type::STRING) { + ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type); + return false; } *result = v->data; @@ -68,7 +68,7 @@ Value* StringValue(const char* str) { if (str == nullptr) { return nullptr; } - return new Value(VAL_STRING, str); + return new Value(Value::Type::STRING, str); } Value* StringValue(const std::string& str) { @@ -421,5 +421,5 @@ Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) { return nullptr; } -State::State(const std::string& script, void* cookie) - : script(script), cookie(cookie), error_code(kNoError), cause_code(kNoCause) {} +State::State(const std::string& script, UpdaterInterface* interface) + : script(script), updater(interface), error_code(kNoError), cause_code(kNoCause) {} diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h index 770d1cf0d0..cd9c70120d 100644 --- a/edify/include/edify/expr.h +++ b/edify/include/edify/expr.h @@ -23,19 +23,20 @@ #include #include +#include "edify/updater_interface.h" + // Forward declaration to avoid including "otautil/error_code.h". enum ErrorCode : int; enum CauseCode : int; struct State { - State(const std::string& script, void* cookie); + State(const std::string& script, UpdaterInterface* cookie); // The source of the original script. const std::string& script; - // Optional pointer to app-specific data; the core of edify never - // uses this value. - void* cookie; + // A pointer to app-specific data; the libedify doesn't use this value. + UpdaterInterface* updater; // The error message (if any) returned if the evaluation aborts. // Should be empty initially, will be either empty or a string that @@ -53,19 +54,16 @@ struct State { bool is_retry = false; }; -enum ValueType { - VAL_INVALID = -1, - VAL_STRING = 1, - VAL_BLOB = 2, -}; - struct Value { - ValueType type; - std::string data; + enum class Type { + STRING = 1, + BLOB = 2, + }; + + Value(Type type, const std::string& str) : type(type), data(str) {} - Value(ValueType type, const std::string& str) : - type(type), - data(str) {} + Type type; + std::string data; }; struct Expr; @@ -156,6 +154,6 @@ Value* StringValue(const char* str); Value* StringValue(const std::string& str); -int parse_string(const char* str, std::unique_ptr* root, int* error_count); +int ParseString(const std::string& str, std::unique_ptr* root, int* error_count); #endif // _EXPRESSION_H diff --git a/edify/include/edify/updater_interface.h b/edify/include/edify/updater_interface.h new file mode 100644 index 0000000000..aa977e3c8d --- /dev/null +++ b/edify/include/edify/updater_interface.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +struct ZipArchive; +typedef ZipArchive* ZipArchiveHandle; + +class UpdaterRuntimeInterface; + +class UpdaterInterface { + public: + virtual ~UpdaterInterface() = default; + + // Writes the message to command pipe, adds a new line in the end. + virtual void WriteToCommandPipe(const std::string_view message, bool flush = false) const = 0; + + // Sends over the message to recovery to print it on the screen. + virtual void UiPrint(const std::string_view message) const = 0; + + // Given the name of the block device, returns |name| for updates on the device; or the file path + // to the fake block device for simulations. + virtual std::string FindBlockDeviceName(const std::string_view name) const = 0; + + virtual UpdaterRuntimeInterface* GetRuntime() const = 0; + virtual ZipArchiveHandle GetPackageHandle() const = 0; + virtual std::string GetResult() const = 0; + virtual uint8_t* GetMappedPackageAddress() const = 0; + virtual size_t GetMappedPackageLength() const = 0; +}; diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h new file mode 100644 index 0000000000..bdd6aecc85 --- /dev/null +++ b/edify/include/edify/updater_runtime_interface.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and +// updates on device and host simulations can have different implementations. e.g. block devices +// during host simulation merely a temporary file. With this class, the caller side in registered +// updater's functions will stay the same for both update and simulation. +class UpdaterRuntimeInterface { + public: + virtual ~UpdaterRuntimeInterface() = default; + + // Returns true if it's a runtime instance for simulation. + virtual bool IsSimulator() const = 0; + + // Returns the value of system property |key|. If the property doesn't exist, returns + // |default_value|. + virtual std::string GetProperty(const std::string_view key, + const std::string_view default_value) const = 0; + + // Given the name of the block device, returns |name| for updates on the device; or the file path + // to the fake block device for simulations. + virtual std::string FindBlockDeviceName(const std::string_view name) const = 0; + + // Mounts the |location| on |mount_point|. Returns 0 on success. + virtual int Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view fs_type, const std::string_view mount_options) = 0; + + // Returns true if |mount_point| is mounted. + virtual bool IsMounted(const std::string_view mount_point) const = 0; + + // Unmounts the |mount_point|. Returns a pair of results with the first value indicating + // if the |mount_point| is mounted, and the second value indicating the result of umount(2). + virtual std::pair Unmount(const std::string_view mount_point) = 0; + + // Reads |filename| and puts its value to |content|. + virtual bool ReadFileToString(const std::string_view filename, std::string* content) const = 0; + + // Updates the content of |filename| with |content|. + virtual bool WriteStringToFile(const std::string_view content, + const std::string_view filename) const = 0; + + // Wipes the first |len| bytes of block device in |filename|. + virtual int WipeBlockDevice(const std::string_view filename, size_t len) const = 0; + + // Starts a child process and runs the program with |args|. Uses vfork(2) if |is_vfork| is true. + virtual int RunProgram(const std::vector& args, bool is_vfork) const = 0; + + // Runs tune2fs with arguments |args|. + virtual int Tune2Fs(const std::vector& args) const = 0; + + // Dynamic partition related functions. + virtual bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) = 0; + virtual bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) = 0; + virtual bool UpdateDynamicPartitions(const std::string_view op_list_value) = 0; + + // On devices supports A/B, add current slot suffix to arg. Otherwise, return |arg| as is. + virtual std::string AddSlotSuffix(const std::string_view arg) const = 0; +}; diff --git a/edify/parser.yy b/edify/parser.yy index bd2e0105f2..37bcdd0312 100644 --- a/edify/parser.yy +++ b/edify/parser.yy @@ -72,7 +72,7 @@ static Expr* Build(Function fn, YYLTYPE loc, size_t count, ...) { %parse-param {std::unique_ptr* root} %parse-param {int* error_count} -%error-verbose +%define parse.error verbose /* declarations in increasing order of precedence */ %left ';' @@ -138,7 +138,7 @@ void yyerror(std::unique_ptr* root, int* error_count, const char* s) { ++*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); +int ParseString(const std::string& str, std::unique_ptr* root, int* error_count) { + yy_switch_to_buffer(yy_scan_string(str.c_str())); + return yyparse(root, error_count); } diff --git a/etc/init.rc b/etc/init.rc index 0fc6c4c13b..3ec45db2fe 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -4,13 +4,23 @@ on early-init # Set the security context of /postinstall if present. restorecon /postinstall + # Copy prebuilt ld.config.txt into linkerconfig directory + copy /system/etc/ld.config.txt /linkerconfig/ld.config.txt + chmod 444 /linkerconfig/ld.config.txt + start ueventd + setprop sys.usb.configfs 0 + on init export ANDROID_ROOT /system export ANDROID_DATA /data export EXTERNAL_STORAGE /sdcard + symlink /proc/self/fd/0 /dev/stdin + symlink /proc/self/fd/1 /dev/stdout + symlink /proc/self/fd/2 /dev/stderr + symlink /system/bin /bin symlink /system/etc /etc @@ -22,6 +32,7 @@ on init mkdir /data mkdir /cache mkdir /sideload + mkdir /mnt/system mount tmpfs tmpfs /tmp chown root shell /tmp @@ -30,20 +41,6 @@ on init write /proc/sys/kernel/panic_on_oops 1 write /proc/sys/vm/max_map_count 1000000 -on fs - write /sys/class/android_usb/android0/f_ffs/aliases adb - mkdir /dev/usb-ffs 0770 shell shell - mkdir /dev/usb-ffs/adb 0770 shell shell - mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000 - - write /sys/class/android_usb/android0/enable 0 - write /sys/class/android_usb/android0/idVendor 18D1 - write /sys/class/android_usb/android0/idProduct D001 - write /sys/class/android_usb/android0/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} - on boot ifup lo hostname localhost @@ -51,10 +48,6 @@ on boot class_start default -# Load properties from /system/ + /factory after fs mount. -on load_system_props_action - load_system_props - on firmware_mounts_complete rm /dev/.booting @@ -65,40 +58,121 @@ on late-init trigger post-fs trigger post-fs-data - # 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 - # Remove a file to wake up anything waiting for firmware trigger firmware_mounts_complete trigger early-boot trigger boot -service ueventd /sbin/ueventd +service ueventd /system/bin/ueventd critical seclabel u:r:ueventd:s0 -service charger /charger -r +service charger /system/bin/charger critical seclabel u:r:charger:s0 -service recovery /sbin/recovery +service recovery /system/bin/recovery + socket recovery stream 422 system system seclabel u:r:recovery:s0 -service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery +service adbd /system/bin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery disabled socket adbd stream 660 system system seclabel u:r:adbd:s0 -# Always start adbd on userdebug and eng builds -on property:ro.debuggable=1 - write /sys/class/android_usb/android0/enable 1 - start adbd +service fastbootd /system/bin/fastbootd + disabled + group system + seclabel u:r:fastbootd:s0 # Restart adbd so it can run as root on property:service.adb.root=1 - write /sys/class/android_usb/android0/enable 0 restart adbd + +on fs && property:sys.usb.configfs=1 + mount configfs none /config + mkdir /config/usb_gadget/g1 0770 shell shell + write /config/usb_gadget/g1/idVendor 0x18D1 + mkdir /config/usb_gadget/g1/strings/0x409 0770 + write /config/usb_gadget/g1/strings/0x409/serialnumber ${ro.serialno} + write /config/usb_gadget/g1/strings/0x409/manufacturer ${ro.product.manufacturer} + write /config/usb_gadget/g1/strings/0x409/product ${ro.product.model} + mkdir /config/usb_gadget/g1/functions/ffs.adb + mkdir /config/usb_gadget/g1/functions/ffs.fastboot + mkdir /config/usb_gadget/g1/configs/b.1 0777 shell shell + mkdir /config/usb_gadget/g1/configs/b.1/strings/0x409 0770 shell shell + +on fs && property:sys.usb.configfs=0 + write /sys/class/android_usb/android0/f_ffs/aliases adb,fastboot + write /sys/class/android_usb/android0/idVendor 18D1 + 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} + +on fs + mkdir /dev/usb-ffs 0775 shell shell + mkdir /dev/usb-ffs/adb 0770 shell shell + mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000 + mkdir /dev/usb-ffs/fastboot 0770 system system + mount functionfs fastboot /dev/usb-ffs/fastboot rmode=0770,fmode=0660,uid=1000,gid=1000 + +on property:sys.usb.config=adb + start adbd + +on property:sys.usb.config=fastboot + start fastbootd + +on property:sys.usb.config=none && property:sys.usb.configfs=0 + stop adbd + stop fastbootd + write /sys/class/android_usb/android0/enable 0 + setprop sys.usb.state ${sys.usb.config} + +on property:sys.usb.config=adb && property:sys.usb.configfs=0 + write /sys/class/android_usb/android0/idProduct D001 + write /sys/class/android_usb/android0/functions adb + write /sys/class/android_usb/android0/enable 1 + setprop sys.usb.state ${sys.usb.config} + +on property:sys.usb.config=sideload && property:sys.usb.configfs=0 + write /sys/class/android_usb/android0/idProduct D001 + write /sys/class/android_usb/android0/functions adb + write /sys/class/android_usb/android0/enable 1 + setprop sys.usb.state ${sys.usb.config} + +on property:sys.usb.config=fastboot && property:sys.usb.configfs=0 + write /sys/class/android_usb/android0/idProduct 4EE0 + write /sys/class/android_usb/android0/functions fastboot write /sys/class/android_usb/android0/enable 1 + setprop sys.usb.state ${sys.usb.config} + +# Configfs triggers +on property:sys.usb.config=none && property:sys.usb.configfs=1 + write /config/usb_gadget/g1/UDC "none" + stop adbd + stop fastbootd + setprop sys.usb.ffs.ready 0 + rm /config/usb_gadget/g1/configs/b.1/f1 + setprop sys.usb.state ${sys.usb.config} + +on property:sys.usb.config=sideload && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1 + write /config/usb_gadget/g1/idProduct 0xD001 + write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "adb" + symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f1 + write /config/usb_gadget/g1/UDC ${sys.usb.controller} + setprop sys.usb.state ${sys.usb.config} + +on property:sys.usb.config=adb && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1 + write /config/usb_gadget/g1/idProduct 0xD001 + write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "adb" + symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f1 + write /config/usb_gadget/g1/UDC ${sys.usb.controller} + setprop sys.usb.state ${sys.usb.config} + +on property:sys.usb.config=fastboot && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1 + write /config/usb_gadget/g1/idProduct 0x4EE0 + write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "fastboot" + symlink /config/usb_gadget/g1/functions/ffs.fastboot /config/usb_gadget/g1/configs/b.1/f1 + write /config/usb_gadget/g1/UDC ${sys.usb.controller} + setprop sys.usb.state ${sys.usb.config} diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp new file mode 100644 index 0000000000..a0930087c7 --- /dev/null +++ b/fastboot/fastboot.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fastboot.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "recovery_ui/ui.h" + +static const std::vector> kFastbootMenuActions{ + { "Reboot system now", Device::REBOOT_FROM_FASTBOOT }, + { "Enter recovery", Device::ENTER_RECOVERY }, + { "Reboot to bootloader", Device::REBOOT_BOOTLOADER }, + { "Power off", Device::SHUTDOWN_FROM_FASTBOOT }, +}; + +Device::BuiltinAction StartFastboot(Device* device, const std::vector& /* args */) { + RecoveryUI* ui = device->GetUI(); + + std::vector title_lines = { "Android Fastboot" }; + title_lines.push_back("Product name - " + android::base::GetProperty("ro.product.device", "")); + title_lines.push_back("Bootloader version - " + android::base::GetProperty("ro.bootloader", "")); + title_lines.push_back("Baseband version - " + + android::base::GetProperty("ro.build.expect.baseband", "")); + title_lines.push_back("Serial number - " + android::base::GetProperty("ro.serialno", "")); + title_lines.push_back(std::string("Secure boot - ") + + ((android::base::GetProperty("ro.secure", "") == "1") ? "yes" : "no")); + title_lines.push_back("HW version - " + android::base::GetProperty("ro.revision", "")); + + ui->ResetKeyInterruptStatus(); + ui->SetTitle(title_lines); + ui->ShowText(true); + device->StartFastboot(); + + // Reset to normal system boot so recovery won't cycle indefinitely. + // TODO(b/112277594) Clear only if 'recovery' field of BCB is empty. If not, + // set the 'command' field of BCB to 'boot-recovery' so the next boot is into recovery + // to finish any interrupted tasks. + std::string err; + if (!clear_bootloader_message(&err)) { + LOG(ERROR) << "Failed to clear BCB message: " << err; + } + + std::vector fastboot_menu_items; + std::transform(kFastbootMenuActions.cbegin(), kFastbootMenuActions.cend(), + std::back_inserter(fastboot_menu_items), + [](const auto& entry) { return entry.first; }); + + auto chosen_item = ui->ShowMenu( + {}, fastboot_menu_items, 0, false, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); + + if (chosen_item == static_cast(RecoveryUI::KeyError::INTERRUPTED)) { + return Device::KEY_INTERRUPTED; + } + if (chosen_item == static_cast(RecoveryUI::KeyError::TIMED_OUT)) { + return Device::BuiltinAction::NO_ACTION; + } + return kFastbootMenuActions[chosen_item].second; +} diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h new file mode 100644 index 0000000000..1aa7de66e4 --- /dev/null +++ b/fastboot/fastboot.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "recovery_ui/device.h" + +Device::BuiltinAction StartFastboot(Device* device, const std::vector& args); diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp deleted file mode 100644 index 46bdf17748..0000000000 --- a/fuse_sdcard_provider.cpp +++ /dev/null @@ -1,83 +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 "fuse_sdcard_provider.h" - -#include -#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(const file_data& fd, uint32_t block, uint8_t* buffer, - uint32_t fetch_size) { - off64_t offset = static_cast(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; -} - -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 = std::bind(&read_block_file, fd, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3); - vtab.close = [&fd]() { close(fd.fd); }; - - // 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.file_size, fd.block_size) == 0; -} diff --git a/otafault/Android.bp b/fuse_sideload/Android.bp similarity index 50% rename from otafault/Android.bp rename to fuse_sideload/Android.bp index b39d5bee21..9bf19eb851 100644 --- a/otafault/Android.bp +++ b/fuse_sideload/Android.bp @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Android Open Source Project +// Copyright (C) 2018 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,57 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -cc_library_static { - name: "libotafault", +cc_library { + name: "libfusesideload", + recovery_available: true, - host_supported: true, + defaults: [ + "recovery_defaults", + ], - srcs: [ - "config.cpp", - "ota_io.cpp", + cflags: [ + "-D_XOPEN_SOURCE", + "-D_GNU_SOURCE", ], - static_libs: [ - "libbase", - "liblog", - "libziparchive", + srcs: [ + "fuse_provider.cpp", + "fuse_sideload.cpp", ], export_include_dirs: [ "include", ], - cflags: [ - "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS", - "-Wall", - "-Werror", - "-Wthread-safety", - "-Wthread-safety-negative", - ], - - target: { - darwin: { - enabled: false, - }, - }, -} - -cc_test { - name: "otafault_test", - - srcs: ["test.cpp"], - - cflags: [ - "-Wall", - "-Werror", + static_libs: [ + "libotautil", ], - static_executable: true, - - static_libs: [ - "libotafault", - "libziparchive", + shared_libs: [ "libbase", - "liblog", + "libcrypto", ], } diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp new file mode 100644 index 0000000000..8fa1b5c2e1 --- /dev/null +++ b/fuse_sideload/fuse_provider.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fuse_provider.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "fuse_sideload.h" +#include "otautil/sysutil.h" + +FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t block_size) { + struct stat sb; + if (stat(path.c_str(), &sb) == -1) { + fprintf(stderr, "failed to stat %s: %s\n", path.c_str(), strerror(errno)); + return; + } + + fd_.reset(open(path.c_str(), O_RDONLY)); + if (fd_ == -1) { + fprintf(stderr, "failed to open %s: %s\n", path.c_str(), strerror(errno)); + return; + } + file_size_ = sb.st_size; + fuse_block_size_ = block_size; +} + +std::unique_ptr FuseFileDataProvider::CreateFromFile(const std::string& path, + uint32_t block_size) { + return std::make_unique(path, block_size); +} + +bool FuseFileDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const { + uint64_t offset = static_cast(start_block) * fuse_block_size_; + if (fetch_size > file_size_ || offset > file_size_ - fetch_size) { + fprintf(stderr, + "Out of bound read, start block: %" PRIu32 ", fetch size: %" PRIu32 + ", file size %" PRIu64 "\n", + start_block, fetch_size, file_size_); + return false; + } + + if (!android::base::ReadFullyAtOffset(fd_, buffer, fetch_size, offset)) { + fprintf(stderr, "Failed to read fetch size: %" PRIu32 " bytes data at offset %" PRIu64 ": %s\n", + fetch_size, offset, strerror(errno)); + return false; + } + + return true; +} + +void FuseFileDataProvider::Close() { + fd_.reset(); +} + +FuseBlockDataProvider::FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size, + android::base::unique_fd&& fd, + uint32_t source_block_size, RangeSet ranges) + : FuseDataProvider(file_size, fuse_block_size), + fd_(std::move(fd)), + source_block_size_(source_block_size), + ranges_(std::move(ranges)) { + // Make sure the offset is also aligned with the blocks on the block device when we call + // ReadBlockAlignedData(). + CHECK_EQ(0, fuse_block_size_ % source_block_size_); +} + +bool FuseBlockDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const { + uint64_t offset = static_cast(start_block) * fuse_block_size_; + if (fetch_size > file_size_ || offset > file_size_ - fetch_size) { + LOG(ERROR) << "Out of bound read, offset: " << offset << ", fetch size: " << fetch_size + << ", file size " << file_size_; + return false; + } + + auto read_ranges = + ranges_.GetSubRanges(offset / source_block_size_, fetch_size / source_block_size_); + if (!read_ranges) { + return false; + } + + uint8_t* next_out = buffer; + for (const auto& [range_start, range_end] : read_ranges.value()) { + uint64_t bytes_start = static_cast(range_start) * source_block_size_; + uint64_t bytes_to_read = static_cast(range_end - range_start) * source_block_size_; + if (!android::base::ReadFullyAtOffset(fd_, next_out, bytes_to_read, bytes_start)) { + PLOG(ERROR) << "Failed to read " << bytes_to_read << " bytes at offset " << bytes_start; + return false; + } + + next_out += bytes_to_read; + } + + if (uint64_t tailing_bytes = fetch_size % source_block_size_; tailing_bytes != 0) { + // Calculate the offset to last partial block. + uint64_t tailing_offset = + read_ranges.value() + ? static_cast((read_ranges->cend() - 1)->second) * source_block_size_ + : static_cast(start_block) * source_block_size_; + if (!android::base::ReadFullyAtOffset(fd_, next_out, tailing_bytes, tailing_offset)) { + PLOG(ERROR) << "Failed to read tailing " << tailing_bytes << " bytes at offset " + << tailing_offset; + return false; + } + } + return true; +} + +std::unique_ptr FuseBlockDataProvider::CreateFromBlockMap( + const std::string& block_map_path, uint32_t fuse_block_size) { + auto block_map = BlockMapData::ParseBlockMapFile(block_map_path); + if (!block_map) { + return nullptr; + } + + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map.path().c_str(), O_RDONLY))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << block_map.path(); + return nullptr; + } + + return std::unique_ptr( + new FuseBlockDataProvider(block_map.file_size(), fuse_block_size, std::move(fd), + block_map.block_size(), block_map.block_ranges())); +} + +void FuseBlockDataProvider::Close() { + fd_.reset(); +} diff --git a/fuse_sideload.cpp b/fuse_sideload/fuse_sideload.cpp similarity index 96% rename from fuse_sideload.cpp rename to fuse_sideload/fuse_sideload.cpp index 1c7e98f015..3d9480309c 100644 --- a/fuse_sideload.cpp +++ b/fuse_sideload/fuse_sideload.cpp @@ -76,7 +76,7 @@ using SHA256Digest = std::array; struct fuse_data { android::base::unique_fd ffd; // file descriptor for the fuse socket - provider_vtab vtab; + FuseDataProvider* provider; // Provider of the source data. uint64_t file_size; // bytes @@ -236,7 +236,7 @@ static int fetch_block(fuse_data* fd, uint32_t block) { return 0; } - size_t fetch_size = fd->block_size; + uint32_t fetch_size = fd->block_size; if (block * fd->block_size + fetch_size > fd->file_size) { // If we're reading the last (partial) block of the file, expect a shorter response from the // host, and pad the rest of the block with zeroes. @@ -244,8 +244,9 @@ static int fetch_block(fuse_data* fd, uint32_t block) { memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size); } - int result = fd->vtab.read_block(block, fd->block_data, fetch_size); - if (result < 0) return result; + if (!fd->provider->ReadBlockAlignedData(fd->block_data, fetch_size, block)) { + return -EIO; + } fd->curr_block = block; @@ -340,12 +341,14 @@ static int handle_read(void* data, fuse_data* fd, const fuse_in_header* hdr) { return NO_STATUS; } -int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size, - const char* mount_point) { +int run_fuse_sideload(std::unique_ptr&& provider, const char* mount_point) { // If something's already mounted on our mountpoint, try to remove it. (Mostly in case of a // previous abnormal exit.) umount2(mount_point, MNT_FORCE); + uint64_t file_size = provider->file_size(); + uint32_t block_size = provider->fuse_block_size(); + // 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); @@ -357,7 +360,7 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl } fuse_data fd = {}; - fd.vtab = vtab; + fd.provider = provider.get(); fd.file_size = file_size; fd.block_size = block_size; fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1); @@ -389,7 +392,7 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl } fd.ffd.reset(open("/dev/fuse", O_RDWR)); - if (!fd.ffd) { + if (fd.ffd == -1) { perror("open /dev/fuse"); result = -1; goto done; @@ -479,7 +482,7 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl } done: - fd.vtab.close(); + provider->Close(); if (umount2(mount_point, MNT_DETACH) == -1) { fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno)); diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h new file mode 100644 index 0000000000..3cdaef33d8 --- /dev/null +++ b/fuse_sideload/include/fuse_provider.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +#include + +#include "otautil/rangeset.h" + +// This is the base class to read data from source and provide the data to FUSE. +class FuseDataProvider { + public: + FuseDataProvider(uint64_t file_size, uint32_t block_size) + : file_size_(file_size), fuse_block_size_(block_size) {} + + virtual ~FuseDataProvider() = default; + + uint64_t file_size() const { + return file_size_; + } + uint32_t fuse_block_size() const { + return fuse_block_size_; + } + + // Reads |fetch_size| bytes data starting from |start_block|. Puts the result in |buffer|. + virtual bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const = 0; + + virtual bool Valid() const = 0; + + virtual void Close() {} + + protected: + FuseDataProvider() = default; + + // Size in bytes of the file to read. + uint64_t file_size_ = 0; + // Block size passed to the fuse, this is different from the block size of the block device. + uint32_t fuse_block_size_ = 0; +}; + +// This class reads data from a file. +class FuseFileDataProvider : public FuseDataProvider { + public: + FuseFileDataProvider(const std::string& path, uint32_t block_size); + + static std::unique_ptr CreateFromFile(const std::string& path, + uint32_t block_size); + + bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const override; + + bool Valid() const override { + return fd_ != -1; + } + + void Close() override; + + private: + // The underlying source to read data from. + android::base::unique_fd fd_; +}; + +// This class parses a block map and reads data from the underlying block device. +class FuseBlockDataProvider : public FuseDataProvider { + public: + // Constructs the fuse provider from the block map. + static std::unique_ptr CreateFromBlockMap(const std::string& block_map_path, + uint32_t fuse_block_size); + + RangeSet ranges() const { + return ranges_; + } + + bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const override; + + bool Valid() const override { + return fd_ != -1; + } + + void Close() override; + + private: + FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size, android::base::unique_fd&& fd, + uint32_t source_block_size, RangeSet ranges); + // The underlying block device to read data from. + android::base::unique_fd fd_; + // The block size of the source block device. + uint32_t source_block_size_; + // The block ranges from the source block device that consist of the file + RangeSet ranges_; +}; diff --git a/fuse_sideload.h b/fuse_sideload/include/fuse_sideload.h similarity index 80% rename from fuse_sideload.h rename to fuse_sideload/include/fuse_sideload.h index 1b34cbdb08..1b7759a7fe 100644 --- a/fuse_sideload.h +++ b/fuse_sideload/include/fuse_sideload.h @@ -17,7 +17,9 @@ #ifndef __FUSE_SIDELOAD_H #define __FUSE_SIDELOAD_H -#include +#include + +#include "fuse_provider.h" // Define the filenames created by the sideload FUSE filesystem. static constexpr const char* FUSE_SIDELOAD_HOST_MOUNTPOINT = "/sideload"; @@ -26,15 +28,7 @@ static constexpr const char* FUSE_SIDELOAD_HOST_PATHNAME = "/sideload/package.zi static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_FLAG = "exit"; static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_PATHNAME = "/sideload/exit"; -struct provider_vtab { - // read a block - std::function read_block; - - // close down - std::function close; -}; - -int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size, +int run_fuse_sideload(std::unique_ptr&& provider, const char* mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT); #endif diff --git a/install.h b/install.h deleted file mode 100644 index f3fda30511..0000000000 --- a/install.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef RECOVERY_INSTALL_H_ -#define RECOVERY_INSTALL_H_ - -#include -#include - -enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED, - INSTALL_RETRY }; - -// Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on -// exit, caller should wipe the cache partition. -int install_package(const std::string& package, bool* wipe_cache, const std::string& install_file, - bool needs_mount, int retry_count); - -// Verify the package by ota keys. Return true if the package is verified successfully, -// otherwise return false. -bool verify_package(const unsigned char* package_data, size_t package_size); - -// Read meta data file of the package, write its content in the string pointed by meta_data. -// Return true if succeed, otherwise return false. -bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata); - -// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the -// entry doesn't exist. -bool verify_package_compatibility(ZipArchiveHandle package_zip); - -#endif // RECOVERY_INSTALL_H_ diff --git a/install/Android.bp b/install/Android.bp new file mode 100644 index 0000000000..bed3bc5045 --- /dev/null +++ b/install/Android.bp @@ -0,0 +1,84 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_defaults { + name: "libinstall_defaults", + + defaults: [ + "recovery_defaults", + ], + + shared_libs: [ + "libbase", + "libbootloader_message", + "libcrypto", + "libext4_utils", + "libfs_mgr", + "libfusesideload", + "libhidl-gen-utils", + "libhidlbase", + "liblog", + "libselinux", + "libtinyxml2", + "libutils", + "libz", + "libziparchive", + ], + + static_libs: [ + "librecovery_utils", + "libotautil", + "libsnapshot_nobinder", + + // external dependencies + "libvintf", + ], +} + +cc_library_static { + name: "libinstall", + recovery_available: true, + + defaults: [ + "libinstall_defaults", + ], + + srcs: [ + "adb_install.cpp", + "asn1_decoder.cpp", + "fuse_install.cpp", + "install.cpp", + "package.cpp", + "snapshot_utils.cpp", + "verifier.cpp", + "wipe_data.cpp", + "wipe_device.cpp", + ], + + header_libs: [ + "libminadbd_headers", + ], + + shared_libs: [ + "librecovery_ui", + ], + + export_include_dirs: [ + "include", + ], + + export_shared_lib_headers: [ + "librecovery_ui", + ], +} diff --git a/install/adb_install.cpp b/install/adb_install.cpp new file mode 100644 index 0000000000..ee79a32c0f --- /dev/null +++ b/install/adb_install.cpp @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2012 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 "install/adb_install.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "fuse_sideload.h" +#include "install/install.h" +#include "install/wipe_data.h" +#include "minadbd/types.h" +#include "otautil/sysutil.h" +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +// A CommandFunction returns a pair of (result, should_continue), which indicates the command +// execution result and whether it should proceed to the next iteration. The execution result will +// always be sent to the minadbd side. +using CommandFunction = std::function()>; + +static bool SetUsbConfig(const std::string& state) { + android::base::SetProperty("sys.usb.config", state); + return android::base::WaitForProperty("sys.usb.state", state); +} + +// Parses the minadbd command in |message|; returns MinadbdCommand::kError upon errors. +static MinadbdCommand ParseMinadbdCommand(const std::string& message) { + if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) { + LOG(ERROR) << "Failed to parse command in message " << message; + return MinadbdCommand::kError; + } + + auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix)); + auto cmd_code = android::base::get_unaligned(cmd_code_string.c_str()); + if (cmd_code >= static_cast(MinadbdCommand::kError)) { + LOG(ERROR) << "Unsupported command code: " << cmd_code; + return MinadbdCommand::kError; + } + + return static_cast(cmd_code); +} + +static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { + char message[kMinadbdMessageSize]; + memcpy(message, kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix)); + android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), status); + + if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to write message " << message; + return false; + } + return true; +} + +// Installs the package from FUSE. Returns the installation result and whether it should continue +// waiting for new commands. +static auto AdbInstallPackageHandler(RecoveryUI* ui, InstallResult* result) { + // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long + // because the minadbd service has already issued an install command. 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.) + constexpr int ADB_INSTALL_TIMEOUT = 15; + bool should_continue = true; + *result = INSTALL_ERROR; + for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { + struct stat st; + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { + if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) { + sleep(1); + continue; + } else { + should_continue = false; + ui->Print("\nTimed out waiting for fuse to be ready.\n\n"); + break; + } + } + + auto package = + Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME, + std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + *result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0, ui); + break; + } + + // Calling stat() on this magic filename signals the FUSE to exit. + struct stat st; + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + return std::make_pair(*result == INSTALL_SUCCESS, should_continue); +} + +static auto AdbRebootHandler(MinadbdCommand command, InstallResult* result, + Device::BuiltinAction* reboot_action) { + // Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows + // rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly + // installed bootloader/recovery image. + switch (command) { + case MinadbdCommand::kRebootBootloader: + *reboot_action = Device::REBOOT_BOOTLOADER; + break; + case MinadbdCommand::kRebootFastboot: + *reboot_action = Device::REBOOT_FASTBOOT; + break; + case MinadbdCommand::kRebootRecovery: + *reboot_action = Device::REBOOT_RECOVERY; + break; + case MinadbdCommand::kRebootRescue: + *reboot_action = Device::REBOOT_RESCUE; + break; + case MinadbdCommand::kRebootAndroid: + default: + *reboot_action = Device::REBOOT; + break; + } + *result = INSTALL_REBOOT; + return std::make_pair(true, false); +} + +// Parses and executes the command from minadbd. Returns whether the caller should keep waiting for +// next command. +static bool HandleMessageFromMinadbd(int socket_fd, + const std::map& command_map) { + char buffer[kMinadbdMessageSize]; + if (!android::base::ReadFully(socket_fd, buffer, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to read message from minadbd"; + return false; + } + + std::string message(buffer, buffer + kMinadbdMessageSize); + auto command_type = ParseMinadbdCommand(message); + if (command_type == MinadbdCommand::kError) { + return false; + } + if (command_map.find(command_type) == command_map.end()) { + LOG(ERROR) << "Unsupported command: " + << android::base::get_unaligned( + message.substr(strlen(kMinadbdCommandPrefix)).c_str()); + return false; + } + + // We have received a valid command, execute the corresponding function. + const auto& command_func = command_map.at(command_type); + const auto [result, should_continue] = command_func(); + LOG(INFO) << "Command " << static_cast(command_type) << " finished with " << result; + if (!WriteStatusToFd(result ? MinadbdCommandStatus::kSuccess : MinadbdCommandStatus::kFailure, + socket_fd)) { + return false; + } + return should_continue; +} + +// TODO(xunchang) add a wrapper function and kill the minadbd service there. +static void ListenAndExecuteMinadbdCommands( + RecoveryUI* ui, pid_t minadbd_pid, android::base::unique_fd&& socket_fd, + const std::map& command_map) { + android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC)); + if (epoll_fd == -1) { + PLOG(ERROR) << "Failed to create epoll"; + kill(minadbd_pid, SIGKILL); + return; + } + + constexpr int EPOLL_MAX_EVENTS = 10; + struct epoll_event ev = {}; + ev.events = EPOLLIN | EPOLLHUP; + ev.data.fd = socket_fd.get(); + struct epoll_event events[EPOLL_MAX_EVENTS]; + if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, socket_fd.get(), &ev) == -1) { + PLOG(ERROR) << "Failed to add socket fd to epoll"; + kill(minadbd_pid, SIGKILL); + return; + } + + // Set the timeout to be 300s when waiting for minadbd commands. + constexpr int TIMEOUT_MILLIS = 300 * 1000; + while (true) { + // Reset the progress bar and the background image before each command. + ui->SetProgressType(RecoveryUI::EMPTY); + ui->SetBackground(RecoveryUI::NO_COMMAND); + + // Poll for the status change of the socket_fd, and handle the message if the fd is ready to + // read. + int event_count = + TEMP_FAILURE_RETRY(epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, TIMEOUT_MILLIS)); + if (event_count == -1) { + PLOG(ERROR) << "Failed to wait for epoll events"; + kill(minadbd_pid, SIGKILL); + return; + } + if (event_count == 0) { + LOG(ERROR) << "Timeout waiting for messages from minadbd"; + kill(minadbd_pid, SIGKILL); + return; + } + + for (int n = 0; n < event_count; n++) { + if (events[n].events & EPOLLHUP) { + LOG(INFO) << "Socket has been closed"; + kill(minadbd_pid, SIGKILL); + return; + } + if (!HandleMessageFromMinadbd(socket_fd.get(), command_map)) { + kill(minadbd_pid, SIGKILL); + return; + } + } + } +} + +// Recovery starts minadbd service as a child process, and spawns another thread to listen for the +// message from minadbd through a socket pair. Here is an example to execute one command from adb +// host. +// a. recovery b. listener thread c. minadbd service +// +// a1. create socket pair +// a2. fork minadbd service +// c3. wait for the adb commands +// from host +// c4. after receiving host commands: +// 1) set up pre-condition (i.e. +// start fuse for adb sideload) +// 2) issue command through +// socket. +// 3) wait for result +// a5. start listener thread +// b6. listen for message from +// minadbd in a loop. +// b7. After receiving a minadbd +// command from socket +// 1) execute the command function +// 2) send the result back to +// minadbd +// ...... +// c8. exit upon receiving the +// result +// a9. wait for listener thread +// to exit. +// +// a10. wait for minadbd to +// exit +// b11. exit the listening loop +// +static void CreateMinadbdServiceAndExecuteCommands( + RecoveryUI* ui, const std::map& command_map, + bool rescue_mode) { + signal(SIGPIPE, SIG_IGN); + + android::base::unique_fd recovery_socket; + android::base::unique_fd minadbd_socket; + if (!android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &recovery_socket, &minadbd_socket)) { + PLOG(ERROR) << "Failed to create socket"; + return; + } + + pid_t child = fork(); + if (child == -1) { + PLOG(ERROR) << "Failed to fork child process"; + return; + } + if (child == 0) { + recovery_socket.reset(); + std::vector minadbd_commands = { + "/system/bin/minadbd", + "--socket_fd", + std::to_string(minadbd_socket.release()), + }; + if (rescue_mode) { + minadbd_commands.push_back("--rescue"); + } + auto exec_args = StringVectorToNullTerminatedArray(minadbd_commands); + execv(exec_args[0], exec_args.data()); + _exit(EXIT_FAILURE); + } + + minadbd_socket.reset(); + + // We need to call SetUsbConfig() after forking minadbd service. Because the function waits for + // the usb state to be updated, which depends on sys.usb.ffs.ready=1 set in the adb daemon. + if (!SetUsbConfig("sideload")) { + LOG(ERROR) << "Failed to set usb config to sideload"; + return; + } + + std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child, + std::move(recovery_socket), std::ref(command_map)); + if (listener_thread.joinable()) { + listener_thread.join(); + } + + int status; + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (WEXITSTATUS(status) == MinadbdErrorCode::kMinadbdAdbVersionError) { + LOG(ERROR) << "\nYou need adb 1.0.32 or newer to sideload\nto this device.\n"; + } else if (!WIFSIGNALED(status)) { + LOG(ERROR) << "\n(adbd status " << WEXITSTATUS(status) << ")"; + } + } + + signal(SIGPIPE, SIG_DFL); +} + +InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) { + // Save the usb state to restore after the sideload operation. + std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); + // Clean up state and stop adbd. + if (usb_state != "none" && !SetUsbConfig("none")) { + LOG(ERROR) << "Failed to clear USB config"; + return INSTALL_ERROR; + } + + RecoveryUI* ui = device->GetUI(); + + InstallResult install_result = INSTALL_ERROR; + std::map command_map{ + { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) }, + { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootBootloader, + std::bind(&AdbRebootHandler, MinadbdCommand::kRebootBootloader, &install_result, + reboot_action) }, + { MinadbdCommand::kRebootFastboot, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootFastboot, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootRecovery, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRecovery, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootRescue, + std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) }, + }; + + if (!rescue_mode) { + ui->Print( + "\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload \"...\n"); + } else { + command_map.emplace(MinadbdCommand::kWipeData, [&device]() { + bool result = WipeData(device, false); + return std::make_pair(result, true); + }); + command_map.emplace(MinadbdCommand::kNoOp, []() { return std::make_pair(true, true); }); + + ui->Print("\n\nWaiting for rescue commands...\n"); + } + + CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode); + + // Clean up before switching to the older state, for example setting the state + // to none sets sys/class/android_usb/android0/enable to 0. + if (!SetUsbConfig("none")) { + LOG(ERROR) << "Failed to clear USB config"; + } + + if (usb_state != "none") { + if (!SetUsbConfig(usb_state)) { + LOG(ERROR) << "Failed to set USB config to " << usb_state; + } + } + + return install_result; +} diff --git a/asn1_decoder.cpp b/install/asn1_decoder.cpp similarity index 98% rename from asn1_decoder.cpp rename to install/asn1_decoder.cpp index 285214f16c..2d81a6e137 100644 --- a/asn1_decoder.cpp +++ b/install/asn1_decoder.cpp @@ -14,9 +14,7 @@ * limitations under the License. */ -#include "asn1_decoder.h" - -#include +#include "private/asn1_decoder.h" int asn1_context::peek_byte() const { if (length_ == 0) { diff --git a/install/fuse_install.cpp b/install/fuse_install.cpp new file mode 100644 index 0000000000..143b5d3fbc --- /dev/null +++ b/install/fuse_install.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "install/fuse_install.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "bootloader_message/bootloader_message.h" +#include "fuse_provider.h" +#include "fuse_sideload.h" +#include "install/install.h" +#include "recovery_utils/roots.h" + +static constexpr const char* SDCARD_ROOT = "/sdcard"; +// How long (in seconds) we wait for the fuse-provided package file to +// appear, before timing out. +static constexpr int SDCARD_INSTALL_TIMEOUT = 10; + +// Set the BCB to reboot back into recovery (it won't resume the install from +// sdcard though). +static void SetSdcardUpdateBootloaderMessage() { + std::vector options; + std::string err; + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << "Failed to set BCB message: " << err; + } +} + +// Returns the selected filename, or an empty string. +static std::string BrowseDirectory(const std::string& path, Device* device, RecoveryUI* ui) { + ensure_path_mounted(path); + + std::unique_ptr d(opendir(path.c_str()), closedir); + if (!d) { + PLOG(ERROR) << "error opening " << path; + return ""; + } + + std::vector dirs; + std::vector entries{ "../" }; // "../" is always the first entry. + + dirent* de; + while ((de = readdir(d.get())) != nullptr) { + std::string name(de->d_name); + + if (de->d_type == DT_DIR) { + // Skip "." and ".." entries. + if (name == "." || name == "..") continue; + dirs.push_back(name + "/"); + } else if (de->d_type == DT_REG && (android::base::EndsWithIgnoreCase(name, ".zip") || + android::base::EndsWithIgnoreCase(name, ".map"))) { + entries.push_back(name); + } + } + + std::sort(dirs.begin(), dirs.end()); + std::sort(entries.begin(), entries.end()); + + // Append dirs to the entries list. + entries.insert(entries.end(), dirs.begin(), dirs.end()); + + std::vector headers{ "Choose a package to install:", path }; + + size_t chosen_item = 0; + while (true) { + chosen_item = ui->ShowMenu( + headers, entries, chosen_item, true, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); + + // Return if WaitKey() was interrupted. + if (chosen_item == static_cast(RecoveryUI::KeyError::INTERRUPTED)) { + return ""; + } + + const std::string& item = entries[chosen_item]; + if (chosen_item == 0) { + // Go up but continue browsing (if the caller is BrowseDirectory). + return ""; + } + + std::string new_path = path + "/" + item; + if (new_path.back() == '/') { + // Recurse down into a subdirectory. + new_path.pop_back(); + std::string result = BrowseDirectory(new_path, device, ui); + if (!result.empty()) return result; + } else { + // Selected a zip file: return the path to the caller. + return new_path; + } + } + + // Unreachable. +} + +static bool StartInstallPackageFuse(std::string_view path) { + if (path.empty()) { + return false; + } + + constexpr auto FUSE_BLOCK_SIZE = 65536; + bool is_block_map = android::base::ConsumePrefix(&path, "@"); + auto fuse_data_provider = + is_block_map ? FuseBlockDataProvider::CreateFromBlockMap(std::string(path), FUSE_BLOCK_SIZE) + : FuseFileDataProvider::CreateFromFile(std::string(path), FUSE_BLOCK_SIZE); + + if (!fuse_data_provider || !fuse_data_provider->Valid()) { + LOG(ERROR) << "Failed to create fuse data provider."; + return false; + } + + if (android::base::StartsWith(path, SDCARD_ROOT)) { + // 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_ROOT, MNT_DETACH); + } + + return run_fuse_sideload(std::move(fuse_data_provider)) == 0; +} + +InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui) { + // We used to use fuse in a thread as opposed to a process. Since accessing + // through fuse involves going from kernel to userspace to kernel, it leads + // to deadlock when a page fault occurs. (Bug: 26313124) + pid_t child; + if ((child = fork()) == 0) { + bool status = StartInstallPackageFuse(path); + + _exit(status ? EXIT_SUCCESS : EXIT_FAILURE); + } + + // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child process is ready. + InstallResult result = INSTALL_ERROR; + int status; + bool waited = false; + for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { + if (waitpid(child, &status, WNOHANG) == -1) { + result = INSTALL_ERROR; + waited = true; + break; + } + + struct stat sb; + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &sb) == -1) { + if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT - 1) { + sleep(1); + continue; + } else { + LOG(ERROR) << "Timed out waiting for the fuse-provided package."; + result = INSTALL_ERROR; + kill(child, SIGKILL); + break; + } + } + auto package = + Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME, + std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + result = + InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0 /* retry_count */, ui); + break; + } + + if (!waited) { + // Calling stat() on this magic filename signals the fuse + // filesystem to shut down. + struct stat sb; + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &sb); + + waitpid(child, &status, 0); + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status); + } + + return result; +} + +InstallResult ApplyFromSdcard(Device* device) { + auto ui = device->GetUI(); + if (ensure_path_mounted(SDCARD_ROOT) != 0) { + LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n"; + return INSTALL_ERROR; + } + + std::string path = BrowseDirectory(SDCARD_ROOT, device, ui); + if (path.empty()) { + LOG(ERROR) << "\n-- No package file selected.\n"; + ensure_path_unmounted(SDCARD_ROOT); + return INSTALL_ERROR; + } + + // Hint the install function to read from a block map file. + if (android::base::EndsWithIgnoreCase(path, ".map")) { + path = "@" + path; + } + + ui->Print("\n-- Install %s ...\n", path.c_str()); + SetSdcardUpdateBootloaderMessage(); + + auto result = InstallWithFuseFromPath(path, ui); + ensure_path_unmounted(SDCARD_ROOT); + return result; +} diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h new file mode 100644 index 0000000000..8800223619 --- /dev/null +++ b/install/include/install/adb_install.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "install/install.h" +#include "recovery_ui/device.h" + +// Applies a package via `adb sideload` or `adb rescue`. Returns the install result. When a reboot +// has been requested, INSTALL_REBOOT will be the return value, with the reboot target set in +// reboot_action. +InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action); diff --git a/install/include/install/fuse_install.h b/install/include/install/fuse_install.h new file mode 100644 index 0000000000..63b116aeb1 --- /dev/null +++ b/install/include/install/fuse_install.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "install/install.h" +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +// Starts FUSE with the package from |path| as the data source. And installs the package from +// |FUSE_SIDELOAD_HOST_PATHNAME|. The |path| can point to the location of a package zip file or a +// block map file with the prefix '@'; e.g. /sdcard/package.zip, @/cache/recovery/block.map. +InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui); + +InstallResult ApplyFromSdcard(Device* device); diff --git a/install/include/install/install.h b/install/include/install/install.h new file mode 100644 index 0000000000..bef23e9caf --- /dev/null +++ b/install/include/install/install.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +#include + +#include "package.h" +#include "recovery_ui/ui.h" + +enum InstallResult { + INSTALL_SUCCESS, + INSTALL_ERROR, + INSTALL_CORRUPT, + INSTALL_NONE, + INSTALL_SKIPPED, + INSTALL_RETRY, + INSTALL_KEY_INTERRUPTED, + INSTALL_REBOOT, +}; + +enum class OtaType { + AB, + BLOCK, + BRICK, +}; + +// Installs the given update package. The package_id is a string provided by the caller (e.g. the +// package path) to identify the package and log to last_install. This function should also wipe the +// cache partition after a successful installation if |should_wipe_cache| is true or an updater +// command asks to wipe the cache. +InstallResult InstallPackage(Package* package, const std::string_view package_id, + bool should_wipe_cache, int retry_count, RecoveryUI* ui); + +// Verifies the package by ota keys. Returns true if the package is verified successfully, +// otherwise returns false. +bool verify_package(Package* package, RecoveryUI* ui); + +// Reads meta data file of the package; parses each line in the format "key=value"; and writes the +// result to |metadata|. Return true if succeed, otherwise return false. +bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map* metadata); + +// Checks if the metadata in the OTA package has expected values. Mandatory checks: ota-type, +// pre-device and serial number (if presents). A/B OTA specific checks: pre-build version, +// fingerprint, timestamp. +bool CheckPackageMetadata(const std::map& metadata, OtaType ota_type); + +// Ensures the path to the update package is mounted. Also set the |should_use_fuse| to true if the +// package stays on a removable media. +bool SetupPackageMount(const std::string& package_path, bool* should_use_fuse); diff --git a/install/include/install/package.h b/install/include/install/package.h new file mode 100644 index 0000000000..0b42332384 --- /dev/null +++ b/install/include/install/package.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include + +#include "verifier.h" + +enum class PackageType { + kMemory, + kFile, +}; + +// This class serves as a wrapper for an OTA update package. It aims to provide the common +// interface for both packages loaded in memory and packages read from fd. +class Package : public VerifierInterface { + public: + static std::unique_ptr CreateMemoryPackage( + const std::string& path, const std::function& set_progress); + static std::unique_ptr CreateMemoryPackage( + std::vector content, const std::function& set_progress); + static std::unique_ptr CreateFilePackage(const std::string& path, + const std::function& set_progress); + + virtual ~Package() = default; + + virtual PackageType GetType() const = 0; + + virtual std::string GetPath() const = 0; + + // Opens the package as a zip file and returns the ZipArchiveHandle. + virtual ZipArchiveHandle GetZipArchiveHandle() = 0; + + // Updates the progress in fraction during package verification. + void SetProgress(float progress) override; + + protected: + // An optional function to update the progress. + std::function set_progress_; +}; diff --git a/install/include/install/snapshot_utils.h b/install/include/install/snapshot_utils.h new file mode 100644 index 0000000000..f4b978d2ea --- /dev/null +++ b/install/include/install/snapshot_utils.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "recovery_ui/device.h" + +bool FinishPendingSnapshotMerges(Device* device); + +/* + * This function tries to create the snapshotted devices in the case a Virtual + * A/B device is updating. + * The function returns false in case of critical failure that would prevent + * the further mountings of devices, or true in case of success, if either the + * devices were created or there was no need to. + */ +bool CreateSnapshotPartitions(); diff --git a/install/include/install/verifier.h b/install/include/install/verifier.h new file mode 100644 index 0000000000..f9e947580e --- /dev/null +++ b/install/include/install/verifier.h @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include + +constexpr size_t MiB = 1024 * 1024; + +using HasherUpdateCallback = std::function; + +struct RSADeleter { + void operator()(RSA* rsa) const { + RSA_free(rsa); + } +}; + +struct ECKEYDeleter { + void operator()(EC_KEY* ec_key) const { + EC_KEY_free(ec_key); + } +}; + +struct Certificate { + typedef enum { + KEY_TYPE_RSA, + KEY_TYPE_EC, + } KeyType; + + Certificate(int hash_len_, KeyType key_type_, std::unique_ptr&& rsa_, + std::unique_ptr&& ec_) + : hash_len(hash_len_), key_type(key_type_), rsa(std::move(rsa_)), ec(std::move(ec_)) {} + + // SHA_DIGEST_LENGTH (SHA-1) or SHA256_DIGEST_LENGTH (SHA-256) + int hash_len; + KeyType key_type; + std::unique_ptr rsa; + std::unique_ptr ec; +}; + +class VerifierInterface { + public: + virtual ~VerifierInterface() = default; + + // Returns the package size in bytes. + virtual uint64_t GetPackageSize() const = 0; + + // Reads |byte_count| data starting from |offset|, and puts the result in |buffer|. + virtual bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) = 0; + + // Updates the hash contexts for |length| bytes data starting from |start|. + virtual bool UpdateHashAtOffset(const std::vector& hashers, uint64_t start, + uint64_t length) = 0; + + // Updates the progress in fraction during package verification. + virtual void SetProgress(float progress) = 0; +}; + +// Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip. +// Verifies that it matches one of the given public keys. Returns VERIFY_SUCCESS or +// VERIFY_FAILURE (if any error is encountered or no key matches the signature). +int verify_file(VerifierInterface* package, const std::vector& keys); + +// Checks that the RSA key has a modulus of 2048 or 4096 bits long, and public exponent is 3 or +// 65537. +bool CheckRSAKey(const std::unique_ptr& rsa); + +// Checks that the field size of the curve for the EC key is 256 bits. +bool CheckECKey(const std::unique_ptr& ec_key); + +// Parses a PEM-encoded x509 certificate from the given buffer and saves it into |cert|. Returns +// false if there is a parsing failure or the signature's encryption algorithm is not supported. +bool LoadCertificateFromBuffer(const std::vector& pem_content, Certificate* cert); + +// Iterates over the zip entries with the suffix "x509.pem" and returns a list of recognized +// certificates. Returns an empty list if we fail to parse any of the entries. +std::vector LoadKeysFromZipfile(const std::string& zip_name); + +#define VERIFY_SUCCESS 0 +#define VERIFY_FAILURE 1 diff --git a/install/include/install/wipe_data.h b/install/include/install/wipe_data.h new file mode 100644 index 0000000000..b34891f3d3 --- /dev/null +++ b/install/include/install/wipe_data.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +struct selabel_handle; + +// Returns true on success. +bool WipeCache(RecoveryUI* ui, const std::function& confirm); + +// Returns true on success. +bool WipeData(Device* device, bool convert_fbe); diff --git a/install/include/install/wipe_device.h b/install/include/install/wipe_device.h new file mode 100644 index 0000000000..c60b99997d --- /dev/null +++ b/install/include/install/wipe_device.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "install/package.h" +#include "recovery_ui/device.h" + +// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE. +bool WipeAbDevice(Device* device, size_t wipe_package_size); + +// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe. +std::vector GetWipePartitionList(Package* wipe_package); diff --git a/asn1_decoder.h b/install/include/private/asn1_decoder.h similarity index 98% rename from asn1_decoder.h rename to install/include/private/asn1_decoder.h index 3e992115a8..e5337d9c4e 100644 --- a/asn1_decoder.h +++ b/install/include/private/asn1_decoder.h @@ -17,6 +17,7 @@ #ifndef ASN1_DECODER_H_ #define ASN1_DECODER_H_ +#include #include class asn1_context { diff --git a/install/include/private/setup_commands.h b/install/include/private/setup_commands.h new file mode 100644 index 0000000000..dcff76112d --- /dev/null +++ b/install/include/private/setup_commands.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Private headers exposed for testing purpose only. + +#pragma once + +#include +#include + +#include + +// Sets up the commands for a non-A/B update. Extracts the updater binary from the open zip archive +// |zip| located at |package|. Stores the command line that should be called into |cmd|. The +// |status_fd| is the file descriptor the child process should use to report back the progress of +// the update. +bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count, + int status_fd, std::vector* cmd); + +// Sets up the commands for an A/B update. Extracts the needed entries from the open zip archive +// |zip| located at |package|. Stores the command line that should be called into |cmd|. The +// |status_fd| is the file descriptor the child process should use to report back the progress of +// the update. Note that since this applies to the sideloading flow only, it takes one less +// parameter |retry_count| than the non-A/B version. +bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd, + std::vector* cmd); diff --git a/install.cpp b/install/install.cpp similarity index 51% rename from install.cpp rename to install/install.cpp index d058931717..d404997dce 100644 --- a/install.cpp +++ b/install/install.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "install.h" +#include "install/install.h" #include #include @@ -30,11 +30,10 @@ #include #include #include +#include #include #include -#include #include -#include #include #include @@ -45,147 +44,117 @@ #include #include #include -#include -#include +#include -#include "common.h" -#include "otautil/SysUtil.h" -#include "otautil/ThermalUtil.h" +#include "install/package.h" +#include "install/verifier.h" +#include "install/wipe_data.h" #include "otautil/error_code.h" -#include "private/install.h" -#include "roots.h" -#include "ui.h" -#include "verifier.h" +#include "otautil/paths.h" +#include "otautil/sysutil.h" +#include "private/setup_commands.h" +#include "recovery_ui/ui.h" +#include "recovery_utils/roots.h" +#include "recovery_utils/thermalutil.h" using namespace std::chrono_literals; +static constexpr int kRecoveryApiVersion = 3; +// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed +// into target_files.zip. Assert the version defined in code and in Android.mk are consistent. +static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions."); + // Default allocation of progress bar segments to operations static constexpr int VERIFICATION_PROGRESS_TIME = 60; static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25; static std::condition_variable finish_log_temperature; -// This function parses and returns the build.version.incremental -static std::string parse_build_number(const std::string& str) { - size_t pos = str.find('='); - if (pos != std::string::npos) { - return android::base::Trim(str.substr(pos+1)); - } - - LOG(ERROR) << "Failed to parse build number in " << str; - return ""; -} - -bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) { +bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map* metadata) { CHECK(metadata != nullptr); static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata"; - ZipString path(METADATA_PATH); ZipEntry entry; - if (FindEntry(zip, path, &entry) != 0) { + if (FindEntry(zip, METADATA_PATH, &entry) != 0) { LOG(ERROR) << "Failed to find " << METADATA_PATH; return false; } uint32_t length = entry.uncompressed_length; - metadata->resize(length, '\0'); - int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast(&(*metadata)[0]), length); + std::string metadata_string(length, '\0'); + int32_t err = + ExtractToMemory(zip, &entry, reinterpret_cast(&metadata_string[0]), length); if (err != 0) { LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err); return false; } - return true; -} - -// Read the build.version.incremental of src/tgt from the metadata and log it to last_install. -static void read_source_target_build(ZipArchiveHandle zip, std::vector* log_buffer) { - std::string metadata; - if (!read_metadata_from_package(zip, &metadata)) { - return; - } - // Examples of the pre-build and post-build strings in metadata: - // pre-build-incremental=2943039 - // post-build-incremental=2951741 - std::vector lines = android::base::Split(metadata, "\n"); - for (const std::string& line : lines) { - std::string str = android::base::Trim(line); - if (android::base::StartsWith(str, "pre-build-incremental")) { - std::string source_build = parse_build_number(str); - if (!source_build.empty()) { - log_buffer->push_back("source_build: " + source_build); - } - } else if (android::base::StartsWith(str, "post-build-incremental")) { - std::string target_build = parse_build_number(str); - if (!target_build.empty()) { - log_buffer->push_back("target_build: " + target_build); - } - } - } -} - -#ifdef AB_OTA_UPDATER -// Parses the metadata of the OTA package in |zip| and checks whether we are -// allowed to accept this A/B package. Downgrading is not allowed unless -// explicitly enabled in the package and only for incremental packages. -static int check_newer_ab_build(ZipArchiveHandle zip) { - std::string metadata_str; - if (!read_metadata_from_package(zip, &metadata_str)) { - return INSTALL_CORRUPT; - } - std::map metadata; - for (const std::string& line : android::base::Split(metadata_str, "\n")) { + for (const std::string& line : android::base::Split(metadata_string, "\n")) { size_t eq = line.find('='); if (eq != std::string::npos) { - metadata[line.substr(0, eq)] = line.substr(eq + 1); + metadata->emplace(android::base::Trim(line.substr(0, eq)), + android::base::Trim(line.substr(eq + 1))); } } - std::string value = android::base::GetProperty("ro.product.device", ""); - const std::string& pkg_device = metadata["pre-device"]; - if (pkg_device != value || pkg_device.empty()) { - LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value; - return INSTALL_ERROR; + return true; +} + +// Gets the value for the given key in |metadata|. Returns an emtpy string if the key isn't +// present. +static std::string get_value(const std::map& metadata, + const std::string& key) { + const auto& it = metadata.find(key); + return (it == metadata.end()) ? "" : it->second; +} + +static std::string OtaTypeToString(OtaType type) { + switch (type) { + case OtaType::AB: + return "AB"; + case OtaType::BLOCK: + return "BLOCK"; + case OtaType::BRICK: + return "BRICK"; } +} - // We allow the package to not have any serialno; and we also allow it to carry multiple serial - // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the - // verification if the device's serialno doesn't match any of these carried numbers. - value = android::base::GetProperty("ro.serialno", ""); - const std::string& pkg_serial_no = metadata["serialno"]; - if (!pkg_serial_no.empty()) { - bool match = false; - for (const std::string& number : android::base::Split(pkg_serial_no, "|")) { - if (value == android::base::Trim(number)) { - match = true; - break; - } - } - if (!match) { - LOG(ERROR) << "Package is for serial " << pkg_serial_no; - return INSTALL_ERROR; - } +// Read the build.version.incremental of src/tgt from the metadata and log it to last_install. +static void ReadSourceTargetBuild(const std::map& metadata, + std::vector* log_buffer) { + // Examples of the pre-build and post-build strings in metadata: + // pre-build-incremental=2943039 + // post-build-incremental=2951741 + auto source_build = get_value(metadata, "pre-build-incremental"); + if (!source_build.empty()) { + log_buffer->push_back("source_build: " + source_build); } - if (metadata["ota-type"] != "AB") { - LOG(ERROR) << "Package is not A/B"; - return INSTALL_ERROR; + auto target_build = get_value(metadata, "post-build-incremental"); + if (!target_build.empty()) { + log_buffer->push_back("target_build: " + target_build); } +} +// Checks the build version, fingerprint and timestamp in the metadata of the A/B package. +// Downgrading is not allowed unless explicitly enabled in the package and only for +// incremental packages. +static bool CheckAbSpecificMetadata(const std::map& metadata) { // Incremental updates should match the current build. - value = android::base::GetProperty("ro.build.version.incremental", ""); - const std::string& pkg_pre_build = metadata["pre-build-incremental"]; - if (!pkg_pre_build.empty() && pkg_pre_build != value) { - LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value; - return INSTALL_ERROR; + auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", ""); + auto pkg_pre_build = get_value(metadata, "pre-build-incremental"); + if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) { + LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " + << device_pre_build; + return false; } - value = android::base::GetProperty("ro.build.fingerprint", ""); - const std::string& pkg_pre_build_fingerprint = metadata["pre-build"]; - if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) { + auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", ""); + auto pkg_pre_build_fingerprint = get_value(metadata, "pre-build"); + if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) { LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected " - << value; - return INSTALL_ERROR; + << device_fingerprint; + return false; } // Check for downgrade version. @@ -194,42 +163,83 @@ static int check_newer_ab_build(ZipArchiveHandle zip) { int64_t pkg_post_timestamp = 0; // We allow to full update to the same version we are running, in case there // is a problem with the current copy of that version. - if (metadata["post-timestamp"].empty() || - !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) || + auto pkg_post_timestamp_string = get_value(metadata, "post-timestamp"); + if (pkg_post_timestamp_string.empty() || + !android::base::ParseInt(pkg_post_timestamp_string, &pkg_post_timestamp) || pkg_post_timestamp < build_timestamp) { - if (metadata["ota-downgrade"] != "yes") { + if (get_value(metadata, "ota-downgrade") != "yes") { LOG(ERROR) << "Update package is older than the current build, expected a build " "newer than timestamp " << build_timestamp << " but package has timestamp " << pkg_post_timestamp << " and downgrade not allowed."; - return INSTALL_ERROR; + return false; } if (pkg_pre_build_fingerprint.empty()) { LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed."; - return INSTALL_ERROR; + return false; } } - return 0; + return true; } -int update_binary_command(const std::string& package, ZipArchiveHandle zip, - const std::string& binary_path, int /* retry_count */, int status_fd, - std::vector* cmd) { - CHECK(cmd != nullptr); - int ret = check_newer_ab_build(zip); - if (ret != 0) { - return ret; +bool CheckPackageMetadata(const std::map& metadata, OtaType ota_type) { + auto package_ota_type = get_value(metadata, "ota-type"); + auto expected_ota_type = OtaTypeToString(ota_type); + if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) { + LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type; + return true; + } + + if (package_ota_type != expected_ota_type) { + LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual " + << package_ota_type; + return false; + } + + auto device = android::base::GetProperty("ro.product.device", ""); + auto pkg_device = get_value(metadata, "pre-device"); + if (pkg_device != device || pkg_device.empty()) { + LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device; + return false; } + // We allow the package to not have any serialno; and we also allow it to carry multiple serial + // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the + // verification if the device's serialno doesn't match any of these carried numbers. + auto pkg_serial_no = get_value(metadata, "serialno"); + if (!pkg_serial_no.empty()) { + auto device_serial_no = android::base::GetProperty("ro.serialno", ""); + bool serial_number_match = false; + for (const auto& number : android::base::Split(pkg_serial_no, "|")) { + if (device_serial_no == android::base::Trim(number)) { + serial_number_match = true; + } + } + if (!serial_number_match) { + LOG(ERROR) << "Package is for serial " << pkg_serial_no; + return false; + } + } + + if (ota_type == OtaType::AB) { + return CheckAbSpecificMetadata(metadata); + } + + return true; +} + +bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd, + std::vector* cmd) { + CHECK(cmd != nullptr); + // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset // in the zip file. static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt"; - ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES); ZipEntry properties_entry; - if (FindEntry(zip, property_name, &properties_entry) != 0) { + if (FindEntry(zip, AB_OTA_PAYLOAD_PROPERTIES, &properties_entry) != 0) { LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES; - return INSTALL_CORRUPT; + return false; } uint32_t properties_entry_length = properties_entry.uncompressed_length; std::vector payload_properties(properties_entry_length); @@ -237,57 +247,57 @@ int update_binary_command(const std::string& package, ZipArchiveHandle zip, ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length); if (err != 0) { LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err); - return INSTALL_CORRUPT; + return false; } static constexpr const char* AB_OTA_PAYLOAD = "payload.bin"; - ZipString payload_name(AB_OTA_PAYLOAD); ZipEntry payload_entry; - if (FindEntry(zip, payload_name, &payload_entry) != 0) { + if (FindEntry(zip, AB_OTA_PAYLOAD, &payload_entry) != 0) { LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD; - return INSTALL_CORRUPT; + return false; } long payload_offset = payload_entry.offset; *cmd = { - binary_path, + "/system/bin/update_engine_sideload", "--payload=file://" + package, android::base::StringPrintf("--offset=%ld", payload_offset), "--headers=" + std::string(payload_properties.begin(), payload_properties.end()), android::base::StringPrintf("--status_fd=%d", status_fd), }; - return 0; + return true; } -#else // !AB_OTA_UPDATER - -int update_binary_command(const std::string& package, ZipArchiveHandle zip, - const std::string& binary_path, int retry_count, int status_fd, - std::vector* cmd) { +bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count, + int status_fd, std::vector* cmd) { CHECK(cmd != nullptr); - // On traditional updates we extract the update binary from the package. + // In non-A/B updates we extract the update binary from the package. static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary"; - ZipString binary_name(UPDATE_BINARY_NAME); ZipEntry binary_entry; - if (FindEntry(zip, binary_name, &binary_entry) != 0) { + if (FindEntry(zip, UPDATE_BINARY_NAME, &binary_entry) != 0) { LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME; - return INSTALL_CORRUPT; + return false; } + const std::string binary_path = Paths::Get().temporary_update_binary(); unlink(binary_path.c_str()); - int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755); + android::base::unique_fd fd( + open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755)); if (fd == -1) { PLOG(ERROR) << "Failed to create " << binary_path; - return INSTALL_ERROR; + return false; } - int32_t error = ExtractEntryToFile(zip, &binary_entry, fd); - close(fd); - if (error != 0) { + if (auto error = ExtractEntryToFile(zip, &binary_entry, fd); error != 0) { LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error); - return INSTALL_ERROR; + return false; } + // When executing the update binary contained in the package, the arguments passed are: + // - the version number for this interface + // - an FD to which the program can write in order to update the progress bar. + // - the name of the package zip file. + // - an optional argument "retry" if this update is a retry of a failed update attempt. *cmd = { binary_path, std::to_string(kRecoveryApiVersion), @@ -297,9 +307,8 @@ int update_binary_command(const std::string& package, ZipArchiveHandle zip, if (retry_count > 0) { cmd->push_back("retry"); } - return 0; + return true; } -#endif // !AB_OTA_UPDATER static void log_max_temperature(int* max_temperature, const std::atomic& logger_finished) { CHECK(max_temperature != nullptr); @@ -312,89 +321,94 @@ static void log_max_temperature(int* max_temperature, const std::atomic& l } // If the package contains an update binary, extract it and run it. -static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache, - std::vector* log_buffer, int retry_count, - int* max_temperature) { - read_source_target_build(zip, log_buffer); +static InstallResult TryUpdateBinary(Package* package, bool* wipe_cache, + std::vector* log_buffer, int retry_count, + int* max_temperature, RecoveryUI* ui) { + std::map metadata; + auto zip = package->GetZipArchiveHandle(); + if (!ReadMetadataFromPackage(zip, &metadata)) { + LOG(ERROR) << "Failed to parse metadata in the zip file"; + return INSTALL_CORRUPT; + } - int pipefd[2]; - pipe(pipefd); + bool package_is_ab = get_value(metadata, "ota-type") == OtaTypeToString(OtaType::AB); + bool device_supports_ab = android::base::GetBoolProperty("ro.build.ab_update", false); + bool ab_device_supports_nonab = + android::base::GetBoolProperty("ro.virtual_ab.allow_non_ab", false); + bool device_only_supports_ab = device_supports_ab && !ab_device_supports_nonab; - std::vector args; -#ifdef AB_OTA_UPDATER - int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count, - pipefd[1], &args); -#else - int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1], - &args); -#endif - if (ret) { - close(pipefd[0]); - close(pipefd[1]); - log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); - return ret; + if (package_is_ab) { + CHECK(package->GetType() == PackageType::kFile); } - // When executing the update binary contained in the package, the - // arguments passed are: - // - // - the version number for this interface - // - // - an FD to which the program can write in order to update the - // progress bar. The program can write single-line commands: - // - // progress - // fill up the next part of of the progress bar - // over seconds. If is zero, use - // set_progress commands to manually control the - // progress of this segment of the bar. - // - // set_progress - // should be between 0.0 and 1.0; sets the - // progress bar within the segment defined by the most - // recent progress command. + // Verify against the metadata in the package first. Expects A/B metadata if: + // Package declares itself as an A/B package + // Package does not declare itself as an A/B package, but device only supports A/B; + // still calls CheckPackageMetadata to get a meaningful error message. + if (package_is_ab || device_only_supports_ab) { + if (!CheckPackageMetadata(metadata, OtaType::AB)) { + log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); + return INSTALL_ERROR; + } + } + + ReadSourceTargetBuild(metadata, log_buffer); + + // The updater in child process writes to the pipe to communicate with recovery. + android::base::unique_fd pipe_read, pipe_write; + // Explicitly disable O_CLOEXEC using 0 as the flags (last) parameter to Pipe + // so that the child updater process will recieve a non-closed fd. + if (!android::base::Pipe(&pipe_read, &pipe_write, 0)) { + PLOG(ERROR) << "Failed to create pipe for updater-recovery communication"; + return INSTALL_CORRUPT; + } + + // The updater-recovery communication protocol. // - // ui_print - // display on the screen. + // progress + // fill up the next part of of the progress bar over seconds. If is + // zero, use `set_progress` commands to manually control the progress of this segment of the + // bar. // - // wipe_cache - // a wipe of cache will be performed following a successful - // installation. + // set_progress + // should be between 0.0 and 1.0; sets the progress bar within the segment defined by + // the most recent progress command. // - // clear_display - // turn off the text display. + // ui_print + // display on the screen. // - // enable_reboot - // packages can explicitly request that they want the user - // to be able to reboot during installation (useful for - // debugging packages that don't exit). + // wipe_cache + // a wipe of cache will be performed following a successful installation. // - // retry_update - // updater encounters some issue during the update. It requests - // a reboot to retry the same package automatically. + // clear_display + // turn off the text display. // - // log - // updater requests logging the string (e.g. cause of the - // failure). + // enable_reboot + // packages can explicitly request that they want the user to be able to reboot during + // installation (useful for debugging packages that don't exit). // - // - the name of the package zip file. + // retry_update + // updater encounters some issue during the update. It requests a reboot to retry the same + // package automatically. // - // - an optional argument "retry" if this update is a retry of a failed - // update attempt. + // log + // updater requests logging the string (e.g. cause of the failure). // - // Convert the vector to a NULL-terminated char* array suitable for execv. - const char* chr_args[args.size() + 1]; - chr_args[args.size()] = nullptr; - for (size_t i = 0; i < args.size(); i++) { - chr_args[i] = args[i].c_str(); + std::string package_path = package->GetPath(); + + std::vector args; + if (auto setup_result = + package_is_ab + ? SetUpAbUpdateCommands(package_path, zip, pipe_write.get(), &args) + : SetUpNonAbUpdateCommands(package_path, zip, retry_count, pipe_write.get(), &args); + !setup_result) { + log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); + return INSTALL_CORRUPT; } pid_t pid = fork(); - if (pid == -1) { - close(pipefd[0]); - close(pipefd[1]); PLOG(ERROR) << "Failed to fork update binary"; log_buffer->push_back(android::base::StringPrintf("error: %d", kForkUpdateBinaryFailure)); return INSTALL_ERROR; @@ -402,16 +416,18 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b if (pid == 0) { umask(022); - close(pipefd[0]); - execv(chr_args[0], const_cast(chr_args)); - // Bug: 34769056 - // We shouldn't use LOG/PLOG in the forked process, since they may cause - // the child process to hang. This deadlock results from an improperly - // copied mutex in the ui functions. + pipe_read.reset(); + + // Convert the std::string vector to a NULL-terminated char* vector suitable for execv. + auto chr_args = StringVectorToNullTerminatedArray(args); + execv(chr_args[0], chr_args.data()); + // We shouldn't use LOG/PLOG in the forked process, since they may cause the child process to + // hang. This deadlock results from an improperly copied mutex in the ui functions. + // (Bug: 34769056) fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno)); _exit(EXIT_FAILURE); } - close(pipefd[1]); + pipe_write.reset(); std::atomic logger_finished(false); std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished)); @@ -420,7 +436,7 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b bool retry_update = false; char buffer[1024]; - FILE* from_child = fdopen(pipefd[0], "r"); + FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r"); while (fgets(buffer, sizeof(buffer), from_child) != nullptr) { std::string line(buffer); size_t space = line.find_first_of(" \n"); @@ -485,168 +501,73 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b if (retry_update) { return INSTALL_RETRY; } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")"; + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != EXIT_SUCCESS) { + LOG(ERROR) << "Error in " << package_path << " (status " << WEXITSTATUS(status) << ")"; + return INSTALL_ERROR; + } + } else if (WIFSIGNALED(status)) { + LOG(ERROR) << "Error in " << package_path << " (killed by signal " << WTERMSIG(status) << ")"; return INSTALL_ERROR; + } else { + LOG(FATAL) << "Invalid status code " << status; } return INSTALL_SUCCESS; } -// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the -// entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA -// package. -bool verify_package_compatibility(ZipArchiveHandle package_zip) { - LOG(INFO) << "Verifying package compatibility..."; - - static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip"; - ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY); - ZipEntry compatibility_entry; - if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) { - LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry"; - return true; - } - - std::string zip_content(compatibility_entry.uncompressed_length, '\0'); - int32_t ret; - if ((ret = ExtractToMemory(package_zip, &compatibility_entry, - reinterpret_cast(&zip_content[0]), - compatibility_entry.uncompressed_length)) != 0) { - LOG(ERROR) << "Failed to read " << COMPATIBILITY_ZIP_ENTRY << ": " << ErrorCodeString(ret); - return false; - } - - ZipArchiveHandle zip_handle; - ret = OpenArchiveFromMemory(static_cast(const_cast(zip_content.data())), - zip_content.size(), COMPATIBILITY_ZIP_ENTRY, &zip_handle); - if (ret != 0) { - LOG(ERROR) << "Failed to OpenArchiveFromMemory: " << ErrorCodeString(ret); - return false; - } - - // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents. - void* cookie; - ret = StartIteration(zip_handle, &cookie, nullptr, nullptr); - if (ret != 0) { - LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret); - CloseArchive(zip_handle); - return false; - } - std::unique_ptr guard(cookie, EndIteration); - - std::vector compatibility_info; - ZipEntry info_entry; - ZipString info_name; - while (Next(cookie, &info_entry, &info_name) == 0) { - std::string content(info_entry.uncompressed_length, '\0'); - int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast(&content[0]), - info_entry.uncompressed_length); - if (ret != 0) { - LOG(ERROR) << "Failed to read " << info_name.name << ": " << ErrorCodeString(ret); - CloseArchive(zip_handle); - return false; - } - compatibility_info.emplace_back(std::move(content)); - } - CloseArchive(zip_handle); - - // VintfObjectRecovery::CheckCompatibility returns zero on success. - std::string err; - int result = android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err); - if (result == 0) { - return true; - } - - LOG(ERROR) << "Failed to verify package compatibility (result " << result << "): " << err; - return false; -} - -static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount, - std::vector* log_buffer, int retry_count, - int* max_temperature) { +static InstallResult VerifyAndInstallPackage(Package* package, bool* wipe_cache, + std::vector* log_buffer, int retry_count, + int* max_temperature, RecoveryUI* ui) { ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); - ui->Print("Finding update package...\n"); // Give verification half the progress bar... ui->SetProgressType(RecoveryUI::DETERMINATE); ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); - LOG(INFO) << "Update location: " << path; - - // Map the update package into memory. - ui->Print("Opening update package...\n"); - - if (needs_mount) { - if (path[0] == '@') { - ensure_path_mounted(path.substr(1).c_str()); - } else { - ensure_path_mounted(path.c_str()); - } - } - - MemMapping map; - if (!map.MapFile(path)) { - LOG(ERROR) << "failed to map file"; - log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure)); - return INSTALL_CORRUPT; - } // Verify package. - if (!verify_package(map.addr, map.length)) { + if (!verify_package(package, ui)) { log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure)); return INSTALL_CORRUPT; } - // Try to open the package. - ZipArchiveHandle zip; - int err = OpenArchiveFromMemory(map.addr, map.length, path.c_str(), &zip); - if (err != 0) { - LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err); - log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure)); - - CloseArchive(zip); - return INSTALL_CORRUPT; - } - - // Additionally verify the compatibility of the package. - if (!verify_package_compatibility(zip)) { - log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure)); - CloseArchive(zip); - return INSTALL_CORRUPT; - } - // Verify and install the contents of the package. ui->Print("Installing update...\n"); if (retry_count > 0) { ui->Print("Retry attempt: %d\n", retry_count); } ui->SetEnableReboot(false); - int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature); + auto result = TryUpdateBinary(package, wipe_cache, log_buffer, retry_count, max_temperature, ui); ui->SetEnableReboot(true); ui->Print("\n"); - CloseArchive(zip); return result; } -int install_package(const std::string& path, bool* wipe_cache, const std::string& install_file, - bool needs_mount, int retry_count) { - CHECK(!path.empty()); - CHECK(!install_file.empty()); - CHECK(wipe_cache != nullptr); - - modified_flash = true; +InstallResult InstallPackage(Package* package, const std::string_view package_id, + bool should_wipe_cache, int retry_count, RecoveryUI* ui) { auto start = std::chrono::system_clock::now(); int start_temperature = GetMaxValueFromThermalZone(); int max_temperature = start_temperature; - int result; + InstallResult result; std::vector log_buffer; - if (setup_install_mounts() != 0) { + + ui->Print("Supported API: %d\n", kRecoveryApiVersion); + + ui->Print("Finding update package...\n"); + LOG(INFO) << "Update package id: " << package_id; + if (!package) { + log_buffer.push_back(android::base::StringPrintf("error: %d", kMapFileFailure)); + result = INSTALL_CORRUPT; + } else if (setup_install_mounts() != 0) { LOG(ERROR) << "failed to set up expected mounts for install; aborting"; result = INSTALL_ERROR; } else { - result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count, - &max_temperature); + bool updater_wipe_cache = false; + result = VerifyAndInstallPackage(package, &updater_wipe_cache, &log_buffer, retry_count, + &max_temperature, ui); + should_wipe_cache = should_wipe_cache || updater_wipe_cache; } // Measure the time spent to apply OTA update in seconds. @@ -673,7 +594,7 @@ int install_package(const std::string& path, bool* wipe_cache, const std::string // The first two lines need to be the package name and install result. std::vector log_header = { - path, + std::string(package_id), result == INSTALL_SUCCESS ? "1" : "0", "time_total: " + std::to_string(time_total), "retry: " + std::to_string(retry_count), @@ -693,6 +614,7 @@ int install_package(const std::string& path, bool* wipe_cache, const std::string std::string log_content = android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n"; + const std::string& install_file = Paths::Get().temporary_install_file(); if (!android::base::WriteStringToFile(log_content, install_file)) { PLOG(ERROR) << "failed to write " << install_file; } @@ -700,23 +622,28 @@ int install_package(const std::string& path, bool* wipe_cache, const std::string // Write a copy into last_log. LOG(INFO) << log_content; + if (result == INSTALL_SUCCESS && should_wipe_cache) { + if (!WipeCache(ui, nullptr)) { + result = INSTALL_ERROR; + } + } + return result; } -bool verify_package(const unsigned char* package_data, size_t package_size) { - static constexpr const char* PUBLIC_KEYS_FILE = "/res/keys"; - std::vector loadedKeys; - if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) { +bool verify_package(Package* package, RecoveryUI* ui) { + static constexpr const char* CERTIFICATE_ZIP_FILE = "/system/etc/security/otacerts.zip"; + std::vector loaded_keys = LoadKeysFromZipfile(CERTIFICATE_ZIP_FILE); + if (loaded_keys.empty()) { LOG(ERROR) << "Failed to load keys"; return false; } - LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE; + LOG(INFO) << loaded_keys.size() << " key(s) loaded from " << CERTIFICATE_ZIP_FILE; // Verify package. ui->Print("Verifying update package...\n"); auto t0 = std::chrono::system_clock::now(); - int err = verify_file(package_data, package_size, loadedKeys, - std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + int err = verify_file(package, loaded_keys); std::chrono::duration duration = std::chrono::system_clock::now() - t0; ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err); if (err != VERIFY_SUCCESS) { @@ -726,3 +653,49 @@ bool verify_package(const unsigned char* package_data, size_t package_size) { } return true; } + +bool SetupPackageMount(const std::string& package_path, bool* should_use_fuse) { + CHECK(should_use_fuse != nullptr); + + if (package_path.empty()) { + return false; + } + + *should_use_fuse = true; + if (package_path[0] == '@') { + auto block_map_path = package_path.substr(1); + if (ensure_path_mounted(block_map_path) != 0) { + LOG(ERROR) << "Failed to mount " << block_map_path; + return false; + } + // uncrypt only produces block map only if the package stays on /data. + *should_use_fuse = false; + return true; + } + + // Package is not a block map file. + if (ensure_path_mounted(package_path) != 0) { + LOG(ERROR) << "Failed to mount " << package_path; + return false; + } + + // Reject the package if the input path doesn't equal the canonicalized path. + // e.g. /cache/../sdcard/update_package. + std::error_code ec; + auto canonical_path = std::filesystem::canonical(package_path, ec); + if (ec) { + LOG(ERROR) << "Failed to get canonical of " << package_path << ", " << ec.message(); + return false; + } + if (canonical_path.string() != package_path) { + LOG(ERROR) << "Installation aborts. The canonical path " << canonical_path.string() + << " doesn't equal the original path " << package_path; + return false; + } + + constexpr const char* CACHE_ROOT = "/cache"; + if (android::base::StartsWith(package_path, CACHE_ROOT)) { + *should_use_fuse = false; + } + return true; +} diff --git a/install/package.cpp b/install/package.cpp new file mode 100644 index 0000000000..86fc0647d1 --- /dev/null +++ b/install/package.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "install/package.h" + +#include +#include + +#include +#include +#include +#include + +#include "otautil/error_code.h" +#include "otautil/sysutil.h" + +// This class wraps the package in memory, i.e. a memory mapped package, or a package loaded +// to a string/vector. +class MemoryPackage : public Package { + public: + // Constructs the class from a file. We will memory maps the file later. + MemoryPackage(const std::string& path, std::unique_ptr map, + const std::function& set_progress); + + // Constructs the class from the package bytes in |content|. + MemoryPackage(std::vector content, const std::function& set_progress); + + ~MemoryPackage() override; + + PackageType GetType() const override { + return PackageType::kMemory; + } + + // Memory maps the package file if necessary. Initializes the start address and size of the + // package. + uint64_t GetPackageSize() const override { + return package_size_; + } + + std::string GetPath() const override { + return path_; + } + + bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override; + + ZipArchiveHandle GetZipArchiveHandle() override; + + bool UpdateHashAtOffset(const std::vector& hashers, uint64_t start, + uint64_t length) override; + + private: + const uint8_t* addr_; // Start address of the package in memory. + uint64_t package_size_; // Package size in bytes. + + // The memory mapped package. + std::unique_ptr map_; + // A copy of the package content, valid only if we create the class with the exact bytes of + // the package. + std::vector package_content_; + // The physical path to the package, empty if we create the class with the package content. + std::string path_; + + // The ZipArchiveHandle of the package. + ZipArchiveHandle zip_handle_; +}; + +void Package::SetProgress(float progress) { + if (set_progress_) { + set_progress_(progress); + } +} + +class FilePackage : public Package { + public: + FilePackage(android::base::unique_fd&& fd, uint64_t file_size, const std::string& path, + const std::function& set_progress); + + ~FilePackage() override; + + PackageType GetType() const override { + return PackageType::kFile; + } + + uint64_t GetPackageSize() const override { + return package_size_; + } + + std::string GetPath() const override { + return path_; + } + + bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override; + + ZipArchiveHandle GetZipArchiveHandle() override; + + bool UpdateHashAtOffset(const std::vector& hashers, uint64_t start, + uint64_t length) override; + + private: + android::base::unique_fd fd_; // The underlying fd to the open package. + uint64_t package_size_; + std::string path_; // The physical path to the package. + + ZipArchiveHandle zip_handle_; +}; + +std::unique_ptr Package::CreateMemoryPackage( + const std::string& path, const std::function& set_progress) { + std::unique_ptr mmap = std::make_unique(); + if (!mmap->MapFile(path)) { + LOG(ERROR) << "failed to map file"; + return nullptr; + } + + return std::make_unique(path, std::move(mmap), set_progress); +} + +std::unique_ptr Package::CreateFilePackage( + const std::string& path, const std::function& set_progress) { + android::base::unique_fd fd(open(path.c_str(), O_RDONLY)); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << path; + return nullptr; + } + + off64_t file_size = lseek64(fd.get(), 0, SEEK_END); + if (file_size == -1) { + PLOG(ERROR) << "Failed to get the package size"; + return nullptr; + } + + return std::make_unique(std::move(fd), file_size, path, set_progress); +} + +std::unique_ptr Package::CreateMemoryPackage( + std::vector content, const std::function& set_progress) { + return std::make_unique(std::move(content), set_progress); +} + +MemoryPackage::MemoryPackage(const std::string& path, std::unique_ptr map, + const std::function& set_progress) + : map_(std::move(map)), path_(path), zip_handle_(nullptr) { + addr_ = map_->addr; + package_size_ = map_->length; + set_progress_ = set_progress; +} + +MemoryPackage::MemoryPackage(std::vector content, + const std::function& set_progress) + : package_content_(std::move(content)), zip_handle_(nullptr) { + CHECK(!package_content_.empty()); + addr_ = package_content_.data(); + package_size_ = package_content_.size(); + set_progress_ = set_progress; +} + +MemoryPackage::~MemoryPackage() { + if (zip_handle_) { + CloseArchive(zip_handle_); + } +} + +bool MemoryPackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) { + if (byte_count > package_size_ || offset > package_size_ - byte_count) { + LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count + << ", total package_size: " << package_size_; + return false; + } + memcpy(buffer, addr_ + offset, byte_count); + return true; +} + +bool MemoryPackage::UpdateHashAtOffset(const std::vector& hashers, + uint64_t start, uint64_t length) { + if (length > package_size_ || start > package_size_ - length) { + LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length + << ", total package_size: " << package_size_; + return false; + } + + for (const auto& hasher : hashers) { + hasher(addr_ + start, length); + } + return true; +} + +ZipArchiveHandle MemoryPackage::GetZipArchiveHandle() { + if (zip_handle_) { + return zip_handle_; + } + + if (auto err = OpenArchiveFromMemory(const_cast(addr_), package_size_, path_.c_str(), + &zip_handle_); + err != 0) { + LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err); + return nullptr; + } + + return zip_handle_; +} + +FilePackage::FilePackage(android::base::unique_fd&& fd, uint64_t file_size, const std::string& path, + const std::function& set_progress) + : fd_(std::move(fd)), package_size_(file_size), path_(path), zip_handle_(nullptr) { + set_progress_ = set_progress; +} + +FilePackage::~FilePackage() { + if (zip_handle_) { + CloseArchive(zip_handle_); + } +} + +bool FilePackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) { + if (byte_count > package_size_ || offset > package_size_ - byte_count) { + LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count + << ", total package_size: " << package_size_; + return false; + } + + if (!android::base::ReadFullyAtOffset(fd_.get(), buffer, byte_count, offset)) { + PLOG(ERROR) << "Failed to read " << byte_count << " bytes data at offset " << offset; + return false; + } + + return true; +} + +bool FilePackage::UpdateHashAtOffset(const std::vector& hashers, + uint64_t start, uint64_t length) { + if (length > package_size_ || start > package_size_ - length) { + LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length + << ", total package_size: " << package_size_; + return false; + } + + uint64_t so_far = 0; + while (so_far < length) { + uint64_t read_size = std::min(length - so_far, 16 * MiB); + std::vector buffer(read_size); + if (!ReadFullyAtOffset(buffer.data(), read_size, start + so_far)) { + return false; + } + + for (const auto& hasher : hashers) { + hasher(buffer.data(), read_size); + } + so_far += read_size; + } + + return true; +} + +ZipArchiveHandle FilePackage::GetZipArchiveHandle() { + if (zip_handle_) { + return zip_handle_; + } + + if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_, false); err != 0) { + LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err); + return nullptr; + } + + return zip_handle_; +} diff --git a/install/snapshot_utils.cpp b/install/snapshot_utils.cpp new file mode 100644 index 0000000000..7235e67c83 --- /dev/null +++ b/install/snapshot_utils.cpp @@ -0,0 +1,74 @@ + +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" +#include "recovery_utils/roots.h" + +using android::snapshot::CreateResult; +using android::snapshot::SnapshotManager; + +bool FinishPendingSnapshotMerges(Device* device) { + if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) { + return true; + } + + RecoveryUI* ui = device->GetUI(); + auto sm = SnapshotManager::NewForFirstStageMount(); + if (!sm) { + ui->Print("Could not create SnapshotManager.\n"); + return false; + } + + auto callback = [&]() -> void { + double progress; + sm->GetUpdateState(&progress); + ui->Print("Waiting for merge to complete: %.2f\n", progress); + }; + if (!sm->HandleImminentDataWipe(callback)) { + ui->Print("Unable to check merge status and/or complete update merge.\n"); + return false; + } + return true; +} + +bool CreateSnapshotPartitions() { + if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) { + // If the device does not support Virtual A/B, there's no need to create + // snapshot devices. + return true; + } + + auto sm = SnapshotManager::NewForFirstStageMount(); + if (!sm) { + // SnapshotManager could not be created. The device is still in a + // consistent state and can continue with the mounting of the existing + // devices, but cannot initialize snapshot devices. + LOG(WARNING) << "Could not create SnapshotManager"; + return true; + } + + auto ret = sm->RecoveryCreateSnapshotDevices(); + if (ret == CreateResult::ERROR) { + return false; + } + return true; +} diff --git a/install/verifier.cpp b/install/verifier.cpp new file mode 100644 index 0000000000..ab750442df --- /dev/null +++ b/install/verifier.cpp @@ -0,0 +1,466 @@ +/* + * 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 "install/verifier.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "otautil/print_sha1.h" +#include "private/asn1_decoder.h" + +/* + * Simple version of PKCS#7 SignedData extraction. This extracts the + * signature OCTET STRING to be used for signature verification. + * + * For full details, see http://www.ietf.org/rfc/rfc3852.txt + * + * The PKCS#7 structure looks like: + * + * SEQUENCE (ContentInfo) + * OID (ContentType) + * [0] (content) + * SEQUENCE (SignedData) + * INTEGER (version CMSVersion) + * SET (DigestAlgorithmIdentifiers) + * SEQUENCE (EncapsulatedContentInfo) + * [0] (CertificateSet OPTIONAL) + * [1] (RevocationInfoChoices OPTIONAL) + * SET (SignerInfos) + * SEQUENCE (SignerInfo) + * INTEGER (CMSVersion) + * SEQUENCE (SignerIdentifier) + * SEQUENCE (DigestAlgorithmIdentifier) + * SEQUENCE (SignatureAlgorithmIdentifier) + * OCTET STRING (SignatureValue) + */ +static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len, + std::vector* sig_der) { + CHECK(sig_der != nullptr); + sig_der->clear(); + + asn1_context ctx(pkcs7_der, pkcs7_der_len); + + std::unique_ptr pkcs7_seq(ctx.asn1_sequence_get()); + if (pkcs7_seq == nullptr || !pkcs7_seq->asn1_sequence_next()) { + return false; + } + + std::unique_ptr signed_data_app(pkcs7_seq->asn1_constructed_get()); + if (signed_data_app == nullptr) { + return false; + } + + std::unique_ptr signed_data_seq(signed_data_app->asn1_sequence_get()); + if (signed_data_seq == nullptr || !signed_data_seq->asn1_sequence_next() || + !signed_data_seq->asn1_sequence_next() || !signed_data_seq->asn1_sequence_next() || + !signed_data_seq->asn1_constructed_skip_all()) { + return false; + } + + std::unique_ptr sig_set(signed_data_seq->asn1_set_get()); + if (sig_set == nullptr) { + return false; + } + + std::unique_ptr sig_seq(sig_set->asn1_sequence_get()); + if (sig_seq == nullptr || !sig_seq->asn1_sequence_next() || !sig_seq->asn1_sequence_next() || + !sig_seq->asn1_sequence_next() || !sig_seq->asn1_sequence_next()) { + return false; + } + + const uint8_t* sig_der_ptr; + size_t sig_der_length; + if (!sig_seq->asn1_octet_string_get(&sig_der_ptr, &sig_der_length)) { + return false; + } + + sig_der->resize(sig_der_length); + std::copy(sig_der_ptr, sig_der_ptr + sig_der_length, sig_der->begin()); + return true; +} + +int verify_file(VerifierInterface* package, const std::vector& keys) { + CHECK(package); + package->SetProgress(0.0); + + // An archive with a whole-file signature will end in six bytes: + // + // (2-byte signature start) $ff $ff (2-byte comment size) + // + // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by + // reading this footer, this tells us how far back from the end we have to start reading to find + // the whole comment. + +#define FOOTER_SIZE 6 + uint64_t length = package->GetPackageSize(); + + if (length < FOOTER_SIZE) { + LOG(ERROR) << "not big enough to contain footer"; + return VERIFY_FAILURE; + } + + uint8_t footer[FOOTER_SIZE]; + if (!package->ReadFullyAtOffset(footer, FOOTER_SIZE, length - FOOTER_SIZE)) { + LOG(ERROR) << "Failed to read footer"; + return VERIFY_FAILURE; + } + + if (footer[2] != 0xff || footer[3] != 0xff) { + LOG(ERROR) << "footer is wrong"; + return VERIFY_FAILURE; + } + + size_t comment_size = footer[4] + (footer[5] << 8); + size_t signature_start = footer[0] + (footer[1] << 8); + LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start + << " bytes from end"; + + if (signature_start > comment_size) { + LOG(ERROR) << "signature start: " << signature_start + << " is larger than comment size: " << comment_size; + return VERIFY_FAILURE; + } + + if (signature_start <= FOOTER_SIZE) { + LOG(ERROR) << "Signature start is in the footer"; + return VERIFY_FAILURE; + } + +#define EOCD_HEADER_SIZE 22 + + // The end-of-central-directory record is 22 bytes plus any comment length. + size_t eocd_size = comment_size + EOCD_HEADER_SIZE; + + if (length < eocd_size) { + LOG(ERROR) << "not big enough to contain EOCD"; + return VERIFY_FAILURE; + } + + // Determine how much of the file is covered by the signature. This is everything except the + // signature data and length, which includes all of the EOCD except for the comment length field + // (2 bytes) and the comment data. + uint64_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2; + + uint8_t eocd[eocd_size]; + if (!package->ReadFullyAtOffset(eocd, eocd_size, length - eocd_size)) { + LOG(ERROR) << "Failed to read EOCD of " << eocd_size << " bytes"; + return VERIFY_FAILURE; + } + + // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06. + if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) { + LOG(ERROR) << "signature length doesn't match EOCD marker"; + return VERIFY_FAILURE; + } + + for (size_t i = 4; i < eocd_size - 3; ++i) { + if (eocd[i] == 0x50 && eocd[i + 1] == 0x4b && eocd[i + 2] == 0x05 && eocd[i + 3] == 0x06) { + // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will + // find the later (wrong) one, which could be exploitable. Fail the verification if this + // sequence occurs anywhere after the real one. + LOG(ERROR) << "EOCD marker occurs after start of EOCD"; + return VERIFY_FAILURE; + } + } + + bool need_sha1 = false; + bool need_sha256 = false; + for (const auto& key : keys) { + switch (key.hash_len) { + case SHA_DIGEST_LENGTH: + need_sha1 = true; + break; + case SHA256_DIGEST_LENGTH: + need_sha256 = true; + break; + } + } + + SHA_CTX sha1_ctx; + SHA256_CTX sha256_ctx; + SHA1_Init(&sha1_ctx); + SHA256_Init(&sha256_ctx); + + std::vector hashers; + if (need_sha1) { + hashers.emplace_back( + std::bind(&SHA1_Update, &sha1_ctx, std::placeholders::_1, std::placeholders::_2)); + } + if (need_sha256) { + hashers.emplace_back( + std::bind(&SHA256_Update, &sha256_ctx, std::placeholders::_1, std::placeholders::_2)); + } + + double frac = -1.0; + uint64_t so_far = 0; + while (so_far < signed_len) { + // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a 1196MiB full OTA and + // 60% for an 89MiB incremental OTA. http://b/28135231. + uint64_t read_size = std::min(signed_len - so_far, 16 * MiB); + package->UpdateHashAtOffset(hashers, so_far, read_size); + so_far += read_size; + + double f = so_far / static_cast(signed_len); + if (f > frac + 0.02 || read_size == so_far) { + package->SetProgress(f); + frac = f; + } + } + + uint8_t sha1[SHA_DIGEST_LENGTH]; + SHA1_Final(sha1, &sha1_ctx); + uint8_t sha256[SHA256_DIGEST_LENGTH]; + SHA256_Final(sha256, &sha256_ctx); + + const uint8_t* signature = eocd + eocd_size - signature_start; + size_t signature_size = signature_start - FOOTER_SIZE; + + LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start) + << ", length: " << signature_size << "): " << print_hex(signature, signature_size); + + std::vector sig_der; + if (!read_pkcs7(signature, signature_size, &sig_der)) { + LOG(ERROR) << "Could not find signature DER block"; + return VERIFY_FAILURE; + } + + // Check to make sure at least one of the keys matches the signature. Since any key can match, + // we need to try each before determining a verification failure has happened. + size_t i = 0; + for (const auto& key : keys) { + const uint8_t* hash; + int hash_nid; + switch (key.hash_len) { + case SHA_DIGEST_LENGTH: + hash = sha1; + hash_nid = NID_sha1; + break; + case SHA256_DIGEST_LENGTH: + hash = sha256; + hash_nid = NID_sha256; + break; + default: + continue; + } + + // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends + // after the signature itself. + if (key.key_type == Certificate::KEY_TYPE_RSA) { + if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der.data(), sig_der.size(), + key.rsa.get())) { + LOG(INFO) << "failed to verify against RSA key " << i; + continue; + } + + LOG(INFO) << "whole-file signature verified against RSA key " << i; + return VERIFY_SUCCESS; + } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) { + if (!ECDSA_verify(0, hash, key.hash_len, sig_der.data(), sig_der.size(), key.ec.get())) { + LOG(INFO) << "failed to verify against EC key " << i; + continue; + } + + LOG(INFO) << "whole-file signature verified against EC key " << i; + return VERIFY_SUCCESS; + } else { + LOG(INFO) << "Unknown key type " << key.key_type; + } + i++; + } + + if (need_sha1) { + LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH); + } + if (need_sha256) { + LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH); + } + LOG(ERROR) << "failed to verify whole-file signature"; + return VERIFY_FAILURE; +} + +static std::vector IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) { + void* cookie; + int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem"); + if (iter_status != 0) { + LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: " + << ErrorCodeString(iter_status); + return {}; + } + + std::vector result; + + std::string_view name; + ZipEntry entry; + while ((iter_status = Next(cookie, &entry, &name)) == 0) { + std::vector pem_content(entry.uncompressed_length); + if (int32_t extract_status = + ExtractToMemory(handle, &entry, pem_content.data(), pem_content.size()); + extract_status != 0) { + LOG(ERROR) << "Failed to extract " << name; + return {}; + } + + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + // Aborts the parsing if we fail to load one of the key file. + if (!LoadCertificateFromBuffer(pem_content, &cert)) { + LOG(ERROR) << "Failed to load keys from " << name; + return {}; + } + + result.emplace_back(std::move(cert)); + } + + if (iter_status != -1) { + LOG(ERROR) << "Error while iterating over zip entries: " << ErrorCodeString(iter_status); + return {}; + } + + return result; +} + +std::vector LoadKeysFromZipfile(const std::string& zip_name) { + ZipArchiveHandle handle; + if (int32_t open_status = OpenArchive(zip_name.c_str(), &handle); open_status != 0) { + LOG(ERROR) << "Failed to open " << zip_name << ": " << ErrorCodeString(open_status); + return {}; + } + + std::vector result = IterateZipEntriesAndSearchForKeys(handle); + CloseArchive(handle); + return result; +} + +bool CheckRSAKey(const std::unique_ptr& rsa) { + if (!rsa) { + return false; + } + + const BIGNUM* out_n; + const BIGNUM* out_e; + RSA_get0_key(rsa.get(), &out_n, &out_e, nullptr /* private exponent */); + auto modulus_bits = BN_num_bits(out_n); + if (modulus_bits != 2048 && modulus_bits != 4096) { + LOG(ERROR) << "Modulus should be 2048 or 4096 bits long, actual: " << modulus_bits; + return false; + } + + BN_ULONG exponent = BN_get_word(out_e); + if (exponent != 3 && exponent != 65537) { + LOG(ERROR) << "Public exponent should be 3 or 65537, actual: " << exponent; + return false; + } + + return true; +} + +bool CheckECKey(const std::unique_ptr& ec_key) { + if (!ec_key) { + return false; + } + + const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key.get()); + if (!ec_group) { + LOG(ERROR) << "Failed to get the ec_group from the ec_key"; + return false; + } + auto degree = EC_GROUP_get_degree(ec_group); + if (degree != 256) { + LOG(ERROR) << "Field size of the ec key should be 256 bits long, actual: " << degree; + return false; + } + + return true; +} + +bool LoadCertificateFromBuffer(const std::vector& pem_content, Certificate* cert) { + std::unique_ptr content( + BIO_new_mem_buf(pem_content.data(), pem_content.size()), BIO_free); + + std::unique_ptr x509( + PEM_read_bio_X509(content.get(), nullptr, nullptr, nullptr), X509_free); + if (!x509) { + LOG(ERROR) << "Failed to read x509 certificate"; + return false; + } + + int nid = X509_get_signature_nid(x509.get()); + switch (nid) { + // SignApk has historically accepted md5WithRSA certificates, but treated them as + // sha1WithRSA anyway. Continue to do so for backwards compatibility. + case NID_md5WithRSA: + case NID_md5WithRSAEncryption: + case NID_sha1WithRSA: + case NID_sha1WithRSAEncryption: + cert->hash_len = SHA_DIGEST_LENGTH; + break; + case NID_sha256WithRSAEncryption: + case NID_ecdsa_with_SHA256: + cert->hash_len = SHA256_DIGEST_LENGTH; + break; + default: + LOG(ERROR) << "Unrecognized signature nid " << OBJ_nid2ln(nid); + return false; + } + + std::unique_ptr public_key(X509_get_pubkey(x509.get()), + EVP_PKEY_free); + if (!public_key) { + LOG(ERROR) << "Failed to extract the public key from x509 certificate"; + return false; + } + + int key_type = EVP_PKEY_id(public_key.get()); + if (key_type == EVP_PKEY_RSA) { + cert->key_type = Certificate::KEY_TYPE_RSA; + cert->ec.reset(); + cert->rsa.reset(EVP_PKEY_get1_RSA(public_key.get())); + if (!cert->rsa || !CheckRSAKey(cert->rsa)) { + LOG(ERROR) << "Failed to validate the rsa key info from public key"; + return false; + } + } else if (key_type == EVP_PKEY_EC) { + cert->key_type = Certificate::KEY_TYPE_EC; + cert->rsa.reset(); + cert->ec.reset(EVP_PKEY_get1_EC_KEY(public_key.get())); + if (!cert->ec || !CheckECKey(cert->ec)) { + LOG(ERROR) << "Failed to validate the ec key info from the public key"; + return false; + } + } else { + LOG(ERROR) << "Unrecognized public key type " << OBJ_nid2ln(key_type); + return false; + } + + return true; +} diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp new file mode 100644 index 0000000000..287208583a --- /dev/null +++ b/install/wipe_data.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "install/wipe_data.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "install/snapshot_utils.h" +#include "otautil/dirutil.h" +#include "recovery_ui/ui.h" +#include "recovery_utils/logging.h" +#include "recovery_utils/roots.h" + +constexpr const char* CACHE_ROOT = "/cache"; +constexpr const char* DATA_ROOT = "/data"; +constexpr const char* METADATA_ROOT = "/metadata"; + +static bool EraseVolume(const char* volume, RecoveryUI* ui, bool convert_fbe) { + bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); + bool is_data = (strcmp(volume, DATA_ROOT) == 0); + + ui->SetBackground(RecoveryUI::ERASING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); + + std::vector log_files; + if (is_cache) { + // If we're reformatting /cache, we load any past logs (i.e. "/cache/recovery/last_*") and the + // current log ("/cache/recovery/log") into memory, so we can restore them after the reformat. + log_files = ReadLogFilesToMemory(); + } + + ui->Print("Formatting %s...\n", volume); + + ensure_path_unmounted(volume); + + int result; + if (is_data && convert_fbe) { + constexpr const char* CONVERT_FBE_DIR = "/tmp/convert_fbe"; + constexpr const char* CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe"; + // Create convert_fbe breadcrumb file to signal init to convert to file based encryption, not + // full disk encryption. + if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { + PLOG(ERROR) << "Failed to mkdir " << CONVERT_FBE_DIR; + return false; + } + FILE* f = fopen(CONVERT_FBE_FILE, "wbe"); + if (!f) { + PLOG(ERROR) << "Failed to convert to file encryption"; + return false; + } + fclose(f); + result = format_volume(volume, CONVERT_FBE_DIR); + remove(CONVERT_FBE_FILE); + rmdir(CONVERT_FBE_DIR); + } else { + result = format_volume(volume); + } + + if (is_cache) { + RestoreLogFilesAfterFormat(log_files); + } + + return (result == 0); +} + +bool WipeCache(RecoveryUI* ui, const std::function& confirm_func) { + bool has_cache = volume_for_mount_point("/cache") != nullptr; + if (!has_cache) { + ui->Print("No /cache partition found.\n"); + return false; + } + + if (confirm_func && !confirm_func()) { + return false; + } + + ui->Print("\n-- Wiping cache...\n"); + bool success = EraseVolume("/cache", ui, false); + ui->Print("Cache wipe %s.\n", success ? "complete" : "failed"); + return success; +} + +bool WipeData(Device* device, bool convert_fbe) { + RecoveryUI* ui = device->GetUI(); + ui->Print("\n-- Wiping data...\n"); + + if (!FinishPendingSnapshotMerges(device)) { + ui->Print("Unable to check update status or complete merge, cannot wipe partitions.\n"); + return false; + } + + bool success = device->PreWipeData(); + if (success) { + success &= EraseVolume(DATA_ROOT, ui, convert_fbe); + bool has_cache = volume_for_mount_point("/cache") != nullptr; + if (has_cache) { + success &= EraseVolume(CACHE_ROOT, ui, false); + } + if (volume_for_mount_point(METADATA_ROOT) != nullptr) { + success &= EraseVolume(METADATA_ROOT, ui, false); + } + } + if (success) { + success &= device->PostWipeData(); + } + ui->Print("Data wipe %s.\n", success ? "complete" : "failed"); + return success; +} diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp new file mode 100644 index 0000000000..89d5d31a3f --- /dev/null +++ b/install/wipe_device.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "install/wipe_device.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "bootloader_message/bootloader_message.h" +#include "install/install.h" +#include "install/package.h" +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +std::vector GetWipePartitionList(Package* wipe_package) { + ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle(); + if (!zip) { + LOG(ERROR) << "Failed to get ZipArchiveHandle"; + return {}; + } + + constexpr char RECOVERY_WIPE_ENTRY_NAME[] = "recovery.wipe"; + + std::string partition_list_content; + ZipEntry entry; + if (FindEntry(zip, RECOVERY_WIPE_ENTRY_NAME, &entry) == 0) { + uint32_t length = entry.uncompressed_length; + partition_list_content = std::string(length, '\0'); + if (auto err = ExtractToMemory( + zip, &entry, reinterpret_cast(partition_list_content.data()), length); + err != 0) { + LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": " + << ErrorCodeString(err); + return {}; + } + } else { + LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME + << ", falling back to use the partition list on device."; + + constexpr char RECOVERY_WIPE_ON_DEVICE[] = "/etc/recovery.wipe"; + if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) { + PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\""; + return {}; + } + } + + std::vector result; + auto lines = android::base::Split(partition_list_content, "\n"); + for (const auto& line : lines) { + auto partition = android::base::Trim(line); + // Ignore '#' comment or empty lines. + if (android::base::StartsWith(partition, "#") || partition.empty()) { + continue; + } + result.push_back(line); + } + + return result; +} + +// Secure-wipes a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with +// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT. +static bool SecureWipePartition(const std::string& partition) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open \"" << partition << "\""; + return false; + } + + uint64_t range[2] = { 0, 0 }; + if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { + PLOG(ERROR) << "Failed to get partition size"; + return false; + } + LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1]; + + LOG(INFO) << " Trying BLKSECDISCARD..."; + if (ioctl(fd, BLKSECDISCARD, &range) == -1) { + PLOG(WARNING) << " Failed"; + + // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. + unsigned int zeroes; + if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { + LOG(INFO) << " Trying BLKDISCARD..."; + if (ioctl(fd, BLKDISCARD, &range) == -1) { + PLOG(ERROR) << " Failed"; + return false; + } + } else { + LOG(INFO) << " Trying BLKZEROOUT..."; + if (ioctl(fd, BLKZEROOUT, &range) == -1) { + PLOG(ERROR) << " Failed"; + return false; + } + } + } + + LOG(INFO) << " Done"; + return true; +} + +static std::unique_ptr ReadWipePackage(size_t wipe_package_size) { + if (wipe_package_size == 0) { + LOG(ERROR) << "wipe_package_size is zero"; + return nullptr; + } + + std::string wipe_package; + if (std::string err_str; !read_wipe_package(&wipe_package, wipe_package_size, &err_str)) { + PLOG(ERROR) << "Failed to read wipe package" << err_str; + return nullptr; + } + + return Package::CreateMemoryPackage( + std::vector(wipe_package.begin(), wipe_package.end()), nullptr); +} + +// Checks if the wipe package matches expectation. If the check passes, reads the list of +// partitions to wipe from the package. Checks include +// 1. verify the package. +// 2. check metadata (ota-type, pre-device and serial number if having one). +static bool CheckWipePackage(Package* wipe_package, RecoveryUI* ui) { + if (!verify_package(wipe_package, ui)) { + LOG(ERROR) << "Failed to verify package"; + return false; + } + + ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle(); + if (!zip) { + LOG(ERROR) << "Failed to get ZipArchiveHandle"; + return false; + } + + std::map metadata; + if (!ReadMetadataFromPackage(zip, &metadata)) { + LOG(ERROR) << "Failed to parse metadata in the zip file"; + return false; + } + + return CheckPackageMetadata(metadata, OtaType::BRICK); +} + +bool WipeAbDevice(Device* device, size_t wipe_package_size) { + auto ui = device->GetUI(); + ui->SetBackground(RecoveryUI::ERASING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); + + auto wipe_package = ReadWipePackage(wipe_package_size); + if (!wipe_package) { + LOG(ERROR) << "Failed to open wipe package"; + return false; + } + + if (!CheckWipePackage(wipe_package.get(), ui)) { + LOG(ERROR) << "Failed to verify wipe package"; + return false; + } + + auto partition_list = GetWipePartitionList(wipe_package.get()); + if (partition_list.empty()) { + LOG(ERROR) << "Empty wipe ab partition list"; + return false; + } + + for (const auto& partition : partition_list) { + // Proceed anyway even if it fails to wipe some partition. + SecureWipePartition(partition); + } + return true; +} diff --git a/minadbd/Android.bp b/minadbd/Android.bp new file mode 100644 index 0000000000..4cdcac6d98 --- /dev/null +++ b/minadbd/Android.bp @@ -0,0 +1,138 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_defaults { + name: "minadbd_defaults", + + cflags: [ + "-DADB_HOST=0", + "-Wall", + "-Werror", + ], + + cpp_std: "experimental", + + include_dirs: [ + "system/core/adb", + ], + + header_libs: [ + "libminadbd_headers", + ], +} + +// `libminadbd_services` is analogous to the `libadbd_services` for regular `adbd`, but providing +// the sideload service only. +cc_library_static { + name: "libminadbd_services", + recovery_available: true, + + defaults: [ + "minadbd_defaults", + "librecovery_utils_defaults", + ], + + srcs: [ + "fuse_adb_provider.cpp", + "minadbd_services.cpp", + ], + + static_libs: [ + "librecovery_utils", + "libotautil", + ], + + shared_libs: [ + "libadbd", + "libbase", + "libcrypto", + "libfusesideload", + ], +} + +cc_library_headers { + name: "libminadbd_headers", + recovery_available: true, + export_include_dirs: [ + "include", + ], + // adb_install.cpp + visibility: [ + "//bootable/recovery/install", + ], +} + +cc_binary { + name: "minadbd", + recovery: true, + + defaults: [ + "minadbd_defaults", + "libadbd_binary_dependencies", + "librecovery_utils_defaults", + ], + + srcs: [ + "minadbd.cpp", + ], + + shared_libs: [ + "libbase", + "libcrypto", + ], + + static_libs: [ + "libminadbd_services", + "libfusesideload", + "librecovery_utils", + ], + + required: [ + "adbd_system_api_recovery", + ] +} + +cc_test { + name: "minadbd_test", + isolated: true, + + defaults: [ + "minadbd_defaults", + "librecovery_utils_defaults", + "libadbd_binary_dependencies", + ], + + srcs: [ + "fuse_adb_provider_test.cpp", + "minadbd_services_test.cpp", + ], + + static_libs: [ + "libminadbd_services", + "libfusesideload", + "librecovery_utils", + "libotautil", + ], + + shared_libs: [ + "libbase", + "libcrypto", + "libcutils", + "liblog", + ], + + test_suites: [ + "device-tests", + ], +} diff --git a/minadbd/Android.mk b/minadbd/Android.mk deleted file mode 100644 index 50e3b34ef5..0000000000 --- a/minadbd/Android.mk +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2005 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) - -minadbd_cflags := \ - -Wall -Werror \ - -DADB_HOST=0 \ - -# libminadbd (static library) -# =============================== -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - fuse_adb_provider.cpp \ - minadbd.cpp \ - minadbd_services.cpp \ - -LOCAL_MODULE := libminadbd -LOCAL_CFLAGS := $(minadbd_cflags) -LOCAL_C_INCLUDES := bootable/recovery system/core/adb -LOCAL_WHOLE_STATIC_LIBRARIES := libadbd -LOCAL_STATIC_LIBRARIES := libcrypto libbase - -include $(BUILD_STATIC_LIBRARY) - -# minadbd_test (native test) -# =============================== -include $(CLEAR_VARS) - -LOCAL_MODULE := minadbd_test -LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_SRC_FILES := fuse_adb_provider_test.cpp -LOCAL_CFLAGS := $(minadbd_cflags) -LOCAL_C_INCLUDES := $(LOCAL_PATH) system/core/adb -LOCAL_STATIC_LIBRARIES := \ - libBionicGtestMain \ - libminadbd -LOCAL_SHARED_LIBRARIES := \ - liblog \ - libbase \ - libcutils - -include $(BUILD_NATIVE_TEST) diff --git a/minadbd/README.md b/minadbd/README.md index 5a0a067def..9a1958309f 100644 --- a/minadbd/README.md +++ b/minadbd/README.md @@ -1,8 +1,24 @@ -minadbd is now mostly built from libadbd. The fuse features are unique to -minadbd, and services.c has been modified as follows: - - - all services removed - - all host mode support removed - - `sideload_service()` added; this is the only service supported. It - receives a single blob of data, writes it to a fixed filename, and - makes the process exit. +minadbd +======= + +`minadbd` is analogous to the regular `adbd`, but providing the minimal services to support +recovery-specific use cases. Generally speaking, `adbd` = `libadbd` + `libadbd_services`, whereas +`minadbd` = `libadbd` + `libminadbd_services`. + +Although both modules may be installed into the recovery image, only one of them, or none, can be +active at any given time. + +- The start / stop of `adbd` is managed via system property `sys.usb.config`, when setting to `adb` + or `none` respectively. Upon starting recovery mode, `adbd` is started in debuggable builds by + default; otherwise `adbd` will stay off at all times in user builds. See the triggers in + `bootable/recovery/etc/init.rc`. + +- `minadbd` is started by `recovery` as needed. + - When requested to start `minadbd`, `recovery` stops `adbd` first, if it's running; it then forks + and execs `minadbd` in a separate process. + - `minadbd` talks to host-side `adb` server to get user requests. + - `minadbd` handles some requests directly, e.g. querying device properties for rescue service. + - `minadbd` communicates with `recovery` to fulfill requests regarding package installation. See + the comments in `bootable/recovery/install/adb_install.cpp` for the IPC protocol between + `recovery` and `minadbd`. + - Upon exiting `minadbd`, `recovery` restarts `adbd` if it was previously running. diff --git a/minadbd/fuse_adb_provider.cpp b/minadbd/fuse_adb_provider.cpp index 9bd3f23929..47719b07ae 100644 --- a/minadbd/fuse_adb_provider.cpp +++ b/minadbd/fuse_adb_provider.cpp @@ -18,39 +18,22 @@ #include #include -#include #include -#include - #include "adb.h" #include "adb_io.h" -#include "fuse_sideload.h" -int read_block_adb(const adb_data& ad, uint32_t block, uint8_t* buffer, uint32_t fetch_size) { - if (!WriteFdFmt(ad.sfd, "%08u", block)) { +bool FuseAdbDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const { + if (!WriteFdFmt(fd_, "%08u", start_block)) { fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno)); - return -EIO; + return false; } - if (!ReadFdExactly(ad.sfd, buffer, fetch_size)) { + if (!ReadFdExactly(fd_, buffer, fetch_size)) { fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno)); - return -EIO; + return false; } - return 0; -} - -int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size) { - adb_data ad; - ad.sfd = sfd; - ad.file_size = file_size; - ad.block_size = block_size; - - provider_vtab vtab; - vtab.read_block = std::bind(read_block_adb, ad, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3); - vtab.close = [&ad]() { WriteFdExactly(ad.sfd, "DONEDONE"); }; - - return run_fuse_sideload(vtab, file_size, block_size); + return true; } diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h index 36d86d5395..43c07d28ee 100644 --- a/minadbd/fuse_adb_provider.h +++ b/minadbd/fuse_adb_provider.h @@ -14,19 +14,26 @@ * limitations under the License. */ -#ifndef __FUSE_ADB_PROVIDER_H -#define __FUSE_ADB_PROVIDER_H +#pragma once #include -struct adb_data { - int sfd; // file descriptor for the adb channel +#include "fuse_provider.h" - uint64_t file_size; - uint32_t block_size; -}; +// This class reads data from adb server. +class FuseAdbDataProvider : public FuseDataProvider { + public: + FuseAdbDataProvider(int fd, uint64_t file_size, uint32_t block_size) + : FuseDataProvider(file_size, block_size), fd_(fd) {} + + bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const override; -int read_block_adb(const adb_data& ad, uint32_t block, uint8_t* buffer, uint32_t fetch_size); -int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size); + bool Valid() const override { + return fd_ != -1; + } -#endif + private: + // The underlying source to read data from (i.e. the one that talks to the host). + int fd_; +}; diff --git a/minadbd/fuse_adb_provider_test.cpp b/minadbd/fuse_adb_provider_test.cpp index 00250e505d..0b097129ea 100644 --- a/minadbd/fuse_adb_provider_test.cpp +++ b/minadbd/fuse_adb_provider_test.cpp @@ -21,19 +21,19 @@ #include +#include #include #include "adb_io.h" #include "fuse_adb_provider.h" TEST(fuse_adb_provider, read_block_adb) { - adb_data data = {}; - int sockets[2]; + android::base::unique_fd device_socket; + android::base::unique_fd host_socket; - ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)); - data.sfd = sockets[0]; + ASSERT_TRUE(android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &device_socket, &host_socket)); + FuseAdbDataProvider data(std::move(device_socket), 0, 0); - int host_socket = sockets[1]; fcntl(host_socket, F_SETFL, O_NONBLOCK); const char expected_data[] = "foobar"; @@ -46,8 +46,8 @@ TEST(fuse_adb_provider, read_block_adb) { uint32_t block = 1234U; const char expected_block[] = "00001234"; - ASSERT_EQ(0, read_block_adb(data, block, reinterpret_cast(block_data), - sizeof(expected_data) - 1)); + ASSERT_TRUE(data.ReadBlockAlignedData(reinterpret_cast(block_data), + sizeof(expected_data) - 1, block)); // Check that read_block_adb requested the right block. char block_req[sizeof(expected_block)] = {}; @@ -65,26 +65,21 @@ TEST(fuse_adb_provider, read_block_adb) { errno = 0; ASSERT_EQ(-1, read(host_socket, &tmp, 1)); ASSERT_EQ(EWOULDBLOCK, errno); - - close(sockets[0]); - close(sockets[1]); } TEST(fuse_adb_provider, read_block_adb_fail_write) { - adb_data data = {}; - int sockets[2]; + android::base::unique_fd device_socket; + android::base::unique_fd host_socket; - ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)); - data.sfd = sockets[0]; + ASSERT_TRUE(android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &device_socket, &host_socket)); + FuseAdbDataProvider data(std::move(device_socket), 0, 0); - ASSERT_EQ(0, close(sockets[1])); + host_socket.reset(); // write(2) raises SIGPIPE since the reading end has been closed. Ignore the signal to avoid // failing the test. signal(SIGPIPE, SIG_IGN); char buf[1]; - ASSERT_EQ(-EIO, read_block_adb(data, 0, reinterpret_cast(buf), 1)); - - close(sockets[0]); + ASSERT_FALSE(data.ReadBlockAlignedData(reinterpret_cast(buf), 1, 0)); } diff --git a/minadbd/include/minadbd/types.h b/minadbd/include/minadbd/types.h new file mode 100644 index 0000000000..002523f1f0 --- /dev/null +++ b/minadbd/include/minadbd/types.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +// The message between recovery and minadbd is 8 bytes in size unless the length is explicitly +// specified. Both the command and status has the format |prefix(4 bytes) + encoded enum(4 bytes)|. +constexpr size_t kMinadbdMessageSize = 8; +constexpr char const kMinadbdCommandPrefix[] = "COMD"; +constexpr char const kMinadbdStatusPrefix[] = "STAT"; + +enum MinadbdErrorCode : int { + kMinadbdSuccess = 0, + kMinadbdArgumentsParsingError = 1, + kMinadbdSocketIOError = 2, + kMinadbdMessageFormatError = 3, + kMinadbdAdbVersionError = 4, + kMinadbdHostCommandArgumentError = 5, + kMinadbdFuseStartError = 6, + kMinadbdUnsupportedCommandError = 7, + kMinadbdCommandExecutionError = 8, + kMinadbdErrorUnknown = 9, + kMinadbdHostSocketIOError = 10, +}; + +enum class MinadbdCommandStatus : uint32_t { + kSuccess = 0, + kFailure = 1, +}; + +enum class MinadbdCommand : uint32_t { + kInstall = 0, + kUiPrint = 1, + kRebootAndroid = 2, + kRebootBootloader = 3, + kRebootFastboot = 4, + kRebootRecovery = 5, + kRebootRescue = 6, + kWipeCache = 7, + kWipeData = 8, + kNoOp = 9, + + // Last but invalid command. + kError, +}; + +static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommand)); +static_assert(kMinadbdMessageSize == + sizeof(kMinadbdStatusPrefix) - 1 + sizeof(MinadbdCommandStatus)); diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp index 349189cc74..7b82faa05b 100644 --- a/minadbd/minadbd.cpp +++ b/minadbd/minadbd.cpp @@ -14,30 +14,62 @@ * limitations under the License. */ -#include "minadbd.h" - #include +#include #include #include #include +#include + +#include +#include #include "adb.h" #include "adb_auth.h" #include "transport.h" -int minadbd_main() { +#include "minadbd/types.h" +#include "minadbd_services.h" + +using namespace std::string_literals; + +int main(int argc, char** argv) { + android::base::InitLogging(argv, &android::base::StderrLogger); + // TODO(xunchang) implement a command parser + if ((argc != 3 && argc != 4) || argv[1] != "--socket_fd"s || + (argc == 4 && argv[3] != "--rescue"s)) { + LOG(ERROR) << "minadbd has invalid arguments, argc: " << argc; + exit(kMinadbdArgumentsParsingError); + } + + int socket_fd; + if (!android::base::ParseInt(argv[2], &socket_fd)) { + LOG(ERROR) << "Failed to parse int in " << argv[2]; + exit(kMinadbdArgumentsParsingError); + } + if (fcntl(socket_fd, F_GETFD, 0) == -1) { + PLOG(ERROR) << "Failed to get minadbd socket"; + exit(kMinadbdSocketIOError); + } + SetMinadbdSocketFd(socket_fd); + + if (argc == 4) { + SetMinadbdRescueMode(true); + adb_device_banner = "rescue"; + } else { adb_device_banner = "sideload"; + } - signal(SIGPIPE, SIG_IGN); + signal(SIGPIPE, SIG_IGN); - // We can't require authentication for sideloading. http://b/22025550. - auth_required = false; + // We can't require authentication for sideloading. http://b/22025550. + auth_required = false; - init_transport_registration(); - usb_init(); + init_transport_registration(); + usb_init(); - VLOG(ADB) << "Event loop starting"; - fdevent_loop(); + VLOG(ADB) << "Event loop starting"; + fdevent_loop(); - return 0; + return 0; } diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 043c51a6a8..ff91ba931c 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "minadbd_services.h" + #include #include #include @@ -21,57 +23,298 @@ #include #include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include + #include "adb.h" -#include "fdevent.h" +#include "adb_unique_fd.h" +#include "adb_utils.h" #include "fuse_adb_provider.h" +#include "fuse_sideload.h" +#include "minadbd/types.h" +#include "recovery_utils/battery_utils.h" +#include "services.h" #include "sysdeps.h" -static void sideload_host_service(int sfd, const std::string& args) { - int file_size; - int block_size; - if (sscanf(args.c_str(), "%d:%d", &file_size, &block_size) != 2) { - printf("bad sideload-host arguments: %s\n", args.c_str()); - exit(1); +static int minadbd_socket = -1; +static bool rescue_mode = false; +static std::string sideload_mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT; + +void SetMinadbdSocketFd(int socket_fd) { + minadbd_socket = socket_fd; +} + +void SetMinadbdRescueMode(bool rescue) { + rescue_mode = rescue; +} + +void SetSideloadMountPoint(const std::string& path) { + sideload_mount_point = path; +} + +static bool WriteCommandToFd(MinadbdCommand cmd, int fd) { + char message[kMinadbdMessageSize]; + memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix)); + android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), cmd); + + if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to write message " << message; + return false; + } + return true; +} + +// Blocks and reads the command status from |fd|. Returns false if the received message has a +// format error. +static bool WaitForCommandStatus(int fd, MinadbdCommandStatus* status) { + char buffer[kMinadbdMessageSize]; + if (!android::base::ReadFully(fd, buffer, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to response status from socket"; + exit(kMinadbdSocketIOError); + } + + std::string message(buffer, buffer + kMinadbdMessageSize); + if (!android::base::StartsWith(message, kMinadbdStatusPrefix)) { + LOG(ERROR) << "Failed to parse status in " << message; + return false; + } + + *status = android::base::get_unaligned( + message.substr(strlen(kMinadbdStatusPrefix)).c_str()); + return true; +} + +static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, + MinadbdCommandStatus* status) { + auto pieces = android::base::Split(args, ":"); + int64_t file_size; + int block_size; + if (pieces.size() != 2 || !android::base::ParseInt(pieces[0], &file_size) || file_size <= 0 || + !android::base::ParseInt(pieces[1], &block_size) || block_size <= 0) { + LOG(ERROR) << "bad sideload-host arguments: " << args; + return kMinadbdHostCommandArgumentError; + } + + LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size; + + if (!WriteCommandToFd(MinadbdCommand::kInstall, minadbd_socket)) { + return kMinadbdSocketIOError; + } + + auto adb_data_reader = std::make_unique(sfd, file_size, block_size); + if (int result = run_fuse_sideload(std::move(adb_data_reader), sideload_mount_point.c_str()); + result != 0) { + LOG(ERROR) << "Failed to start fuse"; + return kMinadbdFuseStartError; + } + + if (!WaitForCommandStatus(minadbd_socket, status)) { + return kMinadbdMessageFormatError; + } + + // Signal host-side adb to stop. For sideload mode, we always send kMinadbdServicesExitSuccess + // (i.e. "DONEDONE") regardless of the install result. For rescue mode, we send failure message on + // install error. + if (!rescue_mode || *status == MinadbdCommandStatus::kSuccess) { + if (!android::base::WriteFully(sfd, kMinadbdServicesExitSuccess, + strlen(kMinadbdServicesExitSuccess))) { + return kMinadbdHostSocketIOError; } + } else { + if (!android::base::WriteFully(sfd, kMinadbdServicesExitFailure, + strlen(kMinadbdServicesExitFailure))) { + return kMinadbdHostSocketIOError; + } + } - printf("sideload-host file size %d block size %d\n", file_size, block_size); + return kMinadbdSuccess; +} - int result = run_adb_fuse(sfd, file_size, block_size); +// Sideload service always exits after serving an install command. +static void SideloadHostService(unique_fd sfd, const std::string& args) { + MinadbdCommandStatus status; + exit(RunAdbFuseSideload(sfd.get(), args, &status)); +} - printf("sideload_host finished\n"); - exit(result == 0 ? 0 : 1); +// Rescue service waits for the next command after an install command. +static void RescueInstallHostService(unique_fd sfd, const std::string& args) { + MinadbdCommandStatus status; + if (auto result = RunAdbFuseSideload(sfd.get(), args, &status); result != kMinadbdSuccess) { + exit(result); + } } -static int create_service_thread(void (*func)(int, const std::string&), const std::string& args) { - int s[2]; - if (adb_socketpair(s)) { - printf("cannot create service socket pair\n"); - return -1; +// Answers the query on a given property |prop|, by writing the result to the given |sfd|. The +// result will be newline-terminated, so nonexistent or nonallowed query will be answered with "\n". +// If given an empty string, dumps all the supported properties (analogous to `adb shell getprop`) +// in lines, e.g. "[prop]: [value]". +static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) { + constexpr const char* kRescueBatteryLevelProp = "rescue.battery_level"; + static const std::set kGetpropAllowedProps = { + // clang-format off + kRescueBatteryLevelProp, + "ro.build.date.utc", + "ro.build.fingerprint", + "ro.build.flavor", + "ro.build.id", + "ro.build.product", + "ro.build.tags", + "ro.build.version.incremental", + "ro.product.device", + "ro.product.vendor.device", + // clang-format on + }; + + auto query_prop = [](const std::string& key) { + if (key == kRescueBatteryLevelProp) { + auto battery_info = GetBatteryInfo(); + return std::to_string(battery_info.capacity); + } + return android::base::GetProperty(key, ""); + }; + + std::string result; + if (prop.empty()) { + for (const auto& key : kGetpropAllowedProps) { + auto value = query_prop(key); + if (value.empty()) { + continue; + } + result += "[" + key + "]: [" + value + "]\n"; } + } else if (kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end()) { + result = query_prop(prop) + "\n"; + } + if (result.empty()) { + result = "\n"; + } + if (!android::base::WriteFully(sfd, result.data(), result.size())) { + exit(kMinadbdHostSocketIOError); + } + + // Send heartbeat signal to keep the rescue service alive. + if (!WriteCommandToFd(MinadbdCommand::kNoOp, minadbd_socket)) { + exit(kMinadbdSocketIOError); + } + if (MinadbdCommandStatus status; !WaitForCommandStatus(minadbd_socket, &status)) { + exit(kMinadbdMessageFormatError); + } +} + +// Reboots into the given target. We don't reboot directly from minadbd, but going through recovery +// instead. This allows recovery to finish all the pending works (clear BCB, save logs etc) before +// the reboot. +static void RebootHostService(unique_fd /* sfd */, const std::string& target) { + MinadbdCommand command; + if (target == "bootloader") { + command = MinadbdCommand::kRebootBootloader; + } else if (target == "rescue") { + command = MinadbdCommand::kRebootRescue; + } else if (target == "recovery") { + command = MinadbdCommand::kRebootRecovery; + } else if (target == "fastboot") { + command = MinadbdCommand::kRebootFastboot; + } else { + command = MinadbdCommand::kRebootAndroid; + } + if (!WriteCommandToFd(command, minadbd_socket)) { + exit(kMinadbdSocketIOError); + } + MinadbdCommandStatus status; + if (!WaitForCommandStatus(minadbd_socket, &status)) { + exit(kMinadbdMessageFormatError); + } +} + +static void WipeDeviceService(unique_fd fd, const std::string& args) { + auto pieces = android::base::Split(args, ":"); + if (pieces.size() != 2 || pieces[0] != "userdata") { + LOG(ERROR) << "Failed to parse wipe device command arguments " << args; + exit(kMinadbdHostCommandArgumentError); + } - std::thread([s, func, args]() { func(s[1], args); }).detach(); + size_t message_size; + if (!android::base::ParseUint(pieces[1], &message_size) || + message_size < strlen(kMinadbdServicesExitSuccess)) { + LOG(ERROR) << "Failed to parse wipe device message size in " << args; + exit(kMinadbdHostCommandArgumentError); + } + + WriteCommandToFd(MinadbdCommand::kWipeData, minadbd_socket); + MinadbdCommandStatus status; + if (!WaitForCommandStatus(minadbd_socket, &status)) { + exit(kMinadbdMessageFormatError); + } - VLOG(SERVICES) << "service thread started, " << s[0] << ":" << s[1]; - return s[0]; + std::string response = (status == MinadbdCommandStatus::kSuccess) ? kMinadbdServicesExitSuccess + : kMinadbdServicesExitFailure; + response += std::string(message_size - response.size(), '\0'); + if (!android::base::WriteFully(fd, response.c_str(), response.size())) { + exit(kMinadbdHostSocketIOError); + } } -int service_to_fd(const char* name, atransport* /* transport */) { - int ret = -1; +asocket* daemon_service_to_socket(std::string_view) { + return nullptr; +} - if (!strncmp(name, "sideload:", 9)) { - // this exit status causes recovery to print a special error - // message saying to use a newer adb (that supports - // sideload-host). - exit(3); - } else if (!strncmp(name, "sideload-host:", 14)) { - std::string arg(name + 14); - ret = create_service_thread(sideload_host_service, arg); +unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) { + // Common services that are supported both in sideload and rescue modes. + if (android::base::ConsumePrefix(&name, "reboot:")) { + // "reboot:", where target must be one of the following. + std::string args(name); + if (args.empty() || args == "bootloader" || args == "rescue" || args == "recovery" || + args == "fastboot") { + return create_service_thread("reboot", + std::bind(RebootHostService, std::placeholders::_1, args)); + } + return unique_fd{}; } - if (ret >= 0) { - close_on_exec(ret); + + // Rescue-specific services. + if (rescue_mode) { + if (android::base::ConsumePrefix(&name, "rescue-install:")) { + // rescue-install:: + std::string args(name); + return create_service_thread( + "rescue-install", std::bind(RescueInstallHostService, std::placeholders::_1, args)); + } else if (android::base::ConsumePrefix(&name, "rescue-getprop:")) { + // rescue-getprop: + std::string args(name); + return create_service_thread( + "rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args)); + } else if (android::base::ConsumePrefix(&name, "rescue-wipe:")) { + // rescue-wipe:target: + std::string args(name); + return create_service_thread("rescue-wipe", + std::bind(WipeDeviceService, std::placeholders::_1, args)); + } + + return unique_fd{}; + } + + // Sideload-specific services. + if (name.starts_with("sideload:")) { + // This exit status causes recovery to print a special error message saying to use a newer adb + // (that supports sideload-host). + exit(kMinadbdAdbVersionError); + } else if (android::base::ConsumePrefix(&name, "sideload-host:")) { + // sideload-host:: + std::string args(name); + return create_service_thread("sideload-host", + std::bind(SideloadHostService, std::placeholders::_1, args)); } - return ret; + return unique_fd{}; } diff --git a/adb_install.h b/minadbd/minadbd_services.h similarity index 72% rename from adb_install.h rename to minadbd/minadbd_services.h index e654c893dd..5575c6b8ef 100644 --- a/adb_install.h +++ b/minadbd/minadbd_services.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 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,9 +14,12 @@ * limitations under the License. */ -#ifndef _ADB_INSTALL_H -#define _ADB_INSTALL_H +#pragma once -int apply_from_adb(bool* wipe_cache, const char* install_file); +#include -#endif +void SetMinadbdSocketFd(int socket_fd); + +void SetMinadbdRescueMode(bool); + +void SetSideloadMountPoint(const std::string& path); diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp new file mode 100644 index 0000000000..b694a57d18 --- /dev/null +++ b/minadbd/minadbd_services_test.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "adb.h" +#include "adb_io.h" +#include "fuse_adb_provider.h" +#include "fuse_sideload.h" +#include "minadbd/types.h" +#include "minadbd_services.h" +#include "socket.h" + +class MinadbdServicesTest : public ::testing::Test { + protected: + static constexpr int EXIT_TIME_OUT = 10; + + void SetUp() override { + ASSERT_TRUE( + android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &minadbd_socket_, &recovery_socket_)); + SetMinadbdSocketFd(minadbd_socket_); + SetSideloadMountPoint(mount_point_.path); + + package_path_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_FILENAME; + exit_flag_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_EXIT_FLAG; + + signal(SIGPIPE, SIG_IGN); + } + + void TearDown() override { + // Umount in case the test fails. Ignore the result. + umount(mount_point_.path); + + signal(SIGPIPE, SIG_DFL); + } + + void ReadAndCheckCommandMessage(int fd, MinadbdCommand expected_command) { + std::vector received(kMinadbdMessageSize, '\0'); + ASSERT_TRUE(android::base::ReadFully(fd, received.data(), kMinadbdMessageSize)); + + std::vector expected(kMinadbdMessageSize, '\0'); + memcpy(expected.data(), kMinadbdCommandPrefix, strlen(kMinadbdCommandPrefix)); + memcpy(expected.data() + strlen(kMinadbdCommandPrefix), &expected_command, + sizeof(expected_command)); + ASSERT_EQ(expected, received); + } + + void WaitForFusePath() { + constexpr int TIME_OUT = 10; + for (int i = 0; i < TIME_OUT; ++i) { + struct stat sb; + if (stat(package_path_.c_str(), &sb) == 0) { + return; + } + + if (errno == ENOENT) { + sleep(1); + continue; + } + FAIL() << "Timed out waiting for the fuse-provided package " << strerror(errno); + } + } + + void StatExitFlagAndExitProcess(int exit_code) { + struct stat sb; + if (stat(exit_flag_.c_str(), &sb) != 0) { + PLOG(ERROR) << "Failed to stat " << exit_flag_; + } + + exit(exit_code); + } + + void WriteMinadbdCommandStatus(MinadbdCommandStatus status) { + std::string status_message(kMinadbdMessageSize, '\0'); + memcpy(status_message.data(), kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix)); + memcpy(status_message.data() + strlen(kMinadbdStatusPrefix), &status, sizeof(status)); + ASSERT_TRUE( + android::base::WriteFully(recovery_socket_, status_message.data(), kMinadbdMessageSize)); + } + + void ExecuteCommandAndWaitForExit(const std::string& command) { + unique_fd fd = daemon_service_to_fd(command, nullptr); + ASSERT_NE(-1, fd); + sleep(EXIT_TIME_OUT); + } + + android::base::unique_fd minadbd_socket_; + android::base::unique_fd recovery_socket_; + + TemporaryDir mount_point_; + std::string package_path_; + std::string exit_flag_; +}; + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_size_argument) { + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:abc:4096"), + ::testing::ExitedWithCode(kMinadbdHostCommandArgumentError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_block_size) { + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:10:20"), + ::testing::ExitedWithCode(kMinadbdFuseStartError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_broken_minadbd_socket) { + SetMinadbdSocketFd(-1); + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdSocketIOError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_broken_recovery_socket) { + recovery_socket_.reset(); + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdSocketIOError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_command_format) { + auto test_body = [&](const std::string& command) { + unique_fd fd = daemon_service_to_fd(command, nullptr); + ASSERT_NE(-1, fd); + WaitForFusePath(); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall); + + struct stat sb; + ASSERT_EQ(0, stat(exit_flag_.c_str(), &sb)); + ASSERT_TRUE(android::base::WriteStringToFd("12345678", recovery_socket_)); + sleep(EXIT_TIME_OUT); + }; + + ASSERT_EXIT(test_body("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdMessageFormatError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_read_data_from_fuse) { + auto test_body = [&]() { + std::vector content(4096, 'a'); + // Start a new process instead of a thread to read from the package mounted by FUSE. Because + // the test may not exit and report failures correctly when the thread blocks by a syscall. + pid_t pid = fork(); + if (pid == 0) { + WaitForFusePath(); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(package_path_.c_str(), O_RDONLY))); + // Do not use assertion here because we want to stat the exit flag and exit the process. + // Otherwise the test will wait for the time out instead of failing immediately. + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << package_path_; + StatExitFlagAndExitProcess(1); + } + std::vector content_from_fuse(4096); + if (!android::base::ReadFully(fd, content_from_fuse.data(), 4096)) { + PLOG(ERROR) << "Failed to read from " << package_path_; + StatExitFlagAndExitProcess(1); + } + if (content_from_fuse != content) { + LOG(ERROR) << "Content read from fuse doesn't match with the expected value"; + StatExitFlagAndExitProcess(1); + } + StatExitFlagAndExitProcess(0); + } + + unique_fd fd = daemon_service_to_fd("sideload-host:4096:4096", nullptr); + ASSERT_NE(-1, fd); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall); + + // Mimic the response from adb host. + std::string adb_message(8, '\0'); + ASSERT_TRUE(android::base::ReadFully(fd, adb_message.data(), 8)); + ASSERT_EQ(android::base::StringPrintf("%08u", 0), adb_message); + ASSERT_TRUE(android::base::WriteFully(fd, content.data(), 4096)); + + // Check that we read the correct data from fuse. + int child_status; + waitpid(pid, &child_status, 0); + ASSERT_TRUE(WIFEXITED(child_status)); + ASSERT_EQ(0, WEXITSTATUS(child_status)); + + WriteMinadbdCommandStatus(MinadbdCommandStatus::kSuccess); + + // TODO(xunchang) check if adb host-side receives "DONEDONE", there's a race condition between + // receiving the message and exit of test body (by detached thread in minadbd service). + exit(kMinadbdSuccess); + }; + + ASSERT_EXIT(test_body(), ::testing::ExitedWithCode(kMinadbdSuccess), ""); +} diff --git a/minui/Android.bp b/minui/Android.bp new file mode 100644 index 0000000000..fff3a8ec98 --- /dev/null +++ b/minui/Android.bp @@ -0,0 +1,47 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_library { + name: "libminui", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + export_include_dirs: [ + "include", + ], + + srcs: [ + "events.cpp", + "graphics.cpp", + "graphics_adf.cpp", + "graphics_drm.cpp", + "graphics_fbdev.cpp", + "resources.cpp", + ], + + whole_static_libs: [ + "libadf", + "libdrm", + "libsync", + ], + + shared_libs: [ + "libbase", + "libpng", + "libz", + ], +} diff --git a/minui/Android.mk b/minui/Android.mk deleted file mode 100644 index ae1552b1b9..0000000000 --- a/minui/Android.mk +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (C) 2007 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH := $(call my-dir) - -# libminui (static library) -# =============================== -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - events.cpp \ - graphics.cpp \ - graphics_adf.cpp \ - graphics_drm.cpp \ - graphics_fbdev.cpp \ - resources.cpp \ - -LOCAL_WHOLE_STATIC_LIBRARIES := \ - libadf \ - libdrm \ - libsync_recovery - -LOCAL_STATIC_LIBRARIES := \ - libpng \ - libbase - -LOCAL_CFLAGS := -Wall -Werror -LOCAL_C_INCLUDES := $(LOCAL_PATH)/include -LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include - -LOCAL_MODULE := libminui - -# This used to compare against values in double-quotes (which are just -# ordinary characters in this context). Strip double-quotes from the -# value so that either will work. - -ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),ABGR_8888) - LOCAL_CFLAGS += -DRECOVERY_ABGR -endif -ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888) - LOCAL_CFLAGS += -DRECOVERY_RGBX -endif -ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888) - LOCAL_CFLAGS += -DRECOVERY_BGRA -endif - -ifneq ($(TARGET_RECOVERY_OVERSCAN_PERCENT),) - LOCAL_CFLAGS += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT) -else - LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0 -endif - -ifneq ($(TARGET_RECOVERY_DEFAULT_ROTATION),) - LOCAL_CFLAGS += -DDEFAULT_ROTATION=$(TARGET_RECOVERY_DEFAULT_ROTATION) -else - LOCAL_CFLAGS += -DDEFAULT_ROTATION=ROTATION_NONE -endif - -include $(BUILD_STATIC_LIBRARY) - -# libminui (shared library) -# =============================== -# Used by OEMs for factory test images. -include $(CLEAR_VARS) -LOCAL_MODULE := libminui -LOCAL_WHOLE_STATIC_LIBRARIES += libminui -LOCAL_SHARED_LIBRARIES := \ - libpng \ - libbase - -LOCAL_CFLAGS := -Wall -Werror -LOCAL_C_INCLUDES := $(LOCAL_PATH)/include -LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include -include $(BUILD_SHARED_LIBRARY) diff --git a/minui/events.cpp b/minui/events.cpp index 2894c3b6b3..87f8112250 100644 --- a/minui/events.cpp +++ b/minui/events.cpp @@ -22,157 +22,247 @@ #include #include #include +#include #include +#include #include #include +#include + +#include #include "minui/minui.h" -#define MAX_DEVICES 16 -#define MAX_MISC_FDS 16 +constexpr const char* INPUT_DEV_DIR = "/dev/input"; + +constexpr size_t MAX_DEVICES = 16; +constexpr size_t MAX_MISC_FDS = 16; -#define BITS_PER_LONG (sizeof(unsigned long) * 8) -#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG) +constexpr size_t BITS_PER_LONG = sizeof(unsigned long) * 8; +constexpr size_t BITS_TO_LONGS(size_t bits) { + return ((bits + BITS_PER_LONG - 1) / BITS_PER_LONG); +} -struct fd_info { - int fd; +struct FdInfo { + android::base::unique_fd fd; ev_callback cb; }; -static int g_epoll_fd; -static epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS]; -static int npolledevents; +static bool g_allow_touch_inputs = true; +static ev_callback g_saved_input_cb; +static android::base::unique_fd g_epoll_fd; +static epoll_event g_polled_events[MAX_DEVICES + MAX_MISC_FDS]; +static int g_polled_events_count; -static fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS]; +static FdInfo ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS]; -static unsigned ev_count = 0; -static unsigned ev_dev_count = 0; -static unsigned ev_misc_count = 0; +static size_t g_ev_count = 0; +static size_t g_ev_dev_count = 0; +static size_t g_ev_misc_count = 0; static bool test_bit(size_t bit, unsigned long* array) { // NOLINT - return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0; + return (array[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0; +} + +static bool should_add_input_device(int fd, bool allow_touch_inputs) { + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + + // Read the evbits of the input device. + if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + return false; + } + + // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also + // allowed if allow_touch_inputs is set. + if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { + if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) { + return false; + } + } + + return true; +} + +static int inotify_cb(int fd, __unused uint32_t epevents) { + if (g_saved_input_cb == nullptr) return -1; + + // The inotify will put one or several complete events. + // Should not read part of one event. + int event_len_int; + int ret = ioctl(fd, FIONREAD, &event_len_int); + if (ret != 0) return -1; + if (event_len_int < 0) return -1; + size_t event_len = event_len_int; + + std::unique_ptr dir(opendir(INPUT_DEV_DIR), closedir); + if (!dir) { + return -1; + } + + std::vector buf(event_len); + + ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf.data(), event_len)); + if (r != event_len) { + return -1; + } + + size_t offset = 0; + while (offset < event_len) { + struct inotify_event* pevent = reinterpret_cast(buf.data() + offset); + if (offset + sizeof(inotify_event) + pevent->len > event_len) { + // The pevent->len is too large and buffer will over flow. + // In general, should not happen, just make more stable. + return -1; + } + offset += sizeof(inotify_event) + pevent->len; + + pevent->name[pevent->len] = '\0'; + if (strncmp(pevent->name, "event", 5)) { + continue; + } + + android::base::unique_fd dfd(openat(dirfd(dir.get()), pevent->name, O_RDONLY)); + if (dfd == -1) { + break; + } + + if (!should_add_input_device(dfd, g_allow_touch_inputs)) { + continue; + } + + // Only add, we assume the user will not plug out and plug in USB device again and again :) + ev_add_fd(std::move(dfd), g_saved_input_cb); + } + + return 0; } int ev_init(ev_callback input_cb, bool allow_touch_inputs) { - g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS); - if (g_epoll_fd == -1) { + g_epoll_fd.reset(); + + android::base::unique_fd epoll_fd(epoll_create1(EPOLL_CLOEXEC)); + if (epoll_fd == -1) { return -1; } - bool epollctlfail = false; - DIR* dir = opendir("/dev/input"); - if (dir != nullptr) { - dirent* de; - while ((de = readdir(dir))) { - if (strncmp(de->d_name, "event", 5)) continue; - int fd = openat(dirfd(dir), de->d_name, O_RDONLY); - if (fd == -1) continue; - - // Use unsigned long to match ioctl's parameter type. - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT - - // Read the evbits of the input device. - if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { - close(fd); - continue; - } + android::base::unique_fd inotify_fd(inotify_init1(IN_CLOEXEC)); + if (inotify_fd.get() == -1) { + return -1; + } - // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also - // allowed if allow_touch_inputs is set. - if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { - if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) { - close(fd); - continue; - } - } + if (inotify_add_watch(inotify_fd, INPUT_DEV_DIR, IN_CREATE) < 0) { + return -1; + } - epoll_event ev; - ev.events = EPOLLIN | EPOLLWAKEUP; - ev.data.ptr = &ev_fdinfo[ev_count]; - if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { - close(fd); - epollctlfail = true; - continue; - } + std::unique_ptr dir(opendir(INPUT_DEV_DIR), closedir); + if (!dir) { + return -1; + } + + bool epoll_ctl_failed = false; + dirent* de; + while ((de = readdir(dir.get())) != nullptr) { + if (strncmp(de->d_name, "event", 5)) continue; + android::base::unique_fd fd(openat(dirfd(dir.get()), de->d_name, O_RDONLY | O_CLOEXEC)); + if (fd == -1) continue; - ev_fdinfo[ev_count].fd = fd; - ev_fdinfo[ev_count].cb = std::move(input_cb); - ev_count++; - ev_dev_count++; - if (ev_dev_count == MAX_DEVICES) break; + if (!should_add_input_device(fd, allow_touch_inputs)) { + continue; } - closedir(dir); + epoll_event ev; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = &ev_fdinfo[g_ev_count]; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + epoll_ctl_failed = true; + continue; + } + + ev_fdinfo[g_ev_count].fd.reset(fd.release()); + ev_fdinfo[g_ev_count].cb = input_cb; + g_ev_count++; + g_ev_dev_count++; + if (g_ev_dev_count == MAX_DEVICES) break; } - if (epollctlfail && !ev_count) { - close(g_epoll_fd); - g_epoll_fd = -1; + if (epoll_ctl_failed && !g_ev_count) { return -1; } + g_epoll_fd.reset(epoll_fd.release()); + + g_saved_input_cb = input_cb; + g_allow_touch_inputs = allow_touch_inputs; + ev_add_fd(std::move(inotify_fd), inotify_cb); + return 0; } int ev_get_epollfd(void) { - return g_epoll_fd; + return g_epoll_fd.get(); } -int ev_add_fd(int fd, ev_callback cb) { - if (ev_misc_count == MAX_MISC_FDS || cb == NULL) { +int ev_add_fd(android::base::unique_fd&& fd, ev_callback cb) { + if (g_ev_misc_count == MAX_MISC_FDS || cb == nullptr) { return -1; } epoll_event ev; ev.events = EPOLLIN | EPOLLWAKEUP; - ev.data.ptr = static_cast(&ev_fdinfo[ev_count]); + ev.data.ptr = static_cast(&ev_fdinfo[g_ev_count]); int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev); if (!ret) { - ev_fdinfo[ev_count].fd = fd; - ev_fdinfo[ev_count].cb = std::move(cb); - ev_count++; - ev_misc_count++; + ev_fdinfo[g_ev_count].fd.reset(fd.release()); + ev_fdinfo[g_ev_count].cb = std::move(cb); + g_ev_count++; + g_ev_misc_count++; } return ret; } void ev_exit(void) { - while (ev_count > 0) { - close(ev_fdinfo[--ev_count].fd); - } - ev_misc_count = 0; - ev_dev_count = 0; - close(g_epoll_fd); + while (g_ev_count > 0) { + ev_fdinfo[--g_ev_count].fd.reset(); + } + g_ev_misc_count = 0; + g_ev_dev_count = 0; + g_saved_input_cb = nullptr; + g_epoll_fd.reset(); } int ev_wait(int timeout) { - npolledevents = epoll_wait(g_epoll_fd, polledevents, ev_count, timeout); - if (npolledevents <= 0) { - return -1; - } - return 0; + g_polled_events_count = epoll_wait(g_epoll_fd, g_polled_events, g_ev_count, timeout); + if (g_polled_events_count <= 0) { + return -1; + } + return 0; } void ev_dispatch(void) { - for (int n = 0; n < npolledevents; n++) { - fd_info* fdi = static_cast(polledevents[n].data.ptr); + for (int n = 0; n < g_polled_events_count; n++) { + FdInfo* fdi = static_cast(g_polled_events[n].data.ptr); const ev_callback& cb = fdi->cb; if (cb) { - cb(fdi->fd, polledevents[n].events); + cb(fdi->fd, g_polled_events[n].events); } } } int ev_get_input(int fd, uint32_t epevents, input_event* ev) { - if (epevents & EPOLLIN) { - ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev))); - if (r == sizeof(*ev)) { - return 0; - } + if (epevents & EPOLLIN) { + ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev))); + if (r == sizeof(*ev)) { + return 0; } - return -1; + } + if (epevents & EPOLLHUP) { + // Delete this watch + epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, fd, nullptr); + } + return -1; } int ev_sync_key_state(const ev_set_key_callback& set_key_cb) { @@ -180,7 +270,7 @@ int ev_sync_key_state(const ev_set_key_callback& set_key_cb) { unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT - for (size_t i = 0; i < ev_dev_count; ++i) { + for (size_t i = 0; i < g_ev_dev_count; ++i) { memset(ev_bits, 0, sizeof(ev_bits)); memset(key_bits, 0, sizeof(key_bits)); @@ -205,37 +295,36 @@ int ev_sync_key_state(const ev_set_key_callback& set_key_cb) { } void ev_iterate_available_keys(const std::function& f) { - // Use unsigned long to match ioctl's parameter type. - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT - unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT - - for (size_t i = 0; i < ev_dev_count; ++i) { - memset(ev_bits, 0, sizeof(ev_bits)); - memset(key_bits, 0, sizeof(key_bits)); - - // Does this device even have keys? - if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { - continue; - } - if (!test_bit(EV_KEY, ev_bits)) { - continue; - } - - int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits); - if (rc == -1) { - continue; - } - - for (int key_code = 0; key_code <= KEY_MAX; ++key_code) { - if (test_bit(key_code, key_bits)) { - f(key_code); - } - } + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT + + for (size_t i = 0; i < g_ev_dev_count; ++i) { + memset(ev_bits, 0, sizeof(ev_bits)); + memset(key_bits, 0, sizeof(key_bits)); + + // Does this device even have keys? + if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + continue; } + if (!test_bit(EV_KEY, ev_bits)) { + continue; + } + + if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits) == -1) { + continue; + } + + for (int key_code = 0; key_code <= KEY_MAX; ++key_code) { + if (test_bit(key_code, key_bits)) { + f(key_code); + } + } + } } void ev_iterate_touch_inputs(const std::function& action) { - for (size_t i = 0; i < ev_dev_count; ++i) { + for (size_t i = 0; i < g_ev_dev_count; ++i) { // Use unsigned long to match ioctl's parameter type. unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {}; // NOLINT if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { diff --git a/minui/font_10x18.h b/minui/font_10x18.h deleted file mode 100644 index 30dfb9c563..0000000000 --- a/minui/font_10x18.h +++ /dev/null @@ -1,214 +0,0 @@ -struct { - unsigned width; - unsigned height; - unsigned char_width; - unsigned char_height; - unsigned char rundata[2973]; -} font = { - .width = 960, - .height = 18, - .char_width = 10, - .char_height = 18, - .rundata = { -0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82, -0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06, -0x84,0x0a,0x81,0x03,0x88,0x04,0x84,0x04,0x88,0x04,0x84,0x06,0x84,0x1e,0x81, -0x0e,0x81,0x0a,0x84,0x06,0x84,0x07,0x82,0x05,0x85,0x07,0x84,0x04,0x86,0x04, -0x88,0x02,0x88,0x04,0x84,0x04,0x82,0x04,0x82,0x02,0x88,0x05,0x86,0x01,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04, -0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02, -0x88,0x03,0x86,0x0e,0x86,0x06,0x82,0x11,0x82,0x10,0x82,0x18,0x82,0x0f,0x84, -0x0d,0x82,0x1c,0x82,0x09,0x84,0x7f,0x16,0x84,0x05,0x82,0x05,0x84,0x07,0x83, -0x02,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x03,0x86,0x04, -0x83,0x02,0x82,0x03,0x82,0x01,0x82,0x07,0x82,0x09,0x82,0x06,0x82,0x3e,0x82, -0x04,0x84,0x06,0x83,0x06,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x03, -0x82,0x09,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82, -0x1c,0x82,0x0e,0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x05,0x84,0x04, -0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82, -0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x04, -0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82, -0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02, -0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82, -0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x0c, -0x82,0x05,0x84,0x11,0x82,0x0f,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82, -0x1c,0x82,0x0b,0x82,0x7f,0x15,0x82,0x08,0x82,0x08,0x82,0x05,0x82,0x01,0x82, -0x01,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x02,0x82,0x01, -0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x07,0x82, -0x08,0x82,0x08,0x82,0x3d,0x82,0x03,0x82,0x02,0x82,0x04,0x84,0x05,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x06,0x83,0x03,0x82,0x08,0x82,0x04,0x81,0x09,0x82, -0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x1a,0x82,0x10,0x82,0x06,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02, -0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83, -0x02,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02, -0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x08,0x82,0x0c,0x82,0x04,0x82,0x02,0x82, -0x11,0x82,0x0e,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,0x0b,0x82,0x0b, -0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x05,0x82, -0x02,0x83,0x1a,0x82,0x07,0x81,0x02,0x81,0x07,0x82,0x01,0x82,0x02,0x82,0x01, -0x82,0x05,0x82,0x01,0x84,0x04,0x82,0x01,0x82,0x07,0x82,0x08,0x82,0x08,0x82, -0x06,0x82,0x02,0x82,0x06,0x82,0x28,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01, -0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x84,0x03,0x82,0x08,0x82, -0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x19,0x82,0x12,0x82,0x05, -0x82,0x04,0x82,0x02,0x82,0x02,0x84,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82, -0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04, -0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,0x02,0x83, -0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,0x05,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08, -0x82,0x04,0x82,0x09,0x82,0x0b,0x82,0x03,0x82,0x04,0x82,0x20,0x82,0x18,0x82, -0x0e,0x82,0x10,0x82,0x0b,0x82,0x0b,0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45, -0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x88,0x01,0x82,0x01,0x82,0x06,0x83, -0x01,0x82,0x04,0x84,0x08,0x81,0x08,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06, -0x82,0x28,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x08,0x82,0x04,0x82, -0x01,0x82,0x03,0x82,0x08,0x82,0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x18,0x82,0x06,0x88,0x06,0x82,0x04,0x82,0x04,0x82,0x02,0x82,0x01,0x85, -0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02, -0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82, -0x02,0x82,0x04,0x82,0x08,0x88,0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02, -0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82, -0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x84,0x06, -0x84,0x08,0x82,0x05,0x82,0x09,0x82,0x0b,0x82,0x2b,0x82,0x18,0x82,0x0e,0x82, -0x10,0x82,0x1c,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x26, -0x82,0x11,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x09,0x82,0x06,0x82,0x12,0x82, -0x0a,0x82,0x06,0x84,0x07,0x82,0x27,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0b, -0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,0x83,0x04,0x82,0x01,0x83, -0x08,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x83,0x07,0x83,0x05, -0x82,0x16,0x82,0x08,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82, -0x02,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08, -0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82, -0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82, -0x0a,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01, -0x82,0x04,0x84,0x06,0x84,0x08,0x82,0x05,0x82,0x0a,0x82,0x0a,0x82,0x23,0x85, -0x03,0x82,0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x84,0x04,0x86,0x05, -0x85,0x01,0x81,0x02,0x82,0x01,0x83,0x05,0x84,0x09,0x84,0x02,0x82,0x03,0x82, -0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x04, -0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x02,0x82,0x01,0x84,0x04,0x86,0x03,0x86, -0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x03,0x87,0x05,0x82,0x08,0x82,0x08,0x82,0x26,0x82, -0x11,0x82,0x01,0x82,0x04,0x86,0x07,0x82,0x05,0x83,0x12,0x82,0x0a,0x82,0x04, -0x88,0x02,0x88,0x0c,0x88,0x10,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0a,0x82, -0x06,0x83,0x04,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x03,0x83,0x02,0x82,0x07, -0x82,0x06,0x84,0x05,0x82,0x02,0x83,0x05,0x83,0x07,0x83,0x04,0x82,0x18,0x82, -0x06,0x82,0x04,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x86,0x04, -0x82,0x08,0x82,0x04,0x82,0x02,0x86,0x04,0x86,0x04,0x82,0x02,0x84,0x02,0x88, -0x05,0x82,0x0a,0x82,0x03,0x85,0x05,0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02, -0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82, -0x04,0x82,0x02,0x82,0x03,0x82,0x05,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x03, -0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x08,0x82,0x08,0x82, -0x06,0x82,0x0a,0x82,0x0a,0x82,0x22,0x82,0x03,0x82,0x02,0x83,0x02,0x82,0x04, -0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x05,0x82,0x06,0x82, -0x03,0x83,0x02,0x83,0x02,0x82,0x06,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,0x07, -0x82,0x05,0x88,0x02,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82, -0x04,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06, -0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82, -0x03,0x82,0x04,0x82,0x08,0x82,0x02,0x84,0x09,0x82,0x09,0x84,0x23,0x82,0x11, -0x82,0x01,0x82,0x06,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x01,0x82,0x11,0x82, -0x0a,0x82,0x06,0x84,0x07,0x82,0x26,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x08, -0x83,0x09,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x02,0x82,0x04,0x82,0x05,0x82, -0x06,0x82,0x02,0x82,0x05,0x83,0x01,0x82,0x17,0x82,0x16,0x82,0x06,0x82,0x05, -0x82,0x01,0x82,0x01,0x82,0x02,0x88,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, -0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82, -0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x86,0x04,0x82,0x04,0x82,0x02, -0x86,0x09,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82, -0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,0x82,0x27, -0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82, -0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02, -0x82,0x01,0x82,0x08,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82, -0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x07, -0x82,0x0a,0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82, -0x04,0x84,0x04,0x82,0x04,0x82,0x07,0x82,0x06,0x82,0x08,0x82,0x08,0x82,0x26, -0x82,0x0f,0x88,0x05,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x01,0x82, -0x0d,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,0x82,0x26,0x82,0x05,0x82,0x04, -0x82,0x05,0x82,0x07,0x82,0x0c,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82, -0x05,0x82,0x05,0x82,0x04,0x82,0x08,0x82,0x18,0x82,0x14,0x82,0x07,0x82,0x05, -0x82,0x01,0x84,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, -0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82, -0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02, -0x82,0x02,0x82,0x0a,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82, -0x01,0x82,0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09, -0x82,0x22,0x87,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88, -0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02, -0x84,0x09,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x86,0x05, -0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82, -0x05,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x08,0x82,0x08,0x82,0x26, -0x82,0x10,0x82,0x01,0x82,0x07,0x82,0x01,0x82,0x04,0x82,0x01,0x83,0x02,0x82, -0x03,0x83,0x0f,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x25,0x82,0x07, -0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x09,0x82, -0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,0x82,0x08,0x82,0x19,0x82,0x05, -0x88,0x05,0x82,0x08,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,0x03,0x82,0x03,0x82, -0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02, -0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x05,0x82, -0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x06, -0x82,0x06,0x82,0x08,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03, -0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x85,0x08,0x82,0x05,0x82, -0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,0x06,0x82,0x04,0x82, -0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05, -0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x38,0x82,0x01,0x82,0x04,0x82,0x01,0x82, -0x01,0x82,0x04,0x84,0x01,0x82,0x01,0x82,0x03,0x82,0x10,0x82,0x08,0x82,0x30, -0x83,0x06,0x82,0x07,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x04,0x82, -0x07,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04, -0x82,0x03,0x81,0x04,0x82,0x1a,0x82,0x10,0x82,0x10,0x82,0x08,0x82,0x04,0x82, -0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08, -0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82, -0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02, -0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x02,0x84,0x02,0x82,0x03,0x82,0x03,0x82, -0x04,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x05,0x83,0x02,0x83,0x03, -0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0c,0x82,0x08,0x82,0x21,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a, -0x82,0x07,0x85,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x02,0x82, -0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82, -0x06,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x04,0x84,0x04, -0x82,0x04,0x82,0x04,0x82,0x09,0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x82, -0x01,0x82,0x05,0x86,0x04,0x82,0x01,0x82,0x01,0x82,0x01,0x83,0x01,0x84,0x10, -0x82,0x06,0x82,0x1d,0x83,0x11,0x83,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x82, -0x09,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x83,0x07,0x83,0x09,0x82, -0x0e,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03, -0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x09,0x82, -0x02,0x83,0x02,0x82,0x04,0x82,0x05,0x82,0x06,0x82,0x01,0x82,0x04,0x82,0x04, -0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82, -0x03,0x82,0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06, -0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82, -0x05,0x82,0x05,0x82,0x09,0x82,0x0d,0x82,0x07,0x82,0x21,0x82,0x04,0x82,0x02, -0x83,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x03,0x82, -0x04,0x82,0x06,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x03, -0x82,0x06,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82, -0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x07,0x82,0x04, -0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x02,0x83,0x05,0x82,0x05,0x88,0x03,0x82, -0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x0a,0x82,0x08,0x82,0x08,0x82,0x26, -0x82,0x1c,0x82,0x06,0x82,0x02,0x83,0x03,0x84,0x02,0x82,0x10,0x82,0x04,0x82, -0x1e,0x83,0x11,0x83,0x05,0x82,0x0a,0x82,0x05,0x88,0x02,0x88,0x04,0x84,0x09, -0x82,0x05,0x84,0x06,0x84,0x05,0x82,0x09,0x84,0x06,0x84,0x07,0x83,0x07,0x83, -0x0a,0x81,0x0e,0x81,0x0b,0x82,0x07,0x85,0x03,0x82,0x04,0x82,0x02,0x86,0x06, -0x84,0x04,0x86,0x04,0x88,0x02,0x82,0x0a,0x84,0x01,0x81,0x02,0x82,0x04,0x82, -0x02,0x88,0x04,0x83,0x05,0x82,0x04,0x82,0x02,0x88,0x02,0x82,0x04,0x82,0x02, -0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x0a,0x85,0x03,0x82,0x04,0x82,0x04,0x84, -0x07,0x82,0x07,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, -0x82,0x05,0x88,0x03,0x86,0x09,0x82,0x03,0x86,0x22,0x85,0x01,0x81,0x02,0x82, -0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x85,0x05,0x82,0x07,0x86,0x03, -0x82,0x04,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x88,0x02,0x82, -0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06, -0x83,0x01,0x82,0x03,0x82,0x08,0x86,0x06,0x84,0x05,0x83,0x01,0x82,0x05,0x82, -0x06,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x83,0x01,0x82,0x03,0x87,0x06, -0x84,0x05,0x82,0x05,0x84,0x7f,0x15,0x83,0x7f,0x14,0x83,0x7f,0x5e,0x82,0x7f, -0x05,0x89,0x47,0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x4e, -0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,0x82,0x04,0x82,0x17,0x82,0x03,0x82, -0x34,0x82,0x0e,0x82,0x48,0x82,0x04,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a, -0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x49,0x82,0x02,0x82, -0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0c,0x86,0x19,0x85,0x35,0x82,0x0e,0x82,0x4a, -0x84,0x3f, -0x00, - } -}; diff --git a/minui/graphics.cpp b/minui/graphics.cpp index 56f471bce2..d34da56744 100644 --- a/minui/graphics.cpp +++ b/minui/graphics.cpp @@ -23,41 +23,57 @@ #include -#include "font_10x18.h" +#include + #include "graphics_adf.h" #include "graphics_drm.h" #include "graphics_fbdev.h" #include "minui/minui.h" -static GRFont* gr_font = NULL; +static GRFont* gr_font = nullptr; static MinuiBackend* gr_backend = nullptr; -static int overscan_percent = OVERSCAN_PERCENT; static int overscan_offset_x = 0; static int overscan_offset_y = 0; static uint32_t gr_current = ~0; static constexpr uint32_t alpha_mask = 0xff000000; -static GRSurface* gr_draw = NULL; -static GRRotation rotation = ROTATION_NONE; +// gr_draw is owned by backends. +static GRSurface* gr_draw = nullptr; +static GRRotation rotation = GRRotation::NONE; +static PixelFormat pixel_format = PixelFormat::UNKNOWN; static bool outside(int x, int y) { - return x < 0 || x >= (rotation % 2 ? gr_draw->height : gr_draw->width) || y < 0 || - y >= (rotation % 2 ? gr_draw->width : gr_draw->height); + auto swapped = (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT); + return x < 0 || x >= (swapped ? gr_draw->height : gr_draw->width) || y < 0 || + y >= (swapped ? gr_draw->width : gr_draw->height); } const GRFont* gr_sys_font() { return gr_font; } +PixelFormat gr_pixel_format() { + return pixel_format; +} + int gr_measure(const GRFont* font, const char* s) { + if (font == nullptr) { + return -1; + } + return font->char_width * strlen(s); } -void gr_font_size(const GRFont* font, int* x, int* y) { +int gr_font_size(const GRFont* font, int* x, int* y) { + if (font == nullptr) { + return -1; + } + *x = font->char_width; *y = font->char_height; + return 0; } // Blends gr_current onto pix value, assumes alpha as most significant byte. @@ -78,47 +94,56 @@ static inline uint32_t pixel_blend(uint8_t alpha, uint32_t pix) { return (out_r & 0xff) | (out_g & 0xff00) | (out_b & 0xff0000) | (gr_current & 0xff000000); } -// increments pixel pointer right, with current rotation. +// Increments pixel pointer right, with current rotation. static void incr_x(uint32_t** p, int row_pixels) { - if (rotation % 2) { - *p = *p + (rotation == 1 ? 1 : -1) * row_pixels; - } else { - *p = *p + (rotation ? -1 : 1); + if (rotation == GRRotation::LEFT) { + *p = *p - row_pixels; + } else if (rotation == GRRotation::RIGHT) { + *p = *p + row_pixels; + } else if (rotation == GRRotation::DOWN) { + *p = *p - 1; + } else { // GRRotation::NONE + *p = *p + 1; } } -// increments pixel pointer down, with current rotation. +// Increments pixel pointer down, with current rotation. static void incr_y(uint32_t** p, int row_pixels) { - if (rotation % 2) { - *p = *p + (rotation == 1 ? -1 : 1); - } else { - *p = *p + (rotation ? -1 : 1) * row_pixels; + if (rotation == GRRotation::LEFT) { + *p = *p + 1; + } else if (rotation == GRRotation::RIGHT) { + *p = *p - 1; + } else if (rotation == GRRotation::DOWN) { + *p = *p - row_pixels; + } else { // GRRotation::NONE + *p = *p + row_pixels; } } -// returns pixel pointer at given coordinates with rotation adjustment. -static uint32_t* pixel_at(GRSurface* surf, int x, int y, int row_pixels) { +// Returns pixel pointer at given coordinates with rotation adjustment. +static uint32_t* PixelAt(GRSurface* surface, int x, int y, int row_pixels) { switch (rotation) { - case ROTATION_NONE: - return reinterpret_cast(surf->data) + y * row_pixels + x; - case ROTATION_RIGHT: - return reinterpret_cast(surf->data) + x * row_pixels + (surf->width - y); - case ROTATION_DOWN: - return reinterpret_cast(surf->data) + (surf->height - 1 - y) * row_pixels + - (surf->width - 1 - x); - case ROTATION_LEFT: - return reinterpret_cast(surf->data) + (surf->height - 1 - x) * row_pixels + y; + case GRRotation::NONE: + return reinterpret_cast(surface->data()) + y * row_pixels + x; + case GRRotation::RIGHT: + return reinterpret_cast(surface->data()) + x * row_pixels + (surface->width - y); + case GRRotation::DOWN: + return reinterpret_cast(surface->data()) + (surface->height - 1 - y) * row_pixels + + (surface->width - 1 - x); + case GRRotation::LEFT: + return reinterpret_cast(surface->data()) + (surface->height - 1 - x) * row_pixels + + y; default: - printf("invalid rotation %d", rotation); + printf("invalid rotation %d", static_cast(rotation)); } return nullptr; } -static void text_blend(uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels, - int width, int height) { +static void TextBlend(const uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels, + int width, int height) { uint8_t alpha_current = static_cast((alpha_mask & gr_current) >> 24); for (int j = 0; j < height; ++j) { - uint8_t* sx = src_p; + const uint8_t* sx = src_p; uint32_t* px = dst_p; for (int i = 0; i < width; ++i, incr_x(&px, dst_row_pixels)) { uint8_t a = *sx++; @@ -152,19 +177,19 @@ void gr_text(const GRFont* font, int x, int y, const char* s, bool bold) { } int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes; - uint8_t* src_p = font->texture->data + ((ch - ' ') * font->char_width) + - (bold ? font->char_height * font->texture->row_bytes : 0); - uint32_t* dst_p = pixel_at(gr_draw, x, y, row_pixels); + const uint8_t* src_p = font->texture->data() + ((ch - ' ') * font->char_width) + + (bold ? font->char_height * font->texture->row_bytes : 0); + uint32_t* dst_p = PixelAt(gr_draw, x, y, row_pixels); - text_blend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width, - font->char_height); + TextBlend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width, + font->char_height); x += font->char_width; } } -void gr_texticon(int x, int y, GRSurface* icon) { - if (icon == NULL) return; +void gr_texticon(int x, int y, const GRSurface* icon) { + if (icon == nullptr) return; if (icon->pixel_bytes != 1) { printf("gr_texticon: source has wrong format\n"); @@ -177,19 +202,18 @@ void gr_texticon(int x, int y, GRSurface* icon) { if (outside(x, y) || outside(x + icon->width - 1, y + icon->height - 1)) return; int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes; - uint8_t* src_p = icon->data; - uint32_t* dst_p = pixel_at(gr_draw, x, y, row_pixels); - - text_blend(src_p, icon->row_bytes, dst_p, row_pixels, icon->width, icon->height); + const uint8_t* src_p = icon->data(); + uint32_t* dst_p = PixelAt(gr_draw, x, y, row_pixels); + TextBlend(src_p, icon->row_bytes, dst_p, row_pixels, icon->width, icon->height); } void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { uint32_t r32 = r, g32 = g, b32 = b, a32 = a; -#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) - gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32; -#else - gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32; -#endif + if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) { + gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32; + } else { + gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32; + } } void gr_clear() { @@ -197,9 +221,9 @@ void gr_clear() { (gr_current & 0xff) == ((gr_current >> 16) & 0xff) && (gr_current & 0xff) == ((gr_current >> 24) & 0xff) && gr_draw->row_bytes == gr_draw->width * gr_draw->pixel_bytes) { - memset(gr_draw->data, gr_current & 0xff, gr_draw->height * gr_draw->row_bytes); + memset(gr_draw->data(), gr_current & 0xff, gr_draw->height * gr_draw->row_bytes); } else { - uint32_t* px = reinterpret_cast(gr_draw->data); + uint32_t* px = reinterpret_cast(gr_draw->data()); int row_diff = gr_draw->row_bytes / gr_draw->pixel_bytes - gr_draw->width; for (int y = 0; y < gr_draw->height; ++y) { for (int x = 0; x < gr_draw->width; ++x) { @@ -220,7 +244,7 @@ void gr_fill(int x1, int y1, int x2, int y2) { if (outside(x1, y1) || outside(x2 - 1, y2 - 1)) return; int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes; - uint32_t* p = pixel_at(gr_draw, x1, y1, row_pixels); + uint32_t* p = PixelAt(gr_draw, x1, y1, row_pixels); uint8_t alpha = static_cast(((gr_current & alpha_mask) >> 24)); if (alpha > 0) { for (int y = y1; y < y2; ++y) { @@ -234,8 +258,8 @@ void gr_fill(int x1, int y1, int x2, int y2) { } } -void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { - if (source == NULL) return; +void gr_blit(const GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { + if (source == nullptr) return; if (gr_draw->pixel_bytes != source->pixel_bytes) { printf("gr_blit: source has wrong format\n"); @@ -247,14 +271,15 @@ void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { if (outside(dx, dy) || outside(dx + w - 1, dy + h - 1)) return; - if (rotation) { + if (rotation != GRRotation::NONE) { int src_row_pixels = source->row_bytes / source->pixel_bytes; int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes; - uint32_t* src_py = reinterpret_cast(source->data) + sy * source->row_bytes / 4 + sx; - uint32_t* dst_py = pixel_at(gr_draw, dx, dy, row_pixels); + const uint32_t* src_py = + reinterpret_cast(source->data()) + sy * source->row_bytes / 4 + sx; + uint32_t* dst_py = PixelAt(gr_draw, dx, dy, row_pixels); for (int y = 0; y < h; y += 1) { - uint32_t* src_px = src_py; + const uint32_t* src_px = src_py; uint32_t* dst_px = dst_py; for (int x = 0; x < w; x += 1) { *dst_px = *src_px++; @@ -264,11 +289,10 @@ void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { incr_y(&dst_py, row_pixels); } } else { - unsigned char* src_p = source->data + sy * source->row_bytes + sx * source->pixel_bytes; - unsigned char* dst_p = gr_draw->data + dy * gr_draw->row_bytes + dx * gr_draw->pixel_bytes; + const uint8_t* src_p = source->data() + sy * source->row_bytes + sx * source->pixel_bytes; + uint8_t* dst_p = gr_draw->data() + dy * gr_draw->row_bytes + dx * gr_draw->pixel_bytes; - int i; - for (i = 0; i < h; ++i) { + for (int i = 0; i < h; ++i) { memcpy(dst_p, src_p, w * source->pixel_bytes); src_p += source->row_bytes; dst_p += gr_draw->row_bytes; @@ -276,15 +300,15 @@ void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { } } -unsigned int gr_get_width(GRSurface* surface) { - if (surface == NULL) { +unsigned int gr_get_width(const GRSurface* surface) { + if (surface == nullptr) { return 0; } return surface->width; } -unsigned int gr_get_height(GRSurface* surface) { - if (surface == NULL) { +unsigned int gr_get_height(const GRSurface* surface) { + if (surface == nullptr) { return 0; } return surface->height; @@ -313,42 +337,30 @@ int gr_init_font(const char* name, GRFont** dest) { return 0; } -static void gr_init_font(void) { - int res = gr_init_font("font", &gr_font); - if (res == 0) { - return; - } - - printf("failed to read font: res=%d\n", res); - - // fall back to the compiled-in font. - gr_font = static_cast(calloc(1, sizeof(*gr_font))); - gr_font->texture = static_cast(malloc(sizeof(*gr_font->texture))); - gr_font->texture->width = font.width; - gr_font->texture->height = font.height; - gr_font->texture->row_bytes = font.width; - gr_font->texture->pixel_bytes = 1; - - unsigned char* bits = static_cast(malloc(font.width * font.height)); - gr_font->texture->data = bits; - - unsigned char data; - unsigned char* in = font.rundata; - while ((data = *in++)) { - memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f); - bits += (data & 0x7f); - } - - gr_font->char_width = font.char_width; - gr_font->char_height = font.char_height; -} - void gr_flip() { gr_draw = gr_backend->Flip(); } int gr_init() { - gr_init_font(); + // pixel_format needs to be set before loading any resources or initializing backends. + std::string format = android::base::GetProperty("ro.minui.pixel_format", ""); + if (format == "ABGR_8888") { + pixel_format = PixelFormat::ABGR; + } else if (format == "RGBX_8888") { + pixel_format = PixelFormat::RGBX; + } else if (format == "ARGB_8888") { + pixel_format = PixelFormat::ARGB; + } else if (format == "BGRA_8888") { + pixel_format = PixelFormat::BGRA; + } else { + pixel_format = PixelFormat::UNKNOWN; + } + + int ret = gr_init_font("font", &gr_font); + if (ret != 0) { + printf("Failed to init font: %d, continuing graphic backend initialization without font file\n", + ret); + } auto backend = std::unique_ptr{ std::make_unique() }; gr_draw = backend->Init(); @@ -369,13 +381,28 @@ int gr_init() { gr_backend = backend.release(); + int overscan_percent = android::base::GetIntProperty("ro.minui.overscan_percent", 0); overscan_offset_x = gr_draw->width * overscan_percent / 100; overscan_offset_y = gr_draw->height * overscan_percent / 100; gr_flip(); gr_flip(); + if (!gr_draw) { + printf("gr_init: gr_draw becomes nullptr after gr_flip\n"); + return -1; + } - gr_rotate(DEFAULT_ROTATION); + std::string rotation_str = + android::base::GetProperty("ro.minui.default_rotation", "ROTATION_NONE"); + if (rotation_str == "ROTATION_RIGHT") { + gr_rotate(GRRotation::RIGHT); + } else if (rotation_str == "ROTATION_DOWN") { + gr_rotate(GRRotation::DOWN); + } else if (rotation_str == "ROTATION_LEFT") { + gr_rotate(GRRotation::LEFT); + } else { // "ROTATION_NONE" or unknown string + gr_rotate(GRRotation::NONE); + } if (gr_draw->pixel_bytes != 4) { printf("gr_init: Only 4-byte pixel formats supported\n"); @@ -386,16 +413,22 @@ int gr_init() { void gr_exit() { delete gr_backend; + gr_backend = nullptr; + + delete gr_font; + gr_font = nullptr; } int gr_fb_width() { - return rotation % 2 ? gr_draw->height - 2 * overscan_offset_y - : gr_draw->width - 2 * overscan_offset_x; + return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT) + ? gr_draw->height - 2 * overscan_offset_y + : gr_draw->width - 2 * overscan_offset_x; } int gr_fb_height() { - return rotation % 2 ? gr_draw->width - 2 * overscan_offset_x - : gr_draw->height - 2 * overscan_offset_y; + return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT) + ? gr_draw->width - 2 * overscan_offset_x + : gr_draw->height - 2 * overscan_offset_y; } void gr_fb_blank(bool blank) { diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp index a59df00c61..10cd607095 100644 --- a/minui/graphics_adf.cpp +++ b/minui/graphics_adf.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -28,51 +29,60 @@ #include "minui/minui.h" -MinuiBackendAdf::MinuiBackendAdf() - : intf_fd(-1), dev(), current_surface(0), n_surfaces(0), surfaces() {} - -int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) { - *surf = {}; - surf->fence_fd = -1; - surf->fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format, - &surf->offset, &surf->pitch); - if (surf->fd < 0) { - return surf->fd; +GRSurfaceAdf::~GRSurfaceAdf() { + if (mmapped_buffer_) { + munmap(mmapped_buffer_, pitch * height); + } + if (fence_fd != -1) { + close(fence_fd); + } + if (fd != -1) { + close(fd); } +} + +std::unique_ptr GRSurfaceAdf::Create(int intf_fd, const drm_mode_modeinfo* mode, + __u32 format, int* err) { + __u32 offset; + __u32 pitch; + auto fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format, + &offset, &pitch); - surf->width = mode->hdisplay; - surf->height = mode->vdisplay; - surf->row_bytes = surf->pitch; - surf->pixel_bytes = (format == DRM_FORMAT_RGB565) ? 2 : 4; - - surf->data = static_cast( - mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset)); - if (surf->data == MAP_FAILED) { - int saved_errno = errno; - close(surf->fd); - return -saved_errno; + if (fd < 0) { + *err = fd; + return nullptr; } - return 0; + std::unique_ptr surf = std::unique_ptr( + new GRSurfaceAdf(mode->hdisplay, mode->vdisplay, pitch, (format == DRM_FORMAT_RGB565 ? 2 : 4), + offset, pitch, fd)); + + auto mmapped = + mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset); + if (mmapped == MAP_FAILED) { + *err = -errno; + return nullptr; + } + surf->mmapped_buffer_ = static_cast(mmapped); + return surf; } +MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), current_surface(0), n_surfaces(0) {} + int MinuiBackendAdf::InterfaceInit() { adf_interface_data intf_data; - int err = adf_get_interface_data(intf_fd, &intf_data); - if (err < 0) return err; + if (int err = adf_get_interface_data(intf_fd, &intf_data); err < 0) return err; - int ret = 0; - err = SurfaceInit(&intf_data.current_mode, &surfaces[0]); - if (err < 0) { - fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err)); - ret = err; + int result = 0; + surfaces[0] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result); + if (!surfaces[0]) { + fprintf(stderr, "Failed to allocate surface 0: %s\n", strerror(-result)); goto done; } - err = SurfaceInit(&intf_data.current_mode, &surfaces[1]); - if (err < 0) { - fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err)); - surfaces[1] = {}; + surfaces[1] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result); + if (!surfaces[1]) { + fprintf(stderr, "Failed to allocate surface 1: %s\n", strerror(-result)); n_surfaces = 1; } else { n_surfaces = 2; @@ -80,7 +90,7 @@ int MinuiBackendAdf::InterfaceInit() { done: adf_free_interface_data(&intf_data); - return ret; + return result; } int MinuiBackendAdf::DeviceInit(adf_device* dev) { @@ -91,7 +101,7 @@ int MinuiBackendAdf::DeviceInit(adf_device* dev) { err = adf_device_attach(dev, eng_id, intf_id); if (err < 0 && err != -EALREADY) return err; - intf_fd = adf_interface_open(dev, intf_id, O_RDWR); + intf_fd = adf_interface_open(dev, intf_id, O_RDWR | O_CLOEXEC); if (intf_fd < 0) return intf_fd; err = InterfaceInit(); @@ -104,15 +114,16 @@ int MinuiBackendAdf::DeviceInit(adf_device* dev) { } GRSurface* MinuiBackendAdf::Init() { -#if defined(RECOVERY_ABGR) - format = DRM_FORMAT_ABGR8888; -#elif defined(RECOVERY_BGRA) - format = DRM_FORMAT_BGRA8888; -#elif defined(RECOVERY_RGBX) - format = DRM_FORMAT_RGBX8888; -#else - format = DRM_FORMAT_RGB565; -#endif + PixelFormat pixel_format = gr_pixel_format(); + if (pixel_format == PixelFormat::ABGR) { + format = DRM_FORMAT_ABGR8888; + } else if (pixel_format == PixelFormat::BGRA) { + format = DRM_FORMAT_BGRA8888; + } else if (pixel_format == PixelFormat::RGBX) { + format = DRM_FORMAT_RGBX8888; + } else { + format = DRM_FORMAT_RGB565; + } adf_id_t* dev_ids = nullptr; ssize_t n_dev_ids = adf_devices(&dev_ids); @@ -152,12 +163,12 @@ GRSurface* MinuiBackendAdf::Init() { } void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) { - static constexpr unsigned int warningTimeout = 3000; + static constexpr unsigned int kWarningTimeout = 3000; if (surf == nullptr) return; if (surf->fence_fd >= 0) { - int err = sync_wait(surf->fence_fd, warningTimeout); + int err = sync_wait(surf->fence_fd, kWarningTimeout); if (err < 0) { perror("adf sync fence wait error\n"); } @@ -168,31 +179,22 @@ void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) { } GRSurface* MinuiBackendAdf::Flip() { - GRSurfaceAdf* surf = &surfaces[current_surface]; + const auto& surf = surfaces[current_surface]; int fence_fd = adf_interface_simple_post(intf_fd, eng_id, surf->width, surf->height, format, surf->fd, surf->offset, surf->pitch, -1); if (fence_fd >= 0) surf->fence_fd = fence_fd; current_surface = (current_surface + 1) % n_surfaces; - Sync(&surfaces[current_surface]); - return &surfaces[current_surface]; + Sync(surfaces[current_surface].get()); + return surfaces[current_surface].get(); } void MinuiBackendAdf::Blank(bool blank) { adf_interface_blank(intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON); } -void MinuiBackendAdf::SurfaceDestroy(GRSurfaceAdf* surf) { - munmap(surf->data, surf->pitch * surf->height); - close(surf->fence_fd); - close(surf->fd); -} - MinuiBackendAdf::~MinuiBackendAdf() { adf_device_close(&dev); - for (unsigned int i = 0; i < n_surfaces; i++) { - SurfaceDestroy(&surfaces[i]); - } if (intf_fd >= 0) close(intf_fd); } diff --git a/minui/graphics_adf.h b/minui/graphics_adf.h index 2f019ed0bb..79d8d2acbf 100644 --- a/minui/graphics_adf.h +++ b/minui/graphics_adf.h @@ -14,45 +14,63 @@ * limitations under the License. */ -#ifndef _GRAPHICS_ADF_H_ -#define _GRAPHICS_ADF_H_ +#pragma once + +#include +#include +#include + +#include #include #include "graphics.h" +#include "minui/minui.h" class GRSurfaceAdf : public GRSurface { - private: - int fence_fd; - int fd; - __u32 offset; - __u32 pitch; + public: + ~GRSurfaceAdf() override; + static std::unique_ptr Create(int intf_fd, const drm_mode_modeinfo* mode, + __u32 format, int* err); + + uint8_t* data() override { + return mmapped_buffer_; + } + + private: friend class MinuiBackendAdf; + + GRSurfaceAdf(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes, __u32 offset, + __u32 pitch, int fd) + : GRSurface(width, height, row_bytes, pixel_bytes), offset(offset), pitch(pitch), fd(fd) {} + + const __u32 offset; + const __u32 pitch; + + int fd; + int fence_fd{ -1 }; + uint8_t* mmapped_buffer_{ nullptr }; }; class MinuiBackendAdf : public MinuiBackend { public: + MinuiBackendAdf(); + ~MinuiBackendAdf() override; GRSurface* Init() override; GRSurface* Flip() override; void Blank(bool) override; - ~MinuiBackendAdf() override; - MinuiBackendAdf(); private: - int SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf); int InterfaceInit(); int DeviceInit(adf_device* dev); - void SurfaceDestroy(GRSurfaceAdf* surf); void Sync(GRSurfaceAdf* surf); int intf_fd; adf_id_t eng_id; __u32 format; adf_device dev; - unsigned int current_surface; - unsigned int n_surfaces; - GRSurfaceAdf surfaces[2]; + size_t current_surface; + size_t n_surfaces; + std::unique_ptr surfaces[2]; }; - -#endif // _GRAPHICS_ADF_H_ diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp index e7d4b38ef6..95759e3827 100644 --- a/minui/graphics_drm.cpp +++ b/minui/graphics_drm.cpp @@ -17,78 +17,44 @@ #include "graphics_drm.h" #include +#include #include #include #include #include #include +#include + +#include +#include +#include #include #include #include #include "minui/minui.h" -#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A))) - -MinuiBackendDrm::MinuiBackendDrm() - : GRSurfaceDrms(), main_monitor_crtc(nullptr), main_monitor_connector(nullptr), drm_fd(-1) {} - -void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) { - if (crtc) { - drmModeSetCrtc(drm_fd, crtc->crtc_id, - 0, // fb_id - 0, 0, // x,y - nullptr, // connectors - 0, // connector_count - nullptr); // mode - } -} - -void MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface) { - int32_t ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y - &main_monitor_connector->connector_id, - 1, // connector_count - &main_monitor_crtc->mode); - - if (ret) { - printf("drmModeSetCrtc failed ret=%d\n", ret); +GRSurfaceDrm::~GRSurfaceDrm() { + if (mmapped_buffer_) { + munmap(mmapped_buffer_, row_bytes * height); } -} -void MinuiBackendDrm::Blank(bool blank) { - if (blank) { - DrmDisableCrtc(drm_fd, main_monitor_crtc); - } else { - DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]); - } -} - -void MinuiBackendDrm::DrmDestroySurface(GRSurfaceDrm* surface) { - if (!surface) return; - - if (surface->data) { - munmap(surface->data, surface->row_bytes * surface->height); - } - - if (surface->fb_id) { - int ret = drmModeRmFB(drm_fd, surface->fb_id); - if (ret) { - printf("drmModeRmFB failed ret=%d\n", ret); + if (fb_id) { + if (drmModeRmFB(drm_fd_, fb_id) != 0) { + perror("Failed to drmModeRmFB"); + // Falling through to free other resources. } } - if (surface->handle) { + if (handle) { drm_gem_close gem_close = {}; - gem_close.handle = surface->handle; + gem_close.handle = handle; - int ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close); - if (ret) { - printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret); + if (drmIoctl(drm_fd_, DRM_IOCTL_GEM_CLOSE, &gem_close) != 0) { + perror("Failed to DRM_IOCTL_GEM_CLOSE"); } } - - delete surface; } static int drm_format_to_bpp(uint32_t format) { @@ -96,6 +62,8 @@ static int drm_format_to_bpp(uint32_t format) { case DRM_FORMAT_ABGR8888: case DRM_FORMAT_BGRA8888: case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_ARGB8888: case DRM_FORMAT_BGRX8888: case DRM_FORMAT_XBGR8888: case DRM_FORMAT_XRGB8888: @@ -108,20 +76,24 @@ static int drm_format_to_bpp(uint32_t format) { } } -GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) { - GRSurfaceDrm* surface = new GRSurfaceDrm; - *surface = {}; - +std::unique_ptr GRSurfaceDrm::Create(int drm_fd, int width, int height) { uint32_t format; -#if defined(RECOVERY_ABGR) - format = DRM_FORMAT_RGBA8888; -#elif defined(RECOVERY_BGRA) - format = DRM_FORMAT_ARGB8888; -#elif defined(RECOVERY_RGBX) - format = DRM_FORMAT_XBGR8888; -#else - format = DRM_FORMAT_RGB565; -#endif + PixelFormat pixel_format = gr_pixel_format(); + // PixelFormat comes in byte order, whereas DRM_FORMAT_* uses little-endian + // (external/libdrm/include/drm/drm_fourcc.h). Note that although drm_fourcc.h also defines a + // macro of DRM_FORMAT_BIG_ENDIAN, it doesn't seem to be actually supported (see the discussion + // in https://lists.freedesktop.org/archives/amd-gfx/2017-May/008560.html). + if (pixel_format == PixelFormat::ABGR) { + format = DRM_FORMAT_RGBA8888; + } else if (pixel_format == PixelFormat::BGRA) { + format = DRM_FORMAT_ARGB8888; + } else if (pixel_format == PixelFormat::RGBX) { + format = DRM_FORMAT_XBGR8888; + } else if (pixel_format == PixelFormat::ARGB) { + format = DRM_FORMAT_BGRA8888; + } else { + format = DRM_FORMAT_RGB565; + } drm_mode_create_dumb create_dumb = {}; create_dumb.height = height; @@ -129,53 +101,74 @@ GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) { create_dumb.bpp = drm_format_to_bpp(format); create_dumb.flags = 0; - int ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); - if (ret) { - printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n", ret); - DrmDestroySurface(surface); + if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) != 0) { + perror("Failed to DRM_IOCTL_MODE_CREATE_DUMB"); return nullptr; } - surface->handle = create_dumb.handle; + + // Cannot use std::make_unique to access non-public ctor. + auto surface = std::unique_ptr(new GRSurfaceDrm( + width, height, create_dumb.pitch, create_dumb.bpp / 8, drm_fd, create_dumb.handle)); uint32_t handles[4], pitches[4], offsets[4]; handles[0] = surface->handle; pitches[0] = create_dumb.pitch; offsets[0] = 0; - - ret = - drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &(surface->fb_id), 0); - if (ret) { - printf("drmModeAddFB2 failed ret=%d\n", ret); - DrmDestroySurface(surface); + if (drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &surface->fb_id, 0) != + 0) { + perror("Failed to drmModeAddFB2"); return nullptr; } drm_mode_map_dumb map_dumb = {}; map_dumb.handle = create_dumb.handle; - ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); - if (ret) { - printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n", ret); - DrmDestroySurface(surface); + if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb) != 0) { + perror("Failed to DRM_IOCTL_MODE_MAP_DUMB"); return nullptr; } - surface->height = height; - surface->width = width; - surface->row_bytes = create_dumb.pitch; - surface->pixel_bytes = create_dumb.bpp / 8; - surface->data = static_cast(mmap(nullptr, surface->height * surface->row_bytes, - PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, - map_dumb.offset)); - if (surface->data == MAP_FAILED) { - perror("mmap() failed"); - DrmDestroySurface(surface); + auto mmapped = mmap(nullptr, surface->height * surface->row_bytes, PROT_READ | PROT_WRITE, + MAP_SHARED, drm_fd, map_dumb.offset); + if (mmapped == MAP_FAILED) { + perror("Failed to mmap()"); return nullptr; } - + surface->mmapped_buffer_ = static_cast(mmapped); return surface; } +void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) { + if (crtc) { + drmModeSetCrtc(drm_fd, crtc->crtc_id, + 0, // fb_id + 0, 0, // x,y + nullptr, // connectors + 0, // connector_count + nullptr); // mode + } +} + +bool MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, + const std::unique_ptr& surface) { + if (drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y + &main_monitor_connector->connector_id, + 1, // connector_count + &main_monitor_crtc->mode) != 0) { + perror("Failed to drmModeSetCrtc"); + return false; + } + return true; +} + +void MinuiBackendDrm::Blank(bool blank) { + if (blank) { + DrmDisableCrtc(drm_fd, main_monitor_crtc); + } else { + DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]); + } +} + static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources, drmModeConnector* connector) { // Find the encoder. If we already have one, just use it. @@ -257,7 +250,7 @@ drmModeConnector* MinuiBackendDrm::FindMainMonitor(int fd, drmModeRes* resources do { main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]); i++; - } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority)); + } while (!main_monitor_connector && i < arraysize(kConnectorPriority)); /* If we didn't find a connector, grab the first one that is connected. */ if (!main_monitor_connector) { @@ -291,60 +284,53 @@ void MinuiBackendDrm::DisableNonMainCrtcs(int fd, drmModeRes* resources, drmMode GRSurface* MinuiBackendDrm::Init() { drmModeRes* res = nullptr; + drm_fd = -1; /* Consider DRM devices in order. */ for (int i = 0; i < DRM_MAX_MINOR; i++) { - char* dev_name; - int ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i); - if (ret < 0) continue; - - drm_fd = open(dev_name, O_RDWR, 0); - free(dev_name); - if (drm_fd < 0) continue; + auto dev_name = android::base::StringPrintf(DRM_DEV_NAME, DRM_DIR_NAME, i); + android::base::unique_fd fd(open(dev_name.c_str(), O_RDWR | O_CLOEXEC)); + if (fd == -1) continue; - uint64_t cap = 0; /* We need dumb buffers. */ - ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap); - if (ret || cap == 0) { - close(drm_fd); + if (uint64_t cap = 0; drmGetCap(fd.get(), DRM_CAP_DUMB_BUFFER, &cap) != 0 || cap == 0) { continue; } - res = drmModeGetResources(drm_fd); + res = drmModeGetResources(fd.get()); if (!res) { - close(drm_fd); continue; } /* Use this device if it has at least one connected monitor. */ if (res->count_crtcs > 0 && res->count_connectors > 0) { - if (find_first_connected_connector(drm_fd, res)) break; + if (find_first_connected_connector(fd.get(), res)) { + drm_fd = fd.release(); + break; + } } drmModeFreeResources(res); - close(drm_fd); res = nullptr; } - if (drm_fd < 0 || res == nullptr) { - perror("cannot find/open a drm device"); + if (drm_fd == -1 || res == nullptr) { + perror("Failed to find/open a drm device"); return nullptr; } uint32_t selected_mode; main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode); - if (!main_monitor_connector) { - printf("main_monitor_connector not found\n"); + fprintf(stderr, "Failed to find main_monitor_connector\n"); drmModeFreeResources(res); close(drm_fd); return nullptr; } main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector); - if (!main_monitor_crtc) { - printf("main_monitor_crtc not found\n"); + fprintf(stderr, "Failed to find main_monitor_crtc\n"); drmModeFreeResources(res); close(drm_fd); return nullptr; @@ -359,35 +345,66 @@ GRSurface* MinuiBackendDrm::Init() { drmModeFreeResources(res); - GRSurfaceDrms[0] = DrmCreateSurface(width, height); - GRSurfaceDrms[1] = DrmCreateSurface(width, height); + GRSurfaceDrms[0] = GRSurfaceDrm::Create(drm_fd, width, height); + GRSurfaceDrms[1] = GRSurfaceDrm::Create(drm_fd, width, height); if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) { - // GRSurfaceDrms and drm_fd should be freed in d'tor. return nullptr; } current_buffer = 0; - DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1]); + // We will likely encounter errors in the backend functions (i.e. Flip) if EnableCrtc fails. + if (!DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1])) { + return nullptr; + } - return GRSurfaceDrms[0]; + return GRSurfaceDrms[0].get(); +} + +static void page_flip_complete(__unused int fd, + __unused unsigned int sequence, + __unused unsigned int tv_sec, + __unused unsigned int tv_usec, + void *user_data) { + *static_cast(user_data) = false; } GRSurface* MinuiBackendDrm::Flip() { - int ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, - GRSurfaceDrms[current_buffer]->fb_id, 0, nullptr); - if (ret < 0) { - printf("drmModePageFlip failed ret=%d\n", ret); + bool ongoing_flip = true; + if (drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, GRSurfaceDrms[current_buffer]->fb_id, + DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip) != 0) { + perror("Failed to drmModePageFlip"); return nullptr; } + + while (ongoing_flip) { + struct pollfd fds = { + .fd = drm_fd, + .events = POLLIN + }; + + if (poll(&fds, 1, -1) == -1 || !(fds.revents & POLLIN)) { + perror("Failed to poll() on drm fd"); + break; + } + + drmEventContext evctx = { + .version = DRM_EVENT_CONTEXT_VERSION, + .page_flip_handler = page_flip_complete + }; + + if (drmHandleEvent(drm_fd, &evctx) != 0) { + perror("Failed to drmHandleEvent"); + break; + } + } + current_buffer = 1 - current_buffer; - return GRSurfaceDrms[current_buffer]; + return GRSurfaceDrms[current_buffer].get(); } MinuiBackendDrm::~MinuiBackendDrm() { DrmDisableCrtc(drm_fd, main_monitor_crtc); - DrmDestroySurface(GRSurfaceDrms[0]); - DrmDestroySurface(GRSurfaceDrms[1]); drmModeFreeCrtc(main_monitor_crtc); drmModeFreeConnector(main_monitor_connector); close(drm_fd); diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h index de96212058..57ba39b837 100644 --- a/minui/graphics_drm.h +++ b/minui/graphics_drm.h @@ -14,45 +14,61 @@ * limitations under the License. */ -#ifndef _GRAPHICS_DRM_H_ -#define _GRAPHICS_DRM_H_ +#pragma once +#include #include +#include + #include #include "graphics.h" #include "minui/minui.h" class GRSurfaceDrm : public GRSurface { - private: - uint32_t fb_id; - uint32_t handle; + public: + ~GRSurfaceDrm() override; + + // Creates a GRSurfaceDrm instance. + static std::unique_ptr Create(int drm_fd, int width, int height); + uint8_t* data() override { + return mmapped_buffer_; + } + + private: friend class MinuiBackendDrm; + + GRSurfaceDrm(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes, int drm_fd, + uint32_t handle) + : GRSurface(width, height, row_bytes, pixel_bytes), drm_fd_(drm_fd), handle(handle) {} + + const int drm_fd_; + + uint32_t fb_id{ 0 }; + uint32_t handle{ 0 }; + uint8_t* mmapped_buffer_{ nullptr }; }; class MinuiBackendDrm : public MinuiBackend { public: + MinuiBackendDrm() = default; + ~MinuiBackendDrm() override; + GRSurface* Init() override; GRSurface* Flip() override; void Blank(bool) override; - ~MinuiBackendDrm() override; - MinuiBackendDrm(); private: void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc); - void DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface); - GRSurfaceDrm* DrmCreateSurface(int width, int height); - void DrmDestroySurface(GRSurfaceDrm* surface); + bool DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, const std::unique_ptr& surface); void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc); drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index); - GRSurfaceDrm* GRSurfaceDrms[2]; - int current_buffer; - drmModeCrtc* main_monitor_crtc; - drmModeConnector* main_monitor_connector; - int drm_fd; + std::unique_ptr GRSurfaceDrms[2]; + int current_buffer{ 0 }; + drmModeCrtc* main_monitor_crtc{ nullptr }; + drmModeConnector* main_monitor_connector{ nullptr }; + int drm_fd{ -1 }; }; - -#endif // _GRAPHICS_DRM_H_ diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp index 746f42aaa7..2584017d67 100644 --- a/minui/graphics_fbdev.cpp +++ b/minui/graphics_fbdev.cpp @@ -26,21 +26,29 @@ #include #include +#include + +#include + #include "minui/minui.h" -MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {} +std::unique_ptr GRSurfaceFbdev::Create(size_t width, size_t height, + size_t row_bytes, size_t pixel_bytes) { + // Cannot use std::make_unique to access non-public ctor. + return std::unique_ptr(new GRSurfaceFbdev(width, height, row_bytes, pixel_bytes)); +} void MinuiBackendFbdev::Blank(bool blank) { int ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); if (ret < 0) perror("ioctl(): blank"); } -void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) { +void MinuiBackendFbdev::SetDisplayedFramebuffer(size_t n) { if (n > 1 || !double_buffered) return; - vi.yres_virtual = gr_framebuffer[0].height * 2; - vi.yoffset = n * gr_framebuffer[0].height; - vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8; + vi.yres_virtual = gr_framebuffer[0]->height * 2; + vi.yoffset = n * gr_framebuffer[0]->height; + vi.bits_per_pixel = gr_framebuffer[0]->pixel_bytes * 8; if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { perror("active fb swap failed"); } @@ -48,7 +56,7 @@ void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) { } GRSurface* MinuiBackendFbdev::Init() { - int fd = open("/dev/graphics/fb0", O_RDWR); + android::base::unique_fd fd(open("/dev/graphics/fb0", O_RDWR | O_CLOEXEC)); if (fd == -1) { perror("cannot open fb0"); return nullptr; @@ -57,13 +65,11 @@ GRSurface* MinuiBackendFbdev::Init() { fb_fix_screeninfo fi; if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { perror("failed to get fb0 info"); - close(fd); return nullptr; } if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) { perror("failed to get fb0 info"); - close(fd); return nullptr; } @@ -90,50 +96,41 @@ GRSurface* MinuiBackendFbdev::Init() { void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (bits == MAP_FAILED) { perror("failed to mmap framebuffer"); - close(fd); return nullptr; } memset(bits, 0, fi.smem_len); - gr_framebuffer[0].width = vi.xres; - gr_framebuffer[0].height = vi.yres; - gr_framebuffer[0].row_bytes = fi.line_length; - gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8; - gr_framebuffer[0].data = static_cast(bits); - memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes); + gr_framebuffer[0] = + GRSurfaceFbdev::Create(vi.xres, vi.yres, fi.line_length, vi.bits_per_pixel / 8); + gr_framebuffer[0]->buffer_ = static_cast(bits); + memset(gr_framebuffer[0]->buffer_, 0, gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes); + + gr_framebuffer[1] = + GRSurfaceFbdev::Create(gr_framebuffer[0]->width, gr_framebuffer[0]->height, + gr_framebuffer[0]->row_bytes, gr_framebuffer[0]->pixel_bytes); /* check if we can use double buffering */ if (vi.yres * fi.line_length * 2 <= fi.smem_len) { double_buffered = true; - memcpy(gr_framebuffer + 1, gr_framebuffer, sizeof(GRSurface)); - gr_framebuffer[1].data = - gr_framebuffer[0].data + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes; - - gr_draw = gr_framebuffer + 1; - + gr_framebuffer[1]->buffer_ = + gr_framebuffer[0]->buffer_ + gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes; } else { double_buffered = false; - // Without double-buffering, we allocate RAM for a buffer to - // draw in, and then "flipping" the buffer consists of a - // memcpy from the buffer we allocated to the framebuffer. - - gr_draw = static_cast(malloc(sizeof(GRSurface))); - memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface)); - gr_draw->data = static_cast(malloc(gr_draw->height * gr_draw->row_bytes)); - if (!gr_draw->data) { - perror("failed to allocate in-memory surface"); - return nullptr; - } + // Without double-buffering, we allocate RAM for a buffer to draw in, and then "flipping" the + // buffer consists of a memcpy from the buffer we allocated to the framebuffer. + memory_buffer.resize(gr_framebuffer[1]->height * gr_framebuffer[1]->row_bytes); + gr_framebuffer[1]->buffer_ = memory_buffer.data(); } - memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes); - fb_fd = fd; + gr_draw = gr_framebuffer[1].get(); + memset(gr_draw->buffer_, 0, gr_draw->height * gr_draw->row_bytes); + fb_fd = std::move(fd); SetDisplayedFramebuffer(0); - printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); + printf("framebuffer: %d (%zu x %zu)\n", fb_fd.get(), gr_draw->width, gr_draw->height); Blank(true); Blank(false); @@ -143,25 +140,13 @@ GRSurface* MinuiBackendFbdev::Init() { GRSurface* MinuiBackendFbdev::Flip() { if (double_buffered) { - // Change gr_draw to point to the buffer currently displayed, - // then flip the driver so we're displaying the other buffer - // instead. - gr_draw = gr_framebuffer + displayed_buffer; + // Change gr_draw to point to the buffer currently displayed, then flip the driver so we're + // displaying the other buffer instead. + gr_draw = gr_framebuffer[displayed_buffer].get(); SetDisplayedFramebuffer(1 - displayed_buffer); } else { // Copy from the in-memory surface to the framebuffer. - memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes); + memcpy(gr_framebuffer[0]->buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes); } return gr_draw; } - -MinuiBackendFbdev::~MinuiBackendFbdev() { - close(fb_fd); - fb_fd = -1; - - if (!double_buffered && gr_draw) { - free(gr_draw->data); - free(gr_draw); - } - gr_draw = nullptr; -} diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h index 107e195673..596ba74eaf 100644 --- a/minui/graphics_fbdev.h +++ b/minui/graphics_fbdev.h @@ -14,31 +14,58 @@ * limitations under the License. */ -#ifndef _GRAPHICS_FBDEV_H_ -#define _GRAPHICS_FBDEV_H_ +#pragma once #include +#include +#include + +#include +#include + +#include #include "graphics.h" #include "minui/minui.h" +class GRSurfaceFbdev : public GRSurface { + public: + // Creates and returns a GRSurfaceFbdev instance, or nullptr on error. + static std::unique_ptr Create(size_t width, size_t height, size_t row_bytes, + size_t pixel_bytes); + + uint8_t* data() override { + return buffer_; + } + + protected: + using GRSurface::GRSurface; + + private: + friend class MinuiBackendFbdev; + + // Points to the start of the buffer: either the mmap'd framebuffer or one allocated in-memory. + uint8_t* buffer_{ nullptr }; +}; + class MinuiBackendFbdev : public MinuiBackend { public: + MinuiBackendFbdev() = default; + ~MinuiBackendFbdev() override = default; + GRSurface* Init() override; GRSurface* Flip() override; void Blank(bool) override; - ~MinuiBackendFbdev() override; - MinuiBackendFbdev(); private: - void SetDisplayedFramebuffer(unsigned n); + void SetDisplayedFramebuffer(size_t n); - GRSurface gr_framebuffer[2]; + std::unique_ptr gr_framebuffer[2]; + // Points to the current surface (i.e. one of the two gr_framebuffer's). + GRSurfaceFbdev* gr_draw{ nullptr }; bool double_buffered; - GRSurface* gr_draw; - int displayed_buffer; + std::vector memory_buffer; + size_t displayed_buffer{ 0 }; fb_var_screeninfo vi; - int fb_fd; + android::base::unique_fd fb_fd; }; - -#endif // _GRAPHICS_FBDEV_H_ diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h index f9da199998..163e41dc6b 100644 --- a/minui/include/minui/minui.h +++ b/minui/include/minui/minui.h @@ -14,25 +14,73 @@ * limitations under the License. */ -#ifndef _MINUI_H_ -#define _MINUI_H_ +#pragma once +#include +#include #include #include +#include #include #include +#include +#include + // // Graphics. // -struct GRSurface { - int width; - int height; - int row_bytes; - int pixel_bytes; - unsigned char* data; +class GRSurface { + public: + static constexpr size_t kSurfaceDataAlignment = 8; + + virtual ~GRSurface() = default; + + // Creates and returns a GRSurface instance that's sufficient for storing an image of the given + // size (i.e. row_bytes * height). The starting address of the surface data is aligned to + // kSurfaceDataAlignment. Returns the created GRSurface instance (in std::unique_ptr), or nullptr + // on error. + static std::unique_ptr Create(size_t width, size_t height, size_t row_bytes, + size_t pixel_bytes); + + // Clones the current GRSurface instance (i.e. an image). + std::unique_ptr Clone() const; + + virtual uint8_t* data() { + return data_.get(); + } + + const uint8_t* data() const { + return const_cast(const_cast(this)->data()); + } + + size_t data_size() const { + return data_size_; + } + + size_t width; + size_t height; + size_t row_bytes; + size_t pixel_bytes; + + protected: + GRSurface(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes) + : width(width), height(height), row_bytes(row_bytes), pixel_bytes(pixel_bytes) {} + + private: + // The deleter for data_, whose data is allocated via aligned_alloc(3). + struct DataDeleter { + void operator()(uint8_t* data) { + free(data); + } + }; + + std::unique_ptr data_; + size_t data_size_; + + DISALLOW_COPY_AND_ASSIGN(GRSurface); }; struct GRFont { @@ -41,14 +89,28 @@ struct GRFont { int char_height; }; -enum GRRotation { - ROTATION_NONE = 0, - ROTATION_RIGHT = 1, - ROTATION_DOWN = 2, - ROTATION_LEFT = 3, +enum class GRRotation : int { + NONE = 0, + RIGHT = 1, + DOWN = 2, + LEFT = 3, +}; + +enum class PixelFormat : int { + UNKNOWN = 0, + ABGR = 1, + RGBX = 2, + BGRA = 3, + ARGB = 4, }; +// Initializes the graphics backend and loads font file. Returns 0 on success, or -1 on error. Note +// that the font initialization failure would be non-fatal, as caller may not need to draw any text +// at all. Caller can check the font initialization result via gr_sys_font() as needed. int gr_init(); + +// Frees the allocated resources. The function is idempotent, and safe to be called if gr_init() +// didn't finish successfully. void gr_exit(); int gr_fb_width(); @@ -57,25 +119,31 @@ int gr_fb_height(); void gr_flip(); void gr_fb_blank(bool blank); -void gr_clear(); // clear entire surface to current color +// Clears entire surface to current color. +void gr_clear(); void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); void gr_fill(int x1, int y1, int x2, int y2); -void gr_texticon(int x, int y, GRSurface* icon); +void gr_texticon(int x, int y, const GRSurface* icon); const GRFont* gr_sys_font(); int gr_init_font(const char* name, GRFont** dest); void gr_text(const GRFont* font, int x, int y, const char* s, bool bold); +// Returns -1 if font is nullptr. int gr_measure(const GRFont* font, const char* s); -void gr_font_size(const GRFont* font, int* x, int* y); +// Returns -1 if font is nullptr. +int gr_font_size(const GRFont* font, int* x, int* y); -void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy); -unsigned int gr_get_width(GRSurface* surface); -unsigned int gr_get_height(GRSurface* surface); +void gr_blit(const GRSurface* source, int sx, int sy, int w, int h, int dx, int dy); +unsigned int gr_get_width(const GRSurface* surface); +unsigned int gr_get_height(const GRSurface* surface); -// Set rotation, flips gr_fb_width/height if 90 degree rotation difference +// Sets rotation, flips gr_fb_width/height if 90 degree rotation difference void gr_rotate(GRRotation rotation); +// Returns the current PixelFormat being used. +PixelFormat gr_pixel_format(); + // // Input events. // @@ -87,7 +155,7 @@ using ev_set_key_callback = std::function; int ev_init(ev_callback input_cb, bool allow_touch_inputs = false); void ev_exit(); -int ev_add_fd(int fd, ev_callback cb); +int ev_add_fd(android::base::unique_fd&& fd, ev_callback cb); void ev_iterate_available_keys(const std::function& f); void ev_iterate_touch_inputs(const std::function& action); int ev_sync_key_state(const ev_set_key_callback& set_key_cb); @@ -146,5 +214,3 @@ std::vector get_locales_in_png(const std::string& png_name); // Free a surface allocated by any of the res_create_*_surface() // functions. void res_free_surface(GRSurface* surface); - -#endif diff --git a/minui/include/private/resources.h b/minui/include/private/resources.h new file mode 100644 index 0000000000..047ebe2e3a --- /dev/null +++ b/minui/include/private/resources.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +#include + +// This class handles the PNG file parsing. It also holds the ownership of the PNG pointer and the +// opened file pointer. Both will be destroyed / closed when this object goes out of scope. +class PngHandler { + public: + // Constructs an instance by loading the PNG file from '/res/images/.png', or ''. + PngHandler(const std::string& name); + + ~PngHandler(); + + png_uint_32 width() const { + return width_; + } + + png_uint_32 height() const { + return height_; + } + + png_byte channels() const { + return channels_; + } + + int bit_depth() const { + return bit_depth_; + } + + int color_type() const { + return color_type_; + } + + png_structp png_ptr() const { + return png_ptr_; + } + + png_infop info_ptr() const { + return info_ptr_; + } + + int error_code() const { + return error_code_; + }; + + operator bool() const { + return error_code_ == 0; + } + + private: + png_structp png_ptr_{ nullptr }; + png_infop info_ptr_{ nullptr }; + png_uint_32 width_; + png_uint_32 height_; + png_byte channels_; + int bit_depth_; + int color_type_; + + // The |error_code_| is set to a negative value if an error occurs when opening the png file. + int error_code_{ 0 }; + // After initialization, we'll keep the file pointer open before destruction of PngHandler. + std::unique_ptr png_fp_{ nullptr, fclose }; +}; + +// Overrides the default resource dir, for testing purpose. +void res_set_resource_dir(const std::string&); diff --git a/minui/mkfont.c b/minui/mkfont.c deleted file mode 100644 index 61a5edeb29..0000000000 --- a/minui/mkfont.c +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include - -int main(int argc, char *argv) -{ - unsigned n; - unsigned char *x; - unsigned m; - unsigned run_val; - unsigned run_count; - - n = gimp_image.width * gimp_image.height; - m = 0; - x = gimp_image.pixel_data; - - printf("struct {\n"); - printf(" unsigned width;\n"); - printf(" unsigned height;\n"); - printf(" unsigned cwidth;\n"); - printf(" unsigned cheight;\n"); - printf(" unsigned char rundata[];\n"); - printf("} font = {\n"); - printf(" .width = %d,\n .height = %d,\n .cwidth = %d,\n .cheight = %d,\n", gimp_image.width, gimp_image.height, - gimp_image.width / 96, gimp_image.height); - printf(" .rundata = {\n"); - - run_val = (*x ? 0 : 255); - run_count = 1; - n--; - x+=3; - - while(n-- > 0) { - unsigned val = (*x ? 0 : 255); - x+=3; - if((val == run_val) && (run_count < 127)) { - run_count++; - } else { -eject: - printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00)); - run_val = val; - run_count = 1; - m += 5; - if(m >= 75) { - printf("\n"); - m = 0; - } - } - } - printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00)); - printf("\n0x00,"); - printf("\n"); - printf(" }\n};\n"); - return 0; -} diff --git a/minui/resources.cpp b/minui/resources.cpp index 52ab60b1b5..f635acd1aa 100644 --- a/minui/resources.cpp +++ b/minui/resources.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "private/resources.h" + #include #include #include @@ -25,87 +27,55 @@ #include #include +#include #include #include #include #include -#include #include #include #include "minui/minui.h" -#define SURFACE_DATA_ALIGNMENT 8 - -static GRSurface* malloc_surface(size_t data_size) { - size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT; - unsigned char* temp = static_cast(malloc(size)); - if (temp == NULL) return NULL; - GRSurface* surface = reinterpret_cast(temp); - surface->data = temp + sizeof(GRSurface) + - (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT)); - return surface; +static std::string g_resource_dir{ "/res/images" }; + +std::unique_ptr GRSurface::Create(size_t width, size_t height, size_t row_bytes, + size_t pixel_bytes) { + if (width == 0 || row_bytes == 0 || height == 0 || pixel_bytes == 0) return nullptr; + if (std::numeric_limits::max() / row_bytes < height) return nullptr; + + // Cannot use std::make_unique to access non-public ctor. + auto result = std::unique_ptr(new GRSurface(width, height, row_bytes, pixel_bytes)); + size_t data_size = row_bytes * height; + result->data_size_ = + (data_size + kSurfaceDataAlignment - 1) / kSurfaceDataAlignment * kSurfaceDataAlignment; + result->data_.reset( + static_cast(aligned_alloc(kSurfaceDataAlignment, result->data_size_))); + if (!result->data_) return nullptr; + return result; } -// This class handles the png file parsing. It also holds the ownership of the png pointer and the -// opened file pointer. Both will be destroyed/closed when this object goes out of scope. -class PngHandler { - public: - PngHandler(const std::string& name); - - ~PngHandler(); - - png_uint_32 width() const { - return width_; - } - - png_uint_32 height() const { - return height_; - } - - png_byte channels() const { - return channels_; - } - - png_structp png_ptr() const { - return png_ptr_; - } - - png_infop info_ptr() const { - return info_ptr_; - } - - int error_code() const { - return error_code_; - }; - - operator bool() const { - return error_code_ == 0; - } - - private: - png_structp png_ptr_{ nullptr }; - png_infop info_ptr_{ nullptr }; - png_uint_32 width_; - png_uint_32 height_; - png_byte channels_; - - // The |error_code_| is set to a negative value if an error occurs when opening the png file. - int error_code_; - // After initialization, we'll keep the file pointer open before destruction of PngHandler. - std::unique_ptr png_fp_; -}; +std::unique_ptr GRSurface::Clone() const { + auto result = GRSurface::Create(width, height, row_bytes, pixel_bytes); + if (!result) return nullptr; + memcpy(result->data(), data(), data_size_); + return result; +} -PngHandler::PngHandler(const std::string& name) : error_code_(0), png_fp_(nullptr, fclose) { - std::string res_path = android::base::StringPrintf("/res/images/%s.png", name.c_str()); +PngHandler::PngHandler(const std::string& name) { + std::string res_path = g_resource_dir + "/" + name + ".png"; png_fp_.reset(fopen(res_path.c_str(), "rbe")); + // Try to read from |name| if the resource path does not work. + if (!png_fp_) { + png_fp_.reset(fopen(name.c_str(), "rbe")); + } if (!png_fp_) { error_code_ = -1; return; } - unsigned char header[8]; + uint8_t header[8]; size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get()); if (bytesRead != sizeof(header)) { error_code_ = -2; @@ -138,19 +108,17 @@ PngHandler::PngHandler(const std::string& name) : error_code_(0), png_fp_(nullpt png_set_sig_bytes(png_ptr_, sizeof(header)); png_read_info(png_ptr_, info_ptr_); - int color_type; - int bit_depth; - png_get_IHDR(png_ptr_, info_ptr_, &width_, &height_, &bit_depth, &color_type, nullptr, nullptr, + png_get_IHDR(png_ptr_, info_ptr_, &width_, &height_, &bit_depth_, &color_type_, nullptr, nullptr, nullptr); channels_ = png_get_channels(png_ptr_, info_ptr_); - if (bit_depth == 8 && channels_ == 3 && color_type == PNG_COLOR_TYPE_RGB) { + if (bit_depth_ == 8 && channels_ == 3 && color_type_ == PNG_COLOR_TYPE_RGB) { // 8-bit RGB images: great, nothing to do. - } else if (bit_depth <= 8 && channels_ == 1 && color_type == PNG_COLOR_TYPE_GRAY) { + } else if (bit_depth_ <= 8 && channels_ == 1 && color_type_ == PNG_COLOR_TYPE_GRAY) { // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray. png_set_expand_gray_1_2_4_to_8(png_ptr_); - } else if (bit_depth <= 8 && channels_ == 1 && color_type == PNG_COLOR_TYPE_PALETTE) { + } else if (bit_depth_ <= 8 && channels_ == 1 && color_type_ == PNG_COLOR_TYPE_PALETTE) { // paletted images: expand to 8-bit RGB. Note that we DON'T // currently expand the tRNS chunk (if any) to an alpha // channel, because minui doesn't support alpha channels in @@ -158,8 +126,8 @@ PngHandler::PngHandler(const std::string& name) : error_code_(0), png_fp_(nullpt png_set_palette_to_rgb(png_ptr_); channels_ = 3; } else { - fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth, - channels_, color_type); + fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth_, + channels_, color_type_); error_code_ = -7; } } @@ -170,70 +138,49 @@ PngHandler::~PngHandler() { } } -// "display" surfaces are transformed into the framebuffer's required -// pixel format (currently only RGBX is supported) at load time, so -// gr_blit() can be nothing more than a memcpy() for each row. The -// next two functions are the only ones that know anything about the -// framebuffer pixel format; they need to be modified if the -// framebuffer format changes (but nothing else should). - -// Allocate and return a GRSurface* sufficient for storing an image of -// the indicated size in the framebuffer pixel format. -static GRSurface* init_display_surface(png_uint_32 width, png_uint_32 height) { - GRSurface* surface = malloc_surface(width * height * 4); - if (surface == NULL) return NULL; - - surface->width = width; - surface->height = height; - surface->row_bytes = width * 4; - surface->pixel_bytes = 4; - - return surface; -} +// "display" surfaces are transformed into the framebuffer's required pixel format (currently only +// RGBX is supported) at load time, so gr_blit() can be nothing more than a memcpy() for each row. -// Copy 'input_row' to 'output_row', transforming it to the -// framebuffer pixel format. The input format depends on the value of -// 'channels': +// Copies 'input_row' to 'output_row', transforming it to the framebuffer pixel format. The input +// format depends on the value of 'channels': // // 1 - input is 8-bit grayscale // 3 - input is 24-bit RGB // 4 - input is 32-bit RGBA/RGBX // // 'width' is the number of pixels in the row. -static void transform_rgb_to_draw(unsigned char* input_row, - unsigned char* output_row, - int channels, int width) { - int x; - unsigned char* ip = input_row; - unsigned char* op = output_row; - - switch (channels) { - case 1: - // expand gray level to RGBX - for (x = 0; x < width; ++x) { - *op++ = *ip; - *op++ = *ip; - *op++ = *ip; - *op++ = 0xff; - ip++; - } - break; - - case 3: - // expand RGBA to RGBX - for (x = 0; x < width; ++x) { - *op++ = *ip++; - *op++ = *ip++; - *op++ = *ip++; - *op++ = 0xff; - } - break; - - case 4: - // copy RGBA to RGBX - memcpy(output_row, input_row, width*4); - break; - } +static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, int channels, + int width) { + const uint8_t* ip = input_row; + uint8_t* op = output_row; + + switch (channels) { + case 1: + // expand gray level to RGBX + for (int x = 0; x < width; ++x) { + *op++ = *ip; + *op++ = *ip; + *op++ = *ip; + *op++ = 0xff; + ip++; + } + break; + + case 3: + // expand RGBA to RGBX + for (int x = 0; x < width; ++x) { + *op++ = *ip++; + *op++ = *ip++; + *op++ = *ip++; + *op++ = 0xff; + } + break; + + case 4: + // copy RGBA to RGBX + memcpy(output_row, input_row, width * 4); + break; + } } int res_create_display_surface(const char* name, GRSurface** pSurface) { @@ -246,23 +193,24 @@ int res_create_display_surface(const char* name, GRSurface** pSurface) { png_uint_32 width = png_handler.width(); png_uint_32 height = png_handler.height(); - GRSurface* surface = init_display_surface(width, height); + auto surface = GRSurface::Create(width, height, width * 4, 4); if (!surface) { return -8; } -#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) - png_set_bgr(png_ptr); -#endif + PixelFormat pixel_format = gr_pixel_format(); + if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) { + png_set_bgr(png_ptr); + } for (png_uint_32 y = 0; y < height; ++y) { - std::vector p_row(width * 4); + std::vector p_row(width * 4); png_read_row(png_ptr, p_row.data(), nullptr); - transform_rgb_to_draw(p_row.data(), surface->data + y * surface->row_bytes, - png_handler.channels(), width); + TransformRgbToDraw(p_row.data(), surface->data() + y * surface->row_bytes, + png_handler.channels(), width); } - *pSurface = surface; + *pSurface = surface.release(); return 0; } @@ -315,23 +263,24 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps, goto exit; } for (int i = 0; i < *frames; ++i) { - surface[i] = init_display_surface(width, height / *frames); - if (!surface[i]) { + auto created_surface = GRSurface::Create(width, height / *frames, width * 4, 4); + if (!created_surface) { result = -8; goto exit; } + surface[i] = created_surface.release(); } -#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) - png_set_bgr(png_ptr); -#endif + if (gr_pixel_format() == PixelFormat::ARGB || gr_pixel_format() == PixelFormat::BGRA) { + png_set_bgr(png_ptr); + } for (png_uint_32 y = 0; y < height; ++y) { - std::vector p_row(width * 4); + std::vector p_row(width * 4); png_read_row(png_ptr, p_row.data(), nullptr); int frame = y % *frames; - unsigned char* out_row = surface[frame]->data + (y / *frames) * surface[frame]->row_bytes; - transform_rgb_to_draw(p_row.data(), out_row, png_handler.channels(), width); + uint8_t* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes; + TransformRgbToDraw(p_row.data(), out_row, png_handler.channels(), width); } *pSurface = surface; @@ -362,29 +311,30 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface) { png_uint_32 width = png_handler.width(); png_uint_32 height = png_handler.height(); - GRSurface* surface = malloc_surface(width * height); + auto surface = GRSurface::Create(width, height, width, 1); if (!surface) { return -8; } - surface->width = width; - surface->height = height; - surface->row_bytes = width; - surface->pixel_bytes = 1; -#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) - png_set_bgr(png_ptr); -#endif + PixelFormat pixel_format = gr_pixel_format(); + if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) { + png_set_bgr(png_ptr); + } for (png_uint_32 y = 0; y < height; ++y) { - unsigned char* p_row = surface->data + y * surface->row_bytes; + uint8_t* p_row = surface->data() + y * surface->row_bytes; png_read_row(png_ptr, p_row, nullptr); } - *pSurface = surface; + *pSurface = surface.release(); return 0; } +void res_set_resource_dir(const std::string& dirname) { + g_resource_dir = dirname; +} + // This function tests if a locale string stored in PNG (prefix) matches // the locale string provided by the system (locale). bool matches_locale(const std::string& prefix, const std::string& locale) { @@ -397,6 +347,10 @@ bool matches_locale(const std::string& prefix, const std::string& locale) { // match the locale string without the {script} section. // For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale // == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN". + if (prefix.empty()) { + return false; + } + if (android::base::StartsWith(locale, prefix)) { return true; } @@ -421,7 +375,7 @@ std::vector get_locales_in_png(const std::string& png_name) { } std::vector result; - std::vector row(png_handler.width()); + std::vector row(png_handler.width()); for (png_uint_32 y = 0; y < png_handler.height(); ++y) { png_read_row(png_handler.png_ptr(), row.data(), nullptr); int h = (row[3] << 8) | row[2]; @@ -457,32 +411,34 @@ int res_create_localized_alpha_surface(const char* name, png_uint_32 height = png_handler.height(); for (png_uint_32 y = 0; y < height; ++y) { - std::vector row(width); + std::vector row(width); png_read_row(png_ptr, row.data(), nullptr); int w = (row[1] << 8) | row[0]; int h = (row[3] << 8) | row[2]; __unused int len = row[4]; char* loc = reinterpret_cast(&row[5]); - if (y + 1 + h >= height || matches_locale(loc, locale)) { + // We need to include one additional line for the metadata of the localized image. + if (y + 1 + h > height) { + printf("Read exceeds the image boundary, y %u, h %d, height %u\n", y, h, height); + return -8; + } + + if (matches_locale(loc, locale)) { printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y); - GRSurface* surface = malloc_surface(w * h); + auto surface = GRSurface::Create(w, h, w, 1); if (!surface) { - return -8; + return -9; } - surface->width = w; - surface->height = h; - surface->row_bytes = w; - surface->pixel_bytes = 1; for (int i = 0; i < h; ++i, ++y) { png_read_row(png_ptr, row.data(), nullptr); - memcpy(surface->data + i * w, row.data(), w); + memcpy(surface->data() + i * w, row.data(), w); } - *pSurface = surface; - break; + *pSurface = surface.release(); + return 0; } for (int i = 0; i < h; ++i, ++y) { @@ -490,7 +446,7 @@ int res_create_localized_alpha_surface(const char* name, } } - return 0; + return -10; } void res_free_surface(GRSurface* surface) { diff --git a/otafault/config.cpp b/otafault/config.cpp deleted file mode 100644 index 3993948ff5..0000000000 --- a/otafault/config.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "otafault/config.h" - -#include -#include - -#include -#include - -#include "otafault/ota_io.h" - -#define OTAIO_MAX_FNAME_SIZE 128 - -static ZipArchiveHandle archive; -static bool is_retry = false; -static std::map should_inject_cache; - -static std::string get_type_path(const char* io_type) { - return android::base::StringPrintf("%s/%s", OTAIO_BASE_DIR, io_type); -} - -void ota_io_init(ZipArchiveHandle za, bool retry) { - archive = za; - is_retry = retry; - ota_set_fault_files(); -} - -bool should_fault_inject(const char* io_type) { - // archive will be NULL if we used an entry point other - // than updater/updater.cpp:main - if (archive == nullptr || is_retry) { - return false; - } - const std::string type_path = get_type_path(io_type); - if (should_inject_cache.find(type_path) != should_inject_cache.end()) { - return should_inject_cache[type_path]; - } - ZipString zip_type_path(type_path.c_str()); - ZipEntry entry; - int status = FindEntry(archive, zip_type_path, &entry); - should_inject_cache[type_path] = (status == 0); - return (status == 0); -} - -bool should_hit_cache() { - return should_fault_inject(OTAIO_CACHE); -} - -std::string fault_fname(const char* io_type) { - std::string type_path = get_type_path(io_type); - std::string fname; - fname.resize(OTAIO_MAX_FNAME_SIZE); - ZipString zip_type_path(type_path.c_str()); - ZipEntry entry; - if (FindEntry(archive, zip_type_path, &entry) != 0) { - return {}; - } - ExtractToMemory(archive, &entry, reinterpret_cast(&fname[0]), OTAIO_MAX_FNAME_SIZE); - return fname; -} diff --git a/otafault/include/otafault/config.h b/otafault/include/otafault/config.h deleted file mode 100644 index cc4bfd2ad1..0000000000 --- a/otafault/include/otafault/config.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Read configuration files in the OTA package to determine which files, if any, will trigger - * errors. - * - * OTA packages can be modified to trigger errors by adding a top-level directory called - * .libotafault, which may optionally contain up to three files called READ, WRITE, and FSYNC. - * Each one of these optional files contains the name of a single file on the device disk which - * will cause an IO error on the first call of the appropriate I/O action to that file. - * - * Example: - * ota.zip - * - * .libotafault - * WRITE - * - * If the contents of the file WRITE were /system/build.prop, the first write action to - * /system/build.prop would fail with EIO. Note that READ and FSYNC files are absent, so these - * actions will not cause an error. - */ - -#ifndef _UPDATER_OTA_IO_CFG_H_ -#define _UPDATER_OTA_IO_CFG_H_ - -#include - -#include - -#define OTAIO_BASE_DIR ".libotafault" -#define OTAIO_READ "READ" -#define OTAIO_WRITE "WRITE" -#define OTAIO_FSYNC "FSYNC" -#define OTAIO_CACHE "CACHE" - -/* - * Initialize libotafault by providing a reference to the OTA package. - */ -void ota_io_init(ZipArchiveHandle zip, bool retry); - -/* - * Return true if a config file is present for the given IO type. - */ -bool should_fault_inject(const char* io_type); - -/* - * Return true if an EIO should occur on the next hit to /cache/saved.file - * instead of the next hit to the specified file. - */ -bool should_hit_cache(); - -/* - * Return the name of the file that should cause an error for the - * given IO type. - */ -std::string fault_fname(const char* io_type); - -#endif diff --git a/otafault/include/otafault/ota_io.h b/otafault/include/otafault/ota_io.h deleted file mode 100644 index 45e481a62e..0000000000 --- a/otafault/include/otafault/ota_io.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Provide a series of proxy functions for basic file accessors. - * The behavior of these functions can be changed to return different - * errors under a variety of conditions. - */ - -#ifndef _UPDATER_OTA_IO_H_ -#define _UPDATER_OTA_IO_H_ - -#include -#include -#include // mode_t - -#include - -#include - -#define OTAIO_CACHE_FNAME "/cache/saved.file" - -void ota_set_fault_files(); - -int ota_open(const char* path, int oflags); - -int ota_open(const char* path, int oflags, mode_t mode); - -FILE* ota_fopen(const char* filename, const char* mode); - -size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream); - -ssize_t ota_read(int fd, void* buf, size_t nbyte); - -size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream); - -ssize_t ota_write(int fd, const void* buf, size_t nbyte); - -int ota_fsync(int fd); - -struct OtaCloser { - static void Close(int); -}; - -using unique_fd = android::base::unique_fd_impl; - -int ota_close(unique_fd& fd); - -struct OtaFcloser { - void operator()(FILE*) const; -}; - -using unique_file = std::unique_ptr; - -int ota_fclose(unique_file& fh); - -#endif diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp deleted file mode 100644 index 63ef18e264..0000000000 --- a/otafault/ota_io.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "otafault/ota_io.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "otafault/config.h" - -static std::mutex filename_mutex; -static std::map filename_cache GUARDED_BY(filename_mutex); -static std::string read_fault_file_name = ""; -static std::string write_fault_file_name = ""; -static std::string fsync_fault_file_name = ""; - -static bool get_hit_file(const char* cached_path, const std::string& ffn) { - return should_hit_cache() - ? !strncmp(cached_path, OTAIO_CACHE_FNAME, strlen(cached_path)) - : !strncmp(cached_path, ffn.c_str(), strlen(cached_path)); -} - -void ota_set_fault_files() { - if (should_fault_inject(OTAIO_READ)) { - read_fault_file_name = fault_fname(OTAIO_READ); - } - if (should_fault_inject(OTAIO_WRITE)) { - write_fault_file_name = fault_fname(OTAIO_WRITE); - } - if (should_fault_inject(OTAIO_FSYNC)) { - fsync_fault_file_name = fault_fname(OTAIO_FSYNC); - } -} - -bool have_eio_error = false; - -int ota_open(const char* path, int oflags) { - // Let the caller handle errors; we do not care if open succeeds or fails - int fd = open(path, oflags); - std::lock_guard lock(filename_mutex); - filename_cache[fd] = path; - return fd; -} - -int ota_open(const char* path, int oflags, mode_t mode) { - int fd = open(path, oflags, mode); - std::lock_guard lock(filename_mutex); - filename_cache[fd] = path; - return fd; -} - -FILE* ota_fopen(const char* path, const char* mode) { - FILE* fh = fopen(path, mode); - std::lock_guard lock(filename_mutex); - filename_cache[(intptr_t)fh] = path; - return fh; -} - -static int __ota_close(int fd) { - // descriptors can be reused, so make sure not to leave them in the cache - std::lock_guard lock(filename_mutex); - filename_cache.erase(fd); - return close(fd); -} - -void OtaCloser::Close(int fd) { - __ota_close(fd); -} - -int ota_close(unique_fd& fd) { - return __ota_close(fd.release()); -} - -static int __ota_fclose(FILE* fh) { - std::lock_guard lock(filename_mutex); - filename_cache.erase(reinterpret_cast(fh)); - return fclose(fh); -} - -void OtaFcloser::operator()(FILE* f) const { - __ota_fclose(f); -}; - -int ota_fclose(unique_file& fh) { - return __ota_fclose(fh.release()); -} - -size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) { - if (should_fault_inject(OTAIO_READ)) { - std::lock_guard lock(filename_mutex); - auto cached = filename_cache.find((intptr_t)stream); - const char* cached_path = cached->second; - if (cached != filename_cache.end() && - get_hit_file(cached_path, read_fault_file_name)) { - read_fault_file_name = ""; - errno = EIO; - have_eio_error = true; - return 0; - } - } - size_t status = fread(ptr, size, nitems, stream); - // If I/O error occurs, set the retry-update flag. - if (status != nitems && errno == EIO) { - have_eio_error = true; - } - return status; -} - -ssize_t ota_read(int fd, void* buf, size_t nbyte) { - if (should_fault_inject(OTAIO_READ)) { - std::lock_guard lock(filename_mutex); - auto cached = filename_cache.find(fd); - const char* cached_path = cached->second; - if (cached != filename_cache.end() - && get_hit_file(cached_path, read_fault_file_name)) { - read_fault_file_name = ""; - errno = EIO; - have_eio_error = true; - return -1; - } - } - ssize_t status = read(fd, buf, nbyte); - if (status == -1 && errno == EIO) { - have_eio_error = true; - } - return status; -} - -size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) { - if (should_fault_inject(OTAIO_WRITE)) { - std::lock_guard lock(filename_mutex); - auto cached = filename_cache.find((intptr_t)stream); - const char* cached_path = cached->second; - if (cached != filename_cache.end() && - get_hit_file(cached_path, write_fault_file_name)) { - write_fault_file_name = ""; - errno = EIO; - have_eio_error = true; - return 0; - } - } - size_t status = fwrite(ptr, size, count, stream); - if (status != count && errno == EIO) { - have_eio_error = true; - } - return status; -} - -ssize_t ota_write(int fd, const void* buf, size_t nbyte) { - if (should_fault_inject(OTAIO_WRITE)) { - std::lock_guard lock(filename_mutex); - auto cached = filename_cache.find(fd); - const char* cached_path = cached->second; - if (cached != filename_cache.end() && - get_hit_file(cached_path, write_fault_file_name)) { - write_fault_file_name = ""; - errno = EIO; - have_eio_error = true; - return -1; - } - } - ssize_t status = write(fd, buf, nbyte); - if (status == -1 && errno == EIO) { - have_eio_error = true; - } - return status; -} - -int ota_fsync(int fd) { - if (should_fault_inject(OTAIO_FSYNC)) { - std::lock_guard lock(filename_mutex); - auto cached = filename_cache.find(fd); - const char* cached_path = cached->second; - if (cached != filename_cache.end() && - get_hit_file(cached_path, fsync_fault_file_name)) { - fsync_fault_file_name = ""; - errno = EIO; - have_eio_error = true; - return -1; - } - } - int status = fsync(fd); - if (status == -1 && errno == EIO) { - have_eio_error = true; - } - return status; -} - diff --git a/otautil/Android.bp b/otautil/Android.bp index 75cf691484..3b3f9cbc43 100644 --- a/otautil/Android.bp +++ b/otautil/Android.bp @@ -16,24 +16,25 @@ cc_library_static { name: "libotautil", host_supported: true, + vendor_available: true, + recovery_available: true, + defaults: [ + "recovery_defaults", + ], + + // Minimal set of files to support host build. srcs: [ - "SysUtil.cpp", - "DirUtil.cpp", - "ThermalUtil.cpp", - "cache_location.cpp", + "dirutil.cpp", + "paths.cpp", "rangeset.cpp", + "sysutil.cpp", ], - static_libs: [ - "libselinux", + shared_libs: [ "libbase", - ], - - cflags: [ - "-D_FILE_OFFSET_BITS=64", - "-Werror", - "-Wall", + "libcutils", + "libselinux", ], export_include_dirs: [ diff --git a/otautil/DirUtil.cpp b/otautil/dirutil.cpp similarity index 99% rename from otautil/DirUtil.cpp rename to otautil/dirutil.cpp index 61c8328139..ae1cd5c286 100644 --- a/otautil/DirUtil.cpp +++ b/otautil/dirutil.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "otautil/DirUtil.h" +#include "otautil/dirutil.h" #include #include diff --git a/otautil/include/otautil/SysUtil.h b/otautil/include/otautil/SysUtil.h deleted file mode 100644 index 52f6d20a7c..0000000000 --- a/otautil/include/otautil/SysUtil.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _OTAUTIL_SYSUTIL -#define _OTAUTIL_SYSUTIL - -#include - -#include -#include - -/* - * Use this to keep track of mapped segments. - */ -class MemMapping { - public: - ~MemMapping(); - // Map a file into a private, read-only memory segment. If 'filename' begins with an '@' - // character, it is a map of blocks to be mapped, otherwise it is treated as an ordinary file. - bool MapFile(const std::string& filename); - size_t ranges() const { - return ranges_.size(); - }; - - unsigned char* addr; // start of data - size_t length; // length of data - - private: - struct MappedRange { - void* addr; - size_t length; - }; - - bool MapBlockFile(const std::string& filename); - bool MapFD(int fd); - - std::vector ranges_; -}; - -#endif // _OTAUTIL_SYSUTIL diff --git a/otautil/include/otautil/boot_state.h b/otautil/include/otautil/boot_state.h new file mode 100644 index 0000000000..6c877baef9 --- /dev/null +++ b/otautil/include/otautil/boot_state.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +class BootState { + public: + BootState(std::string_view reason, std::string_view stage) : reason_(reason), stage_(stage) {} + + std::string reason() const { + return reason_; + } + std::string stage() const { + return stage_; + } + + private: + std::string reason_; // The reason argument provided in "--reason=". + std::string stage_; // The current stage, e.g. "1/2". +}; diff --git a/otautil/include/otautil/cache_location.h b/otautil/include/otautil/cache_location.h deleted file mode 100644 index f2f663816e..0000000000 --- a/otautil/include/otautil/cache_location.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_ -#define _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_ - -#include - -#include "android-base/macros.h" - -// A singleton class to maintain the update related locations. The locations should be only set -// once at the start of the program. -class CacheLocation { - public: - static CacheLocation& location(); - - // getter and setter functions. - std::string cache_temp_source() const { - return cache_temp_source_; - } - void set_cache_temp_source(const std::string& temp_source) { - cache_temp_source_ = temp_source; - } - - std::string last_command_file() const { - return last_command_file_; - } - void set_last_command_file(const std::string& last_command) { - last_command_file_ = last_command; - } - - std::string stash_directory_base() const { - return stash_directory_base_; - } - void set_stash_directory_base(const std::string& base) { - stash_directory_base_ = base; - } - - private: - CacheLocation(); - DISALLOW_COPY_AND_ASSIGN(CacheLocation); - - // 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 it to free up space. If the expected source file doesn't - // exist, or is corrupted, we look to see if the cached file contains the bits we want and use it - // as the source instead. The default location for the cached source is "/cache/saved.file". - std::string cache_temp_source_; - - // Location to save the last command that stashes blocks. - std::string last_command_file_; - - // The base directory to write stashes during update. - std::string stash_directory_base_; -}; - -#endif // _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_ diff --git a/otautil/include/otautil/DirUtil.h b/otautil/include/otautil/dirutil.h similarity index 100% rename from otautil/include/otautil/DirUtil.h rename to otautil/include/otautil/dirutil.h diff --git a/otautil/include/otautil/error_code.h b/otautil/include/otautil/error_code.h index b0ff42d8dd..2b73c13536 100644 --- a/otautil/include/otautil/error_code.h +++ b/otautil/include/otautil/error_code.h @@ -48,6 +48,8 @@ enum CauseCode : int { kRebootFailure, kPackageExtractFileFailure, kPatchApplicationFailure, + kHashTreeComputationFailure, + kEioFailure, kVendorFailure = 200 }; diff --git a/otautil/include/otautil/paths.h b/otautil/include/otautil/paths.h new file mode 100644 index 0000000000..f95741a24b --- /dev/null +++ b/otautil/include/otautil/paths.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _OTAUTIL_PATHS_H_ +#define _OTAUTIL_PATHS_H_ + +#include + +#include + +// A singleton class to maintain the update related paths. The paths should be only set once at the +// start of the program. +class Paths { + public: + static Paths& Get(); + + std::string cache_log_directory() const { + return cache_log_directory_; + } + void set_cache_log_directory(const std::string& log_dir) { + cache_log_directory_ = log_dir; + } + + std::string cache_temp_source() const { + return cache_temp_source_; + } + void set_cache_temp_source(const std::string& temp_source) { + cache_temp_source_ = temp_source; + } + + std::string last_command_file() const { + return last_command_file_; + } + void set_last_command_file(const std::string& last_command_file) { + last_command_file_ = last_command_file; + } + + std::string resource_dir() const { + return resource_dir_; + } + void set_resource_dir(const std::string& resource_dir) { + resource_dir_ = resource_dir; + } + + std::string stash_directory_base() const { + return stash_directory_base_; + } + void set_stash_directory_base(const std::string& base) { + stash_directory_base_ = base; + } + + std::string temporary_install_file() const { + return temporary_install_file_; + } + void set_temporary_install_file(const std::string& install_file) { + temporary_install_file_ = install_file; + } + + std::string temporary_log_file() const { + return temporary_log_file_; + } + void set_temporary_log_file(const std::string& log_file) { + temporary_log_file_ = log_file; + } + + std::string temporary_update_binary() const { + return temporary_update_binary_; + } + void set_temporary_update_binary(const std::string& update_binary) { + temporary_update_binary_ = update_binary; + } + + private: + Paths(); + DISALLOW_COPY_AND_ASSIGN(Paths); + + // Path to the directory that contains last_log and last_kmsg log files. + std::string cache_log_directory_; + + // Path to the temporary source file on /cache. 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 it to + // free up space. If the expected source file doesn't exist, or is corrupted, we look to see if + // the cached file contains the bits we want and use it as the source instead. + std::string cache_temp_source_; + + // Path to the last command file. + std::string last_command_file_; + + // Path to the resource dir; + std::string resource_dir_; + + // Path to the base directory to write stashes during update. + std::string stash_directory_base_; + + // Path to the temporary file that contains the install result. + std::string temporary_install_file_; + + // Path to the temporary log file while under recovery. + std::string temporary_log_file_; + + // Path to the temporary update binary while installing a non-A/B package. + std::string temporary_update_binary_; +}; + +#endif // _OTAUTIL_PATHS_H_ diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h index e91d02ca6c..a18c30e29c 100644 --- a/otautil/include/otautil/rangeset.h +++ b/otautil/include/otautil/rangeset.h @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -49,6 +50,12 @@ class RangeSet { // bounds. For example, "3,5" contains blocks 3 and 4. So "3,5" and "5,7" are not overlapped. bool Overlaps(const RangeSet& other) const; + // Returns a subset of ranges starting from |start_index| with respect to the original range. The + // output range will have |num_of_blocks| blocks in size. Returns std::nullopt if the input is + // invalid. e.g. RangeSet({{0, 5}, {10, 15}}).GetSubRanges(1, 5) returns + // RangeSet({{1, 5}, {10, 11}}). + std::optional GetSubRanges(size_t start_index, size_t num_of_blocks) const; + // Returns a vector of RangeSets that contain the same set of blocks represented by the current // RangeSet. The RangeSets in the vector contain similar number of blocks, with a maximum delta // of 1-block between any two of them. For example, 14 blocks would be split into 4 + 4 + 3 + 3, diff --git a/otautil/include/otautil/sysutil.h b/otautil/include/otautil/sysutil.h new file mode 100644 index 0000000000..d0d2e67d7a --- /dev/null +++ b/otautil/include/otautil/sysutil.h @@ -0,0 +1,114 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +#include "rangeset.h" + +// This class holds the content of a block map file. +class BlockMapData { + public: + // A "block map" which looks like this (from uncrypt/uncrypt.cpp): + // + // /dev/block/platform/msm_sdcc.1/by-name/userdata # block device + // 49652 4096 # file size in bytes, block size + // 3 # count of block ranges + // 1000 1008 # block range 0 + // 2100 2102 # ... block range 1 + // 30 33 # ... block range 2 + // + // Each block range represents a half-open interval; the line "30 33" reprents the blocks + // [30, 31, 32]. + static BlockMapData ParseBlockMapFile(const std::string& block_map_path); + + explicit operator bool() const { + return !path_.empty(); + } + + std::string path() const { + return path_; + } + uint64_t file_size() const { + return file_size_; + } + uint32_t block_size() const { + return block_size_; + } + RangeSet block_ranges() const { + return block_ranges_; + } + + private: + BlockMapData() = default; + + BlockMapData(const std::string& path, uint64_t file_size, uint32_t block_size, + RangeSet block_ranges) + : path_(path), + file_size_(file_size), + block_size_(block_size), + block_ranges_(std::move(block_ranges)) {} + + std::string path_; + uint64_t file_size_ = 0; + uint32_t block_size_ = 0; + RangeSet block_ranges_; +}; + +/* + * Use this to keep track of mapped segments. + */ +class MemMapping { + public: + ~MemMapping(); + // Map a file into a private, read-only memory segment. If 'filename' begins with an '@' + // character, it is a map of blocks to be mapped, otherwise it is treated as an ordinary file. + bool MapFile(const std::string& filename); + size_t ranges() const { + return ranges_.size(); + }; + + unsigned char* addr; // start of data + size_t length; // length of data + + private: + struct MappedRange { + void* addr; + size_t length; + }; + + bool MapBlockFile(const std::string& filename); + bool MapFD(int fd); + + std::vector ranges_; +}; + +// Reboots the device into the specified target, by additionally handling quiescent reboot mode. +// All unknown targets reboot into Android. +[[noreturn]] void Reboot(std::string_view target); + +// Triggers a shutdown. +bool Shutdown(std::string_view target); + +// Returns a null-terminated char* array, where the elements point to the C-strings in the given +// vector, plus an additional nullptr at the end. This is a helper function that facilitates +// calling C functions (such as getopt(3)) that expect an array of C-strings. +std::vector StringVectorToNullTerminatedArray(const std::vector& args); diff --git a/otautil/paths.cpp b/otautil/paths.cpp new file mode 100644 index 0000000000..33ab4a5d47 --- /dev/null +++ b/otautil/paths.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "otautil/paths.h" + +constexpr const char kDefaultCacheLogDirectory[] = "/cache/recovery"; +constexpr const char kDefaultCacheTempSource[] = "/cache/saved.file"; +constexpr const char kDefaultLastCommandFile[] = "/cache/recovery/last_command"; +constexpr const char kDefaultResourceDirectory[] = "/res/images"; +constexpr const char kDefaultStashDirectoryBase[] = "/cache/recovery"; +constexpr const char kDefaultTemporaryInstallFile[] = "/tmp/last_install"; +constexpr const char kDefaultTemporaryLogFile[] = "/tmp/recovery.log"; +constexpr const char kDefaultTemporaryUpdateBinary[] = "/tmp/update-binary"; + +Paths& Paths::Get() { + static Paths paths; + return paths; +} + +Paths::Paths() + : cache_log_directory_(kDefaultCacheLogDirectory), + cache_temp_source_(kDefaultCacheTempSource), + last_command_file_(kDefaultLastCommandFile), + resource_dir_(kDefaultResourceDirectory), + stash_directory_base_(kDefaultStashDirectoryBase), + temporary_install_file_(kDefaultTemporaryInstallFile), + temporary_log_file_(kDefaultTemporaryLogFile), + temporary_update_binary_(kDefaultTemporaryUpdateBinary) {} diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp index 96955b9d0a..8ee99dd7a8 100644 --- a/otautil/rangeset.cpp +++ b/otautil/rangeset.cpp @@ -148,8 +148,8 @@ std::string RangeSet::ToString() const { return ""; } std::string result = std::to_string(ranges_.size() * 2); - for (const auto& r : ranges_) { - result += android::base::StringPrintf(",%zu,%zu", r.first, r.second); + for (const auto& [begin, end] : ranges_) { + result += android::base::StringPrintf(",%zu,%zu", begin, end); } return result; @@ -159,11 +159,11 @@ std::string RangeSet::ToString() const { size_t RangeSet::GetBlockNumber(size_t idx) const { CHECK_LT(idx, blocks_) << "Out of bound index " << idx << " (total blocks: " << blocks_ << ")"; - for (const auto& range : ranges_) { - if (idx < range.second - range.first) { - return range.first + idx; + for (const auto& [begin, end] : ranges_) { + if (idx < end - begin) { + return begin + idx; } - idx -= (range.second - range.first); + idx -= (end - begin); } CHECK(false) << "Failed to find block number for index " << idx; @@ -173,14 +173,10 @@ size_t RangeSet::GetBlockNumber(size_t idx) const { // RangeSet has half-closed half-open bounds. For example, "3,5" contains blocks 3 and 4. So "3,5" // and "5,7" are not overlapped. bool RangeSet::Overlaps(const RangeSet& other) const { - for (const auto& range : ranges_) { - size_t start = range.first; - size_t end = range.second; - for (const auto& other_range : other.ranges_) { - size_t other_start = other_range.first; - size_t other_end = other_range.second; - // [start, end) vs [other_start, other_end) - if (!(other_start >= end || start >= other_end)) { + for (const auto& [begin, end] : ranges_) { + for (const auto& [other_begin, other_end] : other.ranges_) { + // [begin, end) vs [other_begin, other_end) + if (!(other_begin >= end || begin >= other_end)) { return true; } } @@ -188,6 +184,58 @@ bool RangeSet::Overlaps(const RangeSet& other) const { return false; } +std::optional RangeSet::GetSubRanges(size_t start_index, size_t num_of_blocks) const { + size_t end_index = start_index + num_of_blocks; // The index of final block to read plus one + if (start_index > end_index || end_index > blocks_) { + LOG(ERROR) << "Failed to get the sub ranges for start_index " << start_index + << " num_of_blocks " << num_of_blocks + << " total number of blocks the range contains is " << blocks_; + return std::nullopt; + } + + if (num_of_blocks == 0) { + LOG(WARNING) << "num_of_blocks is zero when calling GetSubRanges()"; + return RangeSet(); + } + + RangeSet result; + size_t current_index = 0; + for (const auto& [range_start, range_end] : ranges_) { + CHECK_LT(range_start, range_end); + size_t blocks_in_range = range_end - range_start; + // Linear search to skip the ranges until we reach start_block. + if (current_index + blocks_in_range <= start_index) { + current_index += blocks_in_range; + continue; + } + + size_t trimmed_range_start = range_start; + // We have found the first block range to read, trim the heading blocks. + if (current_index < start_index) { + trimmed_range_start += start_index - current_index; + } + // Trim the trailing blocks if the last range has more blocks than desired; also return the + // result. + if (current_index + blocks_in_range >= end_index) { + size_t trimmed_range_end = range_end - (current_index + blocks_in_range - end_index); + if (!result.PushBack({ trimmed_range_start, trimmed_range_end })) { + return std::nullopt; + } + + return result; + } + + if (!result.PushBack({ trimmed_range_start, range_end })) { + return std::nullopt; + } + current_index += blocks_in_range; + } + + LOG(ERROR) << "Failed to construct byte ranges to read, start_block: " << start_index + << ", num_of_blocks: " << num_of_blocks << " total number of blocks: " << blocks_; + return std::nullopt; +} + // Ranges in the the set should be mutually exclusive; and they're sorted by the start block. SortedRangeSet::SortedRangeSet(std::vector&& pairs) : RangeSet(std::move(pairs)) { std::sort(ranges_.begin(), ranges_.end()); @@ -248,20 +296,20 @@ bool SortedRangeSet::Overlaps(size_t start, size_t len) const { size_t SortedRangeSet::GetOffsetInRangeSet(size_t old_offset) const { size_t old_block_start = old_offset / kBlockSize; size_t new_block_start = 0; - for (const auto& range : ranges_) { + for (const auto& [start, end] : ranges_) { // Find the index of old_block_start. - if (old_block_start >= range.second) { - new_block_start += (range.second - range.first); - } else if (old_block_start >= range.first) { - new_block_start += (old_block_start - range.first); + if (old_block_start >= end) { + new_block_start += (end - start); + } else if (old_block_start >= start) { + new_block_start += (old_block_start - start); return (new_block_start * kBlockSize + old_offset % kBlockSize); } else { CHECK(false) << "block_start " << old_block_start - << " is missing between two ranges: " << this->ToString(); + << " is missing between two ranges: " << ToString(); return 0; } } CHECK(false) << "block_start " << old_block_start - << " exceeds the limit of current RangeSet: " << this->ToString(); + << " exceeds the limit of current RangeSet: " << ToString(); return 0; } diff --git a/otautil/SysUtil.cpp b/otautil/sysutil.cpp similarity index 56% rename from otautil/SysUtil.cpp rename to otautil/sysutil.cpp index 48336ad072..b3ead97368 100644 --- a/otautil/SysUtil.cpp +++ b/otautil/sysutil.cpp @@ -14,22 +14,93 @@ * limitations under the License. */ -#include "otautil/SysUtil.h" +#include "otautil/sysutil.h" #include // TEMP_FAILURE_RETRY #include -#include // SIZE_MAX +#include #include #include #include +#include +#include #include #include #include #include +#include #include #include +#include + +BlockMapData BlockMapData::ParseBlockMapFile(const std::string& block_map_path) { + std::string content; + if (!android::base::ReadFileToString(block_map_path, &content)) { + PLOG(ERROR) << "Failed to read " << block_map_path; + return {}; + } + + std::vector lines = android::base::Split(android::base::Trim(content), "\n"); + if (lines.size() < 4) { + LOG(ERROR) << "Block map file is too short: " << lines.size(); + return {}; + } + + const std::string& block_dev = lines[0]; + + uint64_t file_size; + uint32_t blksize; + if (sscanf(lines[1].c_str(), "%" SCNu64 "%" SCNu32, &file_size, &blksize) != 2) { + LOG(ERROR) << "Failed to parse file size and block size: " << lines[1]; + return {}; + } + + if (file_size == 0 || blksize == 0) { + LOG(ERROR) << "Invalid size in block map file: size " << file_size << ", blksize " << blksize; + return {}; + } + + size_t range_count; + if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) { + LOG(ERROR) << "Failed to parse block map header: " << lines[2]; + return {}; + } + + uint64_t blocks = ((file_size - 1) / blksize) + 1; + if (blocks > std::numeric_limits::max() || range_count == 0 || + lines.size() != 3 + range_count) { + LOG(ERROR) << "Invalid data in block map file: size " << file_size << ", blksize " << blksize + << ", range_count " << range_count << ", lines " << lines.size(); + return {}; + } + + RangeSet ranges; + uint64_t remaining_blocks = blocks; + for (size_t i = 0; i < range_count; ++i) { + const std::string& line = lines[i + 3]; + uint64_t start, end; + if (sscanf(line.c_str(), "%" SCNu64 "%" SCNu64, &start, &end) != 2) { + LOG(ERROR) << "failed to parse range " << i << ": " << line; + return {}; + } + uint64_t range_blocks = end - start; + if (end <= start || range_blocks > remaining_blocks) { + LOG(ERROR) << "Invalid range: " << start << " " << end; + return {}; + } + ranges.PushBack({ start, end }); + remaining_blocks -= range_blocks; + } + + if (remaining_blocks != 0) { + LOG(ERROR) << "Invalid ranges: remaining blocks " << remaining_blocks; + return {}; + } + + return BlockMapData(block_dev, file_size, blksize, std::move(ranges)); +} bool MemMapping::MapFD(int fd) { struct stat sb; @@ -52,115 +123,61 @@ bool MemMapping::MapFD(int fd) { return true; } -// A "block map" which looks like this (from uncrypt/uncrypt.cpp): -// -// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device -// 49652 4096 # file size in bytes, block size -// 3 # count of block ranges -// 1000 1008 # block range 0 -// 2100 2102 # ... block range 1 -// 30 33 # ... block range 2 -// -// Each block range represents a half-open interval; the line "30 33" reprents the blocks -// [30, 31, 32]. bool MemMapping::MapBlockFile(const std::string& filename) { - std::string content; - if (!android::base::ReadFileToString(filename, &content)) { - PLOG(ERROR) << "Failed to read " << filename; - return false; - } - - std::vector lines = android::base::Split(android::base::Trim(content), "\n"); - if (lines.size() < 4) { - LOG(ERROR) << "Block map file is too short: " << lines.size(); + auto block_map_data = BlockMapData::ParseBlockMapFile(filename); + if (!block_map_data) { return false; } - size_t size; - size_t blksize; - if (sscanf(lines[1].c_str(), "%zu %zu", &size, &blksize) != 2) { - LOG(ERROR) << "Failed to parse file size and block size: " << lines[1]; - return false; - } - - size_t range_count; - if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) { - LOG(ERROR) << "Failed to parse block map header: " << lines[2]; - return false; - } - - size_t blocks; - if (blksize != 0) { - blocks = ((size - 1) / blksize) + 1; - } - if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0 || - lines.size() != 3 + range_count) { - LOG(ERROR) << "Invalid data in block map file: size " << size << ", blksize " << blksize - << ", range_count " << range_count << ", lines " << lines.size(); + if (block_map_data.file_size() > std::numeric_limits::max()) { + LOG(ERROR) << "File size is too large for mmap " << block_map_data.file_size(); return false; } // Reserve enough contiguous address space for the whole file. + uint32_t blksize = block_map_data.block_size(); + uint64_t blocks = ((block_map_data.file_size() - 1) / blksize) + 1; void* reserve = mmap(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); if (reserve == MAP_FAILED) { PLOG(ERROR) << "failed to reserve address space"; return false; } - const std::string& block_dev = lines[0]; - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_dev.c_str(), O_RDONLY))); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map_data.path().c_str(), O_RDONLY))); if (fd == -1) { - PLOG(ERROR) << "failed to open block device " << block_dev; + PLOG(ERROR) << "failed to open block device " << block_map_data.path(); munmap(reserve, blocks * blksize); return false; } ranges_.clear(); - unsigned char* next = static_cast(reserve); + auto next = static_cast(reserve); size_t remaining_size = blocks * blksize; - bool success = true; - for (size_t i = 0; i < range_count; ++i) { - const std::string& line = lines[i + 3]; - - size_t start, end; - if (sscanf(line.c_str(), "%zu %zu\n", &start, &end) != 2) { - LOG(ERROR) << "failed to parse range " << i << ": " << line; - success = false; - break; - } + for (const auto& [start, end] : block_map_data.block_ranges()) { size_t range_size = (end - start) * blksize; - if (end <= start || (end - start) > SIZE_MAX / blksize || range_size > remaining_size) { - LOG(ERROR) << "Invalid range: " << start << " " << end; - success = false; - break; - } - void* range_start = mmap(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, static_cast(start) * blksize); if (range_start == MAP_FAILED) { - PLOG(ERROR) << "failed to map range " << i << ": " << line; - success = false; - break; + PLOG(ERROR) << "failed to map range " << start << ": " << end; + munmap(reserve, blocks * blksize); + return false; } ranges_.emplace_back(MappedRange{ range_start, range_size }); next += range_size; remaining_size -= range_size; } - if (success && remaining_size != 0) { + if (remaining_size != 0) { LOG(ERROR) << "Invalid ranges: remaining_size " << remaining_size; - success = false; - } - if (!success) { munmap(reserve, blocks * blksize); return false; } addr = static_cast(reserve); - length = size; + length = block_map_data.file_size(); - LOG(INFO) << "mmapped " << range_count << " ranges"; + LOG(INFO) << "mmapped " << block_map_data.block_ranges().size() << " ranges"; return true; } @@ -201,3 +218,30 @@ MemMapping::~MemMapping() { }; ranges_.clear(); } + +void Reboot(std::string_view target) { + std::string cmd = "reboot," + std::string(target); + // Honor the quiescent mode if applicable. + if (target != "bootloader" && target != "fastboot" && + android::base::GetBoolProperty("ro.boot.quiescent", false)) { + cmd += ",quiescent"; + } + if (!android::base::SetProperty(ANDROID_RB_PROPERTY, cmd)) { + LOG(FATAL) << "Reboot failed"; + } + + while (true) pause(); +} + +bool Shutdown(std::string_view target) { + std::string cmd = "shutdown," + std::string(target); + return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd); +} + +std::vector StringVectorToNullTerminatedArray(const std::vector& args) { + std::vector result(args.size()); + std::transform(args.cbegin(), args.cend(), result.begin(), + [](const std::string& arg) { return const_cast(arg.c_str()); }); + result.push_back(nullptr); + return result; +} diff --git a/private/install.h b/private/install.h deleted file mode 100644 index ef64bd41d3..0000000000 --- a/private/install.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Private headers exposed for testing purpose only. - -#pragma once - -#include -#include - -#include - -// Extract the update binary from the open zip archive |zip| located at |package| to |binary_path|. -// Store the command line that should be called into |cmd|. The |status_fd| is the file descriptor -// the child process should use to report back the progress of the update. -int update_binary_command(const std::string& package, ZipArchiveHandle zip, - const std::string& binary_path, int retry_count, int status_fd, - std::vector* cmd); diff --git a/recovery-persist.cpp b/recovery-persist.cpp index dbce7ff743..ad101ede25 100644 --- a/recovery-persist.cpp +++ b/recovery-persist.cpp @@ -41,13 +41,14 @@ #include #include /* private pmsg functions */ -#include "rotate_logs.h" +#include "recovery_utils/logging.h" +#include "recovery_utils/parse_install_logs.h" -static const char *LAST_LOG_FILE = "/data/misc/recovery/last_log"; -static const char *LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0"; -static const char *LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg"; -static const char *LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0"; -static const char *ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops"; +constexpr const char* LAST_LOG_FILE = "/data/misc/recovery/last_log"; +constexpr const char* LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0"; +constexpr const char* LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg"; +constexpr const char* LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0"; +constexpr const char* ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops"; // close a file, log an error if the error indicator is set static void check_and_fclose(FILE *fp, const char *name) { @@ -138,14 +139,17 @@ int main(int argc, char **argv) { } if (has_cache) { - /* - * TBD: Future location to move content from - * /cache/recovery to /data/misc/recovery/ - */ - /* if --force-persist flag, then transfer pmsg data anyways */ - if ((argc <= 1) || !argv[1] || strcmp(argv[1], "--force-persist")) { - return 0; - } + // Collects and reports the non-a/b update metrics from last_install; and removes the file + // to avoid duplicate report. + if (access(LAST_INSTALL_FILE_IN_CACHE, F_OK) && unlink(LAST_INSTALL_FILE_IN_CACHE) == -1) { + PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE_IN_CACHE; + } + + // TBD: Future location to move content from /cache/recovery to /data/misc/recovery/ + // if --force-persist flag, then transfer pmsg data anyways + if ((argc <= 1) || !argv[1] || strcmp(argv[1], "--force-persist")) { + return 0; + } } /* Is there something in pmsg? */ @@ -157,6 +161,14 @@ int main(int argc, char **argv) { __android_log_pmsg_file_read( LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", logsave, NULL); + // For those device without /cache, the last_install file has been copied to + // /data/misc/recovery from pmsg. Looks for the sideload history only. + if (!has_cache) { + if (access(LAST_INSTALL_FILE, F_OK) && unlink(LAST_INSTALL_FILE) == -1) { + PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE; + } + } + /* Is there a last console log too? */ if (rotated) { if (!access(LAST_CONSOLE_FILE, R_OK)) { diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp index 14565d3f4a..42acd05bef 100644 --- a/recovery-refresh.cpp +++ b/recovery-refresh.cpp @@ -38,11 +38,12 @@ // #include + #include #include /* private pmsg functions */ -#include "rotate_logs.h" +#include "recovery_utils/logging.h" int main(int argc, char **argv) { static const char filter[] = "recovery/"; diff --git a/recovery.cpp b/recovery.cpp index 07ec5cfb65..7675121d4d 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -14,28 +14,22 @@ * limitations under the License. */ +#include "recovery.h" + #include -#include #include -#include #include #include #include -#include #include -#include #include #include #include -#include -#include #include -#include -#include #include -#include -#include +#include +#include #include #include #include @@ -46,98 +40,36 @@ #include #include #include -#include -#include -#include #include /* for property_list */ -#include -#include /* for AID_SYSTEM */ -#include /* private pmsg functions */ -#include -#include -#include +#include #include -#include "adb_install.h" -#include "common.h" -#include "device.h" -#include "fuse_sdcard_provider.h" -#include "fuse_sideload.h" -#include "install.h" -#include "minadbd/minadbd.h" -#include "minui/minui.h" -#include "otautil/DirUtil.h" +#include "bootloader_message/bootloader_message.h" +#include "install/adb_install.h" +#include "install/fuse_install.h" +#include "install/install.h" +#include "install/package.h" +#include "install/snapshot_utils.h" +#include "install/wipe_data.h" +#include "install/wipe_device.h" +#include "otautil/boot_state.h" #include "otautil/error_code.h" -#include "roots.h" -#include "rotate_logs.h" -#include "screen_ui.h" -#include "stub_ui.h" -#include "ui.h" - -static const struct option OPTIONS[] = { - { "update_package", required_argument, NULL, 'u' }, - { "retry_count", required_argument, NULL, 'n' }, - { "wipe_data", no_argument, NULL, 'w' }, - { "wipe_cache", no_argument, NULL, 'c' }, - { "show_text", no_argument, NULL, 't' }, - { "sideload", no_argument, NULL, 's' }, - { "sideload_auto_reboot", no_argument, NULL, 'a' }, - { "just_exit", no_argument, NULL, 'x' }, - { "locale", required_argument, NULL, 'l' }, - { "shutdown_after", no_argument, NULL, 'p' }, - { "reason", required_argument, NULL, 'r' }, - { "security", no_argument, NULL, 'e'}, - { "wipe_ab", no_argument, NULL, 0 }, - { "wipe_package_size", required_argument, NULL, 0 }, - { "prompt_and_wipe_data", no_argument, NULL, 0 }, - { NULL, 0, NULL, 0 }, -}; - -// More bootreasons can be found in "system/core/bootstat/bootstat.cpp". -static const std::vector bootreason_blacklist { - "kernel_panic", - "Panic", -}; - -static const char *CACHE_LOG_DIR = "/cache/recovery"; -static const char *COMMAND_FILE = "/cache/recovery/command"; -static const char *LOG_FILE = "/cache/recovery/log"; -static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; -static const char *LOCALE_FILE = "/cache/recovery/last_locale"; -static const char *CONVERT_FBE_DIR = "/tmp/convert_fbe"; -static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe"; -static const char *CACHE_ROOT = "/cache"; -static const char *DATA_ROOT = "/data"; -static const char* METADATA_ROOT = "/metadata"; -static const char *SDCARD_ROOT = "/sdcard"; -static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; -static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; -static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; -static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; -// We will try to apply the update package 5 times at most in case of an I/O error or -// bspatch | imgpatch error. -static const int RETRY_LIMIT = 4; -static const int BATTERY_READ_TIMEOUT_IN_SEC = 10; -// GmsCore enters recovery mode to install package when having enough battery -// percentage. Normally, the threshold is 40% without charger and 20% with charger. -// So we should check battery with a slightly lower limitation. -static const int BATTERY_OK_PERCENTAGE = 20; -static const int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15; -static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe"; -static constexpr const char* DEFAULT_LOCALE = "en-US"; - -// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed -// into target_files.zip. Assert the version defined in code and in Android.mk are consistent. -static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions."); - -static std::string locale; -static bool has_cache = false; - -RecoveryUI* ui = nullptr; -bool modified_flash = false; -std::string stage; -const char* reason = nullptr; -struct selabel_handle* sehandle; +#include "otautil/paths.h" +#include "otautil/sysutil.h" +#include "recovery_ui/screen_ui.h" +#include "recovery_ui/ui.h" +#include "recovery_utils/battery_utils.h" +#include "recovery_utils/logging.h" +#include "recovery_utils/roots.h" + +static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; +static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; +static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; +static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; + +static constexpr const char* CACHE_ROOT = "/cache"; + +static bool save_current_log = false; /* * The recovery tool communicates with the main system through /cache files. @@ -146,10 +78,13 @@ struct selabel_handle* sehandle; * * The arguments which may be supplied in the recovery.command file: * --update_package=path - verify install an OTA package file + * --install_with_fuse - install the update package with FUSE. This allows installation of large + * packages on LP32 builds. Since the mmap will otherwise fail due to out of memory. * --wipe_data - erase user data (and cache), then reboot - * --prompt_and_wipe_data - prompt the user that data is corrupt, - * with their consent erase user data (and cache), then reboot + * --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user + * data (and cache), then reboot * --wipe_cache - wipe cache (but not user data), then reboot + * --show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519). * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs * --just_exit - do nothing; exit and reboot * @@ -165,7 +100,7 @@ struct selabel_handle* sehandle; * -- after this, rebooting will restart the erase -- * 5. erase_volume() reformats /data * 6. erase_volume() reformats /cache - * 7. finish_recovery() erases BCB + * 7. FinishRecovery() erases BCB * -- after this, rebooting will restart the main system -- * 8. main() calls reboot() to boot main system * @@ -175,321 +110,27 @@ struct selabel_handle* sehandle; * 3. main system reboots into recovery * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..." * -- after this, rebooting will attempt to reinstall the update -- - * 5. install_package() attempts to install the update + * 5. InstallPackage() attempts to install the update * NOTE: the package install must itself be restartable from any point - * 6. finish_recovery() erases BCB + * 6. FinishRecovery() erases BCB * -- after this, rebooting will (try to) restart the main system -- * 7. ** if install failed ** - * 7a. prompt_and_wait() shows an error icon and waits for the user + * 7a. PromptAndWait() shows an error icon and waits for the user * 7b. the user reboots (pulling the battery, etc) into the main system */ -// Open a given path, mounting partitions as necessary. -FILE* fopen_path(const char* path, const char* mode) { - if (ensure_path_mounted(path) != 0) { - LOG(ERROR) << "Can't mount " << path; - return nullptr; - } - - // When writing, try to create the containing directory, if necessary. Use generous permissions, - // the system (init.rc) will reset them. - if (strchr("wa", mode[0])) { - mkdir_recursively(path, 0777, true, sehandle); - } - return fopen(path, mode); -} - -// close a file, log an error if the error indicator is set -static void check_and_fclose(FILE *fp, const char *name) { - fflush(fp); - if (fsync(fileno(fp)) == -1) { - PLOG(ERROR) << "Failed to fsync " << name; - } - if (ferror(fp)) { - PLOG(ERROR) << "Error in " << name; - } - fclose(fp); -} - -bool is_ro_debuggable() { - return android::base::GetBoolProperty("ro.debuggable", false); -} - -bool reboot(const std::string& command) { - std::string cmd = command; - if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { - cmd += ",quiescent"; - } - return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd); -} - -static void redirect_stdio(const char* filename) { - int pipefd[2]; - if (pipe(pipefd) == -1) { - PLOG(ERROR) << "pipe failed"; - - // Fall back to traditional logging mode without timestamps. - // If these fail, there's not really anywhere to complain... - freopen(filename, "a", stdout); setbuf(stdout, NULL); - freopen(filename, "a", stderr); setbuf(stderr, NULL); - - return; - } - - pid_t pid = fork(); - if (pid == -1) { - PLOG(ERROR) << "fork failed"; - - // Fall back to traditional logging mode without timestamps. - // If these fail, there's not really anywhere to complain... - freopen(filename, "a", stdout); setbuf(stdout, NULL); - freopen(filename, "a", stderr); setbuf(stderr, NULL); - - return; - } - - if (pid == 0) { - /// Close the unused write end. - close(pipefd[1]); - - auto start = std::chrono::steady_clock::now(); - - // Child logger to actually write to the log file. - FILE* log_fp = fopen(filename, "ae"); - if (log_fp == nullptr) { - PLOG(ERROR) << "fopen \"" << filename << "\" failed"; - close(pipefd[0]); - _exit(EXIT_FAILURE); - } - - FILE* pipe_fp = fdopen(pipefd[0], "r"); - if (pipe_fp == nullptr) { - PLOG(ERROR) << "fdopen failed"; - check_and_fclose(log_fp, filename); - close(pipefd[0]); - _exit(EXIT_FAILURE); - } - - char* line = nullptr; - size_t len = 0; - while (getline(&line, &len, pipe_fp) != -1) { - auto now = std::chrono::steady_clock::now(); - double duration = std::chrono::duration_cast>( - now - start).count(); - if (line[0] == '\n') { - fprintf(log_fp, "[%12.6lf]\n", duration); - } else { - fprintf(log_fp, "[%12.6lf] %s", duration, line); - } - fflush(log_fp); - } - - PLOG(ERROR) << "getline failed"; - - free(line); - check_and_fclose(log_fp, filename); - close(pipefd[0]); - _exit(EXIT_FAILURE); - } else { - // Redirect stdout/stderr to the logger process. - // Close the unused read end. - close(pipefd[0]); - - setbuf(stdout, nullptr); - setbuf(stderr, nullptr); - - if (dup2(pipefd[1], STDOUT_FILENO) == -1) { - PLOG(ERROR) << "dup2 stdout failed"; - } - if (dup2(pipefd[1], STDERR_FILENO) == -1) { - PLOG(ERROR) << "dup2 stderr failed"; - } - - close(pipefd[1]); - } -} - -// 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) -static std::vector get_args(const int argc, char** const argv) { - CHECK_GT(argc, 0); - - bootloader_message boot = {}; - std::string err; - if (!read_bootloader_message(&boot, &err)) { - LOG(ERROR) << err; - // If fails, leave a zeroed bootloader_message. - boot = {}; - } - stage = std::string(boot.stage); - - if (boot.command[0] != 0) { - std::string boot_command = std::string(boot.command, sizeof(boot.command)); - LOG(INFO) << "Boot command: " << boot_command; - } - - if (boot.status[0] != 0) { - std::string boot_status = std::string(boot.status, sizeof(boot.status)); - LOG(INFO) << "Boot status: " << boot_status; - } - - std::vector args(argv, argv + argc); - - // --- if arguments weren't supplied, look in the bootloader control block - if (args.size() == 1) { - boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination - std::string boot_recovery(boot.recovery); - std::vector tokens = android::base::Split(boot_recovery, "\n"); - if (!tokens.empty() && tokens[0] == "recovery") { - for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { - // Skip empty and '\0'-filled tokens. - if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); - } - LOG(INFO) << "Got " << args.size() << " arguments from boot message"; - } else if (boot.recovery[0] != 0) { - LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\""; - } - } - - // --- if that doesn't work, try the command file (if we have /cache). - if (args.size() == 1 && has_cache) { - std::string content; - if (ensure_path_mounted(COMMAND_FILE) == 0 && - android::base::ReadFileToString(COMMAND_FILE, &content)) { - std::vector tokens = android::base::Split(content, "\n"); - // All the arguments in COMMAND_FILE are needed (unlike the BCB message, - // COMMAND_FILE doesn't use filename as the first argument). - for (auto it = tokens.begin(); it != tokens.end(); it++) { - // Skip empty and '\0'-filled tokens. - if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); - } - LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE; - } - } - - // Write the arguments (excluding the filename in args[0]) back into the - // bootloader control block. So the device will always boot into recovery to - // finish the pending work, until finish_recovery() is called. - std::vector options(args.cbegin() + 1, args.cend()); - if (!update_bootloader_message(options, &err)) { - LOG(ERROR) << "Failed to set BCB message: " << err; - } - - return args; -} - -// Set the BCB to reboot back into recovery (it won't resume the install from -// sdcard though). -static void set_sdcard_update_bootloader_message() { - std::vector options; - std::string err; - if (!update_bootloader_message(options, &err)) { - LOG(ERROR) << "Failed to set BCB message: " << err; - } -} - -// Read from kernel log into buffer and write out to file. -static void save_kernel_log(const char* destination) { - int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); - if (klog_buf_len <= 0) { - PLOG(ERROR) << "Error getting klog size"; - return; - } - - std::string buffer(klog_buf_len, 0); - int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); - if (n == -1) { - PLOG(ERROR) << "Error in reading klog"; - return; - } - buffer.resize(n); - android::base::WriteStringToFile(buffer, destination); -} - -// write content to the current pmsg session. -static ssize_t __pmsg_write(const char *filename, const char *buf, size_t len) { - return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, - filename, buf, len); -} - -static void copy_log_file_to_pmsg(const char* source, const char* destination) { - std::string content; - android::base::ReadFileToString(source, &content); - __pmsg_write(destination, content.c_str(), content.length()); -} - -// How much of the temp log we have copied to the copy in cache. -static off_t tmplog_offset = 0; - -static void copy_log_file(const char* source, const char* destination, bool append) { - FILE* dest_fp = fopen_path(destination, append ? "ae" : "we"); - if (dest_fp == nullptr) { - PLOG(ERROR) << "Can't open " << destination; - } else { - FILE* source_fp = fopen(source, "re"); - if (source_fp != nullptr) { - if (append) { - fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write - } - char buf[4096]; - size_t bytes; - while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { - fwrite(buf, 1, bytes, dest_fp); - } - if (append) { - tmplog_offset = ftello(source_fp); - } - check_and_fclose(source_fp, source); - } - check_and_fclose(dest_fp, destination); - } -} - -static void copy_logs() { - // We only rotate and record the log of the current session if there are - // actual attempts to modify the flash, such as wipes, installs from BCB - // or menu selections. This is to avoid unnecessary rotation (and - // possible deletion) of log files, if it does not do anything loggable. - if (!modified_flash) { - return; - } - - // Always write to pmsg, this allows the OTA logs to be caught in logcat -L - copy_log_file_to_pmsg(TEMPORARY_LOG_FILE, LAST_LOG_FILE); - copy_log_file_to_pmsg(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE); - - // We can do nothing for now if there's no /cache partition. - if (!has_cache) { - return; - } - - ensure_path_mounted(LAST_LOG_FILE); - ensure_path_mounted(LAST_KMSG_FILE); - rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); - - // Copy logs to cache so the system can find out what happened. - copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true); - copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false); - copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false); - save_kernel_log(LAST_KMSG_FILE); - chmod(LOG_FILE, 0600); - chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); - chmod(LAST_KMSG_FILE, 0600); - chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM); - chmod(LAST_LOG_FILE, 0640); - chmod(LAST_INSTALL_FILE, 0644); - sync(); +static bool IsRoDebuggable() { + return android::base::GetBoolProperty("ro.debuggable", false); } // Clear the recovery command and prepare to boot a (hopefully working) system, // copy our log file to cache as well (for the system to read). This function is // idempotent: call it as many times as you like. -static void finish_recovery() { +static void FinishRecovery(RecoveryUI* ui) { + std::string locale = ui->GetLocale(); // Save the locale to cache, so if recovery is next started up without a '--locale' argument // (e.g., directly from the bootloader) it will use the last-known locale. - if (!locale.empty() && has_cache) { + if (!locale.empty() && HasCache()) { LOG(INFO) << "Saving locale \"" << locale << "\""; if (ensure_path_mounted(LOCALE_FILE) != 0) { LOG(ERROR) << "Failed to mount " << LOCALE_FILE; @@ -498,7 +139,7 @@ static void finish_recovery() { } } - copy_logs(); + copy_logs(save_current_log); // Reset to normal system boot so recovery won't cycle indefinitely. std::string err; @@ -507,7 +148,7 @@ static void finish_recovery() { } // Remove the command file, so recovery won't repeat indefinitely. - if (has_cache) { + if (HasCache()) { if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { LOG(WARNING) << "Can't unlink " << COMMAND_FILE; } @@ -517,435 +158,68 @@ static void finish_recovery() { sync(); // For good measure. } -struct saved_log_file { - std::string name; - struct stat sb; - std::string data; -}; - -static bool erase_volume(const char* volume) { - bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); - bool is_data = (strcmp(volume, DATA_ROOT) == 0); - - ui->SetBackground(RecoveryUI::ERASING); - ui->SetProgressType(RecoveryUI::INDETERMINATE); - - std::vector log_files; - - if (is_cache) { - // If we're reformatting /cache, we load any past logs - // (i.e. "/cache/recovery/last_*") and the current log - // ("/cache/recovery/log") into memory, so we can restore them after - // the reformat. - - ensure_path_mounted(volume); - - struct dirent* de; - std::unique_ptr d(opendir(CACHE_LOG_DIR), closedir); - if (d) { - while ((de = readdir(d.get())) != nullptr) { - if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { - std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name); - - struct stat sb; - if (stat(path.c_str(), &sb) == 0) { - // truncate files to 512kb - if (sb.st_size > (1 << 19)) { - sb.st_size = 1 << 19; - } - - std::string data(sb.st_size, '\0'); - FILE* f = fopen(path.c_str(), "rbe"); - fread(&data[0], 1, data.size(), f); - fclose(f); - - log_files.emplace_back(saved_log_file{ path, sb, data }); - } - } - } - } else { - if (errno != ENOENT) { - PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR; - } - } - } - - ui->Print("Formatting %s...\n", volume); - - ensure_path_unmounted(volume); - - int result; - - if (is_data && reason && strcmp(reason, "convert_fbe") == 0) { - // Create convert_fbe breadcrumb file to signal to init - // to convert to file based encryption, not full disk encryption - if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { - ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno)); - return true; - } - FILE* f = fopen(CONVERT_FBE_FILE, "wbe"); - if (!f) { - ui->Print("Failed to convert to file encryption %s\n", strerror(errno)); - return true; - } - fclose(f); - result = format_volume(volume, CONVERT_FBE_DIR); - remove(CONVERT_FBE_FILE); - rmdir(CONVERT_FBE_DIR); - } else { - result = format_volume(volume); - } - - if (is_cache) { - // Re-create the log dir and write back the log entries. - if (ensure_path_mounted(CACHE_LOG_DIR) == 0 && - mkdir_recursively(CACHE_LOG_DIR, 0777, false, sehandle) == 0) { - for (const auto& log : log_files) { - if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid, - log.sb.st_gid)) { - PLOG(ERROR) << "Failed to write to " << log.name; - } - } - } else { - PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR; - } - - // Any part of the log we'd copied to cache is now gone. - // Reset the pointer so we copy from the beginning of the temp - // log. - tmplog_offset = 0; - copy_logs(); - } - - return (result == 0); -} - -// Display a menu with the specified 'headers' and 'items'. Device specific HandleMenuKey() may -// return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only -// a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the -// (non-negative) chosen item number, or -1 if timed out waiting for input. -static int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only, - int initial_selection, Device* device) { - // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. - ui->FlushKeys(); - - ui->StartMenu(headers, items, initial_selection); - - int selected = initial_selection; - int chosen_item = -1; - while (chosen_item < 0) { - int key = ui->WaitKey(); - if (key == -1) { // WaitKey() timed out. - if (ui->WasTextEverVisible()) { - continue; - } else { - LOG(INFO) << "Timed out waiting for key input; rebooting."; - ui->EndMenu(); - return -1; - } - } - - bool visible = ui->IsTextVisible(); - int action = device->HandleMenuKey(key, visible); - - if (action < 0) { - switch (action) { - case Device::kHighlightUp: - selected = ui->SelectMenu(--selected); - break; - case Device::kHighlightDown: - selected = ui->SelectMenu(++selected); - break; - case Device::kInvokeItem: - chosen_item = selected; - break; - case Device::kNoAction: - break; - } - } else if (!menu_only) { - chosen_item = action; - } - } - - ui->EndMenu(); - return chosen_item; -} - -// Returns the selected filename, or an empty string. -static std::string browse_directory(const std::string& path, Device* device) { - ensure_path_mounted(path.c_str()); - - std::unique_ptr d(opendir(path.c_str()), closedir); - if (!d) { - PLOG(ERROR) << "error opening " << path; - return ""; - } - - std::vector dirs; - std::vector zips = { "../" }; // "../" is always the first entry. - - dirent* de; - while ((de = readdir(d.get())) != nullptr) { - std::string name(de->d_name); - - if (de->d_type == DT_DIR) { - // Skip "." and ".." entries. - if (name == "." || name == "..") continue; - dirs.push_back(name + "/"); - } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) { - zips.push_back(name); - } - } - - std::sort(dirs.begin(), dirs.end()); - std::sort(zips.begin(), zips.end()); - - // Append dirs to the zips list. - zips.insert(zips.end(), dirs.begin(), dirs.end()); - - const char* entries[zips.size() + 1]; - entries[zips.size()] = nullptr; - for (size_t i = 0; i < zips.size(); i++) { - entries[i] = zips[i].c_str(); - } - - const char* headers[] = { "Choose a package to install:", path.c_str(), nullptr }; - - int chosen_item = 0; - while (true) { - chosen_item = get_menu_selection(headers, entries, true, chosen_item, device); - - const std::string& item = zips[chosen_item]; - if (chosen_item == 0) { - // Go up but continue browsing (if the caller is browse_directory). - return ""; - } - - std::string new_path = path + "/" + item; - if (new_path.back() == '/') { - // Recurse down into a subdirectory. - new_path.pop_back(); - std::string result = browse_directory(new_path, device); - if (!result.empty()) return result; - } else { - // Selected a zip file: return the path to the caller. - return new_path; - } - } - - // Unreachable. -} - static bool yes_no(Device* device, const char* question1, const char* question2) { - const char* headers[] = { question1, question2, NULL }; - const char* items[] = { " No", " Yes", NULL }; + std::vector headers{ question1, question2 }; + std::vector items{ " No", " Yes" }; - int chosen_item = get_menu_selection(headers, items, true, 0, device); - return (chosen_item == 1); + size_t chosen_item = device->GetUI()->ShowMenu( + headers, items, 0, true, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); + return (chosen_item == 1); } static bool ask_to_wipe_data(Device* device) { - return yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!"); -} + std::vector headers{ "Wipe all user data?", " THIS CAN NOT BE UNDONE!" }; + std::vector items{ " Cancel", " Factory data reset" }; -// Return true on success. -static bool wipe_data(Device* device) { - modified_flash = true; + size_t chosen_item = device->GetUI()->ShowPromptWipeDataConfirmationMenu( + headers, items, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); - ui->Print("\n-- Wiping data...\n"); - bool success = device->PreWipeData(); - if (success) { - success &= erase_volume(DATA_ROOT); - if (has_cache) { - success &= erase_volume(CACHE_ROOT); - } - if (volume_for_mount_point(METADATA_ROOT) != nullptr) { - success &= erase_volume(METADATA_ROOT); - } - } - if (success) { - success &= device->PostWipeData(); - } - ui->Print("Data wipe %s.\n", success ? "complete" : "failed"); - return success; + return (chosen_item == 1); } -static bool prompt_and_wipe_data(Device* device) { +static InstallResult prompt_and_wipe_data(Device* device) { // Use a single string and let ScreenRecoveryUI handles the wrapping. - const char* const headers[] = { + std::vector wipe_data_menu_headers{ "Can't load Android system. Your data may be corrupt. " "If you continue to get this message, you may need to " "perform a factory data reset and erase all user data " "stored on this device.", - nullptr }; - const char* const items[] = { + // clang-format off + std::vector wipe_data_menu_items { "Try again", "Factory data reset", - NULL }; + // clang-format on for (;;) { - int chosen_item = get_menu_selection(headers, items, true, 0, device); - if (chosen_item != 1) { - return true; // Just reboot, no wipe; not a failure, user asked for it + size_t chosen_item = device->GetUI()->ShowPromptWipeDataMenu( + wipe_data_menu_headers, wipe_data_menu_items, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); + // If ShowMenu() returned RecoveryUI::KeyError::INTERRUPTED, WaitKey() was interrupted. + if (chosen_item == static_cast(RecoveryUI::KeyError::INTERRUPTED)) { + return INSTALL_KEY_INTERRUPTED; } - if (ask_to_wipe_data(device)) { - return wipe_data(device); - } - } -} - -// Return true on success. -static bool wipe_cache(bool should_confirm, Device* device) { - if (!has_cache) { - ui->Print("No /cache partition found.\n"); - return false; - } - - if (should_confirm && !yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!")) { - return false; + if (chosen_item != 1) { + return INSTALL_SUCCESS; // Just reboot, no wipe; not a failure, user asked for it } - modified_flash = true; - - ui->Print("\n-- Wiping cache...\n"); - bool success = erase_volume("/cache"); - ui->Print("Cache wipe %s.\n", success ? "complete" : "failed"); - return success; -} - -// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with -// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT. -static bool secure_wipe_partition(const std::string& partition) { - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); - if (fd == -1) { - PLOG(ERROR) << "Failed to open \"" << partition << "\""; - return false; - } - - uint64_t range[2] = { 0, 0 }; - if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { - PLOG(ERROR) << "Failed to get partition size"; - return false; - } - LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1]; - - LOG(INFO) << " Trying BLKSECDISCARD..."; - if (ioctl(fd, BLKSECDISCARD, &range) == -1) { - PLOG(WARNING) << " Failed"; - - // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. - unsigned int zeroes; - if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { - LOG(INFO) << " Trying BLKDISCARD..."; - if (ioctl(fd, BLKDISCARD, &range) == -1) { - PLOG(ERROR) << " Failed"; - return false; - } - } else { - LOG(INFO) << " Trying BLKZEROOUT..."; - if (ioctl(fd, BLKZEROOUT, &range) == -1) { - PLOG(ERROR) << " Failed"; - return false; + if (ask_to_wipe_data(device)) { + CHECK(device->GetReason().has_value()); + bool convert_fbe = device->GetReason().value() == "convert_fbe"; + if (WipeData(device, convert_fbe)) { + return INSTALL_SUCCESS; + } else { + return INSTALL_ERROR; } } } - - LOG(INFO) << " Done"; - return true; -} - -// Check if the wipe package matches expectation: -// 1. verify the package. -// 2. check metadata (ota-type, pre-device and serial number if having one). -static bool check_wipe_package(size_t wipe_package_size) { - if (wipe_package_size == 0) { - LOG(ERROR) << "wipe_package_size is zero"; - return false; - } - std::string wipe_package; - std::string err_str; - if (!read_wipe_package(&wipe_package, wipe_package_size, &err_str)) { - PLOG(ERROR) << "Failed to read wipe package"; - return false; - } - if (!verify_package(reinterpret_cast(wipe_package.data()), - wipe_package.size())) { - LOG(ERROR) << "Failed to verify package"; - return false; - } - - // Extract metadata - ZipArchiveHandle zip; - int err = OpenArchiveFromMemory(static_cast(&wipe_package[0]), wipe_package.size(), - "wipe_package", &zip); - if (err != 0) { - LOG(ERROR) << "Can't open wipe package : " << ErrorCodeString(err); - return false; - } - std::string metadata; - if (!read_metadata_from_package(zip, &metadata)) { - CloseArchive(zip); - return false; - } - CloseArchive(zip); - - // Check metadata - std::vector lines = android::base::Split(metadata, "\n"); - bool ota_type_matched = false; - bool device_type_matched = false; - bool has_serial_number = false; - bool serial_number_matched = false; - for (const auto& line : lines) { - if (line == "ota-type=BRICK") { - ota_type_matched = true; - } else if (android::base::StartsWith(line, "pre-device=")) { - std::string device_type = line.substr(strlen("pre-device=")); - std::string real_device_type = android::base::GetProperty("ro.build.product", ""); - device_type_matched = (device_type == real_device_type); - } else if (android::base::StartsWith(line, "serialno=")) { - std::string serial_no = line.substr(strlen("serialno=")); - std::string real_serial_no = android::base::GetProperty("ro.serialno", ""); - has_serial_number = true; - serial_number_matched = (serial_no == real_serial_no); - } - } - return ota_type_matched && device_type_matched && (!has_serial_number || serial_number_matched); -} - -// Wipe the current A/B device, with a secure wipe of all the partitions in -// RECOVERY_WIPE. -static bool wipe_ab_device(size_t wipe_package_size) { - ui->SetBackground(RecoveryUI::ERASING); - ui->SetProgressType(RecoveryUI::INDETERMINATE); - - if (!check_wipe_package(wipe_package_size)) { - LOG(ERROR) << "Failed to verify wipe package"; - return false; - } - std::string partition_list; - if (!android::base::ReadFileToString(RECOVERY_WIPE, &partition_list)) { - LOG(ERROR) << "failed to read \"" << RECOVERY_WIPE << "\""; - return false; - } - - std::vector lines = android::base::Split(partition_list, "\n"); - for (const std::string& line : lines) { - std::string partition = android::base::Trim(line); - // Ignore '#' comment or empty lines. - if (android::base::StartsWith(partition, "#") || partition.empty()) { - continue; - } - - // Proceed anyway even if it fails to wipe some partition. - secure_wipe_partition(partition); - } - return true; } static void choose_recovery_file(Device* device) { std::vector entries; - if (has_cache) { + if (HasCache()) { for (int i = 0; i < KEEP_LOG_COUNT; i++) { auto add_to_entries = [&](const char* filename) { std::string log_file(filename); @@ -953,7 +227,7 @@ static void choose_recovery_file(Device* device) { log_file += "." + std::to_string(i); } - if (ensure_path_mounted(log_file.c_str()) == 0 && access(log_file.c_str(), R_OK) == 0) { + if (ensure_path_mounted(log_file) == 0 && access(log_file.c_str(), R_OK) == 0) { entries.push_back(std::move(log_file)); } }; @@ -966,32 +240,34 @@ static void choose_recovery_file(Device* device) { } } else { // If cache partition is not found, view /tmp/recovery.log instead. - if (access(TEMPORARY_LOG_FILE, R_OK) == -1) { + if (access(Paths::Get().temporary_log_file().c_str(), R_OK) == -1) { return; } else { - entries.push_back(TEMPORARY_LOG_FILE); + entries.push_back(Paths::Get().temporary_log_file()); } } entries.push_back("Back"); - std::vector menu_entries(entries.size()); - std::transform(entries.cbegin(), entries.cend(), menu_entries.begin(), - [](const std::string& entry) { return entry.c_str(); }); - menu_entries.push_back(nullptr); + std::vector headers{ "Select file to view" }; - const char* headers[] = { "Select file to view", nullptr }; - - int chosen_item = 0; + size_t chosen_item = 0; while (true) { - chosen_item = get_menu_selection(headers, menu_entries.data(), true, chosen_item, device); + chosen_item = device->GetUI()->ShowMenu( + headers, entries, chosen_item, true, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); + + // Handle WaitKey() interrupt. + if (chosen_item == static_cast(RecoveryUI::KeyError::INTERRUPTED)) { + break; + } if (entries[chosen_item] == "Back") break; - ui->ShowFile(entries[chosen_item].c_str()); + device->GetUI()->ShowFile(entries[chosen_item]); } } -static void run_graphics_test() { +static void run_graphics_test(RecoveryUI* ui) { // Switch to graphics screen. ui->ShowText(false); @@ -1034,93 +310,64 @@ static void run_graphics_test() { ui->ShowText(true); } -// How long (in seconds) we wait for the fuse-provided package file to -// appear, before timing out. -#define SDCARD_INSTALL_TIMEOUT 10 - -static int apply_from_sdcard(Device* device, bool* wipe_cache) { - modified_flash = true; - - if (ensure_path_mounted(SDCARD_ROOT) != 0) { - ui->Print("\n-- Couldn't mount %s.\n", SDCARD_ROOT); - return INSTALL_ERROR; - } - - std::string path = browse_directory(SDCARD_ROOT, device); - if (path.empty()) { - ui->Print("\n-- No package file selected.\n"); - ensure_path_unmounted(SDCARD_ROOT); - return INSTALL_ERROR; - } - - ui->Print("\n-- Install %s ...\n", path.c_str()); - set_sdcard_update_bootloader_message(); - - // We used to use fuse in a thread as opposed to a process. Since accessing - // through fuse involves going from kernel to userspace to kernel, it leads - // to deadlock when a page fault occurs. (Bug: 26313124) - pid_t child; - if ((child = fork()) == 0) { - bool status = start_sdcard_fuse(path.c_str()); - - _exit(status ? EXIT_SUCCESS : EXIT_FAILURE); - } - - // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child - // process is ready. - int result = INSTALL_ERROR; - int status; - bool waited = false; - for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { - if (waitpid(child, &status, WNOHANG) == -1) { - result = INSTALL_ERROR; - waited = true; - break; - } - - struct stat sb; - if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &sb) == -1) { - if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT-1) { - sleep(1); - continue; - } else { - LOG(ERROR) << "Timed out waiting for the fuse-provided package."; - result = INSTALL_ERROR; - kill(child, SIGKILL); - break; - } - } +static void WriteUpdateInProgress() { + std::string err; + if (!update_bootloader_message({ "--reason=update_in_progress" }, &err)) { + LOG(ERROR) << "Failed to WriteUpdateInProgress: " << err; + } +} - result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, - TEMPORARY_INSTALL_FILE, false, 0/*retry_count*/); - break; - } +static bool AskToReboot(Device* device, Device::BuiltinAction chosen_action) { + bool is_non_ab = android::base::GetProperty("ro.boot.slot_suffix", "").empty(); + bool is_virtual_ab = android::base::GetBoolProperty("ro.virtual_ab.enabled", false); + if (!is_non_ab && !is_virtual_ab) { + // Only prompt for non-A/B or Virtual A/B devices. + return true; + } - if (!waited) { - // Calling stat() on this magic filename signals the fuse - // filesystem to shut down. - struct stat sb; - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &sb); + std::string header_text; + std::string item_text; + switch (chosen_action) { + case Device::REBOOT: + header_text = "reboot"; + item_text = " Reboot system now"; + break; + case Device::SHUTDOWN: + header_text = "power off"; + item_text = " Power off"; + break; + default: + LOG(FATAL) << "Invalid chosen action " << chosen_action; + break; + } - waitpid(child, &status, 0); - } + std::vector headers{ "WARNING: Previous installation has failed.", + " Your device may fail to boot if you " + header_text + + " now.", + " Confirm reboot?" }; + std::vector items{ " Cancel", item_text }; - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status); - } + size_t chosen_item = device->GetUI()->ShowMenu( + headers, items, 0, true /* menu_only */, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); - ensure_path_unmounted(SDCARD_ROOT); - return result; + return (chosen_item == 1); } -// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, -// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. -static Device::BuiltinAction prompt_and_wait(Device* device, int status) { +// Shows the recovery UI and waits for user input. Returns one of the device builtin actions, such +// as REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, which +// is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. +static Device::BuiltinAction PromptAndWait(Device* device, InstallResult status) { + auto ui = device->GetUI(); + bool update_in_progress = (device->GetReason().value_or("") == "update_in_progress"); for (;;) { - finish_recovery(); + FinishRecovery(ui); switch (status) { case INSTALL_SUCCESS: case INSTALL_NONE: + case INSTALL_SKIPPED: + case INSTALL_RETRY: + case INSTALL_KEY_INTERRUPTED: ui->SetBackground(RecoveryUI::NO_COMMAND); break; @@ -1128,97 +375,152 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { case INSTALL_CORRUPT: ui->SetBackground(RecoveryUI::ERROR); break; + + case INSTALL_REBOOT: + // All the reboots should have been handled prior to entering PromptAndWait() or immediately + // after installing a package. + LOG(FATAL) << "Invalid status code of INSTALL_REBOOT"; + break; } ui->SetProgressType(RecoveryUI::EMPTY); - int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device); + std::vector headers; + if (update_in_progress) { + headers = { "WARNING: Previous installation has failed.", + " Your device may fail to boot if you reboot or power off now." }; + } + size_t chosen_item = ui->ShowMenu( + headers, device->GetMenuItems(), 0, false, + std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); + // Handle Interrupt key + if (chosen_item == static_cast(RecoveryUI::KeyError::INTERRUPTED)) { + return Device::KEY_INTERRUPTED; + } // Device-specific code may take some action here. It may return one of the core actions // handled in the switch statement below. Device::BuiltinAction chosen_action = - (chosen_item == -1) ? Device::REBOOT : device->InvokeMenuItem(chosen_item); + (chosen_item == static_cast(RecoveryUI::KeyError::TIMED_OUT)) + ? Device::REBOOT + : device->InvokeMenuItem(chosen_item); - bool should_wipe_cache = false; switch (chosen_action) { + case Device::REBOOT_FROM_FASTBOOT: // Can not happen + case Device::SHUTDOWN_FROM_FASTBOOT: // Can not happen case Device::NO_ACTION: break; - case Device::REBOOT: - case Device::SHUTDOWN: + case Device::ENTER_FASTBOOT: + case Device::ENTER_RECOVERY: case Device::REBOOT_BOOTLOADER: + case Device::REBOOT_FASTBOOT: + case Device::REBOOT_RECOVERY: + case Device::REBOOT_RESCUE: return chosen_action; + case Device::REBOOT: + case Device::SHUTDOWN: + if (!ui->IsTextVisible()) { + return Device::REBOOT; + } + // okay to reboot; no need to ask. + if (!update_in_progress) { + return Device::REBOOT; + } + // An update might have been failed. Ask if user really wants to reboot. + if (AskToReboot(device, chosen_action)) { + return Device::REBOOT; + } + break; + case Device::WIPE_DATA: + save_current_log = true; if (ui->IsTextVisible()) { if (ask_to_wipe_data(device)) { - wipe_data(device); + WipeData(device, false); } } else { - wipe_data(device); + WipeData(device, false); return Device::NO_ACTION; } break; - case Device::WIPE_CACHE: - wipe_cache(ui->IsTextVisible(), device); + case Device::WIPE_CACHE: { + save_current_log = true; + std::function confirm_func = [&device]() { + return yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!"); + }; + WipeCache(ui, ui->IsTextVisible() ? confirm_func : nullptr); if (!ui->IsTextVisible()) return Device::NO_ACTION; break; + } case Device::APPLY_ADB_SIDELOAD: case Device::APPLY_SDCARD: - { - bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); - if (adb) { - status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE); - } else { - status = apply_from_sdcard(device, &should_wipe_cache); - } + case Device::ENTER_RESCUE: { + save_current_log = true; + + update_in_progress = true; + WriteUpdateInProgress(); + + bool adb = true; + Device::BuiltinAction reboot_action; + if (chosen_action == Device::ENTER_RESCUE) { + // Switch to graphics screen. + ui->ShowText(false); + status = ApplyFromAdb(device, true /* rescue_mode */, &reboot_action); + } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) { + status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action); + } else { + adb = false; + status = ApplyFromSdcard(device); + } - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; - } - } + ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status); + if (status == INSTALL_REBOOT) { + return reboot_action; + } - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - copy_logs(); - } else if (!ui->IsTextVisible()) { + if (status == INSTALL_SUCCESS) { + update_in_progress = false; + if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); } + } else { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + copy_logs(save_current_log); } break; + } case Device::VIEW_RECOVERY_LOGS: choose_recovery_file(device); break; case Device::RUN_GRAPHICS_TEST: - run_graphics_test(); + run_graphics_test(ui); break; case Device::RUN_LOCALE_TEST: { ScreenRecoveryUI* screen_ui = static_cast(ui); - screen_ui->CheckBackgroundTextImages(locale); + screen_ui->CheckBackgroundTextImages(); break; } + case Device::MOUNT_SYSTEM: - // For a system image built with the root directory (i.e. system_root_image == "true"), we - // mount it to /system_root, and symlink /system to /system_root/system to make adb shell - // work (the symlink is created through the build system). (Bug: 22855115) - if (android::base::GetBoolProperty("ro.build.system_root_image", false)) { - if (ensure_path_mounted_at("/", "/system_root") != -1) { - ui->Print("Mounted /system.\n"); - } - } else { - if (ensure_path_mounted("/system") != -1) { - ui->Print("Mounted /system.\n"); - } + // For Virtual A/B, set up the snapshot devices (if exist). + if (!CreateSnapshotPartitions()) { + ui->Print("Virtual A/B: snapshot partitions creation failed.\n"); + break; + } + if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) { + ui->Print("Mounted /system.\n"); } break; + + case Device::KEY_INTERRUPTED: + return Device::KEY_INTERRUPTED; } } } @@ -1227,119 +529,17 @@ static void print_property(const char* key, const char* name, void* /* cookie */ printf("%s=%s\n", key, name); } -static std::string load_locale_from_cache() { - if (ensure_path_mounted(LOCALE_FILE) != 0) { - LOG(ERROR) << "Can't mount " << LOCALE_FILE; - return ""; - } - - std::string content; - if (!android::base::ReadFileToString(LOCALE_FILE, &content)) { - PLOG(ERROR) << "Can't read " << LOCALE_FILE; - return ""; - } - - return android::base::Trim(content); -} - -void ui_print(const char* format, ...) { - std::string buffer; - va_list ap; - va_start(ap, format); - android::base::StringAppendV(&buffer, format, ap); - va_end(ap); +static bool IsBatteryOk(int* required_battery_level) { + // GmsCore enters recovery mode to install package when having enough battery percentage. + // Normally, the threshold is 40% without charger and 20% with charger. So we check the battery + // level against a slightly lower limit. + constexpr int BATTERY_OK_PERCENTAGE = 20; + constexpr int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15; - if (ui != nullptr) { - ui->Print("%s", buffer.c_str()); - } else { - fputs(buffer.c_str(), stdout); - } -} - -static constexpr char log_characters[] = "VDIWEF"; - -void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, - const char* /* tag */, const char* /* file */, unsigned int /* line */, - const char* message) { - if (severity >= android::base::ERROR && ui != nullptr) { - ui->Print("E:%s\n", message); - } else { - fprintf(stdout, "%c:%s\n", log_characters[severity], message); - } -} - -static bool is_battery_ok() { - using android::hardware::health::V1_0::BatteryStatus; - using android::hardware::health::V2_0::Result; - using android::hardware::health::V2_0::toString; - using android::hardware::health::V2_0::implementation::Health; - - struct healthd_config healthd_config = { - .batteryStatusPath = android::String8(android::String8::kEmptyString), - .batteryHealthPath = android::String8(android::String8::kEmptyString), - .batteryPresentPath = android::String8(android::String8::kEmptyString), - .batteryCapacityPath = android::String8(android::String8::kEmptyString), - .batteryVoltagePath = android::String8(android::String8::kEmptyString), - .batteryTemperaturePath = android::String8(android::String8::kEmptyString), - .batteryTechnologyPath = android::String8(android::String8::kEmptyString), - .batteryCurrentNowPath = android::String8(android::String8::kEmptyString), - .batteryCurrentAvgPath = android::String8(android::String8::kEmptyString), - .batteryChargeCounterPath = android::String8(android::String8::kEmptyString), - .batteryFullChargePath = android::String8(android::String8::kEmptyString), - .batteryCycleCountPath = android::String8(android::String8::kEmptyString), - .energyCounter = NULL, - .boot_min_cap = 0, - .screen_on = NULL - }; - - auto health = - android::hardware::health::V2_0::implementation::Health::initInstance(&healthd_config); - - int wait_second = 0; - while (true) { - auto charge_status = BatteryStatus::UNKNOWN; - health - ->getChargeStatus([&charge_status](auto res, auto out_status) { - if (res == Result::SUCCESS) { - charge_status = out_status; - } - }) - .isOk(); // should not have transport error - - // Treat unknown status as charged. - bool charged = (charge_status != BatteryStatus::DISCHARGING && - charge_status != BatteryStatus::NOT_CHARGING); - - Result res = Result::UNKNOWN; - int32_t capacity = INT32_MIN; - health - ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) { - res = out_res; - capacity = out_capacity; - }) - .isOk(); // should not have transport error - - ui_print("charge_status %d, charged %d, status %s, capacity %" PRId32 "\n", charge_status, - charged, toString(res).c_str(), capacity); - // At startup, the battery drivers in devices like N5X/N6P take some time to load - // the battery profile. Before the load finishes, it reports value 50 as a fake - // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected - // to finish loading the battery profile earlier than 10 seconds after kernel startup. - if (res == Result::SUCCESS && capacity == 50) { - if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) { - sleep(1); - wait_second++; - continue; - } - } - // If we can't read battery percentage, it may be a device without battery. In this - // situation, use 100 as a fake battery percentage. - if (res != Result::SUCCESS) { - capacity = 100; - } - return (charged && capacity >= BATTERY_WITH_CHARGER_OK_PERCENTAGE) || - (!charged && capacity >= BATTERY_OK_PERCENTAGE); - } + auto battery_info = GetBatteryInfo(); + *required_battery_level = + battery_info.charging ? BATTERY_WITH_CHARGER_OK_PERCENTAGE : BATTERY_OK_PERCENTAGE; + return battery_info.capacity >= *required_battery_level; } // Set the retry count to |retry_count| in BCB. @@ -1362,136 +562,120 @@ static void set_retry_bootloader_message(int retry_count, const std::vector kBootreasonBlacklist{ + "kernel_panic", + "Panic", + }; + for (const auto& str : kBootreasonBlacklist) { + if (android::base::EqualsIgnoreCase(str, bootreason)) return true; } } return false; } -static void log_failure_code(ErrorCode code, const char *update_package) { - std::vector log_buffer = { - update_package, - "0", // install result - "error: " + std::to_string(code), - }; - std::string log_content = android::base::Join(log_buffer, "\n"); - if (!android::base::WriteStringToFile(log_content, TEMPORARY_INSTALL_FILE)) { - PLOG(ERROR) << "failed to write " << TEMPORARY_INSTALL_FILE; - } - - // Also write the info into last_log. - LOG(INFO) << log_content; -} - -int main(int argc, char **argv) { - // We don't have logcat yet under recovery; so we'll print error on screen and - // log to stdout (which is redirected to recovery.log) as we used to do. - android::base::InitLogging(argv, &UiLogger); - - // Take last pmsg contents and rewrite it to the current pmsg session. - static const char filter[] = "recovery/"; - // Do we need to rotate? - bool doRotate = false; - - __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate); - // Take action to refresh pmsg contents - __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &doRotate); - - // If this binary is started with the single argument "--adbd", - // instead of being the normal recovery binary, it turns into kind - // of a stripped-down version of adbd that only supports the - // 'sideload' command. Note this must be a real argument, not - // anything in the command file or bootloader control block; the - // only way recovery should be run with this argument is when it - // starts a copy of itself from the apply_from_adb() function. - if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { - minadbd_main(); - return 0; - } - - time_t start = time(nullptr); - - // redirect_stdio should be called only in non-sideload mode. Otherwise - // we may have two logger instances with different timestamps. - redirect_stdio(TEMPORARY_LOG_FILE); - - printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); - - load_volume_table(); - has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; - - std::vector args = get_args(argc, argv); - std::vector args_to_parse(args.size()); - std::transform(args.cbegin(), args.cend(), args_to_parse.begin(), - [](const std::string& arg) { return const_cast(arg.c_str()); }); +static void log_failure_code(ErrorCode code, const std::string& update_package) { + std::vector log_buffer = { + update_package, + "0", // install result + "error: " + std::to_string(code), + }; + std::string log_content = android::base::Join(log_buffer, "\n"); + const std::string& install_file = Paths::Get().temporary_install_file(); + if (!android::base::WriteStringToFile(log_content, install_file)) { + PLOG(ERROR) << "Failed to write " << install_file; + } + + // Also write the info into last_log. + LOG(INFO) << log_content; +} + +Device::BuiltinAction start_recovery(Device* device, const std::vector& args) { + static constexpr struct option OPTIONS[] = { + { "fastboot", no_argument, nullptr, 0 }, + { "install_with_fuse", no_argument, nullptr, 0 }, + { "just_exit", no_argument, nullptr, 'x' }, + { "locale", required_argument, nullptr, 0 }, + { "prompt_and_wipe_data", no_argument, nullptr, 0 }, + { "reason", required_argument, nullptr, 0 }, + { "rescue", no_argument, nullptr, 0 }, + { "retry_count", required_argument, nullptr, 0 }, + { "security", no_argument, nullptr, 0 }, + { "show_text", no_argument, nullptr, 't' }, + { "shutdown_after", no_argument, nullptr, 0 }, + { "sideload", no_argument, nullptr, 0 }, + { "sideload_auto_reboot", no_argument, nullptr, 0 }, + { "update_package", required_argument, nullptr, 0 }, + { "wipe_ab", no_argument, nullptr, 0 }, + { "wipe_cache", no_argument, nullptr, 0 }, + { "wipe_data", no_argument, nullptr, 0 }, + { "wipe_package_size", required_argument, nullptr, 0 }, + { nullptr, 0, nullptr, 0 }, + }; const char* update_package = nullptr; + bool install_with_fuse = false; // memory map the update package by default. bool should_wipe_data = false; bool should_prompt_and_wipe_data = false; bool should_wipe_cache = false; bool should_wipe_ab = false; size_t wipe_package_size = 0; - bool show_text = false; bool sideload = false; bool sideload_auto_reboot = false; + bool rescue = false; bool just_exit = false; bool shutdown_after = false; int retry_count = 0; bool security_update = false; + std::string locale; + + auto args_to_parse = StringVectorToNullTerminatedArray(args); int arg; int option_index; - while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS, + // Parse everything before the last element (which must be a nullptr). getopt_long(3) expects a + // null-terminated char* array, but without counting null as an arg (i.e. argv[argc] should be + // nullptr). + while ((arg = getopt_long(args_to_parse.size() - 1, args_to_parse.data(), "", OPTIONS, &option_index)) != -1) { switch (arg) { - case 'n': - android::base::ParseInt(optarg, &retry_count, 0); - break; - case 'u': - update_package = optarg; - break; - case 'w': - should_wipe_data = true; - break; - case 'c': - should_wipe_cache = true; - break; case 't': - show_text = true; - break; - case 's': - sideload = true; - break; - case 'a': - sideload = true; - sideload_auto_reboot = true; + // Handled in recovery_main.cpp break; case 'x': just_exit = true; break; - case 'l': - locale = optarg; - break; - case 'p': - shutdown_after = true; - break; - case 'r': - reason = optarg; - break; - case 'e': - security_update = true; - break; case 0: { std::string option = OPTIONS[option_index].name; - if (option == "wipe_ab") { + if (option == "install_with_fuse") { + install_with_fuse = true; + } else if (option == "locale" || option == "fastboot" || option == "reason") { + // Handled in recovery_main.cpp + } else if (option == "prompt_and_wipe_data") { + should_prompt_and_wipe_data = true; + } else if (option == "rescue") { + rescue = true; + } else if (option == "retry_count") { + android::base::ParseInt(optarg, &retry_count, 0); + } else if (option == "security") { + security_update = true; + } else if (option == "sideload") { + sideload = true; + } else if (option == "sideload_auto_reboot") { + sideload = true; + sideload_auto_reboot = true; + } else if (option == "shutdown_after") { + shutdown_after = true; + } else if (option == "update_package") { + update_package = optarg; + } else if (option == "wipe_ab") { should_wipe_ab = true; + } else if (option == "wipe_cache") { + should_wipe_cache = true; + } else if (option == "wipe_data") { + should_wipe_data = true; } else if (option == "wipe_package_size") { android::base::ParseUint(optarg, &wipe_package_size); - } else if (option == "prompt_and_wipe_data") { - should_prompt_and_wipe_data = true; } break; } @@ -1500,52 +684,29 @@ int main(int argc, char **argv) { continue; } } + optind = 1; - if (locale.empty()) { - if (has_cache) { - locale = load_locale_from_cache(); - } - - if (locale.empty()) { - locale = DEFAULT_LOCALE; - } - } - - printf("locale is [%s]\n", locale.c_str()); - printf("stage is [%s]\n", stage.c_str()); - printf("reason is [%s]\n", reason); + printf("stage is [%s]\n", device->GetStage().value_or("").c_str()); + printf("reason is [%s]\n", device->GetReason().value_or("").c_str()); - Device* device = make_device(); - if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { - printf("Quiescent recovery mode.\n"); - ui = new StubRecoveryUI(); - } else { - ui = device->GetUI(); - - if (!ui->Init(locale)) { - printf("Failed to initialize UI, use stub UI instead.\n"); - ui = new StubRecoveryUI(); - } - } + auto ui = device->GetUI(); // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". ui->SetSystemUpdateText(security_update); int st_cur, st_max; - if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) { + if (!device->GetStage().has_value() && + sscanf(device->GetStage().value().c_str(), "%d/%d", &st_cur, &st_max) == 2) { ui->SetStage(st_cur, st_max); } - ui->SetBackground(RecoveryUI::NONE); - if (show_text) ui->ShowText(true); - - sehandle = selinux_android_file_context_handle(); - selinux_android_set_sehandle(sehandle); - if (!sehandle) { - ui->Print("Warning: No file_contexts\n"); - } + std::vector title_lines = + android::base::Split(android::base::GetProperty("ro.bootimage.build.fingerprint", ""), ":"); + title_lines.insert(std::begin(title_lines), "Android Recovery"); + ui->SetTitle(title_lines); + ui->ResetKeyInterruptStatus(); device->StartRecovery(); printf("Command:"); @@ -1557,23 +718,23 @@ int main(int argc, char **argv) { property_list(print_property, nullptr); printf("\n"); - ui->Print("Supported API: %d\n", kRecoveryApiVersion); - - int status = INSTALL_SUCCESS; + InstallResult status = INSTALL_SUCCESS; + // next_action indicates the next target to reboot into upon finishing the install. It could be + // overridden to a different reboot target per user request. + Device::BuiltinAction next_action = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; if (update_package != nullptr) { // It's not entirely true that we will modify the flash. But we want // to log the update attempt since update_package is non-NULL. - modified_flash = true; + save_current_log = true; - if (!is_battery_ok()) { - ui->Print("battery capacity is not enough for installing package, needed is %d%%\n", - BATTERY_OK_PERCENTAGE); - // Log the error code to last_install when installation skips due to - // low battery. + if (int required_battery_level; retry_count == 0 && !IsBatteryOk(&required_battery_level)) { + ui->Print("battery capacity is not enough for installing package: %d%% needed\n", + required_battery_level); + // Log the error code to last_install when installation skips due to low battery. log_failure_code(kLowBattery, update_package); status = INSTALL_SKIPPED; - } else if (bootreason_in_blacklist()) { + } else if (retry_count == 0 && bootreason_in_blacklist()) { // Skip update-on-reboot when bootreason is kernel_panic or similar ui->Print("bootreason is in the blacklist; skip OTA installation\n"); log_failure_code(kBootreasonInBlacklist, update_package); @@ -1585,83 +746,101 @@ int main(int argc, char **argv) { set_retry_bootloader_message(retry_count + 1, args); } - status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true, - retry_count); - if (status == INSTALL_SUCCESS && should_wipe_cache) { - wipe_cache(false, device); + bool should_use_fuse = false; + if (!SetupPackageMount(update_package, &should_use_fuse)) { + LOG(INFO) << "Failed to set up the package access, skipping installation"; + status = INSTALL_ERROR; + } else if (install_with_fuse || should_use_fuse) { + LOG(INFO) << "Installing package " << update_package << " with fuse"; + status = InstallWithFuseFromPath(update_package, ui); + } else if (auto memory_package = Package::CreateMemoryPackage( + update_package, + std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + memory_package != nullptr) { + status = InstallPackage(memory_package.get(), update_package, should_wipe_cache, + retry_count, ui); + } else { + // We may fail to memory map the package on 32 bit builds for packages with 2GiB+ size. + // In such cases, we will try to install the package with fuse. This is not the default + // installation method because it introduces a layer of indirection from the kernel space. + LOG(WARNING) << "Failed to memory map package " << update_package + << "; falling back to install with fuse"; + status = InstallWithFuseFromPath(update_package, ui); } if (status != INSTALL_SUCCESS) { ui->Print("Installation aborted.\n"); - // When I/O error happens, reboot and retry installation RETRY_LIMIT - // times before we abandon this OTA update. + + // When I/O error or bspatch/imgpatch error happens, reboot and retry installation + // RETRY_LIMIT times before we abandon this OTA update. + static constexpr int RETRY_LIMIT = 4; if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) { - copy_logs(); + copy_logs(save_current_log); retry_count += 1; set_retry_bootloader_message(retry_count, args); // Print retry count on screen. ui->Print("Retry attempt %d\n", retry_count); - // Reboot and retry the update - if (!reboot("reboot,recovery")) { - ui->Print("Reboot failed\n"); - } else { - while (true) { - pause(); - } - } + // Reboot back into recovery to retry the update. + Reboot("recovery"); } // If this is an eng or userdebug build, then automatically // turn the text display on if the script fails so the error // message is visible. - if (is_ro_debuggable()) { + if (IsRoDebuggable()) { ui->ShowText(true); } } } } else if (should_wipe_data) { - if (!wipe_data(device)) { + save_current_log = true; + CHECK(device->GetReason().has_value()); + bool convert_fbe = device->GetReason().value() == "convert_fbe"; + if (!WipeData(device, convert_fbe)) { status = INSTALL_ERROR; } } else if (should_prompt_and_wipe_data) { + // Trigger the logging to capture the cause, even if user chooses to not wipe data. + save_current_log = true; + ui->ShowText(true); ui->SetBackground(RecoveryUI::ERROR); - if (!prompt_and_wipe_data(device)) { - status = INSTALL_ERROR; + status = prompt_and_wipe_data(device); + if (status != INSTALL_KEY_INTERRUPTED) { + ui->ShowText(false); } - ui->ShowText(false); } else if (should_wipe_cache) { - if (!wipe_cache(false, device)) { + save_current_log = true; + if (!WipeCache(ui, nullptr)) { status = INSTALL_ERROR; } } else if (should_wipe_ab) { - if (!wipe_ab_device(wipe_package_size)) { + if (!WipeAbDevice(device, wipe_package_size)) { status = INSTALL_ERROR; } } else if (sideload) { - // 'adb reboot sideload' acts the same as user presses key combinations - // to enter the sideload mode. When 'sideload-auto-reboot' is used, text - // display will NOT be turned on by default. And it will reboot after - // sideload finishes even if there are errors. Unless one turns on the - // text display during the installation. This is to enable automated + // 'adb reboot sideload' acts the same as user presses key combinations to enter the sideload + // mode. When 'sideload-auto-reboot' is used, text display will NOT be turned on by default. And + // it will reboot after sideload finishes even if there are errors. This is to enable automated // testing. + save_current_log = true; if (!sideload_auto_reboot) { ui->ShowText(true); } - status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE); - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; - } - } + status = ApplyFromAdb(device, false /* rescue_mode */, &next_action); ui->Print("\nInstall from ADB complete (status: %d).\n", status); if (sideload_auto_reboot) { + status = INSTALL_REBOOT; ui->Print("Rebooting automatically.\n"); } + } else if (rescue) { + save_current_log = true; + status = ApplyFromAdb(device, true /* rescue_mode */, &next_action); + ui->Print("\nInstall from ADB complete (status: %d).\n", status); } else if (!just_exit) { // If this is an eng or userdebug build, automatically turn on the text display if no command // is specified. Note that this should be called before setting the background to avoid // flickering the background image. - if (is_ro_debuggable()) { + if (IsRoDebuggable()) { ui->ShowText(true); } status = INSTALL_NONE; // No command specified @@ -1675,43 +854,26 @@ int main(int argc, char **argv) { } } - Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; - // 1. If the recovery menu is visible, prompt and wait for commands. - // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into - // recovery to sideload a package.) - // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device - // without waiting. - // 4. In all other cases, reboot the device. Therefore, normal users will observe the device - // reboot after it shows the "error" screen for 5s. - if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) { - Device::BuiltinAction temp = prompt_and_wait(device, status); - if (temp != Device::NO_ACTION) { - after = temp; + // Determine the next action. + // - If the state is INSTALL_REBOOT, device will reboot into the target as specified in + // `next_action`. + // - If the recovery menu is visible, prompt and wait for commands. + // - If the state is INSTALL_NONE, wait for commands (e.g. in user build, one manually boots + // into recovery to sideload a package or to wipe the device). + // - In all other cases, reboot the device. Therefore, normal users will observe the device + // rebooting a) immediately upon successful finish (INSTALL_SUCCESS); or b) an "error" screen + // for 5s followed by an automatic reboot. + if (status != INSTALL_REBOOT) { + if (status == INSTALL_NONE || ui->IsTextVisible()) { + auto temp = PromptAndWait(device, status); + if (temp != Device::NO_ACTION) { + next_action = temp; + } } } // Save logs and clean up before rebooting or shutting down. - finish_recovery(); - - switch (after) { - case Device::SHUTDOWN: - ui->Print("Shutting down...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); - break; - - case Device::REBOOT_BOOTLOADER: - ui->Print("Rebooting to bootloader...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); - break; + FinishRecovery(ui); - default: - ui->Print("Rebooting...\n"); - reboot("reboot,"); - break; - } - while (true) { - pause(); - } - // Should be unreachable. - return EXIT_SUCCESS; + return next_action; } diff --git a/recovery.h b/recovery.h new file mode 100644 index 0000000000..f050549cc8 --- /dev/null +++ b/recovery.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "recovery_ui/device.h" + +Device::BuiltinAction start_recovery(Device* device, const std::vector& args); diff --git a/recovery_main.cpp b/recovery_main.cpp new file mode 100644 index 0000000000..80cba61d34 --- /dev/null +++ b/recovery_main.cpp @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* private pmsg functions */ +#include +#include +#include + +#include "fastboot/fastboot.h" +#include "install/wipe_data.h" +#include "otautil/boot_state.h" +#include "otautil/paths.h" +#include "otautil/sysutil.h" +#include "recovery.h" +#include "recovery_ui/device.h" +#include "recovery_ui/stub_ui.h" +#include "recovery_ui/ui.h" +#include "recovery_utils/logging.h" +#include "recovery_utils/roots.h" + +static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; +static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; + +static RecoveryUI* ui = nullptr; + +static bool IsRoDebuggable() { + return android::base::GetBoolProperty("ro.debuggable", false); +} + +static bool IsDeviceUnlocked() { + return "orange" == android::base::GetProperty("ro.boot.verifiedbootstate", ""); +} + +static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, + const char* /* tag */, const char* /* file */, unsigned int /* line */, + const char* message) { + static constexpr char log_characters[] = "VDIWEF"; + if (severity >= android::base::ERROR && ui != nullptr) { + ui->Print("E:%s\n", message); + } else { + fprintf(stdout, "%c:%s\n", log_characters[severity], message); + } +} + +// Parses the command line argument from various sources; and reads the stage field from BCB. +// 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) +static std::vector get_args(const int argc, char** const argv, std::string* stage) { + CHECK_GT(argc, 0); + + bootloader_message boot = {}; + std::string err; + if (!read_bootloader_message(&boot, &err)) { + LOG(ERROR) << err; + // If fails, leave a zeroed bootloader_message. + boot = {}; + } + if (stage) { + *stage = std::string(boot.stage); + } + + std::string boot_command; + if (boot.command[0] != 0) { + if (memchr(boot.command, '\0', sizeof(boot.command))) { + boot_command = std::string(boot.command); + } else { + boot_command = std::string(boot.command, sizeof(boot.command)); + } + LOG(INFO) << "Boot command: " << boot_command; + } + + if (boot.status[0] != 0) { + std::string boot_status = std::string(boot.status, sizeof(boot.status)); + LOG(INFO) << "Boot status: " << boot_status; + } + + std::vector args(argv, argv + argc); + + // --- if arguments weren't supplied, look in the bootloader control block + if (args.size() == 1) { + boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination + std::string boot_recovery(boot.recovery); + std::vector tokens = android::base::Split(boot_recovery, "\n"); + if (!tokens.empty() && tokens[0] == "recovery") { + for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from boot message"; + } else if (boot.recovery[0] != 0) { + LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\""; + } + } + + // --- if that doesn't work, try the command file (if we have /cache). + if (args.size() == 1 && HasCache()) { + std::string content; + if (ensure_path_mounted(COMMAND_FILE) == 0 && + android::base::ReadFileToString(COMMAND_FILE, &content)) { + std::vector tokens = android::base::Split(content, "\n"); + // All the arguments in COMMAND_FILE are needed (unlike the BCB message, + // COMMAND_FILE doesn't use filename as the first argument). + for (auto it = tokens.begin(); it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE; + } + } + + // Write the arguments (excluding the filename in args[0]) back into the + // bootloader control block. So the device will always boot into recovery to + // finish the pending work, until FinishRecovery() is called. + std::vector options(args.cbegin() + 1, args.cend()); + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << "Failed to set BCB message: " << err; + } + + // Finally, if no arguments were specified, check whether we should boot + // into fastboot or rescue mode. + if (args.size() == 1 && boot_command == "boot-fastboot") { + args.emplace_back("--fastboot"); + } else if (args.size() == 1 && boot_command == "boot-rescue") { + args.emplace_back("--rescue"); + } + + return args; +} + +static std::string load_locale_from_cache() { + if (ensure_path_mounted(LOCALE_FILE) != 0) { + LOG(ERROR) << "Can't mount " << LOCALE_FILE; + return ""; + } + + std::string content; + if (!android::base::ReadFileToString(LOCALE_FILE, &content)) { + PLOG(ERROR) << "Can't read " << LOCALE_FILE; + return ""; + } + + return android::base::Trim(content); +} + +// Sets the usb config to 'state'. +static bool SetUsbConfig(const std::string& state) { + android::base::SetProperty("sys.usb.config", state); + return android::base::WaitForProperty("sys.usb.state", state); +} + +static void ListenRecoverySocket(RecoveryUI* ui, std::atomic& action) { + android::base::unique_fd sock_fd(android_get_control_socket("recovery")); + if (sock_fd < 0) { + PLOG(ERROR) << "Failed to open recovery socket"; + return; + } + listen(sock_fd, 4); + + while (true) { + android::base::unique_fd connection_fd; + connection_fd.reset(accept(sock_fd, nullptr, nullptr)); + if (connection_fd < 0) { + PLOG(ERROR) << "Failed to accept socket connection"; + continue; + } + char msg; + constexpr char kSwitchToFastboot = 'f'; + constexpr char kSwitchToRecovery = 'r'; + ssize_t ret = TEMP_FAILURE_RETRY(read(connection_fd, &msg, sizeof(msg))); + if (ret != sizeof(msg)) { + PLOG(ERROR) << "Couldn't read from socket"; + continue; + } + switch (msg) { + case kSwitchToRecovery: + action = Device::BuiltinAction::ENTER_RECOVERY; + break; + case kSwitchToFastboot: + action = Device::BuiltinAction::ENTER_FASTBOOT; + break; + default: + LOG(ERROR) << "Unrecognized char from socket " << msg; + continue; + } + ui->InterruptKey(); + } +} + +static void redirect_stdio(const char* filename) { + android::base::unique_fd pipe_read, pipe_write; + // Create a pipe that allows parent process sending logs over. + if (!android::base::Pipe(&pipe_read, &pipe_write)) { + PLOG(ERROR) << "Failed to create pipe for redirecting stdio"; + + // Fall back to traditional logging mode without timestamps. If these fail, there's not really + // anywhere to complain... + freopen(filename, "a", stdout); + setbuf(stdout, nullptr); + freopen(filename, "a", stderr); + setbuf(stderr, nullptr); + + return; + } + + pid_t pid = fork(); + if (pid == -1) { + PLOG(ERROR) << "Failed to fork for redirecting stdio"; + + // Fall back to traditional logging mode without timestamps. If these fail, there's not really + // anywhere to complain... + freopen(filename, "a", stdout); + setbuf(stdout, nullptr); + freopen(filename, "a", stderr); + setbuf(stderr, nullptr); + + return; + } + + if (pid == 0) { + // Child process reads the incoming logs and doesn't write to the pipe. + pipe_write.reset(); + + auto start = std::chrono::steady_clock::now(); + + // Child logger to actually write to the log file. + FILE* log_fp = fopen(filename, "ae"); + if (log_fp == nullptr) { + PLOG(ERROR) << "fopen \"" << filename << "\" failed"; + _exit(EXIT_FAILURE); + } + + FILE* pipe_fp = android::base::Fdopen(std::move(pipe_read), "r"); + if (pipe_fp == nullptr) { + PLOG(ERROR) << "fdopen failed"; + check_and_fclose(log_fp, filename); + _exit(EXIT_FAILURE); + } + + char* line = nullptr; + size_t len = 0; + while (getline(&line, &len, pipe_fp) != -1) { + auto now = std::chrono::steady_clock::now(); + double duration = + std::chrono::duration_cast>(now - start).count(); + if (line[0] == '\n') { + fprintf(log_fp, "[%12.6lf]\n", duration); + } else { + fprintf(log_fp, "[%12.6lf] %s", duration, line); + } + fflush(log_fp); + } + + PLOG(ERROR) << "getline failed"; + + fclose(pipe_fp); + free(line); + check_and_fclose(log_fp, filename); + _exit(EXIT_FAILURE); + } else { + // Redirect stdout/stderr to the logger process. Close the unused read end. + pipe_read.reset(); + + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + if (dup2(pipe_write.get(), STDOUT_FILENO) == -1) { + PLOG(ERROR) << "dup2 stdout failed"; + } + if (dup2(pipe_write.get(), STDERR_FILENO) == -1) { + PLOG(ERROR) << "dup2 stderr failed"; + } + } +} + +int main(int argc, char** argv) { + // We don't have logcat yet under recovery; so we'll print error on screen and log to stdout + // (which is redirected to recovery.log) as we used to do. + android::base::InitLogging(argv, &UiLogger); + + // Take last pmsg contents and rewrite it to the current pmsg session. + static constexpr const char filter[] = "recovery/"; + // Do we need to rotate? + bool do_rotate = false; + + __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &do_rotate); + // Take action to refresh pmsg contents + __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate); + + time_t start = time(nullptr); + + // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger + // instances with different timestamps. + redirect_stdio(Paths::Get().temporary_log_file().c_str()); + + load_volume_table(); + + std::string stage; + std::vector args = get_args(argc, argv, &stage); + auto args_to_parse = StringVectorToNullTerminatedArray(args); + + static constexpr struct option OPTIONS[] = { + { "fastboot", no_argument, nullptr, 0 }, + { "locale", required_argument, nullptr, 0 }, + { "reason", required_argument, nullptr, 0 }, + { "show_text", no_argument, nullptr, 't' }, + { nullptr, 0, nullptr, 0 }, + }; + + bool show_text = false; + bool fastboot = false; + std::string locale; + std::string reason; + + // The code here is only interested in the options that signal the intent to start fastbootd or + // recovery. Unrecognized options are likely meant for recovery, which will be processed later in + // start_recovery(). Suppress the warnings for such -- even if some flags were indeed invalid, the + // code in start_recovery() will capture and report them. + opterr = 0; + + int arg; + int option_index; + while ((arg = getopt_long(args_to_parse.size() - 1, args_to_parse.data(), "", OPTIONS, + &option_index)) != -1) { + switch (arg) { + case 't': + show_text = true; + break; + case 0: { + std::string option = OPTIONS[option_index].name; + if (option == "locale") { + locale = optarg; + } else if (option == "reason") { + reason = optarg; + } else if (option == "fastboot" && + android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { + fastboot = true; + } + break; + } + } + } + optind = 1; + opterr = 1; + + if (locale.empty()) { + if (HasCache()) { + locale = load_locale_from_cache(); + } + + if (locale.empty()) { + locale = DEFAULT_LOCALE; + } + } + + static constexpr const char* kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so"; + // Intentionally not calling dlclose(3) to avoid potential gotchas (e.g. `make_device` may have + // handed out pointers to code or static [or thread-local] data and doesn't collect them all back + // in on dlclose). + void* librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW); + + using MakeDeviceType = decltype(&make_device); + MakeDeviceType make_device_func = nullptr; + if (librecovery_ui_ext == nullptr) { + printf("Failed to dlopen %s: %s\n", kDefaultLibRecoveryUIExt, dlerror()); + } else { + reinterpret_cast(make_device_func) = dlsym(librecovery_ui_ext, "make_device"); + if (make_device_func == nullptr) { + printf("Failed to dlsym make_device: %s\n", dlerror()); + } + } + + Device* device; + if (make_device_func == nullptr) { + printf("Falling back to the default make_device() instead\n"); + device = make_device(); + } else { + printf("Loading make_device from %s\n", kDefaultLibRecoveryUIExt); + device = (*make_device_func)(); + } + + if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { + printf("Quiescent recovery mode.\n"); + device->ResetUI(new StubRecoveryUI()); + } else { + if (!device->GetUI()->Init(locale)) { + printf("Failed to initialize UI; using stub UI instead.\n"); + device->ResetUI(new StubRecoveryUI()); + } + } + + BootState boot_state(reason, stage); // recovery_main owns the state of boot. + device->SetBootState(&boot_state); + ui = device->GetUI(); + + if (!HasCache()) { + device->RemoveMenuItemForAction(Device::WIPE_CACHE); + } + + if (!android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { + device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT); + } + + if (!IsRoDebuggable()) { + device->RemoveMenuItemForAction(Device::ENTER_RESCUE); + } + + ui->SetBackground(RecoveryUI::NONE); + if (show_text) ui->ShowText(true); + + LOG(INFO) << "Starting recovery (pid " << getpid() << ") on " << ctime(&start); + LOG(INFO) << "locale is [" << locale << "]"; + + auto sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + if (!sehandle) { + ui->Print("Warning: No file_contexts\n"); + } + + SetLoggingSehandle(sehandle); + + std::atomic action; + std::thread listener_thread(ListenRecoverySocket, ui, std::ref(action)); + listener_thread.detach(); + + while (true) { + // We start adbd in recovery for the device with userdebug build or a unlocked bootloader. + std::string usb_config = + fastboot ? "fastboot" : IsRoDebuggable() || IsDeviceUnlocked() ? "adb" : "none"; + std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); + if (fastboot) { + device->PreFastboot(); + } else { + device->PreRecovery(); + } + if (usb_config != usb_state) { + if (!SetUsbConfig("none")) { + LOG(ERROR) << "Failed to clear USB config"; + } + if (!SetUsbConfig(usb_config)) { + LOG(ERROR) << "Failed to set USB config to " << usb_config; + } + } + + ui->SetEnableFastbootdLogo(fastboot); + + auto ret = fastboot ? StartFastboot(device, args) : start_recovery(device, args); + + if (ret == Device::KEY_INTERRUPTED) { + ret = action.exchange(ret); + if (ret == Device::NO_ACTION) { + continue; + } + } + switch (ret) { + case Device::SHUTDOWN: + ui->Print("Shutting down...\n"); + Shutdown("userrequested,recovery"); + break; + + case Device::SHUTDOWN_FROM_FASTBOOT: + ui->Print("Shutting down...\n"); + Shutdown("userrequested,fastboot"); + break; + + case Device::REBOOT_BOOTLOADER: + ui->Print("Rebooting to bootloader...\n"); + Reboot("bootloader"); + break; + + case Device::REBOOT_FASTBOOT: + ui->Print("Rebooting to recovery/fastboot...\n"); + Reboot("fastboot"); + break; + + case Device::REBOOT_RECOVERY: + ui->Print("Rebooting to recovery...\n"); + Reboot("recovery"); + break; + + case Device::REBOOT_RESCUE: { + // Not using `Reboot("rescue")`, as it requires matching support in kernel and/or + // bootloader. + bootloader_message boot = {}; + strlcpy(boot.command, "boot-rescue", sizeof(boot.command)); + std::string err; + if (!write_bootloader_message(boot, &err)) { + LOG(ERROR) << "Failed to write bootloader message: " << err; + // Stay under recovery on failure. + continue; + } + ui->Print("Rebooting to recovery/rescue...\n"); + Reboot("recovery"); + break; + } + + case Device::ENTER_FASTBOOT: + if (android::fs_mgr::LogicalPartitionsMapped()) { + ui->Print("Partitions may be mounted - rebooting to enter fastboot."); + Reboot("fastboot"); + } else { + LOG(INFO) << "Entering fastboot"; + fastboot = true; + } + break; + + case Device::ENTER_RECOVERY: + LOG(INFO) << "Entering recovery"; + fastboot = false; + break; + + case Device::REBOOT: + ui->Print("Rebooting...\n"); + Reboot("userrequested,recovery"); + break; + + case Device::REBOOT_FROM_FASTBOOT: + ui->Print("Rebooting...\n"); + Reboot("userrequested,fastboot"); + break; + + default: + ui->Print("Rebooting...\n"); + Reboot("unknown" + std::to_string(ret)); + break; + } + } + + // Should be unreachable. + return EXIT_SUCCESS; +} diff --git a/recovery_ui/Android.bp b/recovery_ui/Android.bp new file mode 100644 index 0000000000..9dfee5fd5b --- /dev/null +++ b/recovery_ui/Android.bp @@ -0,0 +1,113 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_library { + name: "librecovery_ui", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "device.cpp", + "ethernet_ui.cpp", + "screen_ui.cpp", + "stub_ui.cpp", + "ui.cpp", + "vr_ui.cpp", + "wear_ui.cpp", + ], + + export_include_dirs: ["include"], + + static_libs: [ + "libminui", + "libotautil", + ], + + shared_libs: [ + "libbase", + "libpng", + "libz", + ], +} + +// Generic device that uses ScreenRecoveryUI. +cc_library_static { + name: "librecovery_ui_default", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "default_device.cpp", + ], + + export_include_dirs: ["include"], +} + +// The default wear device that uses WearRecoveryUI. +cc_library_static { + name: "librecovery_ui_wear", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "wear_device.cpp", + ], + + export_include_dirs: ["include"], +} + +// The default VR device that uses VrRecoveryUI. +cc_library_static { + name: "librecovery_ui_vr", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "vr_device.cpp", + ], + + export_include_dirs: ["include"], +} + +// The default device that uses EthernetRecoveryUI. +cc_library_static { + name: "librecovery_ui_ethernet", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "ethernet_device.cpp", + ], + + shared_libs: [ + "libbase", + ], + + export_include_dirs: ["include"], +} diff --git a/default_device.cpp b/recovery_ui/default_device.cpp similarity index 91% rename from default_device.cpp rename to recovery_ui/default_device.cpp index a9718668dd..4db461af67 100644 --- a/default_device.cpp +++ b/recovery_ui/default_device.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "device.h" -#include "screen_ui.h" +#include "recovery_ui/device.h" +#include "recovery_ui/screen_ui.h" Device* make_device() { return new Device(new ScreenRecoveryUI); diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp new file mode 100644 index 0000000000..d46df92d33 --- /dev/null +++ b/recovery_ui/device.cpp @@ -0,0 +1,110 @@ +/* + * 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 "recovery_ui/device.h" + +#include +#include +#include +#include + +#include + +#include "otautil/boot_state.h" +#include "recovery_ui/ui.h" + +static std::vector> g_menu_actions{ + { "Reboot system now", Device::REBOOT }, + { "Reboot to bootloader", Device::REBOOT_BOOTLOADER }, + { "Enter fastboot", Device::ENTER_FASTBOOT }, + { "Apply update from ADB", Device::APPLY_ADB_SIDELOAD }, + { "Apply update from SD card", Device::APPLY_SDCARD }, + { "Wipe data/factory reset", Device::WIPE_DATA }, + { "Wipe cache partition", Device::WIPE_CACHE }, + { "Mount /system", Device::MOUNT_SYSTEM }, + { "View recovery logs", Device::VIEW_RECOVERY_LOGS }, + { "Run graphics test", Device::RUN_GRAPHICS_TEST }, + { "Run locale test", Device::RUN_LOCALE_TEST }, + { "Enter rescue", Device::ENTER_RESCUE }, + { "Power off", Device::SHUTDOWN }, +}; + +static std::vector g_menu_items; + +static void PopulateMenuItems() { + g_menu_items.clear(); + std::transform(g_menu_actions.cbegin(), g_menu_actions.cend(), std::back_inserter(g_menu_items), + [](const auto& entry) { return entry.first; }); +} + +Device::Device(RecoveryUI* ui) : ui_(ui) { + PopulateMenuItems(); +} + +void Device::RemoveMenuItemForAction(Device::BuiltinAction action) { + g_menu_actions.erase( + std::remove_if(g_menu_actions.begin(), g_menu_actions.end(), + [action](const auto& entry) { return entry.second == action; })); + CHECK(!g_menu_actions.empty()); + + // Re-populate the menu items. + PopulateMenuItems(); +} + +const std::vector& Device::GetMenuItems() { + return g_menu_items; +} + +Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) { + return g_menu_actions[menu_position].second; +} + +int Device::HandleMenuKey(int key, bool visible) { + if (!visible) { + return kNoAction; + } + + switch (key) { + case KEY_DOWN: + case KEY_VOLUMEDOWN: + return kHighlightDown; + + case KEY_UP: + case KEY_VOLUMEUP: + return kHighlightUp; + + case KEY_ENTER: + case KEY_POWER: + return kInvokeItem; + + default: + // If you have all of the above buttons, any other buttons + // are ignored. Otherwise, any button cycles the highlight. + return ui_->HasThreeButtons() ? kNoAction : kHighlightDown; + } +} + +void Device::SetBootState(const BootState* state) { + boot_state_ = state; +} + +std::optional Device::GetReason() const { + return boot_state_ ? std::make_optional(boot_state_->reason()) : std::nullopt; +} + +std::optional Device::GetStage() const { + return boot_state_ ? std::make_optional(boot_state_->stage()) : std::nullopt; +} diff --git a/recovery_ui/ethernet_device.cpp b/recovery_ui/ethernet_device.cpp new file mode 100644 index 0000000000..39ec65dc45 --- /dev/null +++ b/recovery_ui/ethernet_device.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "recovery_ui/device.h" +#include "recovery_ui/ethernet_ui.h" + +class EthernetDevice : public Device { + public: + explicit EthernetDevice(EthernetRecoveryUI* ui); + + void PreRecovery() override; + void PreFastboot() override; + + private: + int SetInterfaceFlags(const unsigned set, const unsigned clr); + void SetTitleIPv6LinkLocalAddress(const bool interface_up); + + android::base::unique_fd ctl_sock_; + static const std::string interface; +}; + +const std::string EthernetDevice::interface = "eth0"; + +EthernetDevice::EthernetDevice(EthernetRecoveryUI* ui) + : Device(ui), ctl_sock_(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) { + if (ctl_sock_ < 0) { + PLOG(ERROR) << "Failed to open socket"; + } +} + +void EthernetDevice::PreRecovery() { + SetInterfaceFlags(0, IFF_UP); + SetTitleIPv6LinkLocalAddress(false); +} + +void EthernetDevice::PreFastboot() { + android::base::SetProperty("fastbootd.protocol", "tcp"); + + if (SetInterfaceFlags(IFF_UP, 0) < 0) { + LOG(ERROR) << "Failed to bring up interface"; + return; + } + + SetTitleIPv6LinkLocalAddress(true); +} + +int EthernetDevice::SetInterfaceFlags(const unsigned set, const unsigned clr) { + struct ifreq ifr; + + if (ctl_sock_ < 0) { + return -1; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + + if (ioctl(ctl_sock_, SIOCGIFFLAGS, &ifr) < 0) { + PLOG(ERROR) << "Failed to get interface active flags"; + return -1; + } + ifr.ifr_flags = (ifr.ifr_flags & (~clr)) | set; + + if (ioctl(ctl_sock_, SIOCSIFFLAGS, &ifr) < 0) { + PLOG(ERROR) << "Failed to set interface active flags"; + return -1; + } + + return 0; +} + +void EthernetDevice::SetTitleIPv6LinkLocalAddress(const bool interface_up) { + auto recovery_ui = reinterpret_cast(GetUI()); + if (!interface_up) { + recovery_ui->SetIPv6LinkLocalAddress(); + return; + } + + struct ifaddrs* ifaddr; + if (getifaddrs(&ifaddr) == -1) { + PLOG(ERROR) << "Failed to get interface addresses"; + recovery_ui->SetIPv6LinkLocalAddress(); + return; + } + + std::unique_ptr guard{ ifaddr, freeifaddrs }; + for (struct ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr->sa_family != AF_INET6 || interface != ifa->ifa_name) { + continue; + } + + auto current_addr = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&(current_addr->sin6_addr))) { + continue; + } + + char addrstr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, reinterpret_cast(¤t_addr->sin6_addr), addrstr, + INET6_ADDRSTRLEN); + LOG(INFO) << "Our IPv6 link-local address is " << addrstr; + recovery_ui->SetIPv6LinkLocalAddress(addrstr); + return; + } + + recovery_ui->SetIPv6LinkLocalAddress(); +} + +// ----------------------------------------------------------------------------------------- +Device* make_device() { + return new EthernetDevice(new EthernetRecoveryUI); +} diff --git a/otafault/test.cpp b/recovery_ui/ethernet_ui.cpp similarity index 52% rename from otafault/test.cpp rename to recovery_ui/ethernet_ui.cpp index 63e2445af6..535d407f90 100644 --- a/otafault/test.cpp +++ b/recovery_ui/ethernet_ui.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,19 @@ * limitations under the License. */ -#include -#include -#include -#include -#include +#include "recovery_ui/ethernet_ui.h" -#include "otafault/ota_io.h" +#include -int main(int /* argc */, char** /* argv */) { - int fd = open("testdata/test.file", O_RDWR); - char buf[8]; - const char* out = "321"; - int readv = ota_read(fd, buf, 4); - printf("Read returned %d\n", readv); - int writev = ota_write(fd, out, 4); - printf("Write returned %d\n", writev); - close(fd); - return 0; +void EthernetRecoveryUI::SetTitle(const std::vector& lines) { + ScreenRecoveryUI::SetTitle(lines); + + // Append IP address, if any + if (!address_.empty()) { + title_lines_.push_back("IPv6 link-local address - " + address_); + } +} + +void EthernetRecoveryUI::SetIPv6LinkLocalAddress(const std::string& address) { + address_ = address; } diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h new file mode 100644 index 0000000000..76166f09d7 --- /dev/null +++ b/recovery_ui/include/recovery_ui/device.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _RECOVERY_DEVICE_H +#define _RECOVERY_DEVICE_H + +#include + +#include +#include +#include +#include + +// Forward declaration to avoid including "ui.h". +class RecoveryUI; + +class BootState; + +class Device { + public: + static constexpr const int kNoAction = -1; + static constexpr const int kHighlightUp = -2; + static constexpr const int kHighlightDown = -3; + static constexpr const int kInvokeItem = -4; + + // ENTER vs REBOOT: The latter will trigger a reboot that goes through bootloader, which allows + // using a new bootloader / recovery image if applicable. For example, REBOOT_RESCUE goes from + // rescue -> bootloader -> rescue, whereas ENTER_RESCUE switches from recovery -> rescue + // directly. + 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, + RUN_LOCALE_TEST = 12, + KEY_INTERRUPTED = 13, + ENTER_FASTBOOT = 14, + ENTER_RECOVERY = 15, + ENTER_RESCUE = 16, + REBOOT_FASTBOOT = 17, + REBOOT_RECOVERY = 18, + REBOOT_RESCUE = 19, + REBOOT_FROM_FASTBOOT = 20, + SHUTDOWN_FROM_FASTBOOT = 21, + }; + + explicit Device(RecoveryUI* ui); + virtual ~Device() {} + + // Returns a raw pointer to the RecoveryUI object. + virtual RecoveryUI* GetUI() { + return ui_.get(); + } + + // Resets the UI object to the given UI. Used to override the default UI in case initialization + // failed, or we want a different UI for some reason. The device object will take the ownership. + virtual void ResetUI(RecoveryUI* ui) { + ui_.reset(ui); + } + + // Called before recovery mode started up, to perform whatever device-specific recovery mode + // preparation as needed. + virtual void PreRecovery() {} + + // 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 before fastboot mode is started up, to perform whatever device-specific fastboot mode + // preparation as needed. + virtual void PreFastboot() {} + + // Called when fastboot starts up (after the UI has been obtained and initialized and after the + // arguments have been parsed, but before anything else). + virtual void StartFastboot() {} + + // 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, such as + // failed to install an OTA package, 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}: negative value) + // - invoke the highlighted item (kInvokeItem: negative value) + // - do nothing (kNoAction: negative value) + // - invoke a specific action (a menu position: non-negative value) + virtual int HandleMenuKey(int key, bool visible); + + // Returns the list of menu items (a vector of strings). The menu_position passed to + // InvokeMenuItem() will correspond to the indexes into this array. + virtual const std::vector& GetMenuItems(); + + // Performs a recovery action selected from the menu. 'menu_position' will be the index of the + // selected menu item, or a non-negative value returned from HandleMenuKey(). The menu will be + // hidden when this is called; implementations can call GetUI()->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(size_t menu_position); + + // Removes the menu item for the given action. This allows tailoring the menu based on the + // runtime info, such as the availability of /cache or /sdcard. + virtual void RemoveMenuItemForAction(Device::BuiltinAction action); + + // 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; + } + + virtual bool PostWipeData() { + return true; + } + + void SetBootState(const BootState* state); + // The getters for reason and stage may return std::nullopt until StartRecovery() is called. It's + // the caller's responsibility to perform the check and handle the exception. + std::optional GetReason() const; + std::optional GetStage() const; + + private: + // The RecoveryUI object that should be used to display the user interface for this device. + std::unique_ptr ui_; + const BootState* boot_state_{ nullptr }; +}; + +// Disable name mangling, as this function will be loaded via dlsym(3). +extern "C" { + +// 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/recovery_ui/include/recovery_ui/ethernet_ui.h b/recovery_ui/include/recovery_ui/ethernet_ui.h new file mode 100644 index 0000000000..f40c73f69c --- /dev/null +++ b/recovery_ui/include/recovery_ui/ethernet_ui.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_ETHERNET_UI_H +#define RECOVERY_ETHERNET_UI_H + +#include "screen_ui.h" + +class EthernetRecoveryUI : public ScreenRecoveryUI { + public: + EthernetRecoveryUI() {} + void SetTitle(const std::vector& lines) override; + + // For EthernetDevice + void SetIPv6LinkLocalAddress(const std::string& address = ""); + + private: + std::string address_; +}; + +#endif // RECOVERY_ETHERNET_UI_H diff --git a/recovery_ui/include/recovery_ui/screen_ui.h b/recovery_ui/include/recovery_ui/screen_ui.h new file mode 100644 index 0000000000..92b3c2546b --- /dev/null +++ b/recovery_ui/include/recovery_ui/screen_ui.h @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_SCREEN_UI_H +#define RECOVERY_SCREEN_UI_H + +#include + +#include +#include +#include +#include +#include +#include + +#include "ui.h" + +// From minui/minui.h. +class GRSurface; + +enum class UIElement { + HEADER, + MENU, + MENU_SEL_BG, + MENU_SEL_BG_ACTIVE, + MENU_SEL_FG, + LOG, + TEXT_FILL, + INFO +}; + +// Interface to draw the UI elements on the screen. +class DrawInterface { + public: + virtual ~DrawInterface() = default; + + // Sets the color to the predefined value for |element|. + virtual void SetColor(UIElement element) const = 0; + + // Draws a highlight bar at (x, y) - (x + width, y + height). + virtual void DrawHighlightBar(int x, int y, int width, int height) const = 0; + + // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis. + virtual int DrawHorizontalRule(int y) const = 0; + + // Draws a line of text. Returns the offset it should be moving along Y-axis. + virtual int DrawTextLine(int x, int y, const std::string& line, bool bold) const = 0; + + // Draws surface portion (sx, sy, w, h) at screen location (dx, dy). + virtual void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const = 0; + + // Draws rectangle at (x, y) - (x + w, y + h). + virtual void DrawFill(int x, int y, int w, int h) const = 0; + + // Draws given surface (surface->pixel_bytes = 1) as text at (x, y). + virtual void DrawTextIcon(int x, int y, const GRSurface* surface) const = 0; + + // Draws multiple text lines. Returns the offset it should be moving along Y-axis. + virtual int DrawTextLines(int x, int y, const std::vector& lines) const = 0; + + // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines. It + // keeps symmetrical margins of 'x' at each end of a line. Returns the offset it should be moving + // along Y-axis. + virtual int DrawWrappedTextLines(int x, int y, const std::vector& lines) const = 0; +}; + +// Interface for classes that maintain the menu selection and display. +class Menu { + public: + virtual ~Menu() = default; + // Returns the current menu selection. + size_t selection() const; + // Sets the current selection to |sel|. Handle the overflow cases depending on if the menu is + // scrollable. + virtual int Select(int sel) = 0; + // Displays the menu headers on the screen at offset x, y + virtual int DrawHeader(int x, int y) const = 0; + // Iterates over the menu items and displays each of them at offset x, y. + virtual int DrawItems(int x, int y, int screen_width, bool long_press) const = 0; + + protected: + Menu(size_t initial_selection, const DrawInterface& draw_func); + // Current menu selection. + size_t selection_; + // Reference to the class that implements all the draw functions. + const DrawInterface& draw_funcs_; +}; + +// This class uses strings as the menu header and items. +class TextMenu : public Menu { + public: + // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial + // selection to |initial_selection|. + TextMenu(bool scrollable, size_t max_items, size_t max_length, + const std::vector& headers, const std::vector& items, + size_t initial_selection, int char_height, const DrawInterface& draw_funcs); + + int Select(int sel) override; + int DrawHeader(int x, int y) const override; + int DrawItems(int x, int y, int screen_width, bool long_press) const override; + + bool scrollable() const { + return scrollable_; + } + + // Returns count of menu items. + size_t ItemsCount() const; + + // Returns the index of the first menu item. + size_t MenuStart() const; + + // Returns the index of the last menu item + 1. + size_t MenuEnd() const; + + // Menu example: + // info: Android Recovery + // .... + // help messages: Swipe up/down to move + // Swipe left/right to select + // empty line (horizontal rule): + // menu headers: Select file to view + // menu items: /cache/recovery/last_log + // /cache/recovery/last_log.1 + // /cache/recovery/last_log.2 + // ... + const std::vector& text_headers() const; + std::string TextItem(size_t index) const; + + // Checks if the menu items fit vertically on the screen. Returns true and set the + // |cur_selection_str| if the items exceed the screen limit. + bool ItemsOverflow(std::string* cur_selection_str) const; + + private: + // The menu is scrollable to display more items. Used on wear devices who have smaller screens. + const bool scrollable_; + // The max number of menu items to fit vertically on a screen. + const size_t max_display_items_; + // The length of each item to fit horizontally on a screen. + const size_t max_item_length_; + // The menu headers. + std::vector text_headers_; + // The actual menu items trimmed to fit the given properties. + std::vector text_items_; + // The first item to display on the screen. + size_t menu_start_; + + // Height in pixels of each character. + int char_height_; +}; + +// This class uses GRSurface's as the menu header and items. +class GraphicMenu : public Menu { + public: + // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial + // selection to |initial_selection|. |headers| and |items| will be made local copies. + GraphicMenu(const GRSurface* graphic_headers, const std::vector& graphic_items, + size_t initial_selection, const DrawInterface& draw_funcs); + + int Select(int sel) override; + int DrawHeader(int x, int y) const override; + int DrawItems(int x, int y, int screen_width, bool long_press) const override; + + // Checks if all the header and items are valid GRSurface's; and that they can fit in the area + // defined by |max_width| and |max_height|. + static bool Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, + const std::vector& graphic_items); + + // Returns true if |surface| fits on the screen with a vertical offset |y|. + static bool ValidateGraphicSurface(size_t max_width, size_t max_height, int y, + const GRSurface* surface); + + private: + // Menu headers and items in graphic icons. These are the copies owned by the class instance. + std::unique_ptr graphic_headers_; + std::vector> graphic_items_; +}; + +// Implementation of RecoveryUI appropriate for devices with a screen +// (shows an icon + a progress bar, text logging, menu, etc.) +class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { + public: + ScreenRecoveryUI(); + explicit ScreenRecoveryUI(bool scrollable_menu); + ~ScreenRecoveryUI() override; + + bool Init(const std::string& locale) override; + std::string GetLocale() const override; + + // overall recovery state ("background image") + void SetBackground(Icon icon) override; + void SetSystemUpdateText(bool security_update) override; + + // progress indicator + void SetProgressType(ProgressType type) override; + void ShowProgress(float portion, float seconds) override; + void SetProgress(float fraction) override; + + void SetStage(int current, int max) override; + + // text log + void ShowText(bool visible) override; + bool IsTextVisible() override; + bool WasTextEverVisible() override; + + // printing messages + void Print(const char* fmt, ...) override __printflike(2, 3); + void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); + void ShowFile(const std::string& filename) override; + + // menu display + size_t ShowMenu(const std::vector& headers, const std::vector& items, + size_t initial_selection, bool menu_only, + const std::function& key_handler) override; + void SetTitle(const std::vector& lines) override; + + void KeyLongPress(int) override; + + void Redraw(); + + // Checks the background text image, for debugging purpose. It iterates the locales embedded in + // the on-device resource files and shows the localized text, for manual inspection. + void CheckBackgroundTextImages(); + + // Displays the localized wipe data menu. + size_t ShowPromptWipeDataMenu(const std::vector& backup_headers, + const std::vector& backup_items, + const std::function& key_handler) override; + + // Displays the localized wipe data confirmation menu. + size_t ShowPromptWipeDataConfirmationMenu( + const std::vector& backup_headers, const std::vector& backup_items, + const std::function& key_handler) override; + + protected: + static constexpr int kMenuIndent = 4; + + // The margin that we don't want to use for showing texts (e.g. round screen, or screen with + // rounded corners). + const int margin_width_; + const int margin_height_; + + // Number of frames per sec (default: 30) for both parts of the animation. + const int animation_fps_; + + // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. + const float density_; + + virtual bool InitTextParams(); + + virtual bool LoadWipeDataMenuText(); + + // Creates a GraphicMenu with |graphic_header| and |graphic_items|. If the GraphicMenu isn't + // valid or it doesn't fit on the screen; falls back to create a TextMenu instead. If succeeds, + // returns a unique pointer to the created menu; otherwise returns nullptr. + virtual std::unique_ptr CreateMenu(const GRSurface* graphic_header, + const std::vector& graphic_items, + const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const; + + // Creates a TextMenu with |text_headers| and |text_items|; and sets the menu selection to + // |initial_selection|. + virtual std::unique_ptr CreateMenu(const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const; + + // Takes the ownership of |menu| and displays it. + virtual size_t ShowMenu(std::unique_ptr&& menu, bool menu_only, + const std::function& key_handler); + + // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item + // selected. + virtual int SelectMenu(int sel); + + // Returns the help message displayed on top of the menu. + virtual std::vector GetMenuHelpMessage() const; + + virtual void draw_background_locked(); + virtual void draw_foreground_locked(); + virtual void draw_screen_locked(); + virtual void draw_menu_and_text_buffer_locked(const std::vector& help_message); + virtual void update_screen_locked(); + virtual void update_progress_locked(); + + const GRSurface* GetCurrentFrame() const; + const GRSurface* GetCurrentText() const; + + void ProgressThreadLoop(); + + virtual void ShowFile(FILE*); + virtual void PrintV(const char*, bool, va_list); + void PutChar(char); + void ClearText(); + + void LoadAnimation(); + std::unique_ptr LoadBitmap(const std::string& filename); + std::unique_ptr LoadLocalizedBitmap(const std::string& filename); + + int PixelsFromDp(int dp) const; + virtual int GetAnimationBaseline() const; + virtual int GetProgressBaseline() const; + virtual int GetTextBaseline() const; + + // Returns pixel width of draw buffer. + virtual int ScreenWidth() const; + // Returns pixel height of draw buffer. + virtual int ScreenHeight() const; + + // Implementation of the draw functions in DrawInterface. + void SetColor(UIElement e) const override; + void DrawHighlightBar(int x, int y, int width, int height) const override; + int DrawHorizontalRule(int y) const override; + void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const override; + void DrawFill(int x, int y, int w, int h) const override; + void DrawTextIcon(int x, int y, const GRSurface* surface) const override; + int DrawTextLine(int x, int y, const std::string& line, bool bold) const override; + int DrawTextLines(int x, int y, const std::vector& lines) const override; + int DrawWrappedTextLines(int x, int y, const std::vector& lines) const override; + + // The layout to use. + int layout_; + + // The images that contain localized texts. + std::unique_ptr erasing_text_; + std::unique_ptr error_text_; + std::unique_ptr installing_text_; + std::unique_ptr no_command_text_; + + // Localized text images for the wipe data menu. + std::unique_ptr cancel_wipe_data_text_; + std::unique_ptr factory_data_reset_text_; + std::unique_ptr try_again_text_; + std::unique_ptr wipe_data_confirmation_text_; + std::unique_ptr wipe_data_menu_header_text_; + + std::unique_ptr fastbootd_logo_; + + // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by + // current_frame_, or error_icon_. + Icon current_icon_; + std::unique_ptr error_icon_; + std::vector> intro_frames_; + std::vector> loop_frames_; + size_t current_frame_; + bool intro_done_; + + // progress_bar and stage_marker images. + std::unique_ptr progress_bar_empty_; + std::unique_ptr progress_bar_fill_; + std::unique_ptr stage_marker_empty_; + std::unique_ptr stage_marker_fill_; + + ProgressType progressBarType; + + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; + + // true when both graphics pages are the same (except for the progress bar). + bool pagesIdentical; + + size_t text_cols_, text_rows_; + + // Log text overlay, displayed when a magic key is pressed. + char** text_; + size_t text_col_, text_row_; + + bool show_text; + bool show_text_ever; // has show_text ever been true? + + std::vector title_lines_; + + bool scrollable_menu_; + std::unique_ptr menu_; + + // An alternate text screen, swapped with 'text_' when we're viewing a log file. + char** file_viewer_text_; + + std::thread progress_thread_; + std::atomic progress_thread_stopped_{ false }; + + int stage, max_stage; + + int char_width_; + int char_height_; + + // The locale that's used to show the rendered texts. + std::string locale_; + bool rtl_locale_; + + std::mutex updateMutex; + + private: + void SetLocale(const std::string&); + + // Display the background texts for "erasing", "error", "no_command" and "installing" for the + // selected locale. + void SelectAndShowBackgroundText(const std::vector& locales_entries, size_t sel); +}; + +#endif // RECOVERY_UI_H diff --git a/stub_ui.h b/recovery_ui/include/recovery_ui/stub_ui.h similarity index 60% rename from stub_ui.h rename to recovery_ui/include/recovery_ui/stub_ui.h index 1f6b29acbf..511b1314a4 100644 --- a/stub_ui.h +++ b/recovery_ui/include/recovery_ui/stub_ui.h @@ -17,6 +17,10 @@ #ifndef RECOVERY_STUB_UI_H #define RECOVERY_STUB_UI_H +#include +#include +#include + #include "ui.h" // Stub implementation of RecoveryUI for devices without screen. @@ -24,6 +28,9 @@ class StubRecoveryUI : public RecoveryUI { public: StubRecoveryUI() = default; + std::string GetLocale() const override { + return ""; + } void SetBackground(Icon /* icon */) override {} void SetSystemUpdateText(bool /* security_update */) override {} @@ -51,15 +58,28 @@ class StubRecoveryUI : public RecoveryUI { va_end(ap); } void PrintOnScreenOnly(const char* /* fmt */, ...) override {} - void ShowFile(const char* /* filename */) override {} + void ShowFile(const std::string& /* filename */) override {} // menu display - void StartMenu(const char* const* /* headers */, const char* const* /* items */, - int /* initial_selection */) override {} - int SelectMenu(int sel) override { - return sel; + size_t ShowMenu(const std::vector& /* headers */, + const std::vector& /* items */, size_t /* initial_selection */, + bool /* menu_only */, + const std::function& /* key_handler */) override; + + size_t ShowPromptWipeDataMenu(const std::vector& /* backup_headers */, + const std::vector& /* backup_items */, + const std::function& /* key_handle */) override { + return 0; } - void EndMenu() override {} + + size_t ShowPromptWipeDataConfirmationMenu( + const std::vector& /* backup_headers */, + const std::vector& /* backup_items */, + const std::function& /* key_handle */) override { + return 0; + } + + void SetTitle(const std::vector& /* lines */) override {} }; #endif // RECOVERY_STUB_UI_H diff --git a/ui.h b/recovery_ui/include/recovery_ui/ui.h similarity index 57% rename from ui.h rename to recovery_ui/include/recovery_ui/ui.h index 4c54d6915d..08ec1d76ab 100644 --- a/ui.h +++ b/recovery_ui/include/recovery_ui/ui.h @@ -17,11 +17,17 @@ #ifndef RECOVERY_UI_H #define RECOVERY_UI_H -#include -#include -#include +#include // KEY_MAX +#include +#include +#include +#include #include +#include +#include + +static constexpr const char* DEFAULT_LOCALE = "en-US"; // Abstract class for controlling the user interface during recovery. class RecoveryUI { @@ -31,30 +37,37 @@ class RecoveryUI { INSTALLING_UPDATE, ERASING, NO_COMMAND, - ERROR + ERROR, }; enum ProgressType { EMPTY, INDETERMINATE, - DETERMINATE + DETERMINATE, }; enum KeyAction { ENQUEUE, TOGGLE, REBOOT, - IGNORE + IGNORE, + }; + + enum class KeyError : int { + TIMED_OUT = -1, + INTERRUPTED = -2, }; RecoveryUI(); - virtual ~RecoveryUI() {} + virtual ~RecoveryUI(); - // Initializes the object; called before anything else. UI texts will be initialized according to - // the given locale. Returns true on success. + // Initializes the object; called before anything else. UI texts will be initialized according + // to the given locale. Returns true on success. virtual bool Init(const std::string& locale); + virtual std::string GetLocale() const = 0; + // Shows a stage indicator. Called immediately after Init(). virtual void SetStage(int current, int max) = 0; @@ -87,19 +100,25 @@ class RecoveryUI { virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; - virtual void ShowFile(const char* filename) = 0; + // Shows the contents of the given file. Caller ensures the patition that contains the file has + // been mounted. + virtual void ShowFile(const std::string& filename) = 0; // --- key handling --- - // Waits for a key and return it. May return -1 after timeout. + // Waits for a key and return it. May return TIMED_OUT after timeout and + // KeyError::INTERRUPTED on a key interrupt. virtual int WaitKey(); + // Wakes up the UI if it is waiting on key input, causing WaitKey to return KeyError::INTERRUPTED. + virtual void InterruptKey(); + virtual bool IsKeyPressed(int key); virtual bool IsLongPress(); // Returns true if you have the volume up/down and power trio typical of phones and tablets, false // otherwise. - virtual bool HasThreeButtons(); + virtual bool HasThreeButtons() const; // Returns true if it has a power key. virtual bool HasPowerKey() const; @@ -128,17 +147,50 @@ class RecoveryUI { // --- menu display --- - // Display some header text followed by a menu of items, which appears at the top of the screen - // (in place of any scrolling ui_print() output, if necessary). - virtual void StartMenu(const char* const* headers, const char* const* items, - int initial_selection) = 0; - - // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item - // selected. - virtual int SelectMenu(int sel) = 0; - - // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed. - virtual void EndMenu() = 0; + virtual void SetTitle(const std::vector& lines) = 0; + + // Displays a menu with the given 'headers' and 'items'. The supplied 'key_handler' callback, + // which is typically bound to Device::HandleMenuKey(), should return the expected action for the + // given key code and menu visibility (e.g. to move the cursor or to select an item). Caller sets + // 'menu_only' to true to ensure only a menu item gets selected and returned. Otherwise if + // 'menu_only' is false, ShowMenu() will forward any non-negative value returned from the + // key_handler, which may be beyond the range of menu items. This could be used to trigger a + // device-specific action, even without that being listed in the menu. Caller needs to handle + // such a case accordingly (e.g. by calling Device::InvokeMenuItem() to process the action). + // Returns a non-negative value (the chosen item number or device-specific action code), or + // static_cast(TIMED_OUT) if timed out waiting for input or + // static_cast(ERR_KEY_INTERTUPT) if interrupted, such as by InterruptKey(). + virtual size_t ShowMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection, + bool menu_only, const std::function& key_handler) = 0; + + // Displays the localized wipe data menu with pre-generated graphs. If there's an issue + // with the graphs, falls back to use the backup string headers and items instead. The initial + // selection is the 0th item in the menu, which is expected to reboot the device without a wipe. + virtual size_t ShowPromptWipeDataMenu(const std::vector& backup_headers, + const std::vector& backup_items, + const std::function& key_handler) = 0; + // Displays the localized wipe data confirmation menu with pre-generated images. Falls back to + // the text strings upon failures. The initial selection is the 0th item, which returns to the + // upper level menu. + virtual size_t ShowPromptWipeDataConfirmationMenu( + const std::vector& backup_headers, const std::vector& backup_items, + const std::function& key_handler) = 0; + + // Set whether or not the fastbootd logo is displayed. + void SetEnableFastbootdLogo(bool enable) { + fastbootd_logo_enabled_ = enable; + } + + // Resets the key interrupt status. + void ResetKeyInterruptStatus() { + key_interrupted_ = false; + } + + // Returns the key interrupt status. + bool IsKeyInterrupted() const { + return key_interrupted_; + } protected: void EnqueueKey(int key_code); @@ -154,49 +206,47 @@ class RecoveryUI { // Whether we should listen for touch inputs (default: false). bool touch_screen_allowed_; + bool fastbootd_logo_enabled_; + private: enum class ScreensaverState { DISABLED, NORMAL, DIMMED, - OFF - }; - - struct key_timer_t { - RecoveryUI* ui; - int key_code; - int count; + OFF, }; // The sensitivity when detecting a swipe. - const int kTouchLowThreshold; - const int kTouchHighThreshold; + const int touch_low_threshold_; + const int touch_high_threshold_; void OnKeyDetected(int key_code); void OnTouchDetected(int dx, int dy); int OnInputEvent(int fd, uint32_t epevents); void ProcessKey(int key_code, int updown); + void TimeKey(int key_code, int count); bool IsUsbConnected(); - static void* time_key_helper(void* cookie); - void time_key(int key_code, int count); - bool InitScreensaver(); + void SetScreensaverState(ScreensaverState state); // Key event input queue - pthread_mutex_t key_queue_mutex; - pthread_cond_t key_queue_cond; + std::mutex key_queue_mutex; + std::condition_variable key_queue_cond; + bool key_interrupted_; int key_queue[256], key_queue_len; - char key_pressed[KEY_MAX + 1]; // under key_queue_mutex - int key_last_down; // under key_queue_mutex - bool key_long_press; // under key_queue_mutex - int key_down_count; // under key_queue_mutex - bool enable_reboot; // under key_queue_mutex - int rel_sum; + // key press events + std::mutex key_press_mutex; + char key_pressed[KEY_MAX + 1]; + int key_last_down; + bool key_long_press; + int key_down_count; + bool enable_reboot; + + int rel_sum; int consecutive_power_keys; - int last_key; bool has_power_key; bool has_up_key; @@ -213,7 +263,8 @@ class RecoveryUI { bool touch_swiping_; bool is_bootreason_recovery_ui_; - pthread_t input_thread_; + std::thread input_thread_; + std::atomic input_thread_stopped_{ false }; ScreensaverState screensaver_state_; diff --git a/vr_ui.h b/recovery_ui/include/recovery_ui/vr_ui.h similarity index 78% rename from vr_ui.h rename to recovery_ui/include/recovery_ui/vr_ui.h index eeb4589121..2e8ac59215 100644 --- a/vr_ui.h +++ b/recovery_ui/include/recovery_ui/vr_ui.h @@ -17,6 +17,8 @@ #ifndef RECOVERY_VR_UI_H #define RECOVERY_VR_UI_H +#include + #include "screen_ui.h" class VrRecoveryUI : public ScreenRecoveryUI { @@ -26,17 +28,18 @@ class VrRecoveryUI : public ScreenRecoveryUI { protected: // Pixel offsets to move drawing functions to visible range. // Can vary per device depending on screen size and lens distortion. - const int kStereoOffset; + const int stereo_offset_; int ScreenWidth() const override; int ScreenHeight() const override; - void DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, int dy) const override; + void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const override; int DrawHorizontalRule(int y) const override; void DrawHighlightBar(int x, int y, int width, int height) const override; void DrawFill(int x, int y, int w, int h) const override; - void DrawTextIcon(int x, int y, GRSurface* surface) const override; - int DrawTextLine(int x, int y, const char* line, bool bold) const override; + void DrawTextIcon(int x, int y, const GRSurface* surface) const override; + int DrawTextLine(int x, int y, const std::string& line, bool bold) const override; }; #endif // RECOVERY_VR_UI_H diff --git a/wear_ui.h b/recovery_ui/include/recovery_ui/wear_ui.h similarity index 79% rename from wear_ui.h rename to recovery_ui/include/recovery_ui/wear_ui.h index 739b4cb1d8..429af69d23 100644 --- a/wear_ui.h +++ b/recovery_ui/include/recovery_ui/wear_ui.h @@ -17,6 +17,9 @@ #ifndef RECOVERY_WEAR_UI_H #define RECOVERY_WEAR_UI_H +#include +#include + #include "screen_ui.h" class WearRecoveryUI : public ScreenRecoveryUI { @@ -25,18 +28,17 @@ class WearRecoveryUI : public ScreenRecoveryUI { void SetStage(int current, int max) override; - // menu display - void StartMenu(const char* const* headers, const char* const* items, - int initial_selection) override; - int SelectMenu(int sel) override; - protected: // progress bar vertical position, it's centered horizontally - const int kProgressBarBaseline; + const int progress_bar_baseline_; // Unusable rows when displaying the recovery menu, including the lines for headers (Android // Recovery, build id and etc) and the bottom lines that may otherwise go out of the screen. - const int kMenuUnusableRows; + const int menu_unusable_rows_; + + std::unique_ptr CreateMenu(const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const override; int GetProgressBaseline() const override; @@ -45,8 +47,6 @@ class WearRecoveryUI : public ScreenRecoveryUI { private: void draw_background_locked() override; void draw_screen_locked() override; - - int menu_start, menu_end; }; #endif // RECOVERY_WEAR_UI_H diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp new file mode 100644 index 0000000000..087fc0e843 --- /dev/null +++ b/recovery_ui/screen_ui.cpp @@ -0,0 +1,1348 @@ +/* + * Copyright (C) 2011 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 "recovery_ui/screen_ui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "minui/minui.h" +#include "otautil/paths.h" +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +// Return the current time as a double (including fractions of a second). +static double now() { + struct timeval tv; + gettimeofday(&tv, nullptr); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +Menu::Menu(size_t initial_selection, const DrawInterface& draw_func) + : selection_(initial_selection), draw_funcs_(draw_func) {} + +size_t Menu::selection() const { + return selection_; +} + +TextMenu::TextMenu(bool scrollable, size_t max_items, size_t max_length, + const std::vector& headers, const std::vector& items, + size_t initial_selection, int char_height, const DrawInterface& draw_funcs) + : Menu(initial_selection, draw_funcs), + scrollable_(scrollable), + max_display_items_(max_items), + max_item_length_(max_length), + text_headers_(headers), + menu_start_(0), + char_height_(char_height) { + CHECK_LE(max_items, static_cast(std::numeric_limits::max())); + + // It's fine to have more entries than text_rows_ if scrollable menu is supported. + size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_); + for (size_t i = 0; i < items_count; ++i) { + text_items_.emplace_back(items[i].substr(0, max_item_length_)); + } + + CHECK(!text_items_.empty()); +} + +const std::vector& TextMenu::text_headers() const { + return text_headers_; +} + +std::string TextMenu::TextItem(size_t index) const { + CHECK_LT(index, text_items_.size()); + + return text_items_[index]; +} + +size_t TextMenu::MenuStart() const { + return menu_start_; +} + +size_t TextMenu::MenuEnd() const { + return std::min(ItemsCount(), menu_start_ + max_display_items_); +} + +size_t TextMenu::ItemsCount() const { + return text_items_.size(); +} + +bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const { + if (!scrollable_ || ItemsCount() <= max_display_items_) { + return false; + } + + *cur_selection_str = + android::base::StringPrintf("Current item: %zu/%zu", selection_ + 1, ItemsCount()); + return true; +} + +// TODO(xunchang) modify the function parameters to button up & down. +int TextMenu::Select(int sel) { + CHECK_LE(ItemsCount(), static_cast(std::numeric_limits::max())); + int count = ItemsCount(); + + // Wraps the selection at boundary if the menu is not scrollable. + if (!scrollable_) { + if (sel < 0) { + selection_ = count - 1; + } else if (sel >= count) { + selection_ = 0; + } else { + selection_ = sel; + } + + return selection_; + } + + if (sel < 0) { + selection_ = 0; + } else if (sel >= count) { + selection_ = count - 1; + } else { + if (static_cast(sel) < menu_start_) { + menu_start_--; + } else if (static_cast(sel) >= MenuEnd()) { + menu_start_++; + } + selection_ = sel; + } + + return selection_; +} + +int TextMenu::DrawHeader(int x, int y) const { + int offset = 0; + + draw_funcs_.SetColor(UIElement::HEADER); + if (!scrollable()) { + offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers()); + } else { + offset += draw_funcs_.DrawTextLines(x, y + offset, text_headers()); + // Show the current menu item number in relation to total number if items don't fit on the + // screen. + std::string cur_selection_str; + if (ItemsOverflow(&cur_selection_str)) { + offset += draw_funcs_.DrawTextLine(x, y + offset, cur_selection_str, true); + } + } + + return offset; +} + +int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { + int offset = 0; + + draw_funcs_.SetColor(UIElement::MENU); + // Do not draw the horizontal rule for wear devices. + if (!scrollable()) { + offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4; + } + for (size_t i = MenuStart(); i < MenuEnd(); ++i) { + bool bold = false; + if (i == selection()) { + // Draw the highlight bar. + draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG); + + int bar_height = char_height_ + 4; + draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height); + + // Bold white text for the selected item. + draw_funcs_.SetColor(UIElement::MENU_SEL_FG); + bold = true; + } + offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), bold); + + draw_funcs_.SetColor(UIElement::MENU); + } + offset += draw_funcs_.DrawHorizontalRule(y + offset); + + return offset; +} + +GraphicMenu::GraphicMenu(const GRSurface* graphic_headers, + const std::vector& graphic_items, + size_t initial_selection, const DrawInterface& draw_funcs) + : Menu(initial_selection, draw_funcs) { + graphic_headers_ = graphic_headers->Clone(); + graphic_items_.reserve(graphic_items.size()); + for (const auto& item : graphic_items) { + graphic_items_.emplace_back(item->Clone()); + } +} + +int GraphicMenu::Select(int sel) { + CHECK_LE(graphic_items_.size(), static_cast(std::numeric_limits::max())); + int count = graphic_items_.size(); + + // Wraps the selection at boundary if the menu is not scrollable. + if (sel < 0) { + selection_ = count - 1; + } else if (sel >= count) { + selection_ = 0; + } else { + selection_ = sel; + } + + return selection_; +} + +int GraphicMenu::DrawHeader(int x, int y) const { + draw_funcs_.SetColor(UIElement::HEADER); + draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get()); + return graphic_headers_->height; +} + +int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { + int offset = 0; + + draw_funcs_.SetColor(UIElement::MENU); + offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4; + + for (size_t i = 0; i < graphic_items_.size(); i++) { + auto& item = graphic_items_[i]; + if (i == selection_) { + draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG); + + int bar_height = item->height + 4; + draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height); + + // Bold white text for the selected item. + draw_funcs_.SetColor(UIElement::MENU_SEL_FG); + } + draw_funcs_.DrawTextIcon(x, y + offset, item.get()); + offset += item->height; + + draw_funcs_.SetColor(UIElement::MENU); + } + offset += draw_funcs_.DrawHorizontalRule(y + offset); + + return offset; +} + +bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, + const std::vector& graphic_items) { + int offset = 0; + if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) { + return false; + } + offset += graphic_headers->height; + + for (const auto& item : graphic_items) { + if (!ValidateGraphicSurface(max_width, max_height, offset, item)) { + return false; + } + offset += item->height; + } + + return true; +} + +bool GraphicMenu::ValidateGraphicSurface(size_t max_width, size_t max_height, int y, + const GRSurface* surface) { + if (!surface) { + fprintf(stderr, "Graphic surface can not be null\n"); + return false; + } + + if (surface->pixel_bytes != 1 || surface->width != surface->row_bytes) { + fprintf(stderr, "Invalid graphic surface, pixel bytes: %zu, width: %zu row_bytes: %zu\n", + surface->pixel_bytes, surface->width, surface->row_bytes); + return false; + } + + if (surface->width > max_width || surface->height > max_height - y) { + fprintf(stderr, + "Graphic surface doesn't fit into the screen. width: %zu, height: %zu, max_width: %zu," + " max_height: %zu, vertical offset: %d\n", + surface->width, surface->height, max_width, max_height, y); + return false; + } + + return true; +} + +ScreenRecoveryUI::ScreenRecoveryUI() : ScreenRecoveryUI(false) {} + +constexpr int kDefaultMarginHeight = 0; +constexpr int kDefaultMarginWidth = 0; +constexpr int kDefaultAnimationFps = 30; + +ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) + : margin_width_( + android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)), + margin_height_( + android::base::GetIntProperty("ro.recovery.ui.margin_height", kDefaultMarginHeight)), + animation_fps_( + android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)), + density_(static_cast(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), + current_icon_(NONE), + current_frame_(0), + intro_done_(false), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + pagesIdentical(false), + text_cols_(0), + text_rows_(0), + text_(nullptr), + text_col_(0), + text_row_(0), + show_text(false), + show_text_ever(false), + scrollable_menu_(scrollable_menu), + file_viewer_text_(nullptr), + stage(-1), + max_stage(-1), + locale_(""), + rtl_locale_(false) {} + +ScreenRecoveryUI::~ScreenRecoveryUI() { + progress_thread_stopped_ = true; + if (progress_thread_.joinable()) { + progress_thread_.join(); + } + // No-op if gr_init() (via Init()) was not called or had failed. + gr_exit(); +} + +const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { + if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) { + return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get(); + } + return error_icon_.get(); +} + +const GRSurface* ScreenRecoveryUI::GetCurrentText() const { + switch (current_icon_) { + case ERASING: + return erasing_text_.get(); + case ERROR: + return error_text_.get(); + case INSTALLING_UPDATE: + return installing_text_.get(); + case NO_COMMAND: + return no_command_text_.get(); + case NONE: + abort(); + } +} + +int ScreenRecoveryUI::PixelsFromDp(int dp) const { + return dp * density_; +} + +// Here's the intended layout: + +// | portrait large landscape large +// ---------+------------------------------------------------- +// gap | +// icon | (200dp) +// gap | 68dp 68dp 56dp 112dp +// text | (14sp) +// gap | 32dp 32dp 26dp 52dp +// progress | (2dp) +// gap | + +// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines +// work), so that's the more useful measurement for calling code. We use even top and bottom gaps. + +enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX }; +enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX }; +static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { + { 32, 68 }, // PORTRAIT + { 32, 68 }, // PORTRAIT_LARGE + { 26, 56 }, // LANDSCAPE + { 52, 112 }, // LANDSCAPE_LARGE +}; + +int ScreenRecoveryUI::GetAnimationBaseline() const { + return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - + gr_get_height(loop_frames_[0].get()); +} + +int ScreenRecoveryUI::GetTextBaseline() const { + return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - + gr_get_height(installing_text_.get()); +} + +int ScreenRecoveryUI::GetProgressBaseline() const { + int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) + + gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) + + gr_get_height(progress_bar_fill_.get()); + int bottom_gap = (ScreenHeight() - elements_sum) / 2; + return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get()); +} + +// Clear the screen and draw the currently selected background icon (if any). +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_background_locked() { + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_clear(); + if (current_icon_ != NONE) { + if (max_stage != -1) { + int stage_height = gr_get_height(stage_marker_empty_.get()); + int stage_width = gr_get_width(stage_marker_empty_.get()); + int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2; + int y = ScreenHeight() - stage_height - margin_height_; + for (int i = 0; i < max_stage; ++i) { + const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_; + DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y); + x += stage_width; + } + } + + const auto& text_surface = GetCurrentText(); + int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2; + int text_y = GetTextBaseline(); + gr_color(255, 255, 255, 255); + DrawTextIcon(text_x, text_y, text_surface); + } +} + +// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be +// called with updateMutex locked. +void ScreenRecoveryUI::draw_foreground_locked() { + if (current_icon_ != NONE) { + const auto& frame = GetCurrentFrame(); + int frame_width = gr_get_width(frame); + int frame_height = gr_get_height(frame); + int frame_x = (ScreenWidth() - frame_width) / 2; + int frame_y = GetAnimationBaseline(); + DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); + } + + if (progressBarType != EMPTY) { + int width = gr_get_width(progress_bar_empty_.get()); + int height = gr_get_height(progress_bar_empty_.get()); + + int progress_x = (ScreenWidth() - width) / 2; + int progress_y = GetProgressBaseline(); + + // Erase behind the progress bar (in case this was a progress-only update) + gr_color(0, 0, 0, 255); + DrawFill(progress_x, progress_y, width, height); + + if (progressBarType == DETERMINATE) { + float p = progressScopeStart + progress * progressScopeSize; + int pos = static_cast(p * width); + + if (rtl_locale_) { + // Fill the progress bar from right to left. + if (pos > 0) { + DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height, + progress_x + width - pos, progress_y); + } + if (pos < width - 1) { + DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y); + } + } else { + // Fill the progress bar from left to right. + if (pos > 0) { + DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y); + } + if (pos < width - 1) { + DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos, + progress_y); + } + } + } + } +} + +void ScreenRecoveryUI::SetColor(UIElement e) const { + switch (e) { + case UIElement::INFO: + gr_color(249, 194, 0, 255); + break; + case UIElement::HEADER: + gr_color(247, 0, 6, 255); + break; + case UIElement::MENU: + case UIElement::MENU_SEL_BG: + gr_color(0, 106, 157, 255); + break; + case UIElement::MENU_SEL_BG_ACTIVE: + gr_color(0, 156, 100, 255); + break; + case UIElement::MENU_SEL_FG: + gr_color(255, 255, 255, 255); + break; + case UIElement::LOG: + gr_color(196, 196, 196, 255); + break; + case UIElement::TEXT_FILL: + gr_color(0, 0, 0, 160); + break; + default: + gr_color(255, 255, 255, 255); + break; + } +} + +void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector& locales_entries, + size_t sel) { + SetLocale(locales_entries[sel]); + std::vector text_name = { "erasing_text", "error_text", "installing_text", + "installing_security_text", "no_command_text" }; + std::unordered_map> surfaces; + for (const auto& name : text_name) { + auto text_image = LoadLocalizedBitmap(name); + if (!text_image) { + Print("Failed to load %s\n", name.c_str()); + return; + } + surfaces.emplace(name, std::move(text_image)); + } + + std::lock_guard lg(updateMutex); + gr_color(0, 0, 0, 255); + gr_clear(); + + int text_y = margin_height_; + int text_x = margin_width_; + int line_spacing = gr_sys_font()->char_height; // Put some extra space between images. + // Write the header and descriptive texts. + SetColor(UIElement::INFO); + std::string header = "Show background text image"; + text_y += DrawTextLine(text_x, text_y, header, true); + std::string locale_selection = android::base::StringPrintf( + "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size()); + // clang-format off + std::vector instruction = { + locale_selection, + "Use volume up/down to switch locales and power to exit." + }; + // clang-format on + text_y += DrawWrappedTextLines(text_x, text_y, instruction); + + // Iterate through the text images and display them in order for the current locale. + for (const auto& p : surfaces) { + text_y += line_spacing; + SetColor(UIElement::LOG); + text_y += DrawTextLine(text_x, text_y, p.first, false); + gr_color(255, 255, 255, 255); + gr_texticon(text_x, text_y, p.second.get()); + text_y += gr_get_height(p.second.get()); + } + // Update the whole screen. + gr_flip(); +} + +void ScreenRecoveryUI::CheckBackgroundTextImages() { + // Load a list of locales embedded in one of the resource files. + std::vector locales_entries = get_locales_in_png("installing_text"); + if (locales_entries.empty()) { + Print("Failed to load locales from the resource files\n"); + return; + } + std::string saved_locale = locale_; + size_t selected = 0; + SelectAndShowBackgroundText(locales_entries, selected); + + FlushKeys(); + while (true) { + int key = WaitKey(); + if (key == static_cast(KeyError::INTERRUPTED)) break; + if (key == KEY_POWER || key == KEY_ENTER) { + break; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1; + SelectAndShowBackgroundText(locales_entries, selected); + } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) { + selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1; + SelectAndShowBackgroundText(locales_entries, selected); + } + } + + SetLocale(saved_locale); +} + +int ScreenRecoveryUI::ScreenWidth() const { + return gr_fb_width(); +} + +int ScreenRecoveryUI::ScreenHeight() const { + return gr_fb_height(); +} + +void ScreenRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const { + gr_blit(surface, sx, sy, w, h, dx, dy); +} + +int ScreenRecoveryUI::DrawHorizontalRule(int y) const { + gr_fill(0, y + 4, ScreenWidth(), y + 6); + return 8; +} + +void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const { + gr_fill(x, y, x + width, y + height); +} + +void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const { + gr_fill(x, y, w, h); +} + +void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const { + gr_texticon(x, y, surface); +} + +int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { + gr_text(gr_sys_font(), x, y, line.c_str(), bold); + return char_height_ + 4; +} + +int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector& lines) const { + int offset = 0; + for (const auto& line : lines) { + offset += DrawTextLine(x, y + offset, line, false); + } + return offset; +} + +int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, + const std::vector& lines) const { + // Keep symmetrical margins based on the given offset (i.e. x). + size_t text_cols = (ScreenWidth() - x * 2) / char_width_; + int offset = 0; + for (const auto& line : lines) { + size_t next_start = 0; + while (next_start < line.size()) { + std::string sub = line.substr(next_start, text_cols + 1); + if (sub.size() <= text_cols) { + next_start += sub.size(); + } else { + // Line too long and must be wrapped to text_cols columns. + size_t last_space = sub.find_last_of(" \t\n"); + if (last_space == std::string::npos) { + // No space found, just draw as much as we can. + sub.resize(text_cols); + next_start += text_cols; + } else { + sub.resize(last_space); + next_start += last_space + 1; + } + } + offset += DrawTextLine(x, y + offset, sub, false); + } + } + return offset; +} + +void ScreenRecoveryUI::SetTitle(const std::vector& lines) { + title_lines_ = lines; +} + +std::vector ScreenRecoveryUI::GetMenuHelpMessage() const { + // clang-format off + static std::vector REGULAR_HELP{ + "Use volume up/down and power.", + }; + static std::vector LONG_PRESS_HELP{ + "Any button cycles highlight.", + "Long-press activates.", + }; + // clang-format on + return HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP; +} + +// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex +// locked. +void ScreenRecoveryUI::draw_screen_locked() { + if (!show_text) { + draw_background_locked(); + draw_foreground_locked(); + return; + } + + gr_color(0, 0, 0, 255); + gr_clear(); + + draw_menu_and_text_buffer_locked(GetMenuHelpMessage()); +} + +// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_menu_and_text_buffer_locked( + const std::vector& help_message) { + int y = margin_height_; + + if (fastbootd_logo_ && fastbootd_logo_enabled_) { + // Try to get this centered on screen. + auto width = gr_get_width(fastbootd_logo_.get()); + auto height = gr_get_height(fastbootd_logo_.get()); + auto centered_x = ScreenWidth() / 2 - width / 2; + DrawSurface(fastbootd_logo_.get(), 0, 0, width, height, centered_x, y); + y += height; + } + + if (menu_) { + int x = margin_width_ + kMenuIndent; + + SetColor(UIElement::INFO); + + for (size_t i = 0; i < title_lines_.size(); i++) { + y += DrawTextLine(x, y, title_lines_[i], i == 0); + } + + y += DrawTextLines(x, y, help_message); + + y += menu_->DrawHeader(x, y); + y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress()); + } + + // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or + // we've displayed the entire text buffer. + SetColor(UIElement::LOG); + int row = text_row_; + size_t count = 0; + for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_; + ty -= char_height_, ++count) { + DrawTextLine(margin_width_, ty, text_[row], false); + --row; + if (row < 0) row = text_rows_ - 1; + } +} + +// Redraw everything on the screen and flip the screen (make it visible). +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::update_screen_locked() { + draw_screen_locked(); + gr_flip(); +} + +// Updates only the progress bar, if possible, otherwise redraws the screen. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::update_progress_locked() { + if (show_text || !pagesIdentical) { + draw_screen_locked(); // Must redraw the whole screen + pagesIdentical = true; + } else { + draw_foreground_locked(); // Draw only the progress bar and overlays + } + gr_flip(); +} + +void ScreenRecoveryUI::ProgressThreadLoop() { + double interval = 1.0 / animation_fps_; + while (!progress_thread_stopped_) { + double start = now(); + bool redraw = false; + { + std::lock_guard lg(updateMutex); + + // update the installation animation, if active + // skip this if we have a text overlay (too expensive to update) + if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) { + if (!intro_done_) { + if (current_frame_ == intro_frames_.size() - 1) { + intro_done_ = true; + current_frame_ = 0; + } else { + ++current_frame_; + } + } else { + current_frame_ = (current_frame_ + 1) % loop_frames_.size(); + } + + redraw = true; + } + + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float p = 1.0 * elapsed / duration; + if (p > 1.0) p = 1.0; + if (p > progress) { + progress = p; + redraw = true; + } + } + + if (redraw) update_progress_locked(); + } + + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end - start); + if (delay < 0.02) delay = 0.02; + usleep(static_cast(delay * 1000000)); + } +} + +std::unique_ptr ScreenRecoveryUI::LoadBitmap(const std::string& filename) { + GRSurface* surface; + if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) { + LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; + return nullptr; + } + return std::unique_ptr(surface); +} + +std::unique_ptr ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) { + GRSurface* surface; + auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface); + if (result == 0) { + return std::unique_ptr(surface); + } + // TODO(xunchang) create a error code enum to refine the retry condition. + LOG(WARNING) << "Failed to load bitmap " << filename << " for locale " << locale_ << " (error " + << result << "). Falling back to use default locale."; + + result = res_create_localized_alpha_surface(filename.c_str(), DEFAULT_LOCALE, &surface); + if (result == 0) { + return std::unique_ptr(surface); + } + + LOG(ERROR) << "Failed to load bitmap " << filename << " for locale " << DEFAULT_LOCALE + << " (error " << result << ")"; + return nullptr; +} + +static char** Alloc2d(size_t rows, size_t cols) { + char** result = new char*[rows]; + for (size_t i = 0; i < rows; ++i) { + result[i] = new char[cols]; + memset(result[i], 0, cols); + } + return result; +} + +// Choose the right background string to display during update. +void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { + if (security_update) { + installing_text_ = LoadLocalizedBitmap("installing_security_text"); + } else { + installing_text_ = LoadLocalizedBitmap("installing_text"); + } + Redraw(); +} + +bool ScreenRecoveryUI::InitTextParams() { + // gr_init() would return successfully on font initialization failure. + if (gr_sys_font() == nullptr) { + return false; + } + gr_font_size(gr_sys_font(), &char_width_, &char_height_); + text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_; + text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_; + return true; +} + +bool ScreenRecoveryUI::LoadWipeDataMenuText() { + // Ignores the errors since the member variables will stay as nullptr. + cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text"); + factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text"); + try_again_text_ = LoadLocalizedBitmap("try_again_text"); + wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text"); + wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text"); + return true; +} + +bool ScreenRecoveryUI::Init(const std::string& locale) { + RecoveryUI::Init(locale); + + if (gr_init() == -1) { + return false; + } + + if (!InitTextParams()) { + return false; + } + + // Are we portrait or landscape? + layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; + // Are we the large variant of our base layout? + if (gr_fb_height() > PixelsFromDp(800)) ++layout_; + + text_ = Alloc2d(text_rows_, text_cols_ + 1); + file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); + + text_col_ = text_row_ = 0; + + // Set up the locale info. + SetLocale(locale); + + error_icon_ = LoadBitmap("icon_error"); + + progress_bar_empty_ = LoadBitmap("progress_empty"); + progress_bar_fill_ = LoadBitmap("progress_fill"); + stage_marker_empty_ = LoadBitmap("stage_empty"); + stage_marker_fill_ = LoadBitmap("stage_fill"); + + erasing_text_ = LoadLocalizedBitmap("erasing_text"); + no_command_text_ = LoadLocalizedBitmap("no_command_text"); + error_text_ = LoadLocalizedBitmap("error_text"); + + if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { + fastbootd_logo_ = LoadBitmap("fastbootd"); + } + + // Background text for "installing_update" could be "installing update" or + // "installing security update". It will be set after Init() according to the commands in BCB. + installing_text_.reset(); + + LoadWipeDataMenuText(); + + LoadAnimation(); + + // Keep the progress bar updated, even when the process is otherwise busy. + progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this); + + return true; +} + +std::string ScreenRecoveryUI::GetLocale() const { + return locale_; +} + +void ScreenRecoveryUI::LoadAnimation() { + std::unique_ptr dir(opendir(Paths::Get().resource_dir().c_str()), + closedir); + dirent* de; + std::vector intro_frame_names; + std::vector loop_frame_names; + + while ((de = readdir(dir.get())) != nullptr) { + int value, num_chars; + if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { + intro_frame_names.emplace_back(de->d_name, num_chars); + } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { + loop_frame_names.emplace_back(de->d_name, num_chars); + } + } + + size_t intro_frames = intro_frame_names.size(); + size_t loop_frames = loop_frame_names.size(); + + // It's okay to not have an intro. + if (intro_frames == 0) intro_done_ = true; + // But you must have an animation. + if (loop_frames == 0) abort(); + + std::sort(intro_frame_names.begin(), intro_frame_names.end()); + std::sort(loop_frame_names.begin(), loop_frame_names.end()); + + intro_frames_.clear(); + intro_frames_.reserve(intro_frames); + for (const auto& frame_name : intro_frame_names) { + intro_frames_.emplace_back(LoadBitmap(frame_name)); + } + + loop_frames_.clear(); + loop_frames_.reserve(loop_frames); + for (const auto& frame_name : loop_frame_names) { + loop_frames_.emplace_back(LoadBitmap(frame_name)); + } +} + +void ScreenRecoveryUI::SetBackground(Icon icon) { + std::lock_guard lg(updateMutex); + + current_icon_ = icon; + update_screen_locked(); +} + +void ScreenRecoveryUI::SetProgressType(ProgressType type) { + std::lock_guard lg(updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_progress_locked(); +} + +void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { + std::lock_guard lg(updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_progress_locked(); +} + +void ScreenRecoveryUI::SetProgress(float fraction) { + std::lock_guard lg(updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = gr_get_width(progress_bar_empty_.get()); + float scale = width * progressScopeSize; + if ((int)(progress * scale) != (int)(fraction * scale)) { + progress = fraction; + update_progress_locked(); + } + } +} + +void ScreenRecoveryUI::SetStage(int current, int max) { + std::lock_guard lg(updateMutex); + stage = current; + max_stage = max; +} + +void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { + std::string str; + android::base::StringAppendV(&str, fmt, ap); + + if (copy_to_stdout) { + fputs(str.c_str(), stdout); + } + + std::lock_guard lg(updateMutex); + if (text_rows_ > 0 && text_cols_ > 0) { + for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col_ >= text_cols_) { + text_[text_row_][text_col_] = '\0'; + text_col_ = 0; + text_row_ = (text_row_ + 1) % text_rows_; + } + if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; + } + text_[text_row_][text_col_] = '\0'; + update_screen_locked(); + } +} + +void ScreenRecoveryUI::Print(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, true, ap); + va_end(ap); +} + +void ScreenRecoveryUI::PrintOnScreenOnly(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); +} + +void ScreenRecoveryUI::PutChar(char ch) { + std::lock_guard lg(updateMutex); + if (ch != '\n') text_[text_row_][text_col_++] = ch; + if (ch == '\n' || text_col_ >= text_cols_) { + text_col_ = 0; + ++text_row_; + } +} + +void ScreenRecoveryUI::ClearText() { + std::lock_guard lg(updateMutex); + text_col_ = 0; + text_row_ = 0; + for (size_t i = 0; i < text_rows_; ++i) { + memset(text_[i], 0, text_cols_ + 1); + } +} + +void ScreenRecoveryUI::ShowFile(FILE* fp) { + std::vector offsets; + offsets.push_back(ftello(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + PrintOnScreenOnly("--(%d%% of %d bytes)--", + static_cast(100 * (double(ftello(fp)) / double(sb.st_size))), + static_cast(sb.st_size)); + Redraw(); + while (show_prompt) { + show_prompt = false; + int key = WaitKey(); + if (key == static_cast(KeyError::INTERRUPTED)) return; + if (key == KEY_POWER || key == KEY_ENTER) { + return; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + if (offsets.size() <= 1) { + show_prompt = true; + } else { + offsets.pop_back(); + fseek(fp, offsets.back(), SEEK_SET); + } + } else { + if (feof(fp)) { + return; + } + offsets.push_back(ftello(fp)); + } + } + ClearText(); + } + + int ch = getc(fp); + if (ch == EOF) { + while (text_row_ < text_rows_ - 1) PutChar('\n'); + show_prompt = true; + } else { + PutChar(ch); + if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { + show_prompt = true; + } + } + } +} + +void ScreenRecoveryUI::ShowFile(const std::string& filename) { + std::unique_ptr fp(fopen(filename.c_str(), "re"), fclose); + if (!fp) { + Print(" Unable to open %s: %s\n", filename.c_str(), strerror(errno)); + return; + } + + char** old_text = text_; + size_t old_text_col = text_col_; + size_t old_text_row = text_row_; + + // Swap in the alternate screen and clear it. + text_ = file_viewer_text_; + ClearText(); + + ShowFile(fp.get()); + + text_ = old_text; + text_col_ = old_text_col; + text_row_ = old_text_row; +} + +std::unique_ptr ScreenRecoveryUI::CreateMenu( + const GRSurface* graphic_header, const std::vector& graphic_items, + const std::vector& text_headers, const std::vector& text_items, + size_t initial_selection) const { + // horizontal unusable area: margin width + menu indent + size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent; + // vertical unusable area: margin height + title lines + helper message + high light bar. + // It is safe to reserve more space. + size_t max_height = ScreenHeight() - margin_height_ - char_height_ * (title_lines_.size() + 3); + if (GraphicMenu::Validate(max_width, max_height, graphic_header, graphic_items)) { + return std::make_unique(graphic_header, graphic_items, initial_selection, *this); + } + + fprintf(stderr, "Failed to initialize graphic menu, falling back to use the text menu.\n"); + + return CreateMenu(text_headers, text_items, initial_selection); +} + +std::unique_ptr ScreenRecoveryUI::CreateMenu(const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const { + if (text_rows_ > 0 && text_cols_ > 1) { + return std::make_unique(scrollable_menu_, text_rows_, text_cols_ - 1, text_headers, + text_items, initial_selection, char_height_, *this); + } + + fprintf(stderr, "Failed to create text menu, text_rows %zu, text_cols %zu.\n", text_rows_, + text_cols_); + return nullptr; +} + +int ScreenRecoveryUI::SelectMenu(int sel) { + std::lock_guard lg(updateMutex); + if (menu_) { + int old_sel = menu_->selection(); + sel = menu_->Select(sel); + + if (sel != old_sel) { + update_screen_locked(); + } + } + return sel; +} + +size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr&& menu, bool menu_only, + const std::function& key_handler) { + // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. + FlushKeys(); + + // If there is a key interrupt in progress, return KeyError::INTERRUPTED without starting the + // menu. + if (IsKeyInterrupted()) return static_cast(KeyError::INTERRUPTED); + + CHECK(menu != nullptr); + + // Starts and displays the menu + menu_ = std::move(menu); + Redraw(); + + int selected = menu_->selection(); + int chosen_item = -1; + while (chosen_item < 0) { + int key = WaitKey(); + if (key == static_cast(KeyError::INTERRUPTED)) { // WaitKey() was interrupted. + return static_cast(KeyError::INTERRUPTED); + } + if (key == static_cast(KeyError::TIMED_OUT)) { // WaitKey() timed out. + if (WasTextEverVisible()) { + continue; + } else { + LOG(INFO) << "Timed out waiting for key input; rebooting."; + menu_.reset(); + Redraw(); + return static_cast(KeyError::TIMED_OUT); + } + } + + bool visible = IsTextVisible(); + int action = key_handler(key, visible); + if (action < 0) { + switch (action) { + case Device::kHighlightUp: + selected = SelectMenu(--selected); + break; + case Device::kHighlightDown: + selected = SelectMenu(++selected); + break; + case Device::kInvokeItem: + chosen_item = selected; + break; + case Device::kNoAction: + break; + } + } else if (!menu_only) { + chosen_item = action; + } + } + + menu_.reset(); + Redraw(); + + return chosen_item; +} + +size_t ScreenRecoveryUI::ShowMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection, + bool menu_only, + const std::function& key_handler) { + auto menu = CreateMenu(headers, items, initial_selection); + if (menu == nullptr) { + return initial_selection; + } + + return ShowMenu(std::move(menu), menu_only, key_handler); +} + +size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector& backup_headers, + const std::vector& backup_items, + const std::function& key_handler) { + auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(), + { try_again_text_.get(), factory_data_reset_text_.get() }, + backup_headers, backup_items, 0); + if (wipe_data_menu == nullptr) { + return 0; + } + + return ShowMenu(std::move(wipe_data_menu), true, key_handler); +} + +size_t ScreenRecoveryUI::ShowPromptWipeDataConfirmationMenu( + const std::vector& backup_headers, const std::vector& backup_items, + const std::function& key_handler) { + auto confirmation_menu = + CreateMenu(wipe_data_confirmation_text_.get(), + { cancel_wipe_data_text_.get(), factory_data_reset_text_.get() }, backup_headers, + backup_items, 0); + if (confirmation_menu == nullptr) { + return 0; + } + + return ShowMenu(std::move(confirmation_menu), true, key_handler); +} + +bool ScreenRecoveryUI::IsTextVisible() { + std::lock_guard lg(updateMutex); + int visible = show_text; + return visible; +} + +bool ScreenRecoveryUI::WasTextEverVisible() { + std::lock_guard lg(updateMutex); + int ever_visible = show_text_ever; + return ever_visible; +} + +void ScreenRecoveryUI::ShowText(bool visible) { + std::lock_guard lg(updateMutex); + show_text = visible; + if (show_text) show_text_ever = true; + update_screen_locked(); +} + +void ScreenRecoveryUI::Redraw() { + std::lock_guard lg(updateMutex); + update_screen_locked(); +} + +void ScreenRecoveryUI::KeyLongPress(int) { + // Redraw so that if we're in the menu, the highlight + // will change color to indicate a successful long press. + Redraw(); +} + +void ScreenRecoveryUI::SetLocale(const std::string& new_locale) { + locale_ = new_locale; + rtl_locale_ = false; + + if (!new_locale.empty()) { + size_t separator = new_locale.find('-'); + // lang has the language prefix prior to the separator, or full string if none exists. + std::string lang = new_locale.substr(0, separator); + + // A bit cheesy: keep an explicit list of supported RTL languages. + if (lang == "ar" || // Arabic + lang == "fa" || // Persian (Farsi) + lang == "he" || // Hebrew (new language code) + lang == "iw" || // Hebrew (old language code) + lang == "ur") { // Urdu + rtl_locale_ = true; + } + } +} diff --git a/recovery_ui/stub_ui.cpp b/recovery_ui/stub_ui.cpp new file mode 100644 index 0000000000..a56b3f7250 --- /dev/null +++ b/recovery_ui/stub_ui.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "recovery_ui/stub_ui.h" + +#include + +#include "recovery_ui/device.h" + +size_t StubRecoveryUI::ShowMenu(const std::vector& /* headers */, + const std::vector& /* items */, + size_t /* initial_selection */, bool /* menu_only */, + const std::function& /*key_handler*/) { + while (true) { + int key = WaitKey(); + // Exit the loop in the case of interruption or time out. + if (key == static_cast(KeyError::INTERRUPTED) || + key == static_cast(KeyError::TIMED_OUT)) { + return static_cast(key); + } + } + LOG(FATAL) << "Unreachable key selected in ShowMenu of stub UI"; +} diff --git a/ui.cpp b/recovery_ui/ui.cpp similarity index 69% rename from ui.cpp rename to recovery_ui/ui.cpp index 3c9ded7350..3307217732 100644 --- a/ui.cpp +++ b/recovery_ui/ui.cpp @@ -14,60 +14,62 @@ * limitations under the License. */ -#include "ui.h" +#include "recovery_ui/ui.h" #include #include -#include -#include -#include #include #include #include -#include #include #include #include #include +#include #include #include +#include #include #include #include #include #include -#include -#include - -#include "common.h" -#include "roots.h" -#include "device.h" - -static constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120; -static constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness"; -static constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness"; -static constexpr const char* BRIGHTNESS_FILE_SDM = - "/sys/class/backlight/panel0-backlight/brightness"; -static constexpr const char* MAX_BRIGHTNESS_FILE_SDM = + +#include "minui/minui.h" +#include "otautil/sysutil.h" + +using namespace std::chrono_literals; + +constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120; +constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness"; +constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness"; +constexpr const char* BRIGHTNESS_FILE_SDM = "/sys/class/backlight/panel0-backlight/brightness"; +constexpr const char* MAX_BRIGHTNESS_FILE_SDM = "/sys/class/backlight/panel0-backlight/max_brightness"; +constexpr int kDefaultTouchLowThreshold = 50; +constexpr int kDefaultTouchHighThreshold = 90; + RecoveryUI::RecoveryUI() : brightness_normal_(50), brightness_dimmed_(25), brightness_file_(BRIGHTNESS_FILE), max_brightness_file_(MAX_BRIGHTNESS_FILE), touch_screen_allowed_(false), - kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD), - kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD), + fastbootd_logo_enabled_(false), + touch_low_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_low_threshold", + kDefaultTouchLowThreshold)), + touch_high_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_high_threshold", + kDefaultTouchHighThreshold)), + key_interrupted_(false), key_queue_len(0), key_last_down(-1), key_long_press(false), key_down_count(0), enable_reboot(true), consecutive_power_keys(0), - last_key(-1), has_power_key(false), has_up_key(false), has_down_key(false), @@ -75,11 +77,17 @@ RecoveryUI::RecoveryUI() touch_slot_(0), is_bootreason_recovery_ui_(false), screensaver_state_(ScreensaverState::DISABLED) { - pthread_mutex_init(&key_queue_mutex, nullptr); - pthread_cond_init(&key_queue_cond, nullptr); memset(key_pressed, 0, sizeof(key_pressed)); } +RecoveryUI::~RecoveryUI() { + ev_exit(); + input_thread_stopped_ = true; + if (input_thread_.joinable()) { + input_thread_.join(); + } +} + void RecoveryUI::OnKeyDetected(int key_code) { if (key_code == KEY_POWER) { has_power_key = true; @@ -92,16 +100,6 @@ void RecoveryUI::OnKeyDetected(int key_code) { } } -// Reads input events, handles special hot keys, and adds to the key queue. -static void* InputThreadLoop(void*) { - while (true) { - if (!ev_wait(-1)) { - ev_dispatch(); - } - } - return nullptr; -} - bool RecoveryUI::InitScreensaver() { // Disabled. if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) { @@ -168,7 +166,15 @@ bool RecoveryUI::Init(const std::string& /* locale */) { LOG(INFO) << "Screensaver disabled"; } - pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr); + // Create a separate thread that handles input events. + input_thread_ = std::thread([this]() { + while (!this->input_thread_stopped_) { + if (!ev_wait(500)) { + ev_dispatch(); + } + } + }); + return true; } @@ -176,15 +182,15 @@ void RecoveryUI::OnTouchDetected(int dx, int dy) { enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction; // We only consider a valid swipe if: - // - the delta along one axis is below kTouchLowThreshold; - // - and the delta along the other axis is beyond kTouchHighThreshold. - if (abs(dy) < kTouchLowThreshold && abs(dx) > kTouchHighThreshold) { + // - the delta along one axis is below touch_low_threshold_; + // - and the delta along the other axis is beyond touch_high_threshold_. + if (abs(dy) < touch_low_threshold_ && abs(dx) > touch_high_threshold_) { direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT; - } else if (abs(dx) < kTouchLowThreshold && abs(dy) > kTouchHighThreshold) { + } else if (abs(dx) < touch_low_threshold_ && abs(dy) > touch_high_threshold_) { direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN; } else { - LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << kTouchLowThreshold - << ", high: " << kTouchHighThreshold << ")"; + LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << touch_low_threshold_ + << ", high: " << touch_high_threshold_ << ")"; return; } @@ -325,46 +331,38 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { return 0; } -// Process a key-up or -down event. A key is "registered" when it is -// pressed and then released, with no other keypresses or releases in -// between. Registered keys are passed to CheckKey() to see if it -// should trigger a visibility toggle, an immediate reboot, or be -// queued to be processed next time the foreground thread wants a key -// (eg, for the menu). +// Processes a key-up or -down event. A key is "registered" when it is pressed and then released, +// with no other keypresses or releases in between. Registered keys are passed to CheckKey() to +// see if it should trigger a visibility toggle, an immediate reboot, or be queued to be processed +// next time the foreground thread wants a key (eg, for the menu). // -// We also keep track of which keys are currently down so that -// CheckKey can call IsKeyPressed to see what other keys are held when -// a key is registered. +// We also keep track of which keys are currently down so that CheckKey() can call IsKeyPressed() +// to see what other keys are held when a key is registered. // // updown == 1 for key down events; 0 for key up events void RecoveryUI::ProcessKey(int key_code, int updown) { bool register_key = false; bool long_press = false; - bool reboot_enabled; - pthread_mutex_lock(&key_queue_mutex); - key_pressed[key_code] = updown; - if (updown) { - ++key_down_count; - key_last_down = key_code; - key_long_press = false; - key_timer_t* info = new key_timer_t; - info->ui = this; - info->key_code = key_code; - info->count = key_down_count; - pthread_t thread; - pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info); - pthread_detach(thread); - } else { - if (key_last_down == key_code) { - long_press = key_long_press; - register_key = true; + { + std::lock_guard lg(key_press_mutex); + key_pressed[key_code] = updown; + if (updown) { + ++key_down_count; + key_last_down = key_code; + key_long_press = false; + std::thread time_key_thread(&RecoveryUI::TimeKey, this, key_code, key_down_count); + time_key_thread.detach(); + } else { + if (key_last_down == key_code) { + long_press = key_long_press; + register_key = true; + } + key_last_down = -1; } - key_last_down = -1; } - reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); + bool reboot_enabled = enable_reboot; if (register_key) { switch (CheckKey(key_code, long_press)) { case RecoveryUI::IGNORE: @@ -376,10 +374,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { case RecoveryUI::REBOOT: if (reboot_enabled) { - reboot("reboot,"); - while (true) { - pause(); - } + Reboot("userrequested,recovery,ui"); } break; @@ -390,67 +385,87 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { } } -void* RecoveryUI::time_key_helper(void* cookie) { - key_timer_t* info = static_cast(cookie); - info->ui->time_key(info->key_code, info->count); - delete info; - return nullptr; -} - -void RecoveryUI::time_key(int key_code, int count) { - usleep(750000); // 750 ms == "long" +void RecoveryUI::TimeKey(int key_code, int count) { + std::this_thread::sleep_for(750ms); // 750 ms == "long" bool long_press = false; - pthread_mutex_lock(&key_queue_mutex); - if (key_last_down == key_code && key_down_count == count) { - long_press = key_long_press = true; + { + std::lock_guard lg(key_press_mutex); + if (key_last_down == key_code && key_down_count == count) { + long_press = key_long_press = true; + } } - pthread_mutex_unlock(&key_queue_mutex); if (long_press) KeyLongPress(key_code); } void RecoveryUI::EnqueueKey(int key_code) { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_queue_mutex); const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); if (key_queue_len < queue_max) { key_queue[key_queue_len++] = key_code; - pthread_cond_signal(&key_queue_cond); + key_queue_cond.notify_one(); + } +} + +void RecoveryUI::SetScreensaverState(ScreensaverState state) { + switch (state) { + case ScreensaverState::NORMAL: + if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_), + brightness_file_)) { + screensaver_state_ = ScreensaverState::NORMAL; + LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ + << "%)"; + } else { + LOG(WARNING) << "Unable to set brightness to normal"; + } + break; + case ScreensaverState::DIMMED: + if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_), + brightness_file_)) { + LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_ + << "%)"; + screensaver_state_ = ScreensaverState::DIMMED; + } else { + LOG(WARNING) << "Unable to set brightness to dim"; + } + break; + case ScreensaverState::OFF: + if (android::base::WriteStringToFile("0", brightness_file_)) { + LOG(INFO) << "Brightness: 0 (off)"; + screensaver_state_ = ScreensaverState::OFF; + } else { + LOG(WARNING) << "Unable to set brightness to off"; + } + break; + default: + LOG(ERROR) << "Invalid screensaver state"; } - pthread_mutex_unlock(&key_queue_mutex); } int RecoveryUI::WaitKey() { - pthread_mutex_lock(&key_queue_mutex); + std::unique_lock lk(key_queue_mutex); + + // Check for a saved key queue interruption. + if (key_interrupted_) { + SetScreensaverState(ScreensaverState::NORMAL); + return static_cast(KeyError::INTERRUPTED); + } - // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is - // plugged in. + // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is plugged in. do { - struct timeval now; - struct timespec timeout; - gettimeofday(&now, nullptr); - timeout.tv_sec = now.tv_sec; - timeout.tv_nsec = now.tv_usec * 1000; - timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; - - int rc = 0; - while (key_queue_len == 0 && rc != ETIMEDOUT) { - rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + bool rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] { + return this->key_queue_len != 0 || key_interrupted_; + }); + if (key_interrupted_) { + SetScreensaverState(ScreensaverState::NORMAL); + return static_cast(KeyError::INTERRUPTED); } - if (screensaver_state_ != ScreensaverState::DISABLED) { - if (rc == ETIMEDOUT) { - // Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF. + if (!rc) { + // Must be after a timeout. Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF. if (screensaver_state_ == ScreensaverState::NORMAL) { - if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_), - brightness_file_)) { - LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_ - << "%)"; - screensaver_state_ = ScreensaverState::DIMMED; - } + SetScreensaverState(ScreensaverState::DIMMED); } else if (screensaver_state_ == ScreensaverState::DIMMED) { - if (android::base::WriteStringToFile("0", brightness_file_)) { - LOG(INFO) << "Brightness: 0 (off)"; - screensaver_state_ = ScreensaverState::OFF; - } + SetScreensaverState(ScreensaverState::OFF); } } else if (screensaver_state_ != ScreensaverState::NORMAL) { // Drop the first key if it's changing from OFF to NORMAL. @@ -461,25 +476,27 @@ int RecoveryUI::WaitKey() { } // Reset the brightness to normal. - if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_), - brightness_file_)) { - screensaver_state_ = ScreensaverState::NORMAL; - LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ - << "%)"; - } + SetScreensaverState(ScreensaverState::NORMAL); } } } while (IsUsbConnected() && key_queue_len == 0); - int key = -1; + int key = static_cast(KeyError::TIMED_OUT); if (key_queue_len > 0) { key = key_queue[0]; memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); } - pthread_mutex_unlock(&key_queue_mutex); return key; } +void RecoveryUI::InterruptKey() { + { + std::lock_guard lg(key_queue_mutex); + key_interrupted_ = true; + } + key_queue_cond.notify_one(); +} + bool RecoveryUI::IsUsbConnected() { int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); if (fd < 0) { @@ -497,20 +514,18 @@ bool RecoveryUI::IsUsbConnected() { } bool RecoveryUI::IsKeyPressed(int key) { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_press_mutex); int pressed = key_pressed[key]; - pthread_mutex_unlock(&key_queue_mutex); return pressed; } bool RecoveryUI::IsLongPress() { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_press_mutex); bool result = key_long_press; - pthread_mutex_unlock(&key_queue_mutex); return result; } -bool RecoveryUI::HasThreeButtons() { +bool RecoveryUI::HasThreeButtons() const { return has_power_key && has_up_key && has_down_key; } @@ -523,15 +538,15 @@ bool RecoveryUI::HasTouchScreen() const { } void RecoveryUI::FlushKeys() { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_queue_mutex); key_queue_len = 0; - pthread_mutex_unlock(&key_queue_mutex); } RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { - pthread_mutex_lock(&key_queue_mutex); - key_long_press = false; - pthread_mutex_unlock(&key_queue_mutex); + { + std::lock_guard lg(key_press_mutex); + key_long_press = false; + } // If we have power and volume up keys, that chord is the signal to toggle the text display. if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) { @@ -554,9 +569,7 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { // Press power seven times in a row to reboot. if (key == KEY_POWER) { - pthread_mutex_lock(&key_queue_mutex); bool reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); if (reboot_enabled) { ++consecutive_power_keys; @@ -568,15 +581,12 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { consecutive_power_keys = 0; } - last_key = key; return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; } -void RecoveryUI::KeyLongPress(int) { -} +void RecoveryUI::KeyLongPress(int) {} void RecoveryUI::SetEnableReboot(bool enabled) { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_press_mutex); enable_reboot = enabled; - pthread_mutex_unlock(&key_queue_mutex); } diff --git a/vr_device.cpp b/recovery_ui/vr_device.cpp similarity index 86% rename from vr_device.cpp rename to recovery_ui/vr_device.cpp index 61e15cbb65..fd76133077 100644 --- a/vr_device.cpp +++ b/recovery_ui/vr_device.cpp @@ -14,10 +14,9 @@ * limitations under the License. */ -#include "device.h" -#include "vr_ui.h" +#include "recovery_ui/device.h" +#include "recovery_ui/vr_ui.h" Device* make_device() { - return new Device(new VrRecoveryUI); + return new Device(new VrRecoveryUI); } - diff --git a/recovery_ui/vr_ui.cpp b/recovery_ui/vr_ui.cpp new file mode 100644 index 0000000000..5b9b1b4e50 --- /dev/null +++ b/recovery_ui/vr_ui.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "recovery_ui/vr_ui.h" + +#include + +#include "minui/minui.h" + +constexpr int kDefaultStereoOffset = 0; + +VrRecoveryUI::VrRecoveryUI() + : stereo_offset_( + android::base::GetIntProperty("ro.recovery.ui.stereo_offset", kDefaultStereoOffset)) {} + +int VrRecoveryUI::ScreenWidth() const { + return gr_fb_width() / 2; +} + +int VrRecoveryUI::ScreenHeight() const { + return gr_fb_height(); +} + +void VrRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const { + gr_blit(surface, sx, sy, w, h, dx + stereo_offset_, dy); + gr_blit(surface, sx, sy, w, h, dx - stereo_offset_ + ScreenWidth(), dy); +} + +void VrRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const { + gr_texticon(x + stereo_offset_, y, surface); + gr_texticon(x - stereo_offset_ + ScreenWidth(), y, surface); +} + +int VrRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { + gr_text(gr_sys_font(), x + stereo_offset_, y, line.c_str(), bold); + gr_text(gr_sys_font(), x - stereo_offset_ + ScreenWidth(), y, line.c_str(), bold); + return char_height_ + 4; +} + +int VrRecoveryUI::DrawHorizontalRule(int y) const { + y += 4; + gr_fill(margin_width_ + stereo_offset_, y, ScreenWidth() - margin_width_ + stereo_offset_, y + 2); + gr_fill(ScreenWidth() + margin_width_ - stereo_offset_, y, + gr_fb_width() - margin_width_ - stereo_offset_, y + 2); + return y + 4; +} + +void VrRecoveryUI::DrawHighlightBar(int /* x */, int y, int /* width */, int height) const { + gr_fill(margin_width_ + stereo_offset_, y, ScreenWidth() - margin_width_ + stereo_offset_, + y + height); + gr_fill(ScreenWidth() + margin_width_ - stereo_offset_, y, + gr_fb_width() - margin_width_ - stereo_offset_, y + height); +} + +void VrRecoveryUI::DrawFill(int x, int y, int w, int h) const { + gr_fill(x + stereo_offset_, y, w, h); + gr_fill(x - stereo_offset_ + ScreenWidth(), y, w, h); +} diff --git a/wear_device.cpp b/recovery_ui/wear_device.cpp similarity index 91% rename from wear_device.cpp rename to recovery_ui/wear_device.cpp index 3268130b0f..bf21bc962c 100644 --- a/wear_device.cpp +++ b/recovery_ui/wear_device.cpp @@ -14,10 +14,9 @@ * limitations under the License. */ -#include "device.h" -#include "wear_ui.h" +#include "recovery_ui/device.h" +#include "recovery_ui/wear_ui.h" Device* make_device() { return new Device(new WearRecoveryUI); } - diff --git a/recovery_ui/wear_ui.cpp b/recovery_ui/wear_ui.cpp new file mode 100644 index 0000000000..8d8108f145 --- /dev/null +++ b/recovery_ui/wear_ui.cpp @@ -0,0 +1,108 @@ +/* + * 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 "recovery_ui/wear_ui.h" + +#include + +#include +#include + +#include +#include +#include + +constexpr int kDefaultProgressBarBaseline = 259; +constexpr int kDefaultMenuUnusableRows = 9; + +WearRecoveryUI::WearRecoveryUI() + : ScreenRecoveryUI(true), + progress_bar_baseline_(android::base::GetIntProperty("ro.recovery.ui.progress_bar_baseline", + kDefaultProgressBarBaseline)), + menu_unusable_rows_(android::base::GetIntProperty("ro.recovery.ui.menu_unusable_rows", + kDefaultMenuUnusableRows)) { + // TODO: menu_unusable_rows_ should be computed based on the lines in draw_screen_locked(). + + touch_screen_allowed_ = true; +} + +int WearRecoveryUI::GetProgressBaseline() const { + return progress_bar_baseline_; +} + +// Draw background frame on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +// TODO merge drawing routines with screen_ui +void WearRecoveryUI::draw_background_locked() { + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (current_icon_ != NONE) { + const auto& frame = GetCurrentFrame(); + int frame_width = gr_get_width(frame); + int frame_height = gr_get_height(frame); + int frame_x = (gr_fb_width() - frame_width) / 2; + int frame_y = (gr_fb_height() - frame_height) / 2; + gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); + + // Draw recovery text on screen above progress bar. + const auto& text = GetCurrentText(); + int text_x = (ScreenWidth() - gr_get_width(text)) / 2; + int text_y = GetProgressBaseline() - gr_get_height(text) - 10; + gr_color(255, 255, 255, 255); + gr_texticon(text_x, text_y, text); + } +} + +void WearRecoveryUI::draw_screen_locked() { + draw_background_locked(); + if (!show_text) { + draw_foreground_locked(); + } else { + SetColor(UIElement::TEXT_FILL); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + // clang-format off + static std::vector SWIPE_HELP = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + }; + // clang-format on + draw_menu_and_text_buffer_locked(SWIPE_HELP); + } +} + +// TODO merge drawing routines with screen_ui +void WearRecoveryUI::update_progress_locked() { + draw_screen_locked(); + gr_flip(); +} + +void WearRecoveryUI::SetStage(int /* current */, int /* max */) {} + +std::unique_ptr WearRecoveryUI::CreateMenu(const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const { + if (text_rows_ > 0 && text_cols_ > 0) { + return std::make_unique(scrollable_menu_, text_rows_ - menu_unusable_rows_ - 1, + text_cols_ - 1, text_headers, text_items, initial_selection, + char_height_, *this); + } + + return nullptr; +} diff --git a/recovery_utils/Android.bp b/recovery_utils/Android.bp new file mode 100644 index 0000000000..bf79a2e878 --- /dev/null +++ b/recovery_utils/Android.bp @@ -0,0 +1,81 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_defaults { + name: "librecovery_utils_defaults", + + defaults: [ + "recovery_defaults", + ], + + shared_libs: [ + "android.hardware.health@2.0", + "libbase", + "libext4_utils", + "libfs_mgr", + "libhidlbase", + "libselinux", + "libutils", + ], + + static_libs: [ + "libotautil", + + // External dependencies. + "libfstab", + "libhealthhalutils", + ], +} + +// A utility lib that's local to recovery (in contrast, libotautil is exposed to device-specific +// recovery_ui lib as well as device-specific updater). +cc_library_static { + name: "librecovery_utils", + + recovery_available: true, + + defaults: [ + "librecovery_utils_defaults", + ], + + srcs: [ + "battery_utils.cpp", + "logging.cpp", + "parse_install_logs.cpp", + "roots.cpp", + "thermalutil.cpp", + ], + + header_libs: [ + "libvold_headers", + ], + + export_include_dirs: [ + "include", + ], + + export_static_lib_headers: [ + // roots.h includes . + "libfstab", + ], + + // Should avoid exposing to the libs that might be used in device-specific codes (e.g. + // libedify, libotautil, librecovery_ui). + visibility: [ + "//bootable/recovery", + "//bootable/recovery/install", + "//bootable/recovery/minadbd", + "//bootable/recovery/tests", + ], +} diff --git a/recovery_utils/battery_utils.cpp b/recovery_utils/battery_utils.cpp new file mode 100644 index 0000000000..323f525375 --- /dev/null +++ b/recovery_utils/battery_utils.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "recovery_utils/battery_utils.h" + +#include +#include + +#include +#include + +BatteryInfo GetBatteryInfo() { + using android::hardware::health::V1_0::BatteryStatus; + using android::hardware::health::V2_0::get_health_service; + using android::hardware::health::V2_0::IHealth; + using android::hardware::health::V2_0::Result; + using android::hardware::health::V2_0::toString; + + android::sp health = get_health_service(); + + int wait_second = 0; + while (true) { + auto charge_status = BatteryStatus::UNKNOWN; + + if (health == nullptr) { + LOG(WARNING) << "No health implementation is found; assuming defaults"; + } else { + health + ->getChargeStatus([&charge_status](auto res, auto out_status) { + if (res == Result::SUCCESS) { + charge_status = out_status; + } + }) + .isOk(); // should not have transport error + } + + // Treat unknown status as on charger. See hardware/interfaces/health/1.0/types.hal for the + // meaning of the return values. + bool charging = (charge_status != BatteryStatus::DISCHARGING && + charge_status != BatteryStatus::NOT_CHARGING); + + Result res = Result::UNKNOWN; + int32_t capacity = INT32_MIN; + if (health != nullptr) { + health + ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) { + res = out_res; + capacity = out_capacity; + }) + .isOk(); // should not have transport error + } + + LOG(INFO) << "charge_status " << toString(charge_status) << ", charging " << charging + << ", status " << toString(res) << ", capacity " << capacity; + + constexpr int BATTERY_READ_TIMEOUT_IN_SEC = 10; + // At startup, the battery drivers in devices like N5X/N6P take some time to load + // the battery profile. Before the load finishes, it reports value 50 as a fake + // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected + // to finish loading the battery profile earlier than 10 seconds after kernel startup. + if (res == Result::SUCCESS && capacity == 50) { + if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) { + sleep(1); + wait_second++; + continue; + } + } + // If we can't read battery percentage, it may be a device without battery. In this + // situation, use 100 as a fake battery percentage. + if (res != Result::SUCCESS) { + capacity = 100; + } + + return BatteryInfo{ charging, capacity }; + } +} diff --git a/recovery_utils/include/recovery_utils/battery_utils.h b/recovery_utils/include/recovery_utils/battery_utils.h new file mode 100644 index 0000000000..a95f71dcac --- /dev/null +++ b/recovery_utils/include/recovery_utils/battery_utils.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +struct BatteryInfo { + // Whether the device is on charger. Note that the value will be `true` if the battery status is + // unknown (BATTERY_STATUS_UNKNOWN). + bool charging; + + // The remaining battery capacity percentage (i.e. between 0 and 100). See getCapacity in + // hardware/interfaces/health/2.0/IHealth.hal. Returns 100 in case it fails to read a value from + // the health HAL. + int32_t capacity; +}; + +// Returns the battery status for OTA installation purpose. +BatteryInfo GetBatteryInfo(); diff --git a/rotate_logs.h b/recovery_utils/include/recovery_utils/logging.h similarity index 62% rename from rotate_logs.h rename to recovery_utils/include/recovery_utils/logging.h index 007c33d441..4462eca6ee 100644 --- a/rotate_logs.h +++ b/recovery_utils/include/recovery_utils/logging.h @@ -14,16 +14,30 @@ * limitations under the License. */ -#ifndef _ROTATE_LOGS_H -#define _ROTATE_LOGS_H +#ifndef _LOGGING_H +#define _LOGGING_H #include +#include #include +#include +#include + #include static constexpr int KEEP_LOG_COUNT = 10; +struct selabel_handle; + +struct saved_log_file { + std::string name; + struct stat sb; + std::string data; +}; + +void SetLoggingSehandle(selabel_handle* handle); + ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len, void* arg); @@ -35,4 +49,17 @@ ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, // Overwrite any existing last_log.$max and last_kmsg.$max. void rotate_logs(const char* last_log_file, const char* last_kmsg_file); -#endif //_ROTATE_LOG_H +// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream. +void check_and_fclose(FILE* fp, const std::string& name); + +void copy_log_file_to_pmsg(const std::string& source, const std::string& destination); +void copy_logs(bool save_current_log); +void reset_tmplog_offset(); + +void save_kernel_log(const char* destination); + +std::vector ReadLogFilesToMemory(); + +bool RestoreLogFilesAfterFormat(const std::vector& log_files); + +#endif //_LOGGING_H diff --git a/recovery_utils/include/recovery_utils/parse_install_logs.h b/recovery_utils/include/recovery_utils/parse_install_logs.h new file mode 100644 index 0000000000..135d29ccf3 --- /dev/null +++ b/recovery_utils/include/recovery_utils/parse_install_logs.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +constexpr const char* LAST_INSTALL_FILE = "/data/misc/recovery/last_install"; +constexpr const char* LAST_INSTALL_FILE_IN_CACHE = "/cache/recovery/last_install"; + +// Parses the metrics of update applied under recovery mode in |lines|, and returns a map with +// "name: value". +std::map ParseRecoveryUpdateMetrics(const std::vector& lines); +// Parses the sideload history and update metrics in the last_install file. Returns a map with +// entries as "metrics_name: value". If no such file exists, returns an empty map. +std::map ParseLastInstall(const std::string& file_name); diff --git a/roots.h b/recovery_utils/include/recovery_utils/roots.h similarity index 78% rename from roots.h rename to recovery_utils/include/recovery_utils/roots.h index 46bb77e02c..92ee756f00 100644 --- a/roots.h +++ b/recovery_utils/include/recovery_utils/roots.h @@ -14,12 +14,13 @@ * limitations under the License. */ -#ifndef RECOVERY_ROOTS_H_ -#define RECOVERY_ROOTS_H_ +#pragma once #include -typedef struct fstab_rec Volume; +#include + +using Volume = android::fs_mgr::FstabEntry; // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); @@ -29,28 +30,29 @@ Volume* volume_for_mount_point(const std::string& mount_point); // Make sure that the volume 'path' is on is mounted. Returns 0 on // success (volume is mounted). -int ensure_path_mounted(const char* path); +int ensure_path_mounted(const std::string& path); // Similar to ensure_path_mounted, but allows one to specify the mount_point. -int ensure_path_mounted_at(const char* path, const char* mount_point); +int ensure_path_mounted_at(const std::string& path, const std::string& mount_point); // Make sure that the volume 'path' is on is unmounted. Returns 0 on // success (volume is unmounted); -int ensure_path_unmounted(const char* path); +int ensure_path_unmounted(const std::string& path); // Reformat the given volume (must be the mount point only, eg // "/cache"), no paths permitted. Attempts to unmount the volume if // it is mounted. -int format_volume(const char* volume); +int format_volume(const std::string& volume); // Reformat the given volume (must be the mount point only, eg // "/cache"), no paths permitted. Attempts to unmount the volume if // it is mounted. // Copies 'directory' to root of the newly formatted volume -int format_volume(const char* volume, const char* directory); +int format_volume(const std::string& volume, const std::string& directory); // Ensure that all and only the volumes that packages expect to find // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); -#endif // RECOVERY_ROOTS_H_ +// Returns true if there is /cache in the volumes. +bool HasCache(); diff --git a/otautil/include/otautil/ThermalUtil.h b/recovery_utils/include/recovery_utils/thermalutil.h similarity index 100% rename from otautil/include/otautil/ThermalUtil.h rename to recovery_utils/include/recovery_utils/thermalutil.h diff --git a/recovery_utils/logging.cpp b/recovery_utils/logging.cpp new file mode 100644 index 0000000000..52f12a8d82 --- /dev/null +++ b/recovery_utils/logging.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 "recovery_utils/logging.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include /* for AID_SYSTEM */ +#include /* private pmsg functions */ +#include + +#include "otautil/dirutil.h" +#include "otautil/paths.h" +#include "recovery_utils/roots.h" + +constexpr const char* LOG_FILE = "/cache/recovery/log"; +constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install"; +constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; +constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; + +constexpr const char* LAST_KMSG_FILTER = "recovery/last_kmsg"; +constexpr const char* LAST_LOG_FILTER = "recovery/last_log"; + +constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; + +static struct selabel_handle* logging_sehandle; + +void SetLoggingSehandle(selabel_handle* handle) { + logging_sehandle = handle; +} + +// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the +// file pointer, or nullptr on error. +static FILE* fopen_path(const std::string& path, const char* mode, const selabel_handle* sehandle) { + if (ensure_path_mounted(path) != 0) { + LOG(ERROR) << "Can't mount " << path; + return nullptr; + } + + // When writing, try to create the containing directory, if necessary. Use generous permissions, + // the system (init.rc) will reset them. + if (strchr("wa", mode[0])) { + mkdir_recursively(path, 0777, true, sehandle); + } + return fopen(path.c_str(), mode); +} + +void check_and_fclose(FILE* fp, const std::string& name) { + fflush(fp); + if (fsync(fileno(fp)) == -1) { + PLOG(ERROR) << "Failed to fsync " << name; + } + if (ferror(fp)) { + PLOG(ERROR) << "Error in " << name; + } + fclose(fp); +} + +// close a file, log an error if the error indicator is set +ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */, + size_t len, void* arg) { + bool* do_rotate = static_cast(arg); + if (std::string(LAST_KMSG_FILTER).find(filename) != std::string::npos || + std::string(LAST_LOG_FILTER).find(filename) != std::string::npos) { + *do_rotate = true; + } + return len; +} + +ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, + void* arg) { + bool* do_rotate = static_cast(arg); + if (!*do_rotate) { + return __android_log_pmsg_file_write(id, prio, filename, buf, len); + } + + std::string name(filename); + size_t dot = name.find_last_of('.'); + std::string sub = name.substr(0, dot); + + if (std::string(LAST_KMSG_FILTER).find(sub) == std::string::npos && + std::string(LAST_LOG_FILTER).find(sub) == std::string::npos) { + return __android_log_pmsg_file_write(id, prio, filename, buf, len); + } + + // filename rotation + if (dot == std::string::npos) { + name += ".1"; + } else { + std::string number = name.substr(dot + 1); + if (!isdigit(number[0])) { + name += ".1"; + } else { + size_t i; + if (!android::base::ParseUint(number, &i)) { + LOG(ERROR) << "failed to parse uint in " << number; + return -1; + } + name = sub + "." + std::to_string(i + 1); + } + } + + return __android_log_pmsg_file_write(id, prio, name.c_str(), buf, len); +} + +// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. +// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. +// Overwrite any existing last_log.$max and last_kmsg.$max. +void rotate_logs(const char* last_log_file, const char* last_kmsg_file) { + // Logs should only be rotated once. + static bool rotated = false; + if (rotated) { + return; + } + rotated = true; + + for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) { + std::string old_log = android::base::StringPrintf("%s", last_log_file); + if (i > 0) { + old_log += "." + std::to_string(i); + } + std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i + 1); + // Ignore errors if old_log doesn't exist. + rename(old_log.c_str(), new_log.c_str()); + + std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file); + if (i > 0) { + old_kmsg += "." + std::to_string(i); + } + std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i + 1); + rename(old_kmsg.c_str(), new_kmsg.c_str()); + } +} + +// Writes content to the current pmsg session. +static ssize_t __pmsg_write(const std::string& filename, const std::string& buf) { + return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filename.c_str(), + buf.data(), buf.size()); +} + +void copy_log_file_to_pmsg(const std::string& source, const std::string& destination) { + std::string content; + android::base::ReadFileToString(source, &content); + __pmsg_write(destination, content); +} + +// How much of the temp log we have copied to the copy in cache. +static off_t tmplog_offset = 0; + +void reset_tmplog_offset() { + tmplog_offset = 0; +} + +static void copy_log_file(const std::string& source, const std::string& destination, bool append) { + FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", logging_sehandle); + if (dest_fp == nullptr) { + PLOG(ERROR) << "Can't open " << destination; + } else { + FILE* source_fp = fopen(source.c_str(), "re"); + if (source_fp != nullptr) { + if (append) { + fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write + } + char buf[4096]; + size_t bytes; + while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { + fwrite(buf, 1, bytes, dest_fp); + } + if (append) { + tmplog_offset = ftello(source_fp); + } + check_and_fclose(source_fp, source); + } + check_and_fclose(dest_fp, destination); + } +} + +void copy_logs(bool save_current_log) { + // We only rotate and record the log of the current session if explicitly requested. This usually + // happens after wipes, installation from BCB or menu selections. This is to avoid unnecessary + // rotation (and possible deletion) of log files, if it does not do anything loggable. + if (!save_current_log) { + return; + } + + // Always write to pmsg, this allows the OTA logs to be caught in `logcat -L`. + copy_log_file_to_pmsg(Paths::Get().temporary_log_file(), LAST_LOG_FILE); + copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE); + + // We can do nothing for now if there's no /cache partition. + if (!HasCache()) { + return; + } + + ensure_path_mounted(LAST_LOG_FILE); + ensure_path_mounted(LAST_KMSG_FILE); + rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); + + // Copy logs to cache so the system can find out what happened. + copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true); + copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false); + copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false); + save_kernel_log(LAST_KMSG_FILE); + chmod(LOG_FILE, 0600); + chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); + chmod(LAST_KMSG_FILE, 0600); + chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM); + chmod(LAST_LOG_FILE, 0640); + chmod(LAST_INSTALL_FILE, 0644); + chown(LAST_INSTALL_FILE, AID_SYSTEM, AID_SYSTEM); + sync(); +} + +// Read from kernel log into buffer and write out to file. +void save_kernel_log(const char* destination) { + int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); + if (klog_buf_len <= 0) { + PLOG(ERROR) << "Error getting klog size"; + return; + } + + std::string buffer(klog_buf_len, 0); + int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); + if (n == -1) { + PLOG(ERROR) << "Error in reading klog"; + return; + } + buffer.resize(n); + android::base::WriteStringToFile(buffer, destination); +} + +std::vector ReadLogFilesToMemory() { + ensure_path_mounted("/cache"); + + struct dirent* de; + std::unique_ptr d(opendir(CACHE_LOG_DIR), closedir); + if (!d) { + if (errno != ENOENT) { + PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR; + } + return {}; + } + + std::vector log_files; + while ((de = readdir(d.get())) != nullptr) { + if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { + std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name); + + struct stat sb; + if (stat(path.c_str(), &sb) != 0) { + PLOG(ERROR) << "Failed to stat " << path; + continue; + } + // Truncate files to 512kb + size_t read_size = std::min(sb.st_size, 1 << 19); + std::string data(read_size, '\0'); + + android::base::unique_fd log_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY))); + if (log_fd == -1 || !android::base::ReadFully(log_fd, data.data(), read_size)) { + PLOG(ERROR) << "Failed to read log file " << path; + continue; + } + + log_files.emplace_back(saved_log_file{ path, sb, data }); + } + } + + return log_files; +} + +bool RestoreLogFilesAfterFormat(const std::vector& log_files) { + // Re-create the log dir and write back the log entries. + if (ensure_path_mounted(CACHE_LOG_DIR) != 0) { + PLOG(ERROR) << "Failed to mount " << CACHE_LOG_DIR; + return false; + } + + if (mkdir_recursively(CACHE_LOG_DIR, 0777, false, logging_sehandle) != 0) { + PLOG(ERROR) << "Failed to create " << CACHE_LOG_DIR; + return false; + } + + for (const auto& log : log_files) { + if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid, + log.sb.st_gid)) { + PLOG(ERROR) << "Failed to write to " << log.name; + } + } + + // Any part of the log we'd copied to cache is now gone. + // Reset the pointer so we copy from the beginning of the temp + // log. + reset_tmplog_offset(); + copy_logs(true /* save_current_log */); + + return true; +} diff --git a/recovery_utils/parse_install_logs.cpp b/recovery_utils/parse_install_logs.cpp new file mode 100644 index 0000000000..c86317623f --- /dev/null +++ b/recovery_utils/parse_install_logs.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "recovery_utils/parse_install_logs.h" + +#include + +#include + +#include +#include +#include +#include +#include + +constexpr const char* OTA_SIDELOAD_METRICS = "ota_sideload"; + +// Here is an example of lines in last_install: +// ... +// time_total: 101 +// bytes_written_vendor: 51074 +// bytes_stashed_vendor: 200 +std::map ParseRecoveryUpdateMetrics(const std::vector& lines) { + constexpr unsigned int kMiB = 1024 * 1024; + std::optional bytes_written_in_mib; + std::optional bytes_stashed_in_mib; + std::map metrics; + for (const auto& line : lines) { + size_t num_index = line.find(':'); + if (num_index == std::string::npos) { + LOG(WARNING) << "Skip parsing " << line; + continue; + } + + std::string num_string = android::base::Trim(line.substr(num_index + 1)); + int64_t parsed_num; + if (!android::base::ParseInt(num_string, &parsed_num)) { + LOG(ERROR) << "Failed to parse numbers in " << line; + continue; + } + + if (android::base::StartsWith(line, "bytes_written")) { + bytes_written_in_mib = bytes_written_in_mib.value_or(0) + parsed_num / kMiB; + } else if (android::base::StartsWith(line, "bytes_stashed")) { + bytes_stashed_in_mib = bytes_stashed_in_mib.value_or(0) + parsed_num / kMiB; + } else if (android::base::StartsWith(line, "time")) { + metrics.emplace("ota_time_total", parsed_num); + } else if (android::base::StartsWith(line, "uncrypt_time")) { + metrics.emplace("ota_uncrypt_time", parsed_num); + } else if (android::base::StartsWith(line, "source_build")) { + metrics.emplace("ota_source_version", parsed_num); + } else if (android::base::StartsWith(line, "temperature_start")) { + metrics.emplace("ota_temperature_start", parsed_num); + } else if (android::base::StartsWith(line, "temperature_end")) { + metrics.emplace("ota_temperature_end", parsed_num); + } else if (android::base::StartsWith(line, "temperature_max")) { + metrics.emplace("ota_temperature_max", parsed_num); + } else if (android::base::StartsWith(line, "error")) { + metrics.emplace("ota_non_ab_error_code", parsed_num); + } else if (android::base::StartsWith(line, "cause")) { + metrics.emplace("ota_non_ab_cause_code", parsed_num); + } + } + + if (bytes_written_in_mib) { + metrics.emplace("ota_written_in_MiBs", bytes_written_in_mib.value()); + } + if (bytes_stashed_in_mib) { + metrics.emplace("ota_stashed_in_MiBs", bytes_stashed_in_mib.value()); + } + + return metrics; +} + +std::map ParseLastInstall(const std::string& file_name) { + if (access(file_name.c_str(), F_OK) != 0) { + return {}; + } + + std::string content; + if (!android::base::ReadFileToString(file_name, &content)) { + PLOG(ERROR) << "Failed to read " << file_name; + return {}; + } + + if (content.empty()) { + LOG(INFO) << "Empty last_install file"; + return {}; + } + + std::vector lines = android::base::Split(content, "\n"); + auto metrics = ParseRecoveryUpdateMetrics(lines); + + // LAST_INSTALL starts with "/sideload/package.zip" after a sideload. + if (android::base::Trim(lines[0]) == "/sideload/package.zip") { + int type = (android::base::GetProperty("ro.build.type", "") == "user") ? 1 : 0; + metrics.emplace(OTA_SIDELOAD_METRICS, type); + } + + return metrics; +} diff --git a/recovery_utils/roots.cpp b/recovery_utils/roots.cpp new file mode 100644 index 0000000000..99f3c5dc9e --- /dev/null +++ b/recovery_utils/roots.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "recovery_utils/roots.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "otautil/sysutil.h" + +using android::fs_mgr::Fstab; +using android::fs_mgr::FstabEntry; +using android::fs_mgr::ReadDefaultFstab; + +static Fstab fstab; + +constexpr const char* CACHE_ROOT = "/cache"; + +void load_volume_table() { + if (!ReadDefaultFstab(&fstab)) { + LOG(ERROR) << "Failed to read default fstab"; + return; + } + + fstab.emplace_back(FstabEntry{ + .blk_device = "ramdisk", + .mount_point = "/tmp", + .fs_type = "ramdisk", + .length = 0, + }); + + std::cout << "recovery filesystem table" << std::endl << "=========================" << std::endl; + for (size_t i = 0; i < fstab.size(); ++i) { + const auto& entry = fstab[i]; + std::cout << " " << i << " " << entry.mount_point << " " + << " " << entry.fs_type << " " << entry.blk_device << " " << entry.length + << std::endl; + } + std::cout << std::endl; +} + +Volume* volume_for_mount_point(const std::string& mount_point) { + return android::fs_mgr::GetEntryForMountPoint(&fstab, mount_point); +} + +// Mount the volume specified by path at the given mount_point. +int ensure_path_mounted_at(const std::string& path, const std::string& mount_point) { + return android::fs_mgr::EnsurePathMounted(&fstab, path, mount_point) ? 0 : -1; +} + +int ensure_path_mounted(const std::string& path) { + // Mount at the default mount point. + return android::fs_mgr::EnsurePathMounted(&fstab, path) ? 0 : -1; +} + +int ensure_path_unmounted(const std::string& path) { + return android::fs_mgr::EnsurePathUnmounted(&fstab, path) ? 0 : -1; +} + +static int exec_cmd(const std::vector& args) { + CHECK(!args.empty()); + auto argv = StringVectorToNullTerminatedArray(args); + + pid_t child; + if ((child = fork()) == 0) { + execv(argv[0], argv.data()); + _exit(EXIT_FAILURE); + } + + int status; + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status); + } + return WEXITSTATUS(status); +} + +static int64_t get_file_size(int fd, uint64_t reserve_len) { + struct stat buf; + int ret = fstat(fd, &buf); + if (ret) return 0; + + int64_t computed_size; + if (S_ISREG(buf.st_mode)) { + computed_size = buf.st_size - reserve_len; + } else if (S_ISBLK(buf.st_mode)) { + uint64_t block_device_size = get_block_device_size(fd); + if (block_device_size < reserve_len || + block_device_size > std::numeric_limits::max()) { + computed_size = 0; + } else { + computed_size = block_device_size - reserve_len; + } + } else { + computed_size = 0; + } + + return computed_size; +} + +int format_volume(const std::string& volume, const std::string& directory) { + const FstabEntry* v = android::fs_mgr::GetEntryForPath(&fstab, volume); + if (v == nullptr) { + LOG(ERROR) << "unknown volume \"" << volume << "\""; + return -1; + } + if (v->fs_type == "ramdisk") { + LOG(ERROR) << "can't format_volume \"" << volume << "\""; + return -1; + } + if (v->mount_point != volume) { + LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume"; + return -1; + } + if (ensure_path_unmounted(volume) != 0) { + LOG(ERROR) << "format_volume: Failed to unmount \"" << v->mount_point << "\""; + return -1; + } + if (v->fs_type != "ext4" && v->fs_type != "f2fs") { + LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported"; + return -1; + } + + bool needs_casefold = false; + bool needs_projid = false; + + if (volume == "/data") { + needs_casefold = android::base::GetBoolProperty("external_storage.casefold.enabled", false); + needs_projid = android::base::GetBoolProperty("external_storage.projid.enabled", false); + } + + // If there's a key_loc that looks like a path, it should be a block device for storing encryption + // metadata. Wipe it too. + if (!v->key_loc.empty() && v->key_loc[0] == '/') { + LOG(INFO) << "Wiping " << v->key_loc; + int fd = open(v->key_loc.c_str(), O_WRONLY | O_CREAT, 0644); + if (fd == -1) { + PLOG(ERROR) << "format_volume: Failed to open " << v->key_loc; + return -1; + } + wipe_block_device(fd, get_file_size(fd)); + close(fd); + } + + int64_t length = 0; + if (v->length > 0) { + length = v->length; + } else if (v->length < 0 || v->key_loc == "footer") { + android::base::unique_fd fd(open(v->blk_device.c_str(), O_RDONLY)); + if (fd == -1) { + PLOG(ERROR) << "format_volume: failed to open " << v->blk_device; + return -1; + } + length = get_file_size(fd.get(), v->length ? -v->length : CRYPT_FOOTER_OFFSET); + if (length <= 0) { + LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device; + return -1; + } + } + + if (v->fs_type == "ext4") { + static constexpr int kBlockSize = 4096; + std::vector mke2fs_args = { + "/system/bin/mke2fs", "-F", "-t", "ext4", "-b", std::to_string(kBlockSize), + }; + + // Project ID's require wider inodes. The Quotas themselves are enabled by tune2fs on boot. + if (needs_projid) { + mke2fs_args.push_back("-I"); + mke2fs_args.push_back("512"); + } + + if (v->fs_mgr_flags.ext_meta_csum) { + mke2fs_args.push_back("-O"); + mke2fs_args.push_back("metadata_csum"); + mke2fs_args.push_back("-O"); + mke2fs_args.push_back("64bit"); + mke2fs_args.push_back("-O"); + mke2fs_args.push_back("extent"); + } + + int raid_stride = v->logical_blk_size / kBlockSize; + int raid_stripe_width = v->erase_blk_size / kBlockSize; + // stride should be the max of 8KB and logical block size + if (v->logical_blk_size != 0 && v->logical_blk_size < 8192) { + raid_stride = 8192 / kBlockSize; + } + if (v->erase_blk_size != 0 && v->logical_blk_size != 0) { + mke2fs_args.push_back("-E"); + mke2fs_args.push_back( + android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride, raid_stripe_width)); + } + mke2fs_args.push_back(v->blk_device); + if (length != 0) { + mke2fs_args.push_back(std::to_string(length / kBlockSize)); + } + + int result = exec_cmd(mke2fs_args); + if (result == 0 && !directory.empty()) { + std::vector e2fsdroid_args = { + "/system/bin/e2fsdroid", "-e", "-f", directory, "-a", volume, v->blk_device, + }; + result = exec_cmd(e2fsdroid_args); + } + + if (result != 0) { + PLOG(ERROR) << "format_volume: Failed to make ext4 on " << v->blk_device; + return -1; + } + return 0; + } + + // Has to be f2fs because we checked earlier. + static constexpr int kSectorSize = 4096; + std::vector make_f2fs_cmd = { + "/system/bin/make_f2fs", + "-g", + "android", + }; + if (needs_projid) { + make_f2fs_cmd.push_back("-O"); + make_f2fs_cmd.push_back("project_quota,extra_attr"); + } + if (needs_casefold) { + make_f2fs_cmd.push_back("-O"); + make_f2fs_cmd.push_back("casefold"); + make_f2fs_cmd.push_back("-C"); + make_f2fs_cmd.push_back("utf8"); + } + make_f2fs_cmd.push_back(v->blk_device); + if (length >= kSectorSize) { + make_f2fs_cmd.push_back(std::to_string(length / kSectorSize)); + } + + if (exec_cmd(make_f2fs_cmd) != 0) { + PLOG(ERROR) << "format_volume: Failed to make_f2fs on " << v->blk_device; + return -1; + } + if (!directory.empty()) { + std::vector sload_f2fs_cmd = { + "/system/bin/sload_f2fs", "-f", directory, "-t", volume, v->blk_device, + }; + if (exec_cmd(sload_f2fs_cmd) != 0) { + PLOG(ERROR) << "format_volume: Failed to sload_f2fs on " << v->blk_device; + return -1; + } + } + return 0; +} + +int format_volume(const std::string& volume) { + return format_volume(volume, ""); +} + +int setup_install_mounts() { + if (fstab.empty()) { + LOG(ERROR) << "can't set up install mounts: no fstab loaded"; + return -1; + } + for (const FstabEntry& entry : fstab) { + // We don't want to do anything with "/". + if (entry.mount_point == "/") { + continue; + } + + if (entry.mount_point == "/tmp" || entry.mount_point == "/cache") { + if (ensure_path_mounted(entry.mount_point) != 0) { + LOG(ERROR) << "Failed to mount " << entry.mount_point; + return -1; + } + } else { + if (ensure_path_unmounted(entry.mount_point) != 0) { + LOG(ERROR) << "Failed to unmount " << entry.mount_point; + return -1; + } + } + } + return 0; +} + +bool HasCache() { + CHECK(!fstab.empty()); + static bool has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; + return has_cache; +} diff --git a/otautil/ThermalUtil.cpp b/recovery_utils/thermalutil.cpp similarity index 98% rename from otautil/ThermalUtil.cpp rename to recovery_utils/thermalutil.cpp index 5d9bd45c08..5436355d6e 100644 --- a/otautil/ThermalUtil.cpp +++ b/recovery_utils/thermalutil.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "otautil/ThermalUtil.h" +#include "recovery_utils/thermalutil.h" #include #include diff --git a/res-560dpi b/res-560dpi deleted file mode 120000 index 1db3a2e237..0000000000 --- a/res-560dpi +++ /dev/null @@ -1 +0,0 @@ -res-xxxhdpi \ No newline at end of file diff --git a/res-hdpi/images/fastbootd.png b/res-hdpi/images/fastbootd.png new file mode 100644 index 0000000000..5127b3b370 Binary files /dev/null and b/res-hdpi/images/fastbootd.png differ diff --git a/res-hdpi/images/loop00000.png b/res-hdpi/images/loop00000.png index 9e9d1e3209..7e5064775c 100644 Binary files a/res-hdpi/images/loop00000.png and b/res-hdpi/images/loop00000.png differ diff --git a/res-hdpi/images/loop00001.png b/res-hdpi/images/loop00001.png index cd53cc6cdf..c8362df781 100644 Binary files a/res-hdpi/images/loop00001.png and b/res-hdpi/images/loop00001.png differ diff --git a/res-hdpi/images/loop00002.png b/res-hdpi/images/loop00002.png index d5b5cead45..d239bd0476 100644 Binary files a/res-hdpi/images/loop00002.png and b/res-hdpi/images/loop00002.png differ diff --git a/res-hdpi/images/loop00003.png b/res-hdpi/images/loop00003.png index 50e08ded4d..f42f5c4cee 100644 Binary files a/res-hdpi/images/loop00003.png and b/res-hdpi/images/loop00003.png differ diff --git a/res-hdpi/images/loop00004.png b/res-hdpi/images/loop00004.png index d69f76263e..9614452fc1 100644 Binary files a/res-hdpi/images/loop00004.png and b/res-hdpi/images/loop00004.png differ diff --git a/res-hdpi/images/loop00005.png b/res-hdpi/images/loop00005.png index 32d368e285..b30dc98c69 100644 Binary files a/res-hdpi/images/loop00005.png and b/res-hdpi/images/loop00005.png differ diff --git a/res-hdpi/images/loop00006.png b/res-hdpi/images/loop00006.png index fcc750bdfe..b54969c720 100644 Binary files a/res-hdpi/images/loop00006.png and b/res-hdpi/images/loop00006.png differ diff --git a/res-hdpi/images/loop00007.png b/res-hdpi/images/loop00007.png index d37ba5ca88..e338574dc8 100644 Binary files a/res-hdpi/images/loop00007.png and b/res-hdpi/images/loop00007.png differ diff --git a/res-hdpi/images/loop00008.png b/res-hdpi/images/loop00008.png index 5a16054706..85fa0f3a54 100644 Binary files a/res-hdpi/images/loop00008.png and b/res-hdpi/images/loop00008.png differ diff --git a/res-hdpi/images/loop00009.png b/res-hdpi/images/loop00009.png index 49ede64ade..d3cbf51b6a 100644 Binary files a/res-hdpi/images/loop00009.png and b/res-hdpi/images/loop00009.png differ diff --git a/res-hdpi/images/loop00010.png b/res-hdpi/images/loop00010.png index f9e219ff7f..daacc2050c 100644 Binary files a/res-hdpi/images/loop00010.png and b/res-hdpi/images/loop00010.png differ diff --git a/res-hdpi/images/loop00011.png b/res-hdpi/images/loop00011.png index 3fbe0b5aaf..11224ab139 100644 Binary files a/res-hdpi/images/loop00011.png and b/res-hdpi/images/loop00011.png differ diff --git a/res-hdpi/images/loop00012.png b/res-hdpi/images/loop00012.png index 32294612c4..3426440c47 100644 Binary files a/res-hdpi/images/loop00012.png and b/res-hdpi/images/loop00012.png differ diff --git a/res-hdpi/images/loop00013.png b/res-hdpi/images/loop00013.png index 69773ec6cc..56875efdf7 100644 Binary files a/res-hdpi/images/loop00013.png and b/res-hdpi/images/loop00013.png differ diff --git a/res-hdpi/images/loop00014.png b/res-hdpi/images/loop00014.png index 56c15ccde9..9117dd201b 100644 Binary files a/res-hdpi/images/loop00014.png and b/res-hdpi/images/loop00014.png differ diff --git a/res-hdpi/images/loop00015.png b/res-hdpi/images/loop00015.png index 26126814df..a0b31d10d8 100644 Binary files a/res-hdpi/images/loop00015.png and b/res-hdpi/images/loop00015.png differ diff --git a/res-hdpi/images/loop00016.png b/res-hdpi/images/loop00016.png index 69f632ec40..9eafa7a9cc 100644 Binary files a/res-hdpi/images/loop00016.png and b/res-hdpi/images/loop00016.png differ diff --git a/res-hdpi/images/loop00017.png b/res-hdpi/images/loop00017.png index af356150e5..869987c581 100644 Binary files a/res-hdpi/images/loop00017.png and b/res-hdpi/images/loop00017.png differ diff --git a/res-hdpi/images/loop00018.png b/res-hdpi/images/loop00018.png index 0f72ff0901..0172c1ea2b 100644 Binary files a/res-hdpi/images/loop00018.png and b/res-hdpi/images/loop00018.png differ diff --git a/res-hdpi/images/loop00019.png b/res-hdpi/images/loop00019.png index f167644c34..c6db029c09 100644 Binary files a/res-hdpi/images/loop00019.png and b/res-hdpi/images/loop00019.png differ diff --git a/res-hdpi/images/loop00020.png b/res-hdpi/images/loop00020.png index 202a0fe3e5..89197e20b2 100644 Binary files a/res-hdpi/images/loop00020.png and b/res-hdpi/images/loop00020.png differ diff --git a/res-hdpi/images/loop00021.png b/res-hdpi/images/loop00021.png index 8c102d9830..52ad8ca48d 100644 Binary files a/res-hdpi/images/loop00021.png and b/res-hdpi/images/loop00021.png differ diff --git a/res-hdpi/images/loop00022.png b/res-hdpi/images/loop00022.png index 4bde99c617..8aa0f7b6f8 100644 Binary files a/res-hdpi/images/loop00022.png and b/res-hdpi/images/loop00022.png differ diff --git a/res-hdpi/images/loop00023.png b/res-hdpi/images/loop00023.png index 350acfb074..e037ef826a 100644 Binary files a/res-hdpi/images/loop00023.png and b/res-hdpi/images/loop00023.png differ diff --git a/res-hdpi/images/loop00024.png b/res-hdpi/images/loop00024.png index dde1a8e708..12611c304a 100644 Binary files a/res-hdpi/images/loop00024.png and b/res-hdpi/images/loop00024.png differ diff --git a/res-hdpi/images/loop00025.png b/res-hdpi/images/loop00025.png index a133ebd4d3..dcc5b83985 100644 Binary files a/res-hdpi/images/loop00025.png and b/res-hdpi/images/loop00025.png differ diff --git a/res-hdpi/images/loop00026.png b/res-hdpi/images/loop00026.png index 6825ad93df..c2762cd194 100644 Binary files a/res-hdpi/images/loop00026.png and b/res-hdpi/images/loop00026.png differ diff --git a/res-hdpi/images/loop00027.png b/res-hdpi/images/loop00027.png index 91bf1cf74f..a119d2cf5f 100644 Binary files a/res-hdpi/images/loop00027.png and b/res-hdpi/images/loop00027.png differ diff --git a/res-hdpi/images/loop00028.png b/res-hdpi/images/loop00028.png index 8cba9bba3d..87c1fb5a71 100644 Binary files a/res-hdpi/images/loop00028.png and b/res-hdpi/images/loop00028.png differ diff --git a/res-hdpi/images/loop00029.png b/res-hdpi/images/loop00029.png index bd05993573..e689c96846 100644 Binary files a/res-hdpi/images/loop00029.png and b/res-hdpi/images/loop00029.png differ diff --git a/res-hdpi/images/loop00030.png b/res-hdpi/images/loop00030.png index e30821a93a..18615f83bb 100644 Binary files a/res-hdpi/images/loop00030.png and b/res-hdpi/images/loop00030.png differ diff --git a/res-hdpi/images/loop00031.png b/res-hdpi/images/loop00031.png index 40198609bd..ecc9cb3905 100644 Binary files a/res-hdpi/images/loop00031.png and b/res-hdpi/images/loop00031.png differ diff --git a/res-hdpi/images/loop00032.png b/res-hdpi/images/loop00032.png index 41832bb94f..d3831ef9f6 100644 Binary files a/res-hdpi/images/loop00032.png and b/res-hdpi/images/loop00032.png differ diff --git a/res-hdpi/images/loop00033.png b/res-hdpi/images/loop00033.png index 583f19cba2..ce123a026b 100644 Binary files a/res-hdpi/images/loop00033.png and b/res-hdpi/images/loop00033.png differ diff --git a/res-hdpi/images/loop00034.png b/res-hdpi/images/loop00034.png index bffa72bcd3..7168fb2d6b 100644 Binary files a/res-hdpi/images/loop00034.png and b/res-hdpi/images/loop00034.png differ diff --git a/res-hdpi/images/loop00035.png b/res-hdpi/images/loop00035.png index d65d6b4e87..0713635ded 100644 Binary files a/res-hdpi/images/loop00035.png and b/res-hdpi/images/loop00035.png differ diff --git a/res-hdpi/images/loop00036.png b/res-hdpi/images/loop00036.png index a26cda1545..55358db1be 100644 Binary files a/res-hdpi/images/loop00036.png and b/res-hdpi/images/loop00036.png differ diff --git a/res-hdpi/images/loop00037.png b/res-hdpi/images/loop00037.png index 660530dcae..430876c2eb 100644 Binary files a/res-hdpi/images/loop00037.png and b/res-hdpi/images/loop00037.png differ diff --git a/res-hdpi/images/loop00038.png b/res-hdpi/images/loop00038.png index a3c9f31b96..1155b00a59 100644 Binary files a/res-hdpi/images/loop00038.png and b/res-hdpi/images/loop00038.png differ diff --git a/res-hdpi/images/loop00039.png b/res-hdpi/images/loop00039.png index 609d8cac22..ea43a89795 100644 Binary files a/res-hdpi/images/loop00039.png and b/res-hdpi/images/loop00039.png differ diff --git a/res-hdpi/images/loop00040.png b/res-hdpi/images/loop00040.png index 41904445b0..e9bbfcc3c3 100644 Binary files a/res-hdpi/images/loop00040.png and b/res-hdpi/images/loop00040.png differ diff --git a/res-hdpi/images/loop00041.png b/res-hdpi/images/loop00041.png index 9c3c37188c..421db512e0 100644 Binary files a/res-hdpi/images/loop00041.png and b/res-hdpi/images/loop00041.png differ diff --git a/res-hdpi/images/loop00042.png b/res-hdpi/images/loop00042.png index dd5baae161..91d3845cf5 100644 Binary files a/res-hdpi/images/loop00042.png and b/res-hdpi/images/loop00042.png differ diff --git a/res-hdpi/images/loop00043.png b/res-hdpi/images/loop00043.png index 814724ef51..944f579355 100644 Binary files a/res-hdpi/images/loop00043.png and b/res-hdpi/images/loop00043.png differ diff --git a/res-hdpi/images/loop00044.png b/res-hdpi/images/loop00044.png index 63c7392a12..2f61616a60 100644 Binary files a/res-hdpi/images/loop00044.png and b/res-hdpi/images/loop00044.png differ diff --git a/res-hdpi/images/loop00045.png b/res-hdpi/images/loop00045.png index 5c666effc0..147a4e9668 100644 Binary files a/res-hdpi/images/loop00045.png and b/res-hdpi/images/loop00045.png differ diff --git a/res-hdpi/images/loop00046.png b/res-hdpi/images/loop00046.png index 6fa4667ab4..fb3ebe01d6 100644 Binary files a/res-hdpi/images/loop00046.png and b/res-hdpi/images/loop00046.png differ diff --git a/res-hdpi/images/loop00047.png b/res-hdpi/images/loop00047.png index 52537ea6e1..437a743cb1 100644 Binary files a/res-hdpi/images/loop00047.png and b/res-hdpi/images/loop00047.png differ diff --git a/res-hdpi/images/loop00048.png b/res-hdpi/images/loop00048.png index 412fd1c712..b91328ed1f 100644 Binary files a/res-hdpi/images/loop00048.png and b/res-hdpi/images/loop00048.png differ diff --git a/res-hdpi/images/loop00049.png b/res-hdpi/images/loop00049.png index 6cc8ef01bf..aa3a1f8157 100644 Binary files a/res-hdpi/images/loop00049.png and b/res-hdpi/images/loop00049.png differ diff --git a/res-hdpi/images/loop00050.png b/res-hdpi/images/loop00050.png index caf36c504d..5687d77614 100644 Binary files a/res-hdpi/images/loop00050.png and b/res-hdpi/images/loop00050.png differ diff --git a/res-hdpi/images/loop00051.png b/res-hdpi/images/loop00051.png index 1cf8fb4a5b..f54a5c08b1 100644 Binary files a/res-hdpi/images/loop00051.png and b/res-hdpi/images/loop00051.png differ diff --git a/res-hdpi/images/loop00052.png b/res-hdpi/images/loop00052.png index 7ee60e82a0..50eaadc39f 100644 Binary files a/res-hdpi/images/loop00052.png and b/res-hdpi/images/loop00052.png differ diff --git a/res-hdpi/images/loop00053.png b/res-hdpi/images/loop00053.png index 691bca0ea5..033c7ccd3b 100644 Binary files a/res-hdpi/images/loop00053.png and b/res-hdpi/images/loop00053.png differ diff --git a/res-hdpi/images/loop00054.png b/res-hdpi/images/loop00054.png index fa8d0002d2..3d9fd89e24 100644 Binary files a/res-hdpi/images/loop00054.png and b/res-hdpi/images/loop00054.png differ diff --git a/res-hdpi/images/loop00055.png b/res-hdpi/images/loop00055.png index 3b7acb0521..b24dc8fa17 100644 Binary files a/res-hdpi/images/loop00055.png and b/res-hdpi/images/loop00055.png differ diff --git a/res-hdpi/images/loop00056.png b/res-hdpi/images/loop00056.png index 1c94d3094c..9ddf436aee 100644 Binary files a/res-hdpi/images/loop00056.png and b/res-hdpi/images/loop00056.png differ diff --git a/res-hdpi/images/loop00057.png b/res-hdpi/images/loop00057.png index 703f48e95f..16218acffc 100644 Binary files a/res-hdpi/images/loop00057.png and b/res-hdpi/images/loop00057.png differ diff --git a/res-hdpi/images/loop00058.png b/res-hdpi/images/loop00058.png index 8dae68a71e..24d5ee31c4 100644 Binary files a/res-hdpi/images/loop00058.png and b/res-hdpi/images/loop00058.png differ diff --git a/res-hdpi/images/loop00059.png b/res-hdpi/images/loop00059.png index 1105b4381c..480e73e249 100644 Binary files a/res-hdpi/images/loop00059.png and b/res-hdpi/images/loop00059.png differ diff --git a/res-hdpi/images/loop00060.png b/res-hdpi/images/loop00060.png index 8ae4a864c6..2429f938a1 100644 Binary files a/res-hdpi/images/loop00060.png and b/res-hdpi/images/loop00060.png differ diff --git a/res-hdpi/images/loop00061.png b/res-hdpi/images/loop00061.png index c4fca2f7e5..ec29add390 100644 Binary files a/res-hdpi/images/loop00061.png and b/res-hdpi/images/loop00061.png differ diff --git a/res-hdpi/images/loop00062.png b/res-hdpi/images/loop00062.png index d59b9d40d4..a1aaae079a 100644 Binary files a/res-hdpi/images/loop00062.png and b/res-hdpi/images/loop00062.png differ diff --git a/res-hdpi/images/loop00063.png b/res-hdpi/images/loop00063.png index 7ac8fdf08f..b567c082a7 100644 Binary files a/res-hdpi/images/loop00063.png and b/res-hdpi/images/loop00063.png differ diff --git a/res-hdpi/images/loop00064.png b/res-hdpi/images/loop00064.png index 1fa8fe8178..995ae43dcf 100644 Binary files a/res-hdpi/images/loop00064.png and b/res-hdpi/images/loop00064.png differ diff --git a/res-hdpi/images/loop00065.png b/res-hdpi/images/loop00065.png index 542ed34cfa..26e9063be4 100644 Binary files a/res-hdpi/images/loop00065.png and b/res-hdpi/images/loop00065.png differ diff --git a/res-hdpi/images/loop00066.png b/res-hdpi/images/loop00066.png index 7b6af52f5d..7eac08cf03 100644 Binary files a/res-hdpi/images/loop00066.png and b/res-hdpi/images/loop00066.png differ diff --git a/res-hdpi/images/loop00067.png b/res-hdpi/images/loop00067.png index 58d4fb732b..c865a9a33e 100644 Binary files a/res-hdpi/images/loop00067.png and b/res-hdpi/images/loop00067.png differ diff --git a/res-hdpi/images/loop00068.png b/res-hdpi/images/loop00068.png index 1f1616e823..2a9012d85d 100644 Binary files a/res-hdpi/images/loop00068.png and b/res-hdpi/images/loop00068.png differ diff --git a/res-hdpi/images/loop00069.png b/res-hdpi/images/loop00069.png index a2dbbfaecf..8d5bbdea07 100644 Binary files a/res-hdpi/images/loop00069.png and b/res-hdpi/images/loop00069.png differ diff --git a/res-hdpi/images/loop00070.png b/res-hdpi/images/loop00070.png index 60a345fc91..a01082ceeb 100644 Binary files a/res-hdpi/images/loop00070.png and b/res-hdpi/images/loop00070.png differ diff --git a/res-hdpi/images/loop00071.png b/res-hdpi/images/loop00071.png index ac444273e4..ec5511ecce 100644 Binary files a/res-hdpi/images/loop00071.png and b/res-hdpi/images/loop00071.png differ diff --git a/res-hdpi/images/loop00072.png b/res-hdpi/images/loop00072.png index a9171eb7da..e3bc89b898 100644 Binary files a/res-hdpi/images/loop00072.png and b/res-hdpi/images/loop00072.png differ diff --git a/res-hdpi/images/loop00073.png b/res-hdpi/images/loop00073.png index 7911d324cd..5ca64fcbd9 100644 Binary files a/res-hdpi/images/loop00073.png and b/res-hdpi/images/loop00073.png differ diff --git a/res-hdpi/images/loop00074.png b/res-hdpi/images/loop00074.png index dcea580a2f..44223ea227 100644 Binary files a/res-hdpi/images/loop00074.png and b/res-hdpi/images/loop00074.png differ diff --git a/res-hdpi/images/loop00075.png b/res-hdpi/images/loop00075.png index 0a7a5a527a..08582df250 100644 Binary files a/res-hdpi/images/loop00075.png and b/res-hdpi/images/loop00075.png differ diff --git a/res-hdpi/images/loop00076.png b/res-hdpi/images/loop00076.png index 674c9d2335..f84ecae74c 100644 Binary files a/res-hdpi/images/loop00076.png and b/res-hdpi/images/loop00076.png differ diff --git a/res-hdpi/images/loop00077.png b/res-hdpi/images/loop00077.png index e344f47f3a..35a737ca28 100644 Binary files a/res-hdpi/images/loop00077.png and b/res-hdpi/images/loop00077.png differ diff --git a/res-hdpi/images/loop00078.png b/res-hdpi/images/loop00078.png index e0968ce3cb..a8a38e29e8 100644 Binary files a/res-hdpi/images/loop00078.png and b/res-hdpi/images/loop00078.png differ diff --git a/res-hdpi/images/loop00079.png b/res-hdpi/images/loop00079.png index 2ff1fb0880..f5f3eb4079 100644 Binary files a/res-hdpi/images/loop00079.png and b/res-hdpi/images/loop00079.png differ diff --git a/res-hdpi/images/loop00080.png b/res-hdpi/images/loop00080.png index 26de5af767..27d566bf01 100644 Binary files a/res-hdpi/images/loop00080.png and b/res-hdpi/images/loop00080.png differ diff --git a/res-hdpi/images/loop00081.png b/res-hdpi/images/loop00081.png index 1ef6cdd980..65e475c4c8 100644 Binary files a/res-hdpi/images/loop00081.png and b/res-hdpi/images/loop00081.png differ diff --git a/res-hdpi/images/loop00082.png b/res-hdpi/images/loop00082.png index 334874ff14..af03a6f740 100644 Binary files a/res-hdpi/images/loop00082.png and b/res-hdpi/images/loop00082.png differ diff --git a/res-hdpi/images/loop00083.png b/res-hdpi/images/loop00083.png index 3b0deb1cbe..5b7c260dc6 100644 Binary files a/res-hdpi/images/loop00083.png and b/res-hdpi/images/loop00083.png differ diff --git a/res-hdpi/images/loop00084.png b/res-hdpi/images/loop00084.png index 4b8494c4c5..7b6ed28c15 100644 Binary files a/res-hdpi/images/loop00084.png and b/res-hdpi/images/loop00084.png differ diff --git a/res-hdpi/images/loop00085.png b/res-hdpi/images/loop00085.png index 2e57027532..bee4ad3171 100644 Binary files a/res-hdpi/images/loop00085.png and b/res-hdpi/images/loop00085.png differ diff --git a/res-hdpi/images/loop00086.png b/res-hdpi/images/loop00086.png index ab6f437bc3..2117acf698 100644 Binary files a/res-hdpi/images/loop00086.png and b/res-hdpi/images/loop00086.png differ diff --git a/res-hdpi/images/loop00087.png b/res-hdpi/images/loop00087.png index d6c3dcddee..ecf9ce474b 100644 Binary files a/res-hdpi/images/loop00087.png and b/res-hdpi/images/loop00087.png differ diff --git a/res-hdpi/images/loop00088.png b/res-hdpi/images/loop00088.png index 88b386842c..ede9daa123 100644 Binary files a/res-hdpi/images/loop00088.png and b/res-hdpi/images/loop00088.png differ diff --git a/res-hdpi/images/loop00089.png b/res-hdpi/images/loop00089.png index 5b4551be90..18fd86fae6 100644 Binary files a/res-hdpi/images/loop00089.png and b/res-hdpi/images/loop00089.png differ diff --git a/res-hdpi/images/loop00090.png b/res-hdpi/images/loop00090.png index 9e9d1e3209..7e5064775c 100644 Binary files a/res-hdpi/images/loop00090.png and b/res-hdpi/images/loop00090.png differ diff --git a/res-mdpi/images/fastbootd.png b/res-mdpi/images/fastbootd.png new file mode 100644 index 0000000000..c1b7bc2fb5 Binary files /dev/null and b/res-mdpi/images/fastbootd.png differ diff --git a/res-mdpi/images/loop00000.png b/res-mdpi/images/loop00000.png index 0e11c01000..7af53f8fff 100644 Binary files a/res-mdpi/images/loop00000.png and b/res-mdpi/images/loop00000.png differ diff --git a/res-mdpi/images/loop00001.png b/res-mdpi/images/loop00001.png index 9d87ecc7d7..83cefe3b27 100644 Binary files a/res-mdpi/images/loop00001.png and b/res-mdpi/images/loop00001.png differ diff --git a/res-mdpi/images/loop00002.png b/res-mdpi/images/loop00002.png index 4a47986450..c3eaa69f15 100644 Binary files a/res-mdpi/images/loop00002.png and b/res-mdpi/images/loop00002.png differ diff --git a/res-mdpi/images/loop00003.png b/res-mdpi/images/loop00003.png index 5e01eabd25..444d2d0ac8 100644 Binary files a/res-mdpi/images/loop00003.png and b/res-mdpi/images/loop00003.png differ diff --git a/res-mdpi/images/loop00004.png b/res-mdpi/images/loop00004.png index cebf84a4b0..d621b66314 100644 Binary files a/res-mdpi/images/loop00004.png and b/res-mdpi/images/loop00004.png differ diff --git a/res-mdpi/images/loop00005.png b/res-mdpi/images/loop00005.png index 4d0e8b039f..605421304a 100644 Binary files a/res-mdpi/images/loop00005.png and b/res-mdpi/images/loop00005.png differ diff --git a/res-mdpi/images/loop00006.png b/res-mdpi/images/loop00006.png index 00f9543cdf..cb8edea137 100644 Binary files a/res-mdpi/images/loop00006.png and b/res-mdpi/images/loop00006.png differ diff --git a/res-mdpi/images/loop00007.png b/res-mdpi/images/loop00007.png index 95642214c5..cbab8f5584 100644 Binary files a/res-mdpi/images/loop00007.png and b/res-mdpi/images/loop00007.png differ diff --git a/res-mdpi/images/loop00008.png b/res-mdpi/images/loop00008.png index 8d41cc514d..a085c45126 100644 Binary files a/res-mdpi/images/loop00008.png and b/res-mdpi/images/loop00008.png differ diff --git a/res-mdpi/images/loop00009.png b/res-mdpi/images/loop00009.png index 2761756b82..15ca20c39c 100644 Binary files a/res-mdpi/images/loop00009.png and b/res-mdpi/images/loop00009.png differ diff --git a/res-mdpi/images/loop00010.png b/res-mdpi/images/loop00010.png index d8b4865ded..722292deb6 100644 Binary files a/res-mdpi/images/loop00010.png and b/res-mdpi/images/loop00010.png differ diff --git a/res-mdpi/images/loop00011.png b/res-mdpi/images/loop00011.png index 84423537e9..3bc7e3e6ae 100644 Binary files a/res-mdpi/images/loop00011.png and b/res-mdpi/images/loop00011.png differ diff --git a/res-mdpi/images/loop00012.png b/res-mdpi/images/loop00012.png index cb986c5323..b9aa25d7d3 100644 Binary files a/res-mdpi/images/loop00012.png and b/res-mdpi/images/loop00012.png differ diff --git a/res-mdpi/images/loop00013.png b/res-mdpi/images/loop00013.png index 63b89b29e2..5897586996 100644 Binary files a/res-mdpi/images/loop00013.png and b/res-mdpi/images/loop00013.png differ diff --git a/res-mdpi/images/loop00014.png b/res-mdpi/images/loop00014.png index 9713813a41..7a16b2564f 100644 Binary files a/res-mdpi/images/loop00014.png and b/res-mdpi/images/loop00014.png differ diff --git a/res-mdpi/images/loop00015.png b/res-mdpi/images/loop00015.png index 3f666d7c67..6791773052 100644 Binary files a/res-mdpi/images/loop00015.png and b/res-mdpi/images/loop00015.png differ diff --git a/res-mdpi/images/loop00016.png b/res-mdpi/images/loop00016.png index 3d76b046eb..15dea3af8d 100644 Binary files a/res-mdpi/images/loop00016.png and b/res-mdpi/images/loop00016.png differ diff --git a/res-mdpi/images/loop00017.png b/res-mdpi/images/loop00017.png index 1438d77b85..6e6db83169 100644 Binary files a/res-mdpi/images/loop00017.png and b/res-mdpi/images/loop00017.png differ diff --git a/res-mdpi/images/loop00018.png b/res-mdpi/images/loop00018.png index c285fc6e9b..2055ea9b1d 100644 Binary files a/res-mdpi/images/loop00018.png and b/res-mdpi/images/loop00018.png differ diff --git a/res-mdpi/images/loop00019.png b/res-mdpi/images/loop00019.png index d6969ec444..0c0d030a07 100644 Binary files a/res-mdpi/images/loop00019.png and b/res-mdpi/images/loop00019.png differ diff --git a/res-mdpi/images/loop00020.png b/res-mdpi/images/loop00020.png index 89aa0124e5..58446e3c34 100644 Binary files a/res-mdpi/images/loop00020.png and b/res-mdpi/images/loop00020.png differ diff --git a/res-mdpi/images/loop00021.png b/res-mdpi/images/loop00021.png index b0bd514026..398d9cc335 100644 Binary files a/res-mdpi/images/loop00021.png and b/res-mdpi/images/loop00021.png differ diff --git a/res-mdpi/images/loop00022.png b/res-mdpi/images/loop00022.png index 684d023de4..068e8fa056 100644 Binary files a/res-mdpi/images/loop00022.png and b/res-mdpi/images/loop00022.png differ diff --git a/res-mdpi/images/loop00023.png b/res-mdpi/images/loop00023.png index d008e9873d..240140db3b 100644 Binary files a/res-mdpi/images/loop00023.png and b/res-mdpi/images/loop00023.png differ diff --git a/res-mdpi/images/loop00024.png b/res-mdpi/images/loop00024.png index 8fe2185eb1..26f0e7a54f 100644 Binary files a/res-mdpi/images/loop00024.png and b/res-mdpi/images/loop00024.png differ diff --git a/res-mdpi/images/loop00025.png b/res-mdpi/images/loop00025.png index c534bbd920..9dbc038bdc 100644 Binary files a/res-mdpi/images/loop00025.png and b/res-mdpi/images/loop00025.png differ diff --git a/res-mdpi/images/loop00026.png b/res-mdpi/images/loop00026.png index 61b11b5553..a5bb811baa 100644 Binary files a/res-mdpi/images/loop00026.png and b/res-mdpi/images/loop00026.png differ diff --git a/res-mdpi/images/loop00027.png b/res-mdpi/images/loop00027.png index 5c01dfc7b3..d9153675fe 100644 Binary files a/res-mdpi/images/loop00027.png and b/res-mdpi/images/loop00027.png differ diff --git a/res-mdpi/images/loop00028.png b/res-mdpi/images/loop00028.png index c3e61c08e2..9ea4ac5497 100644 Binary files a/res-mdpi/images/loop00028.png and b/res-mdpi/images/loop00028.png differ diff --git a/res-mdpi/images/loop00029.png b/res-mdpi/images/loop00029.png index e0b23ffaa4..a9b1251c29 100644 Binary files a/res-mdpi/images/loop00029.png and b/res-mdpi/images/loop00029.png differ diff --git a/res-mdpi/images/loop00030.png b/res-mdpi/images/loop00030.png index 6618ef7dc6..e3a6a2dfe2 100644 Binary files a/res-mdpi/images/loop00030.png and b/res-mdpi/images/loop00030.png differ diff --git a/res-mdpi/images/loop00031.png b/res-mdpi/images/loop00031.png index dfde81e691..6eb2d3abfe 100644 Binary files a/res-mdpi/images/loop00031.png and b/res-mdpi/images/loop00031.png differ diff --git a/res-mdpi/images/loop00032.png b/res-mdpi/images/loop00032.png index dc6a01ea20..18c540ebf9 100644 Binary files a/res-mdpi/images/loop00032.png and b/res-mdpi/images/loop00032.png differ diff --git a/res-mdpi/images/loop00033.png b/res-mdpi/images/loop00033.png index 86d104bc96..2379ae15d2 100644 Binary files a/res-mdpi/images/loop00033.png and b/res-mdpi/images/loop00033.png differ diff --git a/res-mdpi/images/loop00034.png b/res-mdpi/images/loop00034.png index 07a6d7cddf..422154eb53 100644 Binary files a/res-mdpi/images/loop00034.png and b/res-mdpi/images/loop00034.png differ diff --git a/res-mdpi/images/loop00035.png b/res-mdpi/images/loop00035.png index 3e5cb4ea99..b57702aa70 100644 Binary files a/res-mdpi/images/loop00035.png and b/res-mdpi/images/loop00035.png differ diff --git a/res-mdpi/images/loop00036.png b/res-mdpi/images/loop00036.png index 6ac7e35e6f..17f415ab79 100644 Binary files a/res-mdpi/images/loop00036.png and b/res-mdpi/images/loop00036.png differ diff --git a/res-mdpi/images/loop00037.png b/res-mdpi/images/loop00037.png index 527c48d208..0482866abf 100644 Binary files a/res-mdpi/images/loop00037.png and b/res-mdpi/images/loop00037.png differ diff --git a/res-mdpi/images/loop00038.png b/res-mdpi/images/loop00038.png index 41c6a03564..8a54faf3c9 100644 Binary files a/res-mdpi/images/loop00038.png and b/res-mdpi/images/loop00038.png differ diff --git a/res-mdpi/images/loop00039.png b/res-mdpi/images/loop00039.png index d24d6429c2..58810b135a 100644 Binary files a/res-mdpi/images/loop00039.png and b/res-mdpi/images/loop00039.png differ diff --git a/res-mdpi/images/loop00040.png b/res-mdpi/images/loop00040.png index f3f077f8b9..08e97c2b4f 100644 Binary files a/res-mdpi/images/loop00040.png and b/res-mdpi/images/loop00040.png differ diff --git a/res-mdpi/images/loop00041.png b/res-mdpi/images/loop00041.png index 33e0715f24..9b6f5228a8 100644 Binary files a/res-mdpi/images/loop00041.png and b/res-mdpi/images/loop00041.png differ diff --git a/res-mdpi/images/loop00042.png b/res-mdpi/images/loop00042.png index b1ef146914..3d62e3357d 100644 Binary files a/res-mdpi/images/loop00042.png and b/res-mdpi/images/loop00042.png differ diff --git a/res-mdpi/images/loop00043.png b/res-mdpi/images/loop00043.png index d835f33998..01b0bc16c0 100644 Binary files a/res-mdpi/images/loop00043.png and b/res-mdpi/images/loop00043.png differ diff --git a/res-mdpi/images/loop00044.png b/res-mdpi/images/loop00044.png index 47ee00f0ad..3f97b24dd2 100644 Binary files a/res-mdpi/images/loop00044.png and b/res-mdpi/images/loop00044.png differ diff --git a/res-mdpi/images/loop00045.png b/res-mdpi/images/loop00045.png index 2c9dd71276..91a5a019e1 100644 Binary files a/res-mdpi/images/loop00045.png and b/res-mdpi/images/loop00045.png differ diff --git a/res-mdpi/images/loop00046.png b/res-mdpi/images/loop00046.png index 7b0a557bb4..ceb43bfe07 100644 Binary files a/res-mdpi/images/loop00046.png and b/res-mdpi/images/loop00046.png differ diff --git a/res-mdpi/images/loop00047.png b/res-mdpi/images/loop00047.png index 60368fef65..1cf282f762 100644 Binary files a/res-mdpi/images/loop00047.png and b/res-mdpi/images/loop00047.png differ diff --git a/res-mdpi/images/loop00048.png b/res-mdpi/images/loop00048.png index 8da21b50bb..4977fe084b 100644 Binary files a/res-mdpi/images/loop00048.png and b/res-mdpi/images/loop00048.png differ diff --git a/res-mdpi/images/loop00049.png b/res-mdpi/images/loop00049.png index 8604a17487..2172b67c58 100644 Binary files a/res-mdpi/images/loop00049.png and b/res-mdpi/images/loop00049.png differ diff --git a/res-mdpi/images/loop00050.png b/res-mdpi/images/loop00050.png index 230ebd99c6..b23e707526 100644 Binary files a/res-mdpi/images/loop00050.png and b/res-mdpi/images/loop00050.png differ diff --git a/res-mdpi/images/loop00051.png b/res-mdpi/images/loop00051.png index 3165ae893a..4c0d71c48e 100644 Binary files a/res-mdpi/images/loop00051.png and b/res-mdpi/images/loop00051.png differ diff --git a/res-mdpi/images/loop00052.png b/res-mdpi/images/loop00052.png index bf43112340..0077c746e7 100644 Binary files a/res-mdpi/images/loop00052.png and b/res-mdpi/images/loop00052.png differ diff --git a/res-mdpi/images/loop00053.png b/res-mdpi/images/loop00053.png index 7d801fa0a1..aa8f669bc1 100644 Binary files a/res-mdpi/images/loop00053.png and b/res-mdpi/images/loop00053.png differ diff --git a/res-mdpi/images/loop00054.png b/res-mdpi/images/loop00054.png index f3ee2468d8..b362d3e8bd 100644 Binary files a/res-mdpi/images/loop00054.png and b/res-mdpi/images/loop00054.png differ diff --git a/res-mdpi/images/loop00055.png b/res-mdpi/images/loop00055.png index fb9fcfff54..d8220fb071 100644 Binary files a/res-mdpi/images/loop00055.png and b/res-mdpi/images/loop00055.png differ diff --git a/res-mdpi/images/loop00056.png b/res-mdpi/images/loop00056.png index f6b1ee7f32..b8a4dd7ad0 100644 Binary files a/res-mdpi/images/loop00056.png and b/res-mdpi/images/loop00056.png differ diff --git a/res-mdpi/images/loop00057.png b/res-mdpi/images/loop00057.png index af009d1ecd..5874b05d1c 100644 Binary files a/res-mdpi/images/loop00057.png and b/res-mdpi/images/loop00057.png differ diff --git a/res-mdpi/images/loop00058.png b/res-mdpi/images/loop00058.png index 1cd550adc4..27753e066b 100644 Binary files a/res-mdpi/images/loop00058.png and b/res-mdpi/images/loop00058.png differ diff --git a/res-mdpi/images/loop00059.png b/res-mdpi/images/loop00059.png index cf8d18c7b6..6094b99cf6 100644 Binary files a/res-mdpi/images/loop00059.png and b/res-mdpi/images/loop00059.png differ diff --git a/res-mdpi/images/loop00060.png b/res-mdpi/images/loop00060.png index cfa53848d9..0d2fa78b3c 100644 Binary files a/res-mdpi/images/loop00060.png and b/res-mdpi/images/loop00060.png differ diff --git a/res-mdpi/images/loop00061.png b/res-mdpi/images/loop00061.png index 5fcbf4717e..7076ed8c96 100644 Binary files a/res-mdpi/images/loop00061.png and b/res-mdpi/images/loop00061.png differ diff --git a/res-mdpi/images/loop00062.png b/res-mdpi/images/loop00062.png index d360d24375..6901f4c2bd 100644 Binary files a/res-mdpi/images/loop00062.png and b/res-mdpi/images/loop00062.png differ diff --git a/res-mdpi/images/loop00063.png b/res-mdpi/images/loop00063.png index 7f59a6673c..a7579af151 100644 Binary files a/res-mdpi/images/loop00063.png and b/res-mdpi/images/loop00063.png differ diff --git a/res-mdpi/images/loop00064.png b/res-mdpi/images/loop00064.png index e02809f50d..8486ff1269 100644 Binary files a/res-mdpi/images/loop00064.png and b/res-mdpi/images/loop00064.png differ diff --git a/res-mdpi/images/loop00065.png b/res-mdpi/images/loop00065.png index 597e7965fe..ac4acbaac9 100644 Binary files a/res-mdpi/images/loop00065.png and b/res-mdpi/images/loop00065.png differ diff --git a/res-mdpi/images/loop00066.png b/res-mdpi/images/loop00066.png index 3f308f007a..97e28fc111 100644 Binary files a/res-mdpi/images/loop00066.png and b/res-mdpi/images/loop00066.png differ diff --git a/res-mdpi/images/loop00067.png b/res-mdpi/images/loop00067.png index 6435982775..7c1e2329dd 100644 Binary files a/res-mdpi/images/loop00067.png and b/res-mdpi/images/loop00067.png differ diff --git a/res-mdpi/images/loop00068.png b/res-mdpi/images/loop00068.png index 580790b163..943d5f4022 100644 Binary files a/res-mdpi/images/loop00068.png and b/res-mdpi/images/loop00068.png differ diff --git a/res-mdpi/images/loop00069.png b/res-mdpi/images/loop00069.png index ae2f4e8166..3bd05c736e 100644 Binary files a/res-mdpi/images/loop00069.png and b/res-mdpi/images/loop00069.png differ diff --git a/res-mdpi/images/loop00070.png b/res-mdpi/images/loop00070.png index 82403915a5..941b6c5a7d 100644 Binary files a/res-mdpi/images/loop00070.png and b/res-mdpi/images/loop00070.png differ diff --git a/res-mdpi/images/loop00071.png b/res-mdpi/images/loop00071.png index 03f157ce8b..be59796dc3 100644 Binary files a/res-mdpi/images/loop00071.png and b/res-mdpi/images/loop00071.png differ diff --git a/res-mdpi/images/loop00072.png b/res-mdpi/images/loop00072.png index b62dfd0d88..dc4bac9f6f 100644 Binary files a/res-mdpi/images/loop00072.png and b/res-mdpi/images/loop00072.png differ diff --git a/res-mdpi/images/loop00073.png b/res-mdpi/images/loop00073.png index ba746f2cb1..b9167a7046 100644 Binary files a/res-mdpi/images/loop00073.png and b/res-mdpi/images/loop00073.png differ diff --git a/res-mdpi/images/loop00074.png b/res-mdpi/images/loop00074.png index bafd213742..20c2ee62c1 100644 Binary files a/res-mdpi/images/loop00074.png and b/res-mdpi/images/loop00074.png differ diff --git a/res-mdpi/images/loop00075.png b/res-mdpi/images/loop00075.png index fe1f3a4df5..6597e34a6e 100644 Binary files a/res-mdpi/images/loop00075.png and b/res-mdpi/images/loop00075.png differ diff --git a/res-mdpi/images/loop00076.png b/res-mdpi/images/loop00076.png index 49960e5ee4..48cafc0c68 100644 Binary files a/res-mdpi/images/loop00076.png and b/res-mdpi/images/loop00076.png differ diff --git a/res-mdpi/images/loop00077.png b/res-mdpi/images/loop00077.png index a112cb8d2f..037542edea 100644 Binary files a/res-mdpi/images/loop00077.png and b/res-mdpi/images/loop00077.png differ diff --git a/res-mdpi/images/loop00078.png b/res-mdpi/images/loop00078.png index 5d69ab8431..26c85584f4 100644 Binary files a/res-mdpi/images/loop00078.png and b/res-mdpi/images/loop00078.png differ diff --git a/res-mdpi/images/loop00079.png b/res-mdpi/images/loop00079.png index 31f3b5589b..fbf8a829d0 100644 Binary files a/res-mdpi/images/loop00079.png and b/res-mdpi/images/loop00079.png differ diff --git a/res-mdpi/images/loop00080.png b/res-mdpi/images/loop00080.png index 42730befa5..e89a8e1001 100644 Binary files a/res-mdpi/images/loop00080.png and b/res-mdpi/images/loop00080.png differ diff --git a/res-mdpi/images/loop00081.png b/res-mdpi/images/loop00081.png index 5ea003ef7d..e81868025d 100644 Binary files a/res-mdpi/images/loop00081.png and b/res-mdpi/images/loop00081.png differ diff --git a/res-mdpi/images/loop00082.png b/res-mdpi/images/loop00082.png index ead63597d9..b31a67dce8 100644 Binary files a/res-mdpi/images/loop00082.png and b/res-mdpi/images/loop00082.png differ diff --git a/res-mdpi/images/loop00083.png b/res-mdpi/images/loop00083.png index 1d10991a1c..0005c1779b 100644 Binary files a/res-mdpi/images/loop00083.png and b/res-mdpi/images/loop00083.png differ diff --git a/res-mdpi/images/loop00084.png b/res-mdpi/images/loop00084.png index 5aafdec1d0..3297da924b 100644 Binary files a/res-mdpi/images/loop00084.png and b/res-mdpi/images/loop00084.png differ diff --git a/res-mdpi/images/loop00085.png b/res-mdpi/images/loop00085.png index 6813c33759..fc6b1c59c8 100644 Binary files a/res-mdpi/images/loop00085.png and b/res-mdpi/images/loop00085.png differ diff --git a/res-mdpi/images/loop00086.png b/res-mdpi/images/loop00086.png index 5d63072c67..29567f7223 100644 Binary files a/res-mdpi/images/loop00086.png and b/res-mdpi/images/loop00086.png differ diff --git a/res-mdpi/images/loop00087.png b/res-mdpi/images/loop00087.png index 9c65826dd7..d2882a4372 100644 Binary files a/res-mdpi/images/loop00087.png and b/res-mdpi/images/loop00087.png differ diff --git a/res-mdpi/images/loop00088.png b/res-mdpi/images/loop00088.png index 6cb1bf0cff..8587c07a45 100644 Binary files a/res-mdpi/images/loop00088.png and b/res-mdpi/images/loop00088.png differ diff --git a/res-mdpi/images/loop00089.png b/res-mdpi/images/loop00089.png index b3d742dbf3..77cbcb5b8d 100644 Binary files a/res-mdpi/images/loop00089.png and b/res-mdpi/images/loop00089.png differ diff --git a/res-mdpi/images/loop00090.png b/res-mdpi/images/loop00090.png index 0e11c01000..7af53f8fff 100644 Binary files a/res-mdpi/images/loop00090.png and b/res-mdpi/images/loop00090.png differ diff --git a/res-xhdpi/images/fastbootd.png b/res-xhdpi/images/fastbootd.png new file mode 100644 index 0000000000..e3da85ad75 Binary files /dev/null and b/res-xhdpi/images/fastbootd.png differ diff --git a/res-xhdpi/images/loop00000.png b/res-xhdpi/images/loop00000.png index b438e9e673..0b95c097be 100644 Binary files a/res-xhdpi/images/loop00000.png and b/res-xhdpi/images/loop00000.png differ diff --git a/res-xhdpi/images/loop00001.png b/res-xhdpi/images/loop00001.png index 343a185720..54b1a165ae 100644 Binary files a/res-xhdpi/images/loop00001.png and b/res-xhdpi/images/loop00001.png differ diff --git a/res-xhdpi/images/loop00002.png b/res-xhdpi/images/loop00002.png index aa5bc616f4..4b39d3f56c 100644 Binary files a/res-xhdpi/images/loop00002.png and b/res-xhdpi/images/loop00002.png differ diff --git a/res-xhdpi/images/loop00003.png b/res-xhdpi/images/loop00003.png index 5385340ca9..d2a7a7e34a 100644 Binary files a/res-xhdpi/images/loop00003.png and b/res-xhdpi/images/loop00003.png differ diff --git a/res-xhdpi/images/loop00004.png b/res-xhdpi/images/loop00004.png index cdead7d742..fa40d42b41 100644 Binary files a/res-xhdpi/images/loop00004.png and b/res-xhdpi/images/loop00004.png differ diff --git a/res-xhdpi/images/loop00005.png b/res-xhdpi/images/loop00005.png index 8eb502fdfa..4ebd50a5a4 100644 Binary files a/res-xhdpi/images/loop00005.png and b/res-xhdpi/images/loop00005.png differ diff --git a/res-xhdpi/images/loop00006.png b/res-xhdpi/images/loop00006.png index 60b0f4a612..b732001d9c 100644 Binary files a/res-xhdpi/images/loop00006.png and b/res-xhdpi/images/loop00006.png differ diff --git a/res-xhdpi/images/loop00007.png b/res-xhdpi/images/loop00007.png index a76c588a18..158f2440da 100644 Binary files a/res-xhdpi/images/loop00007.png and b/res-xhdpi/images/loop00007.png differ diff --git a/res-xhdpi/images/loop00008.png b/res-xhdpi/images/loop00008.png index 80e160322f..71b2bd58af 100644 Binary files a/res-xhdpi/images/loop00008.png and b/res-xhdpi/images/loop00008.png differ diff --git a/res-xhdpi/images/loop00009.png b/res-xhdpi/images/loop00009.png index b8f4954c1d..c92db12b92 100644 Binary files a/res-xhdpi/images/loop00009.png and b/res-xhdpi/images/loop00009.png differ diff --git a/res-xhdpi/images/loop00010.png b/res-xhdpi/images/loop00010.png index b58d6ac570..0a7622277e 100644 Binary files a/res-xhdpi/images/loop00010.png and b/res-xhdpi/images/loop00010.png differ diff --git a/res-xhdpi/images/loop00011.png b/res-xhdpi/images/loop00011.png index 0b67f3736f..c26ed41bc5 100644 Binary files a/res-xhdpi/images/loop00011.png and b/res-xhdpi/images/loop00011.png differ diff --git a/res-xhdpi/images/loop00012.png b/res-xhdpi/images/loop00012.png index 234d77a84b..9f4405b0fa 100644 Binary files a/res-xhdpi/images/loop00012.png and b/res-xhdpi/images/loop00012.png differ diff --git a/res-xhdpi/images/loop00013.png b/res-xhdpi/images/loop00013.png index 13c65243ed..2d325e2a28 100644 Binary files a/res-xhdpi/images/loop00013.png and b/res-xhdpi/images/loop00013.png differ diff --git a/res-xhdpi/images/loop00014.png b/res-xhdpi/images/loop00014.png index 92e30e3d87..d5328e2666 100644 Binary files a/res-xhdpi/images/loop00014.png and b/res-xhdpi/images/loop00014.png differ diff --git a/res-xhdpi/images/loop00015.png b/res-xhdpi/images/loop00015.png index 9c6076dc99..8c37522498 100644 Binary files a/res-xhdpi/images/loop00015.png and b/res-xhdpi/images/loop00015.png differ diff --git a/res-xhdpi/images/loop00016.png b/res-xhdpi/images/loop00016.png index 6f626c07bf..e3a881b30b 100644 Binary files a/res-xhdpi/images/loop00016.png and b/res-xhdpi/images/loop00016.png differ diff --git a/res-xhdpi/images/loop00017.png b/res-xhdpi/images/loop00017.png index ff67d5bd6d..8f6a906c4e 100644 Binary files a/res-xhdpi/images/loop00017.png and b/res-xhdpi/images/loop00017.png differ diff --git a/res-xhdpi/images/loop00018.png b/res-xhdpi/images/loop00018.png index 67b5d8fe42..e566b4a5f0 100644 Binary files a/res-xhdpi/images/loop00018.png and b/res-xhdpi/images/loop00018.png differ diff --git a/res-xhdpi/images/loop00019.png b/res-xhdpi/images/loop00019.png index 06ca98012f..66996a6d85 100644 Binary files a/res-xhdpi/images/loop00019.png and b/res-xhdpi/images/loop00019.png differ diff --git a/res-xhdpi/images/loop00020.png b/res-xhdpi/images/loop00020.png index c2288b4248..d2fdde347e 100644 Binary files a/res-xhdpi/images/loop00020.png and b/res-xhdpi/images/loop00020.png differ diff --git a/res-xhdpi/images/loop00021.png b/res-xhdpi/images/loop00021.png index ba5df46187..01064ab5b5 100644 Binary files a/res-xhdpi/images/loop00021.png and b/res-xhdpi/images/loop00021.png differ diff --git a/res-xhdpi/images/loop00022.png b/res-xhdpi/images/loop00022.png index 2b1e947ade..a2de17bf83 100644 Binary files a/res-xhdpi/images/loop00022.png and b/res-xhdpi/images/loop00022.png differ diff --git a/res-xhdpi/images/loop00023.png b/res-xhdpi/images/loop00023.png index 292e074726..2bbea09019 100644 Binary files a/res-xhdpi/images/loop00023.png and b/res-xhdpi/images/loop00023.png differ diff --git a/res-xhdpi/images/loop00024.png b/res-xhdpi/images/loop00024.png index 11352f6f7b..787b97f029 100644 Binary files a/res-xhdpi/images/loop00024.png and b/res-xhdpi/images/loop00024.png differ diff --git a/res-xhdpi/images/loop00025.png b/res-xhdpi/images/loop00025.png index 4212c76a75..7f37ed7436 100644 Binary files a/res-xhdpi/images/loop00025.png and b/res-xhdpi/images/loop00025.png differ diff --git a/res-xhdpi/images/loop00026.png b/res-xhdpi/images/loop00026.png index 774d00f763..2ffa399bcd 100644 Binary files a/res-xhdpi/images/loop00026.png and b/res-xhdpi/images/loop00026.png differ diff --git a/res-xhdpi/images/loop00027.png b/res-xhdpi/images/loop00027.png index 1827471b25..0f9676c86e 100644 Binary files a/res-xhdpi/images/loop00027.png and b/res-xhdpi/images/loop00027.png differ diff --git a/res-xhdpi/images/loop00028.png b/res-xhdpi/images/loop00028.png index f4e79f91d1..fd4b45d1e5 100644 Binary files a/res-xhdpi/images/loop00028.png and b/res-xhdpi/images/loop00028.png differ diff --git a/res-xhdpi/images/loop00029.png b/res-xhdpi/images/loop00029.png index 8638500119..820c7896be 100644 Binary files a/res-xhdpi/images/loop00029.png and b/res-xhdpi/images/loop00029.png differ diff --git a/res-xhdpi/images/loop00030.png b/res-xhdpi/images/loop00030.png index 94fd376199..0307431055 100644 Binary files a/res-xhdpi/images/loop00030.png and b/res-xhdpi/images/loop00030.png differ diff --git a/res-xhdpi/images/loop00031.png b/res-xhdpi/images/loop00031.png index 441a52d9ec..eebbc65235 100644 Binary files a/res-xhdpi/images/loop00031.png and b/res-xhdpi/images/loop00031.png differ diff --git a/res-xhdpi/images/loop00032.png b/res-xhdpi/images/loop00032.png index a10598ff6a..b05284a8ff 100644 Binary files a/res-xhdpi/images/loop00032.png and b/res-xhdpi/images/loop00032.png differ diff --git a/res-xhdpi/images/loop00033.png b/res-xhdpi/images/loop00033.png index 96bf453891..cf7412432e 100644 Binary files a/res-xhdpi/images/loop00033.png and b/res-xhdpi/images/loop00033.png differ diff --git a/res-xhdpi/images/loop00034.png b/res-xhdpi/images/loop00034.png index 59baf8c64e..189b88a5dd 100644 Binary files a/res-xhdpi/images/loop00034.png and b/res-xhdpi/images/loop00034.png differ diff --git a/res-xhdpi/images/loop00035.png b/res-xhdpi/images/loop00035.png index 400a8959bb..35b2fd2cee 100644 Binary files a/res-xhdpi/images/loop00035.png and b/res-xhdpi/images/loop00035.png differ diff --git a/res-xhdpi/images/loop00036.png b/res-xhdpi/images/loop00036.png index fda7acc21c..5d156b22cc 100644 Binary files a/res-xhdpi/images/loop00036.png and b/res-xhdpi/images/loop00036.png differ diff --git a/res-xhdpi/images/loop00037.png b/res-xhdpi/images/loop00037.png index d474e6f76f..08edb60712 100644 Binary files a/res-xhdpi/images/loop00037.png and b/res-xhdpi/images/loop00037.png differ diff --git a/res-xhdpi/images/loop00038.png b/res-xhdpi/images/loop00038.png index c5632e1915..0e4cb0ea60 100644 Binary files a/res-xhdpi/images/loop00038.png and b/res-xhdpi/images/loop00038.png differ diff --git a/res-xhdpi/images/loop00039.png b/res-xhdpi/images/loop00039.png index 3cf8b867bd..0671829d9d 100644 Binary files a/res-xhdpi/images/loop00039.png and b/res-xhdpi/images/loop00039.png differ diff --git a/res-xhdpi/images/loop00040.png b/res-xhdpi/images/loop00040.png index ef55a9281c..c0f602f470 100644 Binary files a/res-xhdpi/images/loop00040.png and b/res-xhdpi/images/loop00040.png differ diff --git a/res-xhdpi/images/loop00041.png b/res-xhdpi/images/loop00041.png index 60bf780844..84928df6fc 100644 Binary files a/res-xhdpi/images/loop00041.png and b/res-xhdpi/images/loop00041.png differ diff --git a/res-xhdpi/images/loop00042.png b/res-xhdpi/images/loop00042.png index cee69800ba..131b316ec9 100644 Binary files a/res-xhdpi/images/loop00042.png and b/res-xhdpi/images/loop00042.png differ diff --git a/res-xhdpi/images/loop00043.png b/res-xhdpi/images/loop00043.png index fe5abc15a1..7ef0e8fbe7 100644 Binary files a/res-xhdpi/images/loop00043.png and b/res-xhdpi/images/loop00043.png differ diff --git a/res-xhdpi/images/loop00044.png b/res-xhdpi/images/loop00044.png index f33fcee563..5fde006626 100644 Binary files a/res-xhdpi/images/loop00044.png and b/res-xhdpi/images/loop00044.png differ diff --git a/res-xhdpi/images/loop00045.png b/res-xhdpi/images/loop00045.png index e61b2a04e8..961459eeb6 100644 Binary files a/res-xhdpi/images/loop00045.png and b/res-xhdpi/images/loop00045.png differ diff --git a/res-xhdpi/images/loop00046.png b/res-xhdpi/images/loop00046.png index 4d919c036b..a4610d6827 100644 Binary files a/res-xhdpi/images/loop00046.png and b/res-xhdpi/images/loop00046.png differ diff --git a/res-xhdpi/images/loop00047.png b/res-xhdpi/images/loop00047.png index deaf9a3774..484becb616 100644 Binary files a/res-xhdpi/images/loop00047.png and b/res-xhdpi/images/loop00047.png differ diff --git a/res-xhdpi/images/loop00048.png b/res-xhdpi/images/loop00048.png index 82d8b2b889..44b342dae8 100644 Binary files a/res-xhdpi/images/loop00048.png and b/res-xhdpi/images/loop00048.png differ diff --git a/res-xhdpi/images/loop00049.png b/res-xhdpi/images/loop00049.png index a310cc9e6b..7e9633829b 100644 Binary files a/res-xhdpi/images/loop00049.png and b/res-xhdpi/images/loop00049.png differ diff --git a/res-xhdpi/images/loop00050.png b/res-xhdpi/images/loop00050.png index ad802300e3..3121bbe923 100644 Binary files a/res-xhdpi/images/loop00050.png and b/res-xhdpi/images/loop00050.png differ diff --git a/res-xhdpi/images/loop00051.png b/res-xhdpi/images/loop00051.png index 52f1ce6736..2ea6784ea1 100644 Binary files a/res-xhdpi/images/loop00051.png and b/res-xhdpi/images/loop00051.png differ diff --git a/res-xhdpi/images/loop00052.png b/res-xhdpi/images/loop00052.png index c579e87577..2b9b11a8c1 100644 Binary files a/res-xhdpi/images/loop00052.png and b/res-xhdpi/images/loop00052.png differ diff --git a/res-xhdpi/images/loop00053.png b/res-xhdpi/images/loop00053.png index 2c1bc91b57..716f8a15c7 100644 Binary files a/res-xhdpi/images/loop00053.png and b/res-xhdpi/images/loop00053.png differ diff --git a/res-xhdpi/images/loop00054.png b/res-xhdpi/images/loop00054.png index 8885475197..120adc3599 100644 Binary files a/res-xhdpi/images/loop00054.png and b/res-xhdpi/images/loop00054.png differ diff --git a/res-xhdpi/images/loop00055.png b/res-xhdpi/images/loop00055.png index 00d67dacf7..257bfa4f18 100644 Binary files a/res-xhdpi/images/loop00055.png and b/res-xhdpi/images/loop00055.png differ diff --git a/res-xhdpi/images/loop00056.png b/res-xhdpi/images/loop00056.png index 00ad26a5e0..45467b745c 100644 Binary files a/res-xhdpi/images/loop00056.png and b/res-xhdpi/images/loop00056.png differ diff --git a/res-xhdpi/images/loop00057.png b/res-xhdpi/images/loop00057.png index 3511795698..41a96fcfe4 100644 Binary files a/res-xhdpi/images/loop00057.png and b/res-xhdpi/images/loop00057.png differ diff --git a/res-xhdpi/images/loop00058.png b/res-xhdpi/images/loop00058.png index 9d28f7d1cf..36ecf645e7 100644 Binary files a/res-xhdpi/images/loop00058.png and b/res-xhdpi/images/loop00058.png differ diff --git a/res-xhdpi/images/loop00059.png b/res-xhdpi/images/loop00059.png index 776f40e424..443c0cedb3 100644 Binary files a/res-xhdpi/images/loop00059.png and b/res-xhdpi/images/loop00059.png differ diff --git a/res-xhdpi/images/loop00060.png b/res-xhdpi/images/loop00060.png index 7f728fc031..b7487f3a8f 100644 Binary files a/res-xhdpi/images/loop00060.png and b/res-xhdpi/images/loop00060.png differ diff --git a/res-xhdpi/images/loop00061.png b/res-xhdpi/images/loop00061.png index deba021497..638704de55 100644 Binary files a/res-xhdpi/images/loop00061.png and b/res-xhdpi/images/loop00061.png differ diff --git a/res-xhdpi/images/loop00062.png b/res-xhdpi/images/loop00062.png index e6b618497d..3cdaee05ca 100644 Binary files a/res-xhdpi/images/loop00062.png and b/res-xhdpi/images/loop00062.png differ diff --git a/res-xhdpi/images/loop00063.png b/res-xhdpi/images/loop00063.png index 0e590a573e..c1312277ab 100644 Binary files a/res-xhdpi/images/loop00063.png and b/res-xhdpi/images/loop00063.png differ diff --git a/res-xhdpi/images/loop00064.png b/res-xhdpi/images/loop00064.png index c7b8102ba3..4793c103ad 100644 Binary files a/res-xhdpi/images/loop00064.png and b/res-xhdpi/images/loop00064.png differ diff --git a/res-xhdpi/images/loop00065.png b/res-xhdpi/images/loop00065.png index 2ccad25775..2c8fff2f87 100644 Binary files a/res-xhdpi/images/loop00065.png and b/res-xhdpi/images/loop00065.png differ diff --git a/res-xhdpi/images/loop00066.png b/res-xhdpi/images/loop00066.png index c5573b9920..607fe0be51 100644 Binary files a/res-xhdpi/images/loop00066.png and b/res-xhdpi/images/loop00066.png differ diff --git a/res-xhdpi/images/loop00067.png b/res-xhdpi/images/loop00067.png index 005e9a6f69..3ce100551b 100644 Binary files a/res-xhdpi/images/loop00067.png and b/res-xhdpi/images/loop00067.png differ diff --git a/res-xhdpi/images/loop00068.png b/res-xhdpi/images/loop00068.png index b8d6a6a07e..a1a850b68c 100644 Binary files a/res-xhdpi/images/loop00068.png and b/res-xhdpi/images/loop00068.png differ diff --git a/res-xhdpi/images/loop00069.png b/res-xhdpi/images/loop00069.png index 7e3ba30633..54b2448301 100644 Binary files a/res-xhdpi/images/loop00069.png and b/res-xhdpi/images/loop00069.png differ diff --git a/res-xhdpi/images/loop00070.png b/res-xhdpi/images/loop00070.png index b9810b301f..b2e8f5852f 100644 Binary files a/res-xhdpi/images/loop00070.png and b/res-xhdpi/images/loop00070.png differ diff --git a/res-xhdpi/images/loop00071.png b/res-xhdpi/images/loop00071.png index 726030ca1b..33c94496d1 100644 Binary files a/res-xhdpi/images/loop00071.png and b/res-xhdpi/images/loop00071.png differ diff --git a/res-xhdpi/images/loop00072.png b/res-xhdpi/images/loop00072.png index 30c1e87e18..0684e17c63 100644 Binary files a/res-xhdpi/images/loop00072.png and b/res-xhdpi/images/loop00072.png differ diff --git a/res-xhdpi/images/loop00073.png b/res-xhdpi/images/loop00073.png index 207a5acfcc..6f51a87b75 100644 Binary files a/res-xhdpi/images/loop00073.png and b/res-xhdpi/images/loop00073.png differ diff --git a/res-xhdpi/images/loop00074.png b/res-xhdpi/images/loop00074.png index 4482b0c5dc..73d38fd119 100644 Binary files a/res-xhdpi/images/loop00074.png and b/res-xhdpi/images/loop00074.png differ diff --git a/res-xhdpi/images/loop00075.png b/res-xhdpi/images/loop00075.png index 72afd0876b..a613a88885 100644 Binary files a/res-xhdpi/images/loop00075.png and b/res-xhdpi/images/loop00075.png differ diff --git a/res-xhdpi/images/loop00076.png b/res-xhdpi/images/loop00076.png index 4b66068f5b..562fd75974 100644 Binary files a/res-xhdpi/images/loop00076.png and b/res-xhdpi/images/loop00076.png differ diff --git a/res-xhdpi/images/loop00077.png b/res-xhdpi/images/loop00077.png index a94989efc9..c1e4ee689b 100644 Binary files a/res-xhdpi/images/loop00077.png and b/res-xhdpi/images/loop00077.png differ diff --git a/res-xhdpi/images/loop00078.png b/res-xhdpi/images/loop00078.png index 810e22308d..3d77c787af 100644 Binary files a/res-xhdpi/images/loop00078.png and b/res-xhdpi/images/loop00078.png differ diff --git a/res-xhdpi/images/loop00079.png b/res-xhdpi/images/loop00079.png index 8085b25952..c311148f79 100644 Binary files a/res-xhdpi/images/loop00079.png and b/res-xhdpi/images/loop00079.png differ diff --git a/res-xhdpi/images/loop00080.png b/res-xhdpi/images/loop00080.png index 4aefa4c942..c8e8013b6f 100644 Binary files a/res-xhdpi/images/loop00080.png and b/res-xhdpi/images/loop00080.png differ diff --git a/res-xhdpi/images/loop00081.png b/res-xhdpi/images/loop00081.png index c4a79fbcdd..c3efcf6e28 100644 Binary files a/res-xhdpi/images/loop00081.png and b/res-xhdpi/images/loop00081.png differ diff --git a/res-xhdpi/images/loop00082.png b/res-xhdpi/images/loop00082.png index 0fc9caa21e..30e70e6fdd 100644 Binary files a/res-xhdpi/images/loop00082.png and b/res-xhdpi/images/loop00082.png differ diff --git a/res-xhdpi/images/loop00083.png b/res-xhdpi/images/loop00083.png index f5fb15db55..c984376967 100644 Binary files a/res-xhdpi/images/loop00083.png and b/res-xhdpi/images/loop00083.png differ diff --git a/res-xhdpi/images/loop00084.png b/res-xhdpi/images/loop00084.png index ada5a25331..d06e97ced7 100644 Binary files a/res-xhdpi/images/loop00084.png and b/res-xhdpi/images/loop00084.png differ diff --git a/res-xhdpi/images/loop00085.png b/res-xhdpi/images/loop00085.png index f05e8d6208..335c58b48d 100644 Binary files a/res-xhdpi/images/loop00085.png and b/res-xhdpi/images/loop00085.png differ diff --git a/res-xhdpi/images/loop00086.png b/res-xhdpi/images/loop00086.png index 28c5dfd886..6fab9dcc6b 100644 Binary files a/res-xhdpi/images/loop00086.png and b/res-xhdpi/images/loop00086.png differ diff --git a/res-xhdpi/images/loop00087.png b/res-xhdpi/images/loop00087.png index d969905ce8..a4da498e65 100644 Binary files a/res-xhdpi/images/loop00087.png and b/res-xhdpi/images/loop00087.png differ diff --git a/res-xhdpi/images/loop00088.png b/res-xhdpi/images/loop00088.png index 6533002975..b6c4fa5dc5 100644 Binary files a/res-xhdpi/images/loop00088.png and b/res-xhdpi/images/loop00088.png differ diff --git a/res-xhdpi/images/loop00089.png b/res-xhdpi/images/loop00089.png index 0d5cdea7a4..cbc7c68a27 100644 Binary files a/res-xhdpi/images/loop00089.png and b/res-xhdpi/images/loop00089.png differ diff --git a/res-xhdpi/images/loop00090.png b/res-xhdpi/images/loop00090.png index b438e9e673..0b95c097be 100644 Binary files a/res-xhdpi/images/loop00090.png and b/res-xhdpi/images/loop00090.png differ diff --git a/res-xxhdpi/images/fastbootd.png b/res-xxhdpi/images/fastbootd.png new file mode 100644 index 0000000000..482dbb29c4 Binary files /dev/null and b/res-xxhdpi/images/fastbootd.png differ diff --git a/res-xxhdpi/images/loop00000.png b/res-xxhdpi/images/loop00000.png index 003c2f8752..f723d82d54 100644 Binary files a/res-xxhdpi/images/loop00000.png and b/res-xxhdpi/images/loop00000.png differ diff --git a/res-xxhdpi/images/loop00001.png b/res-xxhdpi/images/loop00001.png index 05de3ddcf1..8cc111c806 100644 Binary files a/res-xxhdpi/images/loop00001.png and b/res-xxhdpi/images/loop00001.png differ diff --git a/res-xxhdpi/images/loop00002.png b/res-xxhdpi/images/loop00002.png index 3b025475a3..3765cb5e97 100644 Binary files a/res-xxhdpi/images/loop00002.png and b/res-xxhdpi/images/loop00002.png differ diff --git a/res-xxhdpi/images/loop00003.png b/res-xxhdpi/images/loop00003.png index 21f0dcc666..d8dcc1804d 100644 Binary files a/res-xxhdpi/images/loop00003.png and b/res-xxhdpi/images/loop00003.png differ diff --git a/res-xxhdpi/images/loop00004.png b/res-xxhdpi/images/loop00004.png index 6a8b758916..ccd65f701b 100644 Binary files a/res-xxhdpi/images/loop00004.png and b/res-xxhdpi/images/loop00004.png differ diff --git a/res-xxhdpi/images/loop00005.png b/res-xxhdpi/images/loop00005.png index a179aef457..f996c12c70 100644 Binary files a/res-xxhdpi/images/loop00005.png and b/res-xxhdpi/images/loop00005.png differ diff --git a/res-xxhdpi/images/loop00006.png b/res-xxhdpi/images/loop00006.png index ef9f5e8495..f5dcece1aa 100644 Binary files a/res-xxhdpi/images/loop00006.png and b/res-xxhdpi/images/loop00006.png differ diff --git a/res-xxhdpi/images/loop00007.png b/res-xxhdpi/images/loop00007.png index 80a477d484..87ae2224c4 100644 Binary files a/res-xxhdpi/images/loop00007.png and b/res-xxhdpi/images/loop00007.png differ diff --git a/res-xxhdpi/images/loop00008.png b/res-xxhdpi/images/loop00008.png index 6c5cec08be..b094a2da8e 100644 Binary files a/res-xxhdpi/images/loop00008.png and b/res-xxhdpi/images/loop00008.png differ diff --git a/res-xxhdpi/images/loop00009.png b/res-xxhdpi/images/loop00009.png index ac5dd30ee3..88cd77b155 100644 Binary files a/res-xxhdpi/images/loop00009.png and b/res-xxhdpi/images/loop00009.png differ diff --git a/res-xxhdpi/images/loop00010.png b/res-xxhdpi/images/loop00010.png index 18f10a17e5..ab3eb3f6a7 100644 Binary files a/res-xxhdpi/images/loop00010.png and b/res-xxhdpi/images/loop00010.png differ diff --git a/res-xxhdpi/images/loop00011.png b/res-xxhdpi/images/loop00011.png index eac89e9333..ab1c4f9b77 100644 Binary files a/res-xxhdpi/images/loop00011.png and b/res-xxhdpi/images/loop00011.png differ diff --git a/res-xxhdpi/images/loop00012.png b/res-xxhdpi/images/loop00012.png index 390f3cfd40..48235a2575 100644 Binary files a/res-xxhdpi/images/loop00012.png and b/res-xxhdpi/images/loop00012.png differ diff --git a/res-xxhdpi/images/loop00013.png b/res-xxhdpi/images/loop00013.png index 18339e93f0..443227dab5 100644 Binary files a/res-xxhdpi/images/loop00013.png and b/res-xxhdpi/images/loop00013.png differ diff --git a/res-xxhdpi/images/loop00014.png b/res-xxhdpi/images/loop00014.png index 77b5be4917..aa1361dc70 100644 Binary files a/res-xxhdpi/images/loop00014.png and b/res-xxhdpi/images/loop00014.png differ diff --git a/res-xxhdpi/images/loop00015.png b/res-xxhdpi/images/loop00015.png index 7c16937afa..6d1c431988 100644 Binary files a/res-xxhdpi/images/loop00015.png and b/res-xxhdpi/images/loop00015.png differ diff --git a/res-xxhdpi/images/loop00016.png b/res-xxhdpi/images/loop00016.png index 50ea46e764..c8fbb459fa 100644 Binary files a/res-xxhdpi/images/loop00016.png and b/res-xxhdpi/images/loop00016.png differ diff --git a/res-xxhdpi/images/loop00017.png b/res-xxhdpi/images/loop00017.png index 40bb9db17f..5f8f439048 100644 Binary files a/res-xxhdpi/images/loop00017.png and b/res-xxhdpi/images/loop00017.png differ diff --git a/res-xxhdpi/images/loop00018.png b/res-xxhdpi/images/loop00018.png index 55b4d70f66..11fdb231f3 100644 Binary files a/res-xxhdpi/images/loop00018.png and b/res-xxhdpi/images/loop00018.png differ diff --git a/res-xxhdpi/images/loop00019.png b/res-xxhdpi/images/loop00019.png index a443090e99..fc158671a2 100644 Binary files a/res-xxhdpi/images/loop00019.png and b/res-xxhdpi/images/loop00019.png differ diff --git a/res-xxhdpi/images/loop00020.png b/res-xxhdpi/images/loop00020.png index 96e77eec97..73b535fc92 100644 Binary files a/res-xxhdpi/images/loop00020.png and b/res-xxhdpi/images/loop00020.png differ diff --git a/res-xxhdpi/images/loop00021.png b/res-xxhdpi/images/loop00021.png index 35260af508..b9d42c98c8 100644 Binary files a/res-xxhdpi/images/loop00021.png and b/res-xxhdpi/images/loop00021.png differ diff --git a/res-xxhdpi/images/loop00022.png b/res-xxhdpi/images/loop00022.png index 1861848d82..221362184e 100644 Binary files a/res-xxhdpi/images/loop00022.png and b/res-xxhdpi/images/loop00022.png differ diff --git a/res-xxhdpi/images/loop00023.png b/res-xxhdpi/images/loop00023.png index 4b2e7da5fe..3b261b499c 100644 Binary files a/res-xxhdpi/images/loop00023.png and b/res-xxhdpi/images/loop00023.png differ diff --git a/res-xxhdpi/images/loop00024.png b/res-xxhdpi/images/loop00024.png index 1ffc765f4e..df97506fc0 100644 Binary files a/res-xxhdpi/images/loop00024.png and b/res-xxhdpi/images/loop00024.png differ diff --git a/res-xxhdpi/images/loop00025.png b/res-xxhdpi/images/loop00025.png index 9fb29d44bd..510704ea12 100644 Binary files a/res-xxhdpi/images/loop00025.png and b/res-xxhdpi/images/loop00025.png differ diff --git a/res-xxhdpi/images/loop00026.png b/res-xxhdpi/images/loop00026.png index 143def39fd..b4f0d179f4 100644 Binary files a/res-xxhdpi/images/loop00026.png and b/res-xxhdpi/images/loop00026.png differ diff --git a/res-xxhdpi/images/loop00027.png b/res-xxhdpi/images/loop00027.png index 623d6bed28..9a3ba1572e 100644 Binary files a/res-xxhdpi/images/loop00027.png and b/res-xxhdpi/images/loop00027.png differ diff --git a/res-xxhdpi/images/loop00028.png b/res-xxhdpi/images/loop00028.png index b7b43d27fe..d95aa72d4d 100644 Binary files a/res-xxhdpi/images/loop00028.png and b/res-xxhdpi/images/loop00028.png differ diff --git a/res-xxhdpi/images/loop00029.png b/res-xxhdpi/images/loop00029.png index c9f183db1d..fc88bf9eb9 100644 Binary files a/res-xxhdpi/images/loop00029.png and b/res-xxhdpi/images/loop00029.png differ diff --git a/res-xxhdpi/images/loop00030.png b/res-xxhdpi/images/loop00030.png index b85c7e35ce..d4692b6120 100644 Binary files a/res-xxhdpi/images/loop00030.png and b/res-xxhdpi/images/loop00030.png differ diff --git a/res-xxhdpi/images/loop00031.png b/res-xxhdpi/images/loop00031.png index 4d938e25a3..500e671391 100644 Binary files a/res-xxhdpi/images/loop00031.png and b/res-xxhdpi/images/loop00031.png differ diff --git a/res-xxhdpi/images/loop00032.png b/res-xxhdpi/images/loop00032.png index 0a1787602a..7a08acf6c4 100644 Binary files a/res-xxhdpi/images/loop00032.png and b/res-xxhdpi/images/loop00032.png differ diff --git a/res-xxhdpi/images/loop00033.png b/res-xxhdpi/images/loop00033.png index c8919c3126..8f78362812 100644 Binary files a/res-xxhdpi/images/loop00033.png and b/res-xxhdpi/images/loop00033.png differ diff --git a/res-xxhdpi/images/loop00034.png b/res-xxhdpi/images/loop00034.png index 1584d5dbbf..4c54233d6c 100644 Binary files a/res-xxhdpi/images/loop00034.png and b/res-xxhdpi/images/loop00034.png differ diff --git a/res-xxhdpi/images/loop00035.png b/res-xxhdpi/images/loop00035.png index 2220cd3c8d..9eef04e1e1 100644 Binary files a/res-xxhdpi/images/loop00035.png and b/res-xxhdpi/images/loop00035.png differ diff --git a/res-xxhdpi/images/loop00036.png b/res-xxhdpi/images/loop00036.png index 97ae5485d2..bdd243c915 100644 Binary files a/res-xxhdpi/images/loop00036.png and b/res-xxhdpi/images/loop00036.png differ diff --git a/res-xxhdpi/images/loop00037.png b/res-xxhdpi/images/loop00037.png index 84fca9758e..153c03b84d 100644 Binary files a/res-xxhdpi/images/loop00037.png and b/res-xxhdpi/images/loop00037.png differ diff --git a/res-xxhdpi/images/loop00038.png b/res-xxhdpi/images/loop00038.png index bba2181d60..5133221076 100644 Binary files a/res-xxhdpi/images/loop00038.png and b/res-xxhdpi/images/loop00038.png differ diff --git a/res-xxhdpi/images/loop00039.png b/res-xxhdpi/images/loop00039.png index 4659625fdd..3c0b17d7e0 100644 Binary files a/res-xxhdpi/images/loop00039.png and b/res-xxhdpi/images/loop00039.png differ diff --git a/res-xxhdpi/images/loop00040.png b/res-xxhdpi/images/loop00040.png index 6b3092ae57..fd5736e5d0 100644 Binary files a/res-xxhdpi/images/loop00040.png and b/res-xxhdpi/images/loop00040.png differ diff --git a/res-xxhdpi/images/loop00041.png b/res-xxhdpi/images/loop00041.png index 5b3cd16632..d2a7224767 100644 Binary files a/res-xxhdpi/images/loop00041.png and b/res-xxhdpi/images/loop00041.png differ diff --git a/res-xxhdpi/images/loop00042.png b/res-xxhdpi/images/loop00042.png index dbb8a7f3ac..806d58f8d0 100644 Binary files a/res-xxhdpi/images/loop00042.png and b/res-xxhdpi/images/loop00042.png differ diff --git a/res-xxhdpi/images/loop00043.png b/res-xxhdpi/images/loop00043.png index 582454237b..dc1106fefc 100644 Binary files a/res-xxhdpi/images/loop00043.png and b/res-xxhdpi/images/loop00043.png differ diff --git a/res-xxhdpi/images/loop00044.png b/res-xxhdpi/images/loop00044.png index d814246ada..15ecacd532 100644 Binary files a/res-xxhdpi/images/loop00044.png and b/res-xxhdpi/images/loop00044.png differ diff --git a/res-xxhdpi/images/loop00045.png b/res-xxhdpi/images/loop00045.png index e6a8d30893..23bbe3eb99 100644 Binary files a/res-xxhdpi/images/loop00045.png and b/res-xxhdpi/images/loop00045.png differ diff --git a/res-xxhdpi/images/loop00046.png b/res-xxhdpi/images/loop00046.png index 2f616bf01b..a0a2c061b2 100644 Binary files a/res-xxhdpi/images/loop00046.png and b/res-xxhdpi/images/loop00046.png differ diff --git a/res-xxhdpi/images/loop00047.png b/res-xxhdpi/images/loop00047.png index 39b74d95f4..cc2b433296 100644 Binary files a/res-xxhdpi/images/loop00047.png and b/res-xxhdpi/images/loop00047.png differ diff --git a/res-xxhdpi/images/loop00048.png b/res-xxhdpi/images/loop00048.png index 2a94b8c70a..a891efb3f6 100644 Binary files a/res-xxhdpi/images/loop00048.png and b/res-xxhdpi/images/loop00048.png differ diff --git a/res-xxhdpi/images/loop00049.png b/res-xxhdpi/images/loop00049.png index 6d86e2e145..da850b3d38 100644 Binary files a/res-xxhdpi/images/loop00049.png and b/res-xxhdpi/images/loop00049.png differ diff --git a/res-xxhdpi/images/loop00050.png b/res-xxhdpi/images/loop00050.png index c6cb34417b..1e90f69a5f 100644 Binary files a/res-xxhdpi/images/loop00050.png and b/res-xxhdpi/images/loop00050.png differ diff --git a/res-xxhdpi/images/loop00051.png b/res-xxhdpi/images/loop00051.png index dc510fa030..909634a1d7 100644 Binary files a/res-xxhdpi/images/loop00051.png and b/res-xxhdpi/images/loop00051.png differ diff --git a/res-xxhdpi/images/loop00052.png b/res-xxhdpi/images/loop00052.png index 9fdd3ad72f..e94465373d 100644 Binary files a/res-xxhdpi/images/loop00052.png and b/res-xxhdpi/images/loop00052.png differ diff --git a/res-xxhdpi/images/loop00053.png b/res-xxhdpi/images/loop00053.png index 8fff9cc122..81780a4b29 100644 Binary files a/res-xxhdpi/images/loop00053.png and b/res-xxhdpi/images/loop00053.png differ diff --git a/res-xxhdpi/images/loop00054.png b/res-xxhdpi/images/loop00054.png index 1f9dfaf3e7..a7a326bfee 100644 Binary files a/res-xxhdpi/images/loop00054.png and b/res-xxhdpi/images/loop00054.png differ diff --git a/res-xxhdpi/images/loop00055.png b/res-xxhdpi/images/loop00055.png index b0f6690703..b887b96931 100644 Binary files a/res-xxhdpi/images/loop00055.png and b/res-xxhdpi/images/loop00055.png differ diff --git a/res-xxhdpi/images/loop00056.png b/res-xxhdpi/images/loop00056.png index 79144d962e..420e8a1bab 100644 Binary files a/res-xxhdpi/images/loop00056.png and b/res-xxhdpi/images/loop00056.png differ diff --git a/res-xxhdpi/images/loop00057.png b/res-xxhdpi/images/loop00057.png index a451181c9b..e5a64f174f 100644 Binary files a/res-xxhdpi/images/loop00057.png and b/res-xxhdpi/images/loop00057.png differ diff --git a/res-xxhdpi/images/loop00058.png b/res-xxhdpi/images/loop00058.png index eb6af3af56..3988ee9136 100644 Binary files a/res-xxhdpi/images/loop00058.png and b/res-xxhdpi/images/loop00058.png differ diff --git a/res-xxhdpi/images/loop00059.png b/res-xxhdpi/images/loop00059.png index d9a976dfda..fa08507757 100644 Binary files a/res-xxhdpi/images/loop00059.png and b/res-xxhdpi/images/loop00059.png differ diff --git a/res-xxhdpi/images/loop00060.png b/res-xxhdpi/images/loop00060.png index 93ff5d9f03..b9e22eeb8d 100644 Binary files a/res-xxhdpi/images/loop00060.png and b/res-xxhdpi/images/loop00060.png differ diff --git a/res-xxhdpi/images/loop00061.png b/res-xxhdpi/images/loop00061.png index 13dcd2ab95..e5b0123861 100644 Binary files a/res-xxhdpi/images/loop00061.png and b/res-xxhdpi/images/loop00061.png differ diff --git a/res-xxhdpi/images/loop00062.png b/res-xxhdpi/images/loop00062.png index 1ffc8f885f..1651c70b39 100644 Binary files a/res-xxhdpi/images/loop00062.png and b/res-xxhdpi/images/loop00062.png differ diff --git a/res-xxhdpi/images/loop00063.png b/res-xxhdpi/images/loop00063.png index 6ec7dae5c3..127add21ad 100644 Binary files a/res-xxhdpi/images/loop00063.png and b/res-xxhdpi/images/loop00063.png differ diff --git a/res-xxhdpi/images/loop00064.png b/res-xxhdpi/images/loop00064.png index 3c5bcc36e7..2c9a42ead8 100644 Binary files a/res-xxhdpi/images/loop00064.png and b/res-xxhdpi/images/loop00064.png differ diff --git a/res-xxhdpi/images/loop00065.png b/res-xxhdpi/images/loop00065.png index 541fa88936..1f44c877a3 100644 Binary files a/res-xxhdpi/images/loop00065.png and b/res-xxhdpi/images/loop00065.png differ diff --git a/res-xxhdpi/images/loop00066.png b/res-xxhdpi/images/loop00066.png index e65ca8ff90..c27ed05002 100644 Binary files a/res-xxhdpi/images/loop00066.png and b/res-xxhdpi/images/loop00066.png differ diff --git a/res-xxhdpi/images/loop00067.png b/res-xxhdpi/images/loop00067.png index c93125b774..50c18a3d4e 100644 Binary files a/res-xxhdpi/images/loop00067.png and b/res-xxhdpi/images/loop00067.png differ diff --git a/res-xxhdpi/images/loop00068.png b/res-xxhdpi/images/loop00068.png index f7ef8e93ef..a6c0d3ee0b 100644 Binary files a/res-xxhdpi/images/loop00068.png and b/res-xxhdpi/images/loop00068.png differ diff --git a/res-xxhdpi/images/loop00069.png b/res-xxhdpi/images/loop00069.png index e3a16c5078..9c17c30758 100644 Binary files a/res-xxhdpi/images/loop00069.png and b/res-xxhdpi/images/loop00069.png differ diff --git a/res-xxhdpi/images/loop00070.png b/res-xxhdpi/images/loop00070.png index 24cfdb111f..4c7fd349f3 100644 Binary files a/res-xxhdpi/images/loop00070.png and b/res-xxhdpi/images/loop00070.png differ diff --git a/res-xxhdpi/images/loop00071.png b/res-xxhdpi/images/loop00071.png index efffad4704..6fcb46d91b 100644 Binary files a/res-xxhdpi/images/loop00071.png and b/res-xxhdpi/images/loop00071.png differ diff --git a/res-xxhdpi/images/loop00072.png b/res-xxhdpi/images/loop00072.png index 63d62f3681..a6cb6b757b 100644 Binary files a/res-xxhdpi/images/loop00072.png and b/res-xxhdpi/images/loop00072.png differ diff --git a/res-xxhdpi/images/loop00073.png b/res-xxhdpi/images/loop00073.png index de0f41041f..d55855517d 100644 Binary files a/res-xxhdpi/images/loop00073.png and b/res-xxhdpi/images/loop00073.png differ diff --git a/res-xxhdpi/images/loop00074.png b/res-xxhdpi/images/loop00074.png index 45c9a74bc7..90a672c660 100644 Binary files a/res-xxhdpi/images/loop00074.png and b/res-xxhdpi/images/loop00074.png differ diff --git a/res-xxhdpi/images/loop00075.png b/res-xxhdpi/images/loop00075.png index a26893783a..5143985313 100644 Binary files a/res-xxhdpi/images/loop00075.png and b/res-xxhdpi/images/loop00075.png differ diff --git a/res-xxhdpi/images/loop00076.png b/res-xxhdpi/images/loop00076.png index 9edd577e74..c99b7cb7d0 100644 Binary files a/res-xxhdpi/images/loop00076.png and b/res-xxhdpi/images/loop00076.png differ diff --git a/res-xxhdpi/images/loop00077.png b/res-xxhdpi/images/loop00077.png index 23a7cc77c4..e5f1268492 100644 Binary files a/res-xxhdpi/images/loop00077.png and b/res-xxhdpi/images/loop00077.png differ diff --git a/res-xxhdpi/images/loop00078.png b/res-xxhdpi/images/loop00078.png index 67dbf2d062..da180ce3c9 100644 Binary files a/res-xxhdpi/images/loop00078.png and b/res-xxhdpi/images/loop00078.png differ diff --git a/res-xxhdpi/images/loop00079.png b/res-xxhdpi/images/loop00079.png index 0ef021fafb..ece8a33d4f 100644 Binary files a/res-xxhdpi/images/loop00079.png and b/res-xxhdpi/images/loop00079.png differ diff --git a/res-xxhdpi/images/loop00080.png b/res-xxhdpi/images/loop00080.png index 0de307b7a9..be89e166be 100644 Binary files a/res-xxhdpi/images/loop00080.png and b/res-xxhdpi/images/loop00080.png differ diff --git a/res-xxhdpi/images/loop00081.png b/res-xxhdpi/images/loop00081.png index cc31e92013..77e89ea1c1 100644 Binary files a/res-xxhdpi/images/loop00081.png and b/res-xxhdpi/images/loop00081.png differ diff --git a/res-xxhdpi/images/loop00082.png b/res-xxhdpi/images/loop00082.png index 6809fa37bf..94771b2043 100644 Binary files a/res-xxhdpi/images/loop00082.png and b/res-xxhdpi/images/loop00082.png differ diff --git a/res-xxhdpi/images/loop00083.png b/res-xxhdpi/images/loop00083.png index c3e3a5827b..b76e8f5797 100644 Binary files a/res-xxhdpi/images/loop00083.png and b/res-xxhdpi/images/loop00083.png differ diff --git a/res-xxhdpi/images/loop00084.png b/res-xxhdpi/images/loop00084.png index fc0df350f6..987fc13f2b 100644 Binary files a/res-xxhdpi/images/loop00084.png and b/res-xxhdpi/images/loop00084.png differ diff --git a/res-xxhdpi/images/loop00085.png b/res-xxhdpi/images/loop00085.png index 38baf7e71e..6bb681f0b9 100644 Binary files a/res-xxhdpi/images/loop00085.png and b/res-xxhdpi/images/loop00085.png differ diff --git a/res-xxhdpi/images/loop00086.png b/res-xxhdpi/images/loop00086.png index c6616ebe34..d31d18a6ca 100644 Binary files a/res-xxhdpi/images/loop00086.png and b/res-xxhdpi/images/loop00086.png differ diff --git a/res-xxhdpi/images/loop00087.png b/res-xxhdpi/images/loop00087.png index 2e6b715cd5..797fe7c790 100644 Binary files a/res-xxhdpi/images/loop00087.png and b/res-xxhdpi/images/loop00087.png differ diff --git a/res-xxhdpi/images/loop00088.png b/res-xxhdpi/images/loop00088.png index 660d0df8f3..a9764c665b 100644 Binary files a/res-xxhdpi/images/loop00088.png and b/res-xxhdpi/images/loop00088.png differ diff --git a/res-xxhdpi/images/loop00089.png b/res-xxhdpi/images/loop00089.png index a6b82c5889..b0384ec509 100644 Binary files a/res-xxhdpi/images/loop00089.png and b/res-xxhdpi/images/loop00089.png differ diff --git a/res-xxhdpi/images/loop00090.png b/res-xxhdpi/images/loop00090.png index 003c2f8752..f723d82d54 100644 Binary files a/res-xxhdpi/images/loop00090.png and b/res-xxhdpi/images/loop00090.png differ diff --git a/res-xxxhdpi/images/fastbootd.png b/res-xxxhdpi/images/fastbootd.png new file mode 100644 index 0000000000..a878b1f060 Binary files /dev/null and b/res-xxxhdpi/images/fastbootd.png differ diff --git a/res-xxxhdpi/images/loop00000.png b/res-xxxhdpi/images/loop00000.png index d6640c5409..3bca5997d2 100644 Binary files a/res-xxxhdpi/images/loop00000.png and b/res-xxxhdpi/images/loop00000.png differ diff --git a/res-xxxhdpi/images/loop00001.png b/res-xxxhdpi/images/loop00001.png index e1b82b9389..665cd8f36d 100644 Binary files a/res-xxxhdpi/images/loop00001.png and b/res-xxxhdpi/images/loop00001.png differ diff --git a/res-xxxhdpi/images/loop00002.png b/res-xxxhdpi/images/loop00002.png index 9b8a381f42..4f87850655 100644 Binary files a/res-xxxhdpi/images/loop00002.png and b/res-xxxhdpi/images/loop00002.png differ diff --git a/res-xxxhdpi/images/loop00003.png b/res-xxxhdpi/images/loop00003.png index b4d244c783..88bdf2de2b 100644 Binary files a/res-xxxhdpi/images/loop00003.png and b/res-xxxhdpi/images/loop00003.png differ diff --git a/res-xxxhdpi/images/loop00004.png b/res-xxxhdpi/images/loop00004.png index c9231596e9..db6348e417 100644 Binary files a/res-xxxhdpi/images/loop00004.png and b/res-xxxhdpi/images/loop00004.png differ diff --git a/res-xxxhdpi/images/loop00005.png b/res-xxxhdpi/images/loop00005.png index ed739fae43..4ec36c2ef1 100644 Binary files a/res-xxxhdpi/images/loop00005.png and b/res-xxxhdpi/images/loop00005.png differ diff --git a/res-xxxhdpi/images/loop00006.png b/res-xxxhdpi/images/loop00006.png index 68116922b3..a00f3065d4 100644 Binary files a/res-xxxhdpi/images/loop00006.png and b/res-xxxhdpi/images/loop00006.png differ diff --git a/res-xxxhdpi/images/loop00007.png b/res-xxxhdpi/images/loop00007.png index bbeee01118..d140655d8c 100644 Binary files a/res-xxxhdpi/images/loop00007.png and b/res-xxxhdpi/images/loop00007.png differ diff --git a/res-xxxhdpi/images/loop00008.png b/res-xxxhdpi/images/loop00008.png index 2c28032e7e..019843fe3e 100644 Binary files a/res-xxxhdpi/images/loop00008.png and b/res-xxxhdpi/images/loop00008.png differ diff --git a/res-xxxhdpi/images/loop00009.png b/res-xxxhdpi/images/loop00009.png index 4ea659cfab..5e9e7bb3ff 100644 Binary files a/res-xxxhdpi/images/loop00009.png and b/res-xxxhdpi/images/loop00009.png differ diff --git a/res-xxxhdpi/images/loop00010.png b/res-xxxhdpi/images/loop00010.png index 45928bc4c7..52ae668e8c 100644 Binary files a/res-xxxhdpi/images/loop00010.png and b/res-xxxhdpi/images/loop00010.png differ diff --git a/res-xxxhdpi/images/loop00011.png b/res-xxxhdpi/images/loop00011.png index 8a8f2f7be4..bd318f6336 100644 Binary files a/res-xxxhdpi/images/loop00011.png and b/res-xxxhdpi/images/loop00011.png differ diff --git a/res-xxxhdpi/images/loop00012.png b/res-xxxhdpi/images/loop00012.png index 1714d1be05..114e326e14 100644 Binary files a/res-xxxhdpi/images/loop00012.png and b/res-xxxhdpi/images/loop00012.png differ diff --git a/res-xxxhdpi/images/loop00013.png b/res-xxxhdpi/images/loop00013.png index 18ab24f2d5..8890e6df9b 100644 Binary files a/res-xxxhdpi/images/loop00013.png and b/res-xxxhdpi/images/loop00013.png differ diff --git a/res-xxxhdpi/images/loop00014.png b/res-xxxhdpi/images/loop00014.png index 5099bc5023..72e6b128e9 100644 Binary files a/res-xxxhdpi/images/loop00014.png and b/res-xxxhdpi/images/loop00014.png differ diff --git a/res-xxxhdpi/images/loop00015.png b/res-xxxhdpi/images/loop00015.png index b7e68683d4..cbf0b1b0f9 100644 Binary files a/res-xxxhdpi/images/loop00015.png and b/res-xxxhdpi/images/loop00015.png differ diff --git a/res-xxxhdpi/images/loop00016.png b/res-xxxhdpi/images/loop00016.png index bc1337574d..b9f16aa99f 100644 Binary files a/res-xxxhdpi/images/loop00016.png and b/res-xxxhdpi/images/loop00016.png differ diff --git a/res-xxxhdpi/images/loop00017.png b/res-xxxhdpi/images/loop00017.png index 8a9bd869d2..536201a876 100644 Binary files a/res-xxxhdpi/images/loop00017.png and b/res-xxxhdpi/images/loop00017.png differ diff --git a/res-xxxhdpi/images/loop00018.png b/res-xxxhdpi/images/loop00018.png index 2150d630e5..6ef1b32925 100644 Binary files a/res-xxxhdpi/images/loop00018.png and b/res-xxxhdpi/images/loop00018.png differ diff --git a/res-xxxhdpi/images/loop00019.png b/res-xxxhdpi/images/loop00019.png index ec0cc589c8..63ffe0468a 100644 Binary files a/res-xxxhdpi/images/loop00019.png and b/res-xxxhdpi/images/loop00019.png differ diff --git a/res-xxxhdpi/images/loop00020.png b/res-xxxhdpi/images/loop00020.png index 6596ea2159..5e28c00a95 100644 Binary files a/res-xxxhdpi/images/loop00020.png and b/res-xxxhdpi/images/loop00020.png differ diff --git a/res-xxxhdpi/images/loop00021.png b/res-xxxhdpi/images/loop00021.png index c874649cbd..0e085f4af4 100644 Binary files a/res-xxxhdpi/images/loop00021.png and b/res-xxxhdpi/images/loop00021.png differ diff --git a/res-xxxhdpi/images/loop00022.png b/res-xxxhdpi/images/loop00022.png index d5f834d45c..9559415eb8 100644 Binary files a/res-xxxhdpi/images/loop00022.png and b/res-xxxhdpi/images/loop00022.png differ diff --git a/res-xxxhdpi/images/loop00023.png b/res-xxxhdpi/images/loop00023.png index eb8af82fa4..3cc2d5cb61 100644 Binary files a/res-xxxhdpi/images/loop00023.png and b/res-xxxhdpi/images/loop00023.png differ diff --git a/res-xxxhdpi/images/loop00024.png b/res-xxxhdpi/images/loop00024.png index 7da550688b..fd4addc90a 100644 Binary files a/res-xxxhdpi/images/loop00024.png and b/res-xxxhdpi/images/loop00024.png differ diff --git a/res-xxxhdpi/images/loop00025.png b/res-xxxhdpi/images/loop00025.png index 884414973a..69fef865ed 100644 Binary files a/res-xxxhdpi/images/loop00025.png and b/res-xxxhdpi/images/loop00025.png differ diff --git a/res-xxxhdpi/images/loop00026.png b/res-xxxhdpi/images/loop00026.png index ee36358aa6..a4ff78f786 100644 Binary files a/res-xxxhdpi/images/loop00026.png and b/res-xxxhdpi/images/loop00026.png differ diff --git a/res-xxxhdpi/images/loop00027.png b/res-xxxhdpi/images/loop00027.png index 0299dae077..6adc3384a7 100644 Binary files a/res-xxxhdpi/images/loop00027.png and b/res-xxxhdpi/images/loop00027.png differ diff --git a/res-xxxhdpi/images/loop00028.png b/res-xxxhdpi/images/loop00028.png index a8f5cef33a..86f2d0ef70 100644 Binary files a/res-xxxhdpi/images/loop00028.png and b/res-xxxhdpi/images/loop00028.png differ diff --git a/res-xxxhdpi/images/loop00029.png b/res-xxxhdpi/images/loop00029.png index 6b2ab3fcd6..4ce7d7cdb4 100644 Binary files a/res-xxxhdpi/images/loop00029.png and b/res-xxxhdpi/images/loop00029.png differ diff --git a/res-xxxhdpi/images/loop00030.png b/res-xxxhdpi/images/loop00030.png index 2d5b48de3a..0599603eaf 100644 Binary files a/res-xxxhdpi/images/loop00030.png and b/res-xxxhdpi/images/loop00030.png differ diff --git a/res-xxxhdpi/images/loop00031.png b/res-xxxhdpi/images/loop00031.png index 40c4296cd7..7f96ed5f63 100644 Binary files a/res-xxxhdpi/images/loop00031.png and b/res-xxxhdpi/images/loop00031.png differ diff --git a/res-xxxhdpi/images/loop00032.png b/res-xxxhdpi/images/loop00032.png index f130b0ab95..6f66c5dca9 100644 Binary files a/res-xxxhdpi/images/loop00032.png and b/res-xxxhdpi/images/loop00032.png differ diff --git a/res-xxxhdpi/images/loop00033.png b/res-xxxhdpi/images/loop00033.png index 24151ba620..4052bb2385 100644 Binary files a/res-xxxhdpi/images/loop00033.png and b/res-xxxhdpi/images/loop00033.png differ diff --git a/res-xxxhdpi/images/loop00034.png b/res-xxxhdpi/images/loop00034.png index f74f89555b..d751eefff7 100644 Binary files a/res-xxxhdpi/images/loop00034.png and b/res-xxxhdpi/images/loop00034.png differ diff --git a/res-xxxhdpi/images/loop00035.png b/res-xxxhdpi/images/loop00035.png index 4a0f8053f9..4d285912ad 100644 Binary files a/res-xxxhdpi/images/loop00035.png and b/res-xxxhdpi/images/loop00035.png differ diff --git a/res-xxxhdpi/images/loop00036.png b/res-xxxhdpi/images/loop00036.png index 74658622dc..8f662e8f98 100644 Binary files a/res-xxxhdpi/images/loop00036.png and b/res-xxxhdpi/images/loop00036.png differ diff --git a/res-xxxhdpi/images/loop00037.png b/res-xxxhdpi/images/loop00037.png index 5d10d10a57..de108e9c8b 100644 Binary files a/res-xxxhdpi/images/loop00037.png and b/res-xxxhdpi/images/loop00037.png differ diff --git a/res-xxxhdpi/images/loop00038.png b/res-xxxhdpi/images/loop00038.png index 15d5db2f27..812ff368e8 100644 Binary files a/res-xxxhdpi/images/loop00038.png and b/res-xxxhdpi/images/loop00038.png differ diff --git a/res-xxxhdpi/images/loop00039.png b/res-xxxhdpi/images/loop00039.png index b92d49d082..e139c4398d 100644 Binary files a/res-xxxhdpi/images/loop00039.png and b/res-xxxhdpi/images/loop00039.png differ diff --git a/res-xxxhdpi/images/loop00040.png b/res-xxxhdpi/images/loop00040.png index 5c19c0254c..0fc29f1d08 100644 Binary files a/res-xxxhdpi/images/loop00040.png and b/res-xxxhdpi/images/loop00040.png differ diff --git a/res-xxxhdpi/images/loop00041.png b/res-xxxhdpi/images/loop00041.png index 2c9d406b74..f9b582d40d 100644 Binary files a/res-xxxhdpi/images/loop00041.png and b/res-xxxhdpi/images/loop00041.png differ diff --git a/res-xxxhdpi/images/loop00042.png b/res-xxxhdpi/images/loop00042.png index bb24da5b48..05678d7e29 100644 Binary files a/res-xxxhdpi/images/loop00042.png and b/res-xxxhdpi/images/loop00042.png differ diff --git a/res-xxxhdpi/images/loop00043.png b/res-xxxhdpi/images/loop00043.png index 0a9efd8a29..eef9bab829 100644 Binary files a/res-xxxhdpi/images/loop00043.png and b/res-xxxhdpi/images/loop00043.png differ diff --git a/res-xxxhdpi/images/loop00044.png b/res-xxxhdpi/images/loop00044.png index 70e1cbc844..9a1060e528 100644 Binary files a/res-xxxhdpi/images/loop00044.png and b/res-xxxhdpi/images/loop00044.png differ diff --git a/res-xxxhdpi/images/loop00045.png b/res-xxxhdpi/images/loop00045.png index 0ecb787b93..41ec4ecd06 100644 Binary files a/res-xxxhdpi/images/loop00045.png and b/res-xxxhdpi/images/loop00045.png differ diff --git a/res-xxxhdpi/images/loop00046.png b/res-xxxhdpi/images/loop00046.png index c2c425abdc..61a87c4724 100644 Binary files a/res-xxxhdpi/images/loop00046.png and b/res-xxxhdpi/images/loop00046.png differ diff --git a/res-xxxhdpi/images/loop00047.png b/res-xxxhdpi/images/loop00047.png index 71812b3a72..1bb7e86e20 100644 Binary files a/res-xxxhdpi/images/loop00047.png and b/res-xxxhdpi/images/loop00047.png differ diff --git a/res-xxxhdpi/images/loop00048.png b/res-xxxhdpi/images/loop00048.png index 6ef44ce222..b7d690d3ca 100644 Binary files a/res-xxxhdpi/images/loop00048.png and b/res-xxxhdpi/images/loop00048.png differ diff --git a/res-xxxhdpi/images/loop00049.png b/res-xxxhdpi/images/loop00049.png index 5c7b1c50a4..26ad6c280a 100644 Binary files a/res-xxxhdpi/images/loop00049.png and b/res-xxxhdpi/images/loop00049.png differ diff --git a/res-xxxhdpi/images/loop00050.png b/res-xxxhdpi/images/loop00050.png index 10dcf213cd..555d66997e 100644 Binary files a/res-xxxhdpi/images/loop00050.png and b/res-xxxhdpi/images/loop00050.png differ diff --git a/res-xxxhdpi/images/loop00051.png b/res-xxxhdpi/images/loop00051.png index e850b32aa0..f7defeb939 100644 Binary files a/res-xxxhdpi/images/loop00051.png and b/res-xxxhdpi/images/loop00051.png differ diff --git a/res-xxxhdpi/images/loop00052.png b/res-xxxhdpi/images/loop00052.png index 7abf444a33..915422d311 100644 Binary files a/res-xxxhdpi/images/loop00052.png and b/res-xxxhdpi/images/loop00052.png differ diff --git a/res-xxxhdpi/images/loop00053.png b/res-xxxhdpi/images/loop00053.png index f680849af2..2dd9b81669 100644 Binary files a/res-xxxhdpi/images/loop00053.png and b/res-xxxhdpi/images/loop00053.png differ diff --git a/res-xxxhdpi/images/loop00054.png b/res-xxxhdpi/images/loop00054.png index 012c14dee9..e5efd001fc 100644 Binary files a/res-xxxhdpi/images/loop00054.png and b/res-xxxhdpi/images/loop00054.png differ diff --git a/res-xxxhdpi/images/loop00055.png b/res-xxxhdpi/images/loop00055.png index ae335dbb9a..aa9e379f6a 100644 Binary files a/res-xxxhdpi/images/loop00055.png and b/res-xxxhdpi/images/loop00055.png differ diff --git a/res-xxxhdpi/images/loop00056.png b/res-xxxhdpi/images/loop00056.png index 8e928ea85f..d5f355c881 100644 Binary files a/res-xxxhdpi/images/loop00056.png and b/res-xxxhdpi/images/loop00056.png differ diff --git a/res-xxxhdpi/images/loop00057.png b/res-xxxhdpi/images/loop00057.png index c23d4f0d73..1f06b377ee 100644 Binary files a/res-xxxhdpi/images/loop00057.png and b/res-xxxhdpi/images/loop00057.png differ diff --git a/res-xxxhdpi/images/loop00058.png b/res-xxxhdpi/images/loop00058.png index d5144aa699..34dc3725cb 100644 Binary files a/res-xxxhdpi/images/loop00058.png and b/res-xxxhdpi/images/loop00058.png differ diff --git a/res-xxxhdpi/images/loop00059.png b/res-xxxhdpi/images/loop00059.png index f8f3a7c16d..befec310bb 100644 Binary files a/res-xxxhdpi/images/loop00059.png and b/res-xxxhdpi/images/loop00059.png differ diff --git a/res-xxxhdpi/images/loop00060.png b/res-xxxhdpi/images/loop00060.png index 8894a236fc..0bfb463286 100644 Binary files a/res-xxxhdpi/images/loop00060.png and b/res-xxxhdpi/images/loop00060.png differ diff --git a/res-xxxhdpi/images/loop00061.png b/res-xxxhdpi/images/loop00061.png index 1c33e84fde..ec622a9967 100644 Binary files a/res-xxxhdpi/images/loop00061.png and b/res-xxxhdpi/images/loop00061.png differ diff --git a/res-xxxhdpi/images/loop00062.png b/res-xxxhdpi/images/loop00062.png index c2242ff496..5f0d50ccbc 100644 Binary files a/res-xxxhdpi/images/loop00062.png and b/res-xxxhdpi/images/loop00062.png differ diff --git a/res-xxxhdpi/images/loop00063.png b/res-xxxhdpi/images/loop00063.png index c357ffaae9..ce967bddff 100644 Binary files a/res-xxxhdpi/images/loop00063.png and b/res-xxxhdpi/images/loop00063.png differ diff --git a/res-xxxhdpi/images/loop00064.png b/res-xxxhdpi/images/loop00064.png index f9466997e4..4b2f5d7744 100644 Binary files a/res-xxxhdpi/images/loop00064.png and b/res-xxxhdpi/images/loop00064.png differ diff --git a/res-xxxhdpi/images/loop00065.png b/res-xxxhdpi/images/loop00065.png index 52d976b25c..54ae1966a5 100644 Binary files a/res-xxxhdpi/images/loop00065.png and b/res-xxxhdpi/images/loop00065.png differ diff --git a/res-xxxhdpi/images/loop00066.png b/res-xxxhdpi/images/loop00066.png index cf37f2f977..1c5a4d43fb 100644 Binary files a/res-xxxhdpi/images/loop00066.png and b/res-xxxhdpi/images/loop00066.png differ diff --git a/res-xxxhdpi/images/loop00067.png b/res-xxxhdpi/images/loop00067.png index d8a1e78358..7a016e700e 100644 Binary files a/res-xxxhdpi/images/loop00067.png and b/res-xxxhdpi/images/loop00067.png differ diff --git a/res-xxxhdpi/images/loop00068.png b/res-xxxhdpi/images/loop00068.png index 8bbaf020d3..39dbbda2cb 100644 Binary files a/res-xxxhdpi/images/loop00068.png and b/res-xxxhdpi/images/loop00068.png differ diff --git a/res-xxxhdpi/images/loop00069.png b/res-xxxhdpi/images/loop00069.png index 99d1072cd3..ea2a1e97e6 100644 Binary files a/res-xxxhdpi/images/loop00069.png and b/res-xxxhdpi/images/loop00069.png differ diff --git a/res-xxxhdpi/images/loop00070.png b/res-xxxhdpi/images/loop00070.png index bd8979e73e..303ee3f886 100644 Binary files a/res-xxxhdpi/images/loop00070.png and b/res-xxxhdpi/images/loop00070.png differ diff --git a/res-xxxhdpi/images/loop00071.png b/res-xxxhdpi/images/loop00071.png index e823dccce3..74052b1f74 100644 Binary files a/res-xxxhdpi/images/loop00071.png and b/res-xxxhdpi/images/loop00071.png differ diff --git a/res-xxxhdpi/images/loop00072.png b/res-xxxhdpi/images/loop00072.png index 475190fd06..8c2a549094 100644 Binary files a/res-xxxhdpi/images/loop00072.png and b/res-xxxhdpi/images/loop00072.png differ diff --git a/res-xxxhdpi/images/loop00073.png b/res-xxxhdpi/images/loop00073.png index 84c4874c93..6fdb8e58be 100644 Binary files a/res-xxxhdpi/images/loop00073.png and b/res-xxxhdpi/images/loop00073.png differ diff --git a/res-xxxhdpi/images/loop00074.png b/res-xxxhdpi/images/loop00074.png index e2d90a2926..6afb23697c 100644 Binary files a/res-xxxhdpi/images/loop00074.png and b/res-xxxhdpi/images/loop00074.png differ diff --git a/res-xxxhdpi/images/loop00075.png b/res-xxxhdpi/images/loop00075.png index ff13dfeb8b..fc9aa7769e 100644 Binary files a/res-xxxhdpi/images/loop00075.png and b/res-xxxhdpi/images/loop00075.png differ diff --git a/res-xxxhdpi/images/loop00076.png b/res-xxxhdpi/images/loop00076.png index 01886ae458..5a4929399d 100644 Binary files a/res-xxxhdpi/images/loop00076.png and b/res-xxxhdpi/images/loop00076.png differ diff --git a/res-xxxhdpi/images/loop00077.png b/res-xxxhdpi/images/loop00077.png index 4bac4ea9b6..4739c13054 100644 Binary files a/res-xxxhdpi/images/loop00077.png and b/res-xxxhdpi/images/loop00077.png differ diff --git a/res-xxxhdpi/images/loop00078.png b/res-xxxhdpi/images/loop00078.png index 6ced1a27ff..9d791148d2 100644 Binary files a/res-xxxhdpi/images/loop00078.png and b/res-xxxhdpi/images/loop00078.png differ diff --git a/res-xxxhdpi/images/loop00079.png b/res-xxxhdpi/images/loop00079.png index f7baed340a..59be263c52 100644 Binary files a/res-xxxhdpi/images/loop00079.png and b/res-xxxhdpi/images/loop00079.png differ diff --git a/res-xxxhdpi/images/loop00080.png b/res-xxxhdpi/images/loop00080.png index fbb0a138d5..7bcfbc9622 100644 Binary files a/res-xxxhdpi/images/loop00080.png and b/res-xxxhdpi/images/loop00080.png differ diff --git a/res-xxxhdpi/images/loop00081.png b/res-xxxhdpi/images/loop00081.png index 3fc7a4959c..3393f8352b 100644 Binary files a/res-xxxhdpi/images/loop00081.png and b/res-xxxhdpi/images/loop00081.png differ diff --git a/res-xxxhdpi/images/loop00082.png b/res-xxxhdpi/images/loop00082.png index 3114002bcd..f8d3013ad6 100644 Binary files a/res-xxxhdpi/images/loop00082.png and b/res-xxxhdpi/images/loop00082.png differ diff --git a/res-xxxhdpi/images/loop00083.png b/res-xxxhdpi/images/loop00083.png index df1b8301aa..733db7b93a 100644 Binary files a/res-xxxhdpi/images/loop00083.png and b/res-xxxhdpi/images/loop00083.png differ diff --git a/res-xxxhdpi/images/loop00084.png b/res-xxxhdpi/images/loop00084.png index 11a72f2e34..d71a10ccb5 100644 Binary files a/res-xxxhdpi/images/loop00084.png and b/res-xxxhdpi/images/loop00084.png differ diff --git a/res-xxxhdpi/images/loop00085.png b/res-xxxhdpi/images/loop00085.png index ba0a43ecc6..0ea2a19ac7 100644 Binary files a/res-xxxhdpi/images/loop00085.png and b/res-xxxhdpi/images/loop00085.png differ diff --git a/res-xxxhdpi/images/loop00086.png b/res-xxxhdpi/images/loop00086.png index c4111b2a37..d2ea05ea99 100644 Binary files a/res-xxxhdpi/images/loop00086.png and b/res-xxxhdpi/images/loop00086.png differ diff --git a/res-xxxhdpi/images/loop00087.png b/res-xxxhdpi/images/loop00087.png index 13b83c7e5f..f7814f0916 100644 Binary files a/res-xxxhdpi/images/loop00087.png and b/res-xxxhdpi/images/loop00087.png differ diff --git a/res-xxxhdpi/images/loop00088.png b/res-xxxhdpi/images/loop00088.png index e7d9d6d7ac..b82ab4c5eb 100644 Binary files a/res-xxxhdpi/images/loop00088.png and b/res-xxxhdpi/images/loop00088.png differ diff --git a/res-xxxhdpi/images/loop00089.png b/res-xxxhdpi/images/loop00089.png index fd1951c6f1..ed354e6f65 100644 Binary files a/res-xxxhdpi/images/loop00089.png and b/res-xxxhdpi/images/loop00089.png differ diff --git a/res-xxxhdpi/images/loop00090.png b/res-xxxhdpi/images/loop00090.png index d6640c5409..3bca5997d2 100644 Binary files a/res-xxxhdpi/images/loop00090.png and b/res-xxxhdpi/images/loop00090.png differ diff --git a/roots.cpp b/roots.cpp deleted file mode 100644 index 184e7992b6..0000000000 --- a/roots.cpp +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "roots.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "mounts.h" - -static struct fstab* fstab = nullptr; - -extern struct selabel_handle* sehandle; - -void load_volume_table() { - fstab = fs_mgr_read_fstab_default(); - if (!fstab) { - LOG(ERROR) << "Failed to read default fstab"; - return; - } - - int ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk"); - if (ret == -1) { - LOG(ERROR) << "Failed to add /tmp entry to fstab"; - fs_mgr_free_fstab(fstab); - fstab = nullptr; - return; - } - - printf("recovery filesystem table\n"); - printf("=========================\n"); - for (int i = 0; i < fstab->num_entries; ++i) { - const Volume* v = &fstab->recs[i]; - printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, v->blk_device, v->length); - } - printf("\n"); -} - -Volume* volume_for_mount_point(const std::string& mount_point) { - return fs_mgr_get_entry_for_mount_point(fstab, mount_point); -} - -// Finds the volume specified by the given path. fs_mgr_get_entry_for_mount_point() does exact match -// only, so it attempts the prefixes recursively (e.g. "/cache/recovery/last_log", -// "/cache/recovery", "/cache", "/" for a given path of "/cache/recovery/last_log") and returns the -// first match or nullptr. -static Volume* volume_for_path(const char* path) { - if (path == nullptr || path[0] == '\0') return nullptr; - std::string str(path); - while (true) { - Volume* result = fs_mgr_get_entry_for_mount_point(fstab, str); - if (result != nullptr || str == "/") { - return result; - } - size_t slash = str.find_last_of('/'); - if (slash == std::string::npos) return nullptr; - if (slash == 0) { - str = "/"; - } else { - str = str.substr(0, slash); - } - } - return nullptr; -} - -// Mount the volume specified by path at the given mount_point. -int ensure_path_mounted_at(const char* path, const char* mount_point) { - Volume* v = volume_for_path(path); - if (v == nullptr) { - LOG(ERROR) << "unknown volume for path [" << path << "]"; - return -1; - } - if (strcmp(v->fs_type, "ramdisk") == 0) { - // The ramdisk is always mounted. - return 0; - } - - if (!scan_mounted_volumes()) { - LOG(ERROR) << "Failed to scan mounted volumes"; - return -1; - } - - if (!mount_point) { - mount_point = v->mount_point; - } - - const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point); - if (mv != nullptr) { - // Volume is already mounted. - return 0; - } - - mkdir(mount_point, 0755); // in case it doesn't already exist - - if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "squashfs") == 0 || - strcmp(v->fs_type, "vfat") == 0) { - int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options); - if (result == -1 && fs_mgr_is_formattable(v)) { - PLOG(ERROR) << "Failed to mount " << mount_point << "; formatting"; - bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer"); - if (fs_mgr_do_format(v, crypt_footer) == 0) { - result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options); - } else { - PLOG(ERROR) << "Failed to format " << mount_point; - return -1; - } - } - - if (result == -1) { - PLOG(ERROR) << "Failed to mount " << mount_point; - return -1; - } - return 0; - } - - LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point; - return -1; -} - -int ensure_path_mounted(const char* path) { - // Mount at the default mount point. - return ensure_path_mounted_at(path, nullptr); -} - -int ensure_path_unmounted(const char* path) { - const Volume* v = volume_for_path(path); - if (v == nullptr) { - LOG(ERROR) << "unknown volume for path [" << path << "]"; - return -1; - } - if (strcmp(v->fs_type, "ramdisk") == 0) { - // The ramdisk is always mounted; you can't unmount it. - return -1; - } - - if (!scan_mounted_volumes()) { - LOG(ERROR) << "Failed to scan mounted volumes"; - return -1; - } - - MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point); - if (mv == nullptr) { - // Volume is already unmounted. - return 0; - } - - return unmount_mounted_volume(mv); -} - -static int exec_cmd(const std::vector& args) { - CHECK_NE(static_cast(0), args.size()); - - std::vector argv(args.size()); - std::transform(args.cbegin(), args.cend(), argv.begin(), - [](const std::string& arg) { return const_cast(arg.c_str()); }); - argv.push_back(nullptr); - - pid_t child; - if ((child = fork()) == 0) { - execv(argv[0], argv.data()); - _exit(EXIT_FAILURE); - } - - int status; - waitpid(child, &status, 0); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status); - } - return WEXITSTATUS(status); -} - -static int64_t get_file_size(int fd, uint64_t reserve_len) { - struct stat buf; - int ret = fstat(fd, &buf); - if (ret) return 0; - - int64_t computed_size; - if (S_ISREG(buf.st_mode)) { - computed_size = buf.st_size - reserve_len; - } else if (S_ISBLK(buf.st_mode)) { - uint64_t block_device_size = get_block_device_size(fd); - if (block_device_size < reserve_len || - block_device_size > std::numeric_limits::max()) { - computed_size = 0; - } else { - computed_size = block_device_size - reserve_len; - } - } else { - computed_size = 0; - } - - return computed_size; -} - -int format_volume(const char* volume, const char* directory) { - const Volume* v = volume_for_path(volume); - if (v == nullptr) { - LOG(ERROR) << "unknown volume \"" << volume << "\""; - return -1; - } - if (strcmp(v->fs_type, "ramdisk") == 0) { - LOG(ERROR) << "can't format_volume \"" << volume << "\""; - return -1; - } - if (strcmp(v->mount_point, volume) != 0) { - LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume"; - return -1; - } - if (ensure_path_unmounted(volume) != 0) { - LOG(ERROR) << "format_volume: Failed to unmount \"" << v->mount_point << "\""; - return -1; - } - if (strcmp(v->fs_type, "ext4") != 0 && strcmp(v->fs_type, "f2fs") != 0) { - LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported"; - return -1; - } - - // If there's a key_loc that looks like a path, it should be a block device for storing encryption - // metadata. Wipe it too. - if (v->key_loc != nullptr && v->key_loc[0] == '/') { - LOG(INFO) << "Wiping " << v->key_loc; - int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644); - if (fd == -1) { - PLOG(ERROR) << "format_volume: Failed to open " << v->key_loc; - return -1; - } - wipe_block_device(fd, get_file_size(fd)); - close(fd); - } - - int64_t length = 0; - if (v->length > 0) { - length = v->length; - } else if (v->length < 0 || - (v->key_loc != nullptr && strcmp(v->key_loc, "footer") == 0)) { - android::base::unique_fd fd(open(v->blk_device, O_RDONLY)); - if (fd == -1) { - PLOG(ERROR) << "format_volume: failed to open " << v->blk_device; - return -1; - } - length = - get_file_size(fd.get(), v->length ? -v->length : CRYPT_FOOTER_OFFSET); - if (length <= 0) { - LOG(ERROR) << "get_file_size: invalid size " << length << " for " - << v->blk_device; - return -1; - } - } - - if (strcmp(v->fs_type, "ext4") == 0) { - static constexpr int kBlockSize = 4096; - std::vector mke2fs_args = { - "/sbin/mke2fs_static", "-F", "-t", "ext4", "-b", std::to_string(kBlockSize), - }; - - int raid_stride = v->logical_blk_size / kBlockSize; - int raid_stripe_width = v->erase_blk_size / kBlockSize; - // stride should be the max of 8KB and logical block size - if (v->logical_blk_size != 0 && v->logical_blk_size < 8192) { - raid_stride = 8192 / kBlockSize; - } - if (v->erase_blk_size != 0 && v->logical_blk_size != 0) { - mke2fs_args.push_back("-E"); - mke2fs_args.push_back( - android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride, raid_stripe_width)); - } - mke2fs_args.push_back(v->blk_device); - if (length != 0) { - mke2fs_args.push_back(std::to_string(length / kBlockSize)); - } - - int result = exec_cmd(mke2fs_args); - if (result == 0 && directory != nullptr) { - std::vector e2fsdroid_args = { - "/sbin/e2fsdroid_static", - "-e", - "-f", - directory, - "-a", - volume, - v->blk_device, - }; - result = exec_cmd(e2fsdroid_args); - } - - if (result != 0) { - PLOG(ERROR) << "format_volume: Failed to make ext4 on " << v->blk_device; - return -1; - } - return 0; - } - - // Has to be f2fs because we checked earlier. - static constexpr int kSectorSize = 4096; - std::string cmd("/sbin/mkfs.f2fs"); - // clang-format off - std::vector make_f2fs_cmd = { - cmd, - "-d1", - "-f", - "-O", "encrypt", - "-O", "quota", - "-O", "verity", - "-w", std::to_string(kSectorSize), - v->blk_device, - }; - // clang-format on - if (length >= kSectorSize) { - make_f2fs_cmd.push_back(std::to_string(length / kSectorSize)); - } - - int result = exec_cmd(make_f2fs_cmd); - if (result == 0 && directory != nullptr) { - cmd = "/sbin/sload.f2fs"; - // clang-format off - std::vector sload_f2fs_cmd = { - cmd, - "-f", directory, - "-t", volume, - v->blk_device, - }; - // clang-format on - result = exec_cmd(sload_f2fs_cmd); - } - if (result != 0) { - PLOG(ERROR) << "format_volume: Failed " << cmd << " on " << v->blk_device; - return -1; - } - return 0; -} - -int format_volume(const char* volume) { - return format_volume(volume, nullptr); -} - -int setup_install_mounts() { - if (fstab == nullptr) { - LOG(ERROR) << "can't set up install mounts: no fstab loaded"; - return -1; - } - for (int i = 0; i < fstab->num_entries; ++i) { - const Volume* v = fstab->recs + i; - - // We don't want to do anything with "/". - if (strcmp(v->mount_point, "/") == 0) { - continue; - } - - if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) { - if (ensure_path_mounted(v->mount_point) != 0) { - LOG(ERROR) << "Failed to mount " << v->mount_point; - return -1; - } - } else { - if (ensure_path_unmounted(v->mount_point) != 0) { - LOG(ERROR) << "Failed to unmount " << v->mount_point; - return -1; - } - } - } - return 0; -} diff --git a/rotate_logs.cpp b/rotate_logs.cpp deleted file mode 100644 index da008792c5..0000000000 --- a/rotate_logs.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "rotate_logs.h" - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include /* private pmsg functions */ - -static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg"; -static const std::string LAST_LOG_FILTER = "recovery/last_log"; - -ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */, - size_t len, void* arg) { - bool* do_rotate = static_cast(arg); - if (LAST_KMSG_FILTER.find(filename) != std::string::npos || - LAST_LOG_FILTER.find(filename) != std::string::npos) { - *do_rotate = true; - } - return len; -} - -ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, - void* arg) { - bool* do_rotate = static_cast(arg); - if (!*do_rotate) { - return __android_log_pmsg_file_write(id, prio, filename, buf, len); - } - - std::string name(filename); - size_t dot = name.find_last_of('.'); - std::string sub = name.substr(0, dot); - - if (LAST_KMSG_FILTER.find(sub) == std::string::npos && - LAST_LOG_FILTER.find(sub) == std::string::npos) { - return __android_log_pmsg_file_write(id, prio, filename, buf, len); - } - - // filename rotation - if (dot == std::string::npos) { - name += ".1"; - } else { - std::string number = name.substr(dot + 1); - if (!isdigit(number[0])) { - name += ".1"; - } else { - size_t i; - if (!android::base::ParseUint(number, &i)) { - LOG(ERROR) << "failed to parse uint in " << number; - return -1; - } - name = sub + "." + std::to_string(i + 1); - } - } - - return __android_log_pmsg_file_write(id, prio, name.c_str(), buf, len); -} - -// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. -// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. -// Overwrite any existing last_log.$max and last_kmsg.$max. -void rotate_logs(const char* last_log_file, const char* last_kmsg_file) { - // Logs should only be rotated once. - static bool rotated = false; - if (rotated) { - return; - } - rotated = true; - - for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) { - std::string old_log = android::base::StringPrintf("%s", last_log_file); - if (i > 0) { - old_log += "." + std::to_string(i); - } - std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i + 1); - // Ignore errors if old_log doesn't exist. - rename(old_log.c_str(), new_log.c_str()); - - std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file); - if (i > 0) { - old_kmsg += "." + std::to_string(i); - } - std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i + 1); - rename(old_kmsg.c_str(), new_kmsg.c_str()); - } -} diff --git a/screen_ui.cpp b/screen_ui.cpp deleted file mode 100644 index c8fb5aa755..0000000000 --- a/screen_ui.cpp +++ /dev/null @@ -1,959 +0,0 @@ -/* - * Copyright (C) 2011 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 "screen_ui.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "common.h" -#include "device.h" -#include "ui.h" - -// Return the current time as a double (including fractions of a second). -static double now() { - struct timeval tv; - gettimeofday(&tv, nullptr); - return tv.tv_sec + tv.tv_usec / 1000000.0; -} - -ScreenRecoveryUI::ScreenRecoveryUI() - : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH), - kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT), - kAnimationFps(RECOVERY_UI_ANIMATION_FPS), - kDensity(static_cast(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), - currentIcon(NONE), - progressBarType(EMPTY), - progressScopeStart(0), - progressScopeSize(0), - progress(0), - pagesIdentical(false), - text_cols_(0), - text_rows_(0), - text_(nullptr), - text_col_(0), - text_row_(0), - show_text(false), - show_text_ever(false), - menu_headers_(nullptr), - show_menu(false), - menu_items(0), - menu_sel(0), - file_viewer_text_(nullptr), - intro_frames(0), - loop_frames(0), - current_frame(0), - intro_done(false), - stage(-1), - max_stage(-1), - locale_(""), - rtl_locale_(false), - updateMutex(PTHREAD_MUTEX_INITIALIZER) {} - -GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { - if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - return intro_done ? loopFrames[current_frame] : introFrames[current_frame]; - } - return error_icon; -} - -GRSurface* ScreenRecoveryUI::GetCurrentText() const { - switch (currentIcon) { - case ERASING: - return erasing_text; - case ERROR: - return error_text; - case INSTALLING_UPDATE: - return installing_text; - case NO_COMMAND: - return no_command_text; - case NONE: - abort(); - } -} - -int ScreenRecoveryUI::PixelsFromDp(int dp) const { - return dp * kDensity; -} - -// Here's the intended layout: - -// | portrait large landscape large -// ---------+------------------------------------------------- -// gap | -// icon | (200dp) -// gap | 68dp 68dp 56dp 112dp -// text | (14sp) -// gap | 32dp 32dp 26dp 52dp -// progress | (2dp) -// gap | - -// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines -// work), so that's the more useful measurement for calling code. We use even top and bottom gaps. - -enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX }; -enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX }; -static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { - { 32, 68, }, // PORTRAIT - { 32, 68, }, // PORTRAIT_LARGE - { 26, 56, }, // LANDSCAPE - { 52, 112, }, // LANDSCAPE_LARGE -}; - -int ScreenRecoveryUI::GetAnimationBaseline() const { - return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]); -} - -int ScreenRecoveryUI::GetTextBaseline() const { - return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - - gr_get_height(installing_text); -} - -int ScreenRecoveryUI::GetProgressBaseline() const { - int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) + - gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) + - gr_get_height(progressBarFill); - int bottom_gap = (ScreenHeight() - elements_sum) / 2; - return ScreenHeight() - bottom_gap - gr_get_height(progressBarFill); -} - -// Clear the screen and draw the currently selected background icon (if any). -// Should only be called with updateMutex locked. -void ScreenRecoveryUI::draw_background_locked() { - pagesIdentical = false; - gr_color(0, 0, 0, 255); - gr_clear(); - if (currentIcon != NONE) { - if (max_stage != -1) { - int stage_height = gr_get_height(stageMarkerEmpty); - int stage_width = gr_get_width(stageMarkerEmpty); - int x = (ScreenWidth() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; - int y = ScreenHeight() - stage_height - kMarginHeight; - for (int i = 0; i < max_stage; ++i) { - GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty; - DrawSurface(stage_surface, 0, 0, stage_width, stage_height, x, y); - x += stage_width; - } - } - - GRSurface* text_surface = GetCurrentText(); - int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2; - int text_y = GetTextBaseline(); - gr_color(255, 255, 255, 255); - DrawTextIcon(text_x, text_y, text_surface); - } -} - -// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be -// called with updateMutex locked. -void ScreenRecoveryUI::draw_foreground_locked() { - if (currentIcon != NONE) { - GRSurface* frame = GetCurrentFrame(); - int frame_width = gr_get_width(frame); - int frame_height = gr_get_height(frame); - int frame_x = (ScreenWidth() - frame_width) / 2; - int frame_y = GetAnimationBaseline(); - DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); - } - - if (progressBarType != EMPTY) { - int width = gr_get_width(progressBarEmpty); - int height = gr_get_height(progressBarEmpty); - - int progress_x = (ScreenWidth() - width) / 2; - int progress_y = GetProgressBaseline(); - - // Erase behind the progress bar (in case this was a progress-only update) - gr_color(0, 0, 0, 255); - DrawFill(progress_x, progress_y, width, height); - - if (progressBarType == DETERMINATE) { - float p = progressScopeStart + progress * progressScopeSize; - int pos = static_cast(p * width); - - if (rtl_locale_) { - // Fill the progress bar from right to left. - if (pos > 0) { - DrawSurface(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos, - progress_y); - } - if (pos < width - 1) { - DrawSurface(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y); - } - } else { - // Fill the progress bar from left to right. - if (pos > 0) { - DrawSurface(progressBarFill, 0, 0, pos, height, progress_x, progress_y); - } - if (pos < width - 1) { - DrawSurface(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y); - } - } - } - } -} - -void ScreenRecoveryUI::SetColor(UIElement e) const { - switch (e) { - case INFO: - gr_color(249, 194, 0, 255); - break; - case HEADER: - gr_color(247, 0, 6, 255); - break; - case MENU: - case MENU_SEL_BG: - gr_color(0, 106, 157, 255); - break; - case MENU_SEL_BG_ACTIVE: - gr_color(0, 156, 100, 255); - break; - case MENU_SEL_FG: - gr_color(255, 255, 255, 255); - break; - case LOG: - gr_color(196, 196, 196, 255); - break; - case TEXT_FILL: - gr_color(0, 0, 0, 160); - break; - default: - gr_color(255, 255, 255, 255); - break; - } -} - -void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector& locales_entries, - size_t sel) { - SetLocale(locales_entries[sel]); - std::vector text_name = { "erasing_text", "error_text", "installing_text", - "installing_security_text", "no_command_text" }; - std::unordered_map> surfaces; - for (const auto& name : text_name) { - GRSurface* text_image = nullptr; - LoadLocalizedBitmap(name.c_str(), &text_image); - if (!text_image) { - Print("Failed to load %s\n", name.c_str()); - return; - } - surfaces.emplace(name, std::unique_ptr(text_image, &free)); - } - - pthread_mutex_lock(&updateMutex); - gr_color(0, 0, 0, 255); - gr_clear(); - - int text_y = kMarginHeight; - int text_x = kMarginWidth; - int line_spacing = gr_sys_font()->char_height; // Put some extra space between images. - // Write the header and descriptive texts. - SetColor(INFO); - std::string header = "Show background text image"; - text_y += DrawTextLine(text_x, text_y, header.c_str(), true); - std::string locale_selection = android::base::StringPrintf( - "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel, locales_entries.size()); - const char* instruction[] = { locale_selection.c_str(), - "Use volume up/down to switch locales and power to exit.", - nullptr }; - text_y += DrawWrappedTextLines(text_x, text_y, instruction); - - // Iterate through the text images and display them in order for the current locale. - for (const auto& p : surfaces) { - text_y += line_spacing; - SetColor(LOG); - text_y += DrawTextLine(text_x, text_y, p.first.c_str(), false); - gr_color(255, 255, 255, 255); - gr_texticon(text_x, text_y, p.second.get()); - text_y += gr_get_height(p.second.get()); - } - // Update the whole screen. - gr_flip(); - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::CheckBackgroundTextImages(const std::string& saved_locale) { - // Load a list of locales embedded in one of the resource files. - std::vector locales_entries = get_locales_in_png("installing_text"); - if (locales_entries.empty()) { - Print("Failed to load locales from the resource files\n"); - return; - } - size_t selected = 0; - SelectAndShowBackgroundText(locales_entries, selected); - - FlushKeys(); - while (true) { - int key = WaitKey(); - if (key == KEY_POWER || key == KEY_ENTER) { - break; - } else if (key == KEY_UP || key == KEY_VOLUMEUP) { - selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1; - SelectAndShowBackgroundText(locales_entries, selected); - } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) { - selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1; - SelectAndShowBackgroundText(locales_entries, selected); - } - } - - SetLocale(saved_locale); -} - -int ScreenRecoveryUI::ScreenWidth() const { - return gr_fb_width(); -} - -int ScreenRecoveryUI::ScreenHeight() const { - return gr_fb_height(); -} - -void ScreenRecoveryUI::DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, - int dy) const { - gr_blit(surface, sx, sy, w, h, dx, dy); -} - -int ScreenRecoveryUI::DrawHorizontalRule(int y) const { - gr_fill(0, y + 4, ScreenWidth(), y + 6); - return 8; -} - -void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const { - gr_fill(x, y, x + width, y + height); -} - -void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const { - gr_fill(x, y, w, h); -} - -void ScreenRecoveryUI::DrawTextIcon(int x, int y, GRSurface* surface) const { - gr_texticon(x, y, surface); -} - -int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const { - gr_text(gr_sys_font(), x, y, line, bold); - return char_height_ + 4; -} - -int ScreenRecoveryUI::DrawTextLines(int x, int y, const char* const* lines) const { - int offset = 0; - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - offset += DrawTextLine(x, y + offset, lines[i], false); - } - return offset; -} - -int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* lines) const { - int offset = 0; - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - // The line will be wrapped if it exceeds text_cols_. - std::string line(lines[i]); - size_t next_start = 0; - while (next_start < line.size()) { - std::string sub = line.substr(next_start, text_cols_ + 1); - if (sub.size() <= text_cols_) { - next_start += sub.size(); - } else { - // Line too long and must be wrapped to text_cols_ columns. - size_t last_space = sub.find_last_of(" \t\n"); - if (last_space == std::string::npos) { - // No space found, just draw as much as we can - sub.resize(text_cols_); - next_start += text_cols_; - } else { - sub.resize(last_space); - next_start += last_space + 1; - } - } - offset += DrawTextLine(x, y + offset, sub.c_str(), false); - } - } - return offset; -} - -static const char* REGULAR_HELP[] = { - "Use volume up/down and power.", - NULL -}; - -static const char* LONG_PRESS_HELP[] = { - "Any button cycles highlight.", - "Long-press activates.", - NULL -}; - -// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex -// locked. -void ScreenRecoveryUI::draw_screen_locked() { - if (!show_text) { - draw_background_locked(); - draw_foreground_locked(); - return; - } - - gr_color(0, 0, 0, 255); - gr_clear(); - - int y = kMarginHeight; - if (show_menu) { - static constexpr int kMenuIndent = 4; - int x = kMarginWidth + kMenuIndent; - - SetColor(INFO); - y += DrawTextLine(x, y, "Android Recovery", true); - std::string recovery_fingerprint = - android::base::GetProperty("ro.bootimage.build.fingerprint", ""); - for (const auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - y += DrawTextLine(x, y, chunk.c_str(), false); - } - y += DrawTextLines(x, y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); - - SetColor(HEADER); - // Ignore kMenuIndent, which is not taken into account by text_cols_. - y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_); - - SetColor(MENU); - y += DrawHorizontalRule(y) + 4; - for (int i = 0; i < menu_items; ++i) { - if (i == menu_sel) { - // Draw the highlight bar. - SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); - DrawHighlightBar(0, y - 2, ScreenWidth(), char_height_ + 4); - // Bold white text for the selected item. - SetColor(MENU_SEL_FG); - y += DrawTextLine(x, y, menu_[i].c_str(), true); - SetColor(MENU); - } else { - y += DrawTextLine(x, y, menu_[i].c_str(), false); - } - } - y += DrawHorizontalRule(y); - } - - // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or - // we've displayed the entire text buffer. - SetColor(LOG); - int row = text_row_; - size_t count = 0; - for (int ty = ScreenHeight() - kMarginHeight - char_height_; ty >= y && count < text_rows_; - ty -= char_height_, ++count) { - DrawTextLine(kMarginWidth, ty, text_[row], false); - --row; - if (row < 0) row = text_rows_ - 1; - } -} - -// Redraw everything on the screen and flip the screen (make it visible). -// Should only be called with updateMutex locked. -void ScreenRecoveryUI::update_screen_locked() { - draw_screen_locked(); - gr_flip(); -} - -// Updates only the progress bar, if possible, otherwise redraws the screen. -// Should only be called with updateMutex locked. -void ScreenRecoveryUI::update_progress_locked() { - if (show_text || !pagesIdentical) { - draw_screen_locked(); // Must redraw the whole screen - pagesIdentical = true; - } else { - draw_foreground_locked(); // Draw only the progress bar and overlays - } - gr_flip(); -} - -// Keeps the progress bar updated, even when the process is otherwise busy. -void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { - reinterpret_cast(data)->ProgressThreadLoop(); - return nullptr; -} - -void ScreenRecoveryUI::ProgressThreadLoop() { - double interval = 1.0 / kAnimationFps; - while (true) { - double start = now(); - pthread_mutex_lock(&updateMutex); - - bool redraw = false; - - // update the installation animation, if active - // skip this if we have a text overlay (too expensive to update) - if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) { - if (!intro_done) { - if (current_frame == intro_frames - 1) { - intro_done = true; - current_frame = 0; - } else { - ++current_frame; - } - } else { - current_frame = (current_frame + 1) % loop_frames; - } - - redraw = true; - } - - // move the progress bar forward on timed intervals, if configured - int duration = progressScopeDuration; - if (progressBarType == DETERMINATE && duration > 0) { - double elapsed = now() - progressScopeTime; - float p = 1.0 * elapsed / duration; - if (p > 1.0) p = 1.0; - if (p > progress) { - progress = p; - redraw = true; - } - } - - if (redraw) update_progress_locked(); - - pthread_mutex_unlock(&updateMutex); - double end = now(); - // minimum of 20ms delay between frames - double delay = interval - (end - start); - if (delay < 0.02) delay = 0.02; - usleep(static_cast(delay * 1000000)); - } -} - -void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { - int result = res_create_display_surface(filename, surface); - if (result < 0) { - LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; - } -} - -void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { - int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface); - if (result < 0) { - LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; - } -} - -static char** Alloc2d(size_t rows, size_t cols) { - char** result = new char*[rows]; - for (size_t i = 0; i < rows; ++i) { - result[i] = new char[cols]; - memset(result[i], 0, cols); - } - return result; -} - -// Choose the right background string to display during update. -void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { - if (security_update) { - LoadLocalizedBitmap("installing_security_text", &installing_text); - } else { - LoadLocalizedBitmap("installing_text", &installing_text); - } - Redraw(); -} - -bool ScreenRecoveryUI::InitTextParams() { - if (gr_init() < 0) { - return false; - } - - gr_font_size(gr_sys_font(), &char_width_, &char_height_); - text_rows_ = (ScreenHeight() - kMarginHeight * 2) / char_height_; - text_cols_ = (ScreenWidth() - kMarginWidth * 2) / char_width_; - return true; -} - -bool ScreenRecoveryUI::Init(const std::string& locale) { - RecoveryUI::Init(locale); - - if (!InitTextParams()) { - return false; - } - - // Are we portrait or landscape? - layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; - // Are we the large variant of our base layout? - if (gr_fb_height() > PixelsFromDp(800)) ++layout_; - - text_ = Alloc2d(text_rows_, text_cols_ + 1); - file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); - - text_col_ = text_row_ = 0; - - // Set up the locale info. - SetLocale(locale); - - LoadBitmap("icon_error", &error_icon); - - LoadBitmap("progress_empty", &progressBarEmpty); - LoadBitmap("progress_fill", &progressBarFill); - - LoadBitmap("stage_empty", &stageMarkerEmpty); - LoadBitmap("stage_fill", &stageMarkerFill); - - // Background text for "installing_update" could be "installing update" - // or "installing security update". It will be set after UI init according - // to commands in BCB. - installing_text = nullptr; - LoadLocalizedBitmap("erasing_text", &erasing_text); - LoadLocalizedBitmap("no_command_text", &no_command_text); - LoadLocalizedBitmap("error_text", &error_text); - - LoadAnimation(); - - pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); - - return true; -} - -void ScreenRecoveryUI::LoadAnimation() { - std::unique_ptr dir(opendir("/res/images"), closedir); - dirent* de; - std::vector intro_frame_names; - std::vector loop_frame_names; - - while ((de = readdir(dir.get())) != nullptr) { - int value, num_chars; - if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { - intro_frame_names.emplace_back(de->d_name, num_chars); - } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { - loop_frame_names.emplace_back(de->d_name, num_chars); - } - } - - intro_frames = intro_frame_names.size(); - loop_frames = loop_frame_names.size(); - - // It's okay to not have an intro. - if (intro_frames == 0) intro_done = true; - // But you must have an animation. - if (loop_frames == 0) abort(); - - std::sort(intro_frame_names.begin(), intro_frame_names.end()); - std::sort(loop_frame_names.begin(), loop_frame_names.end()); - - introFrames = new GRSurface*[intro_frames]; - for (size_t i = 0; i < intro_frames; i++) { - LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]); - } - - loopFrames = new GRSurface*[loop_frames]; - for (size_t i = 0; i < loop_frames; i++) { - LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]); - } -} - -void ScreenRecoveryUI::SetBackground(Icon icon) { - pthread_mutex_lock(&updateMutex); - - currentIcon = icon; - update_screen_locked(); - - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::SetProgressType(ProgressType type) { - pthread_mutex_lock(&updateMutex); - if (progressBarType != type) { - progressBarType = type; - } - progressScopeStart = 0; - progressScopeSize = 0; - progress = 0; - update_progress_locked(); - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { - pthread_mutex_lock(&updateMutex); - progressBarType = DETERMINATE; - progressScopeStart += progressScopeSize; - progressScopeSize = portion; - progressScopeTime = now(); - progressScopeDuration = seconds; - progress = 0; - update_progress_locked(); - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::SetProgress(float fraction) { - pthread_mutex_lock(&updateMutex); - if (fraction < 0.0) fraction = 0.0; - if (fraction > 1.0) fraction = 1.0; - if (progressBarType == DETERMINATE && fraction > progress) { - // Skip updates that aren't visibly different. - int width = gr_get_width(progressBarEmpty); - float scale = width * progressScopeSize; - if ((int)(progress * scale) != (int)(fraction * scale)) { - progress = fraction; - update_progress_locked(); - } - } - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::SetStage(int current, int max) { - pthread_mutex_lock(&updateMutex); - stage = current; - max_stage = max; - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { - std::string str; - android::base::StringAppendV(&str, fmt, ap); - - if (copy_to_stdout) { - fputs(str.c_str(), stdout); - } - - pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col_ >= text_cols_) { - text_[text_row_][text_col_] = '\0'; - text_col_ = 0; - text_row_ = (text_row_ + 1) % text_rows_; - } - if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; - } - text_[text_row_][text_col_] = '\0'; - update_screen_locked(); - } - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::Print(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, true, ap); - va_end(ap); -} - -void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, false, ap); - va_end(ap); -} - -void ScreenRecoveryUI::PutChar(char ch) { - pthread_mutex_lock(&updateMutex); - if (ch != '\n') text_[text_row_][text_col_++] = ch; - if (ch == '\n' || text_col_ >= text_cols_) { - text_col_ = 0; - ++text_row_; - } - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::ClearText() { - pthread_mutex_lock(&updateMutex); - text_col_ = 0; - text_row_ = 0; - for (size_t i = 0; i < text_rows_; ++i) { - memset(text_[i], 0, text_cols_ + 1); - } - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::ShowFile(FILE* fp) { - std::vector offsets; - offsets.push_back(ftello(fp)); - ClearText(); - - struct stat sb; - fstat(fileno(fp), &sb); - - bool show_prompt = false; - while (true) { - if (show_prompt) { - PrintOnScreenOnly("--(%d%% of %d bytes)--", - static_cast(100 * (double(ftello(fp)) / double(sb.st_size))), - static_cast(sb.st_size)); - Redraw(); - while (show_prompt) { - show_prompt = false; - int key = WaitKey(); - if (key == KEY_POWER || key == KEY_ENTER) { - return; - } else if (key == KEY_UP || key == KEY_VOLUMEUP) { - if (offsets.size() <= 1) { - show_prompt = true; - } else { - offsets.pop_back(); - fseek(fp, offsets.back(), SEEK_SET); - } - } else { - if (feof(fp)) { - return; - } - offsets.push_back(ftello(fp)); - } - } - ClearText(); - } - - int ch = getc(fp); - if (ch == EOF) { - while (text_row_ < text_rows_ - 1) PutChar('\n'); - show_prompt = true; - } else { - PutChar(ch); - if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { - show_prompt = true; - } - } - } -} - -void ScreenRecoveryUI::ShowFile(const char* filename) { - FILE* fp = fopen_path(filename, "re"); - if (fp == nullptr) { - Print(" Unable to open %s: %s\n", filename, strerror(errno)); - return; - } - - char** old_text = text_; - size_t old_text_col = text_col_; - size_t old_text_row = text_row_; - - // Swap in the alternate screen and clear it. - text_ = file_viewer_text_; - ClearText(); - - ShowFile(fp); - fclose(fp); - - text_ = old_text; - text_col_ = old_text_col; - text_row_ = old_text_row; -} - -void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items, - int initial_selection) { - pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - menu_headers_ = headers; - menu_.clear(); - for (size_t i = 0; i < text_rows_ && items[i] != nullptr; ++i) { - menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1))); - } - menu_items = static_cast(menu_.size()); - show_menu = true; - menu_sel = initial_selection; - update_screen_locked(); - } - pthread_mutex_unlock(&updateMutex); -} - -int ScreenRecoveryUI::SelectMenu(int sel) { - pthread_mutex_lock(&updateMutex); - if (show_menu) { - int old_sel = menu_sel; - menu_sel = sel; - - // Wrap at top and bottom. - if (menu_sel < 0) menu_sel = menu_items - 1; - if (menu_sel >= menu_items) menu_sel = 0; - - sel = menu_sel; - if (menu_sel != old_sel) update_screen_locked(); - } - pthread_mutex_unlock(&updateMutex); - return sel; -} - -void ScreenRecoveryUI::EndMenu() { - pthread_mutex_lock(&updateMutex); - if (show_menu && text_rows_ > 0 && text_cols_ > 0) { - show_menu = false; - update_screen_locked(); - } - pthread_mutex_unlock(&updateMutex); -} - -bool ScreenRecoveryUI::IsTextVisible() { - pthread_mutex_lock(&updateMutex); - int visible = show_text; - pthread_mutex_unlock(&updateMutex); - return visible; -} - -bool ScreenRecoveryUI::WasTextEverVisible() { - pthread_mutex_lock(&updateMutex); - int ever_visible = show_text_ever; - pthread_mutex_unlock(&updateMutex); - return ever_visible; -} - -void ScreenRecoveryUI::ShowText(bool visible) { - pthread_mutex_lock(&updateMutex); - show_text = visible; - if (show_text) show_text_ever = true; - update_screen_locked(); - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::Redraw() { - pthread_mutex_lock(&updateMutex); - update_screen_locked(); - pthread_mutex_unlock(&updateMutex); -} - -void ScreenRecoveryUI::KeyLongPress(int) { - // Redraw so that if we're in the menu, the highlight - // will change color to indicate a successful long press. - Redraw(); -} - -void ScreenRecoveryUI::SetLocale(const std::string& new_locale) { - locale_ = new_locale; - rtl_locale_ = false; - - if (!new_locale.empty()) { - size_t underscore = new_locale.find('_'); - // lang has the language prefix prior to '_', or full string if '_' doesn't exist. - std::string lang = new_locale.substr(0, underscore); - - // A bit cheesy: keep an explicit list of supported RTL languages. - if (lang == "ar" || // Arabic - lang == "fa" || // Persian (Farsi) - lang == "he" || // Hebrew (new language code) - lang == "iw" || // Hebrew (old language code) - lang == "ur") { // Urdu - rtl_locale_ = true; - } - } -} diff --git a/screen_ui.h b/screen_ui.h deleted file mode 100644 index f05761c42b..0000000000 --- a/screen_ui.h +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef RECOVERY_SCREEN_UI_H -#define RECOVERY_SCREEN_UI_H - -#include -#include - -#include -#include - -#include "ui.h" - -// From minui/minui.h. -struct GRSurface; - -// Implementation of RecoveryUI appropriate for devices with a screen -// (shows an icon + a progress bar, text logging, menu, etc.) -class ScreenRecoveryUI : public RecoveryUI { - public: - enum UIElement { - HEADER, - MENU, - MENU_SEL_BG, - MENU_SEL_BG_ACTIVE, - MENU_SEL_FG, - LOG, - TEXT_FILL, - INFO - }; - - ScreenRecoveryUI(); - - bool Init(const std::string& locale) override; - - // overall recovery state ("background image") - void SetBackground(Icon icon) override; - void SetSystemUpdateText(bool security_update) override; - - // progress indicator - void SetProgressType(ProgressType type) override; - void ShowProgress(float portion, float seconds) override; - void SetProgress(float fraction) override; - - void SetStage(int current, int max) override; - - // text log - void ShowText(bool visible) override; - bool IsTextVisible() override; - bool WasTextEverVisible() override; - - // printing messages - void Print(const char* fmt, ...) override __printflike(2, 3); - void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); - void ShowFile(const char* filename) override; - - // menu display - void StartMenu(const char* const* headers, const char* const* items, - int initial_selection) override; - int SelectMenu(int sel) override; - void EndMenu() override; - - void KeyLongPress(int) override; - - void Redraw(); - - void SetColor(UIElement e) const; - - // Check the background text image. Use volume up/down button to cycle through the locales - // embedded in the png file, and power button to go back to recovery main menu. - void CheckBackgroundTextImages(const std::string& saved_locale); - - protected: - // The margin that we don't want to use for showing texts (e.g. round screen, or screen with - // rounded corners). - const int kMarginWidth; - const int kMarginHeight; - - // Number of frames per sec (default: 30) for both parts of the animation. - const int kAnimationFps; - - // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. - const float kDensity; - - virtual bool InitTextParams(); - - virtual void draw_background_locked(); - virtual void draw_foreground_locked(); - virtual void draw_screen_locked(); - virtual void update_screen_locked(); - virtual void update_progress_locked(); - - GRSurface* GetCurrentFrame() const; - GRSurface* GetCurrentText() const; - - static void* ProgressThreadStartRoutine(void* data); - void ProgressThreadLoop(); - - virtual void ShowFile(FILE*); - virtual void PrintV(const char*, bool, va_list); - void PutChar(char); - void ClearText(); - - void LoadAnimation(); - void LoadBitmap(const char* filename, GRSurface** surface); - void LoadLocalizedBitmap(const char* filename, GRSurface** surface); - - int PixelsFromDp(int dp) const; - virtual int GetAnimationBaseline() const; - virtual int GetProgressBaseline() const; - virtual int GetTextBaseline() const; - - // Returns pixel width of draw buffer. - virtual int ScreenWidth() const; - // Returns pixel height of draw buffer. - virtual int ScreenHeight() const; - - // Draws a highlight bar at (x, y) - (x + width, y + height). - virtual void DrawHighlightBar(int x, int y, int width, int height) const; - // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis. - virtual int DrawHorizontalRule(int y) const; - // Draws a line of text. Returns the offset it should be moving along Y-axis. - virtual int DrawTextLine(int x, int y, const char* line, bool bold) const; - // Draws surface portion (sx, sy, w, h) at screen location (dx, dy). - virtual void DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, int dy) const; - // Draws rectangle at (x, y) - (x + w, y + h). - virtual void DrawFill(int x, int y, int w, int h) const; - // Draws given surface (surface->pixel_bytes = 1) as text at (x, y). - virtual void DrawTextIcon(int x, int y, GRSurface* surface) const; - // Draws multiple text lines. Returns the offset it should be moving along Y-axis. - int DrawTextLines(int x, int y, const char* const* lines) const; - // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines. - // Returns the offset it should be moving along Y-axis. - int DrawWrappedTextLines(int x, int y, const char* const* lines) const; - - Icon currentIcon; - - // The layout to use. - int layout_; - - GRSurface* error_icon; - - GRSurface* erasing_text; - GRSurface* error_text; - GRSurface* installing_text; - GRSurface* no_command_text; - - GRSurface** introFrames; - GRSurface** loopFrames; - - GRSurface* progressBarEmpty; - GRSurface* progressBarFill; - GRSurface* stageMarkerEmpty; - GRSurface* stageMarkerFill; - - ProgressType progressBarType; - - float progressScopeStart, progressScopeSize, progress; - double progressScopeTime, progressScopeDuration; - - // true when both graphics pages are the same (except for the progress bar). - bool pagesIdentical; - - size_t text_cols_, text_rows_; - - // Log text overlay, displayed when a magic key is pressed. - char** text_; - size_t text_col_, text_row_; - - bool show_text; - bool show_text_ever; // has show_text ever been true? - - std::vector menu_; - const char* const* menu_headers_; - bool show_menu; - int menu_items, menu_sel; - - // An alternate text screen, swapped with 'text_' when we're viewing a log file. - char** file_viewer_text_; - - pthread_t progress_thread_; - - // Number of intro frames and loop frames in the animation. - size_t intro_frames; - size_t loop_frames; - - size_t current_frame; - bool intro_done; - - int stage, max_stage; - - int char_width_; - int char_height_; - - // The locale that's used to show the rendered texts. - std::string locale_; - bool rtl_locale_; - - pthread_mutex_t updateMutex; - - private: - void SetLocale(const std::string&); - - // Display the background texts for "erasing", "error", "no_command" and "installing" for the - // selected locale. - void SelectAndShowBackgroundText(const std::vector& locales_entries, size_t sel); -}; - -#endif // RECOVERY_UI_H diff --git a/tests/Android.bp b/tests/Android.bp new file mode 100644 index 0000000000..4c23255ad0 --- /dev/null +++ b/tests/Android.bp @@ -0,0 +1,184 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_defaults { + name: "recovery_test_defaults", + + defaults: [ + "recovery_defaults", + ], + + include_dirs: [ + "bootable/recovery", + ], + + shared_libs: [ + "libbase", + "libcrypto", + "libcutils", + "liblog", + "libpng", + "libprocessgroup", + "libselinux", + "libz", + "libziparchive", + ], + + target: { + android: { + shared_libs: [ + "libutils", + "libvndksupport", + ], + }, + + host: { + static_libs: [ + "libutils", + ], + }, + }, +} + +// libapplypatch, libapplypatch_modes +libapplypatch_static_libs = [ + "libapplypatch_modes", + "libapplypatch", + "libedify", + "libotautil", + "libbsdiff", + "libbspatch", + "libdivsufsort", + "libdivsufsort64", + "libutils", + "libbase", + "libbrotli", + "libbz", + "libz", + "libziparchive", +] + +// librecovery_defaults uses many shared libs that we want to avoid using in tests (e.g. we don't +// have 32-bit android.hardware.health@2.0.so or libbootloader_message.so on marlin). +librecovery_static_libs = [ + "librecovery", + "librecovery_fastboot", + "libinstall", + "librecovery_ui", + "libminui", + "libfusesideload", + "libbootloader_message", + "libotautil", + + "libhealthhalutils", + "libvintf", + + "android.hardware.health@2.0", + "android.hardware.health@1.0", + "libext4_utils", + "libfs_mgr", + "libhidl-gen-utils", + "libhidlbase", + "liblp", + "libtinyxml2", + "libc++fs", +] + +cc_test { + name: "recovery_unit_test", + isolated: true, + require_root: true, + + defaults: [ + "recovery_test_defaults", + "libupdater_defaults", + "libupdater_device_defaults", + ], + + test_suites: ["device-tests"], + + srcs: [ + "unit/*.cpp", + ], + + static_libs: libapplypatch_static_libs + librecovery_static_libs + [ + "librecovery_ui", + "libfusesideload", + "libminui", + "librecovery_utils", + "libotautil", + "libupdater_device", + "libupdater_core", + "libupdate_verifier", + + "libgtest_prod", + "libprotobuf-cpp-lite", + ], + + data: [ + "testdata/*", + ":res-testdata", + ], +} + +cc_test { + name: "recovery_manual_test", + isolated: true, + + defaults: [ + "recovery_test_defaults", + ], + + test_suites: ["device-tests"], + + srcs: [ + "manual/recovery_test.cpp", + ], +} + +cc_test_host { + name: "recovery_host_test", + isolated: true, + + defaults: [ + "recovery_test_defaults", + "libupdater_defaults", + ], + + srcs: [ + "unit/host/*", + ], + + static_libs: [ + "libupdater_host", + "libupdater_core", + "libimgdiff", + "libbsdiff", + "libdivsufsort64", + "libdivsufsort", + "libfstab", + "libc++fs", + ], + + test_suites: ["general-tests"], + + data: ["testdata/*"], + + target: { + darwin: { + // libapplypatch in "libupdater_defaults" is not available on the Mac. + enabled: false, + }, + }, +} diff --git a/tests/Android.mk b/tests/Android.mk deleted file mode 100644 index b3584fe87a..0000000000 --- a/tests/Android.mk +++ /dev/null @@ -1,223 +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. -# - -LOCAL_PATH := $(call my-dir) - -# Unit tests -include $(CLEAR_VARS) -LOCAL_CFLAGS := -Wall -Werror -LOCAL_MODULE := recovery_unit_test -LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_STATIC_LIBRARIES := \ - libverifier \ - libminui \ - libotautil \ - libupdater \ - libziparchive \ - libutils \ - libz \ - libselinux \ - libbase \ - libBionicGtestMain - -LOCAL_SRC_FILES := \ - unit/asn1_decoder_test.cpp \ - unit/dirutil_test.cpp \ - unit/locale_test.cpp \ - unit/rangeset_test.cpp \ - unit/sysutil_test.cpp \ - unit/zip_test.cpp \ - -LOCAL_C_INCLUDES := bootable/recovery -LOCAL_SHARED_LIBRARIES := liblog -include $(BUILD_NATIVE_TEST) - -# Manual tests -include $(CLEAR_VARS) -LOCAL_CFLAGS := -Wall -Werror -LOCAL_MODULE := recovery_manual_test -LOCAL_STATIC_LIBRARIES := \ - libminui \ - libbase \ - libBionicGtestMain - -LOCAL_SRC_FILES := manual/recovery_test.cpp -LOCAL_SHARED_LIBRARIES := \ - liblog \ - libpng - -resource_files := $(call find-files-in-subdirs, bootable/recovery, \ - "*_text.png", \ - res-mdpi/images \ - res-hdpi/images \ - res-xhdpi/images \ - res-xxhdpi/images \ - res-xxxhdpi/images \ - ) - -# The resource image files that will go to $OUT/data/nativetest/recovery. -testimage_out_path := $(TARGET_OUT_DATA)/nativetest/recovery -GEN := $(addprefix $(testimage_out_path)/, $(resource_files)) - -$(GEN): PRIVATE_PATH := $(LOCAL_PATH) -$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@ -$(GEN): $(testimage_out_path)/% : bootable/recovery/% - $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN) - -include $(BUILD_NATIVE_TEST) - -# Component tests -include $(CLEAR_VARS) -LOCAL_CFLAGS := \ - -Wall \ - -Werror \ - -D_FILE_OFFSET_BITS=64 - -ifeq ($(AB_OTA_UPDATER),true) -LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 -endif - -ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true) -LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1 -endif - -ifeq ($(BOARD_AVB_ENABLE),true) -LOCAL_CFLAGS += -DBOARD_AVB_ENABLE=1 -endif - -LOCAL_MODULE := recovery_component_test -LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_C_INCLUDES := bootable/recovery -LOCAL_SRC_FILES := \ - component/applypatch_test.cpp \ - component/bootloader_message_test.cpp \ - component/edify_test.cpp \ - component/imgdiff_test.cpp \ - component/install_test.cpp \ - component/sideload_test.cpp \ - component/uncrypt_test.cpp \ - component/updater_test.cpp \ - component/update_verifier_test.cpp \ - component/verifier_test.cpp - -LOCAL_SHARED_LIBRARIES := \ - libhidlbase - -tune2fs_static_libraries := \ - libext2_com_err \ - libext2_blkid \ - libext2_quota \ - libext2_uuid \ - libext2_e2p \ - libext2fs - -LOCAL_STATIC_LIBRARIES := \ - libapplypatch_modes \ - libapplypatch \ - libedify \ - libimgdiff \ - libimgpatch \ - libbsdiff \ - libbspatch \ - libfusesideload \ - libotafault \ - librecovery \ - libupdater \ - libbootloader_message \ - libverifier \ - libotautil \ - libmounts \ - libupdate_verifier \ - libdivsufsort \ - libdivsufsort64 \ - libfs_mgr \ - libvintf_recovery \ - libvintf \ - libhidl-gen-utils \ - libtinyxml2 \ - libselinux \ - libext4_utils \ - libsparse \ - libcrypto_utils \ - libcrypto \ - libbz \ - libziparchive \ - liblog \ - libutils \ - libz \ - libbase \ - libtune2fs \ - libfec \ - libfec_rs \ - libsquashfs_utils \ - libcutils \ - libbrotli \ - libBionicGtestMain \ - $(tune2fs_static_libraries) - -testdata_files := $(call find-subdir-files, testdata/*) - -# The testdata files that will go to $OUT/data/nativetest/recovery. -testdata_out_path := $(TARGET_OUT_DATA)/nativetest/recovery -GEN := $(addprefix $(testdata_out_path)/, $(testdata_files)) -$(GEN): PRIVATE_PATH := $(LOCAL_PATH) -$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@ -$(GEN): $(testdata_out_path)/% : $(LOCAL_PATH)/% - $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN) - -# A copy of the testdata to be packed into continuous_native_tests.zip. -testdata_continuous_zip_prefix := \ - $(call intermediates-dir-for,PACKAGING,recovery_component_test)/DATA -testdata_continuous_zip_path := $(testdata_continuous_zip_prefix)/nativetest/recovery -GEN := $(addprefix $(testdata_continuous_zip_path)/, $(testdata_files)) -$(GEN): PRIVATE_PATH := $(LOCAL_PATH) -$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@ -$(GEN): $(testdata_continuous_zip_path)/% : $(LOCAL_PATH)/% - $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN) -LOCAL_PICKUP_FILES := $(testdata_continuous_zip_prefix) - -include $(BUILD_NATIVE_TEST) - -# Host tests -include $(CLEAR_VARS) -LOCAL_CFLAGS := -Wall -Werror -LOCAL_MODULE := recovery_host_test -LOCAL_MODULE_HOST_OS := linux -LOCAL_C_INCLUDES := bootable/recovery -LOCAL_SRC_FILES := \ - component/imgdiff_test.cpp -LOCAL_STATIC_LIBRARIES := \ - libimgdiff \ - libimgpatch \ - libotautil \ - libbsdiff \ - libbspatch \ - libziparchive \ - libutils \ - libbase \ - libcrypto \ - libbrotli \ - libbz \ - libdivsufsort64 \ - libdivsufsort \ - libz \ - libBionicGtestMain -LOCAL_SHARED_LIBRARIES := \ - liblog -include $(BUILD_HOST_NATIVE_TEST) diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml deleted file mode 100644 index 3999aa57d6..0000000000 --- a/tests/AndroidTest.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h index 514818e0aa..b6c27a754b 100644 --- a/tests/common/test_constants.h +++ b/tests/common/test_constants.h @@ -17,10 +17,10 @@ #ifndef _OTA_TEST_CONSTANTS_H #define _OTA_TEST_CONSTANTS_H -#include - #include +#include + // Zip entries in ziptest_valid.zip. static const std::string kATxtContents("abcdefghabcdefgh\n"); static const std::string kBTxtContents("abcdefgh\n"); @@ -32,14 +32,9 @@ static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643" // echo -n -e "abcdefgh\n" | sha1sum static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666"); -static std::string from_testdata_base(const std::string& fname) { -#ifdef __ANDROID__ - static std::string data_root = getenv("ANDROID_DATA"); -#else - static std::string data_root = std::string(getenv("ANDROID_PRODUCT_OUT")) + "/data"; -#endif - - return data_root + "/nativetest/recovery/testdata/" + fname; +[[maybe_unused]] static std::string from_testdata_base(const std::string& fname) { + static std::string exec_dir = android::base::GetExecutableDirectory(); + return exec_dir + "/testdata/" + fname; } #endif // _OTA_TEST_CONSTANTS_H diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp deleted file mode 100644 index 61e06adb6a..0000000000 --- a/tests/component/applypatch_test.cpp +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agree to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "applypatch/applypatch.h" -#include "applypatch/applypatch_modes.h" -#include "common/test_constants.h" -#include "otautil/cache_location.h" -#include "otautil/print_sha1.h" - -using namespace std::string_literals; - -static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) { - ASSERT_NE(nullptr, sha1); - - std::string data; - ASSERT_TRUE(android::base::ReadFileToString(fname, &data)); - - if (fsize != nullptr) { - *fsize = data.size(); - } - - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(data.c_str()), data.size(), digest); - *sha1 = print_sha1(digest); -} - -static void mangle_file(const std::string& fname) { - std::string content(1024, '\0'); - for (size_t i = 0; i < 1024; i++) { - content[i] = rand() % 256; - } - ASSERT_TRUE(android::base::WriteStringToFile(content, fname)); -} - -class ApplyPatchTest : public ::testing::Test { - public: - virtual void SetUp() override { - // set up files - old_file = from_testdata_base("old.file"); - new_file = from_testdata_base("new.file"); - nonexistent_file = from_testdata_base("nonexistent.file"); - - // set up SHA constants - sha1sum(old_file, &old_sha1, &old_size); - sha1sum(new_file, &new_sha1, &new_size); - srand(time(nullptr)); - bad_sha1_a = android::base::StringPrintf("%040x", rand()); - bad_sha1_b = android::base::StringPrintf("%040x", rand()); - } - - std::string old_file; - std::string new_file; - std::string nonexistent_file; - - std::string old_sha1; - std::string new_sha1; - std::string bad_sha1_a; - std::string bad_sha1_b; - - size_t old_size; - size_t new_size; -}; - -class ApplyPatchCacheTest : public ApplyPatchTest { - protected: - void SetUp() override { - ApplyPatchTest::SetUp(); - CacheLocation::location().set_cache_temp_source(old_file); - } -}; - -class ApplyPatchModesTest : public ::testing::Test { - protected: - void SetUp() override { - CacheLocation::location().set_cache_temp_source(cache_source.path); - } - - TemporaryFile cache_source; -}; - -TEST_F(ApplyPatchTest, CheckModeSkip) { - std::vector sha1s; - ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); -} - -TEST_F(ApplyPatchTest, CheckModeSingle) { - std::vector sha1s = { old_sha1 }; - ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); -} - -TEST_F(ApplyPatchTest, CheckModeMultiple) { - std::vector sha1s = { bad_sha1_a, old_sha1, bad_sha1_b }; - ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); -} - -TEST_F(ApplyPatchTest, CheckModeFailure) { - std::vector sha1s = { bad_sha1_a, bad_sha1_b }; - ASSERT_NE(0, applypatch_check(&old_file[0], sha1s)); -} - -TEST_F(ApplyPatchTest, CheckModeEmmcTarget) { - // EMMC:old_file:size:sha1 should pass the check. - std::string src_file = - "EMMC:" + old_file + ":" + std::to_string(old_size) + ":" + old_sha1; - std::vector sha1s; - ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s)); - - // EMMC:old_file:(size-1):sha1:(size+1):sha1 should fail the check. - src_file = "EMMC:" + old_file + ":" + std::to_string(old_size - 1) + ":" + old_sha1 + ":" + - std::to_string(old_size + 1) + ":" + old_sha1; - ASSERT_EQ(1, applypatch_check(src_file.c_str(), sha1s)); - - // EMMC:old_file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check. - src_file = "EMMC:" + old_file + ":" + - std::to_string(old_size - 1) + ":" + old_sha1 + ":" + - std::to_string(old_size) + ":" + old_sha1 + ":" + - std::to_string(old_size + 1) + ":" + old_sha1; - ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s)); - - // EMMC:old_file:(size+1):sha1:(size-1):sha1:size:sha1 should pass the check. - src_file = "EMMC:" + old_file + ":" + - std::to_string(old_size + 1) + ":" + old_sha1 + ":" + - std::to_string(old_size - 1) + ":" + old_sha1 + ":" + - std::to_string(old_size) + ":" + old_sha1; - ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s)); - - // EMMC:new_file:(size+1):old_sha1:(size-1):old_sha1:size:old_sha1:size:new_sha1 - // should pass the check. - src_file = "EMMC:" + new_file + ":" + - std::to_string(old_size + 1) + ":" + old_sha1 + ":" + - std::to_string(old_size - 1) + ":" + old_sha1 + ":" + - std::to_string(old_size) + ":" + old_sha1 + ":" + - std::to_string(new_size) + ":" + new_sha1; - ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s)); -} - -TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceSingle) { - TemporaryFile temp_file; - mangle_file(temp_file.path); - std::vector sha1s_single = { old_sha1 }; - ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_single)); - ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_single)); -} - -TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceMultiple) { - TemporaryFile temp_file; - mangle_file(temp_file.path); - std::vector sha1s_multiple = { bad_sha1_a, old_sha1, bad_sha1_b }; - ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_multiple)); - ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_multiple)); -} - -TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceFailure) { - TemporaryFile temp_file; - mangle_file(temp_file.path); - std::vector sha1s_failure = { bad_sha1_a, bad_sha1_b }; - ASSERT_NE(0, applypatch_check(temp_file.path, sha1s_failure)); - ASSERT_NE(0, applypatch_check(nonexistent_file.c_str(), sha1s_failure)); -} - -TEST_F(ApplyPatchModesTest, InvalidArgs) { - // At least two args (including the filename). - ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" })); - - // Unrecognized args. - ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" })); -} - -TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) { - std::string boot_img = from_testdata_base("boot.img"); - size_t boot_img_size; - std::string boot_img_sha1; - sha1sum(boot_img, &boot_img_sha1, &boot_img_size); - - std::string recovery_img = from_testdata_base("recovery.img"); - size_t size; - std::string recovery_img_sha1; - sha1sum(recovery_img, &recovery_img_sha1, &size); - std::string recovery_img_size = std::to_string(size); - - std::string bonus_file = from_testdata_base("bonus.file"); - - // applypatch -b : - TemporaryFile tmp1; - std::string src_file = - "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1; - std::string tgt_file = "EMMC:" + std::string(tmp1.path); - std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p"); - std::vector args = { - "applypatch", - "-b", - bonus_file.c_str(), - src_file.c_str(), - tgt_file.c_str(), - recovery_img_sha1.c_str(), - recovery_img_size.c_str(), - patch.c_str() - }; - ASSERT_EQ(0, applypatch_modes(args.size(), args.data())); - - // applypatch : - TemporaryFile tmp2; - patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p"); - tgt_file = "EMMC:" + std::string(tmp2.path); - std::vector args2 = { - "applypatch", - src_file.c_str(), - tgt_file.c_str(), - recovery_img_sha1.c_str(), - recovery_img_size.c_str(), - patch.c_str() - }; - ASSERT_EQ(0, applypatch_modes(args2.size(), args2.data())); - - // applypatch -b \ - // : : - TemporaryFile tmp3; - tgt_file = "EMMC:" + std::string(tmp3.path); - std::string bad_sha1_a = android::base::StringPrintf("%040x", rand()); - std::string bad_sha1_b = android::base::StringPrintf("%040x", rand()); - std::string patch1 = bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p"); - std::string patch2 = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p"); - std::string patch3 = bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p"); - std::vector args3 = { - "applypatch", - "-b", - bonus_file.c_str(), - src_file.c_str(), - tgt_file.c_str(), - recovery_img_sha1.c_str(), - recovery_img_size.c_str(), - patch1.c_str(), - patch2.c_str(), - patch3.c_str() - }; - ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data())); -} - -// Ensures that applypatch works with a bsdiff based recovery-from-boot.p. -TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) { - std::string boot_img_file = from_testdata_base("boot.img"); - std::string boot_img_sha1; - size_t boot_img_size; - sha1sum(boot_img_file, &boot_img_sha1, &boot_img_size); - - std::string recovery_img_file = from_testdata_base("recovery.img"); - std::string recovery_img_sha1; - size_t recovery_img_size; - sha1sum(recovery_img_file, &recovery_img_sha1, &recovery_img_size); - - // Generate the bsdiff patch of recovery-from-boot.p. - std::string src_content; - ASSERT_TRUE(android::base::ReadFileToString(boot_img_file, &src_content)); - - std::string tgt_content; - ASSERT_TRUE(android::base::ReadFileToString(recovery_img_file, &tgt_content)); - - TemporaryFile patch_file; - ASSERT_EQ(0, - bsdiff::bsdiff(reinterpret_cast(src_content.data()), src_content.size(), - reinterpret_cast(tgt_content.data()), tgt_content.size(), - patch_file.path, nullptr)); - - // applypatch : - std::string src_file_arg = - "EMMC:" + boot_img_file + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1; - TemporaryFile tgt_file; - std::string tgt_file_arg = "EMMC:"s + tgt_file.path; - std::string recovery_img_size_arg = std::to_string(recovery_img_size); - std::string patch_arg = boot_img_sha1 + ":" + patch_file.path; - std::vector args = { "applypatch", - src_file_arg.c_str(), - tgt_file_arg.c_str(), - recovery_img_sha1.c_str(), - recovery_img_size_arg.c_str(), - patch_arg.c_str() }; - ASSERT_EQ(0, applypatch_modes(args.size(), args.data())); - - // Double check the patched recovery image. - std::string tgt_file_sha1; - size_t tgt_file_size; - sha1sum(tgt_file.path, &tgt_file_sha1, &tgt_file_size); - ASSERT_EQ(recovery_img_size, tgt_file_size); - ASSERT_EQ(recovery_img_sha1, tgt_file_sha1); -} - -TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) { - // Invalid bonus file. - ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" })); - - std::string bonus_file = from_testdata_base("bonus.file"); - // With bonus file, but missing args. - ASSERT_EQ(2, applypatch_modes(3, (const char* []){ "applypatch", "-b", bonus_file.c_str() })); - - std::string boot_img = from_testdata_base("boot.img"); - size_t boot_img_size; - std::string boot_img_sha1; - sha1sum(boot_img, &boot_img_sha1, &boot_img_size); - - std::string recovery_img = from_testdata_base("recovery.img"); - size_t size; - std::string recovery_img_sha1; - sha1sum(recovery_img, &recovery_img_sha1, &size); - std::string recovery_img_size = std::to_string(size); - - // Bonus file is not supported in flash mode. - // applypatch -b - TemporaryFile tmp4; - std::vector args4 = { - "applypatch", - "-b", - bonus_file.c_str(), - boot_img.c_str(), - tmp4.path, - recovery_img_sha1.c_str(), - recovery_img_size.c_str() - }; - ASSERT_NE(0, applypatch_modes(args4.size(), args4.data())); - - // Failed to parse patch args. - TemporaryFile tmp5; - std::string bad_arg1 = - "invalid-sha1:filename" + from_testdata_base("recovery-from-boot-with-bonus.p"); - std::vector args5 = { - "applypatch", - boot_img.c_str(), - tmp5.path, - recovery_img_sha1.c_str(), - recovery_img_size.c_str(), - bad_arg1.c_str() - }; - ASSERT_NE(0, applypatch_modes(args5.size(), args5.data())); - - // Target size cannot be zero. - TemporaryFile tmp6; - std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p"); - std::vector args6 = { - "applypatch", - boot_img.c_str(), - tmp6.path, - recovery_img_sha1.c_str(), - "0", // target size - patch.c_str() - }; - ASSERT_NE(0, applypatch_modes(args6.size(), args6.data())); -} - -TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) { - // Insufficient args. - ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" })); -} - -TEST_F(ApplyPatchModesTest, ShowLicenses) { - ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" })); -} diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp deleted file mode 100644 index d19d788e44..0000000000 --- a/tests/component/install_test.cpp +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agree to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "install.h" -#include "private/install.h" - -TEST(InstallTest, verify_package_compatibility_no_entry) { - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - // The archive must have something to be opened correctly. - ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - // Doesn't contain compatibility zip entry. - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - ASSERT_TRUE(verify_package_compatibility(zip)); - CloseArchive(zip); -} - -TEST(InstallTest, verify_package_compatibility_invalid_entry) { - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - // Empty compatibility zip entry. - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - ASSERT_FALSE(verify_package_compatibility(zip)); - CloseArchive(zip); -} - -TEST(InstallTest, read_metadata_from_package_smoke) { - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored)); - const std::string content("abcdefg"); - ASSERT_EQ(0, writer.WriteBytes(content.data(), content.size())); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - std::string metadata; - ASSERT_TRUE(read_metadata_from_package(zip, &metadata)); - ASSERT_EQ(content, metadata); - CloseArchive(zip); - - TemporaryFile temp_file2; - FILE* zip_file2 = fdopen(temp_file2.release(), "w"); - ZipWriter writer2(zip_file2); - ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated)); - ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size())); - ASSERT_EQ(0, writer2.FinishEntry()); - ASSERT_EQ(0, writer2.Finish()); - ASSERT_EQ(0, fclose(zip_file2)); - - ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip)); - metadata.clear(); - ASSERT_TRUE(read_metadata_from_package(zip, &metadata)); - ASSERT_EQ(content, metadata); - CloseArchive(zip); -} - -TEST(InstallTest, read_metadata_from_package_no_entry) { - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - std::string metadata; - ASSERT_FALSE(read_metadata_from_package(zip, &metadata)); - CloseArchive(zip); -} - -TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) { - TemporaryFile compatibility_zip_file; - FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w"); - ZipWriter compatibility_zip_writer(compatibility_zip); - ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated)); - std::string malformed_xml = "malformed"; - ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(malformed_xml.data(), malformed_xml.size())); - ASSERT_EQ(0, compatibility_zip_writer.FinishEntry()); - ASSERT_EQ(0, compatibility_zip_writer.Finish()); - ASSERT_EQ(0, fclose(compatibility_zip)); - - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored)); - std::string compatibility_zip_content; - ASSERT_TRUE( - android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content)); - ASSERT_EQ(0, - writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size())); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - std::vector compatibility_info; - compatibility_info.push_back(malformed_xml); - // Malformed compatibility zip is expected to be rejected by libvintf. But we defer that to - // libvintf. - std::string err; - bool result = - android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0; - ASSERT_EQ(result, verify_package_compatibility(zip)); - CloseArchive(zip); -} - -TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml) { - static constexpr const char* system_manifest_xml_path = "/system/manifest.xml"; - if (access(system_manifest_xml_path, R_OK) == -1) { - GTEST_LOG_(INFO) << "Test skipped on devices w/o /system/manifest.xml."; - return; - } - std::string system_manifest_xml_content; - ASSERT_TRUE( - android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content)); - TemporaryFile compatibility_zip_file; - FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w"); - ZipWriter compatibility_zip_writer(compatibility_zip); - ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated)); - ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(), - system_manifest_xml_content.size())); - ASSERT_EQ(0, compatibility_zip_writer.FinishEntry()); - ASSERT_EQ(0, compatibility_zip_writer.Finish()); - ASSERT_EQ(0, fclose(compatibility_zip)); - - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored)); - std::string compatibility_zip_content; - ASSERT_TRUE( - android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content)); - ASSERT_EQ(0, - writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size())); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - std::vector compatibility_info; - compatibility_info.push_back(system_manifest_xml_content); - std::string err; - bool result = - android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0; - // Make sure the result is consistent with libvintf library. - ASSERT_EQ(result, verify_package_compatibility(zip)); - CloseArchive(zip); -} - -#ifdef AB_OTA_UPDATER -static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool success = true) { - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.StartEntry("payload_properties.txt", kCompressStored)); - const std::string properties = "some_properties"; - ASSERT_EQ(0, writer.WriteBytes(properties.data(), properties.size())); - ASSERT_EQ(0, writer.FinishEntry()); - // A metadata entry is mandatory. - ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored)); - std::string device = android::base::GetProperty("ro.product.device", ""); - ASSERT_NE("", device); - std::string timestamp = android::base::GetProperty("ro.build.date.utc", ""); - ASSERT_NE("", timestamp); - - std::vector meta{ "ota-type=AB", "pre-device=" + device, - "post-timestamp=" + timestamp }; - if (!serialno.empty()) { - meta.push_back("serialno=" + serialno); - } - std::string metadata = android::base::Join(meta, "\n"); - ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size())); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - ZipString payload_name("payload.bin"); - ZipEntry payload_entry; - ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry)); - int status_fd = 10; - std::string package = "/path/to/update.zip"; - std::string binary_path = "/sbin/update_engine_sideload"; - std::vector cmd; - if (success) { - ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd)); - ASSERT_EQ(5U, cmd.size()); - ASSERT_EQ(binary_path, cmd[0]); - ASSERT_EQ("--payload=file://" + package, cmd[1]); - ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]); - ASSERT_EQ("--headers=" + properties, cmd[3]); - ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]); - } else { - ASSERT_EQ(INSTALL_ERROR, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd)); - } - CloseArchive(zip); -} -#endif // AB_OTA_UPDATER - -TEST(InstallTest, update_binary_command_smoke) { -#ifdef AB_OTA_UPDATER - // Empty serialno will pass the verification. - VerifyAbUpdateBinaryCommand({}); -#else - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary"; - ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - int status_fd = 10; - std::string package = "/path/to/update.zip"; - TemporaryDir td; - std::string binary_path = std::string(td.path) + "/update_binary"; - std::vector cmd; - ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd)); - ASSERT_EQ(4U, cmd.size()); - ASSERT_EQ(binary_path, cmd[0]); - ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION - ASSERT_EQ(std::to_string(status_fd), cmd[2]); - ASSERT_EQ(package, cmd[3]); - struct stat sb; - ASSERT_EQ(0, stat(binary_path.c_str(), &sb)); - ASSERT_EQ(static_cast(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); - - // With non-zero retry count. update_binary will be removed automatically. - cmd.clear(); - ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 2, status_fd, &cmd)); - ASSERT_EQ(5U, cmd.size()); - ASSERT_EQ(binary_path, cmd[0]); - ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION - ASSERT_EQ(std::to_string(status_fd), cmd[2]); - ASSERT_EQ(package, cmd[3]); - ASSERT_EQ("retry", cmd[4]); - sb = {}; - ASSERT_EQ(0, stat(binary_path.c_str(), &sb)); - ASSERT_EQ(static_cast(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); - - CloseArchive(zip); -#endif // AB_OTA_UPDATER -} - -TEST(InstallTest, update_binary_command_invalid) { -#ifdef AB_OTA_UPDATER - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - // Missing payload_properties.txt. - ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored)); - ASSERT_EQ(0, writer.FinishEntry()); - // A metadata entry is mandatory. - ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored)); - std::string device = android::base::GetProperty("ro.product.device", ""); - ASSERT_NE("", device); - std::string timestamp = android::base::GetProperty("ro.build.date.utc", ""); - ASSERT_NE("", timestamp); - std::string metadata = android::base::Join( - std::vector{ - "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp, - }, - "\n"); - ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size())); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - int status_fd = 10; - std::string package = "/path/to/update.zip"; - std::string binary_path = "/sbin/update_engine_sideload"; - std::vector cmd; - ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd)); - CloseArchive(zip); -#else - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); - ZipWriter writer(zip_file); - // The archive must have something to be opened correctly. - ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); - - // Missing update binary. - ZipArchiveHandle zip; - ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - int status_fd = 10; - std::string package = "/path/to/update.zip"; - TemporaryDir td; - std::string binary_path = std::string(td.path) + "/update_binary"; - std::vector cmd; - ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd)); - CloseArchive(zip); -#endif // AB_OTA_UPDATER -} - -#ifdef AB_OTA_UPDATER -TEST(InstallTest, update_binary_command_multiple_serialno) { - std::string serialno = android::base::GetProperty("ro.serialno", ""); - ASSERT_NE("", serialno); - - // Single matching serialno will pass the verification. - VerifyAbUpdateBinaryCommand(serialno); - - static constexpr char alphabet[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; }; - - // Generate 900 random serial numbers. - std::string random_serial; - for (size_t i = 0; i < 900; i++) { - generate_n(back_inserter(random_serial), serialno.size(), generator); - random_serial.append("|"); - } - // Random serialnos should fail the verification. - VerifyAbUpdateBinaryCommand(random_serial, false); - - std::string long_serial = random_serial + serialno + "|"; - for (size_t i = 0; i < 99; i++) { - generate_n(back_inserter(long_serial), serialno.size(), generator); - long_serial.append("|"); - } - // String with the matching serialno should pass the verification. - VerifyAbUpdateBinaryCommand(long_serial); -} -#endif // AB_OTA_UPDATER diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp deleted file mode 100644 index 1544bb2a46..0000000000 --- a/tests/component/update_verifier_test.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include -#include -#include -#include - -class UpdateVerifierTest : public ::testing::Test { - protected: - void SetUp() override { -#if defined(PRODUCT_SUPPORTS_VERITY) || defined(BOARD_AVB_ENABLE) - verity_supported = true; -#else - verity_supported = false; -#endif - } - - bool verity_supported; -}; - -TEST_F(UpdateVerifierTest, verify_image_no_care_map) { - // Non-existing care_map is allowed. - ASSERT_TRUE(verify_image("/doesntexist")); -} - -TEST_F(UpdateVerifierTest, verify_image_smoke) { - // This test relies on dm-verity support. - if (!verity_supported) { - GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; - return; - } - - TemporaryFile temp_file; - std::string content = "system\n2,0,1"; - ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path)); - ASSERT_TRUE(verify_image(temp_file.path)); - - // Leading and trailing newlines should be accepted. - ASSERT_TRUE(android::base::WriteStringToFile("\n" + content + "\n\n", temp_file.path)); - ASSERT_TRUE(verify_image(temp_file.path)); -} - -TEST_F(UpdateVerifierTest, verify_image_wrong_lines) { - // The care map file can have only 2 / 4 / 6 lines. - TemporaryFile temp_file; - ASSERT_FALSE(verify_image(temp_file.path)); - - ASSERT_TRUE(android::base::WriteStringToFile("line1", temp_file.path)); - ASSERT_FALSE(verify_image(temp_file.path)); - - ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2\nline3", temp_file.path)); - ASSERT_FALSE(verify_image(temp_file.path)); -} - -TEST_F(UpdateVerifierTest, verify_image_malformed_care_map) { - // This test relies on dm-verity support. - if (!verity_supported) { - GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; - return; - } - - TemporaryFile temp_file; - std::string content = "system\n2,1,0"; - ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path)); - ASSERT_FALSE(verify_image(temp_file.path)); -} - -TEST_F(UpdateVerifierTest, verify_image_legacy_care_map) { - // This test relies on dm-verity support. - if (!verity_supported) { - GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; - return; - } - - TemporaryFile temp_file; - std::string content = "/dev/block/bootdevice/by-name/system\n2,1,0"; - ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path)); - ASSERT_TRUE(verify_image(temp_file.path)); -} diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp deleted file mode 100644 index 5bfd7cb404..0000000000 --- a/tests/component/updater_test.cpp +++ /dev/null @@ -1,933 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/test_constants.h" -#include "edify/expr.h" -#include "otautil/SysUtil.h" -#include "otautil/cache_location.h" -#include "otautil/error_code.h" -#include "otautil/print_sha1.h" -#include "updater/blockimg.h" -#include "updater/install.h" -#include "updater/updater.h" - -struct selabel_handle *sehandle = nullptr; - -static void expect(const char* expected, const char* expr_str, CauseCode cause_code, - UpdaterInfo* info = nullptr) { - std::unique_ptr e; - int error_count = 0; - ASSERT_EQ(0, parse_string(expr_str, &e, &error_count)); - ASSERT_EQ(0, error_count); - - State state(expr_str, info); - - std::string result; - bool status = Evaluate(&state, e, &result); - - if (expected == nullptr) { - ASSERT_FALSE(status); - } else { - ASSERT_TRUE(status); - ASSERT_STREQ(expected, result.c_str()); - } - - // Error code is set in updater/updater.cpp only, by parsing State.errmsg. - ASSERT_EQ(kNoError, state.error_code); - - // Cause code should always be available. - ASSERT_EQ(cause_code, state.cause_code); -} - -static void BuildUpdatePackage(const std::unordered_map& entries, - int fd) { - FILE* zip_file_ptr = fdopen(fd, "wb"); - ZipWriter zip_writer(zip_file_ptr); - - for (const auto& entry : entries) { - ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0)); - if (!entry.second.empty()) { - ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size())); - } - ASSERT_EQ(0, zip_writer.FinishEntry()); - } - - ASSERT_EQ(0, zip_writer.Finish()); - ASSERT_EQ(0, fclose(zip_file_ptr)); -} - -static std::string get_sha1(const std::string& content) { - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(content.c_str()), content.size(), digest); - return print_sha1(digest); -} - -class UpdaterTest : public ::testing::Test { - protected: - virtual void SetUp() override { - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - - // Mock the location of last_command_file. - CacheLocation::location().set_cache_temp_source(temp_saved_source_.path); - CacheLocation::location().set_last_command_file(temp_last_command_.path); - CacheLocation::location().set_stash_directory_base(temp_stash_base_.path); - } - - TemporaryFile temp_saved_source_; - TemporaryFile temp_last_command_; - TemporaryDir temp_stash_base_; -}; - -TEST_F(UpdaterTest, getprop) { - expect(android::base::GetProperty("ro.product.device", "").c_str(), - "getprop(\"ro.product.device\")", - kNoCause); - - expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(), - "getprop(\"ro.build.fingerprint\")", - kNoCause); - - // getprop() accepts only one parameter. - expect(nullptr, "getprop()", kArgsParsingFailure); - expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure); -} - -TEST_F(UpdaterTest, sha1_check) { - // sha1_check(data) returns the SHA-1 of the data. - expect("81fe8bfe87576c3ecb22426f8e57847382917acf", "sha1_check(\"abcd\")", kNoCause); - expect("da39a3ee5e6b4b0d3255bfef95601890afd80709", "sha1_check(\"\")", kNoCause); - - // sha1_check(data, sha1_hex, [sha1_hex, ...]) returns the matched SHA-1. - expect("81fe8bfe87576c3ecb22426f8e57847382917acf", - "sha1_check(\"abcd\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")", - kNoCause); - - expect("81fe8bfe87576c3ecb22426f8e57847382917acf", - "sha1_check(\"abcd\", \"wrong_sha1\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")", - kNoCause); - - // Or "" if there's no match. - expect("", - "sha1_check(\"abcd\", \"wrong_sha1\")", - kNoCause); - - expect("", - "sha1_check(\"abcd\", \"wrong_sha1\", \"wrong_sha2\")", - kNoCause); - - // sha1_check() expects at least one argument. - expect(nullptr, "sha1_check()", kArgsParsingFailure); -} - -TEST_F(UpdaterTest, apply_patch_check) { - // Zero-argument is not valid. - expect(nullptr, "apply_patch_check()", kArgsParsingFailure); - - // File not found. - expect("", "apply_patch_check(\"/doesntexist\")", kNoCause); - - std::string src_file = from_testdata_base("old.file"); - std::string src_content; - ASSERT_TRUE(android::base::ReadFileToString(src_file, &src_content)); - size_t src_size = src_content.size(); - std::string src_hash = get_sha1(src_content); - - // One-argument with EMMC:file:size:sha1 should pass the check. - std::string filename = android::base::Join( - std::vector{ "EMMC", src_file, std::to_string(src_size), src_hash }, ":"); - std::string cmd = "apply_patch_check(\"" + filename + "\")"; - expect("t", cmd.c_str(), kNoCause); - - // EMMC:file:(size-1):sha1:(size+1):sha1 should fail the check. - std::string filename_bad = android::base::Join( - std::vector{ "EMMC", src_file, std::to_string(src_size - 1), src_hash, - std::to_string(src_size + 1), src_hash }, - ":"); - cmd = "apply_patch_check(\"" + filename_bad + "\")"; - expect("", cmd.c_str(), kNoCause); - - // EMMC:file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check. - filename_bad = - android::base::Join(std::vector{ "EMMC", src_file, std::to_string(src_size - 1), - src_hash, std::to_string(src_size), src_hash, - std::to_string(src_size + 1), src_hash }, - ":"); - cmd = "apply_patch_check(\"" + filename_bad + "\")"; - expect("t", cmd.c_str(), kNoCause); - - // Multiple arguments. - cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"wrong_sha2\")"; - expect("", cmd.c_str(), kNoCause); - - cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"" + src_hash + - "\", \"wrong_sha2\")"; - expect("t", cmd.c_str(), kNoCause); - - cmd = "apply_patch_check(\"" + filename_bad + "\", \"wrong_sha1\", \"" + src_hash + - "\", \"wrong_sha2\")"; - expect("t", cmd.c_str(), kNoCause); -} - -TEST_F(UpdaterTest, file_getprop) { - // file_getprop() expects two arguments. - expect(nullptr, "file_getprop()", kArgsParsingFailure); - expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // File doesn't exist. - expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFileGetPropFailure); - - // Reject too large files (current limit = 65536). - TemporaryFile temp_file1; - std::string buffer(65540, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path)); - - // Read some keys. - TemporaryFile temp_file2; - std::string content("ro.product.name=tardis\n" - "# comment\n\n\n" - "ro.product.model\n" - "ro.product.board = magic \n"); - ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path)); - - std::string script1("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.name\")"); - expect("tardis", script1.c_str(), kNoCause); - - std::string script2("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.board\")"); - expect("magic", script2.c_str(), kNoCause); - - // No match. - std::string script3("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.wrong\")"); - expect("", script3.c_str(), kNoCause); - - std::string script4("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.name=\")"); - expect("", script4.c_str(), kNoCause); - - std::string script5("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.nam\")"); - expect("", script5.c_str(), kNoCause); - - std::string script6("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.model\")"); - expect("", script6.c_str(), kNoCause); -} - -// TODO: Test extracting to block device. -TEST_F(UpdaterTest, package_extract_file) { - // package_extract_file expects 1 or 2 arguments. - expect(nullptr, "package_extract_file()", kArgsParsingFailure); - expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Need to set up the ziphandle. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - - // Two-argument version. - TemporaryFile temp_file1; - std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")"); - expect("t", script.c_str(), kNoCause, &updater_info); - - // Verify the extracted entry. - std::string data; - ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); - ASSERT_EQ(kATxtContents, data); - - // Now extract another entry to the same location, which should overwrite. - script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")"; - expect("t", script.c_str(), kNoCause, &updater_info); - - ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); - ASSERT_EQ(kBTxtContents, data); - - // Missing zip entry. The two-argument version doesn't abort. - script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")"; - expect("", script.c_str(), kNoCause, &updater_info); - - // Extract to /dev/full should fail. - script = "package_extract_file(\"a.txt\", \"/dev/full\")"; - expect("", script.c_str(), kNoCause, &updater_info); - - // One-argument version. - script = "sha1_check(package_extract_file(\"a.txt\"))"; - expect(kATxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info); - - script = "sha1_check(package_extract_file(\"b.txt\"))"; - expect(kBTxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info); - - // Missing entry. The one-argument version aborts the evaluation. - script = "package_extract_file(\"doesntexist\")"; - expect(nullptr, script.c_str(), kPackageExtractFileFailure, &updater_info); - - CloseArchive(handle); -} - -TEST_F(UpdaterTest, write_value) { - // write_value() expects two arguments. - expect(nullptr, "write_value()", kArgsParsingFailure); - expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // filename cannot be empty. - expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure); - - // Write some value to file. - TemporaryFile temp_file; - std::string value = "magicvalue"; - std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")"); - expect("t", script.c_str(), kNoCause); - - // Verify the content. - std::string content; - ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); - ASSERT_EQ(value, content); - - // Allow writing empty string. - script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")"; - expect("t", script.c_str(), kNoCause); - - // Verify the content. - ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); - ASSERT_EQ("", content); - - // It should fail gracefully when write fails. - script = "write_value(\"value\", \"/proc/0/file1\")"; - expect("", script.c_str(), kNoCause); -} - -TEST_F(UpdaterTest, get_stage) { - // get_stage() expects one argument. - expect(nullptr, "get_stage()", kArgsParsingFailure); - expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure); - expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Set up a local file as BCB. - TemporaryFile tf; - std::string temp_file(tf.path); - bootloader_message boot; - strlcpy(boot.stage, "2/3", sizeof(boot.stage)); - std::string err; - ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); - - // Can read the stage value. - std::string script("get_stage(\"" + temp_file + "\")"); - expect("2/3", script.c_str(), kNoCause); - - // Bad BCB path. - script = "get_stage(\"doesntexist\")"; - expect("", script.c_str(), kNoCause); -} - -TEST_F(UpdaterTest, set_stage) { - // set_stage() expects two arguments. - expect(nullptr, "set_stage()", kArgsParsingFailure); - expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Set up a local file as BCB. - TemporaryFile tf; - std::string temp_file(tf.path); - bootloader_message boot; - strlcpy(boot.command, "command", sizeof(boot.command)); - strlcpy(boot.stage, "2/3", sizeof(boot.stage)); - std::string err; - ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); - - // Write with set_stage(). - std::string script("set_stage(\"" + temp_file + "\", \"1/3\")"); - expect(tf.path, script.c_str(), kNoCause); - - // Verify. - bootloader_message boot_verify; - ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err)); - - // Stage should be updated, with command part untouched. - ASSERT_STREQ("1/3", boot_verify.stage); - ASSERT_STREQ(boot.command, boot_verify.command); - - // Bad BCB path. - script = "set_stage(\"doesntexist\", \"1/3\")"; - expect("", script.c_str(), kNoCause); - - script = "set_stage(\"/dev/full\", \"1/3\")"; - expect("", script.c_str(), kNoCause); -} - -TEST_F(UpdaterTest, set_progress) { - // set_progress() expects one argument. - expect(nullptr, "set_progress()", kArgsParsingFailure); - expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); - - // Invalid progress argument. - expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure); - expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure); - - TemporaryFile tf; - UpdaterInfo updater_info; - updater_info.cmd_pipe = fdopen(tf.release(), "w"); - expect(".52", "set_progress(\".52\")", kNoCause, &updater_info); - fflush(updater_info.cmd_pipe); - - std::string cmd; - ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); - ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd); - // recovery-updater protocol expects 2 tokens ("set_progress "). - ASSERT_EQ(2U, android::base::Split(cmd, " ").size()); - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); -} - -TEST_F(UpdaterTest, show_progress) { - // show_progress() expects two arguments. - expect(nullptr, "show_progress()", kArgsParsingFailure); - expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Invalid progress arguments. - expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure); - - TemporaryFile tf; - UpdaterInfo updater_info; - updater_info.cmd_pipe = fdopen(tf.release(), "w"); - expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info); - fflush(updater_info.cmd_pipe); - - std::string cmd; - ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); - ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd); - // recovery-updater protocol expects 3 tokens ("progress "). - ASSERT_EQ(3U, android::base::Split(cmd, " ").size()); - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); -} - -TEST_F(UpdaterTest, block_image_update_patch_data) { - std::string src_content = std::string(4096, 'a') + std::string(4096, 'c'); - std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd'); - - // Generate the patch data. - TemporaryFile patch_file; - ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast(src_content.data()), - src_content.size(), reinterpret_cast(tgt_content.data()), - tgt_content.size(), patch_file.path, nullptr)); - std::string patch_content; - ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content)); - - // Create the transfer list that contains a bsdiff. - std::string src_hash = get_sha1(src_content); - std::string tgt_hash = get_sha1(tgt_content); - std::vector transfer_list = { - "4", - "2", - "0", - "2", - "stash " + src_hash + " 2,0,2", - android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(), - src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()), - "free " + src_hash, - }; - - std::unordered_map entries = { - { "new_data", "" }, - { "patch_data", patch_content }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - // Execute the commands in the transfer list. - TemporaryFile update_file; - ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path)); - std::string script = "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))"; - expect("t", script.c_str(), kNoCause, &updater_info); - // The update_file should be patched correctly. - std::string updated_content; - ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content)); - ASSERT_EQ(tgt_hash, get_sha1(updated_content)); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - -TEST_F(UpdaterTest, block_image_update_fail) { - std::string src_content(4096 * 2, 'e'); - std::string src_hash = get_sha1(src_content); - // Stash and free some blocks, then fail the update intentionally. - std::vector transfer_list = { - "4", "2", "0", "2", "stash " + src_hash + " 2,0,2", "free " + src_hash, "fail", - }; - - // Add a new data of 10 bytes to test the deadlock. - std::unordered_map entries = { - { "new_data", std::string(10, 0) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - TemporaryFile update_file; - ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path)); - // Expect the stashed blocks to be freed. - std::string script = "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))"; - expect("", script.c_str(), kNoCause, &updater_info); - // Updater generates the stash name based on the input file name. - std::string name_digest = get_sha1(update_file.path); - std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest; - ASSERT_EQ(0, access(stash_base.c_str(), F_OK)); - ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK)); - ASSERT_EQ(0, rmdir(stash_base.c_str())); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - -TEST_F(UpdaterTest, new_data_over_write) { - std::vector transfer_list = { - "4", "1", "0", "0", "new 2,0,1", - }; - - // Write 4096 + 100 bytes of new data. - std::unordered_map entries = { - { "new_data", std::string(4196, 0) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - TemporaryFile update_file; - std::string script = "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))"; - expect("t", script.c_str(), kNoCause, &updater_info); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - -TEST_F(UpdaterTest, new_data_short_write) { - std::vector transfer_list = { - "4", - "1", - "0", - "0", - "new 2,0,1", - }; - - std::unordered_map entries = { - { "empty_new_data", "" }, - { "short_new_data", std::string(10, 'a') }, - { "exact_new_data", std::string(4096, 'a') }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - // Updater should report the failure gracefully rather than stuck in deadlock. - TemporaryFile update_file; - std::string script_empty_data = "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list"), "empty_new_data", "patch_data"))"; - expect("", script_empty_data.c_str(), kNoCause, &updater_info); - - std::string script_short_data = "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list"), "short_new_data", "patch_data"))"; - expect("", script_short_data.c_str(), kNoCause, &updater_info); - - // Expect to write 1 block of new data successfully. - std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))"; - expect("t", script_exact_data.c_str(), kNoCause, &updater_info); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - -TEST_F(UpdaterTest, brotli_new_data) { - auto generator = []() { return rand() % 128; }; - // Generate 100 blocks of random data. - std::string brotli_new_data; - brotli_new_data.reserve(4096 * 100); - generate_n(back_inserter(brotli_new_data), 4096 * 100, generator); - - size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size()); - std::string encoded_data(encoded_size, 0); - ASSERT_TRUE(BrotliEncoderCompress( - BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(), - reinterpret_cast(brotli_new_data.data()), &encoded_size, - reinterpret_cast(const_cast(encoded_data.data())))); - encoded_data.resize(encoded_size); - - // Write a few small chunks of new data, then a large chunk, and finally a few small chunks. - // This helps us to catch potential short writes. - std::vector transfer_list = { - "4", - "100", - "0", - "0", - "new 2,0,1", - "new 2,1,2", - "new 4,2,50,50,97", - "new 2,97,98", - "new 2,98,99", - "new 2,99,100", - }; - - std::unordered_map entries = { - { "new.dat.br", std::move(encoded_data) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wb"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - // Check if we can decompress the new data correctly. - TemporaryFile update_file; - std::string script_new_data = - "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list"), "new.dat.br", "patch_data"))"; - expect("t", script_new_data.c_str(), kNoCause, &updater_info); - - std::string updated_content; - ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content)); - ASSERT_EQ(brotli_new_data, updated_content); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - -TEST_F(UpdaterTest, last_command_update) { - std::string last_command_file = CacheLocation::location().last_command_file(); - - std::string block1 = std::string(4096, '1'); - std::string block2 = std::string(4096, '2'); - std::string block3 = std::string(4096, '3'); - std::string block1_hash = get_sha1(block1); - std::string block2_hash = get_sha1(block2); - std::string block3_hash = get_sha1(block3); - - // Compose the transfer list to fail the first update. - std::vector transfer_list_fail = { - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - "fail", - }; - - // Mimic a resumed update with the same transfer commands. - std::vector transfer_list_continue = { - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - "move " + block1_hash + " 2,2,3 1 2,0,1", - }; - - std::unordered_map entries = { - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list_fail", android::base::Join(transfer_list_fail, '\n') }, - { "transfer_list_continue", android::base::Join(transfer_list_continue, '\n') }, - }; - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - std::string src_content = block1 + block2 + block3; - TemporaryFile update_file; - ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path)); - std::string script = - "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list_fail"), "new_data", "patch_data"))"; - expect("", script.c_str(), kNoCause, &updater_info); - - // Expect last_command to contain the last stash command. - std::string last_command_content; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content)); - EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content); - std::string updated_contents; - ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents)); - ASSERT_EQ(block1 + block1 + block3, updated_contents); - - // Resume the update, expect the first 'move' to be skipped but the second 'move' to be executed. - ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path)); - std::string script_second_update = - "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list_continue"), "new_data", "patch_data"))"; - expect("t", script_second_update.c_str(), kNoCause, &updater_info); - ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents)); - ASSERT_EQ(block1 + block2 + block1, updated_contents); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - -TEST_F(UpdaterTest, last_command_update_unresumable) { - std::string last_command_file = CacheLocation::location().last_command_file(); - - std::string block1 = std::string(4096, '1'); - std::string block2 = std::string(4096, '2'); - std::string block1_hash = get_sha1(block1); - std::string block2_hash = get_sha1(block2); - - // Construct an unresumable update with source blocks mismatch. - std::vector transfer_list_unresumable = { - "4", "2", "0", "2", "stash " + block1_hash + " 2,0,1", "move " + block2_hash + " 2,1,2 1 2,0,1", - }; - - std::unordered_map entries = { - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list_unresumable", android::base::Join(transfer_list_unresumable, '\n') }, - }; - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - // Set up the last_command_file - ASSERT_TRUE( - android::base::WriteStringToFile("0\nstash " + block1_hash + " 2,0,1", last_command_file)); - - // The last_command_file will be deleted if the update encounters an unresumable failure - // later. - std::string src_content = block1 + block1; - TemporaryFile update_file; - ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path)); - std::string script = - "block_image_update(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list_unresumable"), "new_data", "patch_data"))"; - expect("", script.c_str(), kNoCause, &updater_info); - ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK)); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - -TEST_F(UpdaterTest, last_command_verify) { - std::string last_command_file = CacheLocation::location().last_command_file(); - - std::string block1 = std::string(4096, '1'); - std::string block2 = std::string(4096, '2'); - std::string block3 = std::string(4096, '3'); - std::string block1_hash = get_sha1(block1); - std::string block2_hash = get_sha1(block2); - std::string block3_hash = get_sha1(block3); - - std::vector transfer_list_verify = { - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,0,1 1 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - }; - - std::unordered_map entries = { - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list_verify", android::base::Join(transfer_list_verify, '\n') }, - }; - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - std::string src_content = block1 + block1 + block3; - TemporaryFile update_file; - ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path)); - - ASSERT_TRUE( - android::base::WriteStringToFile("2\nstash " + block3_hash + " 2,2,3", last_command_file)); - - // Expect the verification to succeed and the last_command_file is intact. - std::string script_verify = - "block_image_verify(\"" + std::string(update_file.path) + - R"(", package_extract_file("transfer_list_verify"), "new_data","patch_data"))"; - expect("t", script_verify.c_str(), kNoCause, &updater_info); - - std::string last_command_content; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content)); - EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content); - - // Expect the verification to succeed but last_command_file to be deleted; because the target - // blocks don't have the expected contents for the second move command. - src_content = block1 + block2 + block3; - ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path)); - expect("t", script_verify.c_str(), kNoCause, &updater_info); - ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK)); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp deleted file mode 100644 index 2ef3828ad2..0000000000 --- a/tests/component/verifier_test.cpp +++ /dev/null @@ -1,222 +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 agree to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include "common/test_constants.h" -#include "otautil/SysUtil.h" -#include "verifier.h" - -using namespace std::string_literals; - -class VerifierTest : public testing::TestWithParam> { - protected: - void SetUp() override { - std::vector args = GetParam(); - std::string package = from_testdata_base(args[0]); - if (!memmap.MapFile(package)) { - FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n"; - } - - for (auto it = ++args.cbegin(); it != args.cend(); ++it) { - std::string public_key_file = from_testdata_base("testkey_" + *it + ".txt"); - ASSERT_TRUE(load_keys(public_key_file.c_str(), certs)); - } - } - - MemMapping memmap; - std::vector certs; -}; - -class VerifierSuccessTest : public VerifierTest { -}; - -class VerifierFailureTest : public VerifierTest { -}; - -TEST(VerifierTest, load_keys_multiple_keys) { - std::string testkey_v4; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4)); - - std::string testkey_v3; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3)); - - std::string keys = testkey_v4 + "," + testkey_v3 + "," + testkey_v4; - TemporaryFile key_file1; - ASSERT_TRUE(android::base::WriteStringToFile(keys, key_file1.path)); - std::vector certs; - ASSERT_TRUE(load_keys(key_file1.path, certs)); - ASSERT_EQ(3U, certs.size()); -} - -TEST(VerifierTest, load_keys_invalid_keys) { - std::vector certs; - ASSERT_FALSE(load_keys("/doesntexist", certs)); - - // Empty file. - TemporaryFile key_file1; - ASSERT_FALSE(load_keys(key_file1.path, certs)); - - // Invalid contents. - ASSERT_TRUE(android::base::WriteStringToFile("invalid", key_file1.path)); - ASSERT_FALSE(load_keys(key_file1.path, certs)); - - std::string testkey_v4; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4)); - - // Invalid key version: "v4 ..." => "v6 ...". - std::string invalid_key2(testkey_v4); - invalid_key2[1] = '6'; - TemporaryFile key_file2; - ASSERT_TRUE(android::base::WriteStringToFile(invalid_key2, key_file2.path)); - ASSERT_FALSE(load_keys(key_file2.path, certs)); - - // Invalid key content: inserted extra bytes ",2209831334". - std::string invalid_key3(testkey_v4); - invalid_key3.insert(invalid_key2.size() - 2, ",2209831334"); - TemporaryFile key_file3; - ASSERT_TRUE(android::base::WriteStringToFile(invalid_key3, key_file3.path)); - ASSERT_FALSE(load_keys(key_file3.path, certs)); - - // Invalid key: the last key must not end with an extra ','. - std::string invalid_key4 = testkey_v4 + ","; - TemporaryFile key_file4; - ASSERT_TRUE(android::base::WriteStringToFile(invalid_key4, key_file4.path)); - ASSERT_FALSE(load_keys(key_file4.path, certs)); - - // Invalid key separator. - std::string invalid_key5 = testkey_v4 + ";" + testkey_v4; - TemporaryFile key_file5; - ASSERT_TRUE(android::base::WriteStringToFile(invalid_key5, key_file5.path)); - ASSERT_FALSE(load_keys(key_file5.path, certs)); -} - -TEST(VerifierTest, BadPackage_AlteredFooter) { - std::string testkey_v3; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3)); - TemporaryFile key_file1; - ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path)); - std::vector certs; - ASSERT_TRUE(load_keys(key_file1.path, certs)); - - std::string package; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package)); - ASSERT_EQ(std::string("\xc0\x06\xff\xff\xd2\x06", 6), package.substr(package.size() - 6, 6)); - - // Alter the footer. - package[package.size() - 5] = '\x05'; - ASSERT_EQ(VERIFY_FAILURE, - verify_file(reinterpret_cast(package.data()), package.size(), - certs)); -} - -TEST(VerifierTest, BadPackage_AlteredContent) { - std::string testkey_v3; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3)); - TemporaryFile key_file1; - ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path)); - std::vector certs; - ASSERT_TRUE(load_keys(key_file1.path, certs)); - - std::string package; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package)); - ASSERT_GT(package.size(), static_cast(100)); - - // Alter the content. - std::string altered1(package); - altered1[50] += 1; - ASSERT_EQ(VERIFY_FAILURE, - verify_file(reinterpret_cast(altered1.data()), altered1.size(), - certs)); - - std::string altered2(package); - altered2[10] += 1; - ASSERT_EQ(VERIFY_FAILURE, - verify_file(reinterpret_cast(altered2.data()), altered2.size(), - certs)); -} - -TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) { - std::string testkey_v3; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3)); - - TemporaryFile key_file; - ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file.path)); - std::vector certs; - ASSERT_TRUE(load_keys(key_file.path, certs)); - - // Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369). - std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s; - ASSERT_EQ(VERIFY_FAILURE, verify_file(reinterpret_cast(package.data()), - package.size(), certs)); -} - -TEST_P(VerifierSuccessTest, VerifySucceed) { - ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS); -} - -TEST_P(VerifierFailureTest, VerifyFailure) { - ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_FAILURE); -} - -INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest, - ::testing::Values( - std::vector({"otasigned_v1.zip", "v1"}), - std::vector({"otasigned_v2.zip", "v2"}), - std::vector({"otasigned_v3.zip", "v3"}), - std::vector({"otasigned_v4.zip", "v4"}), - std::vector({"otasigned_v5.zip", "v5"}))); - -INSTANTIATE_TEST_CASE_P(MultiKeySuccess, VerifierSuccessTest, - ::testing::Values( - std::vector({"otasigned_v1.zip", "v1", "v2"}), - std::vector({"otasigned_v2.zip", "v5", "v2"}), - std::vector({"otasigned_v3.zip", "v5", "v1", "v3"}), - std::vector({"otasigned_v4.zip", "v5", "v1", "v4"}), - std::vector({"otasigned_v5.zip", "v4", "v1", "v5"}))); - -INSTANTIATE_TEST_CASE_P(WrongKey, VerifierFailureTest, - ::testing::Values( - std::vector({"otasigned_v1.zip", "v2"}), - std::vector({"otasigned_v2.zip", "v1"}), - std::vector({"otasigned_v3.zip", "v5"}), - std::vector({"otasigned_v4.zip", "v5"}), - std::vector({"otasigned_v5.zip", "v3"}))); - -INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest, - ::testing::Values( - std::vector({"otasigned_v1.zip", "v3"}), - std::vector({"otasigned_v2.zip", "v4"}), - std::vector({"otasigned_v3.zip", "v1"}), - std::vector({"otasigned_v4.zip", "v2"}))); - -INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest, - ::testing::Values( - std::vector({"random.zip", "v1"}), - std::vector({"fake-eocd.zip", "v1"}))); diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp index 64e3b59e65..e1d0771e79 100644 --- a/tests/manual/recovery_test.cpp +++ b/tests/manual/recovery_test.cpp @@ -14,27 +14,22 @@ * limitations under the License. */ -#include +#include +#include #include #include #include +#include #include -#include #include -#include #include #include -#include #include -#include "minui/minui.h" - -static const std::string myFilename = "/data/misc/recovery/inject.txt"; -static const std::string myContent = "Hello World\nWelcome to my recovery\n"; -static const std::string kLocale = "zu"; -static const std::string kResourceTestDir = "/data/nativetest/recovery/"; +static const std::string kInjectTxtFilename = "/data/misc/recovery/inject.txt"; +static const std::string kInjectTxtContent = "Hello World\nWelcome to my recovery\n"; // Failure is expected on systems that do not deliver either the // recovery-persist or recovery-refresh executables. Tests also require @@ -44,9 +39,9 @@ static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename, const char *buf, size_t len, void *arg) { EXPECT_EQ(LOG_ID_SYSTEM, logId); EXPECT_EQ(ANDROID_LOG_INFO, prio); - EXPECT_NE(std::string::npos, myFilename.find(filename)); - EXPECT_EQ(myContent, buf); - EXPECT_EQ(myContent.size(), len); + EXPECT_NE(std::string::npos, kInjectTxtFilename.find(filename)); + EXPECT_EQ(kInjectTxtContent, buf); + EXPECT_EQ(kInjectTxtContent.size(), len); EXPECT_EQ(nullptr, arg); return len; } @@ -59,13 +54,14 @@ TEST(recovery, refresh) { ssize_t ret = __android_log_pmsg_file_read( LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr); if (ret == -ENOENT) { - EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, - myFilename.c_str(), myContent.c_str(), myContent.size())); + EXPECT_LT(0, __android_log_pmsg_file_write( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, kInjectTxtFilename.c_str(), + kInjectTxtContent.c_str(), kInjectTxtContent.size())); - fprintf(stderr, "injected test data, requires two intervening reboots " - "to check for replication\n"); + fprintf(stderr, + "injected test data, requires two intervening reboots to check for replication\n"); } - EXPECT_EQ(static_cast(myContent.size()), ret); + EXPECT_EQ(static_cast(kInjectTxtContent.size()), ret); } // recovery.persist - Requires recovery.inject, then a reboot, then @@ -76,149 +72,18 @@ TEST(recovery, persist) { ssize_t ret = __android_log_pmsg_file_read( LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr); if (ret == -ENOENT) { - EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, - myFilename.c_str(), myContent.c_str(), myContent.size())); + EXPECT_LT(0, __android_log_pmsg_file_write( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, kInjectTxtFilename.c_str(), + kInjectTxtContent.c_str(), kInjectTxtContent.size())); - fprintf(stderr, "injected test data, requires intervening reboot " - "to check for storage\n"); + fprintf(stderr, "injected test data, requires intervening reboot to check for storage\n"); } std::string buf; - EXPECT_TRUE(android::base::ReadFileToString(myFilename, &buf)); - EXPECT_EQ(myContent, buf); - if (access(myFilename.c_str(), F_OK) == 0) { - fprintf(stderr, "Removing persistent test data, " - "check if reconstructed on reboot\n"); - } - EXPECT_EQ(0, unlink(myFilename.c_str())); -} - -std::vector image_dir { - "res-mdpi/images/", - "res-hdpi/images/", - "res-xhdpi/images/", - "res-xxhdpi/images/", - "res-xxxhdpi/images/" -}; - -static int png_filter(const dirent* de) { - if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) { - return 0; + EXPECT_TRUE(android::base::ReadFileToString(kInjectTxtFilename, &buf)); + EXPECT_EQ(kInjectTxtContent, buf); + if (access(kInjectTxtFilename.c_str(), F_OK) == 0) { + fprintf(stderr, "Removing persistent test data, check if reconstructed on reboot\n"); } - return 1; + EXPECT_EQ(0, unlink(kInjectTxtFilename.c_str())); } - -// Find out all png files to test under /data/nativetest/recovery/. -static std::vector add_files() { - std::vector files; - for (const std::string& str : image_dir) { - std::string dir_path = kResourceTestDir + str; - dirent** namelist; - int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort); - if (n == -1) { - printf("Failed to scan dir %s: %s\n", kResourceTestDir.c_str(), strerror(errno)); - return files; - } - if (n == 0) { - printf("No file is added for test in %s\n", kResourceTestDir.c_str()); - } - - while (n--) { - std::string file_path = dir_path + namelist[n]->d_name; - files.push_back(file_path); - free(namelist[n]); - } - free(namelist); - } - return files; -} - -class ResourceTest : public testing::TestWithParam { - public: - static std::vector png_list; - - // Parse a png file and test if it's qualified for the background text image - // under recovery. - void SetUp() override { - std::string file_path = GetParam(); - fp = fopen(file_path.c_str(), "rbe"); - ASSERT_NE(nullptr, fp); - - unsigned char header[8]; - size_t bytesRead = fread(header, 1, sizeof(header), fp); - ASSERT_EQ(sizeof(header), bytesRead); - ASSERT_EQ(0, png_sig_cmp(header, 0, sizeof(header))); - - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - ASSERT_NE(nullptr, png_ptr); - - info_ptr = png_create_info_struct(png_ptr); - ASSERT_NE(nullptr, info_ptr); - - png_init_io(png_ptr, fp); - png_set_sig_bytes(png_ptr, sizeof(header)); - png_read_info(png_ptr, info_ptr); - - int color_type, bit_depth; - png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, - nullptr); - ASSERT_EQ(PNG_COLOR_TYPE_GRAY, color_type) << "Recovery expects grayscale PNG file."; - ASSERT_LT(static_cast(5), width); - ASSERT_LT(static_cast(0), height); - if (bit_depth <= 8) { - // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray. - png_set_expand_gray_1_2_4_to_8(png_ptr); - } - - png_byte channels = png_get_channels(png_ptr, info_ptr); - ASSERT_EQ(1, channels) << "Recovery background text images expects 1-channel PNG file."; - } - - void TearDown() override { - if (png_ptr != nullptr && info_ptr != nullptr) { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - } - - if (fp != nullptr) { - fclose(fp); - } - } - - protected: - png_structp png_ptr; - png_infop info_ptr; - png_uint_32 width, height; - - FILE* fp; -}; - -std::vector ResourceTest::png_list = add_files(); - -TEST_P(ResourceTest, ValidateLocale) { - std::vector row(width); - for (png_uint_32 y = 0; y < height; ++y) { - png_read_row(png_ptr, row.data(), nullptr); - int w = (row[1] << 8) | row[0]; - int h = (row[3] << 8) | row[2]; - int len = row[4]; - EXPECT_LT(0, w); - EXPECT_LT(0, h); - EXPECT_LT(0, len) << "Locale string should be non-empty."; - EXPECT_NE(0, row[5]) << "Locale string is missing."; - - ASSERT_GT(height, y + 1 + h) << "Locale: " << kLocale << " is not found in the file."; - char* loc = reinterpret_cast(&row[5]); - if (matches_locale(loc, kLocale.c_str())) { - EXPECT_TRUE(android::base::StartsWith(loc, kLocale)); - break; - } else { - for (int i = 0; i < h; ++i, ++y) { - png_read_row(png_ptr, row.data(), nullptr); - } - } - } -} - -INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourceTest, - ::testing::ValuesIn(ResourceTest::png_list.cbegin(), - ResourceTest::png_list.cend())); diff --git a/tests/testdata/battery_scale.png b/tests/testdata/battery_scale.png new file mode 100644 index 0000000000..2ae8f0fd7e Binary files /dev/null and b/tests/testdata/battery_scale.png differ diff --git a/tests/testdata/font.png b/tests/testdata/font.png new file mode 100644 index 0000000000..d95408a93d Binary files /dev/null and b/tests/testdata/font.png differ diff --git a/tests/testdata/gzipped_source b/tests/testdata/gzipped_source new file mode 100644 index 0000000000..6d425d059b Binary files /dev/null and b/tests/testdata/gzipped_source differ diff --git a/tests/testdata/gzipped_target b/tests/testdata/gzipped_target new file mode 100644 index 0000000000..5621262861 Binary files /dev/null and b/tests/testdata/gzipped_target differ diff --git a/tests/testdata/jarsigned.zip b/tests/testdata/jarsigned.zip deleted file mode 100644 index 8b1ef8bddb..0000000000 Binary files a/tests/testdata/jarsigned.zip and /dev/null differ diff --git a/tests/testdata/loop00000.png b/tests/testdata/loop00000.png new file mode 100644 index 0000000000..0e11c01000 Binary files /dev/null and b/tests/testdata/loop00000.png differ diff --git a/tests/testdata/new.file b/tests/testdata/new.file deleted file mode 100644 index cdeb8fd505..0000000000 Binary files a/tests/testdata/new.file and /dev/null differ diff --git a/tests/testdata/old.file b/tests/testdata/old.file deleted file mode 100644 index 166c8732eb..0000000000 Binary files a/tests/testdata/old.file and /dev/null differ diff --git a/tests/testdata/otasigned_4096bits.zip b/tests/testdata/otasigned_4096bits.zip new file mode 100644 index 0000000000..5016dfc9a7 Binary files /dev/null and b/tests/testdata/otasigned_4096bits.zip differ diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff deleted file mode 100644 index b78d385736..0000000000 Binary files a/tests/testdata/patch.bsdiff and /dev/null differ diff --git a/tests/testdata/testkey_4096bits.x509.pem b/tests/testdata/testkey_4096bits.x509.pem new file mode 100644 index 0000000000..cba30d61b8 --- /dev/null +++ b/tests/testdata/testkey_4096bits.x509.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGADCCA+igAwIBAgIJAJiRMVvanGUaMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g +VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE +AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0xODEwMzAxMjEzNTFaFw00NjAzMTcxMjEzNTFaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G +A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAL3ghKA8Gz9qOORY8gMY4wlB2tCJLDUO2tFG +LVK1UphtQMp+YEcz/0VQKVV7de7z6V4EMQ5P1HbxHOsjcKn/zXAl4YgFt7b5kZbC +bpNK4CYHEfho3j6fpYtq5d9q8rIA2kI0uZkkqPy1zXKTl2C2PjOoAnLQRk5xBVQG +M10/wYsf7yX36mSWoJJwKPp/EzVFpA+hX8HpljeIiZ6CFzKwJdqv9zO/xzfp6NsX +Tv5EGdkDxmw3qQqKgyl8dLMTZ/2zNfvVOMeZDusEPDF7A/lbU1byLWrKQdCzVb40 +yc7BCSRGYwM29R/byOcgD+lslwKSGzgzNmQXICt1tXz9bSJR8qh4tlAaiRc3ZKBe +hJWIFGkGtD/cDGtDE5DbNAOz6CdSDdE2XN0Qf0cfN1RHVE6fo2FtFicRRVuFBt8M +2cbQ7bzmEvtHD6W6dsf120FH7gppXKmnhMx1WazpxR2QltbiYDTy2ZZi4paS/jDB +fL9gMCWp3Ohg2y74NGfUw5CQWQsDpcki6I7RvwClBCyOV51LHn5LE/nY4DkVrZxk +Pw0/YrTWz5J5PbdMetTuIunE4ec4lm8nZnh1ET+2MHx2+RoyF5vBs4rp1KHHRaEA +veD2AfQOWxz7kOG9+akFot7n+QoWEGdwY0mJ9jsO/IITCjv3VbD7o0OoJv1R2AW5 +sK2KQ4PDAgMBAAGjUzBRMB0GA1UdDgQWBBT2EbrayXGhY6VCvSlLtRNyjW9ceDAf +BgNVHSMEGDAWgBT2EbrayXGhY6VCvSlLtRNyjW9ceDAPBgNVHRMBAf8EBTADAQH/ +MA0GCSqGSIb3DQEBCwUAA4ICAQC7SsWap9zDKmuR0qMUZ6wlualnag0hUG1jZHQP +t63KO6LmNNMSuXRX60Zcq6WWzgLOyoT4HqHZZ47Jamfb4XQQcnWMMW0tJ3pDtTkz +dZILBInHJO8QPYI8Du6XWsDLSvMajq6ueBtO3NdcgsNL7eiHf3WoOtajLZxFM94Z +MESkUQOIsqHolYeTMHLTsuGkX1CK2Zw3Xn18bUSTYwZCHa6mYH00ItUBfetGCnWh +Y7bth/R15Cc+hocSB7ZsOa/R5kDyDdFDIKrnV5nH5Yd7CryrYC6Ac5UarYrxSJTq +eKPwqUlJB/tJW/lvdLt8YaURbFGzf/ZqU12zZRafYjmMjcQvfpzMoDSnbvHTA9IR +ZGO7dwhwykoSaL4/8LWde49xQUq6F2pQBRmEr+7mTzml1MaM5cWEk5emkCMXgLog +k+c56CAk1EdM1teWik7wR0TIqkkYyYJHTSg61GkXUIXrZJ6iYx2ejDg1+QTPm9rU +Yr7nP52gVkQuUAX1+xB6wKLSDizQJw8SNiUGXl5+2vwV6+0BI3/CXlQ8I/nRPBC1 +oqOIkRSbE+IF7DP9QvYuNG/3bZZQ8LUVeHxqI5Mq8K2VIJZd95AIwPNMH34SaDGz +9xjG28Fq4ZkuDP0pCsHM9d2XEwK5PEVS18WW5fJ/QcJKMno4IPTB70ZBBjVzv6Y+ +MYjOrw== +-----END CERTIFICATE----- diff --git a/tests/testdata/unsigned.zip b/tests/testdata/unsigned.zip deleted file mode 100644 index 24e3eadacd..0000000000 Binary files a/tests/testdata/unsigned.zip and /dev/null differ diff --git a/tests/unit/applypatch_modes_test.cpp b/tests/unit/applypatch_modes_test.cpp new file mode 100644 index 0000000000..08414b7961 --- /dev/null +++ b/tests/unit/applypatch_modes_test.cpp @@ -0,0 +1,198 @@ +/* + * 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 agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "applypatch/applypatch_modes.h" +#include "common/test_constants.h" +#include "otautil/paths.h" +#include "otautil/print_sha1.h" +#include "otautil/sysutil.h" + +using namespace std::string_literals; + +// Loads a given partition and returns a string of form "EMMC:name:size:hash". +static std::string GetEmmcTargetString(const std::string& filename, + const std::string& display_name = "") { + std::string data; + if (!android::base::ReadFileToString(filename, &data)) { + PLOG(ERROR) << "Failed to read " << filename; + return {}; + } + + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast(data.c_str()), data.size(), digest); + + return "EMMC:"s + (display_name.empty() ? filename : display_name) + ":" + + std::to_string(data.size()) + ":" + print_sha1(digest); +} + +class ApplyPatchModesTest : public ::testing::Test { + protected: + void SetUp() override { + source = GetEmmcTargetString(from_testdata_base("boot.img")); + ASSERT_FALSE(source.empty()); + + std::string recovery_file = from_testdata_base("recovery.img"); + recovery = GetEmmcTargetString(recovery_file); + ASSERT_FALSE(recovery.empty()); + + ASSERT_TRUE(android::base::WriteStringToFile("", patched_file_.path)); + target = GetEmmcTargetString(recovery_file, patched_file_.path); + ASSERT_FALSE(target.empty()); + + Paths::Get().set_cache_temp_source(cache_source_.path); + } + + std::string source; + std::string target; + std::string recovery; + + private: + TemporaryFile cache_source_; + TemporaryFile patched_file_; +}; + +static int InvokeApplyPatchModes(const std::vector& args) { + auto args_to_call = StringVectorToNullTerminatedArray(args); + return applypatch_modes(args_to_call.size() - 1, args_to_call.data()); +} + +static void VerifyPatchedTarget(const std::string& target) { + std::vector pieces = android::base::Split(target, ":"); + ASSERT_EQ(4, pieces.size()); + ASSERT_EQ("EMMC", pieces[0]); + + std::string patched_emmc = GetEmmcTargetString(pieces[1]); + ASSERT_FALSE(patched_emmc.empty()); + ASSERT_EQ(target, patched_emmc); +} + +TEST_F(ApplyPatchModesTest, InvalidArgs) { + // At least two args (including the filename). + ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch" })); + + // Unrecognized args. + ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "-x" })); +} + +TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) { + std::vector args{ + "applypatch", + "--bonus", + from_testdata_base("bonus.file"), + "--patch", + from_testdata_base("recovery-from-boot.p"), + "--target", + target, + "--source", + source, + }; + ASSERT_EQ(0, InvokeApplyPatchModes(args)); + VerifyPatchedTarget(target); +} + +// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has +// everything). +TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithoutBonusFile) { + std::vector args{ + "applypatch", "--patch", from_testdata_base("recovery-from-boot-with-bonus.p"), + "--target", target, "--source", + source, + }; + + ASSERT_EQ(0, InvokeApplyPatchModes(args)); + VerifyPatchedTarget(target); +} + +// Ensures that applypatch works with a bsdiff based recovery-from-boot.p. +TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) { + // Generate the bsdiff patch of recovery-from-boot.p. + std::string src_content; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("boot.img"), &src_content)); + + std::string tgt_content; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("recovery.img"), &tgt_content)); + + TemporaryFile patch_file; + ASSERT_EQ(0, + bsdiff::bsdiff(reinterpret_cast(src_content.data()), src_content.size(), + reinterpret_cast(tgt_content.data()), tgt_content.size(), + patch_file.path, nullptr)); + + std::vector args{ + "applypatch", "--patch", patch_file.path, "--target", target, "--source", source, + }; + ASSERT_EQ(0, InvokeApplyPatchModes(args)); + VerifyPatchedTarget(target); +} + +TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) { + // Invalid bonus file. + std::vector args{ + "applypatch", "--bonus", "/doesntexist", "--patch", from_testdata_base("recovery-from-boot.p"), + "--target", target, "--source", source, + }; + ASSERT_NE(0, InvokeApplyPatchModes(args)); + + // With bonus file, but missing args. + ASSERT_NE(0, + InvokeApplyPatchModes({ "applypatch", "--bonus", from_testdata_base("bonus.file") })); +} + +TEST_F(ApplyPatchModesTest, FlashMode) { + std::vector args{ + "applypatch", "--flash", from_testdata_base("recovery.img"), "--target", target, + }; + ASSERT_EQ(0, InvokeApplyPatchModes(args)); + VerifyPatchedTarget(target); +} + +TEST_F(ApplyPatchModesTest, FlashModeInvalidArgs) { + std::vector args{ + "applypatch", "--bonus", from_testdata_base("bonus.file"), "--flash", source, + "--target", target, + }; + ASSERT_NE(0, InvokeApplyPatchModes(args)); +} + +TEST_F(ApplyPatchModesTest, CheckMode) { + ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", recovery })); + ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", source })); +} + +TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) { + ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "--check" })); +} + +TEST_F(ApplyPatchModesTest, CheckModeNonEmmcTarget) { + ASSERT_NE(0, InvokeApplyPatchModes({ "applypatch", "--check", from_testdata_base("boot.img") })); +} + +TEST_F(ApplyPatchModesTest, ShowLicenses) { + ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--license" })); +} diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp new file mode 100644 index 0000000000..218a224f8f --- /dev/null +++ b/tests/unit/applypatch_test.cpp @@ -0,0 +1,290 @@ +/* + * 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 agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "applypatch/applypatch.h" +#include "common/test_constants.h" +#include "edify/expr.h" +#include "otautil/paths.h" +#include "otautil/print_sha1.h" + +using namespace std::string_literals; + +class ApplyPatchTest : public ::testing::Test { + protected: + void SetUp() override { + source_file = from_testdata_base("boot.img"); + FileContents boot_fc; + ASSERT_TRUE(LoadFileContents(source_file, &boot_fc)); + source_size = boot_fc.data.size(); + source_sha1 = print_sha1(boot_fc.sha1); + + target_file = from_testdata_base("recovery.img"); + FileContents recovery_fc; + ASSERT_TRUE(LoadFileContents(target_file, &recovery_fc)); + target_size = recovery_fc.data.size(); + target_sha1 = print_sha1(recovery_fc.sha1); + + source_partition = Partition(source_file, source_size, source_sha1); + target_partition = Partition(partition_file.path, target_size, target_sha1); + + srand(time(nullptr)); + bad_sha1_a = android::base::StringPrintf("%040x", rand()); + bad_sha1_b = android::base::StringPrintf("%040x", rand()); + + // Reset the cache backup file. + Paths::Get().set_cache_temp_source(cache_temp_source.path); + } + + void TearDown() override { + ASSERT_TRUE(android::base::RemoveFileIfExists(cache_temp_source.path)); + } + + std::string source_file; + std::string source_sha1; + size_t source_size; + + std::string target_file; + std::string target_sha1; + size_t target_size; + + std::string bad_sha1_a; + std::string bad_sha1_b; + + Partition source_partition; + Partition target_partition; + + private: + TemporaryFile partition_file; + TemporaryFile cache_temp_source; +}; + +TEST_F(ApplyPatchTest, CheckPartition) { + ASSERT_TRUE(CheckPartition(source_partition)); +} + +TEST_F(ApplyPatchTest, CheckPartition_Mismatching) { + ASSERT_FALSE(CheckPartition(Partition(source_file, target_size, target_sha1))); + ASSERT_FALSE(CheckPartition(Partition(source_file, source_size, bad_sha1_a))); + + ASSERT_FALSE(CheckPartition(Partition(source_file, source_size - 1, source_sha1))); + ASSERT_FALSE(CheckPartition(Partition(source_file, source_size + 1, source_sha1))); +} + +TEST_F(ApplyPatchTest, PatchPartitionCheck) { + ASSERT_TRUE(PatchPartitionCheck(target_partition, source_partition)); + + ASSERT_TRUE( + PatchPartitionCheck(Partition(source_file, source_size - 1, source_sha1), source_partition)); + + ASSERT_TRUE( + PatchPartitionCheck(Partition(source_file, source_size + 1, source_sha1), source_partition)); +} + +TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup) { + ASSERT_FALSE( + PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1))); + + Paths::Get().set_cache_temp_source(source_file); + ASSERT_TRUE( + PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1))); +} + +TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup_BothCorrupted) { + ASSERT_FALSE( + PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1))); + + Paths::Get().set_cache_temp_source(target_file); + ASSERT_FALSE( + PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1))); +} + +TEST_F(ApplyPatchTest, PatchPartition) { + FileContents patch_fc; + ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot.p"), &patch_fc)); + Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend())); + + FileContents bonus_fc; + ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc)); + Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend())); + + ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus, false)); +} + +// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has +// everything). +TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) { + FileContents patch_fc; + ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc)); + Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend())); + + ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr, false)); +} + +class FreeCacheTest : public ::testing::Test { + protected: + static constexpr size_t PARTITION_SIZE = 4096 * 10; + + // Returns a sorted list of files in |dirname|. + static std::vector FindFilesInDir(const std::string& dirname) { + std::vector file_list; + + std::unique_ptr d(opendir(dirname.c_str()), closedir); + struct dirent* de; + while ((de = readdir(d.get())) != 0) { + std::string path = dirname + "/" + de->d_name; + + struct stat st; + if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { + file_list.emplace_back(de->d_name); + } + } + + std::sort(file_list.begin(), file_list.end()); + return file_list; + } + + void AddFilesToDir(const std::string& dir, const std::vector& files) { + std::string zeros(4096, 0); + for (const auto& file : files) { + temporary_files_.push_back(dir + "/" + file); + ASSERT_TRUE(android::base::WriteStringToFile(zeros, temporary_files_.back())); + } + } + + void SetUp() override { + Paths::Get().set_cache_log_directory(mock_log_dir.path); + temporary_files_.clear(); + } + + void TearDown() override { + for (const auto& file : temporary_files_) { + ASSERT_TRUE(android::base::RemoveFileIfExists(file)); + } + } + + // A mock method to calculate the free space. It assumes the partition has a total size of 40960 + // bytes and all files are 4096 bytes in size. + static size_t MockFreeSpaceChecker(const std::string& dirname) { + std::vector files = FindFilesInDir(dirname); + return PARTITION_SIZE - 4096 * files.size(); + } + + TemporaryDir mock_cache; + TemporaryDir mock_log_dir; + + private: + std::vector temporary_files_; +}; + +TEST_F(FreeCacheTest, FreeCacheSmoke) { + std::vector files = { "file1", "file2", "file3" }; + AddFilesToDir(mock_cache.path, files); + ASSERT_EQ(files, FindFilesInDir(mock_cache.path)); + ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path)); + + ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_cache.path, MockFreeSpaceChecker)); + + ASSERT_EQ(std::vector{ "file3" }, FindFilesInDir(mock_cache.path)); + ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_cache.path)); +} + +TEST_F(FreeCacheTest, FreeCacheFreeSpaceCheckerError) { + std::vector files{ "file1", "file2", "file3" }; + AddFilesToDir(mock_cache.path, files); + ASSERT_EQ(files, FindFilesInDir(mock_cache.path)); + ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path)); + + ASSERT_FALSE( + RemoveFilesInDirectory(4096 * 9, mock_cache.path, [](const std::string&) { return -1; })); +} + +TEST_F(FreeCacheTest, FreeCacheOpenFile) { + std::vector files = { "file1", "file2" }; + AddFilesToDir(mock_cache.path, files); + ASSERT_EQ(files, FindFilesInDir(mock_cache.path)); + ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_cache.path)); + + std::string file1_path = mock_cache.path + "/file1"s; + android::base::unique_fd fd(open(file1_path.c_str(), O_RDONLY)); + + // file1 can't be deleted as it's opened by us. + ASSERT_FALSE(RemoveFilesInDirectory(4096 * 10, mock_cache.path, MockFreeSpaceChecker)); + + ASSERT_EQ(std::vector{ "file1" }, FindFilesInDir(mock_cache.path)); +} + +TEST_F(FreeCacheTest, FreeCacheLogsSmoke) { + std::vector log_files = { "last_log", "last_log.1", "last_kmsg.2", "last_log.5", + "last_log.10" }; + AddFilesToDir(mock_log_dir.path, log_files); + ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path)); + + ASSERT_TRUE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker)); + + // Logs with a higher index will be deleted first + std::vector expected = { "last_log", "last_log.1" }; + ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path)); + ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_log_dir.path)); +} + +TEST_F(FreeCacheTest, FreeCacheLogsStringComparison) { + std::vector log_files = { "last_log.1", "last_kmsg.1", "last_log.not_number", + "last_kmsgrandom" }; + AddFilesToDir(mock_log_dir.path, log_files); + ASSERT_EQ(4096 * 6, MockFreeSpaceChecker(mock_log_dir.path)); + + ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_log_dir.path, MockFreeSpaceChecker)); + + // Logs with incorrect format will be deleted first; and the last_kmsg with the same index is + // deleted before last_log. + std::vector expected = { "last_log.1" }; + ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path)); + ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_log_dir.path)); +} + +TEST_F(FreeCacheTest, FreeCacheLogsOtherFiles) { + std::vector log_files = { "last_install", "command", "block.map", "last_log", + "last_kmsg.1" }; + AddFilesToDir(mock_log_dir.path, log_files); + ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path)); + + ASSERT_FALSE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker)); + + // Non log files in /cache/recovery won't be deleted. + std::vector expected = { "block.map", "command", "last_install" }; + ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path)); +} diff --git a/tests/unit/asn1_decoder_test.cpp b/tests/unit/asn1_decoder_test.cpp index b334a655b5..d94dd4353b 100644 --- a/tests/unit/asn1_decoder_test.cpp +++ b/tests/unit/asn1_decoder_test.cpp @@ -20,7 +20,7 @@ #include -#include "asn1_decoder.h" +#include "private/asn1_decoder.h" TEST(Asn1DecoderTest, Empty_Failure) { uint8_t empty[] = {}; diff --git a/tests/unit/battery_utils_test.cpp b/tests/unit/battery_utils_test.cpp new file mode 100644 index 0000000000..55639fdb5f --- /dev/null +++ b/tests/unit/battery_utils_test.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "recovery_utils/battery_utils.h" + +TEST(BatteryInfoTest, GetBatteryInfo) { + auto info = GetBatteryInfo(); + // 0 <= capacity <= 100 + ASSERT_LE(0, info.capacity); + ASSERT_LE(info.capacity, 100); +} diff --git a/tests/component/bootloader_message_test.cpp b/tests/unit/bootloader_message_test.cpp similarity index 96% rename from tests/component/bootloader_message_test.cpp rename to tests/unit/bootloader_message_test.cpp index 6cc59a4954..731c8feb7c 100644 --- a/tests/component/bootloader_message_test.cpp +++ b/tests/unit/bootloader_message_test.cpp @@ -15,13 +15,18 @@ */ #include +#include #include +#include #include -#include #include #include +using namespace std::string_literals; + +extern void SetMiscBlockDeviceForTest(std::string_view misc_device); + TEST(BootloaderMessageTest, read_and_write_bootloader_message) { TemporaryFile temp_misc; @@ -113,4 +118,3 @@ TEST(BootloaderMessageTest, update_bootloader_message_recovery_options_long) { ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'), std::string(boot.reserved, sizeof(boot.reserved))); } - diff --git a/tests/unit/commands_test.cpp b/tests/unit/commands_test.cpp new file mode 100644 index 0000000000..8a54df7038 --- /dev/null +++ b/tests/unit/commands_test.cpp @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include "otautil/print_sha1.h" +#include "otautil/rangeset.h" +#include "private/commands.h" + +TEST(CommandsTest, ParseType) { + ASSERT_EQ(Command::Type::ZERO, Command::ParseType("zero")); + ASSERT_EQ(Command::Type::NEW, Command::ParseType("new")); + ASSERT_EQ(Command::Type::ERASE, Command::ParseType("erase")); + ASSERT_EQ(Command::Type::MOVE, Command::ParseType("move")); + ASSERT_EQ(Command::Type::BSDIFF, Command::ParseType("bsdiff")); + ASSERT_EQ(Command::Type::IMGDIFF, Command::ParseType("imgdiff")); + ASSERT_EQ(Command::Type::STASH, Command::ParseType("stash")); + ASSERT_EQ(Command::Type::FREE, Command::ParseType("free")); + ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, Command::ParseType("compute_hash_tree")); +} + +TEST(CommandsTest, ParseType_InvalidCommand) { + ASSERT_EQ(Command::Type::LAST, Command::ParseType("foo")); + ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar")); +} + +TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly) { + const std::vector tokens{ + "4,569884,569904,591946,592043", + "117", + "4,566779,566799,591946,592043", + }; + TargetInfo target; + SourceInfo source; + std::string err; + ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo( + tokens, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target, + "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err)); + ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 569884, 569904 }, { 591946, 592043 } })), + target); + ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), {}, {}), + source); + ASSERT_EQ(117, source.blocks()); +} + +TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly) { + const std::vector tokens{ + "2,350729,350731", + "2", + "-", + "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15:2,0,2", + }; + TargetInfo target; + SourceInfo source; + std::string err; + ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo( + tokens, "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", &target, + "1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", &source, &err)); + ASSERT_EQ( + TargetInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 350729, 350731 } })), + target); + ASSERT_EQ( + SourceInfo("1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", {}, {}, + { + StashInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 0, 2 } })), + }), + source); + ASSERT_EQ(2, source.blocks()); +} + +TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes) { + const std::vector tokens{ + "4,611641,611643,636981,637075", + "96", + "4,636981,637075,770665,770666", + "4,0,94,95,96", + "9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95", + }; + TargetInfo target; + SourceInfo source; + std::string err; + ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo( + tokens, "4734d1b241eb3d0f993714aaf7d665fae43772b6", &target, + "a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", &source, &err)); + ASSERT_EQ(TargetInfo("4734d1b241eb3d0f993714aaf7d665fae43772b6", + RangeSet({ { 611641, 611643 }, { 636981, 637075 } })), + target); + ASSERT_EQ(SourceInfo( + "a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", + RangeSet({ { 636981, 637075 }, { 770665, 770666 } }), // source ranges + RangeSet({ { 0, 94 }, { 95, 96 } }), // source location + { + StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", RangeSet({ { 94, 95 } })), + }), + source); + ASSERT_EQ(96, source.blocks()); +} + +TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput) { + const std::vector tokens{ + "4,611641,611643,636981,637075", + "96", + "4,636981,637075,770665,770666", + "4,0,94,95,96", + "9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95", + }; + TargetInfo target; + SourceInfo source; + std::string err; + + // Mismatching block count. + { + std::vector tokens_copy(tokens); + tokens_copy[1] = "97"; + ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo( + tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target, + "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err)); + } + + // Excess stashes (causing block count mismatch). + { + std::vector tokens_copy(tokens); + tokens_copy.push_back("e145a2f83a33334714ac65e34969c1f115e54a6f:2,0,22"); + ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo( + tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target, + "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err)); + } + + // Invalid args. + for (size_t i = 0; i < tokens.size(); i++) { + TargetInfo target; + SourceInfo source; + std::string err; + ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo( + std::vector(tokens.cbegin() + i + 1, tokens.cend()), + "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target, + "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err)); + } +} + +TEST(CommandsTest, Parse_EmptyInput) { + std::string err; + ASSERT_FALSE(Command::Parse("", 0, &err)); + ASSERT_EQ("invalid type", err); +} + +TEST(CommandsTest, Parse_ABORT_Allowed) { + Command::abort_allowed_ = true; + + const std::string input{ "abort" }; + std::string err; + Command command = Command::Parse(input, 0, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(TargetInfo(), command.target()); + ASSERT_EQ(SourceInfo(), command.source()); + ASSERT_EQ(StashInfo(), command.stash()); + ASSERT_EQ(PatchInfo(), command.patch()); +} + +TEST(CommandsTest, Parse_ABORT_NotAllowed) { + const std::string input{ "abort" }; + std::string err; + Command command = Command::Parse(input, 0, &err); + ASSERT_FALSE(command); +} + +TEST(CommandsTest, Parse_BSDIFF) { + const std::string input{ + "bsdiff 0 148 " + "f201a4e04bd3860da6ad47b957ef424d58a58f8c 9d5d223b4bc5c45dbd25a799c4f1a98466731599 " + "4,565704,565752,566779,566799 " + "68 4,64525,64545,565704,565752" + }; + std::string err; + Command command = Command::Parse(input, 1, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::BSDIFF, command.type()); + ASSERT_EQ(1, command.index()); + ASSERT_EQ(input, command.cmdline()); + + ASSERT_EQ(TargetInfo("9d5d223b4bc5c45dbd25a799c4f1a98466731599", + RangeSet({ { 565704, 565752 }, { 566779, 566799 } })), + command.target()); + ASSERT_EQ(SourceInfo("f201a4e04bd3860da6ad47b957ef424d58a58f8c", + RangeSet({ { 64525, 64545 }, { 565704, 565752 } }), RangeSet(), {}), + command.source()); + ASSERT_EQ(StashInfo(), command.stash()); + ASSERT_EQ(PatchInfo(0, 148), command.patch()); +} + +TEST(CommandsTest, Parse_ERASE) { + const std::string input{ "erase 2,5,10" }; + std::string err; + Command command = Command::Parse(input, 2, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::ERASE, command.type()); + ASSERT_EQ(2, command.index()); + ASSERT_EQ(input, command.cmdline()); + + ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 5, 10 } })), command.target()); + ASSERT_EQ(SourceInfo(), command.source()); + ASSERT_EQ(StashInfo(), command.stash()); + ASSERT_EQ(PatchInfo(), command.patch()); +} + +TEST(CommandsTest, Parse_FREE) { + const std::string input{ "free hash1" }; + std::string err; + Command command = Command::Parse(input, 3, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::FREE, command.type()); + ASSERT_EQ(3, command.index()); + ASSERT_EQ(input, command.cmdline()); + + ASSERT_EQ(TargetInfo(), command.target()); + ASSERT_EQ(SourceInfo(), command.source()); + ASSERT_EQ(StashInfo("hash1", RangeSet()), command.stash()); + ASSERT_EQ(PatchInfo(), command.patch()); +} + +TEST(CommandsTest, Parse_IMGDIFF) { + const std::string input{ + "imgdiff 29629269 185 " + "a6b1c49aed1b57a2aab1ec3e1505b945540cd8db 51978f65035f584a8ef7afa941dacb6d5e862164 " + "2,90851,90852 " + "1 2,90851,90852" + }; + std::string err; + Command command = Command::Parse(input, 4, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::IMGDIFF, command.type()); + ASSERT_EQ(4, command.index()); + ASSERT_EQ(input, command.cmdline()); + + ASSERT_EQ(TargetInfo("51978f65035f584a8ef7afa941dacb6d5e862164", RangeSet({ { 90851, 90852 } })), + command.target()); + ASSERT_EQ(SourceInfo("a6b1c49aed1b57a2aab1ec3e1505b945540cd8db", RangeSet({ { 90851, 90852 } }), + RangeSet(), {}), + command.source()); + ASSERT_EQ(StashInfo(), command.stash()); + ASSERT_EQ(PatchInfo(29629269, 185), command.patch()); +} + +TEST(CommandsTest, Parse_MOVE) { + const std::string input{ + "move 1d74d1a60332fd38cf9405f1bae67917888da6cb " + "4,569884,569904,591946,592043 117 4,566779,566799,591946,592043" + }; + std::string err; + Command command = Command::Parse(input, 5, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::MOVE, command.type()); + ASSERT_EQ(5, command.index()); + ASSERT_EQ(input, command.cmdline()); + + ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 569884, 569904 }, { 591946, 592043 } })), + command.target()); + ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), RangeSet(), {}), + command.source()); + ASSERT_EQ(StashInfo(), command.stash()); + ASSERT_EQ(PatchInfo(), command.patch()); +} + +TEST(CommandsTest, Parse_NEW) { + const std::string input{ "new 4,3,5,10,12" }; + std::string err; + Command command = Command::Parse(input, 6, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::NEW, command.type()); + ASSERT_EQ(6, command.index()); + ASSERT_EQ(input, command.cmdline()); + + ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 3, 5 }, { 10, 12 } })), command.target()); + ASSERT_EQ(SourceInfo(), command.source()); + ASSERT_EQ(StashInfo(), command.stash()); + ASSERT_EQ(PatchInfo(), command.patch()); +} + +TEST(CommandsTest, Parse_STASH) { + const std::string input{ "stash hash1 2,5,10" }; + std::string err; + Command command = Command::Parse(input, 7, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::STASH, command.type()); + ASSERT_EQ(7, command.index()); + ASSERT_EQ(input, command.cmdline()); + + ASSERT_EQ(TargetInfo(), command.target()); + ASSERT_EQ(SourceInfo(), command.source()); + ASSERT_EQ(StashInfo("hash1", RangeSet({ { 5, 10 } })), command.stash()); + ASSERT_EQ(PatchInfo(), command.patch()); +} + +TEST(CommandsTest, Parse_ZERO) { + const std::string input{ "zero 2,1,5" }; + std::string err; + Command command = Command::Parse(input, 8, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::ZERO, command.type()); + ASSERT_EQ(8, command.index()); + ASSERT_EQ(input, command.cmdline()); + + ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 1, 5 } })), command.target()); + ASSERT_EQ(SourceInfo(), command.source()); + ASSERT_EQ(StashInfo(), command.stash()); + ASSERT_EQ(PatchInfo(), command.patch()); +} + +TEST(CommandsTest, Parse_COMPUTE_HASH_TREE) { + const std::string input{ "compute_hash_tree 2,0,1 2,3,4 sha1 unknown-salt unknown-root-hash" }; + std::string err; + Command command = Command::Parse(input, 9, &err); + ASSERT_TRUE(command); + + ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, command.type()); + ASSERT_EQ(9, command.index()); + ASSERT_EQ(input, command.cmdline()); + + HashTreeInfo expected_info(RangeSet({ { 0, 1 } }), RangeSet({ { 3, 4 } }), "sha1", "unknown-salt", + "unknown-root-hash"); + ASSERT_EQ(expected_info, command.hash_tree_info()); + ASSERT_EQ(TargetInfo(), command.target()); + ASSERT_EQ(SourceInfo(), command.source()); + ASSERT_EQ(StashInfo(), command.stash()); + ASSERT_EQ(PatchInfo(), command.patch()); +} + +TEST(CommandsTest, Parse_InvalidNumberOfArgs) { + Command::abort_allowed_ = true; + + // Note that the case of having excess args in BSDIFF, IMGDIFF and MOVE is covered by + // ParseTargetInfoAndSourceInfo_InvalidInput. + std::vector inputs{ + "abort foo", + "bsdiff", + "compute_hash_tree, 2,0,1 2,0,1 unknown-algorithm unknown-salt", + "erase", + "erase 4,3,5,10,12 hash1", + "free", + "free id1 id2", + "imgdiff", + "move", + "new", + "new 4,3,5,10,12 hash1", + "stash", + "stash id1", + "stash id1 4,3,5,10,12 id2", + "zero", + "zero 4,3,5,10,12 hash2", + }; + for (const auto& input : inputs) { + std::string err; + ASSERT_FALSE(Command::Parse(input, 0, &err)); + } +} + +TEST(SourceInfoTest, Overlaps) { + ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {}) + .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 7, 9 }, { 16, 20 } })))); + + ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {}) + .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 4, 7 }, { 16, 23 } })))); + + ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {}) + .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 9, 16 } })))); +} + +TEST(SourceInfoTest, Overlaps_EmptySourceOrTarget) { + ASSERT_FALSE(SourceInfo().Overlaps(TargetInfo())); + + ASSERT_FALSE(SourceInfo().Overlaps( + TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", RangeSet({ { 7, 9 }, { 16, 20 } })))); + + ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {}) + .Overlaps(TargetInfo())); +} + +TEST(SourceInfoTest, Overlaps_WithStashes) { + ASSERT_FALSE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", + RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges + RangeSet({ { 0, 94 }, { 95, 96 } }), // source location + { StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", + RangeSet({ { 94, 95 } })) }) + .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 175, 265 } })))); + + ASSERT_TRUE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", + RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges + RangeSet({ { 0, 94 }, { 95, 96 } }), // source location + { StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", + RangeSet({ { 94, 95 } })) }) + .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", + RangeSet({ { 265, 266 } })))); +} + +// The block size should be specified by the caller of ReadAll (i.e. from Command instance during +// normal run). +constexpr size_t kBlockSize = 4096; + +TEST(SourceInfoTest, ReadAll) { + // "2727756cfee3fbfe24bf5650123fd7743d7b3465" is the SHA-1 hex digest of 8192 * 'a'. + const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {}, + {}); + auto block_reader = [](const RangeSet& src, std::vector* block_buffer) -> int { + std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a'); + return 0; + }; + auto stash_reader = [](const std::string&, std::vector*) -> int { return 0; }; + std::vector buffer(source.blocks() * kBlockSize); + ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader)); + ASSERT_EQ(source.blocks() * kBlockSize, buffer.size()); + + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(buffer.data(), buffer.size(), digest); + ASSERT_EQ(source.hash(), print_sha1(digest)); +} + +TEST(SourceInfoTest, ReadAll_WithStashes) { + const SourceInfo source( + // SHA-1 hex digest of 8192 * 'a' + 4096 * 'b'. + "ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }), + { StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) }); + auto block_reader = [](const RangeSet& src, std::vector* block_buffer) -> int { + std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a'); + return 0; + }; + auto stash_reader = [](const std::string&, std::vector* stash_buffer) -> int { + std::fill_n(stash_buffer->begin(), kBlockSize, 'b'); + return 0; + }; + std::vector buffer(source.blocks() * kBlockSize); + ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader)); + ASSERT_EQ(source.blocks() * kBlockSize, buffer.size()); + + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(buffer.data(), buffer.size(), digest); + ASSERT_EQ(source.hash(), print_sha1(digest)); +} + +TEST(SourceInfoTest, ReadAll_BufferTooSmall) { + const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {}, + {}); + auto block_reader = [](const RangeSet&, std::vector*) -> int { return 0; }; + auto stash_reader = [](const std::string&, std::vector*) -> int { return 0; }; + std::vector buffer(source.blocks() * kBlockSize - 1); + ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader)); +} + +TEST(SourceInfoTest, ReadAll_FailingReader) { + const SourceInfo source( + "ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }), + { StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) }); + std::vector buffer(source.blocks() * kBlockSize); + auto failing_block_reader = [](const RangeSet&, std::vector*) -> int { return -1; }; + auto stash_reader = [](const std::string&, std::vector*) -> int { return 0; }; + ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, failing_block_reader, stash_reader)); + + auto block_reader = [](const RangeSet&, std::vector*) -> int { return 0; }; + auto failing_stash_reader = [](const std::string&, std::vector*) -> int { return -1; }; + ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, failing_stash_reader)); +} + +TEST(TransferListTest, Parse) { + std::vector input_lines{ + "4", // version + "2", // total blocks + "1", // max stashed entries + "1", // max stashed blocks + "stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1", + "move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1 2,0,1", + }; + + std::string err; + TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err); + ASSERT_TRUE(static_cast(transfer_list)); + ASSERT_EQ(4, transfer_list.version()); + ASSERT_EQ(2, transfer_list.total_blocks()); + ASSERT_EQ(1, transfer_list.stash_max_entries()); + ASSERT_EQ(1, transfer_list.stash_max_blocks()); + ASSERT_EQ(2U, transfer_list.commands().size()); + ASSERT_EQ(Command::Type::STASH, transfer_list.commands()[0].type()); + ASSERT_EQ(Command::Type::MOVE, transfer_list.commands()[1].type()); +} + +TEST(TransferListTest, Parse_InvalidCommand) { + std::vector input_lines{ + "4", // version + "2", // total blocks + "1", // max stashed entries + "1", // max stashed blocks + "stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1", + "move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1", + }; + + std::string err; + TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err); + ASSERT_FALSE(static_cast(transfer_list)); +} + +TEST(TransferListTest, Parse_ZeroTotalBlocks) { + std::vector input_lines{ + "4", // version + "0", // total blocks + "0", // max stashed entries + "0", // max stashed blocks + }; + + std::string err; + TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err); + ASSERT_TRUE(static_cast(transfer_list)); + ASSERT_EQ(4, transfer_list.version()); + ASSERT_EQ(0, transfer_list.total_blocks()); + ASSERT_EQ(0, transfer_list.stash_max_entries()); + ASSERT_EQ(0, transfer_list.stash_max_blocks()); + ASSERT_TRUE(transfer_list.commands().empty()); +} diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp index 7f85d13ea0..4dd111a703 100644 --- a/tests/unit/dirutil_test.cpp +++ b/tests/unit/dirutil_test.cpp @@ -20,9 +20,10 @@ #include -#include +#include #include -#include + +#include "otautil/dirutil.h" TEST(DirUtilTest, create_invalid) { // Requesting to create an empty dir is invalid. diff --git a/tests/component/edify_test.cpp b/tests/unit/edify_test.cpp similarity index 75% rename from tests/component/edify_test.cpp rename to tests/unit/edify_test.cpp index 61a1e6b649..8397bd38ed 100644 --- a/tests/component/edify_test.cpp +++ b/tests/unit/edify_test.cpp @@ -21,30 +21,29 @@ #include "edify/expr.h" -static void expect(const char* expr_str, const char* expected) { - std::unique_ptr e; - int error_count = 0; - EXPECT_EQ(0, parse_string(expr_str, &e, &error_count)); - EXPECT_EQ(0, error_count); - - State state(expr_str, nullptr); - - std::string result; - bool status = Evaluate(&state, e, &result); - - if (expected == nullptr) { - EXPECT_FALSE(status); - } else { - EXPECT_STREQ(expected, result.c_str()); - } - +static void expect(const std::string& expr_str, const char* expected) { + std::unique_ptr e; + int error_count = 0; + EXPECT_EQ(0, ParseString(expr_str, &e, &error_count)); + EXPECT_EQ(0, error_count); + + State state(expr_str, nullptr); + + std::string result; + bool status = Evaluate(&state, e, &result); + + if (expected == nullptr) { + EXPECT_FALSE(status); + } else { + EXPECT_STREQ(expected, result.c_str()); + } } class EdifyTest : public ::testing::Test { - protected: - virtual void SetUp() { - RegisterBuiltins(); - } + protected: + void SetUp() { + RegisterBuiltins(); + } }; TEST_F(EdifyTest, parsing) { @@ -146,25 +145,23 @@ TEST_F(EdifyTest, comparison) { } TEST_F(EdifyTest, big_string) { - // big string - expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str()); + expect(std::string(8192, 's'), std::string(8192, 's').c_str()); } TEST_F(EdifyTest, unknown_function) { - // unknown function - const char* script1 = "unknown_function()"; - std::unique_ptr expr; - int error_count = 0; - EXPECT_EQ(1, parse_string(script1, &expr, &error_count)); - EXPECT_EQ(1, error_count); - - const char* script2 = "abc; unknown_function()"; - error_count = 0; - EXPECT_EQ(1, parse_string(script2, &expr, &error_count)); - EXPECT_EQ(1, error_count); - - const char* script3 = "unknown_function1() || yes"; - error_count = 0; - EXPECT_EQ(1, parse_string(script3, &expr, &error_count)); - EXPECT_EQ(1, error_count); + const char* script1 = "unknown_function()"; + std::unique_ptr expr; + int error_count = 0; + EXPECT_EQ(1, ParseString(script1, &expr, &error_count)); + EXPECT_EQ(1, error_count); + + const char* script2 = "abc; unknown_function()"; + error_count = 0; + EXPECT_EQ(1, ParseString(script2, &expr, &error_count)); + EXPECT_EQ(1, error_count); + + const char* script3 = "unknown_function1() || yes"; + error_count = 0; + EXPECT_EQ(1, ParseString(script3, &expr, &error_count)); + EXPECT_EQ(1, error_count); } diff --git a/tests/unit/fuse_provider_test.cpp b/tests/unit/fuse_provider_test.cpp new file mode 100644 index 0000000000..37f99f92e4 --- /dev/null +++ b/tests/unit/fuse_provider_test.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "fuse_provider.h" +#include "fuse_sideload.h" +#include "install/install.h" + +TEST(FuseBlockMapTest, CreateFromBlockMap_smoke) { + TemporaryFile fake_block_device; + std::vector lines = { + fake_block_device.path, "10000 4096", "3", "10 11", "20 21", "22 23", + }; + + TemporaryFile temp_file; + android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path); + auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096); + + ASSERT_TRUE(block_map_data); + ASSERT_EQ(10000, block_map_data->file_size()); + ASSERT_EQ(4096, block_map_data->fuse_block_size()); + ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 21 }, { 22, 23 } }), + static_cast(block_map_data.get())->ranges()); +} + +TEST(FuseBlockMapTest, ReadBlockAlignedData_smoke) { + std::string content; + content.reserve(40960); + for (char c = 0; c < 10; c++) { + content += std::string(4096, c); + } + TemporaryFile fake_block_device; + ASSERT_TRUE(android::base::WriteStringToFile(content, fake_block_device.path)); + + std::vector lines = { + fake_block_device.path, + "20000 4096", + "1", + "0 5", + }; + TemporaryFile temp_file; + android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path); + auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096); + + std::vector result(2000); + ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 2000, 1)); + ASSERT_EQ(std::vector(content.begin() + 4096, content.begin() + 6096), result); + + result.resize(20000); + ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 0)); + ASSERT_EQ(std::vector(content.begin(), content.begin() + 20000), result); +} + +TEST(FuseBlockMapTest, ReadBlockAlignedData_large_fuse_block) { + std::string content; + for (char c = 0; c < 10; c++) { + content += std::string(4096, c); + } + + TemporaryFile temp_file; + ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path)); + + std::vector lines = { + temp_file.path, "36384 4096", "2", "0 5", "6 10", + }; + TemporaryFile block_map; + ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(lines, '\n'), block_map.path)); + + auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(block_map.path, 16384); + ASSERT_TRUE(block_map_data); + + std::vector result(20000); + // Out of bound read + ASSERT_FALSE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 2)); + ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 1)); + // expected source block contains: 4, 6-9 + std::string expected = content.substr(16384, 4096) + content.substr(24576, 15904); + ASSERT_EQ(std::vector(expected.begin(), expected.end()), result); +} diff --git a/tests/component/sideload_test.cpp b/tests/unit/fuse_sideload_test.cpp similarity index 67% rename from tests/component/sideload_test.cpp rename to tests/unit/fuse_sideload_test.cpp index b7109fcc2c..ea895038c2 100644 --- a/tests/component/sideload_test.cpp +++ b/tests/unit/fuse_sideload_test.cpp @@ -16,29 +16,46 @@ #include +#include #include #include #include #include -#include #include +#include "fuse_provider.h" #include "fuse_sideload.h" TEST(SideloadTest, fuse_device) { ASSERT_EQ(0, access("/dev/fuse", R_OK | W_OK)); } +class FuseTestDataProvider : public FuseDataProvider { + public: + FuseTestDataProvider(uint64_t file_size, uint32_t block_size) + : FuseDataProvider(file_size, block_size) {} + + private: + bool ReadBlockAlignedData(uint8_t*, uint32_t, uint32_t) const override { + return true; + } + + bool Valid() const override { + return true; + } +}; + TEST(SideloadTest, run_fuse_sideload_wrong_parameters) { - provider_vtab vtab; - vtab.close = [](void) {}; + auto provider_small_block = std::make_unique(4096, 4095); + ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_small_block))); - ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, 4095)); - ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, (1 << 22) + 1)); + auto provider_large_block = std::make_unique(4096, (1 << 22) + 1); + ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_large_block))); - // Too many blocks. - ASSERT_EQ(-1, run_fuse_sideload(vtab, ((1 << 18) + 1) * 4096, 4096)); + auto provider_too_many_blocks = + std::make_unique(((1 << 18) + 1) * 4096, 4096); + ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_too_many_blocks))); } TEST(SideloadTest, run_fuse_sideload) { @@ -51,18 +68,15 @@ TEST(SideloadTest, run_fuse_sideload) { const std::string content = android::base::Join(blocks, ""); ASSERT_EQ(16384U, content.size()); - provider_vtab vtab; - vtab.close = [](void) {}; - vtab.read_block = [&blocks](uint32_t block, uint8_t* buffer, uint32_t fetch_size) { - if (block >= 4) return -1; - blocks[block].copy(reinterpret_cast(buffer), fetch_size); - return 0; - }; + TemporaryFile temp_file; + ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path)); + auto provider = std::make_unique(temp_file.path, 4096); + ASSERT_TRUE(provider->Valid()); TemporaryDir mount_point; pid_t pid = fork(); if (pid == 0) { - ASSERT_EQ(0, run_fuse_sideload(vtab, 16384, 4096, mount_point.path)); + ASSERT_EQ(0, run_fuse_sideload(std::move(provider), mount_point.path)); _exit(EXIT_SUCCESS); } diff --git a/tests/component/imgdiff_test.cpp b/tests/unit/host/imgdiff_test.cpp similarity index 92% rename from tests/component/imgdiff_test.cpp rename to tests/unit/host/imgdiff_test.cpp index 6c23def01d..e76ccbdfb5 100644 --- a/tests/component/imgdiff_test.cpp +++ b/tests/unit/host/imgdiff_test.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -197,12 +196,17 @@ TEST(ImgdiffTest, zip_mode_smoke_store) { } TEST(ImgdiffTest, zip_mode_smoke_compressed) { + // Generate 1 block of random data. + std::string random_data; + random_data.reserve(4096); + generate_n(back_inserter(random_data), 4096, []() { return rand() % 256; }); + // Construct src and tgt zip files. TemporaryFile src_file; FILE* src_file_ptr = fdopen(src_file.release(), "wb"); ZipWriter src_writer(src_file_ptr); ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string src_content("abcdefg"); + const std::string src_content = random_data; ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); ASSERT_EQ(0, src_writer.FinishEntry()); ASSERT_EQ(0, src_writer.Finish()); @@ -212,7 +216,7 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) { FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); ZipWriter tgt_writer(tgt_file_ptr); ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string tgt_content("abcdefgxyz"); + const std::string tgt_content = random_data + "extra contents"; ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); ASSERT_EQ(0, tgt_writer.FinishEntry()); ASSERT_EQ(0, tgt_writer.Finish()); @@ -245,13 +249,57 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) { verify_patched_image(src, patch, tgt); } +TEST(ImgdiffTest, zip_mode_empty_target) { + TemporaryFile src_file; + FILE* src_file_ptr = fdopen(src_file.release(), "wb"); + ZipWriter src_writer(src_file_ptr); + ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress)); + const std::string src_content = "abcdefg"; + ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); + ASSERT_EQ(0, src_writer.FinishEntry()); + ASSERT_EQ(0, src_writer.Finish()); + ASSERT_EQ(0, fclose(src_file_ptr)); + + // Construct a empty entry in the target zip. + TemporaryFile tgt_file; + FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); + ZipWriter tgt_writer(tgt_file_ptr); + ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress)); + const std::string tgt_content; + ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); + ASSERT_EQ(0, tgt_writer.FinishEntry()); + ASSERT_EQ(0, tgt_writer.Finish()); + + // Compute patch. + TemporaryFile patch_file; + std::vector args = { + "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string tgt; + ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); + std::string src; + ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src)); + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + verify_patched_image(src, patch, tgt); +} + TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { + // Generate 1 block of random data. + std::string random_data; + random_data.reserve(4096); + generate_n(back_inserter(random_data), 4096, []() { return rand() % 256; }); + // Construct src and tgt zip files. TemporaryFile src_file; FILE* src_file_ptr = fdopen(src_file.release(), "wb"); ZipWriter src_writer(src_file_ptr); ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string src_content("abcdefg"); + const std::string src_content = random_data; ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); ASSERT_EQ(0, src_writer.FinishEntry()); ASSERT_EQ(0, src_writer.Finish()); @@ -261,7 +309,7 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); ZipWriter tgt_writer(tgt_file_ptr); ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string tgt_content("abcdefgxyz"); + const std::string tgt_content = random_data + "abcdefg"; ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); ASSERT_EQ(0, tgt_writer.FinishEntry()); ASSERT_EQ(0, tgt_writer.Finish()); @@ -298,23 +346,19 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { } TEST(ImgdiffTest, image_mode_simple) { - // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd). - const std::vector src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', - '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', - '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', - '\x00', '\x00', '\x00' }; - const std::string src(src_data.cbegin(), src_data.cend()); + std::string gzipped_source_path = from_testdata_base("gzipped_source"); + std::string gzipped_source; + ASSERT_TRUE(android::base::ReadFileToString(gzipped_source_path, &gzipped_source)); + + const std::string src = "abcdefg" + gzipped_source; TemporaryFile src_file; ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - // tgt: "abcdefgxyz" + gzipped "xxyyzz". - const std::vector tgt_data = { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b', - '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac', - '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00' - }; - const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + std::string gzipped_target_path = from_testdata_base("gzipped_target"); + std::string gzipped_target; + ASSERT_TRUE(android::base::ReadFileToString(gzipped_target_path, &gzipped_target)); + const std::string tgt = "abcdefgxyz" + gzipped_target; + TemporaryFile tgt_file; ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); @@ -404,23 +448,21 @@ TEST(ImgdiffTest, image_mode_different_num_chunks) { } TEST(ImgdiffTest, image_mode_merge_chunks) { - // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd). - const std::vector src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', - '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', - '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', - '\x00', '\x00', '\x00' }; - const std::string src(src_data.cbegin(), src_data.cend()); + // src: "abcdefg" + gzipped_source. + std::string gzipped_source_path = from_testdata_base("gzipped_source"); + std::string gzipped_source; + ASSERT_TRUE(android::base::ReadFileToString(gzipped_source_path, &gzipped_source)); + + const std::string src = "abcdefg" + gzipped_source; TemporaryFile src_file; ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - // tgt: gzipped "xyz" + "abcdefgh". - const std::vector tgt_data = { - '\x1f', '\x8b', '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', - '\xa8', '\xac', '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', - '\x00', '\x00', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' - }; - const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + // tgt: gzipped_target + "abcdefgxyz". + std::string gzipped_target_path = from_testdata_base("gzipped_target"); + std::string gzipped_target; + ASSERT_TRUE(android::base::ReadFileToString(gzipped_target_path, &gzipped_target)); + + const std::string tgt = gzipped_target + "abcdefgxyz"; TemporaryFile tgt_file; ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); diff --git a/tests/unit/host/update_simulator_test.cpp b/tests/unit/host/update_simulator_test.cpp new file mode 100644 index 0000000000..fb1217877d --- /dev/null +++ b/tests/unit/host/update_simulator_test.cpp @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "otautil/paths.h" +#include "otautil/print_sha1.h" +#include "updater/blockimg.h" +#include "updater/build_info.h" +#include "updater/install.h" +#include "updater/simulator_runtime.h" +#include "updater/target_files.h" +#include "updater/updater.h" + +using std::string; + +// echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 && +// hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img +// The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get +// the full image. +constexpr uint8_t SPARSE_SYSTEM_HEADER[] = { + 0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static void AddZipEntries(int fd, const std::map& entries) { + FILE* zip_file = fdopen(fd, "w"); + ZipWriter writer(zip_file); + for (const auto& pair : entries) { + ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0)); + ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size())); + ASSERT_EQ(0, writer.FinishEntry()); + } + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); +} + +static string CalculateSha1(const string& data) { + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast(data.c_str()), data.size(), digest); + return print_sha1(digest); +} + +static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) { + TemporaryFile patch_file; + ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast(src.data()), src.size(), + reinterpret_cast(tgt.data()), tgt.size(), + patch_file.path, nullptr)); + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch)); +} + +static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) { + TemporaryFile cmd_pipe; + TemporaryFile temp_saved_source; + TemporaryFile temp_last_command; + TemporaryDir temp_stash_base; + + Paths::Get().set_cache_temp_source(temp_saved_source.path); + Paths::Get().set_last_command_file(temp_last_command.path); + Paths::Get().set_stash_directory_base(temp_stash_base.path); + + // Configure edify's functions. + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + + // Run the update simulation and check the result. + TemporaryDir work_dir; + BuildInfo build_info(work_dir.path, false); + ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false)); + Updater updater(std::make_unique(&build_info)); + ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false)); + ASSERT_EQ(expected, updater.RunUpdate()); + // TODO(xunchang) check the recovery&system has the expected contents. +} + +class UpdateSimulatorTest : public ::testing::Test { + protected: + void SetUp() override { + std::vector props = { + "import /oem/oem.prop oem*", + "# begin build properties", + "# autogenerated by buildinfo.sh", + "ro.build.id=OPR1.170510.001", + "ro.build.display.id=OPR1.170510.001 dev-keys", + "ro.build.version.incremental=3993052", + "ro.build.version.release=O", + "ro.build.date=Wed May 10 11:10:29 UTC 2017", + "ro.build.date.utc=1494414629", + "ro.build.type=user", + "ro.build.tags=dev-keys", + "ro.build.flavor=angler-user", + "ro.product.system.brand=google", + "ro.product.system.name=angler", + "ro.product.system.device=angler", + }; + build_prop_string_ = android::base::Join(props, "\n"); + + fstab_content_ = R"( +# +# More comments..... + +/dev/block/by-name/system /system ext4 ro,barrier=1 wait +/dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata +/dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check +/dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait +/dev/block/by-name/boot /boot emmc defaults defaults +/dev/block/by-name/recovery /recovery emmc defaults defaults +/dev/block/by-name/misc /misc emmc defaults +/dev/block/by-name/modem /modem emmc defaults defaults)"; + + raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total + sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) + + string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0'); + } + + string build_prop_string_; + string fstab_content_; + string raw_system_string_; + string sparse_system_string_; +}; + +TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) { + TemporaryFile zip_file; + AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" }, + { "IMAGES/system.img", sparse_system_string_ } }); + TargetFile target_file(zip_file.path, false); + ASSERT_TRUE(target_file.Open()); + + TemporaryDir temp_dir; + TemporaryFile raw_image; + ASSERT_TRUE(target_file.ExtractImage( + "IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image)); + + // Check the raw image has expected contents. + string content; + ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content)); + string expected_content = "system.img" + string(4086, '\0'); + ASSERT_EQ(expected_content, content); +} + +TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) { + TemporaryFile zip_file; + AddZipEntries(zip_file.release(), + { { "META/misc_info.txt", "" }, + { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } }); + TargetFile target_file(zip_file.path, false); + ASSERT_TRUE(target_file.Open()); + + std::vector fstab_info; + EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info)); + + std::vector> transformed; + std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed), + [](const FstabInfo& info) { + return std::vector{ info.blockdev_name, info.mount_point, info.fs_type }; + }); + + std::vector> expected = { + { "/dev/block/by-name/system", "/system", "ext4" }, + { "/dev/block/by-name/vendor", "/vendor", "ext4" }, + { "/dev/block/by-name/cache", "/cache", "ext4" }, + { "/dev/block/by-name/boot", "/boot", "emmc" }, + { "/dev/block/by-name/recovery", "/recovery", "emmc" }, + { "/dev/block/by-name/misc", "/misc", "emmc" }, + { "/dev/block/by-name/modem", "/modem", "emmc" }, + }; + EXPECT_EQ(expected, transformed); +} + +TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) { + std::map entries = { + { "META/misc_info.txt", "" }, + { "SYSTEM/build.prop", build_prop_string_ }, + { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ }, + { "IMAGES/recovery.img", "" }, + { "IMAGES/boot.img", "" }, + { "IMAGES/misc.img", "" }, + { "IMAGES/system.map", "" }, + { "IMAGES/system.img", sparse_system_string_ }, + }; + + TemporaryFile zip_file; + AddZipEntries(zip_file.release(), entries); + + TemporaryDir temp_dir; + BuildInfo build_info(temp_dir.path, false); + ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false)); + + std::map expected_result = { + { "ro.build.id", "OPR1.170510.001" }, + { "ro.build.display.id", "OPR1.170510.001 dev-keys" }, + { "ro.build.version.incremental", "3993052" }, + { "ro.build.version.release", "O" }, + { "ro.build.date", "Wed May 10 11:10:29 UTC 2017" }, + { "ro.build.date.utc", "1494414629" }, + { "ro.build.type", "user" }, + { "ro.build.tags", "dev-keys" }, + { "ro.build.flavor", "angler-user" }, + { "ro.product.brand", "google" }, + { "ro.product.name", "angler" }, + { "ro.product.device", "angler" }, + }; + + for (const auto& [key, value] : expected_result) { + ASSERT_EQ(value, build_info.GetProperty(key, "")); + } + + // Check that the temp files for each block device are created successfully. + for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery", + "/dev/block/by-name/boot", "/dev/block/by-name/misc" }) { + ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK)); + } +} + +TEST_F(UpdateSimulatorTest, RunUpdateSmoke) { + string recovery_img_string = "recovery.img"; + string boot_img_string = "boot.img"; + + std::map src_entries{ + { "META/misc_info.txt", "extfs_sparse_flag=-s" }, + { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, + { "SYSTEM/build.prop", build_prop_string_ }, + { "IMAGES/recovery.img", "" }, + { "IMAGES/boot.img", boot_img_string }, + { "IMAGES/system.img", sparse_system_string_ }, + }; + + // Construct the source target-files. + TemporaryFile src_tf; + AddZipEntries(src_tf.release(), src_entries); + + string recovery_from_boot; + CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot); + + // Set up the apply patch commands to patch the recovery image. + string recovery_sha1 = CalculateSha1(recovery_img_string); + string boot_sha1 = CalculateSha1(boot_img_string); + string apply_patch_source_string = android::base::StringPrintf( + "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); + string apply_patch_target_string = android::base::StringPrintf( + "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); + string check_command = android::base::StringPrintf( + R"(patch_partition_check("%s", "%s") || abort("check failed");)", + apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); + string patch_command = android::base::StringPrintf( + R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)", + apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); + + // Add the commands to update the system image. Test common commands: + // * getprop + // * ui_print + // * patch_partition + // * package_extract_file (single argument) + // * block_image_verify, block_image_update + string tgt_system_string = string(4096, 'a'); + string system_patch; + CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch); + + string tgt_system_hash = CalculateSha1(tgt_system_string); + string src_system_hash = CalculateSha1(raw_system_string_); + + std::vector transfer_list = { + "4", + "1", + "0", + "0", + android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(), + src_system_hash.c_str(), tgt_system_hash.c_str()), + }; + + // Construct the updater_script. + std::vector updater_commands = { + R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)", + R"(ui_print("Source: angler/OPR1.170510.001");)", + check_command, + patch_command, + R"(block_image_verify("/dev/block/by-name/system", )" + R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" + R"(abort("Failed to verify system.");)", + R"(block_image_update("/dev/block/by-name/system", )" + R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" + R"(abort("Failed to verify system.");)", + }; + string updater_script = android::base::Join(updater_commands, '\n'); + + // Construct the ota update package. + std::map ota_entries{ + { "system.new.dat", "" }, + { "system.patch.dat", system_patch }, + { "system.transfer.list", android::base::Join(transfer_list, '\n') }, + { "META-INF/com/google/android/updater-script", updater_script }, + { "patch.p", recovery_from_boot }, + }; + + TemporaryFile ota_package; + AddZipEntries(ota_package.release(), ota_entries); + + RunSimulation(src_tf.path, ota_package.path, true); +} + +TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) { + std::map src_entries{ + { "META/misc_info.txt", "extfs_sparse_flag=-s" }, + { "IMAGES/system.img", sparse_system_string_ }, + { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, + { "SYSTEM/build.prop", build_prop_string_ }, + }; + + TemporaryFile src_tf; + AddZipEntries(src_tf.release(), src_entries); + + std::map ota_entries{ + { "system.new.dat", "" }, + { "system.patch.dat", "" }, + { "system.transfer.list", "" }, + { "META-INF/com/google/android/updater-script", R"(bad_function("");)" }, + }; + + TemporaryFile ota_package; + AddZipEntries(ota_package.release(), ota_entries); + + RunSimulation(src_tf.path, ota_package.path, false); +} + +TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) { + string recovery_img_string = "recovery.img"; + string boot_img_string = "boot.img"; + + std::map src_entries{ + { "META/misc_info.txt", "extfs_sparse_flag=-s" }, + { "IMAGES/recovery.img", "" }, + { "IMAGES/boot.img", boot_img_string }, + { "IMAGES/system.img", sparse_system_string_ }, + { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, + { "SYSTEM/build.prop", build_prop_string_ }, + }; + + TemporaryFile src_tf; + AddZipEntries(src_tf.release(), src_entries); + + string recovery_sha1 = CalculateSha1(recovery_img_string); + string boot_sha1 = CalculateSha1(boot_img_string); + string apply_patch_source_string = android::base::StringPrintf( + "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); + string apply_patch_target_string = android::base::StringPrintf( + "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); + string check_command = android::base::StringPrintf( + R"(patch_partition_check("%s", "%s") || abort("check failed");)", + apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); + string patch_command = android::base::StringPrintf( + R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)", + apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); + + // Give an invalid recovery patch and expect the apply patch to fail. + // TODO(xunchang) check the cause code. + std::vector updater_commands = { + R"(ui_print("Source: angler/OPR1.170510.001");)", + check_command, + patch_command, + }; + + string updater_script = android::base::Join(updater_commands, '\n'); + std::map ota_entries{ + { "system.new.dat", "" }, + { "system.patch.dat", "" }, + { "system.transfer.list", "" }, + { "META-INF/com/google/android/updater-script", updater_script }, + { "patch.p", "random string" }, + }; + + TemporaryFile ota_package; + AddZipEntries(ota_package.release(), ota_entries); + + RunSimulation(src_tf.path, ota_package.path, false); +} diff --git a/tests/unit/install_test.cpp b/tests/unit/install_test.cpp new file mode 100644 index 0000000000..ee753494cb --- /dev/null +++ b/tests/unit/install_test.cpp @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "install/install.h" +#include "install/wipe_device.h" +#include "otautil/paths.h" +#include "private/setup_commands.h" +#include "recovery_utils/roots.h" + +static void BuildZipArchive(const std::map& file_map, int fd, + int compression_type) { + FILE* zip_file = fdopen(fd, "w"); + ZipWriter writer(zip_file); + for (const auto& [name, content] : file_map) { + ASSERT_EQ(0, writer.StartEntry(name.c_str(), compression_type)); + ASSERT_EQ(0, writer.WriteBytes(content.data(), content.size())); + ASSERT_EQ(0, writer.FinishEntry()); + } + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); +} + +TEST(InstallTest, read_metadata_from_package_smoke) { + TemporaryFile temp_file; + const std::string content("abc=defg"); + BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file.release(), + kCompressStored); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + std::map metadata; + ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata)); + ASSERT_EQ("defg", metadata["abc"]); + CloseArchive(zip); + + TemporaryFile temp_file2; + BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file2.release(), + kCompressDeflated); + + ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip)); + metadata.clear(); + ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata)); + ASSERT_EQ("defg", metadata["abc"]); + CloseArchive(zip); +} + +TEST(InstallTest, read_metadata_from_package_no_entry) { + TemporaryFile temp_file; + BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + std::map metadata; + ASSERT_FALSE(ReadMetadataFromPackage(zip, &metadata)); + CloseArchive(zip); +} + +TEST(InstallTest, read_wipe_ab_partition_list) { + std::vector partition_list = { + "/dev/block/bootdevice/by-name/system_a", "/dev/block/bootdevice/by-name/system_b", + "/dev/block/bootdevice/by-name/vendor_a", "/dev/block/bootdevice/by-name/vendor_b", + "/dev/block/bootdevice/by-name/userdata", "# Wipe the boot partitions last", + "/dev/block/bootdevice/by-name/boot_a", "/dev/block/bootdevice/by-name/boot_b", + }; + TemporaryFile temp_file; + BuildZipArchive({ { "recovery.wipe", android::base::Join(partition_list, '\n') } }, + temp_file.release(), kCompressDeflated); + std::string wipe_package; + ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &wipe_package)); + + auto package = Package::CreateMemoryPackage( + std::vector(wipe_package.begin(), wipe_package.end()), nullptr); + + auto read_partition_list = GetWipePartitionList(package.get()); + std::vector expected = { + "/dev/block/bootdevice/by-name/system_a", "/dev/block/bootdevice/by-name/system_b", + "/dev/block/bootdevice/by-name/vendor_a", "/dev/block/bootdevice/by-name/vendor_b", + "/dev/block/bootdevice/by-name/userdata", "/dev/block/bootdevice/by-name/boot_a", + "/dev/block/bootdevice/by-name/boot_b", + }; + ASSERT_EQ(expected, read_partition_list); +} + +TEST(InstallTest, SetUpNonAbUpdateCommands) { + TemporaryFile temp_file; + static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary"; + BuildZipArchive({ { UPDATE_BINARY_NAME, "" } }, temp_file.release(), kCompressStored); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + int status_fd = 10; + std::string package = "/path/to/update.zip"; + TemporaryDir td; + std::string binary_path = std::string(td.path) + "/update_binary"; + Paths::Get().set_temporary_update_binary(binary_path); + std::vector cmd; + ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd)); + ASSERT_EQ(4U, cmd.size()); + ASSERT_EQ(binary_path, cmd[0]); + ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION + ASSERT_EQ(std::to_string(status_fd), cmd[2]); + ASSERT_EQ(package, cmd[3]); + struct stat sb; + ASSERT_EQ(0, stat(binary_path.c_str(), &sb)); + ASSERT_EQ(static_cast(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + + // With non-zero retry count. update_binary will be removed automatically. + cmd.clear(); + ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd)); + ASSERT_EQ(5U, cmd.size()); + ASSERT_EQ(binary_path, cmd[0]); + ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION + ASSERT_EQ(std::to_string(status_fd), cmd[2]); + ASSERT_EQ(package, cmd[3]); + ASSERT_EQ("retry", cmd[4]); + sb = {}; + ASSERT_EQ(0, stat(binary_path.c_str(), &sb)); + ASSERT_EQ(static_cast(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + + CloseArchive(zip); +} + +TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) { + TemporaryFile temp_file; + // The archive must have something to be opened correctly. + BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored); + + // Missing update binary. + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + int status_fd = 10; + std::string package = "/path/to/update.zip"; + TemporaryDir td; + Paths::Get().set_temporary_update_binary(std::string(td.path) + "/update_binary"); + std::vector cmd; + ASSERT_FALSE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd)); + CloseArchive(zip); +} + +static void VerifyAbUpdateCommands(const std::string& serialno, bool success = true) { + TemporaryFile temp_file; + + const std::string properties = "some_properties"; + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + std::string timestamp = android::base::GetProperty("ro.build.date.utc", ""); + ASSERT_NE("", timestamp); + + std::vector meta{ "ota-type=AB", "pre-device=" + device, + "post-timestamp=" + timestamp }; + if (!serialno.empty()) { + meta.push_back("serialno=" + serialno); + } + std::string metadata_string = android::base::Join(meta, "\n"); + + BuildZipArchive({ { "payload.bin", "" }, + { "payload_properties.txt", properties }, + { "META-INF/com/android/metadata", metadata_string } }, + temp_file.release(), kCompressStored); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + ZipEntry payload_entry; + ASSERT_EQ(0, FindEntry(zip, "payload.bin", &payload_entry)); + + std::map metadata; + ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata)); + if (success) { + ASSERT_TRUE(CheckPackageMetadata(metadata, OtaType::AB)); + + int status_fd = 10; + std::string package = "/path/to/update.zip"; + std::vector cmd; + ASSERT_TRUE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd)); + ASSERT_EQ(5U, cmd.size()); + ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]); + ASSERT_EQ("--payload=file://" + package, cmd[1]); + ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]); + ASSERT_EQ("--headers=" + properties, cmd[3]); + ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]); + } else { + ASSERT_FALSE(CheckPackageMetadata(metadata, OtaType::AB)); + } + CloseArchive(zip); +} + +TEST(InstallTest, SetUpAbUpdateCommands) { + // Empty serialno will pass the verification. + VerifyAbUpdateCommands({}); +} + +TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) { + TemporaryFile temp_file; + + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + std::string timestamp = android::base::GetProperty("ro.build.date.utc", ""); + ASSERT_NE("", timestamp); + std::string metadata = android::base::Join( + std::vector{ + "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp, + }, + "\n"); + + BuildZipArchive( + { + { "payload.bin", "" }, + { "META-INF/com/android/metadata", metadata }, + }, + temp_file.release(), kCompressStored); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + int status_fd = 10; + std::string package = "/path/to/update.zip"; + std::vector cmd; + ASSERT_FALSE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd)); + CloseArchive(zip); +} + +TEST(InstallTest, SetUpAbUpdateCommands_MultipleSerialnos) { + std::string serialno = android::base::GetProperty("ro.serialno", ""); + ASSERT_NE("", serialno); + + // Single matching serialno will pass the verification. + VerifyAbUpdateCommands(serialno); + + static constexpr char alphabet[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; }; + + // Generate 900 random serial numbers. + std::string random_serialno; + for (size_t i = 0; i < 900; i++) { + generate_n(back_inserter(random_serialno), serialno.size(), generator); + random_serialno.append("|"); + } + // Random serialnos should fail the verification. + VerifyAbUpdateCommands(random_serialno, false); + + std::string long_serialno = random_serialno + serialno + "|"; + for (size_t i = 0; i < 99; i++) { + generate_n(back_inserter(long_serialno), serialno.size(), generator); + long_serialno.append("|"); + } + // String with the matching serialno should pass the verification. + VerifyAbUpdateCommands(long_serialno); +} + +static void TestCheckPackageMetadata(const std::string& metadata_string, OtaType ota_type, + bool exptected_result) { + TemporaryFile temp_file; + BuildZipArchive( + { + { "META-INF/com/android/metadata", metadata_string }, + }, + temp_file.release(), kCompressStored); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + + std::map metadata; + ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata)); + ASSERT_EQ(exptected_result, CheckPackageMetadata(metadata, ota_type)); + CloseArchive(zip); +} + +TEST(InstallTest, CheckPackageMetadata_ota_type) { + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + + // ota-type must be present + std::string metadata = android::base::Join( + std::vector{ + "pre-device=" + device, + "post-timestamp=" + std::to_string(std::numeric_limits::max()), + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, false); + + // Checks if ota-type matches + metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + "post-timestamp=" + std::to_string(std::numeric_limits::max()), + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, true); + + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); +} + +TEST(InstallTest, CheckPackageMetadata_device_type) { + // device type can not be empty + std::string metadata = android::base::Join( + std::vector{ + "ota-type=BRICK", + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); + + // device type mismatches + metadata = android::base::Join( + std::vector{ + "ota-type=BRICK", + "pre-device=dummy_device_type", + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); +} + +TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) { + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + + // Serial number doesn't need to exist + std::string metadata = android::base::Join( + std::vector{ + "ota-type=BRICK", + "pre-device=" + device, + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::BRICK, true); + + // Serial number mismatches + metadata = android::base::Join( + std::vector{ + "ota-type=BRICK", + "pre-device=" + device, + "serialno=dummy_serial", + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); + + std::string serialno = android::base::GetProperty("ro.serialno", ""); + ASSERT_NE("", serialno); + metadata = android::base::Join( + std::vector{ + "ota-type=BRICK", + "pre-device=" + device, + "serialno=" + serialno, + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::BRICK, true); +} + +TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) { + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + + std::string serialno = android::base::GetProperty("ro.serialno", ""); + ASSERT_NE("", serialno); + + std::vector serial_numbers; + // Creates a dummy serial number string. + for (char c = 'a'; c <= 'z'; c++) { + serial_numbers.emplace_back(serialno.size(), c); + } + + // No matched serialno found. + std::string metadata = android::base::Join( + std::vector{ + "ota-type=BRICK", + "pre-device=" + device, + "serialno=" + android::base::Join(serial_numbers, '|'), + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); + + serial_numbers.emplace_back(serialno); + std::shuffle(serial_numbers.begin(), serial_numbers.end(), std::default_random_engine()); + metadata = android::base::Join( + std::vector{ + "ota-type=BRICK", + "pre-device=" + device, + "serialno=" + android::base::Join(serial_numbers, '|'), + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::BRICK, true); +} + +TEST(InstallTest, CheckPackageMetadata_ab_build_version) { + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + + std::string build_version = android::base::GetProperty("ro.build.version.incremental", ""); + ASSERT_NE("", build_version); + + std::string metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + "pre-build-incremental=" + build_version, + "post-timestamp=" + std::to_string(std::numeric_limits::max()), + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, true); + + metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + "pre-build-incremental=dummy_build", + "post-timestamp=" + std::to_string(std::numeric_limits::max()), + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, false); +} + +TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) { + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + + std::string finger_print = android::base::GetProperty("ro.build.fingerprint", ""); + ASSERT_NE("", finger_print); + + std::string metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + "pre-build=" + finger_print, + "post-timestamp=" + std::to_string(std::numeric_limits::max()), + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, true); + + metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + "pre-build=dummy_build_fingerprint", + "post-timestamp=" + std::to_string(std::numeric_limits::max()), + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, false); +} + +TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) { + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + + // post timestamp is required for upgrade. + std::string metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, false); + + // post timestamp should be larger than the timestamp on device. + metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + "post-timestamp=0", + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, false); + + // fingerprint is required for downgrade + metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + "post-timestamp=0", + "ota-downgrade=yes", + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, false); + + std::string finger_print = android::base::GetProperty("ro.build.fingerprint", ""); + ASSERT_NE("", finger_print); + + metadata = android::base::Join( + std::vector{ + "ota-type=AB", + "pre-device=" + device, + "post-timestamp=0", + "pre-build=" + finger_print, + "ota-downgrade=yes", + }, + "\n"); + TestCheckPackageMetadata(metadata, OtaType::AB, true); +} + +TEST(InstallTest, SetupPackageMount_package_path) { + load_volume_table(); + bool install_with_fuse; + + // Setup should fail if the input path doesn't exist. + ASSERT_FALSE(SetupPackageMount("/does_not_exist", &install_with_fuse)); + + // Package should be installed with fuse if it's not in /cache. + TemporaryDir temp_dir; + TemporaryFile update_package(temp_dir.path); + ASSERT_TRUE(SetupPackageMount(update_package.path, &install_with_fuse)); + ASSERT_TRUE(install_with_fuse); + + // Setup should fail if the input path isn't canonicalized. + std::string uncanonical_package_path = android::base::Join( + std::vector{ + temp_dir.path, + "..", + android::base::Basename(temp_dir.path), + android::base::Basename(update_package.path), + }, + '/'); + + ASSERT_EQ(0, access(uncanonical_package_path.c_str(), R_OK)); + ASSERT_FALSE(SetupPackageMount(uncanonical_package_path, &install_with_fuse)); +} diff --git a/tests/unit/locale_test.cpp b/tests/unit/locale_test.cpp index cdaba0e8b5..c69434c124 100644 --- a/tests/unit/locale_test.cpp +++ b/tests/unit/locale_test.cpp @@ -27,7 +27,7 @@ TEST(LocaleTest, Misc) { EXPECT_FALSE(matches_locale("en-GB", "en")); EXPECT_FALSE(matches_locale("en-GB", "en-US")); EXPECT_FALSE(matches_locale("en-US", "")); - // Empty locale prefix in the PNG file will match the input locale. - EXPECT_TRUE(matches_locale("", "en-US")); + // Empty locale prefix in the PNG file should not match the input locale. + EXPECT_FALSE(matches_locale("", "en-US")); EXPECT_TRUE(matches_locale("sr-Latn", "sr-Latn-BA")); } diff --git a/tests/unit/minui_test.cpp b/tests/unit/minui_test.cpp new file mode 100644 index 0000000000..c7d7f7eefc --- /dev/null +++ b/tests/unit/minui_test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include + +#include + +#include "minui/minui.h" + +TEST(GRSurfaceTest, Create_aligned) { + auto surface = GRSurface::Create(9, 11, 9, 1); + ASSERT_TRUE(surface); + ASSERT_EQ(0, reinterpret_cast(surface->data()) % GRSurface::kSurfaceDataAlignment); + // data_size will be rounded up to the next multiple of GRSurface::kSurfaceDataAlignment. + ASSERT_EQ(0, surface->data_size() % GRSurface::kSurfaceDataAlignment); + ASSERT_GE(surface->data_size(), 11 * 9); +} + +TEST(GRSurfaceTest, Create_invalid_inputs) { + ASSERT_FALSE(GRSurface::Create(9, 11, 0, 1)); + ASSERT_FALSE(GRSurface::Create(9, 0, 9, 1)); + ASSERT_FALSE(GRSurface::Create(0, 11, 9, 1)); + ASSERT_FALSE(GRSurface::Create(9, 11, 9, 0)); + ASSERT_FALSE(GRSurface::Create(9, 101, std::numeric_limits::max() / 100, 1)); +} + +TEST(GRSurfaceTest, Clone) { + auto image = GRSurface::Create(50, 10, 50, 1); + ASSERT_GE(image->data_size(), 10 * 50); + for (auto i = 0; i < image->data_size(); i++) { + image->data()[i] = rand() % 128; + } + auto image_copy = image->Clone(); + ASSERT_EQ(image->data_size(), image_copy->data_size()); + ASSERT_EQ(std::vector(image->data(), image->data() + image->data_size()), + std::vector(image_copy->data(), image_copy->data() + image->data_size())); +} diff --git a/tests/unit/package_test.cpp b/tests/unit/package_test.cpp new file mode 100644 index 0000000000..5e31f7fa5e --- /dev/null +++ b/tests/unit/package_test.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "common/test_constants.h" +#include "install/package.h" + +class PackageTest : public ::testing::Test { + protected: + void SetUp() override; + + // A list of package classes for test, including MemoryPackage and FilePackage. + std::vector> packages_; + + TemporaryFile temp_file_; // test package file. + std::string file_content_; // actual bytes of the package file. +}; + +void PackageTest::SetUp() { + std::vector entries = { "file1.txt", "file2.txt", "dir1/file3.txt" }; + FILE* file_ptr = fdopen(temp_file_.release(), "wb"); + ZipWriter writer(file_ptr); + for (const auto& entry : entries) { + ASSERT_EQ(0, writer.StartEntry(entry.c_str(), ZipWriter::kCompress)); + ASSERT_EQ(0, writer.WriteBytes(entry.c_str(), entry.size())); + ASSERT_EQ(0, writer.FinishEntry()); + } + writer.Finish(); + ASSERT_EQ(0, fclose(file_ptr)); + + ASSERT_TRUE(android::base::ReadFileToString(temp_file_.path, &file_content_)); + auto memory_package = Package::CreateMemoryPackage(temp_file_.path, nullptr); + ASSERT_TRUE(memory_package); + packages_.emplace_back(std::move(memory_package)); + + auto file_package = Package::CreateFilePackage(temp_file_.path, nullptr); + ASSERT_TRUE(file_package); + packages_.emplace_back(std::move(file_package)); +} + +TEST_F(PackageTest, ReadFullyAtOffset_success) { + for (const auto& package : packages_) { + std::vector buffer(file_content_.size()); + ASSERT_TRUE(package->ReadFullyAtOffset(buffer.data(), file_content_.size(), 0)); + ASSERT_EQ(file_content_, std::string(buffer.begin(), buffer.end())); + + ASSERT_TRUE(package->ReadFullyAtOffset(buffer.data(), file_content_.size() - 10, 10)); + ASSERT_EQ(file_content_.substr(10), std::string(buffer.begin(), buffer.end() - 10)); + } +} + +TEST_F(PackageTest, ReadFullyAtOffset_failure) { + for (const auto& package : packages_) { + std::vector buffer(file_content_.size()); + // Out of bound read. + ASSERT_FALSE(package->ReadFullyAtOffset(buffer.data(), file_content_.size(), 10)); + } +} + +TEST_F(PackageTest, UpdateHashAtOffset_sha1_hash) { + // Check that the hash matches for first half of the file. + uint64_t hash_size = file_content_.size() / 2; + std::vector expected_sha(SHA_DIGEST_LENGTH); + SHA1(reinterpret_cast(file_content_.data()), hash_size, expected_sha.data()); + + for (const auto& package : packages_) { + SHA_CTX ctx; + SHA1_Init(&ctx); + std::vector hashers{ std::bind(&SHA1_Update, &ctx, std::placeholders::_1, + std::placeholders::_2) }; + package->UpdateHashAtOffset(hashers, 0, hash_size); + + std::vector calculated_sha(SHA_DIGEST_LENGTH); + SHA1_Final(calculated_sha.data(), &ctx); + ASSERT_EQ(expected_sha, calculated_sha); + } +} + +TEST_F(PackageTest, GetZipArchiveHandle_extract_entry) { + for (const auto& package : packages_) { + ZipArchiveHandle zip = package->GetZipArchiveHandle(); + ASSERT_TRUE(zip); + + // Check that we can extract one zip entry. + std::string_view entry_name = "dir1/file3.txt"; + ZipEntry entry; + ASSERT_EQ(0, FindEntry(zip, entry_name, &entry)); + + std::vector extracted(entry_name.size()); + ASSERT_EQ(0, ExtractToMemory(zip, &entry, extracted.data(), extracted.size())); + ASSERT_EQ(entry_name, std::string(extracted.begin(), extracted.end())); + } +} diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp new file mode 100644 index 0000000000..052f71c989 --- /dev/null +++ b/tests/unit/parse_install_logs_test.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include + +#include "recovery_utils/parse_install_logs.h" + +TEST(ParseInstallLogsTest, EmptyFile) { + TemporaryFile last_install; + + auto metrics = ParseLastInstall(last_install.path); + ASSERT_TRUE(metrics.empty()); +} + +TEST(ParseInstallLogsTest, SideloadSmoke) { + TemporaryFile last_install; + ASSERT_TRUE(android::base::WriteStringToFile("/cache/recovery/ota.zip\n0\n", last_install.path)); + auto metrics = ParseLastInstall(last_install.path); + ASSERT_EQ(metrics.end(), metrics.find("ota_sideload")); + + ASSERT_TRUE(android::base::WriteStringToFile("/sideload/package.zip\n0\n", last_install.path)); + metrics = ParseLastInstall(last_install.path); + ASSERT_NE(metrics.end(), metrics.find("ota_sideload")); +} + +TEST(ParseInstallLogsTest, ParseRecoveryUpdateMetrics) { + std::vector lines = { + "/sideload/package.zip", + "0", + "time_total: 300", + "uncrypt_time: 40", + "source_build: 4973410", + "bytes_written_system: " + std::to_string(1200 * 1024 * 1024), + "bytes_stashed_system: " + std::to_string(300 * 1024 * 1024), + "bytes_written_vendor: " + std::to_string(40 * 1024 * 1024), + "bytes_stashed_vendor: " + std::to_string(50 * 1024 * 1024), + "temperature_start: 37000", + "temperature_end: 38000", + "temperature_max: 39000", + "error: 22", + "cause: 55", + }; + + auto metrics = ParseRecoveryUpdateMetrics(lines); + + std::map expected_result = { + { "ota_time_total", 300 }, { "ota_uncrypt_time", 40 }, + { "ota_source_version", 4973410 }, { "ota_written_in_MiBs", 1240 }, + { "ota_stashed_in_MiBs", 350 }, { "ota_temperature_start", 37000 }, + { "ota_temperature_end", 38000 }, { "ota_temperature_max", 39000 }, + { "ota_non_ab_error_code", 22 }, { "ota_non_ab_cause_code", 55 }, + }; + + ASSERT_EQ(expected_result, metrics); +} diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp index 7ae193e18e..699f933a01 100644 --- a/tests/unit/rangeset_test.cpp +++ b/tests/unit/rangeset_test.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -209,6 +210,7 @@ TEST(RangeSetTest, GetBlockNumber) { ASSERT_EQ(static_cast(6), rs.GetBlockNumber(5)); ASSERT_EQ(static_cast(9), rs.GetBlockNumber(8)); + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; // Out of bound. ASSERT_EXIT(rs.GetBlockNumber(9), ::testing::KilledBySignal(SIGABRT), ""); } @@ -247,6 +249,29 @@ TEST(RangeSetTest, ToString) { ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString()); } +TEST(RangeSetTest, GetSubRanges_invalid) { + RangeSet range0({ { 1, 11 }, { 20, 30 } }); + ASSERT_FALSE(range0.GetSubRanges(0, 21)); // too many blocks + ASSERT_FALSE(range0.GetSubRanges(21, 1)); // start block OOB +} + +TEST(RangeSetTest, GetSubRanges_empty) { + RangeSet range0({ { 1, 11 }, { 20, 30 } }); + ASSERT_EQ(RangeSet{}, range0.GetSubRanges(1, 0)); // empty num_of_blocks +} + +TEST(RangeSetTest, GetSubRanges_smoke) { + RangeSet range0({ { 10, 11 } }); + ASSERT_EQ(RangeSet({ { 10, 11 } }), range0.GetSubRanges(0, 1)); + + RangeSet range1({ { 10, 11 }, { 20, 21 }, { 30, 31 } }); + ASSERT_EQ(range1, range1.GetSubRanges(0, 3)); + ASSERT_EQ(RangeSet({ { 20, 21 } }), range1.GetSubRanges(1, 1)); + + RangeSet range2({ { 1, 11 }, { 20, 25 }, { 30, 35 } }); + ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 25 }, { 30, 31 } }), range2.GetSubRanges(9, 7)); +} + TEST(SortedRangeSetTest, Insert) { SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } }); rs.Insert({ 1, 2 }); @@ -284,6 +309,8 @@ TEST(SortedRangeSetTest, file_range) { ASSERT_EQ(static_cast(10), rs.GetOffsetInRangeSet(4106)); ASSERT_EQ(static_cast(40970), rs.GetOffsetInRangeSet(4096 * 16 + 10)); + + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; // block#10 not in range. ASSERT_EXIT(rs.GetOffsetInRangeSet(40970), ::testing::KilledBySignal(SIGABRT), ""); } diff --git a/tests/unit/resources_test.cpp b/tests/unit/resources_test.cpp new file mode 100644 index 0000000000..3027443087 --- /dev/null +++ b/tests/unit/resources_test.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "common/test_constants.h" +#include "minui/minui.h" +#include "private/resources.h" + +static const std::string kLocale = "zu"; + +static const std::vector kResourceImagesDirs{ + "res-mdpi/images/", "res-hdpi/images/", "res-xhdpi/images/", + "res-xxhdpi/images/", "res-xxxhdpi/images/", +}; + +static int png_filter(const dirent* de) { + if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) { + return 0; + } + return 1; +} + +// Finds out all the PNG files to test, which stay under the same dir with the executabl.. +static std::vector add_files() { + std::vector files; + for (const std::string& images_dir : kResourceImagesDirs) { + static std::string exec_dir = android::base::GetExecutableDirectory(); + std::string dir_path = exec_dir + "/" + images_dir; + dirent** namelist; + int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort); + if (n == -1) { + printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno)); + continue; + } + if (n == 0) { + printf("No file is added for test in %s\n", dir_path.c_str()); + } + + while (n--) { + std::string file_path = dir_path + namelist[n]->d_name; + files.push_back(file_path); + free(namelist[n]); + } + free(namelist); + } + return files; +} + +TEST(ResourcesTest, res_create_multi_display_surface) { + GRSurface** frames; + int frame_count; + int fps; + ASSERT_EQ(0, res_create_multi_display_surface(from_testdata_base("battery_scale.png").c_str(), + &frame_count, &fps, &frames)); + ASSERT_EQ(6, frame_count); + ASSERT_EQ(20, fps); + + for (auto i = 0; i < frame_count; i++) { + free(frames[i]); + } + free(frames); +} + +class ResourcesTest : public testing::TestWithParam { + public: + static std::vector png_list; + + protected: + void SetUp() override { + png_ = std::make_unique(GetParam()); + ASSERT_TRUE(png_); + + ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file."; + ASSERT_LT(static_cast(5), png_->width()); + ASSERT_LT(static_cast(0), png_->height()); + ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file."; + } + + std::unique_ptr png_{ nullptr }; +}; + +// Parses a png file and tests if it's qualified for the background text image under recovery. +TEST_P(ResourcesTest, ValidateLocale) { + std::vector row(png_->width()); + for (png_uint_32 y = 0; y < png_->height(); ++y) { + png_read_row(png_->png_ptr(), row.data(), nullptr); + int w = (row[1] << 8) | row[0]; + int h = (row[3] << 8) | row[2]; + int len = row[4]; + EXPECT_LT(0, w); + EXPECT_LT(0, h); + EXPECT_LT(0, len) << "Locale string should be non-empty."; + EXPECT_NE(0, row[5]) << "Locale string is missing."; + + ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file."; + char* loc = reinterpret_cast(&row[5]); + if (matches_locale(loc, kLocale.c_str())) { + EXPECT_TRUE(android::base::StartsWith(loc, kLocale)); + break; + } + for (int i = 0; i < h; ++i, ++y) { + png_read_row(png_->png_ptr(), row.data(), nullptr); + } + } +} + +std::vector ResourcesTest::png_list = add_files(); + +INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest, + ::testing::ValuesIn(ResourcesTest::png_list.cbegin(), + ResourcesTest::png_list.cend())); diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp new file mode 100644 index 0000000000..883dfbde24 --- /dev/null +++ b/tests/unit/screen_ui_test.cpp @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/test_constants.h" +#include "minui/minui.h" +#include "otautil/paths.h" +#include "private/resources.h" +#include "recovery_ui/device.h" +#include "recovery_ui/screen_ui.h" + +static const std::vector HEADERS{ "header" }; +static const std::vector ITEMS{ "item1", "item2", "item3", "item4", "1234567890" }; + +// TODO(xunchang) check if some draw functions are called when drawing menus. +class MockDrawFunctions : public DrawInterface { + void SetColor(UIElement /* element */) const override {} + void DrawHighlightBar(int /* x */, int /* y */, int /* width */, + int /* height */) const override {} + int DrawHorizontalRule(int /* y */) const override { + return 0; + } + int DrawTextLine(int /* x */, int /* y */, const std::string& /* line */, + bool /* bold */) const override { + return 0; + } + void DrawSurface(const GRSurface* /* surface */, int /* sx */, int /* sy */, int /* w */, + int /* h */, int /* dx */, int /* dy */) const override {} + void DrawFill(int /* x */, int /* y */, int /* w */, int /* h */) const override {} + void DrawTextIcon(int /* x */, int /* y */, const GRSurface* /* surface */) const override {} + int DrawTextLines(int /* x */, int /* y */, + const std::vector& /* lines */) const override { + return 0; + } + int DrawWrappedTextLines(int /* x */, int /* y */, + const std::vector& /* lines */) const override { + return 0; + } +}; + +class ScreenUITest : public testing::Test { + protected: + MockDrawFunctions draw_funcs_; +}; + +TEST_F(ScreenUITest, StartPhoneMenuSmoke) { + TextMenu menu(false, 10, 20, HEADERS, ITEMS, 0, 20, draw_funcs_); + ASSERT_FALSE(menu.scrollable()); + ASSERT_EQ(HEADERS[0], menu.text_headers()[0]); + ASSERT_EQ(5u, menu.ItemsCount()); + + std::string message; + ASSERT_FALSE(menu.ItemsOverflow(&message)); + for (size_t i = 0; i < menu.ItemsCount(); i++) { + ASSERT_EQ(ITEMS[i], menu.TextItem(i)); + } + + ASSERT_EQ(0, menu.selection()); +} + +TEST_F(ScreenUITest, StartWearMenuSmoke) { + TextMenu menu(true, 10, 8, HEADERS, ITEMS, 1, 20, draw_funcs_); + ASSERT_TRUE(menu.scrollable()); + ASSERT_EQ(HEADERS[0], menu.text_headers()[0]); + ASSERT_EQ(5u, menu.ItemsCount()); + + std::string message; + ASSERT_FALSE(menu.ItemsOverflow(&message)); + for (size_t i = 0; i < menu.ItemsCount() - 1; i++) { + ASSERT_EQ(ITEMS[i], menu.TextItem(i)); + } + // Test of the last item is truncated + ASSERT_EQ("12345678", menu.TextItem(4)); + ASSERT_EQ(1, menu.selection()); +} + +TEST_F(ScreenUITest, StartPhoneMenuItemsOverflow) { + TextMenu menu(false, 1, 20, HEADERS, ITEMS, 0, 20, draw_funcs_); + ASSERT_FALSE(menu.scrollable()); + ASSERT_EQ(1u, menu.ItemsCount()); + + std::string message; + ASSERT_FALSE(menu.ItemsOverflow(&message)); + for (size_t i = 0; i < menu.ItemsCount(); i++) { + ASSERT_EQ(ITEMS[i], menu.TextItem(i)); + } + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(1u, menu.MenuEnd()); +} + +TEST_F(ScreenUITest, StartWearMenuItemsOverflow) { + TextMenu menu(true, 1, 20, HEADERS, ITEMS, 0, 20, draw_funcs_); + ASSERT_TRUE(menu.scrollable()); + ASSERT_EQ(5u, menu.ItemsCount()); + + std::string message; + ASSERT_TRUE(menu.ItemsOverflow(&message)); + ASSERT_EQ("Current item: 1/5", message); + + for (size_t i = 0; i < menu.ItemsCount(); i++) { + ASSERT_EQ(ITEMS[i], menu.TextItem(i)); + } + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(1u, menu.MenuEnd()); +} + +TEST_F(ScreenUITest, PhoneMenuSelectSmoke) { + int sel = 0; + TextMenu menu(false, 10, 20, HEADERS, ITEMS, sel, 20, draw_funcs_); + // Mimic down button 10 times (2 * items size) + for (int i = 0; i < 10; i++) { + sel = menu.Select(++sel); + ASSERT_EQ(sel, menu.selection()); + + // Wraps the selection for unscrollable menu when it reaches the boundary. + int expected = (i + 1) % 5; + ASSERT_EQ(expected, menu.selection()); + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + } + + // Mimic up button 10 times + for (int i = 0; i < 10; i++) { + sel = menu.Select(--sel); + ASSERT_EQ(sel, menu.selection()); + + int expected = (9 - i) % 5; + ASSERT_EQ(expected, menu.selection()); + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + } +} + +TEST_F(ScreenUITest, WearMenuSelectSmoke) { + int sel = 0; + TextMenu menu(true, 10, 20, HEADERS, ITEMS, sel, 20, draw_funcs_); + // Mimic pressing down button 10 times (2 * items size) + for (int i = 0; i < 10; i++) { + sel = menu.Select(++sel); + ASSERT_EQ(sel, menu.selection()); + + // Stops the selection at the boundary if the menu is scrollable. + int expected = std::min(i + 1, 4); + ASSERT_EQ(expected, menu.selection()); + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + } + + // Mimic pressing up button 10 times + for (int i = 0; i < 10; i++) { + sel = menu.Select(--sel); + ASSERT_EQ(sel, menu.selection()); + + int expected = std::max(3 - i, 0); + ASSERT_EQ(expected, menu.selection()); + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + } +} + +TEST_F(ScreenUITest, WearMenuSelectItemsOverflow) { + int sel = 1; + TextMenu menu(true, 3, 20, HEADERS, ITEMS, sel, 20, draw_funcs_); + ASSERT_EQ(5u, menu.ItemsCount()); + + // Scroll the menu to the end, and check the start & end of menu. + for (int i = 0; i < 3; i++) { + sel = menu.Select(++sel); + ASSERT_EQ(i + 2, sel); + ASSERT_EQ(static_cast(i), menu.MenuStart()); + ASSERT_EQ(static_cast(i + 3), menu.MenuEnd()); + } + + // Press down button one more time won't change the MenuStart() and MenuEnd(). + sel = menu.Select(++sel); + ASSERT_EQ(4, sel); + ASSERT_EQ(2u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + + // Scroll the menu to the top. + // The expected menu sel, start & ends are: + // sel 3, start 2, end 5 + // sel 2, start 2, end 5 + // sel 1, start 1, end 4 + // sel 0, start 0, end 3 + for (int i = 0; i < 4; i++) { + sel = menu.Select(--sel); + ASSERT_EQ(3 - i, sel); + ASSERT_EQ(static_cast(std::min(3 - i, 2)), menu.MenuStart()); + ASSERT_EQ(static_cast(std::min(6 - i, 5)), menu.MenuEnd()); + } + + // Press up button one more time won't change the MenuStart() and MenuEnd(). + sel = menu.Select(--sel); + ASSERT_EQ(0, sel); + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(3u, menu.MenuEnd()); +} + +TEST_F(ScreenUITest, GraphicMenuSelection) { + auto image = GRSurface::Create(50, 50, 50, 1); + auto header = image->Clone(); + std::vector items = { + image.get(), + image.get(), + image.get(), + }; + GraphicMenu menu(header.get(), items, 0, draw_funcs_); + + ASSERT_EQ(0, menu.selection()); + + int sel = 0; + for (int i = 0; i < 3; i++) { + sel = menu.Select(++sel); + ASSERT_EQ((i + 1) % 3, sel); + ASSERT_EQ(sel, menu.selection()); + } + + sel = 0; + for (int i = 0; i < 3; i++) { + sel = menu.Select(--sel); + ASSERT_EQ(2 - i, sel); + ASSERT_EQ(sel, menu.selection()); + } +} + +TEST_F(ScreenUITest, GraphicMenuValidate) { + auto image = GRSurface::Create(50, 50, 50, 1); + auto header = image->Clone(); + std::vector items = { + image.get(), + image.get(), + image.get(), + }; + + ASSERT_TRUE(GraphicMenu::Validate(200, 200, header.get(), items)); + + // Menu exceeds the horizontal boundary. + auto wide_surface = GRSurface::Create(300, 50, 300, 1); + ASSERT_FALSE(GraphicMenu::Validate(299, 200, wide_surface.get(), items)); + + // Menu exceeds the vertical boundary. + items.emplace_back(image.get()); + ASSERT_FALSE(GraphicMenu::Validate(200, 249, header.get(), items)); +} + +static constexpr int kMagicAction = 101; + +enum class KeyCode : int { + TIMEOUT = -1, + NO_OP = 0, + UP = 1, + DOWN = 2, + ENTER = 3, + MAGIC = 1001, + LAST, +}; + +static const std::map kKeyMapping{ + // clang-format off + { KeyCode::NO_OP, Device::kNoAction }, + { KeyCode::UP, Device::kHighlightUp }, + { KeyCode::DOWN, Device::kHighlightDown }, + { KeyCode::ENTER, Device::kInvokeItem }, + { KeyCode::MAGIC, kMagicAction }, + // clang-format on +}; + +class TestableScreenRecoveryUI : public ScreenRecoveryUI { + public: + int WaitKey() override; + + void SetKeyBuffer(const std::vector& buffer); + + int KeyHandler(int key, bool visible) const; + + private: + FRIEND_TEST(DISABLED_ScreenRecoveryUITest, Init); + FRIEND_TEST(DISABLED_ScreenRecoveryUITest, RtlLocale); + FRIEND_TEST(DISABLED_ScreenRecoveryUITest, RtlLocaleWithSuffix); + FRIEND_TEST(DISABLED_ScreenRecoveryUITest, LoadAnimation); + FRIEND_TEST(DISABLED_ScreenRecoveryUITest, LoadAnimation_MissingAnimation); + + std::vector key_buffer_; + size_t key_buffer_index_; +}; + +void TestableScreenRecoveryUI::SetKeyBuffer(const std::vector& buffer) { + key_buffer_ = buffer; + key_buffer_index_ = 0; +} + +int TestableScreenRecoveryUI::KeyHandler(int key, bool) const { + KeyCode key_code = static_cast(key); + if (kKeyMapping.find(key_code) != kKeyMapping.end()) { + return kKeyMapping.at(key_code); + } + return Device::kNoAction; +} + +int TestableScreenRecoveryUI::WaitKey() { + if (IsKeyInterrupted()) { + return static_cast(RecoveryUI::KeyError::INTERRUPTED); + } + + CHECK_LT(key_buffer_index_, key_buffer_.size()); + return static_cast(key_buffer_[key_buffer_index_++]); +} + +class DISABLED_ScreenRecoveryUITest : public ::testing::Test { + protected: + const std::string kTestLocale = "en-US"; + const std::string kTestRtlLocale = "ar"; + const std::string kTestRtlLocaleWithSuffix = "ar-EG"; + + void SetUp() override { + has_graphics_ = gr_init() == 0; + gr_exit(); + + if (has_graphics_) { + ui_ = std::make_unique(); + } + + testdata_dir_ = from_testdata_base(""); + Paths::Get().set_resource_dir(testdata_dir_); + res_set_resource_dir(testdata_dir_); + } + + bool has_graphics_; + std::unique_ptr ui_; + std::string testdata_dir_; +}; + +#define RETURN_IF_NO_GRAPHICS \ + do { \ + if (!has_graphics_) { \ + GTEST_LOG_(INFO) << "Test skipped due to no available graphics device"; \ + return; \ + } \ + } while (false) + +TEST_F(DISABLED_ScreenRecoveryUITest, Init) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + ASSERT_EQ(kTestLocale, ui_->GetLocale()); + ASSERT_FALSE(ui_->rtl_locale_); + ASSERT_FALSE(ui_->IsTextVisible()); + ASSERT_FALSE(ui_->WasTextEverVisible()); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, dtor_NotCallingInit) { + ui_.reset(); + ASSERT_FALSE(ui_); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, ShowText) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + ASSERT_FALSE(ui_->IsTextVisible()); + ui_->ShowText(true); + ASSERT_TRUE(ui_->IsTextVisible()); + ASSERT_TRUE(ui_->WasTextEverVisible()); + + ui_->ShowText(false); + ASSERT_FALSE(ui_->IsTextVisible()); + ASSERT_TRUE(ui_->WasTextEverVisible()); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, RtlLocale) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestRtlLocale)); + ASSERT_TRUE(ui_->rtl_locale_); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, RtlLocaleWithSuffix) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix)); + ASSERT_TRUE(ui_->rtl_locale_); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + ui_->SetKeyBuffer({ + KeyCode::UP, + KeyCode::DOWN, + KeyCode::UP, + KeyCode::DOWN, + KeyCode::ENTER, + }); + ASSERT_EQ(3u, ui_->ShowMenu(HEADERS, ITEMS, 3, true, + std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(), + std::placeholders::_1, std::placeholders::_2))); + + ui_->SetKeyBuffer({ + KeyCode::UP, + KeyCode::UP, + KeyCode::NO_OP, + KeyCode::NO_OP, + KeyCode::UP, + KeyCode::ENTER, + }); + ASSERT_EQ(2u, ui_->ShowMenu(HEADERS, ITEMS, 0, true, + std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(), + std::placeholders::_1, std::placeholders::_2))); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_NotMenuOnly) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + ui_->SetKeyBuffer({ + KeyCode::MAGIC, + }); + ASSERT_EQ(static_cast(kMagicAction), + ui_->ShowMenu(HEADERS, ITEMS, 3, false, + std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(), + std::placeholders::_1, std::placeholders::_2))); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_TimedOut) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + ui_->SetKeyBuffer({ + KeyCode::TIMEOUT, + }); + ASSERT_EQ(static_cast(RecoveryUI::KeyError::TIMED_OUT), + ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr)); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + ui_->ShowText(true); + ui_->ShowText(false); + ASSERT_TRUE(ui_->WasTextEverVisible()); + + ui_->SetKeyBuffer({ + KeyCode::TIMEOUT, + KeyCode::DOWN, + KeyCode::ENTER, + }); + ASSERT_EQ(4u, ui_->ShowMenu(HEADERS, ITEMS, 3, true, + std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(), + std::placeholders::_1, std::placeholders::_2))); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenuWithInterrupt) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + ui_->SetKeyBuffer({ + KeyCode::UP, + KeyCode::DOWN, + KeyCode::UP, + KeyCode::DOWN, + KeyCode::ENTER, + }); + + ui_->InterruptKey(); + ASSERT_EQ(static_cast(RecoveryUI::KeyError::INTERRUPTED), + ui_->ShowMenu(HEADERS, ITEMS, 3, true, + std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(), + std::placeholders::_1, std::placeholders::_2))); + + ui_->SetKeyBuffer({ + KeyCode::UP, + KeyCode::UP, + KeyCode::NO_OP, + KeyCode::NO_OP, + KeyCode::UP, + KeyCode::ENTER, + }); + ASSERT_EQ(static_cast(RecoveryUI::KeyError::INTERRUPTED), + ui_->ShowMenu(HEADERS, ITEMS, 0, true, + std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(), + std::placeholders::_1, std::placeholders::_2))); +} + +TEST_F(DISABLED_ScreenRecoveryUITest, LoadAnimation) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + // Make a few copies of loop00000.png from testdata. + std::string image_data; + ASSERT_TRUE(android::base::ReadFileToString(testdata_dir_ + "/loop00000.png", &image_data)); + + std::vector tempfiles; + TemporaryDir resource_dir; + for (const auto& name : { "00002", "00100", "00050" }) { + tempfiles.push_back(android::base::StringPrintf("%s/loop%s.png", resource_dir.path, name)); + ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back())); + } + for (const auto& name : { "00", "01" }) { + tempfiles.push_back(android::base::StringPrintf("%s/intro%s.png", resource_dir.path, name)); + ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back())); + } + Paths::Get().set_resource_dir(resource_dir.path); + + ui_->LoadAnimation(); + + ASSERT_EQ(2u, ui_->intro_frames_.size()); + ASSERT_EQ(3u, ui_->loop_frames_.size()); + + for (const auto& name : tempfiles) { + ASSERT_EQ(0, unlink(name.c_str())); + } +} + +TEST_F(DISABLED_ScreenRecoveryUITest, LoadAnimation_MissingAnimation) { + RETURN_IF_NO_GRAPHICS; + + ASSERT_TRUE(ui_->Init(kTestLocale)); + // We need a dir that doesn't contain any animation. However, using TemporaryDir will give + // leftovers since this is a death test where TemporaryDir::~TemporaryDir() won't be called. + Paths::Get().set_resource_dir("/proc/self"); + + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + ASSERT_EXIT(ui_->LoadAnimation(), ::testing::KilledBySignal(SIGABRT), ""); +} + +#undef RETURN_IF_NO_GRAPHICS diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp index 434ee25bfd..64b8956f72 100644 --- a/tests/unit/sysutil_test.cpp +++ b/tests/unit/sysutil_test.cpp @@ -14,14 +14,14 @@ * limitations under the License. */ -#include - #include #include -#include +#include +#include -#include "otautil/SysUtil.h" +#include "otautil/rangeset.h" +#include "otautil/sysutil.h" TEST(SysUtilTest, InvalidArgs) { MemMapping mapping; @@ -30,6 +30,65 @@ TEST(SysUtilTest, InvalidArgs) { ASSERT_FALSE(mapping.MapFile("")); } +TEST(SysUtilTest, ParseBlockMapFile_smoke) { + std::vector content = { + "/dev/abc", "49652 4096", "3", "1000 1008", "2100 2102", "30 33", + }; + + TemporaryFile temp_file; + ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path)); + + auto block_map_data = BlockMapData::ParseBlockMapFile(temp_file.path); + ASSERT_EQ("/dev/abc", block_map_data.path()); + ASSERT_EQ(49652, block_map_data.file_size()); + ASSERT_EQ(4096, block_map_data.block_size()); + ASSERT_EQ(RangeSet(std::vector{ + { 1000, 1008 }, + { 2100, 2102 }, + { 30, 33 }, + }), + block_map_data.block_ranges()); +} + +TEST(SysUtilTest, ParseBlockMapFile_invalid_line_count) { + std::vector content = { + "/dev/abc", "49652 4096", "2", "1000 1008", "2100 2102", "30 33", + }; + + TemporaryFile temp_file; + ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path)); + + auto block_map_data1 = BlockMapData::ParseBlockMapFile(temp_file.path); + ASSERT_FALSE(block_map_data1); +} + +TEST(SysUtilTest, ParseBlockMapFile_invalid_size) { + std::vector content = { + "/dev/abc", + "42949672950 4294967295", + "1", + "0 10", + }; + + TemporaryFile temp_file; + ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path)); + + auto block_map_data = BlockMapData::ParseBlockMapFile(temp_file.path); + ASSERT_EQ("/dev/abc", block_map_data.path()); + ASSERT_EQ(42949672950, block_map_data.file_size()); + ASSERT_EQ(4294967295, block_map_data.block_size()); + + content[1] = "42949672950 4294967296"; + ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path)); + auto large_block_size = BlockMapData::ParseBlockMapFile(temp_file.path); + ASSERT_FALSE(large_block_size); + + content[1] = "4294967296 1"; + ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path)); + auto too_many_blocks = BlockMapData::ParseBlockMapFile(temp_file.path); + ASSERT_FALSE(too_many_blocks); +} + TEST(SysUtilTest, MapFileRegularFile) { TemporaryFile temp_file1; std::string content = "abc"; @@ -128,3 +187,13 @@ TEST(SysUtilTest, MapFileBlockMapInvalidBlockMap) { ASSERT_TRUE(android::base::WriteStringToFile("/doesntexist\n4096 4096\n1\n0 1\n", temp_file.path)); ASSERT_FALSE(mapping.MapFile(filename)); } + +TEST(SysUtilTest, StringVectorToNullTerminatedArray) { + std::vector args{ "foo", "bar", "baz" }; + auto args_with_nullptr = StringVectorToNullTerminatedArray(args); + ASSERT_EQ(4, args_with_nullptr.size()); + ASSERT_STREQ("foo", args_with_nullptr[0]); + ASSERT_STREQ("bar", args_with_nullptr[1]); + ASSERT_STREQ("baz", args_with_nullptr[2]); + ASSERT_EQ(nullptr, args_with_nullptr[3]); +} diff --git a/tests/component/uncrypt_test.cpp b/tests/unit/uncrypt_test.cpp similarity index 99% rename from tests/component/uncrypt_test.cpp rename to tests/unit/uncrypt_test.cpp index 55baca2e35..e97d589a64 100644 --- a/tests/component/uncrypt_test.cpp +++ b/tests/unit/uncrypt_test.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/tests/unit/update_verifier_test.cpp b/tests/unit/update_verifier_test.cpp new file mode 100644 index 0000000000..e27e58c227 --- /dev/null +++ b/tests/unit/update_verifier_test.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "care_map.pb.h" + +using namespace std::string_literals; + +class UpdateVerifierTest : public ::testing::Test { + protected: + void SetUp() override { + std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", ""); + verity_supported = android::base::EqualsIgnoreCase(verity_mode, "enforcing"); + + care_map_prefix_ = care_map_dir_.path + "/care_map"s; + care_map_pb_ = care_map_dir_.path + "/care_map.pb"s; + care_map_txt_ = care_map_dir_.path + "/care_map.txt"s; + // Overrides the the care_map_prefix. + verifier_.set_care_map_prefix(care_map_prefix_); + + property_id_ = "ro.build.fingerprint"; + fingerprint_ = android::base::GetProperty(property_id_, ""); + // Overrides the property_reader if we cannot read the given property on the device. + if (fingerprint_.empty()) { + fingerprint_ = "mock_fingerprint"; + verifier_.set_property_reader([](const std::string& /* id */) { return "mock_fingerprint"; }); + } + } + + void TearDown() override { + unlink(care_map_pb_.c_str()); + unlink(care_map_txt_.c_str()); + } + + // Returns a serialized string of the proto3 message according to the given partition info. + std::string ConstructProto( + std::vector>& partitions) { + recovery_update_verifier::CareMap result; + for (const auto& partition : partitions) { + recovery_update_verifier::CareMap::PartitionInfo info; + if (partition.find("name") != partition.end()) { + info.set_name(partition.at("name")); + } + if (partition.find("ranges") != partition.end()) { + info.set_ranges(partition.at("ranges")); + } + if (partition.find("id") != partition.end()) { + info.set_id(partition.at("id")); + } + if (partition.find("fingerprint") != partition.end()) { + info.set_fingerprint(partition.at("fingerprint")); + } + + *result.add_partitions() = info; + } + + return result.SerializeAsString(); + } + + bool verity_supported; + UpdateVerifier verifier_; + + TemporaryDir care_map_dir_; + std::string care_map_prefix_; + std::string care_map_pb_; + std::string care_map_txt_; + + std::string property_id_; + std::string fingerprint_; +}; + +TEST_F(UpdateVerifierTest, verify_image_no_care_map) { + ASSERT_FALSE(verifier_.ParseCareMap()); +} + +TEST_F(UpdateVerifierTest, verify_image_text_format) { + // This test relies on dm-verity support. + if (!verity_supported) { + GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; + return; + } + + std::string content = "system\n2,0,1"; + ASSERT_TRUE(android::base::WriteStringToFile(content, care_map_txt_)); + // CareMap in text format is no longer supported. + ASSERT_FALSE(verifier_.ParseCareMap()); +} + +TEST_F(UpdateVerifierTest, verify_image_empty_care_map) { + ASSERT_FALSE(verifier_.ParseCareMap()); +} + +TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_smoke) { + // This test relies on dm-verity support. + if (!verity_supported) { + GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; + return; + } + + std::vector> partitions = { + { + { "name", "system" }, + { "ranges", "2,0,1" }, + { "id", property_id_ }, + { "fingerprint", fingerprint_ }, + }, + }; + + std::string proto = ConstructProto(partitions); + ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_)); + ASSERT_TRUE(verifier_.ParseCareMap()); + ASSERT_TRUE(verifier_.VerifyPartitions()); +} + +TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_missing_name) { + // This test relies on dm-verity support. + if (!verity_supported) { + GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; + return; + } + + std::vector> partitions = { + { + { "ranges", "2,0,1" }, + { "id", property_id_ }, + { "fingerprint", fingerprint_ }, + }, + }; + + std::string proto = ConstructProto(partitions); + ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_)); + ASSERT_FALSE(verifier_.ParseCareMap()); +} + +TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_bad_ranges) { + // This test relies on dm-verity support. + if (!verity_supported) { + GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; + return; + } + + std::vector> partitions = { + { + { "name", "system" }, + { "ranges", "3,0,1" }, + { "id", property_id_ }, + { "fingerprint", fingerprint_ }, + }, + }; + + std::string proto = ConstructProto(partitions); + ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_)); + ASSERT_FALSE(verifier_.ParseCareMap()); +} + +TEST_F(UpdateVerifierTest, verify_image_protobuf_empty_fingerprint) { + // This test relies on dm-verity support. + if (!verity_supported) { + GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; + return; + } + + std::vector> partitions = { + { + { "name", "system" }, + { "ranges", "2,0,1" }, + }, + }; + + std::string proto = ConstructProto(partitions); + ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_)); + ASSERT_FALSE(verifier_.ParseCareMap()); +} + +TEST_F(UpdateVerifierTest, verify_image_protobuf_fingerprint_mismatch) { + // This test relies on dm-verity support. + if (!verity_supported) { + GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; + return; + } + + std::vector> partitions = { + { + { "name", "system" }, + { "ranges", "2,0,1" }, + { "id", property_id_ }, + { "fingerprint", "unsupported_fingerprint" }, + }, + }; + + std::string proto = ConstructProto(partitions); + ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_)); + ASSERT_FALSE(verifier_.ParseCareMap()); +} diff --git a/tests/unit/updater_test.cpp b/tests/unit/updater_test.cpp new file mode 100644 index 0000000000..8993dd8b70 --- /dev/null +++ b/tests/unit/updater_test.cpp @@ -0,0 +1,1227 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "applypatch/applypatch.h" +#include "common/test_constants.h" +#include "edify/expr.h" +#include "otautil/error_code.h" +#include "otautil/paths.h" +#include "otautil/print_sha1.h" +#include "otautil/sysutil.h" +#include "private/commands.h" +#include "updater/blockimg.h" +#include "updater/install.h" +#include "updater/updater.h" +#include "updater/updater_runtime.h" + +using namespace std::string_literals; + +using PackageEntries = std::unordered_map; + +static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code, + Updater* updater) { + std::unique_ptr e; + int error_count = 0; + ASSERT_EQ(0, ParseString(expr_str, &e, &error_count)); + ASSERT_EQ(0, error_count); + + State state(expr_str, updater); + + std::string result; + bool status = Evaluate(&state, e, &result); + + if (expected == nullptr) { + ASSERT_FALSE(status); + } else { + ASSERT_TRUE(status) << "Evaluate() finished with error message: " << state.errmsg; + ASSERT_STREQ(expected, result.c_str()); + } + + // Error code is set in updater/updater.cpp only, by parsing State.errmsg. + ASSERT_EQ(kNoError, state.error_code); + + // Cause code should always be available. + ASSERT_EQ(cause_code, state.cause_code); +} + +static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code) { + Updater updater(std::make_unique(nullptr)); + expect(expected, expr_str, cause_code, &updater); +} + +static void BuildUpdatePackage(const PackageEntries& entries, int fd) { + FILE* zip_file_ptr = fdopen(fd, "wb"); + ZipWriter zip_writer(zip_file_ptr); + + for (const auto& entry : entries) { + // All the entries are written as STORED. + ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0)); + if (!entry.second.empty()) { + ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size())); + } + ASSERT_EQ(0, zip_writer.FinishEntry()); + } + + ASSERT_EQ(0, zip_writer.Finish()); + ASSERT_EQ(0, fclose(zip_file_ptr)); +} + +static std::string GetSha1(std::string_view content) { + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast(content.data()), content.size(), digest); + return print_sha1(digest); +} + +static Value* BlobToString(const char* name, State* state, + const std::vector>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); + } + + std::vector> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } + + if (args[0]->type != Value::Type::BLOB) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects a BLOB argument", name); + } + + args[0]->type = Value::Type::STRING; + return args[0].release(); +} + +class UpdaterTestBase { + protected: + UpdaterTestBase() : updater_(std::make_unique(nullptr)) {} + + void SetUp() { + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + + // Each test is run in a separate process (isolated mode). Shared temporary files won't cause + // conflicts. + Paths::Get().set_cache_temp_source(temp_saved_source_.path); + Paths::Get().set_last_command_file(temp_last_command_.path); + Paths::Get().set_stash_directory_base(temp_stash_base_.path); + + last_command_file_ = temp_last_command_.path; + image_file_ = image_temp_file_.path; + } + + void TearDown() { + // Clean up the last_command_file if any. + ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_)); + + // Clear partition updated marker if any. + std::string updated_marker{ temp_stash_base_.path }; + updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED"; + ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker)); + } + + void RunBlockImageUpdate(bool is_verify, PackageEntries entries, const std::string& image_file, + const std::string& result, CauseCode cause_code = kNoCause) { + CHECK(entries.find("transfer_list") != entries.end()); + std::string new_data = + entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data"; + std::string script = is_verify ? "block_image_verify" : "block_image_update"; + script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data + + R"(", "patch_data"))"; + entries.emplace(Updater::SCRIPT_NAME, script); + + // Build the update package. + TemporaryFile zip_file; + BuildUpdatePackage(entries, zip_file.release()); + + // Set up the handler, command_pipe, patch offset & length. + TemporaryFile temp_pipe; + ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false)); + ASSERT_TRUE(updater_.RunUpdate()); + ASSERT_EQ(result, updater_.GetResult()); + + // Parse the cause code written to the command pipe. + int received_cause_code = kNoCause; + std::string pipe_content; + ASSERT_TRUE(android::base::ReadFileToString(temp_pipe.path, &pipe_content)); + auto lines = android::base::Split(pipe_content, "\n"); + for (std::string_view line : lines) { + if (android::base::ConsumePrefix(&line, "log cause: ")) { + ASSERT_TRUE(android::base::ParseInt(line.data(), &received_cause_code)); + } + } + ASSERT_EQ(cause_code, received_cause_code); + } + + TemporaryFile temp_saved_source_; + TemporaryDir temp_stash_base_; + std::string last_command_file_; + std::string image_file_; + + Updater updater_; + + private: + TemporaryFile temp_last_command_; + TemporaryFile image_temp_file_; +}; + +class UpdaterTest : public UpdaterTestBase, public ::testing::Test { + protected: + void SetUp() override { + UpdaterTestBase::SetUp(); + + RegisterFunction("blob_to_string", BlobToString); + // Enable a special command "abort" to simulate interruption. + Command::abort_allowed_ = true; + } + + void TearDown() override { + UpdaterTestBase::TearDown(); + } + + void SetUpdaterCmdPipe(int fd) { + FILE* cmd_pipe = fdopen(fd, "w"); + ASSERT_NE(nullptr, cmd_pipe); + updater_.cmd_pipe_.reset(cmd_pipe); + } + + void SetUpdaterOtaPackageHandle(ZipArchiveHandle handle) { + updater_.package_handle_ = handle; + } + + void FlushUpdaterCommandPipe() const { + fflush(updater_.cmd_pipe_.get()); + } +}; + +TEST_F(UpdaterTest, getprop) { + expect(android::base::GetProperty("ro.product.device", "").c_str(), + "getprop(\"ro.product.device\")", + kNoCause); + + expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(), + "getprop(\"ro.build.fingerprint\")", + kNoCause); + + // getprop() accepts only one parameter. + expect(nullptr, "getprop()", kArgsParsingFailure); + expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure); +} + +TEST_F(UpdaterTest, patch_partition_check) { + // Zero argument is not valid. + expect(nullptr, "patch_partition_check()", kArgsParsingFailure); + + std::string source_file = from_testdata_base("boot.img"); + std::string source_content; + ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content)); + size_t source_size = source_content.size(); + std::string source_hash = GetSha1(source_content); + Partition source(source_file, source_size, source_hash); + + std::string target_file = from_testdata_base("recovery.img"); + std::string target_content; + ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content)); + size_t target_size = target_content.size(); + std::string target_hash = GetSha1(target_content); + Partition target(target_file, target_size, target_hash); + + // One argument is not valid. + expect(nullptr, "patch_partition_check(\"" + source.ToString() + "\")", kArgsParsingFailure); + expect(nullptr, "patch_partition_check(\"" + target.ToString() + "\")", kArgsParsingFailure); + + // Both of the source and target have the desired checksum. + std::string cmd = + "patch_partition_check(\"" + source.ToString() + "\", \"" + target.ToString() + "\")"; + expect("t", cmd, kNoCause); + + // Only source partition has the desired checksum. + Partition bad_target(target_file, target_size - 1, target_hash); + cmd = "patch_partition_check(\"" + source.ToString() + "\", \"" + bad_target.ToString() + "\")"; + expect("t", cmd, kNoCause); + + // Only target partition has the desired checksum. + Partition bad_source(source_file, source_size + 1, source_hash); + cmd = "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + target.ToString() + "\")"; + expect("t", cmd, kNoCause); + + // Neither of the source or target has the desired checksum. + cmd = + "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + bad_target.ToString() + "\")"; + expect("", cmd, kNoCause); +} + +TEST_F(UpdaterTest, file_getprop) { + // file_getprop() expects two arguments. + expect(nullptr, "file_getprop()", kArgsParsingFailure); + expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // File doesn't exist. + expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFreadFailure); + + // Reject too large files (current limit = 65536). + TemporaryFile temp_file1; + std::string buffer(65540, '\0'); + ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path)); + + // Read some keys. + TemporaryFile temp_file2; + std::string content("ro.product.name=tardis\n" + "# comment\n\n\n" + "ro.product.model\n" + "ro.product.board = magic \n"); + ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path)); + + std::string script1("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.name\")"); + expect("tardis", script1, kNoCause); + + std::string script2("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.board\")"); + expect("magic", script2, kNoCause); + + // No match. + std::string script3("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.wrong\")"); + expect("", script3, kNoCause); + + std::string script4("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.name=\")"); + expect("", script4, kNoCause); + + std::string script5("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.nam\")"); + expect("", script5, kNoCause); + + std::string script6("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.model\")"); + expect("", script6, kNoCause); +} + +// TODO: Test extracting to block device. +TEST_F(UpdaterTest, package_extract_file) { + // package_extract_file expects 1 or 2 arguments. + expect(nullptr, "package_extract_file()", kArgsParsingFailure); + expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Need to set up the ziphandle. + SetUpdaterOtaPackageHandle(handle); + + // Two-argument version. + TemporaryFile temp_file1; + std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")"); + expect("t", script, kNoCause, &updater_); + + // Verify the extracted entry. + std::string data; + ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); + ASSERT_EQ(kATxtContents, data); + + // Now extract another entry to the same location, which should overwrite. + script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")"; + expect("t", script, kNoCause, &updater_); + + ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); + ASSERT_EQ(kBTxtContents, data); + + // Missing zip entry. The two-argument version doesn't abort. + script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")"; + expect("", script, kNoCause, &updater_); + + // Extract to /dev/full should fail. + script = "package_extract_file(\"a.txt\", \"/dev/full\")"; + expect("", script, kNoCause, &updater_); + + // One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to + // VAL_STRING for equality test. + script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\""; + expect("t", script, kNoCause, &updater_); + + script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\""; + expect("t", script, kNoCause, &updater_); + + // Missing entry. The one-argument version aborts the evaluation. + script = "package_extract_file(\"doesntexist\")"; + expect(nullptr, script, kPackageExtractFileFailure, &updater_); +} + +TEST_F(UpdaterTest, read_file) { + // read_file() expects one argument. + expect(nullptr, "read_file()", kArgsParsingFailure); + expect(nullptr, "read_file(\"arg1\", \"arg2\")", kArgsParsingFailure); + + // Write some value to file and read back. + TemporaryFile temp_file; + std::string script("write_value(\"foo\", \""s + temp_file.path + "\");"); + expect("t", script, kNoCause); + + script = "read_file(\""s + temp_file.path + "\") == \"foo\""; + expect("t", script, kNoCause); + + script = "read_file(\""s + temp_file.path + "\") == \"bar\""; + expect("", script, kNoCause); + + // It should fail gracefully when read fails. + script = "read_file(\"/doesntexist\")"; + expect("", script, kNoCause); +} + +TEST_F(UpdaterTest, compute_hash_tree_smoke) { + std::string data; + for (unsigned char i = 0; i < 128; i++) { + data += std::string(4096, i); + } + // Appends an additional block for verity data. + data += std::string(4096, 0); + ASSERT_EQ(129 * 4096, data.size()); + ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_)); + + std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"; + std::string expected_root_hash = + "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca"; + // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash + std::vector tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt, + expected_root_hash }; + std::string hash_tree_command = android::base::Join(tokens, " "); + + std::vector transfer_list{ + "4", "2", "0", "2", hash_tree_command, + }; + + PackageEntries entries{ + { "new_data", "" }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list, "\n") }, + }; + + RunBlockImageUpdate(false, entries, image_file_, "t"); + + std::string updated; + ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated)); + ASSERT_EQ(129 * 4096, updated.size()); + ASSERT_EQ(data.substr(0, 128 * 4096), updated.substr(0, 128 * 4096)); + + // Computes the SHA256 of the salt + hash_tree_data and expects the result to match with the + // root_hash. + std::vector salt_bytes; + ASSERT_TRUE(HashTreeBuilder::ParseBytesArrayFromString(salt, &salt_bytes)); + std::vector hash_tree = std::move(salt_bytes); + hash_tree.insert(hash_tree.end(), updated.begin() + 128 * 4096, updated.end()); + + std::vector digest(SHA256_DIGEST_LENGTH); + SHA256(hash_tree.data(), hash_tree.size(), digest.data()); + ASSERT_EQ(expected_root_hash, HashTreeBuilder::BytesArrayToString(digest)); +} + +TEST_F(UpdaterTest, compute_hash_tree_root_mismatch) { + std::string data; + for (size_t i = 0; i < 128; i++) { + data += std::string(4096, i); + } + // Appends an additional block for verity data. + data += std::string(4096, 0); + ASSERT_EQ(129 * 4096, data.size()); + // Corrupts one bit + data[4096] = 'A'; + ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_)); + + std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"; + std::string expected_root_hash = + "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca"; + // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash + std::vector tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt, + expected_root_hash }; + std::string hash_tree_command = android::base::Join(tokens, " "); + + std::vector transfer_list{ + "4", "2", "0", "2", hash_tree_command, + }; + + PackageEntries entries{ + { "new_data", "" }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list, "\n") }, + }; + + RunBlockImageUpdate(false, entries, image_file_, "", kHashTreeComputationFailure); +} + +TEST_F(UpdaterTest, write_value) { + // write_value() expects two arguments. + expect(nullptr, "write_value()", kArgsParsingFailure); + expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // filename cannot be empty. + expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure); + + // Write some value to file. + TemporaryFile temp_file; + std::string value = "magicvalue"; + std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")"); + expect("t", script, kNoCause); + + // Verify the content. + std::string content; + ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); + ASSERT_EQ(value, content); + + // Allow writing empty string. + script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")"; + expect("t", script, kNoCause); + + // Verify the content. + ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); + ASSERT_EQ("", content); + + // It should fail gracefully when write fails. + script = "write_value(\"value\", \"/proc/0/file1\")"; + expect("", script, kNoCause); +} + +TEST_F(UpdaterTest, get_stage) { + // get_stage() expects one argument. + expect(nullptr, "get_stage()", kArgsParsingFailure); + expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure); + expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // Set up a local file as BCB. + TemporaryFile tf; + std::string temp_file(tf.path); + bootloader_message boot; + strlcpy(boot.stage, "2/3", sizeof(boot.stage)); + std::string err; + ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); + + // Can read the stage value. + std::string script("get_stage(\"" + temp_file + "\")"); + expect("2/3", script, kNoCause); + + // Bad BCB path. + script = "get_stage(\"doesntexist\")"; + expect("", script, kNoCause); +} + +TEST_F(UpdaterTest, set_stage) { + // set_stage() expects two arguments. + expect(nullptr, "set_stage()", kArgsParsingFailure); + expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // Set up a local file as BCB. + TemporaryFile tf; + std::string temp_file(tf.path); + bootloader_message boot; + strlcpy(boot.command, "command", sizeof(boot.command)); + strlcpy(boot.stage, "2/3", sizeof(boot.stage)); + std::string err; + ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); + + // Write with set_stage(). + std::string script("set_stage(\"" + temp_file + "\", \"1/3\")"); + expect(tf.path, script, kNoCause); + + // Verify. + bootloader_message boot_verify; + ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err)); + + // Stage should be updated, with command part untouched. + ASSERT_STREQ("1/3", boot_verify.stage); + ASSERT_STREQ(boot.command, boot_verify.command); + + // Bad BCB path. + script = "set_stage(\"doesntexist\", \"1/3\")"; + expect("", script, kNoCause); + + script = "set_stage(\"/dev/full\", \"1/3\")"; + expect("", script, kNoCause); +} + +TEST_F(UpdaterTest, set_progress) { + // set_progress() expects one argument. + expect(nullptr, "set_progress()", kArgsParsingFailure); + expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); + + // Invalid progress argument. + expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure); + expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure); + + TemporaryFile tf; + SetUpdaterCmdPipe(tf.release()); + expect(".52", "set_progress(\".52\")", kNoCause, &updater_); + FlushUpdaterCommandPipe(); + + std::string cmd; + ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); + ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd); + // recovery-updater protocol expects 2 tokens ("set_progress "). + ASSERT_EQ(2U, android::base::Split(cmd, " ").size()); +} + +TEST_F(UpdaterTest, show_progress) { + // show_progress() expects two arguments. + expect(nullptr, "show_progress()", kArgsParsingFailure); + expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // Invalid progress arguments. + expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); + expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure); + expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure); + + TemporaryFile tf; + SetUpdaterCmdPipe(tf.release()); + expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_); + FlushUpdaterCommandPipe(); + + std::string cmd; + ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); + ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd); + // recovery-updater protocol expects 3 tokens ("progress "). + ASSERT_EQ(3U, android::base::Split(cmd, " ").size()); +} + +TEST_F(UpdaterTest, block_image_update_parsing_error) { + std::vector transfer_list{ + // clang-format off + "4", + "2", + "0", + // clang-format on + }; + + PackageEntries entries{ + { "new_data", "" }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list, '\n') }, + }; + + RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure); +} + +// Generates the bsdiff of the given source and target images, and writes the result entries. +// target_blocks specifies the block count to be written into the `bsdiff` command, which may be +// different from the given target size in order to trigger overrun / underrun paths. +static void GetEntriesForBsdiff(std::string_view source, std::string_view target, + size_t target_blocks, PackageEntries* entries) { + // Generate the patch data. + TemporaryFile patch_file; + ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast(source.data()), source.size(), + reinterpret_cast(target.data()), target.size(), + patch_file.path, nullptr)); + std::string patch_content; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content)); + + // Create the transfer list that contains a bsdiff. + std::string src_hash = GetSha1(source); + std::string tgt_hash = GetSha1(target); + size_t source_blocks = source.size() / 4096; + std::vector transfer_list{ + // clang-format off + "4", + std::to_string(target_blocks), + "0", + "0", + // bsdiff patch_offset patch_length source_hash target_hash target_range source_block_count + // source_range + android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,%zu %zu 2,0,%zu", patch_content.size(), + src_hash.c_str(), tgt_hash.c_str(), target_blocks, source_blocks, + source_blocks), + // clang-format on + }; + + *entries = { + { "new_data", "" }, + { "patch_data", patch_content }, + { "transfer_list", android::base::Join(transfer_list, '\n') }, + }; +} + +TEST_F(UpdaterTest, block_image_update_patch_data) { + // Both source and target images have 10 blocks. + std::string source = + std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); + std::string target = + std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); + ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); + + PackageEntries entries; + GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), + std::string_view(target).substr(0, 4096 * 2), 2, &entries); + RunBlockImageUpdate(false, entries, image_file_, "t"); + + // The update_file should be patched correctly. + std::string updated; + ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated)); + ASSERT_EQ(target, updated); +} + +TEST_F(UpdaterTest, block_image_update_patch_overrun) { + // Both source and target images have 10 blocks. + std::string source = + std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); + std::string target = + std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); + ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); + + // Provide one less block to trigger the overrun path. + PackageEntries entries; + GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), + std::string_view(target).substr(0, 4096 * 2), 1, &entries); + + // The update should fail due to overrun. + RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure); +} + +TEST_F(UpdaterTest, block_image_update_patch_underrun) { + // Both source and target images have 10 blocks. + std::string source = + std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); + std::string target = + std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); + ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); + + // Provide one more block to trigger the overrun path. + PackageEntries entries; + GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), + std::string_view(target).substr(0, 4096 * 2), 3, &entries); + + // The update should fail due to underrun. + RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure); +} + +TEST_F(UpdaterTest, block_image_update_fail) { + std::string src_content(4096 * 2, 'e'); + std::string src_hash = GetSha1(src_content); + // Stash and free some blocks, then fail the update intentionally. + std::vector transfer_list{ + // clang-format off + "4", + "2", + "0", + "2", + "stash " + src_hash + " 2,0,2", + "free " + src_hash, + "abort", + // clang-format on + }; + + // Add a new data of 10 bytes to test the deadlock. + PackageEntries entries{ + { "new_data", std::string(10, 0) }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list, '\n') }, + }; + + ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_)); + + RunBlockImageUpdate(false, entries, image_file_, ""); + + // Updater generates the stash name based on the input file name. + std::string name_digest = GetSha1(image_file_); + std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest; + ASSERT_EQ(0, access(stash_base.c_str(), F_OK)); + // Expect the stashed blocks to be freed. + ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK)); + ASSERT_EQ(0, rmdir(stash_base.c_str())); +} + +TEST_F(UpdaterTest, new_data_over_write) { + std::vector transfer_list{ + // clang-format off + "4", + "1", + "0", + "0", + "new 2,0,1", + // clang-format on + }; + + // Write 4096 + 100 bytes of new data. + PackageEntries entries{ + { "new_data", std::string(4196, 0) }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list, '\n') }, + }; + + RunBlockImageUpdate(false, entries, image_file_, "t"); +} + +TEST_F(UpdaterTest, new_data_short_write) { + std::vector transfer_list{ + // clang-format off + "4", + "1", + "0", + "0", + "new 2,0,1", + // clang-format on + }; + + PackageEntries entries{ + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list, '\n') }, + }; + + // Updater should report the failure gracefully rather than stuck in deadlock. + entries["new_data"] = ""; + RunBlockImageUpdate(false, entries, image_file_, ""); + + entries["new_data"] = std::string(10, 'a'); + RunBlockImageUpdate(false, entries, image_file_, ""); + + // Expect to write 1 block of new data successfully. + entries["new_data"] = std::string(4096, 'a'); + RunBlockImageUpdate(false, entries, image_file_, "t"); +} + +TEST_F(UpdaterTest, brotli_new_data) { + auto generator = []() { return rand() % 128; }; + // Generate 100 blocks of random data. + std::string brotli_new_data; + brotli_new_data.reserve(4096 * 100); + generate_n(back_inserter(brotli_new_data), 4096 * 100, generator); + + size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size()); + std::string encoded_data(encoded_size, 0); + ASSERT_TRUE(BrotliEncoderCompress( + BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(), + reinterpret_cast(brotli_new_data.data()), &encoded_size, + reinterpret_cast(const_cast(encoded_data.data())))); + encoded_data.resize(encoded_size); + + // Write a few small chunks of new data, then a large chunk, and finally a few small chunks. + // This helps us to catch potential short writes. + std::vector transfer_list = { + "4", + "100", + "0", + "0", + "new 2,0,1", + "new 2,1,2", + "new 4,2,50,50,97", + "new 2,97,98", + "new 2,98,99", + "new 2,99,100", + }; + + PackageEntries entries{ + { "new_data.br", std::move(encoded_data) }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list, '\n') }, + }; + + RunBlockImageUpdate(false, entries, image_file_, "t"); + + std::string updated_content; + ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content)); + ASSERT_EQ(brotli_new_data, updated_content); +} + +TEST_F(UpdaterTest, last_command_update) { + std::string block1(4096, '1'); + std::string block2(4096, '2'); + std::string block3(4096, '3'); + std::string block1_hash = GetSha1(block1); + std::string block2_hash = GetSha1(block2); + std::string block3_hash = GetSha1(block3); + + // Compose the transfer list to fail the first update. + std::vector transfer_list_fail{ + // clang-format off + "4", + "2", + "0", + "2", + "stash " + block1_hash + " 2,0,1", + "move " + block1_hash + " 2,1,2 1 2,0,1", + "stash " + block3_hash + " 2,2,3", + "abort", + // clang-format on + }; + + // Mimic a resumed update with the same transfer commands. + std::vector transfer_list_continue{ + // clang-format off + "4", + "2", + "0", + "2", + "stash " + block1_hash + " 2,0,1", + "move " + block1_hash + " 2,1,2 1 2,0,1", + "stash " + block3_hash + " 2,2,3", + "move " + block1_hash + " 2,2,3 1 2,0,1", + // clang-format on + }; + + ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); + + PackageEntries entries{ + { "new_data", "" }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list_fail, '\n') }, + }; + + // "2\nstash " + block3_hash + " 2,2,3" + std::string last_command_content = + "2\n" + transfer_list_fail[TransferList::kTransferListHeaderLines + 2]; + + RunBlockImageUpdate(false, entries, image_file_, ""); + + // Expect last_command to contain the last stash command. + std::string last_command_actual; + ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); + EXPECT_EQ(last_command_content, last_command_actual); + + std::string updated_contents; + ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents)); + ASSERT_EQ(block1 + block1 + block3, updated_contents); + + // "Resume" the update. Expect the first 'move' to be skipped but the second 'move' to be + // executed. Note that we intentionally reset the image file. + entries["transfer_list"] = android::base::Join(transfer_list_continue, '\n'); + ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); + RunBlockImageUpdate(false, entries, image_file_, "t"); + + ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents)); + ASSERT_EQ(block1 + block2 + block1, updated_contents); +} + +TEST_F(UpdaterTest, last_command_update_unresumable) { + std::string block1(4096, '1'); + std::string block2(4096, '2'); + std::string block1_hash = GetSha1(block1); + std::string block2_hash = GetSha1(block2); + + // Construct an unresumable update with source blocks mismatch. + std::vector transfer_list_unresumable{ + // clang-format off + "4", + "2", + "0", + "2", + "stash " + block1_hash + " 2,0,1", + "move " + block2_hash + " 2,1,2 1 2,0,1", + // clang-format on + }; + + PackageEntries entries{ + { "new_data", "" }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list_unresumable, '\n') }, + }; + + ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1, image_file_)); + + std::string last_command_content = + "0\n" + transfer_list_unresumable[TransferList::kTransferListHeaderLines]; + ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_)); + + RunBlockImageUpdate(false, entries, image_file_, ""); + + // The last_command_file will be deleted if the update encounters an unresumable failure later. + ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); +} + +TEST_F(UpdaterTest, last_command_verify) { + std::string block1(4096, '1'); + std::string block2(4096, '2'); + std::string block3(4096, '3'); + std::string block1_hash = GetSha1(block1); + std::string block2_hash = GetSha1(block2); + std::string block3_hash = GetSha1(block3); + + std::vector transfer_list_verify{ + // clang-format off + "4", + "2", + "0", + "2", + "stash " + block1_hash + " 2,0,1", + "move " + block1_hash + " 2,0,1 1 2,0,1", + "move " + block1_hash + " 2,1,2 1 2,0,1", + "stash " + block3_hash + " 2,2,3", + // clang-format on + }; + + PackageEntries entries{ + { "new_data", "" }, + { "patch_data", "" }, + { "transfer_list", android::base::Join(transfer_list_verify, '\n') }, + }; + + ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1 + block3, image_file_)); + + // Last command: "move " + block1_hash + " 2,1,2 1 2,0,1" + std::string last_command_content = + "2\n" + transfer_list_verify[TransferList::kTransferListHeaderLines + 2]; + + // First run: expect the verification to succeed and the last_command_file is intact. + ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_)); + + RunBlockImageUpdate(true, entries, image_file_, "t"); + + std::string last_command_actual; + ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); + EXPECT_EQ(last_command_content, last_command_actual); + + // Second run with a mismatching block image: expect the verification to succeed but + // last_command_file to be deleted; because the target blocks in the last command don't have the + // expected contents for the second move command. + ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); + RunBlockImageUpdate(true, entries, image_file_, "t"); + ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); +} + +class ResumableUpdaterTest : public UpdaterTestBase, public testing::TestWithParam { + protected: + void SetUp() override { + UpdaterTestBase::SetUp(); + // Enable a special command "abort" to simulate interruption. + Command::abort_allowed_ = true; + index_ = GetParam(); + } + + void TearDown() override { + UpdaterTestBase::TearDown(); + } + + size_t index_; +}; + +static std::string g_source_image; +static std::string g_target_image; +static PackageEntries g_entries; + +static std::vector GenerateTransferList() { + std::string a(4096, 'a'); + std::string b(4096, 'b'); + std::string c(4096, 'c'); + std::string d(4096, 'd'); + std::string e(4096, 'e'); + std::string f(4096, 'f'); + std::string g(4096, 'g'); + std::string h(4096, 'h'); + std::string i(4096, 'i'); + std::string zero(4096, '\0'); + + std::string a_hash = GetSha1(a); + std::string b_hash = GetSha1(b); + std::string c_hash = GetSha1(c); + std::string e_hash = GetSha1(e); + + auto loc = [](const std::string& range_text) { + std::vector pieces = android::base::Split(range_text, "-"); + size_t left; + size_t right; + if (pieces.size() == 1) { + CHECK(android::base::ParseUint(pieces[0], &left)); + right = left + 1; + } else { + CHECK_EQ(2u, pieces.size()); + CHECK(android::base::ParseUint(pieces[0], &left)); + CHECK(android::base::ParseUint(pieces[1], &right)); + right++; + } + return android::base::StringPrintf("2,%zu,%zu", left, right); + }; + + // patch 1: "b d c" -> "g" + TemporaryFile patch_file_bdc_g; + std::string bdc = b + d + c; + std::string bdc_hash = GetSha1(bdc); + std::string g_hash = GetSha1(g); + CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast(bdc.data()), bdc.size(), + reinterpret_cast(g.data()), g.size(), + patch_file_bdc_g.path, nullptr)); + std::string patch_bdc_g; + CHECK(android::base::ReadFileToString(patch_file_bdc_g.path, &patch_bdc_g)); + + // patch 2: "a b c d" -> "d c b" + TemporaryFile patch_file_abcd_dcb; + std::string abcd = a + b + c + d; + std::string abcd_hash = GetSha1(abcd); + std::string dcb = d + c + b; + std::string dcb_hash = GetSha1(dcb); + CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast(abcd.data()), abcd.size(), + reinterpret_cast(dcb.data()), dcb.size(), + patch_file_abcd_dcb.path, nullptr)); + std::string patch_abcd_dcb; + CHECK(android::base::ReadFileToString(patch_file_abcd_dcb.path, &patch_abcd_dcb)); + + std::vector transfer_list{ + "4", + "10", // total blocks written + "2", // maximum stash entries + "2", // maximum number of stashed blocks + + // a b c d e a b c d e + "stash " + b_hash + " " + loc("1"), + // a b c d e a b c d e [b(1)] + "stash " + c_hash + " " + loc("2"), + // a b c d e a b c d e [b(1)][c(2)] + "new " + loc("1-2"), + // a i h d e a b c d e [b(1)][c(2)] + "zero " + loc("0"), + // 0 i h d e a b c d e [b(1)][c(2)] + + // bsdiff "b d c" (from stash, 3, stash) to get g(3) + android::base::StringPrintf( + "bsdiff 0 %zu %s %s %s 3 %s %s %s:%s %s:%s", + patch_bdc_g.size(), // patch start (0), patch length + bdc_hash.c_str(), // source hash + g_hash.c_str(), // target hash + loc("3").c_str(), // target range + loc("3").c_str(), loc("1").c_str(), // load "d" from block 3, into buffer at offset 1 + b_hash.c_str(), loc("0").c_str(), // load "b" from stash, into buffer at offset 0 + c_hash.c_str(), loc("2").c_str()), // load "c" from stash, into buffer at offset 2 + + // 0 i h g e a b c d e [b(1)][c(2)] + "free " + b_hash, + // 0 i h g e a b c d e [c(2)] + "free " + a_hash, + // 0 i h g e a b c d e + "stash " + a_hash + " " + loc("5"), + // 0 i h g e a b c d e [a(5)] + "move " + e_hash + " " + loc("5") + " 1 " + loc("4"), + // 0 i h g e e b c d e [a(5)] + + // bsdiff "a b c d" (from stash, 6-8) to "d c b" (6-8) + android::base::StringPrintf( // + "bsdiff %zu %zu %s %s %s 4 %s %s %s:%s", + patch_bdc_g.size(), // patch start + patch_bdc_g.size() + patch_abcd_dcb.size(), // patch length + abcd_hash.c_str(), // source hash + dcb_hash.c_str(), // target hash + loc("6-8").c_str(), // target range + loc("6-8").c_str(), // load "b c d" from blocks 6-8 + loc("1-3").c_str(), // into buffer at offset 1-3 + a_hash.c_str(), // load "a" from stash + loc("0").c_str()), // into buffer at offset 0 + + // 0 i h g e e d c b e [a(5)] + "new " + loc("4"), + // 0 i h g f e d c b e [a(5)] + "move " + a_hash + " " + loc("9") + " 1 - " + a_hash + ":" + loc("0"), + // 0 i h g f e d c b a [a(5)] + "free " + a_hash, + // 0 i h g f e d c b a + }; + + std::string new_data = i + h + f; + std::string patch_data = patch_bdc_g + patch_abcd_dcb; + + g_entries = { + { "new_data", new_data }, + { "patch_data", patch_data }, + }; + g_source_image = a + b + c + d + e + a + b + c + d + e; + g_target_image = zero + i + h + g + f + e + d + c + b + a; + + return transfer_list; +} + +static const std::vector g_transfer_list = GenerateTransferList(); + +INSTANTIATE_TEST_CASE_P(InterruptAfterEachCommand, ResumableUpdaterTest, + ::testing::Range(static_cast(0), + g_transfer_list.size() - + TransferList::kTransferListHeaderLines)); + +TEST_P(ResumableUpdaterTest, InterruptVerifyResume) { + ASSERT_TRUE(android::base::WriteStringToFile(g_source_image, image_file_)); + + LOG(INFO) << "Interrupting at line " << index_ << " (" + << g_transfer_list[TransferList::kTransferListHeaderLines + index_] << ")"; + + std::vector transfer_list_copy{ g_transfer_list }; + transfer_list_copy[TransferList::kTransferListHeaderLines + index_] = "abort"; + + g_entries["transfer_list"] = android::base::Join(transfer_list_copy, '\n'); + + // Run update that's expected to fail. + RunBlockImageUpdate(false, g_entries, image_file_, ""); + + std::string last_command_expected; + + // Assert the last_command_file. + if (index_ == 0) { + ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); + } else { + last_command_expected = std::to_string(index_ - 1) + "\n" + + g_transfer_list[TransferList::kTransferListHeaderLines + index_ - 1]; + std::string last_command_actual; + ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); + ASSERT_EQ(last_command_expected, last_command_actual); + } + + g_entries["transfer_list"] = android::base::Join(g_transfer_list, '\n'); + + // Resume the interrupted update, by doing verification first. + RunBlockImageUpdate(true, g_entries, image_file_, "t"); + + // last_command_file should remain intact. + if (index_ == 0) { + ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); + } else { + std::string last_command_actual; + ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); + ASSERT_EQ(last_command_expected, last_command_actual); + } + + // Resume the update. + RunBlockImageUpdate(false, g_entries, image_file_, "t"); + + // last_command_file should be gone after successful update. + ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); + + std::string updated_image_actual; + ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_image_actual)); + ASSERT_EQ(g_target_image, updated_image_actual); +} diff --git a/tests/unit/verifier_test.cpp b/tests/unit/verifier_test.cpp new file mode 100644 index 0000000000..ded23c52fd --- /dev/null +++ b/tests/unit/verifier_test.cpp @@ -0,0 +1,364 @@ +/* + * 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 agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/test_constants.h" +#include "install/package.h" +#include "install/verifier.h" +#include "otautil/sysutil.h" + +using namespace std::string_literals; + +static void LoadKeyFromFile(const std::string& file_name, Certificate* cert) { + std::string testkey_string; + ASSERT_TRUE(android::base::ReadFileToString(file_name, &testkey_string)); + ASSERT_TRUE(LoadCertificateFromBuffer( + std::vector(testkey_string.begin(), testkey_string.end()), cert)); +} + +static void VerifyFile(const std::string& content, const std::vector& keys, + int expected) { + auto package = + Package::CreateMemoryPackage(std::vector(content.begin(), content.end()), nullptr); + ASSERT_NE(nullptr, package); + + ASSERT_EQ(expected, verify_file(package.get(), keys)); +} + +static void VerifyPackageWithCertificates(const std::string& name, + const std::vector& certs) { + std::string path = from_testdata_base(name); + auto package = Package::CreateMemoryPackage(path, nullptr); + ASSERT_NE(nullptr, package); + + ASSERT_EQ(VERIFY_SUCCESS, verify_file(package.get(), certs)); +} + +static void VerifyPackageWithSingleCertificate(const std::string& name, Certificate&& cert) { + std::vector certs; + certs.emplace_back(std::move(cert)); + VerifyPackageWithCertificates(name, certs); +} + +static void BuildCertificateArchive(const std::vector& file_names, int fd) { + FILE* zip_file_ptr = fdopen(fd, "wb"); + ZipWriter zip_writer(zip_file_ptr); + + for (const auto& name : file_names) { + std::string content; + ASSERT_TRUE(android::base::ReadFileToString(name, &content)); + + // Makes sure the zip entry name has the correct suffix. + std::string entry_name = name; + if (!android::base::EndsWith(entry_name, "x509.pem")) { + entry_name += "x509.pem"; + } + ASSERT_EQ(0, zip_writer.StartEntry(entry_name.c_str(), ZipWriter::kCompress)); + ASSERT_EQ(0, zip_writer.WriteBytes(content.data(), content.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + } + + ASSERT_EQ(0, zip_writer.Finish()); + ASSERT_EQ(0, fclose(zip_file_ptr)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_failure) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + std::string testkey_string; + ASSERT_TRUE( + android::base::ReadFileToString(from_testdata_base("testkey_v1.txt"), &testkey_string)); + ASSERT_FALSE(LoadCertificateFromBuffer( + std::vector(testkey_string.begin(), testkey_string.end()), &cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha1_exponent3) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v1.x509.pem"), &cert); + + ASSERT_EQ(SHA_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithSingleCertificate("otasigned_v1.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha1_exponent65537) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v2.x509.pem"), &cert); + + ASSERT_EQ(SHA_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithSingleCertificate("otasigned_v2.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha256_exponent3) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &cert); + + ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithSingleCertificate("otasigned_v3.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha256_exponent65537) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v4.x509.pem"), &cert); + + ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithSingleCertificate("otasigned_v4.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha256_ec256bits) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v5.x509.pem"), &cert); + + ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_EC, cert.key_type); + ASSERT_EQ(nullptr, cert.rsa); + + VerifyPackageWithSingleCertificate("otasigned_v5.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha256_rsa4096_bits) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_4096bits.x509.pem"), &cert); + + ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithSingleCertificate("otasigned_4096bits.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_check_rsa_keys) { + std::unique_ptr rsa(RSA_new()); + std::unique_ptr exponent(BN_new(), BN_free); + BN_set_word(exponent.get(), 3); + RSA_generate_key_ex(rsa.get(), 2048, exponent.get(), nullptr); + ASSERT_TRUE(CheckRSAKey(rsa)); + + // Exponent is expected to be 3 or 65537 + BN_set_word(exponent.get(), 17); + RSA_generate_key_ex(rsa.get(), 2048, exponent.get(), nullptr); + ASSERT_FALSE(CheckRSAKey(rsa)); + + // Modulus is expected to be 2048. + BN_set_word(exponent.get(), 3); + RSA_generate_key_ex(rsa.get(), 1024, exponent.get(), nullptr); + ASSERT_FALSE(CheckRSAKey(rsa)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_check_ec_keys) { + std::unique_ptr ec(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + ASSERT_EQ(1, EC_KEY_generate_key(ec.get())); + ASSERT_TRUE(CheckECKey(ec)); + + // Expects 256-bit EC key with curve NIST P-256 + ec.reset(EC_KEY_new_by_curve_name(NID_secp224r1)); + ASSERT_EQ(1, EC_KEY_generate_key(ec.get())); + ASSERT_FALSE(CheckECKey(ec)); +} + +TEST(VerifierTest, LoadKeysFromZipfile_empty_archive) { + TemporaryFile otacerts; + BuildCertificateArchive({}, otacerts.release()); + std::vector certs = LoadKeysFromZipfile(otacerts.path); + ASSERT_TRUE(certs.empty()); +} + +TEST(VerifierTest, LoadKeysFromZipfile_single_key) { + TemporaryFile otacerts; + BuildCertificateArchive({ from_testdata_base("testkey_v1.x509.pem") }, otacerts.release()); + std::vector certs = LoadKeysFromZipfile(otacerts.path); + ASSERT_EQ(1, certs.size()); + + VerifyPackageWithCertificates("otasigned_v1.zip", certs); +} + +TEST(VerifierTest, LoadKeysFromZipfile_corrupted_key) { + TemporaryFile corrupted_key; + std::string content; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v1.x509.pem"), &content)); + content = "random-contents" + content; + ASSERT_TRUE(android::base::WriteStringToFd(content, corrupted_key.release())); + + TemporaryFile otacerts; + BuildCertificateArchive({ from_testdata_base("testkey_v2.x509.pem"), corrupted_key.path }, + otacerts.release()); + std::vector certs = LoadKeysFromZipfile(otacerts.path); + ASSERT_EQ(0, certs.size()); +} + +TEST(VerifierTest, LoadKeysFromZipfile_multiple_key) { + TemporaryFile otacerts; + BuildCertificateArchive( + { + from_testdata_base("testkey_v3.x509.pem"), + from_testdata_base("testkey_v4.x509.pem"), + from_testdata_base("testkey_v5.x509.pem"), + + }, + otacerts.release()); + std::vector certs = LoadKeysFromZipfile(otacerts.path); + ASSERT_EQ(3, certs.size()); + + VerifyPackageWithCertificates("otasigned_v3.zip", certs); + VerifyPackageWithCertificates("otasigned_v4.zip", certs); + VerifyPackageWithCertificates("otasigned_v5.zip", certs); +} + +class VerifierTest : public testing::TestWithParam> { + protected: + void SetUp() override { + std::vector args = GetParam(); + std::string path = from_testdata_base(args[0]); + memory_package_ = Package::CreateMemoryPackage(path, nullptr); + ASSERT_NE(nullptr, memory_package_); + file_package_ = Package::CreateFilePackage(path, nullptr); + ASSERT_NE(nullptr, file_package_); + + for (auto it = ++args.cbegin(); it != args.cend(); ++it) { + std::string public_key_file = from_testdata_base("testkey_" + *it + ".x509.pem"); + certs_.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(public_key_file, &certs_.back()); + } + } + + std::unique_ptr memory_package_; + std::unique_ptr file_package_; + std::vector certs_; +}; + +class VerifierSuccessTest : public VerifierTest { +}; + +class VerifierFailureTest : public VerifierTest { +}; + +TEST(VerifierTest, BadPackage_AlteredFooter) { + std::vector certs; + certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back()); + + std::string package; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package)); + ASSERT_EQ(std::string("\xc0\x06\xff\xff\xd2\x06", 6), package.substr(package.size() - 6, 6)); + + // Alter the footer. + package[package.size() - 5] = '\x05'; + VerifyFile(package, certs, VERIFY_FAILURE); +} + +TEST(VerifierTest, BadPackage_AlteredContent) { + std::vector certs; + certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back()); + + std::string package; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package)); + ASSERT_GT(package.size(), static_cast(100)); + + // Alter the content. + std::string altered1(package); + altered1[50] += 1; + VerifyFile(altered1, certs, VERIFY_FAILURE); + + std::string altered2(package); + altered2[10] += 1; + VerifyFile(altered2, certs, VERIFY_FAILURE); +} + +TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) { + std::vector certs; + certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back()); + + // Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369). + std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s; + VerifyFile(package, certs, VERIFY_FAILURE); +} + +TEST_P(VerifierSuccessTest, VerifySucceed) { + ASSERT_EQ(VERIFY_SUCCESS, verify_file(memory_package_.get(), certs_)); + ASSERT_EQ(VERIFY_SUCCESS, verify_file(file_package_.get(), certs_)); +} + +TEST_P(VerifierFailureTest, VerifyFailure) { + ASSERT_EQ(VERIFY_FAILURE, verify_file(memory_package_.get(), certs_)); + ASSERT_EQ(VERIFY_FAILURE, verify_file(file_package_.get(), certs_)); +} + +INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest, + ::testing::Values( + std::vector({"otasigned_v1.zip", "v1"}), + std::vector({"otasigned_v2.zip", "v2"}), + std::vector({"otasigned_v3.zip", "v3"}), + std::vector({"otasigned_v4.zip", "v4"}), + std::vector({"otasigned_v5.zip", "v5"}))); + +INSTANTIATE_TEST_CASE_P(MultiKeySuccess, VerifierSuccessTest, + ::testing::Values( + std::vector({"otasigned_v1.zip", "v1", "v2"}), + std::vector({"otasigned_v2.zip", "v5", "v2"}), + std::vector({"otasigned_v3.zip", "v5", "v1", "v3"}), + std::vector({"otasigned_v4.zip", "v5", "v1", "v4"}), + std::vector({"otasigned_v5.zip", "v4", "v1", "v5"}))); + +INSTANTIATE_TEST_CASE_P(WrongKey, VerifierFailureTest, + ::testing::Values( + std::vector({"otasigned_v1.zip", "v2"}), + std::vector({"otasigned_v2.zip", "v1"}), + std::vector({"otasigned_v3.zip", "v5"}), + std::vector({"otasigned_v4.zip", "v5"}), + std::vector({"otasigned_v5.zip", "v3"}))); + +INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest, + ::testing::Values( + std::vector({"otasigned_v1.zip", "v3"}), + std::vector({"otasigned_v2.zip", "v4"}), + std::vector({"otasigned_v3.zip", "v1"}), + std::vector({"otasigned_v4.zip", "v2"}))); + +INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest, + ::testing::Values( + std::vector({"random.zip", "v1"}), + std::vector({"fake-eocd.zip", "v1"}))); diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp index 827668521e..0753d64e18 100644 --- a/tests/unit/zip_test.cpp +++ b/tests/unit/zip_test.cpp @@ -21,12 +21,11 @@ #include #include -#include #include -#include #include #include "common/test_constants.h" +#include "otautil/sysutil.h" TEST(ZipTest, OpenFromMemory) { std::string zip_path = from_testdata_base("ziptest_dummy-update.zip"); @@ -38,10 +37,9 @@ TEST(ZipTest, OpenFromMemory) { ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle)); static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary"; - ZipString binary_path(BINARY_PATH); ZipEntry binary_entry; // Make sure the package opens correctly and its entry can be read. - ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry)); + ASSERT_EQ(0, FindEntry(handle, BINARY_PATH, &binary_entry)); TemporaryFile tmp_binary; ASSERT_NE(-1, tmp_binary.fd); diff --git a/tools/Android.mk b/tools/Android.mk deleted file mode 100644 index 65711611cc..0000000000 --- a/tools/Android.mk +++ /dev/null @@ -1 +0,0 @@ -include $(all-subdir-makefiles) diff --git a/tools/dumpkey/DumpPublicKey.java b/tools/dumpkey/DumpPublicKey.java deleted file mode 100644 index 3eb139842f..0000000000 --- a/tools/dumpkey/DumpPublicKey.java +++ /dev/null @@ -1,270 +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. - */ - -package com.android.dumpkey; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -import java.io.FileInputStream; -import java.math.BigInteger; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.KeyStore; -import java.security.Key; -import java.security.PublicKey; -import java.security.Security; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.ECPoint; - -/** - * Command line tool to extract RSA public keys from X.509 certificates - * and output source code with data initializers for the keys. - * @hide - */ -class DumpPublicKey { - /** - * @param key to perform sanity checks on - * @return version number of key. Supported versions are: - * 1: 2048-bit RSA key with e=3 and SHA-1 hash - * 2: 2048-bit RSA key with e=65537 and SHA-1 hash - * 3: 2048-bit RSA key with e=3 and SHA-256 hash - * 4: 2048-bit RSA key with e=65537 and SHA-256 hash - * @throws Exception if the key has the wrong size or public exponent - */ - static int checkRSA(RSAPublicKey key, boolean useSHA256) throws Exception { - BigInteger pubexp = key.getPublicExponent(); - BigInteger modulus = key.getModulus(); - int version; - - if (pubexp.equals(BigInteger.valueOf(3))) { - version = useSHA256 ? 3 : 1; - } else if (pubexp.equals(BigInteger.valueOf(65537))) { - version = useSHA256 ? 4 : 2; - } else { - throw new Exception("Public exponent should be 3 or 65537 but is " + - pubexp.toString(10) + "."); - } - - if (modulus.bitLength() != 2048) { - throw new Exception("Modulus should be 2048 bits long but is " + - modulus.bitLength() + " bits."); - } - - return version; - } - - /** - * @param key to perform sanity checks on - * @return version number of key. Supported versions are: - * 5: 256-bit EC key with curve NIST P-256 - * @throws Exception if the key has the wrong size or public exponent - */ - static int checkEC(ECPublicKey key) throws Exception { - if (key.getParams().getCurve().getField().getFieldSize() != 256) { - throw new Exception("Curve must be NIST P-256"); - } - - return 5; - } - - /** - * Perform sanity check on public key. - */ - static int check(PublicKey key, boolean useSHA256) throws Exception { - if (key instanceof RSAPublicKey) { - return checkRSA((RSAPublicKey) key, useSHA256); - } else if (key instanceof ECPublicKey) { - if (!useSHA256) { - throw new Exception("Must use SHA-256 with EC keys!"); - } - return checkEC((ECPublicKey) key); - } else { - throw new Exception("Unsupported key class: " + key.getClass().getName()); - } - } - - /** - * @param key to output - * @return a String representing this public key. If the key is a - * version 1 key, the string will be a C initializer; this is - * not true for newer key versions. - */ - static String printRSA(RSAPublicKey key, boolean useSHA256) throws Exception { - int version = check(key, useSHA256); - - BigInteger N = key.getModulus(); - - StringBuilder result = new StringBuilder(); - - int nwords = N.bitLength() / 32; // # of 32 bit integers in modulus - - if (version > 1) { - result.append("v"); - result.append(Integer.toString(version)); - result.append(" "); - } - - result.append("{"); - result.append(nwords); - - BigInteger B = BigInteger.valueOf(0x100000000L); // 2^32 - BigInteger N0inv = B.subtract(N.modInverse(B)); // -1 / N[0] mod 2^32 - - result.append(",0x"); - result.append(N0inv.toString(16)); - - BigInteger R = BigInteger.valueOf(2).pow(N.bitLength()); - BigInteger RR = R.multiply(R).mod(N); // 2^4096 mod N - - // Write out modulus as little endian array of integers. - result.append(",{"); - for (int i = 0; i < nwords; ++i) { - long n = N.mod(B).longValue(); - result.append(n); - - if (i != nwords - 1) { - result.append(","); - } - - N = N.divide(B); - } - result.append("}"); - - // Write R^2 as little endian array of integers. - result.append(",{"); - for (int i = 0; i < nwords; ++i) { - long rr = RR.mod(B).longValue(); - result.append(rr); - - if (i != nwords - 1) { - result.append(","); - } - - RR = RR.divide(B); - } - result.append("}"); - - result.append("}"); - return result.toString(); - } - - /** - * @param key to output - * @return a String representing this public key. If the key is a - * version 1 key, the string will be a C initializer; this is - * not true for newer key versions. - */ - static String printEC(ECPublicKey key) throws Exception { - int version = checkEC(key); - - StringBuilder result = new StringBuilder(); - - result.append("v"); - result.append(Integer.toString(version)); - result.append(" "); - - BigInteger X = key.getW().getAffineX(); - BigInteger Y = key.getW().getAffineY(); - int nbytes = key.getParams().getCurve().getField().getFieldSize() / 8; // # of 32 bit integers in X coordinate - - result.append("{"); - result.append(nbytes); - - BigInteger B = BigInteger.valueOf(0x100L); // 2^8 - - // Write out Y coordinate as array of characters. - result.append(",{"); - for (int i = 0; i < nbytes; ++i) { - long n = X.mod(B).longValue(); - result.append(n); - - if (i != nbytes - 1) { - result.append(","); - } - - X = X.divide(B); - } - result.append("}"); - - // Write out Y coordinate as array of characters. - result.append(",{"); - for (int i = 0; i < nbytes; ++i) { - long n = Y.mod(B).longValue(); - result.append(n); - - if (i != nbytes - 1) { - result.append(","); - } - - Y = Y.divide(B); - } - result.append("}"); - - result.append("}"); - return result.toString(); - } - - static String print(PublicKey key, boolean useSHA256) throws Exception { - if (key instanceof RSAPublicKey) { - return printRSA((RSAPublicKey) key, useSHA256); - } else if (key instanceof ECPublicKey) { - return printEC((ECPublicKey) key); - } else { - throw new Exception("Unsupported key class: " + key.getClass().getName()); - } - } - - public static void main(String[] args) { - if (args.length < 1) { - System.err.println("Usage: DumpPublicKey certfile ... > source.c"); - System.exit(1); - } - Security.addProvider(new BouncyCastleProvider()); - try { - for (int i = 0; i < args.length; i++) { - FileInputStream input = new FileInputStream(args[i]); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509Certificate cert = (X509Certificate) cf.generateCertificate(input); - - boolean useSHA256 = false; - String sigAlg = cert.getSigAlgName(); - if ("SHA1withRSA".equals(sigAlg) || "MD5withRSA".equals(sigAlg)) { - // SignApk has historically accepted "MD5withRSA" - // certificates, but treated them as "SHA1withRSA" - // anyway. Continue to do so for backwards - // compatibility. - useSHA256 = false; - } else if ("SHA256withRSA".equals(sigAlg) || "SHA256withECDSA".equals(sigAlg)) { - useSHA256 = true; - } else { - System.err.println(args[i] + ": unsupported signature algorithm \"" + - sigAlg + "\""); - System.exit(1); - } - - PublicKey key = cert.getPublicKey(); - check(key, useSHA256); - System.out.print(print(key, useSHA256)); - System.out.println(i < args.length - 1 ? "," : ""); - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - System.exit(0); - } -} diff --git a/tools/dumpkey/DumpPublicKey.mf b/tools/dumpkey/DumpPublicKey.mf deleted file mode 100644 index 7bb3bc88de..0000000000 --- a/tools/dumpkey/DumpPublicKey.mf +++ /dev/null @@ -1 +0,0 @@ -Main-Class: com.android.dumpkey.DumpPublicKey diff --git a/tools/image_generator/Android.bp b/tools/image_generator/Android.bp new file mode 100644 index 0000000000..83000407cf --- /dev/null +++ b/tools/image_generator/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +java_library_host { + name: "RecoveryImageGenerator", + + manifest: "ImageGenerator.mf", + + static_libs: [ + "commons-cli-1.2", + "icu4j", + ], + + srcs: [ + "ImageGenerator.java", + ], +} diff --git a/tools/image_generator/ImageGenerator.java b/tools/image_generator/ImageGenerator.java new file mode 100644 index 0000000000..1da43e5c67 --- /dev/null +++ b/tools/image_generator/ImageGenerator.java @@ -0,0 +1,757 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.recovery.tools; + +import com.ibm.icu.text.BreakIterator; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.awt.Color; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.font.TextAttribute; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.text.AttributedString; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.imageio.ImageIO; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** Command line tool to generate the localized image for recovery mode. */ +public class ImageGenerator { + // Initial height of the image to draw. + private static final int INITIAL_HEIGHT = 20000; + + private static final float DEFAULT_FONT_SIZE = 40; + + private static final Logger LOGGER = Logger.getLogger(ImageGenerator.class.getName()); + + // This is the canvas we used to draw texts. + private BufferedImage mBufferedImage; + + // The width in pixels of our image. The value will be adjusted once when we calculate the + // maximum width to fit the wrapped text strings. + private int mImageWidth; + + // The current height in pixels of our image. We will adjust the value when drawing more texts. + private int mImageHeight; + + // The current vertical offset in pixels to draw the top edge of new text strings. + private int mVerticalOffset; + + // The font size to draw the texts. + private final float mFontSize; + + // The name description of the text to localize. It's used to find the translated strings in the + // resource file. + private final String mTextName; + + // The directory that contains all the needed font files (e.g. ttf, otf, ttc files). + private final String mFontDirPath; + + // Align the text in the center of the image. + private final boolean mCenterAlignment; + + // Some localized font cannot draw the word "Android" and some PUNCTUATIONS; we need to fall + // back to use our default latin font instead. + private static final char[] PUNCTUATIONS = {',', ';', '.', '!', '?'}; + + private static final String ANDROID_STRING = "Android"; + + // The width of the word "Android" when drawing with the default font. + private int mAndroidStringWidth; + + // The default Font to draw latin characters. It's loaded from DEFAULT_FONT_NAME. + private Font mDefaultFont; + // Cache of the loaded fonts for all languages. + private Map mLoadedFontMap; + + // An explicit map from language to the font name to use. + // The map is extracted from frameworks/base/data/fonts/fonts.xml. + // And the language-subtag-registry is found in: + // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + private static final String DEFAULT_FONT_NAME = "Roboto-Regular"; + private static final Map LANGUAGE_TO_FONT_MAP = + new TreeMap() { + { + put("am", "NotoSansEthiopic-Regular"); + put("ar", "NotoNaskhArabicUI-Regular"); + put("as", "NotoSansBengaliUI-Regular"); + put("bn", "NotoSansBengaliUI-Regular"); + put("fa", "NotoNaskhArabicUI-Regular"); + put("gu", "NotoSansGujaratiUI-Regular"); + put("hi", "NotoSansDevanagariUI-Regular"); + put("hy", "NotoSansArmenian-Regular"); + put("iw", "NotoSansHebrew-Regular"); + put("ja", "NotoSansCJK-Regular"); + put("ka", "NotoSansGeorgian-VF"); + put("ko", "NotoSansCJK-Regular"); + put("km", "NotoSansKhmerUI-Regular"); + put("kn", "NotoSansKannadaUI-Regular"); + put("lo", "NotoSansLaoUI-Regular"); + put("ml", "NotoSansMalayalamUI-Regular"); + put("mr", "NotoSansDevanagariUI-Regular"); + put("my", "NotoSansMyanmarUI-Regular"); + put("ne", "NotoSansDevanagariUI-Regular"); + put("or", "NotoSansOriya-Regular"); + put("pa", "NotoSansGurmukhiUI-Regular"); + put("si", "NotoSansSinhala-Regular"); + put("ta", "NotoSansTamilUI-Regular"); + put("te", "NotoSansTeluguUI-Regular"); + put("th", "NotoSansThaiUI-Regular"); + put("ur", "NotoNaskhArabicUI-Regular"); + put("zh", "NotoSansCJK-Regular"); + } + }; + + // Languages that write from right to left. + private static final Set RTL_LANGUAGE = + new HashSet() { + { + add("ar"); // Arabic + add("fa"); // Persian + add("he"); // Hebrew + add("iw"); // Hebrew + add("ur"); // Urdu + } + }; + + /** Exception to indicate the failure to find the translated text strings. */ + public static class LocalizedStringNotFoundException extends Exception { + public LocalizedStringNotFoundException(String message) { + super(message); + } + + public LocalizedStringNotFoundException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * This class maintains the content of wrapped text, the attributes to draw these text, and + * the width of each wrapped lines. + */ + private class WrappedTextInfo { + /** LineInfo holds the AttributedString and width of each wrapped line. */ + private class LineInfo { + public AttributedString mLineContent; + public int mLineWidth; + + LineInfo(AttributedString text, int width) { + mLineContent = text; + mLineWidth = width; + } + } + + // Maintains the content of each line, as well as the width needed to draw these lines for + // a given language. + public List mWrappedLines; + + WrappedTextInfo() { + mWrappedLines = new ArrayList<>(); + } + + /** + * Checks if the given text has words "Android" and some PUNCTUATIONS. If it does, and its + * associated textFont cannot display them correctly (e.g. for persian and hebrew); sets the + * attributes of these substrings to use our default font instead. + * + * @param text the input string to perform the check on + * @param width the pre-calculated width for the given text + * @param textFont the localized font to draw the input string + * @param fallbackFont our default font to draw latin characters + */ + public void addLine(String text, int width, Font textFont, Font fallbackFont) { + AttributedString attributedText = new AttributedString(text); + attributedText.addAttribute(TextAttribute.FONT, textFont); + attributedText.addAttribute(TextAttribute.SIZE, mFontSize); + + // Skips the check if we don't specify a fallbackFont. + if (fallbackFont != null) { + // Adds the attribute to use default font to draw the word "Android". + if (text.contains(ANDROID_STRING) + && textFont.canDisplayUpTo(ANDROID_STRING) != -1) { + int index = text.indexOf(ANDROID_STRING); + attributedText.addAttribute(TextAttribute.FONT, fallbackFont, index, + index + ANDROID_STRING.length()); + } + + // Adds the attribute to use default font to draw the PUNCTUATIONS ", . ; ! ?" + for (char punctuation : PUNCTUATIONS) { + // TODO (xunchang) handle the RTL language that has different directions for '?' + if (text.indexOf(punctuation) != -1 && !textFont.canDisplay(punctuation)) { + int index = 0; + while ((index = text.indexOf(punctuation, index)) != -1) { + attributedText.addAttribute(TextAttribute.FONT, fallbackFont, index, + index + 1); + index += 1; + } + } + } + } + + mWrappedLines.add(new LineInfo(attributedText, width)); + } + + /** Merges two WrappedTextInfo. */ + public void addLines(WrappedTextInfo other) { + mWrappedLines.addAll(other.mWrappedLines); + } + } + + /** Initailizes the fields of the image image. */ + public ImageGenerator( + int initialImageWidth, + String textName, + float fontSize, + String fontDirPath, + boolean centerAlignment) { + mImageWidth = initialImageWidth; + mImageHeight = INITIAL_HEIGHT; + mVerticalOffset = 0; + + // Initialize the canvas with the default height. + mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY); + + mTextName = textName; + mFontSize = fontSize; + mFontDirPath = fontDirPath; + mLoadedFontMap = new TreeMap<>(); + + mCenterAlignment = centerAlignment; + } + + /** + * Finds the translated text string for the given textName by parsing the resourceFile. Example + * of the xml fields: + * "Sicherheitsupdate + * wird installiert" + * + * @param resourceFile the input resource file in xml format. + * @param textName the name description of the text. + * @return the string representation of the translated text. + */ + private String getTextString(File resourceFile, String textName) + throws IOException, ParserConfigurationException, org.xml.sax.SAXException, + LocalizedStringNotFoundException { + DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = builder.newDocumentBuilder(); + + Document doc = db.parse(resourceFile); + doc.getDocumentElement().normalize(); + + NodeList nodeList = doc.getElementsByTagName("string"); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + String name = node.getAttributes().getNamedItem("name").getNodeValue(); + if (name.equals(textName)) { + return node.getTextContent(); + } + } + + throw new LocalizedStringNotFoundException( + textName + " not found in " + resourceFile.getName()); + } + + /** Constructs the locale from the name of the resource file. */ + private Locale getLocaleFromFilename(String filename) throws IOException { + // Gets the locale string by trimming the top "values-". + String localeString = filename.substring(7); + if (localeString.matches("[A-Za-z]+")) { + return Locale.forLanguageTag(localeString); + } + if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) { + // "${Language}-r${Region}". e.g. en-rGB + String[] tokens = localeString.split("-r"); + return Locale.forLanguageTag(String.join("-", tokens)); + } + if (localeString.startsWith("b+")) { + // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}" + String[] tokens = localeString.substring(2).split("\\+"); + return Locale.forLanguageTag(String.join("-", tokens)); + } + + throw new IOException("Unrecognized locale string " + localeString); + } + + /** + * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource + * directory and collect the translated text. + * + * @param resourcePath the path to the resource directory + * @param localesSet a list of supported locales; resources of other locales will be omitted. + * @return a map with the locale as key, and translated text as value + * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given + * locale + */ + public Map readLocalizedStringFromXmls(String resourcePath, + Set localesSet) throws IOException, LocalizedStringNotFoundException { + File resourceDir = new File(resourcePath); + if (!resourceDir.isDirectory()) { + throw new LocalizedStringNotFoundException(resourcePath + " is not a directory."); + } + + Map result = + // Overrides the string comparator so that sr is sorted behind sr-Latn. And thus + // recovery can find the most relevant locale when going down the list. + new TreeMap<>( + (Locale l1, Locale l2) -> { + if (l1.toLanguageTag().equals(l2.toLanguageTag())) { + return 0; + } + if (l1.getLanguage().equals(l2.toLanguageTag())) { + return -1; + } + if (l2.getLanguage().equals(l1.toLanguageTag())) { + return 1; + } + return l1.toLanguageTag().compareTo(l2.toLanguageTag()); + }); + + // Find all the localized resource subdirectories in the format of values-$LOCALE + String[] nameList = + resourceDir.list((File file, String name) -> name.startsWith("values-")); + for (String name : nameList) { + String localeString = name.substring(7); + if (localesSet != null && !localesSet.contains(localeString)) { + LOGGER.info("Skip parsing text for locale " + localeString); + continue; + } + + File textFile = new File(resourcePath, name + "/strings.xml"); + String localizedText; + try { + localizedText = getTextString(textFile, mTextName); + } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) { + throw new LocalizedStringNotFoundException( + "Failed to read the translated text for locale " + name, e); + } + + Locale locale = getLocaleFromFilename(name); + // Removes the double quotation mark from the text. + result.put(locale, localizedText.substring(1, localizedText.length() - 1)); + } + + return result; + } + + /** + * Returns a font object associated given the given locale + * + * @throws IOException if the font file fails to open + * @throws FontFormatException if the font file doesn't have the expected format + */ + private Font loadFontsByLocale(String language) throws IOException, FontFormatException { + if (mLoadedFontMap.containsKey(language)) { + return mLoadedFontMap.get(language); + } + + String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME); + String[] suffixes = {".otf", ".ttf", ".ttc"}; + for (String suffix : suffixes) { + File fontFile = new File(mFontDirPath, fontName + suffix); + if (fontFile.isFile()) { + Font result = Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize); + mLoadedFontMap.put(language, result); + return result; + } + } + + throw new IOException( + "Can not find the font file " + fontName + " for language " + language); + } + + /** Wraps the text with a maximum of mImageWidth pixels per line. */ + private WrappedTextInfo wrapText(String text, FontMetrics metrics) { + WrappedTextInfo info = new WrappedTextInfo(); + + BreakIterator lineBoundary = BreakIterator.getLineInstance(); + lineBoundary.setText(text); + + int lineWidth = 0; // Width of the processed words of the current line. + int start = lineBoundary.first(); + StringBuilder line = new StringBuilder(); + for (int end = lineBoundary.next(); end != BreakIterator.DONE; + start = end, end = lineBoundary.next()) { + String token = text.substring(start, end); + int tokenWidth = metrics.stringWidth(token); + // Handles the width mismatch of the word "Android" between different fonts. + if (token.contains(ANDROID_STRING) + && metrics.getFont().canDisplayUpTo(ANDROID_STRING) != -1) { + tokenWidth = tokenWidth - metrics.stringWidth(ANDROID_STRING) + mAndroidStringWidth; + } + + if (lineWidth + tokenWidth > mImageWidth) { + info.addLine(line.toString(), lineWidth, metrics.getFont(), mDefaultFont); + + line = new StringBuilder(); + lineWidth = 0; + } + line.append(token); + lineWidth += tokenWidth; + } + + info.addLine(line.toString(), lineWidth, metrics.getFont(), mDefaultFont); + + return info; + } + + /** + * Handles the special characters of the raw text embedded in the xml file; and wraps the text + * with a maximum of mImageWidth pixels per line. + * + * @param text the string representation of text to wrap + * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of + * the text given its string representation + * @return a WrappedTextInfo class with the width of each AttributedString smaller than + * mImageWidth pixels + */ + private WrappedTextInfo processAndWrapText(String text, FontMetrics metrics) { + // Apostrophe is escaped in the xml file. + String processed = text.replace("\\'", "'"); + // The separator "\n\n" indicates a new line in the text. + String[] lines = processed.split("\\\\n\\\\n"); + WrappedTextInfo result = new WrappedTextInfo(); + for (String line : lines) { + result.addLines(wrapText(line, metrics)); + } + + return result; + } + + /** + * Encodes the information of the text image for |locale|. According to minui/resources.cpp, the + * width, height and locale of the image is decoded as: int w = (row[1] << 8) | row[0]; int h = + * (row[3] << 8) | row[2]; __unused int len = row[4]; char* loc = + * reinterpret_cast(&row[5]); + */ + private List encodeTextInfo(int width, int height, String locale) { + List info = + new ArrayList<>( + Arrays.asList( + width & 0xff, + width >> 8, + height & 0xff, + height >> 8, + locale.length())); + + byte[] localeBytes = locale.getBytes(); + for (byte b : localeBytes) { + info.add((int) b); + } + info.add(0); + + return info; + } + + /** Returns Graphics2D object that uses the given locale. */ + private Graphics2D createGraphics(Locale locale) throws IOException, FontFormatException { + Graphics2D graphics = mBufferedImage.createGraphics(); + graphics.setColor(Color.WHITE); + graphics.setRenderingHint( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); + graphics.setFont(loadFontsByLocale(locale.getLanguage())); + + return graphics; + } + + /** Returns the maximum screen width needed to fit the given text after wrapping. */ + private int measureTextWidth(String text, Locale locale) + throws IOException, FontFormatException { + Graphics2D graphics = createGraphics(locale); + FontMetrics fontMetrics = graphics.getFontMetrics(); + WrappedTextInfo wrappedTextInfo = processAndWrapText(text, fontMetrics); + + int textWidth = 0; + for (WrappedTextInfo.LineInfo lineInfo : wrappedTextInfo.mWrappedLines) { + textWidth = Math.max(textWidth, lineInfo.mLineWidth); + } + + // This may happen if one single word is larger than the image width. + if (textWidth > mImageWidth) { + throw new IllegalStateException( + "Wrapped text width " + + textWidth + + " is larger than image width " + + mImageWidth + + " for locale: " + + locale); + } + + return textWidth; + } + + /** + * Draws the text string on the canvas for given locale. + * + * @param text the string to draw on canvas + * @param locale the current locale tag of the string to draw + * @throws IOException if we cannot find the corresponding font file for the given locale. + * @throws FontFormatException if we failed to load the font file for the given locale. + */ + private void drawText(String text, Locale locale, String languageTag) + throws IOException, FontFormatException { + LOGGER.info("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text); + + Graphics2D graphics = createGraphics(locale); + FontMetrics fontMetrics = graphics.getFontMetrics(); + WrappedTextInfo wrappedTextInfo = processAndWrapText(text, fontMetrics); + + // Marks the start y offset for the text image of current locale; and reserves one line to + // encode the image metadata. + int currentImageStart = mVerticalOffset; + mVerticalOffset += 1; + for (WrappedTextInfo.LineInfo lineInfo : wrappedTextInfo.mWrappedLines) { + int lineHeight = fontMetrics.getHeight(); + // Doubles the height of the image if we are short of space. + if (mVerticalOffset + lineHeight >= mImageHeight) { + resize(mImageWidth, mImageHeight * 2); + // Recreates the graphics since it's attached to the buffered image. + graphics = createGraphics(locale); + } + + // Draws the text at mVerticalOffset and increments the offset with line space. + int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent(); + + // Draws from right if it's an RTL language. + int x = + mCenterAlignment + ? (mImageWidth - lineInfo.mLineWidth) / 2 + : RTL_LANGUAGE.contains(languageTag) + ? mImageWidth - lineInfo.mLineWidth + : 0; + graphics.drawString(lineInfo.mLineContent.getIterator(), x, baseLine); + + mVerticalOffset += lineHeight; + } + + // Encodes the metadata of the current localized image as pixels. + int currentImageHeight = mVerticalOffset - currentImageStart - 1; + List info = encodeTextInfo(mImageWidth, currentImageHeight, languageTag); + for (int i = 0; i < info.size(); i++) { + int[] pixel = {info.get(i)}; + mBufferedImage.getRaster().setPixel(i, currentImageStart, pixel); + } + } + + /** + * Redraws the image with the new width and new height. + * + * @param width the new width of the image in pixels. + * @param height the new height of the image in pixels. + */ + private void resize(int width, int height) { + BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + Graphics2D graphic = resizedImage.createGraphics(); + graphic.drawImage(mBufferedImage, 0, 0, null); + graphic.dispose(); + + mBufferedImage = resizedImage; + mImageWidth = width; + mImageHeight = height; + } + + /** + * This function draws the font characters and saves the result to outputPath. + * + * @param localizedTextMap a map from locale to its translated text string + * @param outputPath the path to write the generated image file. + * @throws FontFormatException if there's a format error in one of the font file + * @throws IOException if we cannot find the font file for one of the locale, or we failed to + * write the image file. + */ + public void generateImage(Map localizedTextMap, String outputPath) + throws FontFormatException, IOException { + FontMetrics defaultFontMetrics = + createGraphics(Locale.forLanguageTag("en")).getFontMetrics(); + mDefaultFont = defaultFontMetrics.getFont(); + mAndroidStringWidth = defaultFontMetrics.stringWidth(ANDROID_STRING); + + // The last country variant should be the fallback locale for a given language. + Map fallbackLocaleMap = new HashMap<>(); + int textWidth = 0; + for (Locale locale : localizedTextMap.keySet()) { + // Updates the fallback locale if we have a new language variant. Don't do it for en-XC + // as it's a pseudo-locale. + if (!locale.toLanguageTag().equals("en-XC")) { + fallbackLocaleMap.put(locale.getLanguage(), locale); + } + textWidth = Math.max(textWidth, measureTextWidth(localizedTextMap.get(locale), locale)); + } + + // Removes the black margins to reduce the size of the image. + resize(textWidth, mImageHeight); + + for (Locale locale : localizedTextMap.keySet()) { + // Recovery expects en-US instead of en_US. + String languageTag = locale.toLanguageTag(); + Locale fallbackLocale = fallbackLocaleMap.get(locale.getLanguage()); + if (locale.equals(fallbackLocale)) { + // Makes the last country variant for a given language be the catch-all for that + // language. + languageTag = locale.getLanguage(); + } else if (localizedTextMap.get(locale).equals(localizedTextMap.get(fallbackLocale))) { + LOGGER.info("Skip parsing text for duplicate locale " + locale); + continue; + } + + drawText(localizedTextMap.get(locale), locale, languageTag); + } + + resize(mImageWidth, mVerticalOffset); + ImageIO.write(mBufferedImage, "png", new File(outputPath)); + } + + /** Prints the helper message. */ + public static void printUsage(Options options) { + new HelpFormatter().printHelp("java -jar path_to_jar [required_options]", options); + } + + /** Creates the command line options. */ + public static Options createOptions() { + Options options = new Options(); + options.addOption( + OptionBuilder.withLongOpt("image_width") + .withDescription("The initial width of the image in pixels.") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("text_name") + .withDescription( + "The description of the text string, e.g. recovery_erasing") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("font_dir") + .withDescription( + "The directory that contains all the support font format files, " + + "e.g. $OUT/system/fonts/") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("resource_dir") + .withDescription( + "The resource directory that contains all the translated strings in" + + " xml format, e.g." + + " bootable/recovery/tools/recovery_l10n/res/") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("output_file") + .withDescription("Path to the generated image.") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("center_alignment") + .withDescription("Align the text in the center of the screen.") + .hasArg(false) + .create()); + + options.addOption( + OptionBuilder.withLongOpt("verbose") + .withDescription("Output the logging above info level.") + .hasArg(false) + .create()); + + options.addOption( + OptionBuilder.withLongOpt("locales") + .withDescription("A list of android locales separated by ',' e.g." + + " 'af,en,zh-rTW'") + .hasArg(true) + .create()); + + return options; + } + + /** The main function parses the command line options and generates the desired text image. */ + public static void main(String[] args) + throws NumberFormatException, IOException, FontFormatException, + LocalizedStringNotFoundException { + Options options = createOptions(); + CommandLine cmd; + try { + cmd = new GnuParser().parse(options, args); + } catch (ParseException e) { + System.err.println(e.getMessage()); + printUsage(options); + return; + } + + int imageWidth = Integer.parseUnsignedInt(cmd.getOptionValue("image_width")); + + if (cmd.hasOption("verbose")) { + LOGGER.setLevel(Level.INFO); + } else { + LOGGER.setLevel(Level.WARNING); + } + + ImageGenerator imageGenerator = + new ImageGenerator( + imageWidth, + cmd.getOptionValue("text_name"), + DEFAULT_FONT_SIZE, + cmd.getOptionValue("font_dir"), + cmd.hasOption("center_alignment")); + + Set localesSet = null; + if (cmd.hasOption("locales")) { + String[] localesList = cmd.getOptionValue("locales").split(","); + localesSet = new HashSet<>(Arrays.asList(localesList)); + // Ensures that we have the default locale, all english translations are identical. + localesSet.add("en-rAU"); + } + Map localizedStringMap = + imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"), + localesSet); + imageGenerator.generateImage(localizedStringMap, cmd.getOptionValue("output_file")); + } +} diff --git a/tools/image_generator/ImageGenerator.mf b/tools/image_generator/ImageGenerator.mf new file mode 100644 index 0000000000..17712d129f --- /dev/null +++ b/tools/image_generator/ImageGenerator.mf @@ -0,0 +1 @@ +Main-Class: com.android.recovery.tools.ImageGenerator diff --git a/tools/image_generator/README.md b/tools/image_generator/README.md new file mode 100644 index 0000000000..5d70354e47 --- /dev/null +++ b/tools/image_generator/README.md @@ -0,0 +1,21 @@ +Recovery Image Generator +------------------------- + +This program uses java.awt.Graphics2D to generate the background text files used +under recovery mode. And thus we don't need to do the manual work by running +emulators with different dpi. + +# Usage: + `java -jar path_to_jar --image_width imageWidth --text_name textName --font_dir fontDirectory + --resource_dir resourceDirectory --output_file outputFilename` + +# Description of the parameters: +1. `imageWidth`: The number of pixels per line; and the text strings will be + wrapped accordingly. +2. `textName`: The description of the text string, e.g. "recovery_erasing", + "recovery_installing_security" +3. `fontDirectory`: The directory that contains all the support .ttf | .ttc + files, e.g. $OUT/system/fonts/ +4. `resourceDirectory`: The resource directory that contains all the translated + strings in xml format, e.g. bootable/recovery/tools/recovery_l10n/res/ +5. `outputFilename`: Path to the generated image. diff --git a/tools/recovery_l10n/Android.bp b/tools/recovery_l10n/Android.bp new file mode 100644 index 0000000000..d0a6d4b47a --- /dev/null +++ b/tools/recovery_l10n/Android.bp @@ -0,0 +1,23 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_app { + name: "RecoveryLocalizer", + + sdk_version: "current", + + srcs: [ + "src/**/*.java", + ], +} diff --git a/tools/recovery_l10n/Android.mk b/tools/recovery_l10n/Android.mk deleted file mode 100644 index 7197c5c784..0000000000 --- a/tools/recovery_l10n/Android.mk +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2012 Google Inc. All Rights Reserved. - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_PACKAGE_NAME := RecoveryLocalizer -LOCAL_SDK_VERSION := current -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -include $(BUILD_PACKAGE) diff --git a/tools/recovery_l10n/res/values-af/strings.xml b/tools/recovery_l10n/res/values-af/strings.xml index b1974da204..85a3c90373 100644 --- a/tools/recovery_l10n/res/values-af/strings.xml +++ b/tools/recovery_l10n/res/values-af/strings.xml @@ -6,4 +6,9 @@ "Geen opdrag nie" "Fout!" "Installeer tans sekuriteitopdatering" + "Kan nie Android-stelsel laai nie. Jou data is dalk korrup. As jy aanhou om hierdie boodskap te kry, sal jy dalk \'n fabrieksterugstelling moet doen en alle gebruikerdata moet uitvee wat op hierdie toestel geberg word." + "Probeer weer" + "Fabrieksterugstelling" + "Vee alle gebruikerdata uit?\n\n DIT KAN NIE ONTDOEN WORD NIE!" + "Kanselleer" diff --git a/tools/recovery_l10n/res/values-am/strings.xml b/tools/recovery_l10n/res/values-am/strings.xml index 75c17fbad9..353f2233b4 100644 --- a/tools/recovery_l10n/res/values-am/strings.xml +++ b/tools/recovery_l10n/res/values-am/strings.xml @@ -6,4 +6,9 @@ "ምንም ትዕዛዝ የለም" "ስህተት!" "የደህንነት ዝማኔ በመጫን ላይ" + "የAndroid ስርዓትን መጫን አልተቻለም። የእርስዎ ውሂብ የተበላሸ ሊሆን ይችላል። ይህን መልዕክት ማግኘቱን ከቀጠሉ የፋብሪካ ውሂብ ዳግም ማስጀመር ማከናወንና በዚህ መሣሪያ ላይ የተከማቸ ሁሉንም የተጠቃሚ ውሂብ መሰረዝ ሊኖርብዎት ይችላል።" + "እንደገና ሞክር" + "የፋብሪካ ውሂብ ዳግም ማስጀመር" + "ሁሉም የተጠቃሚ ውሂብ ይሰረዝ?\n\n ይህ ሊቀለበስ አይችልም!" + "ይቅር" diff --git a/tools/recovery_l10n/res/values-ar/strings.xml b/tools/recovery_l10n/res/values-ar/strings.xml index 601b5832bc..a9cd2d1331 100644 --- a/tools/recovery_l10n/res/values-ar/strings.xml +++ b/tools/recovery_l10n/res/values-ar/strings.xml @@ -6,4 +6,9 @@ "ليس هناك أي أمر" "خطأ!" "جارٍ تثبيت تحديث الأمان" + "‏يتعذَّر تحميل نظام Android، حيث قد تكون بياناتك تالفة. وإذا استمر ظهور هذه الرسالة، قد يتعيَّن عليك إجراء إعادة الضبط على الإعدادات الأصلية ومحو جميع بيانات المستخدم المُخزَّنة على هذا الجهاز." + "إعادة المحاولة" + "إعادة الضبط على الإعدادات الأصلية" + "هل تريد حجب كل بيانات المستخدم؟\n\n لا يمكن التراجع عن هذا الإجراء." + "إلغاء" diff --git a/tools/recovery_l10n/res/values-as/strings.xml b/tools/recovery_l10n/res/values-as/strings.xml index 2624cebe45..33a204d055 100644 --- a/tools/recovery_l10n/res/values-as/strings.xml +++ b/tools/recovery_l10n/res/values-as/strings.xml @@ -6,4 +6,9 @@ "কোনো আদেশ নাই" "ত্ৰুটি!" "সুৰক্ষা আপডেইট ইনষ্টল কৰি থকা হৈছে" + "Android ছিষ্টেম ল\'ড কৰিব নোৱাৰি। আপোনাৰ ডেটাত কিবা আসোঁৱাহ থকা যেন লাগিছে। আপুনি যদি এই বাৰ্তাটো পায়েই থাকে, আপুনি নিজৰ ডিভাইচটো ফেক্টৰী ডেটা ৰিছেট কৰি সেইটোত থকা ব্যৱহাৰকাৰীৰ সকলো ডেটা মচিব লগা হ\'ব পাৰে।" + "আকৌ চেষ্টা কৰক" + "ফেক্টৰী ডেটা ৰিছেট" + "ব্যৱহাৰকাৰীৰ সকলো ডেটা মচিবনে?\n\n এইটো কৰাৰ পিছত আনডু কৰিব নোৱাৰি!" + "বাতিল কৰক" diff --git a/tools/recovery_l10n/res/values-az/strings.xml b/tools/recovery_l10n/res/values-az/strings.xml index c6765a9ea7..35194c4b27 100644 --- a/tools/recovery_l10n/res/values-az/strings.xml +++ b/tools/recovery_l10n/res/values-az/strings.xml @@ -6,4 +6,9 @@ "Əmr yoxdur" "Xəta!" "Təhlükəsizlik güncəlləməsi yüklənir" + "Android sistemi yüklənmir. Datanız zədələnə bilər. Bu mesajı yenə qəbul etsəniz, data zavod sıfırlamasını həyata keçirməli və bu cihazda saxlanmış istifadəçi datasının hamısını silməlisiniz." + "Yenidən cəhd edin" + "Data zavod sıfırlaması" + "Bütün istifadəçi datası silinsin?\n\n BU ƏMƏLİYYATI GERİ QAYTARMAQ MÜMKÜN DEYİL!" + "Ləğv edin" diff --git a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml index c2d8f2239e..19c6f41946 100644 --- a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml +++ b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml @@ -6,4 +6,9 @@ "Nema komande" "Greška!" "Instalira se bezbednosno ažuriranje" + "Učitavanje Android sistema nije uspelo. Podaci su možda oštećeni. Ako nastavite da dobijate ovu poruku, možda ćete morati da resetujete uređaj na fabrička podešavanja i obrišete sve podatke korisnika koje čuvate na njemu." + "Probaj ponovo" + "Resetovanje na fabrička podešavanja" + "Želite li da izbrišete sve podatke korisnika?\n\n OVO NE MOŽE DA SE OPOZOVE!" + "Otkaži" diff --git a/tools/recovery_l10n/res/values-be/strings.xml b/tools/recovery_l10n/res/values-be/strings.xml index 7c0954d318..ad14fbe279 100644 --- a/tools/recovery_l10n/res/values-be/strings.xml +++ b/tools/recovery_l10n/res/values-be/strings.xml @@ -6,4 +6,9 @@ "Няма каманды" "Памылка" "Усталёўка абнаўлення сістэмы бяспекі" + "Не ўдалося загрузіць сістэму Android. Магчыма, вашы даныя пашкоджаны. Калі вы зноў убачыце гэта паведамленне, скіньце налады прылады да заводскіх значэнняў і сатрыце ўсе карыстальніцкія даныя, якія на ёй захоўваюцца." + "Паўтарыць спробу" + "Скінуць да заводскіх налад" + "Ачысціць усе карыстальніцкія даныя?\n\n ГЭТА ДЗЕЯННЕ НЕЛЬГА АДРАБІЦЬ!" + "Скасаваць" diff --git a/tools/recovery_l10n/res/values-bg/strings.xml b/tools/recovery_l10n/res/values-bg/strings.xml index 9e628a2af6..e96ff44640 100644 --- a/tools/recovery_l10n/res/values-bg/strings.xml +++ b/tools/recovery_l10n/res/values-bg/strings.xml @@ -6,4 +6,9 @@ "Без команда" "Грешка!" "Актуализацията на сигурносттa се инсталира" + "Системата Android не може да се зареди. Данните ви може да са повредени. Ако продължите да получавате това съобщение, може да е необходимо да възстановите фабричните настройки и да изтриете всички потребителски данни, съхранени на това устройство." + "Нов опит" + "Възстановяване на фабричните настройки" + "Да се изчистят ли всички потребителски данни?\n\n ТОВА ДЕЙСТВИЕ НЕ МОЖЕ ДА БЪДЕ ОТМЕНЕНО!" + "Отказ" diff --git a/tools/recovery_l10n/res/values-bn/strings.xml b/tools/recovery_l10n/res/values-bn/strings.xml index 0a481faf1b..5967bc4b85 100644 --- a/tools/recovery_l10n/res/values-bn/strings.xml +++ b/tools/recovery_l10n/res/values-bn/strings.xml @@ -6,4 +6,9 @@ "কোনো আদেশ নেই" "ত্রুটি!" "নিরাপত্তার আপডেট ইনস্টল করা হচ্ছে" + "Android সিস্টেম লোড করা যায়নি। আপনার ডেটা হয়ত নষ্ট হয়ে গেছে। যদি এই মেসেজটি আসতেই থাকে তাহলে হয়ত ফ্যাক্টরি ডেটা রিসেট করে এই ডিভাইসে থাকা ব্যবহারকারীর সব ডেটা মুছে ফেলতে হবে।" + "আবার চেষ্টা করুন" + "ফ্যাক্টরি ডেটা রিসেট করুন" + "ব্যবহারকারীর সব ডেটা মুছে দিতে চান?\n\n এই ডেটা আর ফিরে পাওয়া যাবে না!" + "বাতিল করুন" diff --git a/tools/recovery_l10n/res/values-bs/strings.xml b/tools/recovery_l10n/res/values-bs/strings.xml index 412cf02763..38f197f29c 100644 --- a/tools/recovery_l10n/res/values-bs/strings.xml +++ b/tools/recovery_l10n/res/values-bs/strings.xml @@ -6,4 +6,9 @@ "Nema komande" "Greška!" "Instaliranje sigurnosnog ažuriranja…" + "Nije moguće učitati Android sistem. Podaci su možda oštećeni. Ako opet primite ovu poruku, možda ćete morati vratiti uređaj na fabričke postavke i izbrisati sve podatke korisnika pohranjene na ovom uređaju." + "Pokušaj ponovo" + "Vraćanje na fabričke postavke" + "Izbrisati sve podatke korisnika?\n\n TA RADNJA SE NE MOŽE PONIŠTITI!" + "Otkaži" diff --git a/tools/recovery_l10n/res/values-ca/strings.xml b/tools/recovery_l10n/res/values-ca/strings.xml index 3f266d2df1..6b7bec0774 100644 --- a/tools/recovery_l10n/res/values-ca/strings.xml +++ b/tools/recovery_l10n/res/values-ca/strings.xml @@ -6,4 +6,9 @@ "No hi ha cap ordre" "S\'ha produït un error" "S\'està instal·lant una actualització de seguretat" + "No s\'ha pogut carregar el sistema Android. És possible que les teves dades estiguin malmeses. Si continues veient aquest missatge, pot ser que hagis de restablir les dades de fàbrica i esborrar totes les dades d\'usuari emmagatzemades en aquest dispositiu." + "Torna-ho a provar" + "Restableix les dades de fàbrica" + "Vols eliminar totes les dades d\'usuari?\n\n AQUESTA ACCIÓ NO ES POT DESFER." + "Cancel·la" diff --git a/tools/recovery_l10n/res/values-cs/strings.xml b/tools/recovery_l10n/res/values-cs/strings.xml index eb436a810d..c42dab2c41 100644 --- a/tools/recovery_l10n/res/values-cs/strings.xml +++ b/tools/recovery_l10n/res/values-cs/strings.xml @@ -6,4 +6,9 @@ "Žádný příkaz" "Chyba!" "Instalace aktualizace zabezpečení" + "Systém Android se nepodařilo načíst. Vaše data jsou možná poškozena. Pokud se tato zpráva bude zobrazovat i nadále, bude nutné vymazat všechna uživatelská data v zařízení a obnovit tovární data." + "Zkusit znovu" + "Obnovení továrních dat" + "Vymazat všechna uživatelská data?\n\nTUTO AKCI NELZE VRÁTIT ZPĚT!" + "Zrušit" diff --git a/tools/recovery_l10n/res/values-da/strings.xml b/tools/recovery_l10n/res/values-da/strings.xml index c6e64a245f..814c0df091 100644 --- a/tools/recovery_l10n/res/values-da/strings.xml +++ b/tools/recovery_l10n/res/values-da/strings.xml @@ -6,4 +6,9 @@ "Ingen kommando" "Fejl!" "Installerer sikkerhedsopdateringen" + "Android-systemet kan ikke indlæses. Dine data er muligvis beskadigede. Hvis du bliver ved med at få denne meddelelse, er du måske nødt til at udføre en gendannelse af fabriksdata og slette alle brugerdata, der er gemt på denne enhed." + "Prøv igen" + "Gendannelse af fabriksdata" + "Vil du rydde alle brugerdata?\n\n DETTE KAN IKKE FORTRYDES!" + "Annuller" diff --git a/tools/recovery_l10n/res/values-de/strings.xml b/tools/recovery_l10n/res/values-de/strings.xml index 6b6726a235..80fa971106 100644 --- a/tools/recovery_l10n/res/values-de/strings.xml +++ b/tools/recovery_l10n/res/values-de/strings.xml @@ -6,4 +6,9 @@ "Kein Befehl" "Fehler" "Sicherheitsupdate wird installiert" + "Android-System kann nicht geladen werden. Deine Daten sind eventuell beschädigt. Wenn du diese Nachricht weiterhin erhältst, musst du dein Gerät unter Umständen auf die Werkseinstellungen zurücksetzen und alle darauf gespeicherten Nutzerdaten löschen." + "Noch einmal versuchen" + "Zurücksetzen auf Werkseinstellungen" + "Alle Nutzerdaten löschen?\n\n DIESE AKTION KANN NICHT RÜCKGÄNGIG GEMACHT WERDEN." + "Abbrechen" diff --git a/tools/recovery_l10n/res/values-el/strings.xml b/tools/recovery_l10n/res/values-el/strings.xml index 4cb2da5f90..204ae4092a 100644 --- a/tools/recovery_l10n/res/values-el/strings.xml +++ b/tools/recovery_l10n/res/values-el/strings.xml @@ -6,4 +6,9 @@ "Καμία εντολή" "Σφάλμα!" "Εγκατάσταση ενημέρωσης ασφαλείας" + "Δεν είναι δυνατή η φόρτωση του συστήματος Android. Τα δεδομένα σας μπορεί να είναι κατεστραμμένα. Εάν εξακολουθήσετε να λαμβάνετε αυτό το μήνυμα, μπορεί να χρειαστεί να κάνετε επαναφορά εργοστασιακών ρυθμίσεων και να διαγράψετε όλα τα δεδομένα που έχουν αποθηκευτεί σε αυτήν τη συσκευή." + "Δοκιμάστε ξανά" + "Επαναφορά εργοστασιακών δεδομένων" + "Να διαγραφούν όλα τα δεδομένα χρήστη;\n\n ΔΕΝ ΕΙΝΑΙ ΔΥΝΑΤΗ Η ΑΝΑΙΡΕΣΗ ΑΥΤΗΣ ΤΗΣ ΕΝΕΡΓΕΙΑΣ!" + "Ακύρωση" diff --git a/tools/recovery_l10n/res/values-en-rAU/strings.xml b/tools/recovery_l10n/res/values-en-rAU/strings.xml index dc75c2374e..6451e5b6c3 100644 --- a/tools/recovery_l10n/res/values-en-rAU/strings.xml +++ b/tools/recovery_l10n/res/values-en-rAU/strings.xml @@ -6,4 +6,9 @@ "No command" "Error!" "Installing security update" + "Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device." + "Try again" + "Factory data reset" + "Wipe all user data?\n\n THIS CANNOT BE UNDONE!" + "Cancel" diff --git a/tools/recovery_l10n/res/values-en-rCA/strings.xml b/tools/recovery_l10n/res/values-en-rCA/strings.xml index dc75c2374e..6451e5b6c3 100644 --- a/tools/recovery_l10n/res/values-en-rCA/strings.xml +++ b/tools/recovery_l10n/res/values-en-rCA/strings.xml @@ -6,4 +6,9 @@ "No command" "Error!" "Installing security update" + "Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device." + "Try again" + "Factory data reset" + "Wipe all user data?\n\n THIS CANNOT BE UNDONE!" + "Cancel" diff --git a/tools/recovery_l10n/res/values-en-rGB/strings.xml b/tools/recovery_l10n/res/values-en-rGB/strings.xml index dc75c2374e..6451e5b6c3 100644 --- a/tools/recovery_l10n/res/values-en-rGB/strings.xml +++ b/tools/recovery_l10n/res/values-en-rGB/strings.xml @@ -6,4 +6,9 @@ "No command" "Error!" "Installing security update" + "Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device." + "Try again" + "Factory data reset" + "Wipe all user data?\n\n THIS CANNOT BE UNDONE!" + "Cancel" diff --git a/tools/recovery_l10n/res/values-en-rIN/strings.xml b/tools/recovery_l10n/res/values-en-rIN/strings.xml index dc75c2374e..6451e5b6c3 100644 --- a/tools/recovery_l10n/res/values-en-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-en-rIN/strings.xml @@ -6,4 +6,9 @@ "No command" "Error!" "Installing security update" + "Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device." + "Try again" + "Factory data reset" + "Wipe all user data?\n\n THIS CANNOT BE UNDONE!" + "Cancel" diff --git a/tools/recovery_l10n/res/values-en-rXC/strings.xml b/tools/recovery_l10n/res/values-en-rXC/strings.xml index 2d528b3fb8..18b077f297 100644 --- a/tools/recovery_l10n/res/values-en-rXC/strings.xml +++ b/tools/recovery_l10n/res/values-en-rXC/strings.xml @@ -1,9 +1,14 @@ - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎Installing system update‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎Erasing‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎No command‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎Error!‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎Installing security update‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎Installing system update‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎Erasing‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎No command‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎Error!‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎Installing security update‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎Try again‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎Factory data reset‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‎Wipe all user data?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ THIS CAN NOT BE UNDONE!‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎Cancel‎‏‎‎‏‎" diff --git a/tools/recovery_l10n/res/values-es-rUS/strings.xml b/tools/recovery_l10n/res/values-es-rUS/strings.xml index 06b86069bd..c0baa5924b 100644 --- a/tools/recovery_l10n/res/values-es-rUS/strings.xml +++ b/tools/recovery_l10n/res/values-es-rUS/strings.xml @@ -6,4 +6,9 @@ "Ningún comando" "Error" "Instalando actualización de seguridad" + "No se puede cargar el sistema Android. Es posible que los datos estén dañados. Si este mensaje no desaparece, es posible que debas restablecer la configuración de fábrica del dispositivo y borrar todos los datos del usuario almacenados en él." + "Reintentar" + "Restablecer configuración de fábrica" + "¿Quieres borrar todos los datos del usuario?\n\n ESTA ACCIÓN NO SE PUEDE DESHACER" + "Cancelar" diff --git a/tools/recovery_l10n/res/values-es/strings.xml b/tools/recovery_l10n/res/values-es/strings.xml index d8618f2f4e..de3b69bf32 100644 --- a/tools/recovery_l10n/res/values-es/strings.xml +++ b/tools/recovery_l10n/res/values-es/strings.xml @@ -6,4 +6,9 @@ "Sin comandos" "Error" "Instalando actualización de seguridad" + "No se puede cargar el sistema Android. Es posible que tus datos estén dañados. Si sigue apareciendo este mensaje, es posible que tengas que restablecer el estado de fábrica y borrar todos los datos de usuario almacenados en este dispositivo." + "Reintentar" + "Restablecer estado de fábrica" + "¿Quieres borrar todos los datos de usuario?\n\n ESTA ACCIÓN NO SE PUEDE DESHACER." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-et/strings.xml b/tools/recovery_l10n/res/values-et/strings.xml index 072a9ef800..cafb32ffdb 100644 --- a/tools/recovery_l10n/res/values-et/strings.xml +++ b/tools/recovery_l10n/res/values-et/strings.xml @@ -6,4 +6,9 @@ "Käsk puudub" "Viga!" "Turvavärskenduse installimine" + "Android-süsteemi ei saa laadida. Teie andmed on võib-olla rikutud. Kui jätkate selle sõnumi hankimist, peate võib-olla tegema tehaseandmetele lähtestamise ja kustutama kõik sellesse seadmesse salvestatud kasutajaandmed." + "Proovige uuesti" + "Tehaseandmetele lähtestamine" + "Kas kustutada kõik kasutajaandmed?\n\n SEDA TOIMINGUT EI SAA TAGASI VÕTTA!" + "Tühista" diff --git a/tools/recovery_l10n/res/values-eu/strings.xml b/tools/recovery_l10n/res/values-eu/strings.xml index 5540469d03..005a04264c 100644 --- a/tools/recovery_l10n/res/values-eu/strings.xml +++ b/tools/recovery_l10n/res/values-eu/strings.xml @@ -6,4 +6,9 @@ "Ez dago agindurik" "Errorea" "Segurtasun-eguneratzea instalatzen" + "Ezin da kargatu Android sistema. Zure datuak hondatuta egon daitezke. Mezu hau jasotzen jarraitzen baduzu, jatorrizko datuak berrezarri beharko dituzu eta gailuan gordetako erabiltzaile-datu guztiak ezabatu beharko dituzu." + "Saiatu berriro" + "Berrezarri jatorrizko datuak" + "Erabiltzailearen datu guztiak xahutu nahi dituzu?\n\n EKINTZA HORI EZIN DA DESEGIN!" + "Utzi" diff --git a/tools/recovery_l10n/res/values-fa/strings.xml b/tools/recovery_l10n/res/values-fa/strings.xml index cc390ae844..1c1be9ae39 100644 --- a/tools/recovery_l10n/res/values-fa/strings.xml +++ b/tools/recovery_l10n/res/values-fa/strings.xml @@ -6,4 +6,9 @@ "فرمانی وجود ندارد" "خطا!" "در حال نصب به‌روزرسانی امنیتی" + "‏نمی‌توان سیستم Android را بارگیری کرد. ممکن است داده‌های شما خراب باشند. اگر همچنان این پیام را دریافت می‌کنید، شاید لازم باشد بازنشانی داده‌های کارخانه‌ای انجام دهید و همه داده‌های کاربر را که در این دستگاه ذخیره شده است پاک کنید." + "تلاش مجدد" + "بازنشانی داده‌های کارخانه" + "همه داده‌های کاربر پاک شود؟\n\n این کار قابل‌واگرد نیست!" + "لغو" diff --git a/tools/recovery_l10n/res/values-fi/strings.xml b/tools/recovery_l10n/res/values-fi/strings.xml index 5141642c84..fddaf14532 100644 --- a/tools/recovery_l10n/res/values-fi/strings.xml +++ b/tools/recovery_l10n/res/values-fi/strings.xml @@ -6,4 +6,9 @@ "Ei komentoa" "Virhe!" "Asennetaan tietoturvapäivitystä" + "Android-järjestelmän lataaminen epäonnistui. Datasi voi olla vioittunut. Jos näet tämän viestin toistuvasti, sinun on ehkä palautettava tehdasasetukset ja poistettava kaikki laitteella olevat käyttäjätiedot." + "Yritä uudelleen" + "Tehdasasetuksien palauttaminen" + "Poistetaanko kaikki käyttäjätiedot?\n\nTÄTÄ EI VOI PERUA!" + "Peruuta" diff --git a/tools/recovery_l10n/res/values-fr-rCA/strings.xml b/tools/recovery_l10n/res/values-fr-rCA/strings.xml index b2415290b0..978e9ff937 100644 --- a/tools/recovery_l10n/res/values-fr-rCA/strings.xml +++ b/tools/recovery_l10n/res/values-fr-rCA/strings.xml @@ -6,4 +6,9 @@ "Aucune commande" "Erreur!" "Installation de la mise à jour de sécurité en cours..." + "Impossible de charger le système Android. Il se peut que vos données soient corrompues. Si vous continuez de recevoir ce message, vous devrez peut-être effectuer une réinitialisation de l\'appareil à ses paramètres d\'usine et effacer toutes les données d\'utilisateur qu\'il contient." + "Réessayer" + "Réinitialiser aux paramètres d\'usine" + "Effacer toutes les données de l\'utilisateur?\n\n CETTE ACTION NE PEUT PAS ÊTRE ANNULÉE!" + "Annuler" diff --git a/tools/recovery_l10n/res/values-fr/strings.xml b/tools/recovery_l10n/res/values-fr/strings.xml index f0472b5ac0..693a5ddb41 100644 --- a/tools/recovery_l10n/res/values-fr/strings.xml +++ b/tools/recovery_l10n/res/values-fr/strings.xml @@ -6,4 +6,9 @@ "Aucune commande" "Erreur !" "Installation de la mise à jour de sécurité…" + "Impossible de charger le système Android. Vos données sont peut-être corrompues. Si vous continuez à recevoir ce message, vous devrez peut-être rétablir la configuration d\'usine de votre appareil et effacer toutes les données utilisateur stockées sur cet appareil." + "Réessayer" + "Rétablir la configuration d\'usine" + "Effacer toutes les données utilisateur ?\n\n CETTE ACTION NE PEUT PAS ÊTRE ANNULÉE." + "Annuler" diff --git a/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml index 42b2016c29..e6f2ffd84e 100644 --- a/tools/recovery_l10n/res/values-gl/strings.xml +++ b/tools/recovery_l10n/res/values-gl/strings.xml @@ -6,4 +6,9 @@ "Non hai ningún comando" "Erro" "Instalando actualización de seguranza" + "Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos do usuario almacenados neste dispositivo." + "Tentar de novo" + "Restablecemento dos datos de fábrica" + "Queres borrar todos os datos do usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-gu/strings.xml b/tools/recovery_l10n/res/values-gu/strings.xml index 2355a0f4f1..bd83447d77 100644 --- a/tools/recovery_l10n/res/values-gu/strings.xml +++ b/tools/recovery_l10n/res/values-gu/strings.xml @@ -6,4 +6,9 @@ "કોઈ આદેશ નથી" "ભૂલ!" "સુરક્ષા અપડેટ ઇન્સ્ટૉલ કરી રહ્યાં છે" + "Android સિસ્ટમ લોડ કરી શકાતી નથી. તમારો ડેટા કદાચ દૂષિત થયો હોઈ શકે છે. જો તમને આ સંદેશ મળવાનું ચાલુ રહે, તો કદાચ તમારે આ ડિવાઇસ માટે ફેક્ટરી ડેટા રીસેટ કરવાની પ્રક્રિયા કરવી અને આના પર સ્ટોર કરેલો વપરાશકર્તાનો બધો ડેટા કાઢી નાખવો જરૂરી રહેશે." + "ફરી પ્રયાસ કરો" + "ફેક્ટરી ડેટા રીસેટ કરો" + "શું વપરાશકર્તાનો બધો ડેટા વાઇપ કરીએ?\n\n આ ક્રિયામાં કરેલો ફેરફાર રદ કરી શકાતો નથી!" + "રદ કરો" diff --git a/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml index 65d0033523..c1aa2e97f4 100644 --- a/tools/recovery_l10n/res/values-hi/strings.xml +++ b/tools/recovery_l10n/res/values-hi/strings.xml @@ -6,4 +6,9 @@ "कोई निर्देश नहीं मिला" "गड़बड़ी!" "सुरक्षा अपडेट इंस्टॉल किया जा रहा है" + "Android सिस्टम लोड नहीं किया जा सकता. शायद आपके डेटा में गड़बड़ी है. अगर आपको यह मैसेज मिलता रहता है, तो शायद आपको फ़ैक्ट्री डेटा रीसेट करना पड़े और इस डिवाइस की मेमोरी में मौजूद उपयोगकर्ता का सभी डेटा हमेशा के लिए मिटाना पड़े." + "फिर से कोशिश करें" + "फ़ैक्ट्री डेटा रीसेट" + "क्या उपयोगकर्ता का सभी डेटा मिटाएं?\n\n इसे वापस नहीं लाया जा सकता!" + "अभी नहीं" diff --git a/tools/recovery_l10n/res/values-hr/strings.xml b/tools/recovery_l10n/res/values-hr/strings.xml index 3b75ff1150..0fa8fa9fe8 100644 --- a/tools/recovery_l10n/res/values-hr/strings.xml +++ b/tools/recovery_l10n/res/values-hr/strings.xml @@ -6,4 +6,9 @@ "Nema naredbe" "Pogreška!" "Instaliranje sigurnosnog ažuriranja" + "Sustav Android ne može se učitati. Podaci su možda oštećeni. Ako opet primite ovu poruku, možda ćete morati vratiti uređaj na tvorničko stanje i izbrisati sve podatke korisnika pohranjene na ovom uređaju." + "Pokušaj ponovo" + "Vraćanje na tvorničko stanje" + "Želite li izbrisati sve podatke korisnika?\n\n TO SE NE MOŽE PONIŠTITI!" + "Odustani" diff --git a/tools/recovery_l10n/res/values-hu/strings.xml b/tools/recovery_l10n/res/values-hu/strings.xml index 12d4d9fe7c..b7998cea37 100644 --- a/tools/recovery_l10n/res/values-hu/strings.xml +++ b/tools/recovery_l10n/res/values-hu/strings.xml @@ -6,4 +6,9 @@ "Nincs parancs" "Hiba!" "Biztonsági frissítés telepítése" + "Nem sikerült az Android rendszer betöltése. Az adatok sérültek lehetnek. Ha újra megjelenik ez az üzenet, előfordulhat, hogy vissza kell állítania az eszköz gyári adatait, és törölnie kell az eszközön tárolt összes felhasználói adatot." + "Újra" + "Gyári adatok visszaállítása" + "Törli az összes felhasználói adatot?\n\n A MŰVELET NEM VONHATÓ VISSZA." + "Mégse" diff --git a/tools/recovery_l10n/res/values-hy/strings.xml b/tools/recovery_l10n/res/values-hy/strings.xml index 9d62bb7632..76c28a7071 100644 --- a/tools/recovery_l10n/res/values-hy/strings.xml +++ b/tools/recovery_l10n/res/values-hy/strings.xml @@ -6,4 +6,9 @@ "Հրամանը տրված չէ" "Սխալ" "Անվտանգության թարմացման տեղադրում" + "Չհաջողվեց բեռնել Android համակարգը։ Հնարավոր է՝ ձեր տվյալները վնասված են։ Եթե նորից տեսնեք այս հաղորդագրությունը, փորձեք վերակայել սարքի կարգավորումները և ջնջել օգտատիրոջ բոլոր տվյալները։" + "Նորից փորձել" + "Վերակայել բոլոր տվյալները" + "Ջնջե՞լ օգտատիրոջ բոլոր տվյալները։\n\n ԱՅՍ ԳՈՐԾՈՂՈՒԹՅՈՒՆԸ ՀՆԱՐԱՎՈՐ ՉԻ ԼԻՆԻ ՀԵՏԱՐԿԵԼ" + "Չեղարկել" diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml index 0e56e0dd9b..43c9deb946 100644 --- a/tools/recovery_l10n/res/values-in/strings.xml +++ b/tools/recovery_l10n/res/values-in/strings.xml @@ -6,4 +6,9 @@ "Tidak ada perintah" "Error!" "Memasang pembaruan keamanan" + "Tidak dapat memuat sistem Android. Data Anda mungkin rusak. Jika terus mendapatkan pesan ini, Anda mungkin perlu melakukan reset ke setelan pabrik dan menghapus semua data pengguna yang disimpan di perangkat ini." + "Coba lagi" + "Reset ke setelan pabrik" + "Hapus total semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!" + "Batal" diff --git a/tools/recovery_l10n/res/values-is/strings.xml b/tools/recovery_l10n/res/values-is/strings.xml index 5065b6522c..4a6295af2a 100644 --- a/tools/recovery_l10n/res/values-is/strings.xml +++ b/tools/recovery_l10n/res/values-is/strings.xml @@ -6,4 +6,9 @@ "Engin skipun" "Villa!" "Setur upp öryggisuppfærslu" + "Ekki er hægt að hlaða Android kerfi. Gögnin þín kunna að vera skemmd. Ef þessi skilaboð halda áfram að birtast gætirðu þurft að núllstilla og eyða öllum notandagögnum sem eru vistuð í þessu tæki." + "Reyna aftur" + "Núllstilling" + "Viltu eyða öllum notandagögnum?\n\n EKKI ER HÆGT AÐ AFTURKALLA ÞETTA!" + "Hætta við" diff --git a/tools/recovery_l10n/res/values-it/strings.xml b/tools/recovery_l10n/res/values-it/strings.xml index 2c0364e603..8bc203c154 100644 --- a/tools/recovery_l10n/res/values-it/strings.xml +++ b/tools/recovery_l10n/res/values-it/strings.xml @@ -6,4 +6,9 @@ "Nessun comando" "Errore!" "Installazione aggiornamento sicurezza…" + "Impossibile caricare il sistema Android. I tuoi dati potrebbero essere danneggiati. Se continui a ricevere questo messaggio, potrebbe essere necessario eseguire un ripristino dei dati di fabbrica e cancellare tutti i dati utente memorizzati su questo dispositivo." + "Riprova" + "Ripristino dati di fabbrica" + "Vuoi cancellare tutti i dati utente?\n\n NON È POSSIBILE ANNULLARE L\'OPERAZIONE." + "Annulla" diff --git a/tools/recovery_l10n/res/values-iw/strings.xml b/tools/recovery_l10n/res/values-iw/strings.xml index ea5e6f2c93..8ca3bdf007 100644 --- a/tools/recovery_l10n/res/values-iw/strings.xml +++ b/tools/recovery_l10n/res/values-iw/strings.xml @@ -6,4 +6,9 @@ "אין פקודה" "שגיאה!" "מתקין עדכון אבטחה" + "‏לא ניתן לטעון את מערכת Android. ייתכן שהנתונים שלך פגומים. אם הודעה זו תופיע שוב, ייתכן שיהיה עליך לבצע איפוס לנתוני היצרן ולמחוק את כל נתוני המשתמש ששמורים במכשיר זה." + "ניסיון נוסף" + "איפוס לנתוני היצרן" + "לאפס את כל נתוני המשתמש?\n\n לא ניתן לבטל פעולה זו!" + "ביטול" diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml index 36e029b0ff..2d6c0abc4e 100644 --- a/tools/recovery_l10n/res/values-ja/strings.xml +++ b/tools/recovery_l10n/res/values-ja/strings.xml @@ -6,4 +6,9 @@ "コマンドが指定されていません" "エラーが発生しました。" "セキュリティ アップデートをインストールしています" + "Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、このデバイスに保存されているすべてのユーザー データを消去することが必要な場合があります。" + "再試行" + "データの初期化" + "すべてのユーザー データをワイプしますか?\n\nこの操作は元に戻せません。" + "キャンセル" diff --git a/tools/recovery_l10n/res/values-ka/strings.xml b/tools/recovery_l10n/res/values-ka/strings.xml index 6a46b36775..04b8a417f5 100644 --- a/tools/recovery_l10n/res/values-ka/strings.xml +++ b/tools/recovery_l10n/res/values-ka/strings.xml @@ -6,4 +6,9 @@ "ბრძანება არ არის" "წარმოიქმნა შეცდომა!" "მიმდინარეობს უსაფრთხოების განახლების ინსტალაცია" + "Android სისტემის ჩატვირთვა ვერ მოხერხდა. შესაძლოა თქვენი მონაცემები დაზიანებულია. თუ ამ შეტყობინებას კვლავ მიიღებთ, შეიძლება საჭირო იყოს ქარხნული მონაცემების აღდგენა და ამ მოწყობილობაზე შენახული მომხმარებლის ყველა მონაცემის ამოშლა." + "ხელახლა ცდა" + "ქარხნული მონაცემების აღდგენა" + "გსურთ მომხმარებლის ყველა მონაცემის ამოშლა?\n\n ამ მოქმედების გაუქმება ვერ მოხერხდება!" + "გაუქმება" diff --git a/tools/recovery_l10n/res/values-kk/strings.xml b/tools/recovery_l10n/res/values-kk/strings.xml index a4bd86e660..3f6aa23dad 100644 --- a/tools/recovery_l10n/res/values-kk/strings.xml +++ b/tools/recovery_l10n/res/values-kk/strings.xml @@ -6,4 +6,9 @@ "Пәрмен жоқ" "Қате!" "Қауіпсіздік жаңартуы орнатылуда" + "Android жүйесі жүктелмейді. Деректеріңіз бүлінген болуы мүмкін. Егер осы хабар қайта шықса, зауыттық деректерді қалпына келтіріп, пайдаланушы деректерін жойып көріңіз." + "Қайталау" + "Зауыттық деректерді қалпына келтіру" + "Пайдаланушының барлық деректері жойылсын ба?\n\n БҰЛ ӘРЕКЕТТІ ҚАЙТАРЫЛМАЙДЫ!" + "Бас тарту" diff --git a/tools/recovery_l10n/res/values-km/strings.xml b/tools/recovery_l10n/res/values-km/strings.xml index 313c0f4579..0cedb6bb72 100644 --- a/tools/recovery_l10n/res/values-km/strings.xml +++ b/tools/recovery_l10n/res/values-km/strings.xml @@ -6,4 +6,9 @@ "គ្មានពាក្យបញ្ជាទេ" "កំហុស!" "កំពុងដំឡើងការអាប់ដេតសុវត្ថិភាព" + "មិនអាច​ផ្ទុកប្រព័ន្ធ Android បានទេ។ ទិន្នន័យ​របស់​អ្នកអាច​នឹងខូច។ ប្រសិនបើ​អ្នក​បន្តទទួល​បានសារនេះ អ្នកអាចនឹងត្រូវកំណត់​ទិន្នន័យ​ដូច​ចេញ​ពី​រោងចក្រ និងលុបទិន្នន័យ​ទាំងអស់​របស់អ្នក​ប្រើប្រាស់​ដែលបានផ្ទុកនៅ​លើ​ឧបករណ៍​នេះ។" + "ព្យាយាម​ម្ដងទៀត" + "កំណត់​ទិន្នន័យ​ដូច​ចេញ​ពី​រោងចក្រ" + "ឈូស​ទិន្នន័យ​ទាំងអស់​របស់អ្នក​ប្រើប្រាស់?\n\nសកម្មភាព​នេះមិនអាចត្រឡប់វិញបានទេ!" + "បោះបង់" diff --git a/tools/recovery_l10n/res/values-kn/strings.xml b/tools/recovery_l10n/res/values-kn/strings.xml index 5bf6260ee5..a98f4692ae 100644 --- a/tools/recovery_l10n/res/values-kn/strings.xml +++ b/tools/recovery_l10n/res/values-kn/strings.xml @@ -6,4 +6,9 @@ "ಯಾವುದೇ ಆದೇಶವಿಲ್ಲ" "ದೋಷ!" "ಭದ್ರತೆಯ ಅಪ್‌ಡೇಟ್‌ ಸ್ಥಾಪಿಸಲಾಗುತ್ತಿದೆ" + "Android ಸಿಸ್ಟಂ ಅನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಡೇಟಾ ದೋಷಪೂರಿತವಾಗಿರಬಹುದು. ನೀವು ಈ ಸಂದೇಶ ಪಡೆಯುವುದು ಮುಂದುವರಿದರೆ, ನೀವು ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾ ರಿಸೆಟ್ ಮಾಡುವ ಅಗತ್ಯವಿದೆ ಮತ್ತು ಈ ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾದ ಎಲ್ಲಾ ಬಳಕೆದಾರರ ಡೇಟಾವನ್ನು ಅಳಿಸಬೇಕಾಗುತ್ತದೆ." + "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ" + "ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾ ರಿಸೆಟ್‌" + "ಎಲ್ಲಾ ಬಳಕೆದಾರರ ಡೇಟಾವನ್ನು ಅಳಿಸುವುದೇ?\n\n ಇದನ್ನು ರದ್ದುಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ!" + "ರದ್ದುಮಾಡಿ" diff --git a/tools/recovery_l10n/res/values-ko/strings.xml b/tools/recovery_l10n/res/values-ko/strings.xml index aca13bbe79..9067f4c343 100644 --- a/tools/recovery_l10n/res/values-ko/strings.xml +++ b/tools/recovery_l10n/res/values-ko/strings.xml @@ -6,4 +6,9 @@ "명령어 없음" "오류!" "보안 업데이트 설치 중" + "Android 시스템을 로드할 수 없습니다. 데이터가 손상되었을 수 있습니다. 이 메시지가 계속 표시되면 초기화를 실행하여 기기에 저장된 사용자 데이터를 모두 삭제해야 할 수도 있습니다." + "다시 시도" + "초기화" + "사용자 데이터를 모두 삭제하시겠습니까?\n\n 이 작업은 실행취소할 수 없습니다." + "취소" diff --git a/tools/recovery_l10n/res/values-ky/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml index 0a6bd783af..837cf7d68a 100644 --- a/tools/recovery_l10n/res/values-ky/strings.xml +++ b/tools/recovery_l10n/res/values-ky/strings.xml @@ -6,4 +6,9 @@ "Буйрук берилген жок" "Ката!" "Коопсуздук жаңыртуусу орнотулууда" + "Android тутуму жүктөлбөй жатат. Дайын-даректериңиз бузук болушу мүмкүн. Бул билдирүү дагы деле келе берсе, түзмөктү кайра башынан жөндөп, анда сакталган бардык колдонуучу дайындарын тазалашыңыз керек." + "Кайталоо" + "Кайра башынан жөндөө" + "Колдонуучу дайындарынын баары жашырылсынбы?\n\n МУНУ АРТКА КАЙТАРУУ МҮМКҮН ЭМЕС!" + "Жок" diff --git a/tools/recovery_l10n/res/values-lo/strings.xml b/tools/recovery_l10n/res/values-lo/strings.xml index d3dbb3970a..4a81427839 100644 --- a/tools/recovery_l10n/res/values-lo/strings.xml +++ b/tools/recovery_l10n/res/values-lo/strings.xml @@ -6,4 +6,9 @@ "ບໍ່ມີຄຳສັ່ງ" "ຜິດພາດ!" "ກຳລັງຕິດຕັ້ງອັບເດດຄວາມປອດໄພ" + "ບໍ່ສາມາດໂຫຼດລະບົບ Android ໄດ້. ຂໍ້ມູນຂອງທ່ານອາດເສຍຫາຍ. ຫາກທ່ານຍັງໄດ້ຮັບຂໍ້ຄວາມນີ້ຕໍ່ໄປ, ທ່ານອາດຕ້ອງຣີເຊັດເປັນຄ່າຈາກໂຮງງານ ແລະ ລຶບຂໍ້ມູນຜູ້ໃຊ້ທັງໝົດທີ່ຈັດເກັບໄວ້ຢູ່ອຸປະກອນນີ້ອອກ." + "ລອງໃໝ່" + "ຣີເຊັດຄ່າຈາກໂຮງງານ" + "ລຶບລ້າງຂໍ້ມູນຜູ້ໃຊ້ທັງໝົດບໍ?\n\n ຄຳສັ່ງນີ້ຈະບໍ່ສາມາດຍົກເລີກໄດ້!" + "ຍົກເລີກ" diff --git a/tools/recovery_l10n/res/values-lt/strings.xml b/tools/recovery_l10n/res/values-lt/strings.xml index d5d5e88fd1..f9b7d39177 100644 --- a/tools/recovery_l10n/res/values-lt/strings.xml +++ b/tools/recovery_l10n/res/values-lt/strings.xml @@ -6,4 +6,9 @@ "Nėra jokių komandų" "Klaida!" "Diegiamas saugos naujinys" + "Negalima įkelti „Android“ sistemos. Duomenys gali būti pažeisti. Jei ir toliau gausite šį pranešimą, jums gali reikėti atkurti gamyklinius duomenis ir ištrinti visus naudotojo duomenis, saugomus šiame įrenginyje." + "Bandyti dar kartą" + "Gamyklinių duomenų atkūrimas" + "Išvalyti visus naudotojo duomenis?\n\n ŠIO VEIKSMO NEGALIMA ANULIUOTI!" + "Atšaukti" diff --git a/tools/recovery_l10n/res/values-lv/strings.xml b/tools/recovery_l10n/res/values-lv/strings.xml index d877f6a614..6cf8ce30ef 100644 --- a/tools/recovery_l10n/res/values-lv/strings.xml +++ b/tools/recovery_l10n/res/values-lv/strings.xml @@ -6,4 +6,9 @@ "Nav nevienas komandas" "Kļūda!" "Notiek drošības atjauninājuma instalēšana" + "Nevar ielādēt Android sistēmu. Jūsu dati var būt bojāti. Ja šis ziņojums tiek rādīts atkārtoti, iespējams, jums ir jāveic rūpnīcas datu atiestatīšana un jādzēš visi šajā ierīcē saglabātie lietotāja dati." + "Mēģināt vēlreiz" + "Rūpnīcas datu atiestatīšana" + "Vai dzēst visus lietotāja datus?\n\n ŠO DARBĪBU NEVAR ATSAUKT!" + "Atcelt" diff --git a/tools/recovery_l10n/res/values-mk/strings.xml b/tools/recovery_l10n/res/values-mk/strings.xml index 3514597306..ff56131f90 100644 --- a/tools/recovery_l10n/res/values-mk/strings.xml +++ b/tools/recovery_l10n/res/values-mk/strings.xml @@ -6,4 +6,9 @@ "Нема наредба" "Грешка!" "Се инсталира безбедносно ажурирање" + "Не може да се вчита системот Android. Можно е податоците да се оштетени. Ако и понатаму ја примате поракава, можеби ќе треба да извршите ресетирање на фабрички податоци и да ги избришете сите кориснички податоци меморирани на уредов." + "Обиди се пак" + "Ресетирање на фабрички податоци" + "Да се избришат ли сите кориснички податоци?\n\n ОВА НЕ МОЖЕ ДА СЕ ВРАТИ!" + "Откажи" diff --git a/tools/recovery_l10n/res/values-ml/strings.xml b/tools/recovery_l10n/res/values-ml/strings.xml index b506e25303..2b331ac7e8 100644 --- a/tools/recovery_l10n/res/values-ml/strings.xml +++ b/tools/recovery_l10n/res/values-ml/strings.xml @@ -6,4 +6,9 @@ "കമാൻഡ് ഒന്നുമില്ല" "പിശക്!" "സുരക്ഷാ അപ്ഡേറ്റ് ഇൻസ്റ്റാൾ ചെയ്യുന്നു" + "Android സിസ്‌റ്റം ലോഡ് ചെയ്യാനാവില്ല. നിങ്ങളുടെ ഡാറ്റ കേടായിരിക്കാം. ഈ സന്ദേശം തുടർന്നും ലഭിക്കുകയാണെങ്കിൽ, നിങ്ങൾ ഒരു ഫാക്‌ടറി ഡാറ്റ പുനഃക്രമീകരണം നടത്തേണ്ടതുണ്ട് ഒപ്പം ഈ ഉപകരണത്തിൽ സ്‌റ്റോർ ചെയ്‌തിട്ടുള്ള എല്ലാ ഉപയോക്തൃ ഡാറ്റകളും മായ്‌ക്കേണ്ടതുണ്ട്." + "വീണ്ടും ശ്രമിക്കുക" + "ഫാക്‌ടറി ഡാറ്റ പുനഃക്രമീകരണം" + "എല്ലാ ഉപയോക്തൃ ഡാറ്റകളും മായ്‌ക്കണോ?\n\n ഇത് പഴയപടിയാക്കാനാവില്ല!" + "റദ്ദാക്കുക" diff --git a/tools/recovery_l10n/res/values-mn/strings.xml b/tools/recovery_l10n/res/values-mn/strings.xml index e3dd2e90ef..b0a57ed1a1 100644 --- a/tools/recovery_l10n/res/values-mn/strings.xml +++ b/tools/recovery_l10n/res/values-mn/strings.xml @@ -6,4 +6,9 @@ "Тушаал байхгүй" "Алдаа!" "Аюулгүй байдлын шинэчлэлтийг суулгаж байна" + "Андройд системийг ачаалах боломжгүй байна. Таны өгөгдөл эвдэрч болзошгүй. Хэрэв та энэ мессежийг үргэлжлүүлэн авах бол үйлдвэрээс гарсан төлөвийг ажиллуулж, энэ төхөөрөмжид хадгалсан хэрэглэгчийн бүх өгөгдлийг устгах шаардлагатай байж болзошгүй." + "Дахин оролдох" + "Үйлдвэрээс гарсан төлөвт" + "Хэрэглэгчийн бүх өгөгдлийг арчих уу?\n\n ҮҮНИЙГ БУЦААХ БОЛОМЖГҮЙ!" + "Цуцлах" diff --git a/tools/recovery_l10n/res/values-mr/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml index 5f820336fb..9b13707944 100644 --- a/tools/recovery_l10n/res/values-mr/strings.xml +++ b/tools/recovery_l10n/res/values-mr/strings.xml @@ -6,4 +6,9 @@ "कोणतीही कमांड नाही" "एरर!" "सुरक्षा अपडेट इंस्टॉल करत आहे" + "Android सिस्टम लोड करू शकत नाही. तुमचा डेटा धोक्यात असू शकतो.तुम्हाला हा मेसेज मिळत राहिल्यास, फॅक्टरी डेटा रीसेट करणे आणि या डिव्हाइसवर स्टोअर केलेला सर्व वापरकर्ता डेटा मिटवणे आवश्यक आहे." + "पुन्हा प्रयत्न करा" + "फॅक्‍टरी डेटा रीसेट" + "सर्व वापरकर्ता डेटा पुसून टाकायचा का?\n\n हे पहिल्‍यासारखे करू शकत नाही!" + "रद्द करा" diff --git a/tools/recovery_l10n/res/values-ms/strings.xml b/tools/recovery_l10n/res/values-ms/strings.xml index 0e24ac4e16..d094f547b4 100644 --- a/tools/recovery_l10n/res/values-ms/strings.xml +++ b/tools/recovery_l10n/res/values-ms/strings.xml @@ -6,4 +6,9 @@ "Tiada perintah" "Ralat!" "Memasang kemas kini keselamatan" + "Tidak dapat memuatkan sistem Android. Data anda mungkin rosak. Jika anda menerima mesej ini secara berterusan, anda mungkin perlu melaksanakan tetapan semula data kilang dan memadamkan semua data pengguna yang disimpan pada peranti ini." + "Cuba lagi" + "Tetapan semula data kilang" + "Lapkan semua data pengguna?\n\n TINDAKAN INI TIDAK BOLEH DIBUAT ASAL!" + "Batal" diff --git a/tools/recovery_l10n/res/values-my/strings.xml b/tools/recovery_l10n/res/values-my/strings.xml index f137524610..09cd4ea514 100644 --- a/tools/recovery_l10n/res/values-my/strings.xml +++ b/tools/recovery_l10n/res/values-my/strings.xml @@ -6,4 +6,9 @@ "ညွှန်ကြားချက်မပေးထားပါ" "မှားနေပါသည်!" "လုံခြုံရေး အပ်ဒိတ်ကို ထည့်သွင်းနေသည်" + "Android စနစ် ဖွင့်၍မရပါ။ သင့်ဒေတာများ ပျက်နေခြင်း ဖြစ်နိုင်သည်။ ဤမက်ဆေ့ဂျ် ဆက်လက်ရရှိနေလျှင် စက်ရုံထုတ်အခြေအနေပြန်ယူပြီး ဤစက်ပေါ်တွင် သိမ်းထားသော အသုံးပြုသူဒေတာအားလုံး ဖျက်ရန် လိုအပ်နိုင်သည်။" + "ထပ်စမ်းကြည့်ပါ" + "စက်ရုံထုတ်အခြေအနေပြန်ယူခြင်း" + "အသုံးပြုသူဒေတာ အားလုံးကို ရှင်းလင်းမလား။\n\n ၎င်းကို ပြန်ပြင်၍မရပါ။" + "မလုပ်တော့" diff --git a/tools/recovery_l10n/res/values-nb/strings.xml b/tools/recovery_l10n/res/values-nb/strings.xml index ad6f20e469..e8cad136cc 100644 --- a/tools/recovery_l10n/res/values-nb/strings.xml +++ b/tools/recovery_l10n/res/values-nb/strings.xml @@ -6,4 +6,9 @@ "Ingen kommandoer" "Feil!" "Installerer sikkerhetsoppdateringen" + "Kan ikke laste inn Android-systemet. Dataene dine er muligens skadet. Hvis du fortsetter å se denne meldingen, må du muligens tilbakestille til fabrikkstandard og tømme alle brukerdataene som er lagret på denne enheten." + "Prøv igjen" + "Tilbakestill til fabrikkstandard" + "Vil du viske ut alle brukerdataene?\n\n DETTE KAN IKKE ANGRES!" + "Avbryt" diff --git a/tools/recovery_l10n/res/values-ne/strings.xml b/tools/recovery_l10n/res/values-ne/strings.xml index 1880e807b0..fa53e9daec 100644 --- a/tools/recovery_l10n/res/values-ne/strings.xml +++ b/tools/recovery_l10n/res/values-ne/strings.xml @@ -6,4 +6,9 @@ "कुनै आदेश छैन" "त्रुटि!" "सुरक्षा सम्बन्धी अद्यावधिकलाई स्थापना गर्दै" + "Android प्रणाली लोड गर्न सकिएन। तपाईंको डेटा बिग्रेको हुन सक्छ। तपाईं यो सन्देश प्राप्त गर्नुहुन्छ भने तपाईंले फ्याक्ट्री डेटा रिसेट गर्न आवश्यक छ र यो यन्त्रमा भण्डारण गरेका सबै प्रयोगकर्ताको डेटा मेट्न पर्छ।" + "फेरि प्रयास गर्नुहोस्" + "फ्याक्ट्री डेटा रिसेट" + "प्रयोगकर्ताको सबै डेटा मेट्ने हो?\n\n यो अन्डू गर्न सकिँदैन!" + "रद्द गर्नुहोस्" diff --git a/tools/recovery_l10n/res/values-nl/strings.xml b/tools/recovery_l10n/res/values-nl/strings.xml index 0d6c15abb1..b42bb65827 100644 --- a/tools/recovery_l10n/res/values-nl/strings.xml +++ b/tools/recovery_l10n/res/values-nl/strings.xml @@ -6,4 +6,9 @@ "Geen opdracht" "Fout!" "Beveiligingsupdate installeren" + "Kan het Android-systeem niet laden. Je gegevens zijn mogelijk beschadigd. Als je dit bericht blijft ontvangen, moet je mogelijk de fabrieksinstellingen terugzetten en alle gebruikersgegevens wissen die op dit apparaat zijn opgeslagen." + "Opnieuw proberen" + "Terugzetten op fabrieksinstellingen" + "Alle gebruikersgegevens wissen?\n\n DIT KAN NIET ONGEDAAN WORDEN GEMAAKT." + "Annuleren" diff --git a/tools/recovery_l10n/res/values-or/strings.xml b/tools/recovery_l10n/res/values-or/strings.xml index 2b0851cdd3..25b28e65ae 100644 --- a/tools/recovery_l10n/res/values-or/strings.xml +++ b/tools/recovery_l10n/res/values-or/strings.xml @@ -6,4 +6,9 @@ "କୌଣସି କମାଣ୍ଡ ନାହିଁ" "ତ୍ରୁଟି!" "ସୁରକ୍ଷା ଅପ୍‌ଡେଟ୍‌ ଇନ୍‌ଷ୍ଟଲ୍‌ କରୁଛି" + "Android ସିଷ୍ଟମ୍‍ ଲୋଡ୍‍ କରାଯାଇପାରିବ ନାହିଁ। ଆପଣଙ୍କ ଡାଟା ହୁଏତ ତ୍ରୁଟି ରହିଥାଇ ପାରେ। ଯଦି ଆପଣ ଏହି ମେସେଜ୍‍ ପାଇବା ଜାରି ରଖନ୍ତି, ତେବେ ଆପଣଙ୍କୁ ଫ୍ୟାକ୍ଟେରୀ ଡାଟା ରିସେଟ୍‍ କରିବାକୁ ହେବ ଏବଂ ଏହି ଡିଭାଇସ୍‍‍ରେ ଷ୍ଟୋର୍‍ ହୋଇଥିବା ସମସ୍ତ ଡାଟା ଇରେଜ୍‍ କରନ୍ତୁ।" + "ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ" + "ଫ୍ୟାକ୍ଟୋରୀ ଡାଟା ରିସେଟ୍‌" + "ସମସ୍ଯ ଉପଯୋଗକର୍ତ୍ତା ଡାଟା ୱାଇପ୍‍ କରିବେ?\n\n ଏହା ଫେରାଇ ନିଆଯାଇପାରିବ ନାହିଁ!" + "ବାତିଲ୍‌ କରନ୍ତୁ" diff --git a/tools/recovery_l10n/res/values-pa/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml index 27972d1177..3743068053 100644 --- a/tools/recovery_l10n/res/values-pa/strings.xml +++ b/tools/recovery_l10n/res/values-pa/strings.xml @@ -6,4 +6,9 @@ "ਕੋਈ ਆਦੇਸ਼ ਨਹੀਂ" "ਅਸ਼ੁੱਧੀ!" "ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + "Android ਸਿਸਟਮ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਸ਼ਾਇਦ ਤੁਹਾਡਾ ਡਾਟਾ ਖਰਾਬ ਹੈ। ਜੇਕਰ ਤੁਹਾਨੂੰ ਇਹ ਸੁਨੇਹਾ ਪ੍ਰਾਪਤ ਹੋਣਾ ਜਾਰੀ ਰਹਿੰਦਾ ਹੈ, ਤਾਂ ਸ਼ਾਇਦ ਤੁਹਾਨੂੰ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰਨਾ ਪਵੇ ਅਤੇ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ ਵਰਤੋਂਕਾਰ ਡਾਟੇ ਨੂੰ ਮਿਟਾਉਣਾ ਪਵੇ।" + "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ" + "ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰੋ" + "ਕੀ ਸਾਰਾ ਵਰਤੋਂਕਾਰ ਡਾਟਾ ਸਾਫ਼ ਕਰਨਾ ਹੈ?\n\n ਇਸਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ!" + "ਰੱਦ ਕਰੋ" diff --git a/tools/recovery_l10n/res/values-pl/strings.xml b/tools/recovery_l10n/res/values-pl/strings.xml index 8d6db388de..48d3dbf6e6 100644 --- a/tools/recovery_l10n/res/values-pl/strings.xml +++ b/tools/recovery_l10n/res/values-pl/strings.xml @@ -6,4 +6,9 @@ "Brak polecenia" "Błąd" "Instaluję aktualizację zabezpieczeń" + "Nie można załadować systemu Android. Dane mogą być uszkodzone. Jeśli ten komunikat nadal będzie się pojawiać, może być konieczne przywrócenie danych fabrycznych urządzenia i usunięcie wszystkich zapisanych na nim danych użytkownika." + "Ponów próbę" + "Przywracanie danych fabrycznych" + "Wyczyścić wszystkie dane użytkownika?\n\n TEJ CZYNNOŚCI NIE MOŻNA COFNĄĆ." + "Anuluj" diff --git a/tools/recovery_l10n/res/values-pt-rBR/strings.xml b/tools/recovery_l10n/res/values-pt-rBR/strings.xml index b727043859..0df3edccb7 100644 --- a/tools/recovery_l10n/res/values-pt-rBR/strings.xml +++ b/tools/recovery_l10n/res/values-pt-rBR/strings.xml @@ -6,4 +6,9 @@ "Nenhum comando" "Erro!" "Instalando atualização de segurança" + "Não é possível carregar o sistema Android. Seus dados podem estar corrompidos. Se você continuar recebendo esta mensagem, talvez seja necessário realizar uma redefinição para a configuração original e limpar todos os dados do usuário armazenados neste dispositivo." + "Tentar novamente" + "Redefinição para configuração original" + "Limpar todos os dados do usuário?\n\n NÃO É POSSÍVEL DESFAZER ESSA AÇÃO." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-pt-rPT/strings.xml b/tools/recovery_l10n/res/values-pt-rPT/strings.xml index 9814637397..08eb3c953c 100644 --- a/tools/recovery_l10n/res/values-pt-rPT/strings.xml +++ b/tools/recovery_l10n/res/values-pt-rPT/strings.xml @@ -6,4 +6,9 @@ "Nenhum comando" "Erro!" "A instalar atualização de segurança" + "Não é possível carregar o sistema Android. Os seus dados podem estar danificados. Se continuar a receber esta mensagem, pode ter de efetuar uma reposição de dados de fábrica e apagar todos os dados do utilizador armazenados neste dispositivo." + "Tentar novamente" + "Reposição de dados de fábrica" + "Pretende limpar todos os dados do utilizador?\n\n NÃO É POSSÍVEL ANULAR ESTA AÇÃO." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-pt/strings.xml b/tools/recovery_l10n/res/values-pt/strings.xml index b727043859..0df3edccb7 100644 --- a/tools/recovery_l10n/res/values-pt/strings.xml +++ b/tools/recovery_l10n/res/values-pt/strings.xml @@ -6,4 +6,9 @@ "Nenhum comando" "Erro!" "Instalando atualização de segurança" + "Não é possível carregar o sistema Android. Seus dados podem estar corrompidos. Se você continuar recebendo esta mensagem, talvez seja necessário realizar uma redefinição para a configuração original e limpar todos os dados do usuário armazenados neste dispositivo." + "Tentar novamente" + "Redefinição para configuração original" + "Limpar todos os dados do usuário?\n\n NÃO É POSSÍVEL DESFAZER ESSA AÇÃO." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-ro/strings.xml b/tools/recovery_l10n/res/values-ro/strings.xml index 8032865b86..585db83550 100644 --- a/tools/recovery_l10n/res/values-ro/strings.xml +++ b/tools/recovery_l10n/res/values-ro/strings.xml @@ -6,4 +6,9 @@ "Nicio comandă" "Eroare!" "Se instalează actualizarea de securitate" + "Nu se poate încărca sistemul Android. Datele dvs. pot fi corupte. Dacă primiți în continuare acest mesaj, poate fi necesar să reveniți la setările din fabrică și să ștergeți toate datele utilizatorului stocate pe acest dispozitiv." + "Reîncercați" + "Revenire la setările din fabrică" + "Ștergeți toate datele utilizatorului?\n\n ACEST LUCRU NU POATE FI ANULAT!" + "Anulați" diff --git a/tools/recovery_l10n/res/values-ru/strings.xml b/tools/recovery_l10n/res/values-ru/strings.xml index feebecf314..db8b7611b8 100644 --- a/tools/recovery_l10n/res/values-ru/strings.xml +++ b/tools/recovery_l10n/res/values-ru/strings.xml @@ -6,4 +6,9 @@ "Команды нет" "Ошибка" "Установка обновления системы безопасности…" + "Не удалось загрузить систему Android. Возможно, данные повреждены. Если вы снова увидите это сообщение, попробуйте сбросить настройки устройства и удалить все пользовательские данные." + "Повторить попытку" + "Сбросить настройки" + "Стереть все пользовательские данные?\n\nЭТО ДЕЙСТВИЕ НЕЛЬЗЯ ОТМЕНИТЬ." + "Отмена" diff --git a/tools/recovery_l10n/res/values-si/strings.xml b/tools/recovery_l10n/res/values-si/strings.xml index 456cdc5672..67aca72f85 100644 --- a/tools/recovery_l10n/res/values-si/strings.xml +++ b/tools/recovery_l10n/res/values-si/strings.xml @@ -6,4 +6,9 @@ "විධානයක් නොමැත" "දෝෂය!" "ආරක්ෂක යාවත්කාලීනය ස්ථාපනය කරමින්" + "Android පද්ධතිය පූරණය කළ නොහැකිය. ඔබේ දත්ත දූෂිත විය හැකිය. ඔබට මෙම පණිවිඩය දිගටම ලැබෙන්නේ නම්, කර්මාන්ත ශාලා දත්ත යළි සැකසීමක් සිදු කර මෙම උපාංගයේ ගබඩා කළ සියලු පරිශීලක දත්ත මකා දැමීමට ඔබට අවශ්‍ය විය හැකිය." + "නැවත උත්සාහ කරන්න" + "කර්මාන්ත ශාලා දත්ත යළි සැකසීම" + "සියලු පරිශීලක දත්ත මකා දමන්නද?\n\n මෙය පසුගමනය කළ නොහැකිය!" + "අවලංගු කරන්න" diff --git a/tools/recovery_l10n/res/values-sk/strings.xml b/tools/recovery_l10n/res/values-sk/strings.xml index b15f3802b5..8a2d2e0c1b 100644 --- a/tools/recovery_l10n/res/values-sk/strings.xml +++ b/tools/recovery_l10n/res/values-sk/strings.xml @@ -6,4 +6,9 @@ "Žiadny príkaz" "Chyba!" "Inštaluje sa bezpečnostná aktualizácia" + "Systém Android sa nedá načítať. Vaše údaje môžu byť poškodené. Ak chcete získať túto správu a budete pokračovať, zrejme budete musieť obnoviť výrobné nastavenia a vymazať tak všetky údaje používateľa uložené v tomto zariadení." + "Skúsiť znova" + "Obnovenie výrobných nastavení" + "Chcete vymazať všetky údaje používateľa?\n\n TÁTO AKCIA SA NEDÁ VRÁTIŤ SPÄŤ!" + "Zrušiť" diff --git a/tools/recovery_l10n/res/values-sl/strings.xml b/tools/recovery_l10n/res/values-sl/strings.xml index d608b7506d..653c4274e8 100644 --- a/tools/recovery_l10n/res/values-sl/strings.xml +++ b/tools/recovery_l10n/res/values-sl/strings.xml @@ -6,4 +6,9 @@ "Ni ukaza" "Napaka" "Nameščanje varnostne posodobitve" + "Sistema Android ni mogoče naložiti. Podatki so morda poškodovani. Če se bo to sporočilo še naprej prikazovalo, boste morda morali izvesti ponastavitev na tovarniške nastavitve in izbrisati vse uporabniške podatke, ki so shranjeni v tej napravi." + "Poskusi znova" + "Ponastavitev na tovarniške nastavitve" + "Želite izbrisati vse uporabniške podatke?\n\n TEGA NI MOGOČE RAZVELJAVITI!" + "Prekliči" diff --git a/tools/recovery_l10n/res/values-sq/strings.xml b/tools/recovery_l10n/res/values-sq/strings.xml index 1156931fb3..5c824e6833 100644 --- a/tools/recovery_l10n/res/values-sq/strings.xml +++ b/tools/recovery_l10n/res/values-sq/strings.xml @@ -6,4 +6,9 @@ "Nuk ka komanda" "Gabim!" "Po instalon përditësimin e sigurisë" + "Sistemi Android nuk mund të ngarkohet. Të dhënat e tua mund të jenë të dëmtuara. Nëse vazhdon të marrësh këtë mesazh, mund të jetë e nevojshme të kryesh një rivendosje të të dhënave të fabrikës dhe të spastrosh të gjitha të dhënat e përdoruesit të ruajtura në këtë pajisje." + "Provo përsëri" + "Rivendosja e të dhënave të fabrikës" + "Të pastrohen të gjitha të dhënat e përdoruesit?\n\n KJO NUK MUND TË ZHBËHET!" + "Anulo" diff --git a/tools/recovery_l10n/res/values-sr/strings.xml b/tools/recovery_l10n/res/values-sr/strings.xml index a593d8faa5..1583beaaf2 100644 --- a/tools/recovery_l10n/res/values-sr/strings.xml +++ b/tools/recovery_l10n/res/values-sr/strings.xml @@ -6,4 +6,9 @@ "Нема команде" "Грешка!" "Инсталира се безбедносно ажурирање" + "Учитавање Android система није успело. Подаци су можда оштећени. Ако наставите да добијате ову поруку, можда ћете морати да ресетујете уређај на фабричка подешавања и обришете све податке корисника које чувате на њему." + "Пробај поново" + "Ресетовање на фабричка подешавања" + "Желите ли да избришете све податке корисника?\n\n ОВО НЕ МОЖЕ ДА СЕ ОПОЗОВЕ!" + "Откажи" diff --git a/tools/recovery_l10n/res/values-sv/strings.xml b/tools/recovery_l10n/res/values-sv/strings.xml index b33ce253f4..cf43b2511c 100644 --- a/tools/recovery_l10n/res/values-sv/strings.xml +++ b/tools/recovery_l10n/res/values-sv/strings.xml @@ -6,4 +6,9 @@ "Inget kommando" "Fel!" "Säkerhetsuppdatering installeras" + "Det gick inte att läsa in Android-systemet. Data kan ha skadats. Om det här meddelandet visas igen kan du behöva återställa standardinställningarna så att all användardata som sparats på enheten raderas." + "Försök igen" + "Återställ standardinställningarna" + "Vill du rensa bort all användardata?\n\n DET GÅR INTE ATT ÅNGRA DENNA ÅTGÄRD." + "Avbryt" diff --git a/tools/recovery_l10n/res/values-sw/strings.xml b/tools/recovery_l10n/res/values-sw/strings.xml index 1567658812..6fa72825a2 100644 --- a/tools/recovery_l10n/res/values-sw/strings.xml +++ b/tools/recovery_l10n/res/values-sw/strings.xml @@ -6,4 +6,9 @@ "Hakuna amri" "Hitilafu fulani imetokea!" "Inasakinisha sasisho la usalama" + "Imeshindwa kupakia mfumo wa Android. Huenda data yako imeharibika. Kama utandelea kupata ujumbe huu, huenda ukahitaji kurejesha data iliyotoka nayo kiwandani na ufute data yote ya mtumiaji iliyohifadhiwa kwenye kifaa hiki." + "Jaribu tena" + "Kurejesha data iliyotoka nayo kiwandani" + "Ungependa kufuta data yote ya mtumiaji?\n\n KITENDO HIKI HAKIWEZI KUTENDULIWA!" + "Ghairi" diff --git a/tools/recovery_l10n/res/values-ta/strings.xml b/tools/recovery_l10n/res/values-ta/strings.xml index d49186d8d8..bc370f7bf4 100644 --- a/tools/recovery_l10n/res/values-ta/strings.xml +++ b/tools/recovery_l10n/res/values-ta/strings.xml @@ -6,4 +6,9 @@ "கட்டளை இல்லை" "பிழை!" "பாதுகாப்புப் புதுப்பிப்பை நிறுவுகிறது" + "Android சிஸ்டத்தைக் காண்பிக்க இயலவில்லை. உங்களின் தரவு சிதைந்திருக்கலாம். இந்த மெசேஜ் உங்களுக்குத் தொடர்ந்து வந்தால், தரவின் ஆரம்பநிலைக்கு மீட்டமைத்தல் மற்றும் இந்தச் சாதனத்தில் சேமிக்கப்பட்டுள்ள அனைத்துப் பயனர் தரவையும் அழித்தல் ஆகியவற்றைச் செய்ய வேண்டியிருக்கலாம்." + "மீண்டும் முயல்க" + "தரவின் ஆரம்பநிலை மீட்டமைப்பு" + "பயனரின் அனைத்துத் தரவையும் நீக்கவா?\n\n இதைச் செயல்தவிர்க்க இயலாது!" + "இல்லை" diff --git a/tools/recovery_l10n/res/values-te/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml index e35c82bc4f..4d521143f2 100644 --- a/tools/recovery_l10n/res/values-te/strings.xml +++ b/tools/recovery_l10n/res/values-te/strings.xml @@ -6,4 +6,9 @@ "ఆదేశం లేదు" "ఎర్రర్ సంభవించింది!" "భద్రతా నవీకరణను ఇన్‌స్టాల్ చేస్తోంది" + "Android సిస్టమ్‌ని లోడ్ చేయడం సాధ్యం కాదు. మీ డేటా పాడై ఉండవచ్చు. మీకు ఈ సందేశం వస్తూనే ఉంటే, మీరు ఫ్యాక్టరీ డేటా రీసెట్ చేసి, పరికరంలో నిల్వ అయిన వినియోగదారు డేటా మొత్తాన్ని తొలగించాల్సి రావచ్చు." + "మళ్లీ ప్రయత్నించు" + "ఫ్యాక్టరీ డేటా రీసెట్" + "వినియోగదారు డేటా మొత్తాన్ని తొలగించాలా?\n\n ఈ చర్యను రద్దు చేయలేరు!" + "రద్దు చేయి" diff --git a/tools/recovery_l10n/res/values-th/strings.xml b/tools/recovery_l10n/res/values-th/strings.xml index 155affea08..83d445dfe2 100644 --- a/tools/recovery_l10n/res/values-th/strings.xml +++ b/tools/recovery_l10n/res/values-th/strings.xml @@ -6,4 +6,9 @@ "ไม่มีคำสั่ง" "ข้อผิดพลาด!" "กำลังติดตั้งการอัปเดตความปลอดภัย" + "โหลดระบบ Android ไม่ได้ ข้อมูลของคุณอาจเสียหาย หากคุณยังคงได้รับข้อความนี้อยู่ คุณอาจต้องรีเซ็ตข้อมูลเป็นค่าเริ่มต้นและลบข้อมูลผู้ใช้ทั้งหมดที่เก็บอยู่ในอุปกรณ์นี้" + "ลองอีกครั้ง" + "รีเซ็ตข้อมูลเป็นค่าเริ่มต้น" + "ต้องการล้างข้อมูลผู้ใช้ทั้งหมดใช่ไหม\n\n การกระทำนี้จะยกเลิกไม่ได้" + "ยกเลิก" diff --git a/tools/recovery_l10n/res/values-tl/strings.xml b/tools/recovery_l10n/res/values-tl/strings.xml index 555b42b8dc..6621473fd7 100644 --- a/tools/recovery_l10n/res/values-tl/strings.xml +++ b/tools/recovery_l10n/res/values-tl/strings.xml @@ -6,4 +6,9 @@ "Walang command" "Error!" "Nag-i-install ng update sa seguridad" + "Hindi ma-load ang Android system. Maaaring sira ang iyong data. Kung patuloy mong matatanggap ang mensaheng ito, maaaring kailanganin mong magsagawa ng pag-reset ng factory data at burahin ang lahat ng data ng user na naka-store sa device na ito." + "Subukang muli" + "Pag-reset ng factory data" + "I-wipe ang lahat ng data ng user?\n\n HINDI ITO MAA-UNDO!" + "Kanselahin" diff --git a/tools/recovery_l10n/res/values-tr/strings.xml b/tools/recovery_l10n/res/values-tr/strings.xml index 5387cb2ae8..e4eca52d83 100644 --- a/tools/recovery_l10n/res/values-tr/strings.xml +++ b/tools/recovery_l10n/res/values-tr/strings.xml @@ -6,4 +6,9 @@ "Komut yok" "Hata!" "Güvenlik güncellemesi yükleniyor" + "Android sisteminiz yüklenemedi. Verileriniz bozulmuş olabilir. Bu mesajı almaya devam ederseniz fabrika verilerine sıfırlama işlemi yapmanız ve bu cihazda depolanan tüm kullanıcı verilerini silmeniz gerekebilir." + "Tekrar dene" + "Fabrika verilerine sıfırla" + "Tüm kullanıcı verileri silinsin mi?\n\n BU İŞLEM GERİ ALINAMAZ!" + "İptal" diff --git a/tools/recovery_l10n/res/values-uk/strings.xml b/tools/recovery_l10n/res/values-uk/strings.xml index 0c2fa164a9..7bd6fecf02 100644 --- a/tools/recovery_l10n/res/values-uk/strings.xml +++ b/tools/recovery_l10n/res/values-uk/strings.xml @@ -6,4 +6,9 @@ "Немає команди" "Помилка!" "Установлюється оновлення системи безпеки" + "Не вдається завантажити систему Android. Можливо, ваші дані пошкоджено. Якщо ви далі отримуватимете це повідомлення, можливо, доведеться відновити заводські налаштування й видалити всі дані користувача з цього пристрою." + "Повторити" + "Відновити заводські налаштування" + "Видалити всі дані користувача?\n\n ЦЮ ДІЮ НЕ МОЖНА ВІДМІНИТИ." + "Скасувати" diff --git a/tools/recovery_l10n/res/values-ur/strings.xml b/tools/recovery_l10n/res/values-ur/strings.xml index 12e32fbc14..13dc6b37d8 100644 --- a/tools/recovery_l10n/res/values-ur/strings.xml +++ b/tools/recovery_l10n/res/values-ur/strings.xml @@ -6,4 +6,9 @@ "کوئی کمانڈ نہیں ہے" "خرابی!" "سیکیورٹی اپ ڈیٹ انسٹال ہو رہی ہے" + "‏Android سسٹم لوڈ نہیں کیا جا سکتا۔ آپ کا ڈیٹا خراب ہو سکتا ہے۔ اگر آپ کو مستقل یہ پیغام موصول ہوتا ہے تو آپ کو فیکٹری ڈیٹا ری سیٹ انجام دینے اور اس آلہ پر اسٹور کردہ سبھی صارف ڈیٹا کو مٹانے کی ضرورت پڑ سکتی ہے۔" + "دوبارہ کوشش کریں" + "فیکٹری ڈیٹا ری سیٹ" + "سبھی صارف ڈیٹا صاف کریں؟\n\n اسے کالعدم نہیں کیا جا سکتا!" + "منسوخ کریں" diff --git a/tools/recovery_l10n/res/values-uz/strings.xml b/tools/recovery_l10n/res/values-uz/strings.xml index 2c309d646c..9bde4c6bf5 100644 --- a/tools/recovery_l10n/res/values-uz/strings.xml +++ b/tools/recovery_l10n/res/values-uz/strings.xml @@ -6,4 +6,9 @@ "Buyruq yo‘q" "Xato!" "Xavfsizlik yangilanishi o‘rnatilmoqda" + "Android tizimi yuklanmadi. Maʼlumotlaringiz buzuq shekilli. Yana shu xabarni olsangiz, zavod sozlamalarini tiklashingiz va bu qurilmadagi barcha maʼlumotlarni tozalab tashlashingiz lozim." + "Qayta urinish" + "Zavod sozlamalarini tiklash" + "Barcha maʼlumotlar tozalab tashlansinmi?\n\n ULARNI TIKLASH IMKONSIZ!" + "Bekor qilish" diff --git a/tools/recovery_l10n/res/values-vi/strings.xml b/tools/recovery_l10n/res/values-vi/strings.xml index c77d0c8c2d..3753394e6f 100644 --- a/tools/recovery_l10n/res/values-vi/strings.xml +++ b/tools/recovery_l10n/res/values-vi/strings.xml @@ -6,4 +6,9 @@ "Không có lệnh nào" "Lỗi!" "Đang cài đặt bản cập nhật bảo mật" + "Không thể tải hệ thống Android. Dữ liệu của bạn có thể bị hỏng. Nếu tiếp tục thấy thông báo này, bạn có thể cần phải thiết lập lại dữ liệu ban đầu và xóa tất cả dữ liệu người dùng lưu trữ trên thiết bị này." + "Thử lại" + "Thiết lập lại dữ liệu ban đầu" + "Xóa sạch tất cả dữ liệu người dùng?\n\n KHÔNG THỂ HOÀN TÁC THAO TÁC NÀY!" + "Hủy" diff --git a/tools/recovery_l10n/res/values-zh-rCN/strings.xml b/tools/recovery_l10n/res/values-zh-rCN/strings.xml index e06149791d..ab1fdbbc28 100644 --- a/tools/recovery_l10n/res/values-zh-rCN/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rCN/strings.xml @@ -6,4 +6,9 @@ "无命令" "出错了!" "正在安装安全更新" + "无法加载 Android 系统。您的数据可能已损坏。如果系统仍然显示这条消息,您可能需要恢复出厂设置,并清空存储在此设备上的所有用户数据。" + "重试" + "恢复出厂设置" + "是否清除所有用户数据?\n\n此操作无法撤消!" + "取消" diff --git a/tools/recovery_l10n/res/values-zh-rHK/strings.xml b/tools/recovery_l10n/res/values-zh-rHK/strings.xml index ec3315d32f..55ce31e933 100644 --- a/tools/recovery_l10n/res/values-zh-rHK/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rHK/strings.xml @@ -6,4 +6,9 @@ "沒有指令" "錯誤!" "正在安裝安全性更新" + "無法載入 Android 系統。您的資料可能已損壞。如您繼續收到此訊息,則可能需要將裝置回復原廠設定,並清除儲存在裝置上的所有使用者資料。" + "再試一次" + "回復原廠設定" + "要清除所有使用者資料嗎?\n\n這項操作無法復原!" + "取消" diff --git a/tools/recovery_l10n/res/values-zh-rTW/strings.xml b/tools/recovery_l10n/res/values-zh-rTW/strings.xml index 78eae24294..0a777a6e3f 100644 --- a/tools/recovery_l10n/res/values-zh-rTW/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rTW/strings.xml @@ -6,4 +6,9 @@ "沒有指令" "錯誤!" "正在安裝安全性更新" + "無法載入 Android 系統。你的資料可能已經損毀。如果系統持續顯示這則訊息,你可能必須恢復原廠設定,並清除裝置上儲存的所有使用者資料。" + "再試一次" + "恢復原廠設定" + "要抹除所有使用者資料嗎?\n\n請注意,一旦抹除就無法復原!" + "取消" diff --git a/tools/recovery_l10n/res/values-zu/strings.xml b/tools/recovery_l10n/res/values-zu/strings.xml index 6b815e1ab0..4667dac404 100644 --- a/tools/recovery_l10n/res/values-zu/strings.xml +++ b/tools/recovery_l10n/res/values-zu/strings.xml @@ -6,4 +6,9 @@ "Awukho umyalo" "Iphutha!" "Ifaka isibuyekezo sokuphepha" + "Ayikwazi ukulayisha isistimu ye-Android. Idatha yakho kungenzeka yonakele. Uma uqhubeka ukuthola lo mlayezo, kungenzeka kumele wenze ukusethwa kabusha kwasekuqaleni kwedatha uphinde usule yonke idatha yomsebenzisi egcinwe kule divayisi." + "Zama futhi" + "Ukuhlela kabusha idatha yasembonini" + "Sula yonke idatha yomsebenzisi?\n\n LOKHU AKUKWAZI UKUHLEHLISWA!" + "Khansela" diff --git a/tools/recovery_l10n/res/values/strings.xml b/tools/recovery_l10n/res/values/strings.xml index d56d0733c5..a557ba8afb 100644 --- a/tools/recovery_l10n/res/values/strings.xml +++ b/tools/recovery_l10n/res/values/strings.xml @@ -36,4 +36,36 @@ system is installing a security update. [CHAR LIMIT=60] --> Installing security update + + Cannot load Android + system. Your data may be corrupt. If you continue to get this + message, you may need to perform a factory data reset and erase + all user data stored on this device. + + + Try again + + + Factory data reset + + + Wipe all user data?\n\n + THIS CAN NOT BE UNDONE! + + + Cancel + diff --git a/uncrypt/Android.bp b/uncrypt/Android.bp index aa56d2f742..107a7f0fc6 100644 --- a/uncrypt/Android.bp +++ b/uncrypt/Android.bp @@ -24,13 +24,15 @@ cc_binary { "-Werror", ], - static_libs: [ - "libbootloader_message", - "libotautil", - "libfs_mgr", + shared_libs: [ "libbase", + "libbootloader_message", "libcutils", - "liblog", + "libfs_mgr", + ], + + static_libs: [ + "libotautil", ], init_rc: [ diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp index bb43c2c4ae..f1f4f69f0f 100644 --- a/uncrypt/uncrypt.cpp +++ b/uncrypt/uncrypt.cpp @@ -89,7 +89,6 @@ #include #include #include -#include #include #include #include @@ -103,6 +102,7 @@ #include #include +#include #include #include @@ -115,9 +115,13 @@ #include #include #include +#include #include "otautil/error_code.h" +using android::fs_mgr::Fstab; +using android::fs_mgr::ReadDefaultFstab; + static constexpr int WINDOW_SIZE = 5; static constexpr int FIBMAP_RETRY_LIMIT = 3; @@ -136,7 +140,7 @@ static const std::string UNCRYPT_PATH_FILE = "/cache/recovery/uncrypt_file"; static const std::string UNCRYPT_STATUS = "/cache/recovery/uncrypt_status"; static const std::string UNCRYPT_SOCKET = "uncrypt"; -static struct fstab* fstab = nullptr; +static Fstab fstab; static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) { if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) { @@ -162,47 +166,34 @@ static void add_block_to_ranges(std::vector& ranges, int new_block) { } } -static struct fstab* read_fstab() { - fstab = fs_mgr_read_fstab_default(); - if (!fstab) { - LOG(ERROR) << "failed to read default fstab"; - return NULL; - } - - return fstab; -} - -static const char* find_block_device(const char* path, bool* encryptable, bool* encrypted, bool *f2fs_fs) { - // Look for a volume whose mount point is the prefix of path and - // return its block device. Set encrypted if it's currently - // encrypted. - - // ensure f2fs_fs is set to 0 first. - if (f2fs_fs) - *f2fs_fs = false; - for (int i = 0; i < fstab->num_entries; ++i) { - struct fstab_rec* v = &fstab->recs[i]; - if (!v->mount_point) { - continue; - } - int len = strlen(v->mount_point); - if (strncmp(path, v->mount_point, len) == 0 && - (path[len] == '/' || path[len] == 0)) { - *encrypted = false; - *encryptable = false; - if (fs_mgr_is_encryptable(v) || fs_mgr_is_file_encrypted(v)) { - *encryptable = true; - if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") { - *encrypted = true; - } - } - if (f2fs_fs && strcmp(v->fs_type, "f2fs") == 0) - *f2fs_fs = true; - return v->blk_device; +// Looks for a volume whose mount point is the prefix of path and returns its block device or an +// empty string. Sets encryption flags accordingly. +static std::string FindBlockDevice(const std::string& path, bool* encryptable, bool* encrypted, + bool* f2fs_fs) { + // Ensure f2fs_fs is set to false first. + *f2fs_fs = false; + + for (const auto& entry : fstab) { + if (entry.mount_point.empty()) { + continue; + } + if (android::base::StartsWith(path, entry.mount_point + "/")) { + *encrypted = false; + *encryptable = false; + if (entry.is_encryptable() || entry.fs_mgr_flags.file_encryption) { + *encryptable = true; + if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") { + *encrypted = true; } + } + if (entry.fs_type == "f2fs") { + *f2fs_fs = true; + } + return entry.blk_device; } + } - return NULL; + return ""; } static bool write_status_to_socket(int status, int socket) { @@ -215,113 +206,127 @@ static bool write_status_to_socket(int status, int socket) { return android::base::WriteFully(socket, &status_out, sizeof(int)); } -// Parse uncrypt_file to find the update package name. -static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::string* package_name) { - CHECK(package_name != nullptr); - std::string uncrypt_path; - if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) { - PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\""; - return false; - } - - // Remove the trailing '\n' if present. - *package_name = android::base::Trim(uncrypt_path); - return true; -} - -static int retry_fibmap(const int fd, const char* name, int* block, const int head_block) { - CHECK(block != nullptr); - for (size_t i = 0; i < FIBMAP_RETRY_LIMIT; i++) { - if (fsync(fd) == -1) { - PLOG(ERROR) << "failed to fsync \"" << name << "\""; - return kUncryptFileSyncError; - } - if (ioctl(fd, FIBMAP, block) != 0) { - PLOG(ERROR) << "failed to find block " << head_block; - return kUncryptIoctlError; - } - if (*block != 0) { - return kUncryptNoError; - } - sleep(1); - } - LOG(ERROR) << "fibmap of " << head_block << "always returns 0"; - return kUncryptIoctlError; +// Parses the given path file to find the update package name. +static bool FindUncryptPackage(const std::string& uncrypt_path_file, std::string* package_name) { + CHECK(package_name != nullptr); + std::string uncrypt_path; + if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) { + PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\""; + return false; + } + + // Remove the trailing '\n' if present. + *package_name = android::base::Trim(uncrypt_path); + return true; } -static int produce_block_map(const char* path, const char* map_file, const char* blk_dev, - bool encrypted, bool f2fs_fs, int socket) { - std::string err; - if (!android::base::RemoveFileIfExists(map_file, &err)) { - LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err; - return kUncryptFileRemoveError; - } - std::string tmp_map_file = std::string(map_file) + ".tmp"; - android::base::unique_fd mapfd(open(tmp_map_file.c_str(), - O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)); - if (mapfd == -1) { - PLOG(ERROR) << "failed to open " << tmp_map_file; - return kUncryptFileOpenError; +static int RetryFibmap(int fd, const std::string& name, int* block, const int head_block) { + CHECK(block != nullptr); + for (size_t i = 0; i < FIBMAP_RETRY_LIMIT; i++) { + if (fsync(fd) == -1) { + PLOG(ERROR) << "failed to fsync \"" << name << "\""; + return kUncryptFileSyncError; } - - // Make sure we can write to the socket. - if (!write_status_to_socket(0, socket)) { - LOG(ERROR) << "failed to write to socket " << socket; - return kUncryptSocketWriteError; + if (ioctl(fd, FIBMAP, block) != 0) { + PLOG(ERROR) << "failed to find block " << head_block; + return kUncryptIoctlError; } - - struct stat sb; - if (stat(path, &sb) != 0) { - LOG(ERROR) << "failed to stat " << path; - return kUncryptFileStatError; - } - - LOG(INFO) << " block size: " << sb.st_blksize << " bytes"; - - int blocks = ((sb.st_size-1) / sb.st_blksize) + 1; - LOG(INFO) << " file size: " << sb.st_size << " bytes, " << blocks << " blocks"; - - std::vector ranges; - - std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n", - blk_dev, static_cast(sb.st_size), - static_cast(sb.st_blksize)); - if (!android::base::WriteStringToFd(s, mapfd)) { - PLOG(ERROR) << "failed to write " << tmp_map_file; - return kUncryptWriteError; - } - - std::vector> buffers; - if (encrypted) { - buffers.resize(WINDOW_SIZE, std::vector(sb.st_blksize)); - } - int head_block = 0; - int head = 0, tail = 0; - - android::base::unique_fd fd(open(path, O_RDONLY)); - if (fd == -1) { - PLOG(ERROR) << "failed to open " << path << " for reading"; - return kUncryptFileOpenError; - } - - android::base::unique_fd wfd; - if (encrypted) { - wfd.reset(open(blk_dev, O_WRONLY)); - if (wfd == -1) { - PLOG(ERROR) << "failed to open " << blk_dev << " for writing"; - return kUncryptBlockOpenError; - } + if (*block != 0) { + return kUncryptNoError; } + sleep(1); + } + LOG(ERROR) << "fibmap of " << head_block << " always returns 0"; + return kUncryptIoctlError; +} -#ifndef F2FS_IOC_SET_DONTMOVE +static int ProductBlockMap(const std::string& path, const std::string& map_file, + const std::string& blk_dev, bool encrypted, bool f2fs_fs, int socket) { + std::string err; + if (!android::base::RemoveFileIfExists(map_file, &err)) { + LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err; + return kUncryptFileRemoveError; + } + std::string tmp_map_file = map_file + ".tmp"; + android::base::unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)); + if (mapfd == -1) { + PLOG(ERROR) << "failed to open " << tmp_map_file; + return kUncryptFileOpenError; + } + + // Make sure we can write to the socket. + if (!write_status_to_socket(0, socket)) { + LOG(ERROR) << "failed to write to socket " << socket; + return kUncryptSocketWriteError; + } + + struct stat sb; + if (stat(path.c_str(), &sb) != 0) { + PLOG(ERROR) << "failed to stat " << path; + return kUncryptFileStatError; + } + + LOG(INFO) << " block size: " << sb.st_blksize << " bytes"; + + int blocks = ((sb.st_size - 1) / sb.st_blksize) + 1; + LOG(INFO) << " file size: " << sb.st_size << " bytes, " << blocks << " blocks"; + + std::vector ranges; + + std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n", blk_dev.c_str(), + static_cast(sb.st_size), + static_cast(sb.st_blksize)); + if (!android::base::WriteStringToFd(s, mapfd)) { + PLOG(ERROR) << "failed to write " << tmp_map_file; + return kUncryptWriteError; + } + + std::vector> buffers; + if (encrypted) { + buffers.resize(WINDOW_SIZE, std::vector(sb.st_blksize)); + } + int head_block = 0; + int head = 0, tail = 0; + + android::base::unique_fd fd(open(path.c_str(), O_RDWR)); + if (fd == -1) { + PLOG(ERROR) << "failed to open " << path << " for reading"; + return kUncryptFileOpenError; + } + + android::base::unique_fd wfd; + if (encrypted) { + wfd.reset(open(blk_dev.c_str(), O_WRONLY)); + if (wfd == -1) { + PLOG(ERROR) << "failed to open " << blk_dev << " for writing"; + return kUncryptBlockOpenError; + } + } + +// F2FS-specific ioctl +// It requires the below kernel commit merged in v4.16-rc1. +// 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file") +// In android-4.4, +// 56ee1e817908 ("f2fs: updates on v4.16-rc1") +// In android-4.9, +// 2f17e34672a8 ("f2fs: updates on v4.16-rc1") +// In android-4.14, +// ce767d9a55bc ("f2fs: updates on v4.16-rc1") +#ifndef F2FS_IOC_SET_PIN_FILE #ifndef F2FS_IOCTL_MAGIC #define F2FS_IOCTL_MAGIC 0xf5 #endif -#define F2FS_IOC_SET_DONTMOVE _IO(F2FS_IOCTL_MAGIC, 13) +#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32) +#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32) #endif - if (f2fs_fs && ioctl(fd, F2FS_IOC_SET_DONTMOVE) < 0) { - PLOG(ERROR) << "Failed to set non-movable file for f2fs: " << path << " on " << blk_dev; - return kUncryptIoctlError; + if (f2fs_fs) { + __u32 set = 1; + int error = ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set); + // Don't break the old kernels which don't support it. + if (error && errno != ENOTTY && errno != ENOTSUP) { + PLOG(ERROR) << "Failed to set pin_file for f2fs: " << path << " on " << blk_dev; + return kUncryptIoctlError; + } } off64_t pos = 0; @@ -344,7 +349,7 @@ static int produce_block_map(const char* path, const char* map_file, const char* if (block == 0) { LOG(ERROR) << "failed to find block " << head_block << ", retrying"; - int error = retry_fibmap(fd, path, &block, head_block); + int error = RetryFibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error; } @@ -389,7 +394,7 @@ static int produce_block_map(const char* path, const char* map_file, const char* if (block == 0) { LOG(ERROR) << "failed to find block " << head_block << ", retrying"; - int error = retry_fibmap(fd, path, &block, head_block); + int error = RetryFibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error; } @@ -439,13 +444,12 @@ static int produce_block_map(const char* path, const char* map_file, const char* } } - if (rename(tmp_map_file.c_str(), map_file) == -1) { - PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file; - return kUncryptFileRenameError; + if (rename(tmp_map_file.c_str(), map_file.c_str()) == -1) { + PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file; + return kUncryptFileRenameError; } // Sync dir to make rename() result written to disk. - std::string file_name = map_file; - std::string dir_name = dirname(&file_name[0]); + std::string dir_name = android::base::Dirname(map_file); android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY)); if (dfd == -1) { PLOG(ERROR) << "failed to open dir " << dir_name; @@ -462,45 +466,42 @@ static int produce_block_map(const char* path, const char* map_file, const char* return 0; } -static int uncrypt(const char* input_path, const char* map_file, const int socket) { - LOG(INFO) << "update package is \"" << input_path << "\""; - - // Turn the name of the file we're supposed to convert into an absolute path, so we can find - // what filesystem it's on. - char path[PATH_MAX+1]; - if (realpath(input_path, path) == nullptr) { - PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path"; - return kUncryptRealpathFindError; - } - - bool encryptable; - bool encrypted; - bool f2fs_fs; - const char* blk_dev = find_block_device(path, &encryptable, &encrypted, &f2fs_fs); - if (blk_dev == nullptr) { - LOG(ERROR) << "failed to find block device for " << path; - return kUncryptBlockDeviceFindError; - } - - // If the filesystem it's on isn't encrypted, we only produce the - // block map, we don't rewrite the file contents (it would be - // pointless to do so). - LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no"); - LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no"); - - // Recovery supports installing packages from 3 paths: /cache, - // /data, and /sdcard. (On a particular device, other locations - // may work, but those are three we actually expect.) - // - // On /data we want to convert the file to a block map so that we - // can read the package without mounting the partition. On /cache - // and /sdcard we leave the file alone. - if (strncmp(path, "/data/", 6) == 0) { - LOG(INFO) << "writing block map " << map_file; - return produce_block_map(path, map_file, blk_dev, encrypted, f2fs_fs, socket); - } - - return 0; +static int Uncrypt(const std::string& input_path, const std::string& map_file, int socket) { + LOG(INFO) << "update package is \"" << input_path << "\""; + + // Turn the name of the file we're supposed to convert into an absolute path, so we can find what + // filesystem it's on. + std::string path; + if (!android::base::Realpath(input_path, &path)) { + PLOG(ERROR) << "Failed to convert \"" << input_path << "\" to absolute path"; + return kUncryptRealpathFindError; + } + + bool encryptable; + bool encrypted; + bool f2fs_fs; + const std::string blk_dev = FindBlockDevice(path, &encryptable, &encrypted, &f2fs_fs); + if (blk_dev.empty()) { + LOG(ERROR) << "Failed to find block device for " << path; + return kUncryptBlockDeviceFindError; + } + + // If the filesystem it's on isn't encrypted, we only produce the block map, we don't rewrite the + // file contents (it would be pointless to do so). + LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no"); + LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no"); + + // Recovery supports installing packages from 3 paths: /cache, /data, and /sdcard. (On a + // particular device, other locations may work, but those are three we actually expect.) + // + // On /data we want to convert the file to a block map so that we can read the package without + // mounting the partition. On /cache and /sdcard we leave the file alone. + if (android::base::StartsWith(path, "/data/")) { + LOG(INFO) << "writing block map " << map_file; + return ProductBlockMap(path, map_file, blk_dev, encrypted, f2fs_fs, socket); + } + + return 0; } static void log_uncrypt_error_code(UncryptErrorCode error_code) { @@ -516,18 +517,18 @@ static bool uncrypt_wrapper(const char* input_path, const char* map_file, const std::string package; if (input_path == nullptr) { - if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) { - write_status_to_socket(-1, socket); - // Overwrite the error message. - log_uncrypt_error_code(kUncryptPackageMissingError); - return false; - } - input_path = package.c_str(); + if (!FindUncryptPackage(UNCRYPT_PATH_FILE, &package)) { + write_status_to_socket(-1, socket); + // Overwrite the error message. + log_uncrypt_error_code(kUncryptPackageMissingError); + return false; + } + input_path = package.c_str(); } CHECK(map_file != nullptr); auto start = std::chrono::system_clock::now(); - int status = uncrypt(input_path, map_file, socket); + int status = Uncrypt(input_path, map_file, socket); std::chrono::duration duration = std::chrono::system_clock::now() - start; int count = static_cast(duration.count()); @@ -637,7 +638,8 @@ int main(int argc, char** argv) { return 2; } - if ((fstab = read_fstab()) == nullptr) { + if (!ReadDefaultFstab(&fstab)) { + LOG(ERROR) << "failed to read default fstab"; log_uncrypt_error_code(kUncryptFstabReadError); return 1; } diff --git a/update_verifier/Android.bp b/update_verifier/Android.bp new file mode 100644 index 0000000000..f6567137ed --- /dev/null +++ b/update_verifier/Android.bp @@ -0,0 +1,123 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_defaults { + name: "update_verifier_defaults", + + defaults: [ + "recovery_defaults", + ], + + local_include_dirs: [ + "include", + ], +} + +cc_library_static { + name: "libupdate_verifier", + + defaults: [ + "update_verifier_defaults", + ], + + srcs: [ + "care_map.proto", + "update_verifier.cpp", + ], + + export_include_dirs: [ + "include", + ], + + static_libs: [ + "libotautil", + "libvold_binder", + ], + + shared_libs: [ + "android.hardware.boot@1.0", + "libbase", + "libcutils", + "libbinder", + "libutils", + ], + + proto: { + type: "lite", + export_proto_headers: true, + }, +} + +cc_binary { + name: "update_verifier", + + defaults: [ + "update_verifier_defaults", + ], + + srcs: [ + "update_verifier_main.cpp", + ], + + static_libs: [ + "libupdate_verifier", + "libotautil", + "libvold_binder", + ], + + shared_libs: [ + "android.hardware.boot@1.0", + "libbase", + "libcutils", + "libhardware", + "libhidlbase", + "liblog", + "libprotobuf-cpp-lite", + "libbinder", + "libutils", + ], + + init_rc: [ + "update_verifier.rc", + ], +} + +python_binary_host { + name: "care_map_generator", + + srcs: [ + "care_map_generator.py", + "care_map.proto", + ], + libs: [ + "python-symbol", + // Soong won't add "libprotobuf-python" to the dependencies if + // filegroup contains .proto files. So add it here explicitly. + "libprotobuf-python", + ], + proto: { + canonical_path_from_root: false, + }, + + version: { + py2: { + enabled: true, + embedded_launcher: true, + }, + py3: { + enabled: false, + embedded_launcher: false, + }, + }, +} diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk deleted file mode 100644 index 0ff88546f6..0000000000 --- a/update_verifier/Android.mk +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH := $(call my-dir) - -# libupdate_verifier (static library) -# =============================== -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - update_verifier.cpp - -LOCAL_MODULE := libupdate_verifier - -LOCAL_STATIC_LIBRARIES := \ - libotautil - -LOCAL_SHARED_LIBRARIES := \ - libbase \ - libcutils \ - android.hardware.boot@1.0 - -LOCAL_CFLAGS := -Wall -Werror - -LOCAL_EXPORT_C_INCLUDE_DIRS := \ - $(LOCAL_PATH)/include - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/include - -ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true) -LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1 -endif - -ifeq ($(BOARD_AVB_ENABLE),true) -LOCAL_CFLAGS += -DBOARD_AVB_ENABLE=1 -endif - -include $(BUILD_STATIC_LIBRARY) - -# update_verifier (executable) -# =============================== -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - update_verifier_main.cpp - -LOCAL_MODULE := update_verifier -LOCAL_STATIC_LIBRARIES := \ - libupdate_verifier \ - libotautil - -LOCAL_SHARED_LIBRARIES := \ - libbase \ - libcutils \ - libhardware \ - liblog \ - libutils \ - libhidlbase \ - android.hardware.boot@1.0 - -LOCAL_CFLAGS := -Wall -Werror - -LOCAL_INIT_RC := update_verifier.rc - -include $(BUILD_EXECUTABLE) diff --git a/otautil/cache_location.cpp b/update_verifier/care_map.proto similarity index 52% rename from otautil/cache_location.cpp rename to update_verifier/care_map.proto index 8ddefec5e9..15d3afa832 100644 --- a/otautil/cache_location.cpp +++ b/update_verifier/care_map.proto @@ -14,18 +14,18 @@ * limitations under the License. */ -#include "otautil/cache_location.h" +syntax = "proto3"; -constexpr const char kDefaultCacheTempSource[] = "/cache/saved.file"; -constexpr const char kDefaultLastCommandFile[] = "/cache/recovery/last_command"; -constexpr const char kDefaultStashDirectoryBase[] = "/cache/recovery"; +package recovery_update_verifier; +option optimize_for = LITE_RUNTIME; -CacheLocation& CacheLocation::location() { - static CacheLocation cache_location; - return cache_location; -} +message CareMap { + message PartitionInfo { + string name = 1; + string ranges = 2; + string id = 3; + string fingerprint = 4; + } -CacheLocation::CacheLocation() - : cache_temp_source_(kDefaultCacheTempSource), - last_command_file_(kDefaultLastCommandFile), - stash_directory_base_(kDefaultStashDirectoryBase) {} + repeated PartitionInfo partitions = 1; +} diff --git a/update_verifier/care_map_generator.py b/update_verifier/care_map_generator.py new file mode 100644 index 0000000000..051d98deb0 --- /dev/null +++ b/update_verifier/care_map_generator.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Parses a input care_map.txt in plain text format; converts it into the proto +buf message; and writes the result to the output file. + +""" + +import argparse +import logging +import sys + +import care_map_pb2 + + +def GenerateCareMapProtoFromLegacyFormat(lines, fingerprint_enabled): + """Constructs a care map proto message from the lines of the input file.""" + + # Expected format of the legacy care_map.txt: + # system + # system's care_map ranges + # [system's fingerprint property id] + # [system's fingerprint] + # [vendor] + # [vendor's care_map ranges] + # [vendor's fingerprint property id] + # [vendor's fingerprint] + # ... + + step = 4 if fingerprint_enabled else 2 + assert len(lines) % step == 0, \ + "line count must be multiple of {}: {}".format(step, len(lines)) + + care_map_proto = care_map_pb2.CareMap() + for index in range(0, len(lines), step): + info = care_map_proto.partitions.add() + info.name = lines[index] + info.ranges = lines[index + 1] + if fingerprint_enabled: + info.id = lines[index + 2] + info.fingerprint = lines[index + 3] + logging.info("Care map info: name %s, ranges %s, id %s, fingerprint %s", + info.name, info.ranges, info.id, info.fingerprint) + + return care_map_proto + + +def ParseProtoMessage(message, fingerprint_enabled): + """Parses the care_map proto message and returns its text representation. + Args: + message: Care_map in protobuf format. + fingerprint_enabled: Input protobuf message contains the fields 'id' and + 'fingerprint'. + + Returns: + A string of the care_map information, similar to the care_map legacy + format. + """ + care_map_proto = care_map_pb2.CareMap() + care_map_proto.MergeFromString(message) + + info_list = [] + for info in care_map_proto.partitions: + assert info.name, "partition name is required in care_map" + assert info.ranges, "source range is required in care_map" + info_list += [info.name, info.ranges] + if fingerprint_enabled: + assert info.id, "property id is required in care_map" + assert info.fingerprint, "fingerprint is required in care_map" + info_list += [info.id, info.fingerprint] + + return '\n'.join(info_list) + + +def main(argv): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("input_care_map", + help="Path to the legacy care_map file (or path to" + " care_map in protobuf format if --parse_proto is" + " specified).") + parser.add_argument("output_file", + help="Path to output file to write the result.") + parser.add_argument("--no_fingerprint", action="store_false", + dest="fingerprint_enabled", + help="The 'id' and 'fingerprint' fields are disabled in" + " the caremap.") + parser.add_argument("--parse_proto", "-p", action="store_true", + help="Parses the input as proto message, and outputs" + " the care_map in plain text.") + parser.add_argument("--verbose", "-v", action="store_true") + + args = parser.parse_args(argv) + + logging_format = '%(filename)s %(levelname)s: %(message)s' + logging.basicConfig(level=logging.INFO if args.verbose else logging.WARNING, + format=logging_format) + + with open(args.input_care_map, 'r') as input_care_map: + content = input_care_map.read() + + if args.parse_proto: + result = ParseProtoMessage(content, args.fingerprint_enabled) + else: + care_map_proto = GenerateCareMapProtoFromLegacyFormat( + content.rstrip().splitlines(), args.fingerprint_enabled) + result = care_map_proto.SerializeToString() + + with open(args.output_file, 'w') as output: + output.write(result) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/update_verifier/include/update_verifier/update_verifier.h b/update_verifier/include/update_verifier/update_verifier.h index 16b394e984..4c64b1ea1d 100644 --- a/update_verifier/include/update_verifier/update_verifier.h +++ b/update_verifier/include/update_verifier/update_verifier.h @@ -16,9 +16,56 @@ #pragma once +#include +#include #include +#include +#include "otautil/rangeset.h" + +// The update verifier performs verification upon the first boot to a new slot on A/B devices. +// During the verification, it reads all the blocks in the care_map. And if a failure happens, +// it rejects the current boot and triggers a fallback. + +// Note that update_verifier should be backward compatible to not reject care_map.txt from old +// releases, which could otherwise fail to boot into the new release. For example, we've changed +// the care_map format between N and O. An O update_verifier would fail to work with N care_map.txt. +// This could be a result of sideloading an O OTA while the device having a pending N update. int update_verifier(int argc, char** argv); -// Exposed for testing purpose. -bool verify_image(const std::string& care_map_name); +// The UpdateVerifier parses the content in the care map, and continues to verify the +// partitions by reading the cared blocks if there's no format error in the file. Otherwise, +// it should skip the verification to avoid bricking the device. +class UpdateVerifier { + public: + UpdateVerifier(); + + // This function tries to process the care_map.pb as protobuf message; and falls back to use + // care_map.txt if the pb format file doesn't exist. If the parsing succeeds, put the result + // of the pair into the |partition_map_|. + bool ParseCareMap(); + + // Verifies the new boot by reading all the cared blocks for partitions in |partition_map_|. + bool VerifyPartitions(); + + private: + friend class UpdateVerifierTest; + // Finds all the dm-enabled partitions, and returns a map of . + std::map FindDmPartitions(); + + // Returns true if we successfully read the blocks in |ranges| of the |dm_block_device|. + bool ReadBlocks(const std::string partition_name, const std::string& dm_block_device, + const RangeSet& ranges); + + // Functions to override the care_map_prefix_ and property_reader_, used in test only. + void set_care_map_prefix(const std::string& prefix); + void set_property_reader(const std::function& property_reader); + + std::map partition_map_; + // The path to the care_map excluding the filename extension; default value: + // "/data/ota_package/care_map" + std::string care_map_prefix_; + + // The function to read the device property; default value: android::base::GetProperty() + std::function property_reader_; +}; diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp index 92d9313710..a042f90080 100644 --- a/update_verifier/update_verifier.cpp +++ b/update_verifier/update_verifier.cpp @@ -15,24 +15,26 @@ */ /* - * This program verifies the integrity of the partitions after an A/B OTA - * update. It gets invoked by init, and will only perform the verification if - * it's the first boot post an A/B OTA update. + * update_verifier verifies the integrity of the partitions after an A/B OTA update. It gets invoked + * by init, and will only perform the verification if it's the first boot post an A/B OTA update + * (https://source.android.com/devices/tech/ota/ab/#after_reboot). * - * Update_verifier relies on dm-verity to capture any corruption on the partitions - * being verified. And its behavior varies depending on the dm-verity mode. - * Upon detection of failures: + * update_verifier relies on device-mapper-verity (dm-verity) to capture any corruption on the + * partitions being verified (https://source.android.com/security/verifiedboot). The verification + * will be skipped, if dm-verity is not enabled on the device. + * + * Upon detecting verification failures, the device will be rebooted, although the trigger of the + * reboot depends on the dm-verity mode. * enforcing mode: dm-verity reboots the device * eio mode: dm-verity fails the read and update_verifier reboots the device * other mode: not supported and update_verifier reboots the device * - * After a predefined number of failing boot attempts, the bootloader should - * mark the slot as unbootable and stops trying. Other dm-verity modes ( - * for example, veritymode=EIO) are not accepted and simply lead to a - * verification failure. + * All these reboots prevent the device from booting into a known corrupt state. If the device + * continuously fails to boot into the new slot, the bootloader should mark the slot as unbootable + * and trigger a fallback to the old slot. * - * The current slot will be marked as having booted successfully if the - * verifier reaches the end after the verification. + * The current slot will be marked as having booted successfully if the verifier reaches the end + * after the verification. */ #include "update_verifier/update_verifier.h" @@ -40,14 +42,15 @@ #include #include #include +#include #include +#include #include #include #include #include -#include -#include +#include #include #include @@ -56,15 +59,21 @@ #include #include #include +#include +#include +#include #include -#include "otautil/rangeset.h" +#include "care_map.pb.h" using android::sp; using android::hardware::boot::V1_0::IBootControl; using android::hardware::boot::V1_0::BoolResult; using android::hardware::boot::V1_0::CommandResult; +// TODO(xunchang) remove the prefix and use a default path instead. +constexpr const char* kDefaultCareMapPrefix = "/data/ota_package/care_map"; + // Find directories in format of "/sys/block/dm-X". static int dm_name_filter(const dirent* de) { if (android::base::StartsWith(de->d_name, "dm-")) { @@ -73,29 +82,29 @@ static int dm_name_filter(const dirent* de) { return 0; } -static bool read_blocks(const std::string& partition, const std::string& range_str) { - if (partition != "system" && partition != "vendor" && partition != "product") { - LOG(ERROR) << "Invalid partition name \"" << partition << "\""; - return false; - } - // Iterate the content of "/sys/block/dm-X/dm/name". If it matches one of "system", "vendor" or - // "product", then dm-X is a dm-wrapped device for that target. We will later read all the - // ("cared") blocks from "/dev/block/dm-X" to ensure the target partition's integrity. +UpdateVerifier::UpdateVerifier() + : care_map_prefix_(kDefaultCareMapPrefix), + property_reader_([](const std::string& id) { return android::base::GetProperty(id, ""); }) {} + +// Iterate the content of "/sys/block/dm-X/dm/name" and find all the dm-wrapped block devices. +// We will later read all the ("cared") blocks from "/dev/block/dm-X" to ensure the target +// partition's integrity. +std::map UpdateVerifier::FindDmPartitions() { static constexpr auto DM_PATH_PREFIX = "/sys/block/"; dirent** namelist; int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort); if (n == -1) { PLOG(ERROR) << "Failed to scan dir " << DM_PATH_PREFIX; - return false; + return {}; } if (n == 0) { - LOG(ERROR) << "dm block device not found for " << partition; - return false; + LOG(ERROR) << "No dm block device found."; + return {}; } static constexpr auto DM_PATH_SUFFIX = "/dm/name"; static constexpr auto DEV_PATH = "/dev/block/"; - std::string dm_block_device; + std::map dm_block_devices; while (n--) { std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX; std::string content; @@ -103,39 +112,31 @@ static bool read_blocks(const std::string& partition, const std::string& range_s PLOG(WARNING) << "Failed to read " << path; } else { std::string dm_block_name = android::base::Trim(content); -#ifdef BOARD_AVB_ENABLE // AVB is using 'vroot' for the root block device but we're expecting 'system'. if (dm_block_name == "vroot") { dm_block_name = "system"; + } else if (android::base::EndsWith(dm_block_name, "-verity")) { + auto npos = dm_block_name.rfind("-verity"); + dm_block_name = dm_block_name.substr(0, npos); + } else if (!android::base::GetProperty("ro.boot.avb_version", "").empty()) { + // Verified Boot 1.0 doesn't add a -verity suffix. On AVB 2 devices, + // if DAP is enabled, then a -verity suffix must be used to + // differentiate between dm-linear and dm-verity devices. If we get + // here, we're AVB 2 and looking at a non-verity partition. + continue; } -#endif - if (dm_block_name == partition) { - dm_block_device = DEV_PATH + std::string(namelist[n]->d_name); - while (n--) { - free(namelist[n]); - } - break; - } + + dm_block_devices.emplace(dm_block_name, DEV_PATH + std::string(namelist[n]->d_name)); } free(namelist[n]); } free(namelist); - if (dm_block_device.empty()) { - LOG(ERROR) << "Failed to find dm block device for " << partition; - return false; - } - - // For block range string, first integer 'count' equals 2 * total number of valid ranges, - // followed by 'count' number comma separated integers. Every two integers reprensent a - // block range with the first number included in range but second number not included. - // For example '4,64536,65343,74149,74150' represents: [64536,65343) and [74149,74150). - RangeSet ranges = RangeSet::Parse(range_str); - if (!ranges) { - LOG(ERROR) << "Error parsing RangeSet string " << range_str; - return false; - } + return dm_block_devices; +} +bool UpdateVerifier::ReadBlocks(const std::string partition_name, + const std::string& dm_block_device, const RangeSet& ranges) { // RangeSet::Split() splits the ranges into multiple groups with same number of blocks (except for // the last group). size_t thread_num = std::thread::hardware_concurrency() ?: 4; @@ -143,10 +144,10 @@ static bool read_blocks(const std::string& partition, const std::string& range_s std::vector> threads; for (const auto& group : groups) { - auto thread_func = [&group, &dm_block_device, &partition]() { + auto thread_func = [&group, &dm_block_device, &partition_name]() { android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY))); if (fd.get() == -1) { - PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition; + PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition_name; return false; } @@ -154,9 +155,7 @@ static bool read_blocks(const std::string& partition, const std::string& range_s std::vector buf(1024 * kBlockSize); size_t block_count = 0; - for (const auto& range : group) { - size_t range_start = range.first; - size_t range_end = range.second; + for (const auto& [range_start, range_end] : group) { if (lseek64(fd.get(), static_cast(range_start) * kBlockSize, SEEK_SET) == -1) { PLOG(ERROR) << "lseek to " << range_start << " failed"; return false; @@ -189,54 +188,112 @@ static bool read_blocks(const std::string& partition, const std::string& range_s return ret; } -// Returns true to indicate a passing verification (or the error should be ignored); Otherwise -// returns false on fatal errors, where we should reject the current boot and trigger a fallback. -// Note that update_verifier should be backward compatible to not reject care_map.txt from old -// releases, which could otherwise fail to boot into the new release. For example, we've changed -// the care_map format between N and O. An O update_verifier would fail to work with N -// care_map.txt. This could be a result of sideloading an O OTA while the device having a pending N -// update. -bool verify_image(const std::string& care_map_name) { +bool UpdateVerifier::VerifyPartitions() { + auto dm_block_devices = FindDmPartitions(); + if (dm_block_devices.empty()) { + LOG(ERROR) << "No dm-enabled block device is found."; + return false; + } + + for (const auto& [partition_name, ranges] : partition_map_) { + if (dm_block_devices.find(partition_name) == dm_block_devices.end()) { + LOG(ERROR) << "Failed to find dm block device for " << partition_name; + return false; + } + + if (!ReadBlocks(partition_name, dm_block_devices.at(partition_name), ranges)) { + return false; + } + } + + return true; +} + +bool UpdateVerifier::ParseCareMap() { + partition_map_.clear(); + + std::string care_map_name = care_map_prefix_ + ".pb"; + if (access(care_map_name.c_str(), R_OK) == -1) { + LOG(ERROR) << care_map_name << " doesn't exist"; + return false; + } + android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY))); - // If the device is flashed before the current boot, it may not have care_map.txt - // in /data/ota_package. To allow the device to continue booting in this situation, - // we should print a warning and skip the block verification. + // If the device is flashed before the current boot, it may not have care_map.txt in + // /data/ota_package. To allow the device to continue booting in this situation, we should + // print a warning and skip the block verification. if (care_map_fd.get() == -1) { PLOG(WARNING) << "Failed to open " << care_map_name; - return true; + return false; } - // care_map file has up to six lines, where every two lines make a pair. Within each pair, the - // first line has the partition name (e.g. "system"), while the second line holds the ranges of - // all the blocks to verify. + std::string file_content; if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) { - LOG(ERROR) << "Error reading care map contents to string."; + PLOG(WARNING) << "Failed to read " << care_map_name; return false; } - std::vector lines; - lines = android::base::Split(android::base::Trim(file_content), "\n"); - if (lines.size() != 2 && lines.size() != 4 && lines.size() != 6) { - LOG(ERROR) << "Invalid lines in care_map: found " << lines.size() - << " lines, expecting 2 or 4 or 6 lines."; + if (file_content.empty()) { + LOG(WARNING) << "Unexpected empty care map"; return false; } - for (size_t i = 0; i < lines.size(); i += 2) { - // We're seeing an N care_map.txt. Skip the verification since it's not compatible with O - // update_verifier (the last few metadata blocks can't be read via device mapper). - if (android::base::StartsWith(lines[i], "/dev/block/")) { - LOG(WARNING) << "Found legacy care_map.txt; skipped."; - return true; + recovery_update_verifier::CareMap care_map; + if (!care_map.ParseFromString(file_content)) { + LOG(WARNING) << "Failed to parse " << care_map_name << " in protobuf format."; + return false; + } + + for (const auto& partition : care_map.partitions()) { + if (partition.name().empty()) { + LOG(WARNING) << "Unexpected empty partition name."; + return false; + } + if (partition.ranges().empty()) { + LOG(WARNING) << "Unexpected block ranges for partition " << partition.name(); + return false; } - if (!read_blocks(lines[i], lines[i+1])) { + RangeSet ranges = RangeSet::Parse(partition.ranges()); + if (!ranges) { + LOG(WARNING) << "Error parsing RangeSet string " << partition.ranges(); return false; } + + // Continues to check other partitions if there is a fingerprint mismatch. + if (partition.id().empty() || partition.id() == "unknown") { + LOG(WARNING) << "Skip reading partition " << partition.name() + << ": property_id is not provided to get fingerprint."; + continue; + } + + std::string fingerprint = property_reader_(partition.id()); + if (fingerprint != partition.fingerprint()) { + LOG(WARNING) << "Skip reading partition " << partition.name() << ": fingerprint " + << fingerprint << " doesn't match the expected value " + << partition.fingerprint(); + continue; + } + + partition_map_.emplace(partition.name(), ranges); + } + + if (partition_map_.empty()) { + LOG(WARNING) << "No partition to verify"; + return false; } return true; } +void UpdateVerifier::set_care_map_prefix(const std::string& prefix) { + care_map_prefix_ = prefix; +} + +void UpdateVerifier::set_property_reader( + const std::function& property_reader) { + property_reader_ = property_reader; +} + static int reboot_device() { if (android_reboot(ANDROID_RB_RESTART2, 0, nullptr) == -1) { LOG(ERROR) << "Failed to reboot."; @@ -264,19 +321,13 @@ int update_verifier(int argc, char** argv) { if (is_successful == BoolResult::FALSE) { // The current slot has not booted successfully. -#if defined(PRODUCT_SUPPORTS_VERITY) || defined(BOARD_AVB_ENABLE) bool skip_verification = false; std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", ""); if (verity_mode.empty()) { - // With AVB it's possible to disable verification entirely and - // in this case ro.boot.veritymode is empty. -#if defined(BOARD_AVB_ENABLE) - LOG(WARNING) << "verification has been disabled; marking without verification."; + // Skip the verification if ro.boot.veritymode property is not set. This could be a result + // that device doesn't support dm-verity, or has disabled that. + LOG(WARNING) << "dm-verity not enabled; marking without verification."; skip_verification = true; -#else - LOG(ERROR) << "Failed to get dm-verity mode."; - return reboot_device(); -#endif } else if (android::base::EqualsIgnoreCase(verity_mode, "eio")) { // We shouldn't see verity in EIO mode if the current slot hasn't booted successfully before. // Continue the verification until we fail to read some blocks. @@ -285,28 +336,48 @@ int update_verifier(int argc, char** argv) { LOG(WARNING) << "dm-verity in disabled mode; marking without verification."; skip_verification = true; } else if (verity_mode != "enforcing") { - LOG(ERROR) << "Unexpected dm-verity mode : " << verity_mode << ", expecting enforcing."; + LOG(ERROR) << "Unexpected dm-verity mode: " << verity_mode << ", expecting enforcing."; return reboot_device(); } if (!skip_verification) { - static constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt"; - if (!verify_image(CARE_MAP_FILE)) { + UpdateVerifier verifier; + if (!verifier.ParseCareMap()) { + LOG(WARNING) << "Failed to parse the care map file, skipping verification"; + } else if (!verifier.VerifyPartitions()) { LOG(ERROR) << "Failed to verify all blocks in care map file."; return reboot_device(); } } -#else - LOG(WARNING) << "dm-verity not enabled; marking without verification."; -#endif - - CommandResult cr; - module->markBootSuccessful([&cr](CommandResult result) { cr = result; }); - if (!cr.success) { - LOG(ERROR) << "Error marking booted successfully: " << cr.errMsg; - return reboot_device(); + + bool supports_checkpoint = false; + auto sm = android::defaultServiceManager(); + android::sp binder = sm->getService(android::String16("vold")); + if (binder) { + auto vold = android::interface_cast(binder); + android::binder::Status status = vold->supportsCheckpoint(&supports_checkpoint); + if (!status.isOk()) { + LOG(ERROR) << "Failed to check if checkpoints supported. Continuing"; + } + } else { + LOG(ERROR) << "Failed to obtain vold Binder. Continuing"; + } + + if (!supports_checkpoint) { + CommandResult cr; + module->markBootSuccessful([&cr](CommandResult result) { cr = result; }); + if (!cr.success) { + LOG(ERROR) << "Error marking booted successfully: " << cr.errMsg; + return reboot_device(); + } + LOG(INFO) << "Marked slot " << current_slot << " as booted successfully."; + // Clears the warm reset flag for next reboot. + if (!android::base::SetProperty("ota.warm_reset", "0")) { + LOG(WARNING) << "Failed to reset the warm reset flag"; + } + } else { + LOG(INFO) << "Deferred marking slot " << current_slot << " as booted successfully."; } - LOG(INFO) << "Marked slot " << current_slot << " as booted successfully."; } LOG(INFO) << "Leaving update_verifier."; diff --git a/updater/Android.bp b/updater/Android.bp new file mode 100644 index 0000000000..f00a192b97 --- /dev/null +++ b/updater/Android.bp @@ -0,0 +1,189 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_defaults { + name: "libupdater_static_libs", + + static_libs: [ + "libapplypatch", + "libbootloader_message", + "libbspatch", + "libedify", + "libotautil", + "libext4_utils", + "libdm", + "libfec", + "libfec_rs", + "libavb", + "libverity_tree", + "libgtest_prod", + "liblog", + "liblp", + "libselinux", + "libsparse", + "libsquashfs_utils", + "libbrotli", + "libbz", + "libziparchive", + "libz", + "libbase", + "libcrypto_utils", + "libcutils", + "libutils", + ], +} + +cc_defaults { + name: "libupdater_defaults", + + defaults: [ + "recovery_defaults", + "libupdater_static_libs", + ], + + shared_libs: [ + "libcrypto", + ], +} + +cc_defaults { + name: "libupdater_device_defaults", + + static_libs: [ + "libfs_mgr", + "libtune2fs", + + "libext2_com_err", + "libext2_blkid", + "libext2_quota", + "libext2_uuid", + "libext2_e2p", + "libext2fs", + ], +} + +cc_library_static { + name: "libupdater_core", + + host_supported: true, + + defaults: [ + "recovery_defaults", + "libupdater_defaults", + ], + + srcs: [ + "blockimg.cpp", + "commands.cpp", + "install.cpp", + "mounts.cpp", + "updater.cpp", + ], + + target: { + darwin: { + enabled: false, + }, + }, + + export_include_dirs: [ + "include", + ], +} + +cc_library_static { + name: "libupdater_device", + + defaults: [ + "recovery_defaults", + "libupdater_defaults", + "libupdater_device_defaults", + ], + + srcs: [ + "dynamic_partitions.cpp", + "updater_runtime.cpp", + "updater_runtime_dynamic_partitions.cpp", + ], + + static_libs: [ + "libupdater_core", + ], + + include_dirs: [ + "external/e2fsprogs/misc", + ], + + export_include_dirs: [ + "include", + ], +} + +cc_library_host_static { + name: "libupdater_host", + + defaults: [ + "recovery_defaults", + "libupdater_defaults", + ], + + srcs: [ + "build_info.cpp", + "dynamic_partitions.cpp", + "simulator_runtime.cpp", + "target_files.cpp", + ], + + static_libs: [ + "libupdater_core", + "libfstab", + "libc++fs", + ], + + target: { + darwin: { + enabled: false, + }, + }, + + export_include_dirs: [ + "include", + ], +} + +cc_binary_host { + name: "update_host_simulator", + defaults: ["libupdater_static_libs"], + + srcs: ["update_simulator_main.cpp"], + + cflags: [ + "-Wall", + "-Werror", + ], + + static_libs: [ + "libupdater_host", + "libupdater_core", + "libcrypto_static", + "libfstab", + "libc++fs", + ], + + target: { + darwin: { + enabled: false, + }, + }, +} diff --git a/updater/Android.mk b/updater/Android.mk index 6f334ee180..46300d974d 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -24,58 +24,48 @@ tune2fs_static_libraries := \ updater_common_static_libraries := \ libapplypatch \ + libbootloader_message \ libbspatch \ libedify \ - libziparchive \ libotautil \ - libbootloader_message \ - libutils \ - libmounts \ - libotafault \ libext4_utils \ + libdm \ libfec \ libfec_rs \ - libfs_mgr \ + libavb \ + libverity_tree \ + libgtest_prod \ liblog \ + liblp \ libselinux \ libsparse \ libsquashfs_utils \ + libbrotli \ libbz \ + libziparchive \ libz \ libbase \ - libcrypto \ + libcrypto_static \ libcrypto_utils \ libcutils \ - libtune2fs \ - libbrotli \ - $(tune2fs_static_libraries) - -# libupdater (static library) -# =============================== -include $(CLEAR_VARS) - -LOCAL_MODULE := libupdater - -LOCAL_SRC_FILES := \ - install.cpp \ - blockimg.cpp - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/.. \ - $(LOCAL_PATH)/include \ - external/e2fsprogs/misc - -LOCAL_CFLAGS := \ - -Wall \ - -Werror + libutils -LOCAL_EXPORT_C_INCLUDE_DIRS := \ - $(LOCAL_PATH)/include -LOCAL_STATIC_LIBRARIES := \ - $(updater_common_static_libraries) +# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function +# named "Register_()". Here we emit a little C function that +# gets #included by updater.cpp. It calls all those registration +# functions. +# $(1): the path to the register.inc file +# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS +define generate-register-inc + $(hide) mkdir -p $(dir $(1)) + $(hide) echo "" > $(1) + $(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);) + $(hide) echo "void RegisterDeviceExtensions() {" >> $(1) + $(hide) $(foreach lib,$(2),echo " Register_$(lib)();" >> $(1);) + $(hide) echo "}" >> $(1) +endef -include $(BUILD_STATIC_LIBRARY) # updater (static executable) # =============================== @@ -84,10 +74,9 @@ include $(CLEAR_VARS) LOCAL_MODULE := updater LOCAL_SRC_FILES := \ - updater.cpp + updater_main.cpp LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/.. \ $(LOCAL_PATH)/include LOCAL_CFLAGS := \ @@ -95,33 +84,26 @@ LOCAL_CFLAGS := \ -Werror LOCAL_STATIC_LIBRARIES := \ - libupdater \ + libupdater_device \ + libupdater_core \ $(TARGET_RECOVERY_UPDATER_LIBS) \ $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \ - $(updater_common_static_libraries) + $(updater_common_static_libraries) \ + libfs_mgr \ + libtune2fs \ + $(tune2fs_static_libraries) -# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function -# named "Register_()". Here we emit a little C function that -# gets #included by updater.c. It calls all those registration -# functions. +LOCAL_MODULE_CLASS := EXECUTABLES +inc := $(call local-generated-sources-dir)/register.inc # Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS. # These libs are also linked in with updater, but we don't try to call # any sort of registration function for these. Use this variable for # any subsidiary static libraries required for your registered # extension libs. - -LOCAL_MODULE_CLASS := EXECUTABLES -inc := $(call local-generated-sources-dir)/register.inc - $(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS) $(inc) : - $(hide) mkdir -p $(dir $@) - $(hide) echo "" > $@ - $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;) - $(hide) echo "void RegisterDeviceExtensions() {" >> $@ - $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;) - $(hide) echo "}" >> $@ + $(call generate-register-inc,$@,$(libs)) LOCAL_GENERATED_SOURCES := $(inc) diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index e93196b278..2d41f610b8 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -15,8 +15,8 @@ */ #include -#include #include +#include #include #include #include @@ -25,13 +25,12 @@ #include #include #include +#include #include #include #include -#include #include #include -#include #include #include @@ -43,38 +42,47 @@ #include #include #include +#include #include #include #include #include +#include #include -#include +#include #include #include "edify/expr.h" -#include "otafault/ota_io.h" -#include "otautil/cache_location.h" +#include "edify/updater_interface.h" +#include "otautil/dirutil.h" #include "otautil/error_code.h" +#include "otautil/paths.h" #include "otautil/print_sha1.h" #include "otautil/rangeset.h" +#include "private/commands.h" #include "updater/install.h" -#include "updater/updater.h" -// Set this to 0 to interpret 'erase' transfers to mean do a -// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret -// erase to mean fill the region with zeroes. +#ifdef __ANDROID__ +#include +// Set this to 0 to interpret 'erase' transfers to mean do a BLKDISCARD ioctl (the normal behavior). +// Set to 1 to interpret erase to mean fill the region with zeroes. #define DEBUG_ERASE 0 +#else +#define DEBUG_ERASE 1 +#define AID_SYSTEM -1 +#endif // __ANDROID__ static constexpr size_t BLOCKSIZE = 4096; static constexpr mode_t STASH_DIRECTORY_MODE = 0700; static constexpr mode_t STASH_FILE_MODE = 0600; +static constexpr mode_t MARKER_DIRECTORY_MODE = 0700; static CauseCode failure_type = kNoCause; static bool is_retry = false; static std::unordered_map stash_map; static void DeleteLastCommandFile() { - std::string last_command_file = CacheLocation::location().last_command_file(); + const std::string& last_command_file = Paths::Get().last_command_file(); if (unlink(last_command_file.c_str()) == -1 && errno != ENOENT) { PLOG(ERROR) << "Failed to unlink: " << last_command_file; } @@ -82,8 +90,8 @@ static void DeleteLastCommandFile() { // Parse the last command index of the last update and save the result to |last_command_index|. // Return true if we successfully read the index. -static bool ParseLastCommandFile(int* last_command_index) { - std::string last_command_file = CacheLocation::location().last_command_file(); +static bool ParseLastCommandFile(size_t* last_command_index) { + const std::string& last_command_file = Paths::Get().last_command_file(); android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(last_command_file.c_str(), O_RDONLY))); if (fd == -1) { if (errno != ENOENT) { @@ -108,7 +116,7 @@ static bool ParseLastCommandFile(int* last_command_index) { return false; } - if (!android::base::ParseInt(lines[0], last_command_index)) { + if (!android::base::ParseUint(lines[0], last_command_index)) { LOG(ERROR) << "Failed to parse integer in: " << lines[0]; return false; } @@ -116,10 +124,24 @@ static bool ParseLastCommandFile(int* last_command_index) { return true; } -// Update the last command index in the last_command_file if the current command writes to the -// stash either explicitly or implicitly. -static bool UpdateLastCommandIndex(int command_index, const std::string& command_string) { - std::string last_command_file = CacheLocation::location().last_command_file(); +static bool FsyncDir(const std::string& dirname) { + android::base::unique_fd dfd(TEMP_FAILURE_RETRY(open(dirname.c_str(), O_RDONLY | O_DIRECTORY))); + if (dfd == -1) { + failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; + PLOG(ERROR) << "Failed to open " << dirname; + return false; + } + if (fsync(dfd) == -1) { + failure_type = errno == EIO ? kEioFailure : kFsyncFailure; + PLOG(ERROR) << "Failed to fsync " << dirname; + return false; + } + return true; +} + +// Update the last executed command index in the last_command_file. +static bool UpdateLastCommandIndex(size_t command_index, const std::string& command_string) { + const std::string& last_command_file = Paths::Get().last_command_file(); std::string last_command_tmp = last_command_file + ".tmp"; std::string content = std::to_string(command_index) + "\n" + command_string; android::base::unique_fd wfd( @@ -144,71 +166,44 @@ static bool UpdateLastCommandIndex(int command_index, const std::string& command return false; } - std::string last_command_dir = android::base::Dirname(last_command_file); - android::base::unique_fd dfd( - TEMP_FAILURE_RETRY(ota_open(last_command_dir.c_str(), O_RDONLY | O_DIRECTORY))); - if (dfd == -1) { - PLOG(ERROR) << "Failed to open " << last_command_dir; - return false; - } - - if (fsync(dfd) == -1) { - PLOG(ERROR) << "Failed to fsync " << last_command_dir; + if (!FsyncDir(android::base::Dirname(last_command_file))) { return false; } return true; } -static int read_all(int fd, uint8_t* data, size_t size) { - size_t so_far = 0; - while (so_far < size) { - ssize_t r = TEMP_FAILURE_RETRY(ota_read(fd, data+so_far, size-so_far)); - if (r == -1) { - failure_type = kFreadFailure; - PLOG(ERROR) << "read failed"; - return -1; - } else if (r == 0) { - failure_type = kFreadFailure; - LOG(ERROR) << "read reached unexpected EOF."; - return -1; - } - so_far += r; - } - return 0; -} - -static int read_all(int fd, std::vector& buffer, size_t size) { - return read_all(fd, buffer.data(), size); -} - -static int write_all(int fd, const uint8_t* data, size_t size) { - size_t written = 0; - while (written < size) { - ssize_t w = TEMP_FAILURE_RETRY(ota_write(fd, data+written, size-written)); - if (w == -1) { - failure_type = kFwriteFailure; - PLOG(ERROR) << "write failed"; - return -1; - } - written += w; - } - - return 0; -} +bool SetUpdatedMarker(const std::string& marker) { + auto dirname = android::base::Dirname(marker); + auto res = mkdir(dirname.c_str(), MARKER_DIRECTORY_MODE); + if (res == -1 && errno != EEXIST) { + PLOG(ERROR) << "Failed to create directory for marker: " << dirname; + return false; + } -static int write_all(int fd, const std::vector& buffer, size_t size) { - return write_all(fd, buffer.data(), size); + if (!android::base::WriteStringToFile("", marker)) { + PLOG(ERROR) << "Failed to write to marker file " << marker; + return false; + } + if (!FsyncDir(dirname)) { + return false; + } + LOG(INFO) << "Wrote updated marker to " << marker; + return true; } -static bool discard_blocks(int fd, off64_t offset, uint64_t size) { - // Don't discard blocks unless the update is a retry run. - if (!is_retry) { +static bool discard_blocks(int fd, off64_t offset, uint64_t size, bool force = false) { + // Don't discard blocks unless the update is a retry run or force == true + if (!is_retry && !force) { return true; } uint64_t args[2] = { static_cast(offset), size }; if (ioctl(fd, BLKDISCARD, &args) == -1) { + // On devices that does not support BLKDISCARD, ignore the error. + if (errno == EOPNOTSUPP) { + return true; + } PLOG(ERROR) << "BLKDISCARD ioctl failed"; return false; } @@ -225,11 +220,10 @@ static bool check_lseek(int fd, off64_t offset, int whence) { return true; } -static void allocate(size_t size, std::vector& buffer) { - // if the buffer's big enough, reuse it. - if (size <= buffer.size()) return; - - buffer.resize(size); +static void allocate(size_t size, std::vector* buffer) { + // If the buffer's big enough, reuse it. + if (size <= buffer->size()) return; + buffer->resize(size); } /** @@ -274,7 +268,9 @@ class RangeSinkWriter { write_now = current_range_left_; } - if (write_all(fd_, data, write_now) == -1) { + if (!android::base::WriteFully(fd_, data, write_now)) { + failure_type = errno == EIO ? kEioFailure : kFwriteFailure; + PLOG(ERROR) << "Failed to write " << write_now << " bytes of data"; break; } @@ -483,15 +479,17 @@ static void* unzip_new_data(void* cookie) { return nullptr; } -static int ReadBlocks(const RangeSet& src, std::vector& buffer, int fd) { +static int ReadBlocks(const RangeSet& src, std::vector* buffer, int fd) { size_t p = 0; - for (const auto& range : src) { - if (!check_lseek(fd, static_cast(range.first) * BLOCKSIZE, SEEK_SET)) { + for (const auto& [begin, end] : src) { + if (!check_lseek(fd, static_cast(begin) * BLOCKSIZE, SEEK_SET)) { return -1; } - size_t size = (range.second - range.first) * BLOCKSIZE; - if (read_all(fd, buffer.data() + p, size) == -1) { + size_t size = (end - begin) * BLOCKSIZE; + if (!android::base::ReadFully(fd, buffer->data() + p, size)) { + failure_type = errno == EIO ? kEioFailure : kFreadFailure; + PLOG(ERROR) << "Failed to read " << size << " bytes of data"; return -1; } @@ -503,9 +501,9 @@ static int ReadBlocks(const RangeSet& src, std::vector& buffer, int fd) static int WriteBlocks(const RangeSet& tgt, const std::vector& buffer, int fd) { size_t written = 0; - for (const auto& range : tgt) { - off64_t offset = static_cast(range.first) * BLOCKSIZE; - size_t size = (range.second - range.first) * BLOCKSIZE; + for (const auto& [begin, end] : tgt) { + off64_t offset = static_cast(begin) * BLOCKSIZE; + size_t size = (end - begin) * BLOCKSIZE; if (!discard_blocks(fd, offset, size)) { return -1; } @@ -514,7 +512,9 @@ static int WriteBlocks(const RangeSet& tgt, const std::vector& buffer, return -1; } - if (write_all(fd, buffer.data() + written, size) == -1) { + if (!android::base::WriteFully(fd, buffer.data() + written, size)) { + failure_type = errno == EIO ? kEioFailure : kFwriteFailure; + PLOG(ERROR) << "Failed to write " << size << " bytes of data"; return -1; } @@ -528,9 +528,8 @@ static int WriteBlocks(const RangeSet& tgt, const std::vector& buffer, struct CommandParameters { std::vector tokens; size_t cpos; - int cmdindex; - const char* cmdname; - const char* cmdline; + std::string cmdname; + std::string cmdline; std::string freestash; std::string stashbase; bool canwrite; @@ -644,43 +643,43 @@ static void PrintHashForMissingStashedBlocks(const std::string& id, int fd) { LOG(INFO) << "print hash in hex for source blocks in missing stash: " << id; const RangeSet& src = stash_map[id]; std::vector buffer(src.blocks() * BLOCKSIZE); - if (ReadBlocks(src, buffer, fd) == -1) { - LOG(ERROR) << "failed to read source blocks for stash: " << id; - return; + if (ReadBlocks(src, &buffer, fd) == -1) { + LOG(ERROR) << "failed to read source blocks for stash: " << id; + return; } PrintHashForCorruptedStashedBlocks(id, buffer, src); } static int VerifyBlocks(const std::string& expected, const std::vector& buffer, - const size_t blocks, bool printerror) { - uint8_t digest[SHA_DIGEST_LENGTH]; - const uint8_t* data = buffer.data(); + const size_t blocks, bool printerror) { + uint8_t digest[SHA_DIGEST_LENGTH]; + const uint8_t* data = buffer.data(); - SHA1(data, blocks * BLOCKSIZE, digest); + SHA1(data, blocks * BLOCKSIZE, digest); - std::string hexdigest = print_sha1(digest); + std::string hexdigest = print_sha1(digest); - if (hexdigest != expected) { - if (printerror) { - LOG(ERROR) << "failed to verify blocks (expected " << expected << ", read " - << hexdigest << ")"; - } - return -1; + if (hexdigest != expected) { + if (printerror) { + LOG(ERROR) << "failed to verify blocks (expected " << expected << ", read " << hexdigest + << ")"; } + return -1; + } - return 0; + return 0; } static std::string GetStashFileName(const std::string& base, const std::string& id, - const std::string& postfix) { - if (base.empty()) { - return ""; - } - - std::string fn(CacheLocation::location().stash_directory_base()); - fn += "/" + base + "/" + id + postfix; - - return fn; + const std::string& postfix) { + if (base.empty()) { + return ""; + } + std::string filename = Paths::Get().stash_directory_base() + "/" + base; + if (id.empty() && postfix.empty()) { + return filename; + } + return filename + "/" + id + postfix; } // Does a best effort enumeration of stash files. Ignores possible non-file items in the stash @@ -733,8 +732,8 @@ static void DeleteStash(const std::string& base) { } } -static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks, - std::vector& buffer, bool printnoent) { +static int LoadStash(const CommandParameters& params, const std::string& id, bool verify, + std::vector* buffer, bool printnoent) { // In verify mode, if source range_set was saved for the given hash, check contents in the source // blocks first. If the check fails, search for the stashed files on /cache as usual. if (!params.canwrite) { @@ -746,20 +745,17 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri LOG(ERROR) << "failed to read source blocks in stash map."; return -1; } - if (VerifyBlocks(id, buffer, src.blocks(), true) != 0) { + if (VerifyBlocks(id, *buffer, src.blocks(), true) != 0) { LOG(ERROR) << "failed to verify loaded source blocks in stash map."; - PrintHashForCorruptedStashedBlocks(id, buffer, src); + if (!is_retry) { + PrintHashForCorruptedStashedBlocks(id, *buffer, src); + } return -1; } return 0; } } - size_t blockcount = 0; - if (!blocks) { - blocks = &blockcount; - } - std::string fn = GetStashFileName(params.stashbase, id, ""); struct stat sb; @@ -778,28 +774,30 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri return -1; } - android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY))); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY))); if (fd == -1) { + failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; PLOG(ERROR) << "open \"" << fn << "\" failed"; return -1; } allocate(sb.st_size, buffer); - if (read_all(fd, buffer, sb.st_size) == -1) { + if (!android::base::ReadFully(fd, buffer->data(), sb.st_size)) { + failure_type = errno == EIO ? kEioFailure : kFreadFailure; + PLOG(ERROR) << "Failed to read " << sb.st_size << " bytes of data"; return -1; } - *blocks = sb.st_size / BLOCKSIZE; - - if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) { + size_t blocks = sb.st_size / BLOCKSIZE; + if (verify && VerifyBlocks(id, *buffer, blocks, true) != 0) { LOG(ERROR) << "unexpected contents in " << fn; if (stash_map.find(id) == stash_map.end()) { LOG(ERROR) << "failed to find source blocks number for stash " << id << " when executing command: " << params.cmdname; } else { const RangeSet& src = stash_map[id]; - PrintHashForCorruptedStashedBlocks(id, buffer, src); + PrintHashForCorruptedStashedBlocks(id, *buffer, src); } DeleteFile(fn); return -1; @@ -809,111 +807,92 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri } static int WriteStash(const std::string& base, const std::string& id, int blocks, - std::vector& buffer, bool checkspace, bool* exists) { - if (base.empty()) { - return -1; - } - - if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) { - LOG(ERROR) << "not enough space to write stash"; - return -1; - } - - std::string fn = GetStashFileName(base, id, ".partial"); - std::string cn = GetStashFileName(base, id, ""); + const std::vector& buffer, bool checkspace, bool* exists) { + if (base.empty()) { + return -1; + } - if (exists) { - struct stat sb; - int res = stat(cn.c_str(), &sb); + if (checkspace && !CheckAndFreeSpaceOnCache(blocks * BLOCKSIZE)) { + LOG(ERROR) << "not enough space to write stash"; + return -1; + } - if (res == 0) { - // The file already exists and since the name is the hash of the contents, - // it's safe to assume the contents are identical (accidental hash collisions - // are unlikely) - LOG(INFO) << " skipping " << blocks << " existing blocks in " << cn; - *exists = true; - return 0; - } + std::string fn = GetStashFileName(base, id, ".partial"); + std::string cn = GetStashFileName(base, id, ""); - *exists = false; + if (exists) { + struct stat sb; + int res = stat(cn.c_str(), &sb); + + if (res == 0) { + // The file already exists and since the name is the hash of the contents, + // it's safe to assume the contents are identical (accidental hash collisions + // are unlikely) + LOG(INFO) << " skipping " << blocks << " existing blocks in " << cn; + *exists = true; + return 0; } - LOG(INFO) << " writing " << blocks << " blocks to " << cn; + *exists = false; + } - android::base::unique_fd fd( - TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE))); - if (fd == -1) { - PLOG(ERROR) << "failed to create \"" << fn << "\""; - return -1; - } + LOG(INFO) << " writing " << blocks << " blocks to " << cn; - if (fchown(fd, AID_SYSTEM, AID_SYSTEM) != 0) { // system user - PLOG(ERROR) << "failed to chown \"" << fn << "\""; - return -1; - } + android::base::unique_fd fd( + TEMP_FAILURE_RETRY(open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE))); + if (fd == -1) { + failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; + PLOG(ERROR) << "failed to create \"" << fn << "\""; + return -1; + } - if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) { - return -1; - } + if (fchown(fd, AID_SYSTEM, AID_SYSTEM) != 0) { // system user + PLOG(ERROR) << "failed to chown \"" << fn << "\""; + return -1; + } - if (ota_fsync(fd) == -1) { - failure_type = kFsyncFailure; - PLOG(ERROR) << "fsync \"" << fn << "\" failed"; - return -1; - } + if (!android::base::WriteFully(fd, buffer.data(), blocks * BLOCKSIZE)) { + failure_type = errno == EIO ? kEioFailure : kFwriteFailure; + PLOG(ERROR) << "Failed to write " << blocks * BLOCKSIZE << " bytes of data"; + return -1; + } - if (rename(fn.c_str(), cn.c_str()) == -1) { - PLOG(ERROR) << "rename(\"" << fn << "\", \"" << cn << "\") failed"; - return -1; - } + if (fsync(fd) == -1) { + failure_type = errno == EIO ? kEioFailure : kFsyncFailure; + PLOG(ERROR) << "fsync \"" << fn << "\" failed"; + return -1; + } - std::string dname = GetStashFileName(base, "", ""); - android::base::unique_fd dfd(TEMP_FAILURE_RETRY(ota_open(dname.c_str(), - O_RDONLY | O_DIRECTORY))); - if (dfd == -1) { - failure_type = kFileOpenFailure; - PLOG(ERROR) << "failed to open \"" << dname << "\" failed"; - return -1; - } + if (rename(fn.c_str(), cn.c_str()) == -1) { + PLOG(ERROR) << "rename(\"" << fn << "\", \"" << cn << "\") failed"; + return -1; + } - if (ota_fsync(dfd) == -1) { - failure_type = kFsyncFailure; - PLOG(ERROR) << "fsync \"" << dname << "\" failed"; - return -1; - } + std::string dname = GetStashFileName(base, "", ""); + if (!FsyncDir(dname)) { + return -1; + } - return 0; + return 0; } // Creates a directory for storing stash files and checks if the /cache partition // hash enough space for the expected amount of blocks we need to store. Returns // >0 if we created the directory, zero if it existed already, and <0 of failure. - -static int CreateStash(State* state, size_t maxblocks, const std::string& blockdev, - std::string& base) { - if (blockdev.empty()) { - return -1; - } - - // Stash directory should be different for each partition to avoid conflicts - // when updating multiple partitions at the same time, so we use the hash of - // the block device name as the base directory - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(blockdev.data()), blockdev.size(), digest); - base = print_sha1(digest); - +static int CreateStash(State* state, size_t maxblocks, const std::string& base) { std::string dirname = GetStashFileName(base, "", ""); struct stat sb; int res = stat(dirname.c_str(), &sb); - size_t max_stash_size = maxblocks * BLOCKSIZE; - if (res == -1 && errno != ENOENT) { ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s", dirname.c_str(), strerror(errno)); return -1; - } else if (res != 0) { + } + + size_t max_stash_size = maxblocks * BLOCKSIZE; + if (res == -1) { LOG(INFO) << "creating stash " << dirname; - res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE); + res = mkdir_recursively(dirname, STASH_DIRECTORY_MODE, false, nullptr); if (res != 0) { ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s", dirname.c_str(), @@ -927,7 +906,7 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd return -1; } - if (CacheSizeCheck(max_stash_size) != 0) { + if (!CheckAndFreeSpaceOnCache(max_stash_size)) { ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)", max_stash_size); return -1; @@ -959,7 +938,7 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd if (max_stash_size > existing) { size_t needed = max_stash_size - existing; - if (CacheSizeCheck(needed) != 0) { + if (!CheckAndFreeSpaceOnCache(needed)) { ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)", needed); return -1; @@ -1023,7 +1002,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size return -1; } - allocate(*src_blocks * BLOCKSIZE, params.buffer); + allocate(*src_blocks * BLOCKSIZE, ¶ms.buffer); // "-" or [] if (params.tokens[params.cpos] == "-") { @@ -1034,7 +1013,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size CHECK(static_cast(src)); *overlap = src.Overlaps(tgt); - if (ReadBlocks(src, params.buffer, params.fd) == -1) { + if (ReadBlocks(src, ¶ms.buffer, params.fd) == -1) { return -1; } @@ -1059,7 +1038,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size } std::vector stash; - if (LoadStash(params, tokens[0], false, nullptr, stash, true) == -1) { + if (LoadStash(params, tokens[0], false, &stash, true) == -1) { // These source blocks will fail verification if used later, but we // will let the caller decide if this is a fatal failure LOG(ERROR) << "failed to load stash " << tokens[0]; @@ -1100,10 +1079,9 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size * * If the return value is 0, source blocks have expected content and the command can be performed. */ -static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* src_blocks, - bool onehash, bool* overlap) { +static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet* tgt, size_t* src_blocks, + bool onehash) { CHECK(src_blocks != nullptr); - CHECK(overlap != nullptr); if (params.cpos >= params.tokens.size()) { LOG(ERROR) << "missing source hash"; @@ -1131,29 +1109,30 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* } // - tgt = RangeSet::Parse(params.tokens[params.cpos++]); - CHECK(static_cast(tgt)); + *tgt = RangeSet::Parse(params.tokens[params.cpos++]); + CHECK(static_cast(*tgt)); - std::vector tgtbuffer(tgt.blocks() * BLOCKSIZE); - if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) { + std::vector tgtbuffer(tgt->blocks() * BLOCKSIZE); + if (ReadBlocks(*tgt, &tgtbuffer, params.fd) == -1) { return -1; } // Return now if target blocks already have expected content. - if (VerifyBlocks(tgthash, tgtbuffer, tgt.blocks(), false) == 0) { + if (VerifyBlocks(tgthash, tgtbuffer, tgt->blocks(), false) == 0) { return 1; } // Load source blocks. - if (LoadSourceBlocks(params, tgt, src_blocks, overlap) == -1) { + bool overlap = false; + if (LoadSourceBlocks(params, *tgt, src_blocks, &overlap) == -1) { return -1; } if (VerifyBlocks(srchash, params.buffer, *src_blocks, true) == 0) { - // If source and target blocks overlap, stash the source blocks so we can - // resume from possible write errors. In verify mode, we can skip stashing - // because the source blocks won't be overwritten. - if (*overlap && params.canwrite) { + // If source and target blocks overlap, stash the source blocks so we can resume from possible + // write errors. In verify mode, we can skip stashing because the source blocks won't be + // overwritten. + if (overlap && params.canwrite) { LOG(INFO) << "stashing " << *src_blocks << " overlapping blocks to " << srchash; bool stash_exists = false; @@ -1163,10 +1142,6 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* return -1; } - if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) { - LOG(WARNING) << "Failed to update the last command file."; - } - params.stashed += *src_blocks; // Can be deleted when the write has completed. if (!stash_exists) { @@ -1178,7 +1153,7 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* return 0; } - if (*overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) { + if (overlap && LoadStash(params, srchash, true, ¶ms.buffer, true) == 0) { // Overlapping source blocks were previously stashed, command can proceed. We are recovering // from an interrupted command, so we don't know if the stash can safely be deleted after this // command. @@ -1196,9 +1171,8 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* static int PerformCommandMove(CommandParameters& params) { size_t blocks = 0; - bool overlap = false; RangeSet tgt; - int status = LoadSrcTgtVersion3(params, tgt, &blocks, true, &overlap); + int status = LoadSrcTgtVersion3(params, &tgt, &blocks, true); if (status == -1) { LOG(ERROR) << "failed to read blocks for move"; @@ -1244,8 +1218,7 @@ static int PerformCommandStash(CommandParameters& params) { } const std::string& id = params.tokens[params.cpos++]; - size_t blocks = 0; - if (LoadStash(params, id, true, &blocks, params.buffer, false) == 0) { + if (LoadStash(params, id, true, ¶ms.buffer, false) == 0) { // Stash file already exists and has expected contents. Do not read from source again, as the // source may have been already overwritten during a previous attempt. return 0; @@ -1254,11 +1227,11 @@ static int PerformCommandStash(CommandParameters& params) { RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]); CHECK(static_cast(src)); - allocate(src.blocks() * BLOCKSIZE, params.buffer); - if (ReadBlocks(src, params.buffer, params.fd) == -1) { + size_t blocks = src.blocks(); + allocate(blocks * BLOCKSIZE, ¶ms.buffer); + if (ReadBlocks(src, ¶ms.buffer, params.fd) == -1) { return -1; } - blocks = src.blocks(); stash_map[id] = src; if (VerifyBlocks(id, params.buffer, blocks, true) != 0) { @@ -1277,10 +1250,6 @@ static int PerformCommandStash(CommandParameters& params) { LOG(INFO) << "stashing " << blocks << " blocks to " << id; int result = WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr); if (result == 0) { - if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) { - LOG(WARNING) << "Failed to update the last command file."; - } - params.stashed += blocks; } return result; @@ -1314,13 +1283,13 @@ static int PerformCommandZero(CommandParameters& params) { LOG(INFO) << " zeroing " << tgt.blocks() << " blocks"; - allocate(BLOCKSIZE, params.buffer); + allocate(BLOCKSIZE, ¶ms.buffer); memset(params.buffer.data(), 0, BLOCKSIZE); if (params.canwrite) { - for (const auto& range : tgt) { - off64_t offset = static_cast(range.first) * BLOCKSIZE; - size_t size = (range.second - range.first) * BLOCKSIZE; + for (const auto& [begin, end] : tgt) { + off64_t offset = static_cast(begin) * BLOCKSIZE; + size_t size = (end - begin) * BLOCKSIZE; if (!discard_blocks(params.fd, offset, size)) { return -1; } @@ -1329,8 +1298,10 @@ static int PerformCommandZero(CommandParameters& params) { return -1; } - for (size_t j = range.first; j < range.second; ++j) { - if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) { + for (size_t j = begin; j < end; ++j) { + if (!android::base::WriteFully(params.fd, params.buffer.data(), BLOCKSIZE)) { + failure_type = errno == EIO ? kEioFailure : kFwriteFailure; + PLOG(ERROR) << "Failed to write " << BLOCKSIZE << " bytes of data"; return -1; } } @@ -1401,8 +1372,7 @@ static int PerformCommandDiff(CommandParameters& params) { RangeSet tgt; size_t blocks = 0; - bool overlap = false; - int status = LoadSrcTgtVersion3(params, tgt, &blocks, false, &overlap); + int status = LoadSrcTgtVersion3(params, &tgt, &blocks, false); if (status == -1) { LOG(ERROR) << "failed to read blocks for diff"; @@ -1422,14 +1392,15 @@ static int PerformCommandDiff(CommandParameters& params) { if (status == 0) { LOG(INFO) << "patching " << blocks << " blocks to " << tgt.blocks(); Value patch_value( - VAL_BLOB, std::string(reinterpret_cast(params.patch_start + offset), len)); + Value::Type::BLOB, + std::string(reinterpret_cast(params.patch_start + offset), len)); RangeSinkWriter writer(params.fd, tgt); if (params.cmdname[0] == 'i') { // imgdiff if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value, std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1, std::placeholders::_2), - nullptr, nullptr) != 0) { + nullptr) != 0) { LOG(ERROR) << "Failed to apply image patch."; failure_type = kPatchApplicationFailure; return -1; @@ -1437,8 +1408,7 @@ static int PerformCommandDiff(CommandParameters& params) { } else { if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value, 0, std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1, - std::placeholders::_2), - nullptr) != 0) { + std::placeholders::_2)) != 0) { LOG(ERROR) << "Failed to apply bsdiff patch."; failure_type = kPatchApplicationFailure; return -1; @@ -1447,7 +1417,10 @@ static int PerformCommandDiff(CommandParameters& params) { // We expect the output of the patcher to fill the tgt ranges exactly. if (!writer.Finished()) { - LOG(ERROR) << "range sink underrun?"; + LOG(ERROR) << "Failed to fully write target blocks (range sink underrun): Missing " + << writer.AvailableSpace() << " bytes"; + failure_type = kPatchApplicationFailure; + return -1; } } else { LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " [" @@ -1492,41 +1465,169 @@ static int PerformCommandErase(CommandParameters& params) { if (params.canwrite) { LOG(INFO) << " erasing " << tgt.blocks() << " blocks"; - for (const auto& range : tgt) { - uint64_t blocks[2]; - // offset in bytes - blocks[0] = range.first * static_cast(BLOCKSIZE); - // length in bytes - blocks[1] = (range.second - range.first) * static_cast(BLOCKSIZE); + for (const auto& [begin, end] : tgt) { + off64_t offset = static_cast(begin) * BLOCKSIZE; + size_t size = (end - begin) * BLOCKSIZE; + if (!discard_blocks(params.fd, offset, size, true /* force */)) { + return -1; + } + } + } - if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) { - PLOG(ERROR) << "BLKDISCARD ioctl failed"; + return 0; +} + +static int PerformCommandAbort(CommandParameters&) { + LOG(INFO) << "Aborting as instructed"; + return -1; +} + +// Computes the hash_tree bytes based on the parameters, checks if the root hash of the tree +// matches the expected hash and writes the result to the specified range on the block_device. +// Hash_tree computation arguments: +// hash_tree_ranges +// source_ranges +// hash_algorithm +// salt_hex +// root_hash +static int PerformCommandComputeHashTree(CommandParameters& params) { + if (params.cpos + 5 != params.tokens.size()) { + LOG(ERROR) << "Invaild arguments count in hash computation " << params.cmdline; + return -1; + } + + // Expects the hash_tree data to be contiguous. + RangeSet hash_tree_ranges = RangeSet::Parse(params.tokens[params.cpos++]); + if (!hash_tree_ranges || hash_tree_ranges.size() != 1) { + LOG(ERROR) << "Invalid hash tree ranges in " << params.cmdline; + return -1; + } + + RangeSet source_ranges = RangeSet::Parse(params.tokens[params.cpos++]); + if (!source_ranges) { + LOG(ERROR) << "Invalid source ranges in " << params.cmdline; + return -1; + } + + auto hash_function = HashTreeBuilder::HashFunction(params.tokens[params.cpos++]); + if (hash_function == nullptr) { + LOG(ERROR) << "Invalid hash algorithm in " << params.cmdline; + return -1; + } + + std::vector salt; + std::string salt_hex = params.tokens[params.cpos++]; + if (salt_hex.empty() || !HashTreeBuilder::ParseBytesArrayFromString(salt_hex, &salt)) { + LOG(ERROR) << "Failed to parse salt in " << params.cmdline; + return -1; + } + + std::string expected_root_hash = params.tokens[params.cpos++]; + if (expected_root_hash.empty()) { + LOG(ERROR) << "Invalid root hash in " << params.cmdline; + return -1; + } + + // Starts the hash_tree computation. + HashTreeBuilder builder(BLOCKSIZE, hash_function); + if (!builder.Initialize(static_cast(source_ranges.blocks()) * BLOCKSIZE, salt)) { + LOG(ERROR) << "Failed to initialize hash tree computation, source " << source_ranges.ToString() + << ", salt " << salt_hex; + return -1; + } + + // Iterates through every block in the source_ranges and updates the hash tree structure + // accordingly. + for (const auto& [begin, end] : source_ranges) { + uint8_t buffer[BLOCKSIZE]; + if (!check_lseek(params.fd, static_cast(begin) * BLOCKSIZE, SEEK_SET)) { + PLOG(ERROR) << "Failed to seek to block: " << begin; + return -1; + } + + for (size_t i = begin; i < end; i++) { + if (!android::base::ReadFully(params.fd, buffer, BLOCKSIZE)) { + failure_type = errno == EIO ? kEioFailure : kFreadFailure; + LOG(ERROR) << "Failed to read data in " << begin << ":" << end; + return -1; + } + + if (!builder.Update(reinterpret_cast(buffer), BLOCKSIZE)) { + LOG(ERROR) << "Failed to update hash tree builder"; return -1; } } } + if (!builder.BuildHashTree()) { + LOG(ERROR) << "Failed to build hash tree"; + return -1; + } + + std::string root_hash_hex = HashTreeBuilder::BytesArrayToString(builder.root_hash()); + if (root_hash_hex != expected_root_hash) { + LOG(ERROR) << "Root hash of the verity hash tree doesn't match the expected value. Expected: " + << expected_root_hash << ", actual: " << root_hash_hex; + return -1; + } + + uint64_t write_offset = static_cast(hash_tree_ranges.GetBlockNumber(0)) * BLOCKSIZE; + if (params.canwrite && !builder.WriteHashTreeToFd(params.fd, write_offset)) { + LOG(ERROR) << "Failed to write hash tree to output"; + return -1; + } + + // TODO(xunchang) validates the written bytes + return 0; } -// Definitions for transfer list command functions -typedef int (*CommandFunction)(CommandParameters&); +using CommandFunction = std::function; -struct Command { - const char* name; - CommandFunction f; -}; +using CommandMap = std::unordered_map; + +static bool Sha1DevicePath(const std::string& path, uint8_t digest[SHA_DIGEST_LENGTH]) { + auto device_name = android::base::Basename(path); + auto dm_target_name_path = "/sys/block/" + device_name + "/dm/name"; + + struct stat sb; + if (stat(dm_target_name_path.c_str(), &sb) == 0) { + // This is a device mapper target. Use partition name as part of the hash instead. Do not + // include extents as part of the hash, because the size of a partition may be shrunk after + // the patches are applied. + std::string dm_target_name; + if (!android::base::ReadFileToString(dm_target_name_path, &dm_target_name)) { + PLOG(ERROR) << "Cannot read " << dm_target_name_path; + return false; + } + SHA1(reinterpret_cast(dm_target_name.data()), dm_target_name.size(), digest); + return true; + } + + if (errno != ENOENT) { + // This is a device mapper target, but its name cannot be retrieved. + PLOG(ERROR) << "Cannot get dm target name for " << path; + return false; + } + + // This doesn't appear to be a device mapper target, but if its name starts with dm-, something + // else might have gone wrong. + if (android::base::StartsWith(device_name, "dm-")) { + LOG(WARNING) << "Device " << path << " starts with dm- but is not mapped by device-mapper."; + } -// args: -// - block device (or file) to modify in-place -// - transfer list (blob) -// - new data stream (filename within package.zip) -// - patch stream (filename within package.zip, must be uncompressed) + // Stash directory should be different for each partition to avoid conflicts when updating + // multiple partitions at the same time, so we use the hash of the block device name as the base + // directory. + SHA1(reinterpret_cast(path.data()), path.size(), digest); + return true; +} static Value* PerformBlockImageUpdate(const char* name, State* state, const std::vector>& argv, - const Command* commands, size_t cmdcount, bool dryrun) { + const CommandMap& command_map, bool dryrun) { CommandParameters params = {}; + stash_map.clear(); params.canwrite = !dryrun; LOG(INFO) << "performing " << (dryrun ? "verification" : "update"); @@ -1545,87 +1646,98 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, return nullptr; } + // args: + // - block device (or file) to modify in-place + // - transfer list (blob) + // - new data stream (filename within package.zip) + // - patch stream (filename within package.zip, must be uncompressed) const std::unique_ptr& blockdev_filename = args[0]; const std::unique_ptr& transfer_list_value = args[1]; const std::unique_ptr& new_data_fn = args[2]; const std::unique_ptr& patch_data_fn = args[3]; - if (blockdev_filename->type != VAL_STRING) { + if (blockdev_filename->type != Value::Type::STRING) { ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); return StringValue(""); } - if (transfer_list_value->type != VAL_BLOB) { + if (transfer_list_value->type != Value::Type::BLOB) { ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name); return StringValue(""); } - if (new_data_fn->type != VAL_STRING) { + if (new_data_fn->type != Value::Type::STRING) { ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name); return StringValue(""); } - if (patch_data_fn->type != VAL_STRING) { + if (patch_data_fn->type != Value::Type::STRING) { ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string", name); return StringValue(""); } - UpdaterInfo* ui = static_cast(state->cookie); - if (ui == nullptr) { + auto updater = state->updater; + auto block_device_path = updater->FindBlockDeviceName(blockdev_filename->data); + if (block_device_path.empty()) { + LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name + << " failed."; return StringValue(""); } - FILE* cmd_pipe = ui->cmd_pipe; - ZipArchiveHandle za = ui->package_zip; - - if (cmd_pipe == nullptr || za == nullptr) { + ZipArchiveHandle za = updater->GetPackageHandle(); + if (za == nullptr) { return StringValue(""); } - ZipString path_data(patch_data_fn->data.c_str()); + std::string_view path_data(patch_data_fn->data); ZipEntry patch_entry; if (FindEntry(za, path_data, &patch_entry) != 0) { LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package"; return StringValue(""); } + params.patch_start = updater->GetMappedPackageAddress() + patch_entry.offset; - params.patch_start = ui->package_zip_addr + patch_entry.offset; - ZipString new_data(new_data_fn->data.c_str()); + std::string_view new_data(new_data_fn->data); ZipEntry new_entry; if (FindEntry(za, new_data, &new_entry) != 0) { LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package"; return StringValue(""); } - params.fd.reset(TEMP_FAILURE_RETRY(ota_open(blockdev_filename->data.c_str(), O_RDWR))); + params.fd.reset(TEMP_FAILURE_RETRY(open(block_device_path.c_str(), O_RDWR))); if (params.fd == -1) { - PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed"; + failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; + PLOG(ERROR) << "open \"" << block_device_path << "\" failed"; return StringValue(""); } - if (params.canwrite) { - params.nti.za = za; - params.nti.entry = new_entry; - params.nti.brotli_compressed = android::base::EndsWith(new_data_fn->data, ".br"); - if (params.nti.brotli_compressed) { - // Initialize brotli decoder state. - params.nti.brotli_decoder_state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); - } - params.nti.receiver_available = true; - - pthread_mutex_init(¶ms.nti.mu, nullptr); - pthread_cond_init(¶ms.nti.cv, nullptr); - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + uint8_t digest[SHA_DIGEST_LENGTH]; + if (!Sha1DevicePath(block_device_path, digest)) { + return StringValue(""); + } + params.stashbase = print_sha1(digest); - int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); - if (error != 0) { - PLOG(ERROR) << "pthread_create failed"; + // Possibly do return early on retry, by checking the marker. If the update on this partition has + // been finished (but interrupted at a later point), there could be leftover on /cache that would + // fail the no-op retry. + std::string updated_marker = GetStashFileName(params.stashbase + ".UPDATED", "", ""); + if (is_retry) { + struct stat sb; + int result = stat(updated_marker.c_str(), &sb); + if (result == 0) { + LOG(INFO) << "Skipping already updated partition " << block_device_path << " based on marker"; + return StringValue("t"); + } + } else { + // Delete the obsolete marker if any. + std::string err; + if (!android::base::RemoveFileIfExists(updated_marker, &err)) { + LOG(ERROR) << "Failed to remove partition updated marker " << updated_marker << ": " << err; return StringValue(""); } } + static constexpr size_t kTransferListHeaderLines = 4; std::vector lines = android::base::Split(transfer_list_value->data, "\n"); - if (lines.size() < 2) { - ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]", + if (lines.size() < kTransferListHeaderLines) { + ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]", lines.size()); return StringValue(""); } @@ -1649,13 +1761,6 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, return StringValue("t"); } - size_t start = 2; - if (lines.size() < 4) { - ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]", - lines.size()); - return StringValue(""); - } - // Third line is how many stash entries are needed simultaneously. LOG(INFO) << "maximum stash entries " << lines[2]; @@ -1667,15 +1772,38 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, return StringValue(""); } - int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase); + int res = CreateStash(state, stash_max_blocks, params.stashbase); if (res == -1) { return StringValue(""); } - params.createdstash = res; - // When performing an update, save the index and cmdline of the current command into - // the last_command_file if this command writes to the stash either explicitly of implicitly. + // Set up the new data writer. + if (params.canwrite) { + params.nti.za = za; + params.nti.entry = new_entry; + params.nti.brotli_compressed = android::base::EndsWith(new_data_fn->data, ".br"); + if (params.nti.brotli_compressed) { + // Initialize brotli decoder state. + params.nti.brotli_decoder_state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); + } + params.nti.receiver_available = true; + + pthread_mutex_init(¶ms.nti.mu, nullptr); + pthread_cond_init(¶ms.nti.cv, nullptr); + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); + if (error != 0) { + LOG(ERROR) << "pthread_create failed: " << strerror(error); + return StringValue(""); + } + } + + // When performing an update, save the index and cmdline of the current command into the + // last_command_file. // Upon resuming an update, read the saved index first; then // 1. In verification mode, check if the 'move' or 'diff' commands before the saved index has // the expected target blocks already. If not, these commands cannot be skipped and we need @@ -1684,92 +1812,90 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, // 2. In update mode, skip all commands before the saved index. Therefore, we can avoid deleting // stashes with duplicate id unintentionally (b/69858743); and also speed up the update. // If an update succeeds or is unresumable, delete the last_command_file. - int saved_last_command_index; + bool skip_executed_command = true; + size_t saved_last_command_index; if (!ParseLastCommandFile(&saved_last_command_index)) { DeleteLastCommandFile(); - // We failed to parse the last command, set it explicitly to -1. - saved_last_command_index = -1; - } - - start += 2; - - // Build a map of the available commands - std::unordered_map cmd_map; - for (size_t i = 0; i < cmdcount; ++i) { - if (cmd_map.find(commands[i].name) != cmd_map.end()) { - LOG(ERROR) << "Error: command [" << commands[i].name << "] already exists in the cmd map."; - return StringValue(strdup("")); - } - cmd_map[commands[i].name] = &commands[i]; + // We failed to parse the last command. Disallow skipping executed commands. + skip_executed_command = false; } int rc = -1; // Subsequent lines are all individual transfer commands - for (size_t i = start; i < lines.size(); i++) { + for (size_t i = kTransferListHeaderLines; i < lines.size(); i++) { const std::string& line = lines[i]; if (line.empty()) continue; + size_t cmdindex = i - kTransferListHeaderLines; params.tokens = android::base::Split(line, " "); params.cpos = 0; - if (i - start > std::numeric_limits::max()) { - params.cmdindex = -1; - } else { - params.cmdindex = i - start; - } - params.cmdname = params.tokens[params.cpos++].c_str(); - params.cmdline = line.c_str(); + params.cmdname = params.tokens[params.cpos++]; + params.cmdline = line; params.target_verified = false; - if (cmd_map.find(params.cmdname) == cmd_map.end()) { + Command::Type cmd_type = Command::ParseType(params.cmdname); + if (cmd_type == Command::Type::LAST) { LOG(ERROR) << "unexpected command [" << params.cmdname << "]"; goto pbiudone; } - const Command* cmd = cmd_map[params.cmdname]; + const CommandFunction& performer = command_map.at(cmd_type); // Skip the command if we explicitly set the corresponding function pointer to nullptr, e.g. // "erase" during block_image_verify. - if (cmd->f == nullptr) { + if (performer == nullptr) { LOG(DEBUG) << "skip executing command [" << line << "]"; continue; } - // Skip all commands before the saved last command index when resuming an update. - if (params.canwrite && params.cmdindex != -1 && params.cmdindex <= saved_last_command_index) { - LOG(INFO) << "Skipping already executed command: " << params.cmdindex + // Skip all commands before the saved last command index when resuming an update, except for + // "new" command. Because new commands read in the data sequentially. + if (params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index && + cmd_type != Command::Type::NEW) { + LOG(INFO) << "Skipping already executed command: " << cmdindex << ", last executed command for previous update: " << saved_last_command_index; continue; } - if (cmd->f(params) == -1) { + if (performer(params) == -1) { LOG(ERROR) << "failed to execute command [" << line << "]"; + if (cmd_type == Command::Type::COMPUTE_HASH_TREE && failure_type == kNoCause) { + failure_type = kHashTreeComputationFailure; + } goto pbiudone; } - // In verify mode, check if the commands before the saved last_command_index have been - // executed correctly. If some target blocks have unexpected contents, delete the last command - // file so that we will resume the update from the first command in the transfer list. - if (!params.canwrite && saved_last_command_index != -1 && params.cmdindex != -1 && - params.cmdindex <= saved_last_command_index) { + // In verify mode, check if the commands before the saved last_command_index have been executed + // correctly. If some target blocks have unexpected contents, delete the last command file so + // that we will resume the update from the first command in the transfer list. + if (!params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index) { // TODO(xunchang) check that the cmdline of the saved index is correct. - std::string cmdname = std::string(params.cmdname); - if ((cmdname == "move" || cmdname == "bsdiff" || cmdname == "imgdiff") && + if ((cmd_type == Command::Type::MOVE || cmd_type == Command::Type::BSDIFF || + cmd_type == Command::Type::IMGDIFF) && !params.target_verified) { LOG(WARNING) << "Previously executed command " << saved_last_command_index << ": " << params.cmdline << " doesn't produce expected target blocks."; - saved_last_command_index = -1; + skip_executed_command = false; DeleteLastCommandFile(); } } + if (params.canwrite) { - if (ota_fsync(params.fd) == -1) { - failure_type = kFsyncFailure; + if (fsync(params.fd) == -1) { + failure_type = errno == EIO ? kEioFailure : kFsyncFailure; PLOG(ERROR) << "fsync failed"; goto pbiudone; } - fprintf(cmd_pipe, "set_progress %.4f\n", static_cast(params.written) / total_blocks); - fflush(cmd_pipe); + + if (!UpdateLastCommandIndex(cmdindex, params.cmdline)) { + LOG(WARNING) << "Failed to update the last command file."; + } + + updater->WriteToCommandPipe( + android::base::StringPrintf("set_progress %.4f", + static_cast(params.written) / total_blocks), + true); } } @@ -1794,16 +1920,27 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, LOG(INFO) << "stashed " << params.stashed << " blocks"; LOG(INFO) << "max alloc needed was " << params.buffer.size(); - const char* partition = strrchr(blockdev_filename->data.c_str(), '/'); + const char* partition = strrchr(block_device_path.c_str(), '/'); if (partition != nullptr && *(partition + 1) != 0) { - fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE); - fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE); - fflush(cmd_pipe); + updater->WriteToCommandPipe( + android::base::StringPrintf("log bytes_written_%s: %" PRIu64, partition + 1, + static_cast(params.written) * BLOCKSIZE)); + updater->WriteToCommandPipe( + android::base::StringPrintf("log bytes_stashed_%s: %" PRIu64, partition + 1, + static_cast(params.stashed) * BLOCKSIZE), + true); } // Delete stash only after successfully completing the update, as it may contain blocks needed // to complete the update later. DeleteStash(params.stashbase); DeleteLastCommandFile(); + + // Create a marker on /cache partition, which allows skipping the update on this partition on + // retry. The marker will be removed once booting into normal boot, or before starting next + // fresh install. + if (!SetUpdatedMarker(updated_marker)) { + LOG(WARNING) << "Failed to set updated marker; continuing"; + } } pthread_mutex_destroy(¶ms.nti.mu); @@ -1812,8 +1949,8 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, LOG(INFO) << "verified partition contents; update may be resumed"; } - if (ota_fsync(params.fd) == -1) { - failure_type = kFsyncFailure; + if (fsync(params.fd) == -1) { + failure_type = errno == EIO ? kEioFailure : kFsyncFailure; PLOG(ERROR) << "fsync failed"; } // params.fd will be automatically closed because it's a unique_fd. @@ -1886,38 +2023,46 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, */ Value* BlockImageVerifyFn(const char* name, State* state, const std::vector>& argv) { - // Commands which are not tested are set to nullptr to skip them completely - const Command commands[] = { - { "bsdiff", PerformCommandDiff }, - { "erase", nullptr }, - { "free", PerformCommandFree }, - { "imgdiff", PerformCommandDiff }, - { "move", PerformCommandMove }, - { "new", nullptr }, - { "stash", PerformCommandStash }, - { "zero", nullptr } - }; - - // Perform a dry run without writing to test if an update can proceed - return PerformBlockImageUpdate(name, state, argv, commands, - sizeof(commands) / sizeof(commands[0]), true); + // Commands which are not allowed are set to nullptr to skip them completely. + const CommandMap command_map{ + // clang-format off + { Command::Type::ABORT, PerformCommandAbort }, + { Command::Type::BSDIFF, PerformCommandDiff }, + { Command::Type::COMPUTE_HASH_TREE, nullptr }, + { Command::Type::ERASE, nullptr }, + { Command::Type::FREE, PerformCommandFree }, + { Command::Type::IMGDIFF, PerformCommandDiff }, + { Command::Type::MOVE, PerformCommandMove }, + { Command::Type::NEW, nullptr }, + { Command::Type::STASH, PerformCommandStash }, + { Command::Type::ZERO, nullptr }, + // clang-format on + }; + CHECK_EQ(static_cast(Command::Type::LAST), command_map.size()); + + // Perform a dry run without writing to test if an update can proceed. + return PerformBlockImageUpdate(name, state, argv, command_map, true); } Value* BlockImageUpdateFn(const char* name, State* state, const std::vector>& argv) { - const Command commands[] = { - { "bsdiff", PerformCommandDiff }, - { "erase", PerformCommandErase }, - { "free", PerformCommandFree }, - { "imgdiff", PerformCommandDiff }, - { "move", PerformCommandMove }, - { "new", PerformCommandNew }, - { "stash", PerformCommandStash }, - { "zero", PerformCommandZero } - }; - - return PerformBlockImageUpdate(name, state, argv, commands, - sizeof(commands) / sizeof(commands[0]), false); + const CommandMap command_map{ + // clang-format off + { Command::Type::ABORT, PerformCommandAbort }, + { Command::Type::BSDIFF, PerformCommandDiff }, + { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree }, + { Command::Type::ERASE, PerformCommandErase }, + { Command::Type::FREE, PerformCommandFree }, + { Command::Type::IMGDIFF, PerformCommandDiff }, + { Command::Type::MOVE, PerformCommandMove }, + { Command::Type::NEW, PerformCommandNew }, + { Command::Type::STASH, PerformCommandStash }, + { Command::Type::ZERO, PerformCommandZero }, + // clang-format on + }; + CHECK_EQ(static_cast(Command::Type::LAST), command_map.size()); + + return PerformBlockImageUpdate(name, state, argv, command_map, false); } Value* RangeSha1Fn(const char* name, State* state, const std::vector>& argv) { @@ -1934,18 +2079,26 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector& blockdev_filename = args[0]; const std::unique_ptr& ranges = args[1]; - if (blockdev_filename->type != VAL_STRING) { + if (blockdev_filename->type != Value::Type::STRING) { ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); return StringValue(""); } - if (ranges->type != VAL_STRING) { + if (ranges->type != Value::Type::STRING) { ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); return StringValue(""); } - android::base::unique_fd fd(ota_open(blockdev_filename->data.c_str(), O_RDWR)); + auto block_device_path = state->updater->FindBlockDeviceName(blockdev_filename->data); + if (block_device_path.empty()) { + LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name + << " failed."; + return StringValue(""); + } + + android::base::unique_fd fd(open(block_device_path.c_str(), O_RDWR)); if (fd == -1) { - ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", blockdev_filename->data.c_str(), + CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure; + ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -1957,16 +2110,17 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector buffer(BLOCKSIZE); - for (const auto& range : rs) { - if (!check_lseek(fd, static_cast(range.first) * BLOCKSIZE, SEEK_SET)) { - ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(), + for (const auto& [begin, end] : rs) { + if (!check_lseek(fd, static_cast(begin) * BLOCKSIZE, SEEK_SET)) { + ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } - for (size_t j = range.first; j < range.second; ++j) { - if (read_all(fd, buffer, BLOCKSIZE) == -1) { - ErrorAbort(state, kFreadFailure, "failed to read %s: %s", blockdev_filename->data.c_str(), + for (size_t j = begin; j < end; ++j) { + if (!android::base::ReadFully(fd, buffer.data(), BLOCKSIZE)) { + CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure; + ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2000,14 +2154,22 @@ Value* CheckFirstBlockFn(const char* name, State* state, const std::unique_ptr& arg_filename = args[0]; - if (arg_filename->type != VAL_STRING) { + if (arg_filename->type != Value::Type::STRING) { ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); return StringValue(""); } - android::base::unique_fd fd(ota_open(arg_filename->data.c_str(), O_RDONLY)); + auto block_device_path = state->updater->FindBlockDeviceName(arg_filename->data); + if (block_device_path.empty()) { + LOG(ERROR) << "Block device path for " << arg_filename->data << " not found. " << name + << " failed."; + return StringValue(""); + } + + android::base::unique_fd fd(open(block_device_path.c_str(), O_RDONLY)); if (fd == -1) { - ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data.c_str(), + CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure; + ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2015,8 +2177,9 @@ Value* CheckFirstBlockFn(const char* name, State* state, RangeSet blk0(std::vector{ Range{ 0, 1 } }); std::vector block0_buffer(BLOCKSIZE); - if (ReadBlocks(blk0, block0_buffer, fd) == -1) { - ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(), + if (ReadBlocks(blk0, &block0_buffer, fd) == -1) { + CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure; + ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2032,8 +2195,10 @@ Value* CheckFirstBlockFn(const char* name, State* state, uint16_t mount_count = *reinterpret_cast(&block0_buffer[0x400 + 0x34]); if (mount_count > 0) { - uiPrintf(state, "Device was remounted R/W %" PRIu16 " times", mount_count); - uiPrintf(state, "Last remount happened on %s", ctime(&mount_time)); + state->updater->UiPrint( + android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count)); + state->updater->UiPrint( + android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time))); } return StringValue("t"); @@ -2055,11 +2220,11 @@ Value* BlockImageRecoverFn(const char* name, State* state, const std::unique_ptr& filename = args[0]; const std::unique_ptr& ranges = args[1]; - if (filename->type != VAL_STRING) { + if (filename->type != Value::Type::STRING) { ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); return StringValue(""); } - if (ranges->type != VAL_STRING) { + if (ranges->type != Value::Type::STRING) { ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); return StringValue(""); } @@ -2069,14 +2234,21 @@ Value* BlockImageRecoverFn(const char* name, State* state, return StringValue(""); } + auto block_device_path = state->updater->FindBlockDeviceName(filename->data); + if (block_device_path.empty()) { + LOG(ERROR) << "Block device path for " << filename->data << " not found. " << name + << " failed."; + return StringValue(""); + } + // Output notice to log when recover is attempted - LOG(INFO) << filename->data << " image corrupted, attempting to recover..."; + LOG(INFO) << block_device_path << " image corrupted, attempting to recover..."; // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read - fec::io fh(filename->data, O_RDWR); + fec::io fh(block_device_path, O_RDWR); if (!fh) { - ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(), + ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2093,8 +2265,8 @@ Value* BlockImageRecoverFn(const char* name, State* state, } uint8_t buffer[BLOCKSIZE]; - for (const auto& range : rs) { - for (size_t j = range.first; j < range.second; ++j) { + for (const auto& [begin, end] : rs) { + for (size_t j = begin; j < end; ++j) { // Stay within the data area, libfec validates and corrects metadata if (status.data_size <= static_cast(j) * BLOCKSIZE) { continue; @@ -2102,7 +2274,7 @@ Value* BlockImageRecoverFn(const char* name, State* state, if (fh.pread(buffer, BLOCKSIZE, static_cast(j) * BLOCKSIZE) != BLOCKSIZE) { ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s", - filename->data.c_str(), j, strerror(errno)); + block_device_path.c_str(), j, strerror(errno)); return StringValue(""); } @@ -2118,7 +2290,7 @@ Value* BlockImageRecoverFn(const char* name, State* state, // read and check if the errors field value has increased. } } - LOG(INFO) << "..." << filename->data << " image recovered successfully."; + LOG(INFO) << "..." << block_device_path << " image recovered successfully."; return StringValue("t"); } diff --git a/updater/build_info.cpp b/updater/build_info.cpp new file mode 100644 index 0000000000..f168008ec5 --- /dev/null +++ b/updater/build_info.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/build_info.h" + +#include + +#include +#include + +#include +#include +#include + +#include "updater/target_files.h" + +bool BuildInfo::ParseTargetFile(const std::string_view target_file_path, bool extracted_input) { + TargetFile target_file(std::string(target_file_path), extracted_input); + if (!target_file.Open()) { + return false; + } + + if (!target_file.GetBuildProps(&build_props_)) { + return false; + } + + std::vector fstab_info_list; + if (!target_file.ParseFstabInfo(&fstab_info_list)) { + return false; + } + + for (const auto& fstab_info : fstab_info_list) { + for (const auto& directory : { "IMAGES", "RADIO" }) { + std::string entry_name = directory + fstab_info.mount_point + ".img"; + if (!target_file.EntryExists(entry_name)) { + LOG(WARNING) << "Failed to find the image entry in the target file: " << entry_name; + continue; + } + + temp_files_.emplace_back(work_dir_); + auto& image_file = temp_files_.back(); + if (!target_file.ExtractImage(entry_name, fstab_info, work_dir_, &image_file)) { + LOG(ERROR) << "Failed to set up source image files."; + return false; + } + + std::string mapped_path = image_file.path; + // Rename the images to more readable ones if we want to keep the image. + if (keep_images_) { + mapped_path = work_dir_ + fstab_info.mount_point + ".img"; + image_file.release(); + if (rename(image_file.path, mapped_path.c_str()) != 0) { + PLOG(ERROR) << "Failed to rename " << image_file.path << " to " << mapped_path; + return false; + } + } + + LOG(INFO) << "Mounted " << fstab_info.mount_point << "\nMapping: " << fstab_info.blockdev_name + << " to " << mapped_path; + + blockdev_map_.emplace( + fstab_info.blockdev_name, + FakeBlockDevice(fstab_info.blockdev_name, fstab_info.mount_point, mapped_path)); + break; + } + } + + return true; +} + +std::string BuildInfo::GetProperty(const std::string_view key, + const std::string_view default_value) const { + // The logic to parse the ro.product properties should be in line with the generation script. + // More details in common.py BuildInfo.GetBuildProp. + // TODO(xunchang) handle the oem property and the source order defined in + // ro.product.property_source_order + const std::set> ro_product_props = { + "ro.product.brand", "ro.product.device", "ro.product.manufacturer", "ro.product.model", + "ro.product.name" + }; + const std::vector source_order = { + "product", "odm", "vendor", "system_ext", "system", + }; + if (ro_product_props.find(key) != ro_product_props.end()) { + std::string_view key_suffix(key); + CHECK(android::base::ConsumePrefix(&key_suffix, "ro.product")); + for (const auto& source : source_order) { + std::string resolved_key = "ro.product." + source + std::string(key_suffix); + if (auto entry = build_props_.find(resolved_key); entry != build_props_.end()) { + return entry->second; + } + } + LOG(WARNING) << "Failed to find property: " << key; + return std::string(default_value); + } else if (key == "ro.build.fingerprint") { + // clang-format off + return android::base::StringPrintf("%s/%s/%s:%s/%s/%s:%s/%s", + GetProperty("ro.product.brand", "").c_str(), + GetProperty("ro.product.name", "").c_str(), + GetProperty("ro.product.device", "").c_str(), + GetProperty("ro.build.version.release", "").c_str(), + GetProperty("ro.build.id", "").c_str(), + GetProperty("ro.build.version.incremental", "").c_str(), + GetProperty("ro.build.type", "").c_str(), + GetProperty("ro.build.tags", "").c_str()); + // clang-format on + } + + auto entry = build_props_.find(key); + if (entry == build_props_.end()) { + LOG(WARNING) << "Failed to find property: " << key; + return std::string(default_value); + } + + return entry->second; +} + +std::string BuildInfo::FindBlockDeviceName(const std::string_view name) const { + auto entry = blockdev_map_.find(name); + if (entry == blockdev_map_.end()) { + LOG(WARNING) << "Failed to find path to block device " << name; + return ""; + } + + return entry->second.mounted_file_path; +} diff --git a/updater/commands.cpp b/updater/commands.cpp new file mode 100644 index 0000000000..aed63369cd --- /dev/null +++ b/updater/commands.cpp @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "private/commands.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "otautil/print_sha1.h" +#include "otautil/rangeset.h" + +using namespace std::string_literals; + +bool Command::abort_allowed_ = false; + +Command::Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info) + : type_(type), + index_(index), + cmdline_(std::move(cmdline)), + hash_tree_info_(std::move(hash_tree_info)) { + CHECK(type == Type::COMPUTE_HASH_TREE); +} + +Command::Type Command::ParseType(const std::string& type_str) { + if (type_str == "abort") { + if (!abort_allowed_) { + LOG(ERROR) << "ABORT disallowed"; + return Type::LAST; + } + return Type::ABORT; + } else if (type_str == "bsdiff") { + return Type::BSDIFF; + } else if (type_str == "compute_hash_tree") { + return Type::COMPUTE_HASH_TREE; + } else if (type_str == "erase") { + return Type::ERASE; + } else if (type_str == "free") { + return Type::FREE; + } else if (type_str == "imgdiff") { + return Type::IMGDIFF; + } else if (type_str == "move") { + return Type::MOVE; + } else if (type_str == "new") { + return Type::NEW; + } else if (type_str == "stash") { + return Type::STASH; + } else if (type_str == "zero") { + return Type::ZERO; + } + return Type::LAST; +}; + +bool Command::ParseTargetInfoAndSourceInfo(const std::vector& tokens, + const std::string& tgt_hash, TargetInfo* target, + const std::string& src_hash, SourceInfo* source, + std::string* err) { + // We expect the given args (in 'tokens' vector) in one of the following formats. + // + // - <[stash_id:location] ...> + // (loads data from stashes only) + // + // + // (loads data from source image only) + // + // <[stash_id:location] ...> + // (loads data from both of source image and stashes) + + // At least it needs to provide three args: , and "-"/. + if (tokens.size() < 3) { + *err = "invalid number of args"; + return false; + } + + size_t pos = 0; + RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]); + if (!tgt_ranges) { + *err = "invalid target ranges"; + return false; + } + *target = TargetInfo(tgt_hash, tgt_ranges); + + // + const std::string& token = tokens[pos++]; + size_t src_blocks; + if (!android::base::ParseUint(token, &src_blocks)) { + *err = "invalid src_block_count \""s + token + "\""; + return false; + } + + RangeSet src_ranges; + RangeSet src_ranges_location; + // "-" or [] + if (tokens[pos] == "-") { + // no source ranges, only stashes + pos++; + } else { + src_ranges = RangeSet::Parse(tokens[pos++]); + if (!src_ranges) { + *err = "invalid source ranges"; + return false; + } + + if (pos >= tokens.size()) { + // No stashes, only source ranges. + SourceInfo result(src_hash, src_ranges, {}, {}); + + // Sanity check the block count. + if (result.blocks() != src_blocks) { + *err = + android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(), + src_ranges.ToString().c_str(), src_blocks); + return false; + } + + *source = result; + return true; + } + + src_ranges_location = RangeSet::Parse(tokens[pos++]); + if (!src_ranges_location) { + *err = "invalid source ranges location"; + return false; + } + } + + // <[stash_id:stash_location]> + std::vector stashes; + while (pos < tokens.size()) { + // Each word is a an index into the stash table, a colon, and then a RangeSet describing where + // in the source block that stashed data should go. + std::vector pairs = android::base::Split(tokens[pos++], ":"); + if (pairs.size() != 2) { + *err = "invalid stash info"; + return false; + } + RangeSet stash_location = RangeSet::Parse(pairs[1]); + if (!stash_location) { + *err = "invalid stash location"; + return false; + } + stashes.emplace_back(pairs[0], stash_location); + } + + SourceInfo result(src_hash, src_ranges, src_ranges_location, stashes); + if (src_blocks != result.blocks()) { + *err = android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(), + src_ranges.ToString().c_str(), src_blocks); + return false; + } + + *source = result; + return true; +} + +Command Command::Parse(const std::string& line, size_t index, std::string* err) { + std::vector tokens = android::base::Split(line, " "); + size_t pos = 0; + // tokens.size() will be 1 at least. + Type op = ParseType(tokens[pos++]); + if (op == Type::LAST) { + *err = "invalid type"; + return {}; + } + + PatchInfo patch_info; + TargetInfo target_info; + SourceInfo source_info; + StashInfo stash_info; + + if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) { + // zero/new/erase + if (pos + 1 != tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)", + tokens.size() - pos); + return {}; + } + RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]); + if (!tgt_ranges) { + return {}; + } + static const std::string kUnknownHash{ "unknown-hash" }; + target_info = TargetInfo(kUnknownHash, tgt_ranges); + } else if (op == Type::STASH) { + // stash + if (pos + 2 != tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 2)", + tokens.size() - pos); + return {}; + } + const std::string& id = tokens[pos++]; + RangeSet src_ranges = RangeSet::Parse(tokens[pos++]); + if (!src_ranges) { + *err = "invalid token"; + return {}; + } + stash_info = StashInfo(id, src_ranges); + } else if (op == Type::FREE) { + // free + if (pos + 1 != tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)", + tokens.size() - pos); + return {}; + } + stash_info = StashInfo(tokens[pos++], {}); + } else if (op == Type::MOVE) { + // + if (pos + 1 > tokens.size()) { + *err = "missing hash"; + return {}; + } + std::string hash = tokens[pos++]; + if (!ParseTargetInfoAndSourceInfo( + std::vector(tokens.cbegin() + pos, tokens.cend()), hash, &target_info, + hash, &source_info, err)) { + return {}; + } + } else if (op == Type::BSDIFF || op == Type::IMGDIFF) { + // + if (pos + 4 > tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 4+)", + tokens.size() - pos); + return {}; + } + size_t offset; + size_t length; + if (!android::base::ParseUint(tokens[pos++], &offset) || + !android::base::ParseUint(tokens[pos++], &length)) { + *err = "invalid patch offset/length"; + return {}; + } + patch_info = PatchInfo(offset, length); + + std::string src_hash = tokens[pos++]; + std::string dst_hash = tokens[pos++]; + if (!ParseTargetInfoAndSourceInfo( + std::vector(tokens.cbegin() + pos, tokens.cend()), dst_hash, &target_info, + src_hash, &source_info, err)) { + return {}; + } + } else if (op == Type::ABORT) { + // No-op, other than sanity checking the input args. + if (pos != tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 0)", + tokens.size() - pos); + return {}; + } + } else if (op == Type::COMPUTE_HASH_TREE) { + // + if (pos + 5 != tokens.size()) { + *err = android::base::StringPrintf("invalid number of args: %zu (expected 5)", + tokens.size() - pos); + return {}; + } + + // Expects the hash_tree data to be contiguous. + RangeSet hash_tree_ranges = RangeSet::Parse(tokens[pos++]); + if (!hash_tree_ranges || hash_tree_ranges.size() != 1) { + *err = "invalid hash tree ranges in: " + line; + return {}; + } + + RangeSet source_ranges = RangeSet::Parse(tokens[pos++]); + if (!source_ranges) { + *err = "invalid source ranges in: " + line; + return {}; + } + + std::string hash_algorithm = tokens[pos++]; + std::string salt_hex = tokens[pos++]; + std::string root_hash = tokens[pos++]; + if (hash_algorithm.empty() || salt_hex.empty() || root_hash.empty()) { + *err = "invalid hash tree arguments in " + line; + return {}; + } + + HashTreeInfo hash_tree_info(std::move(hash_tree_ranges), std::move(source_ranges), + std::move(hash_algorithm), std::move(salt_hex), + std::move(root_hash)); + return Command(op, index, line, std::move(hash_tree_info)); + } else { + *err = "invalid op"; + return {}; + } + + return Command(op, index, line, patch_info, target_info, source_info, stash_info); +} + +bool SourceInfo::Overlaps(const TargetInfo& target) const { + return ranges_.Overlaps(target.ranges()); +} + +// Moves blocks in the 'source' vector to the specified locations (as in 'locs') in the 'dest' +// vector. Note that source and dest may be the same buffer. +static void MoveRange(std::vector* dest, const RangeSet& locs, + const std::vector& source, size_t block_size) { + const uint8_t* from = source.data(); + uint8_t* to = dest->data(); + size_t start = locs.blocks(); + // Must do the movement backward. + for (auto it = locs.crbegin(); it != locs.crend(); it++) { + size_t blocks = it->second - it->first; + start -= blocks; + memmove(to + (it->first * block_size), from + (start * block_size), blocks * block_size); + } +} + +bool SourceInfo::ReadAll( + std::vector* buffer, size_t block_size, + const std::function*)>& block_reader, + const std::function*)>& stash_reader) const { + if (buffer->size() < blocks() * block_size) { + return false; + } + + // Read in the source ranges. + if (ranges_) { + if (block_reader(ranges_, buffer) != 0) { + return false; + } + if (location_) { + MoveRange(buffer, location_, *buffer, block_size); + } + } + + // Read in the stashes. + for (const StashInfo& stash : stashes_) { + std::vector stash_buffer(stash.blocks() * block_size); + if (stash_reader(stash.id(), &stash_buffer) != 0) { + return false; + } + MoveRange(buffer, stash.ranges(), stash_buffer, block_size); + } + return true; +} + +void SourceInfo::DumpBuffer(const std::vector& buffer, size_t block_size) const { + LOG(INFO) << "Dumping hashes in hex for " << ranges_.blocks() << " source blocks"; + + const RangeSet& location = location_ ? location_ : RangeSet({ Range{ 0, ranges_.blocks() } }); + for (size_t i = 0; i < ranges_.blocks(); i++) { + size_t block_num = ranges_.GetBlockNumber(i); + size_t buffer_index = location.GetBlockNumber(i); + CHECK_LE((buffer_index + 1) * block_size, buffer.size()); + + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(buffer.data() + buffer_index * block_size, block_size, digest); + std::string hexdigest = print_sha1(digest); + LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; + } +} + +std::ostream& operator<<(std::ostream& os, const Command& command) { + os << command.index() << ": " << command.cmdline(); + return os; +} + +std::ostream& operator<<(std::ostream& os, const TargetInfo& target) { + os << target.blocks() << " blocks (" << target.hash_ << "): " << target.ranges_.ToString(); + return os; +} + +std::ostream& operator<<(std::ostream& os, const StashInfo& stash) { + os << stash.blocks() << " blocks (" << stash.id_ << "): " << stash.ranges_.ToString(); + return os; +} + +std::ostream& operator<<(std::ostream& os, const SourceInfo& source) { + os << source.blocks_ << " blocks (" << source.hash_ << "): "; + if (source.ranges_) { + os << source.ranges_.ToString(); + if (source.location_) { + os << " (location: " << source.location_.ToString() << ")"; + } + } + if (!source.stashes_.empty()) { + os << " " << source.stashes_.size() << " stash(es)"; + } + return os; +} + +TransferList TransferList::Parse(const std::string& transfer_list_str, std::string* err) { + TransferList result{}; + + std::vector lines = android::base::Split(transfer_list_str, "\n"); + if (lines.size() < kTransferListHeaderLines) { + *err = android::base::StringPrintf("too few lines in the transfer list [%zu]", lines.size()); + return TransferList{}; + } + + // First line in transfer list is the version number. + if (!android::base::ParseInt(lines[0], &result.version_, 3, 4)) { + *err = "unexpected transfer list version ["s + lines[0] + "]"; + return TransferList{}; + } + + // Second line in transfer list is the total number of blocks we expect to write. + if (!android::base::ParseUint(lines[1], &result.total_blocks_)) { + *err = "unexpected block count ["s + lines[1] + "]"; + return TransferList{}; + } + + // Third line is how many stash entries are needed simultaneously. + if (!android::base::ParseUint(lines[2], &result.stash_max_entries_)) { + return TransferList{}; + } + + // Fourth line is the maximum number of blocks that will be stashed simultaneously. + if (!android::base::ParseUint(lines[3], &result.stash_max_blocks_)) { + *err = "unexpected maximum stash blocks ["s + lines[3] + "]"; + return TransferList{}; + } + + // Subsequent lines are all individual transfer commands. + for (size_t i = kTransferListHeaderLines; i < lines.size(); i++) { + const std::string& line = lines[i]; + if (line.empty()) continue; + + size_t cmdindex = i - kTransferListHeaderLines; + std::string parsing_error; + Command command = Command::Parse(line, cmdindex, &parsing_error); + if (!command) { + *err = android::base::StringPrintf("Failed to parse command %zu [%s]: %s", cmdindex, + line.c_str(), parsing_error.c_str()); + return TransferList{}; + } + result.commands_.push_back(command); + } + + return result; +} diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp new file mode 100644 index 0000000000..a340116feb --- /dev/null +++ b/updater/dynamic_partitions.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/dynamic_partitions.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "edify/expr.h" +#include "edify/updater_runtime_interface.h" +#include "otautil/error_code.h" +#include "otautil/paths.h" +#include "private/utils.h" + +static std::vector ReadStringArgs(const char* name, State* state, + const std::vector>& argv, + const std::vector& arg_names) { + if (argv.size() != arg_names.size()) { + ErrorAbort(state, kArgsParsingFailure, "%s expects %zu arguments, got %zu", name, + arg_names.size(), argv.size()); + return {}; + } + + std::vector> args; + if (!ReadValueArgs(state, argv, &args)) { + return {}; + } + + CHECK_EQ(args.size(), arg_names.size()); + + for (size_t i = 0; i < arg_names.size(); ++i) { + if (args[i]->type != Value::Type::STRING) { + ErrorAbort(state, kArgsParsingFailure, "%s argument to %s must be string", + arg_names[i].c_str(), name); + return {}; + } + } + + std::vector ret; + std::transform(args.begin(), args.end(), std::back_inserter(ret), + [](const auto& arg) { return arg->data; }); + return ret; +} + +Value* UnmapPartitionFn(const char* name, State* state, + const std::vector>& argv) { + auto args = ReadStringArgs(name, state, argv, { "name" }); + if (args.empty()) return StringValue(""); + + auto updater_runtime = state->updater->GetRuntime(); + return updater_runtime->UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") + : StringValue(""); +} + +Value* MapPartitionFn(const char* name, State* state, + const std::vector>& argv) { + auto args = ReadStringArgs(name, state, argv, { "name" }); + if (args.empty()) return StringValue(""); + + std::string path; + auto updater_runtime = state->updater->GetRuntime(); + bool result = updater_runtime->MapPartitionOnDeviceMapper(args[0], &path); + return result ? StringValue(path) : StringValue(""); +} + +static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED"; + +Value* UpdateDynamicPartitionsFn(const char* name, State* state, + const std::vector>& argv) { + if (argv.size() != 1) { + ErrorAbort(state, kArgsParsingFailure, "%s expects 1 arguments, got %zu", name, argv.size()); + return StringValue(""); + } + std::vector> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } + const std::unique_ptr& op_list_value = args[0]; + if (op_list_value->type != Value::Type::BLOB) { + ErrorAbort(state, kArgsParsingFailure, "op_list argument to %s must be blob", name); + return StringValue(""); + } + + std::string updated_marker = Paths::Get().stash_directory_base() + kMetadataUpdatedMarker; + if (state->is_retry) { + struct stat sb; + int result = stat(updated_marker.c_str(), &sb); + if (result == 0) { + LOG(INFO) << "Skipping already updated dynamic partition metadata based on marker"; + return StringValue("t"); + } + } else { + // Delete the obsolete marker if any. + std::string err; + if (!android::base::RemoveFileIfExists(updated_marker, &err)) { + LOG(ERROR) << "Failed to remove dynamic partition metadata updated marker " << updated_marker + << ": " << err; + return StringValue(""); + } + } + + auto updater_runtime = state->updater->GetRuntime(); + if (!updater_runtime->UpdateDynamicPartitions(op_list_value->data)) { + return StringValue(""); + } + + if (!SetUpdatedMarker(updated_marker)) { + LOG(ERROR) << "Failed to set metadata updated marker."; + return StringValue(""); + } + + return StringValue("t"); +} + +void RegisterDynamicPartitionsFunctions() { + RegisterFunction("unmap_partition", UnmapPartitionFn); + RegisterFunction("map_partition", MapPartitionFn); + RegisterFunction("update_dynamic_partitions", UpdateDynamicPartitionsFn); +} diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h new file mode 100644 index 0000000000..79f915434f --- /dev/null +++ b/updater/include/private/commands.h @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include + +#include // FRIEND_TEST + +#include "otautil/rangeset.h" + +// Represents the target info used in a Command. TargetInfo contains the ranges of the blocks and +// the expected hash. +class TargetInfo { + public: + TargetInfo() = default; + + TargetInfo(std::string hash, RangeSet ranges) + : hash_(std::move(hash)), ranges_(std::move(ranges)) {} + + const std::string& hash() const { + return hash_; + } + + const RangeSet& ranges() const { + return ranges_; + } + + size_t blocks() const { + return ranges_.blocks(); + } + + bool operator==(const TargetInfo& other) const { + return hash_ == other.hash_ && ranges_ == other.ranges_; + } + + private: + friend std::ostream& operator<<(std::ostream& os, const TargetInfo& source); + + // The hash of the data represented by the object. + std::string hash_; + // The block ranges that the data should be written to. + RangeSet ranges_; +}; + +std::ostream& operator<<(std::ostream& os, const TargetInfo& source); + +// Represents the stash info used in a Command. +class StashInfo { + public: + StashInfo() = default; + + StashInfo(std::string id, RangeSet ranges) : id_(std::move(id)), ranges_(std::move(ranges)) {} + + size_t blocks() const { + return ranges_.blocks(); + } + + const std::string& id() const { + return id_; + } + + const RangeSet& ranges() const { + return ranges_; + } + + bool operator==(const StashInfo& other) const { + return id_ == other.id_ && ranges_ == other.ranges_; + } + + private: + friend std::ostream& operator<<(std::ostream& os, const StashInfo& stash); + + // The id (i.e. hash) of the stash. + std::string id_; + // The matching location of the stash. + RangeSet ranges_; +}; + +std::ostream& operator<<(std::ostream& os, const StashInfo& stash); + +// Represents the source info in a Command, whose data could come from source image, stashed blocks, +// or both. +class SourceInfo { + public: + SourceInfo() = default; + + SourceInfo(std::string hash, RangeSet ranges, RangeSet location, std::vector stashes) + : hash_(std::move(hash)), + ranges_(std::move(ranges)), + location_(std::move(location)), + stashes_(std::move(stashes)) { + blocks_ = ranges_.blocks(); + for (const auto& stash : stashes_) { + blocks_ += stash.ranges().blocks(); + } + } + + // Reads all the data specified by this SourceInfo object into the given 'buffer', by calling the + // given readers. Caller needs to specify the block size for the represented blocks. The given + // buffer needs to be sufficiently large. Otherwise it returns false. 'block_reader' and + // 'stash_reader' read the specified data into the given buffer (guaranteed to be large enough) + // respectively. The readers should return 0 on success, or -1 on error. + bool ReadAll( + std::vector* buffer, size_t block_size, + const std::function*)>& block_reader, + const std::function*)>& stash_reader) const; + + // Whether this SourceInfo overlaps with the given TargetInfo object. + bool Overlaps(const TargetInfo& target) const; + + // Dumps the hashes in hex for the given buffer that's loaded from this SourceInfo object + // (excluding the stashed blocks which are handled separately). + void DumpBuffer(const std::vector& buffer, size_t block_size) const; + + const std::string& hash() const { + return hash_; + } + + size_t blocks() const { + return blocks_; + } + + bool operator==(const SourceInfo& other) const { + return hash_ == other.hash_ && ranges_ == other.ranges_ && location_ == other.location_ && + stashes_ == other.stashes_; + } + + private: + friend std::ostream& operator<<(std::ostream& os, const SourceInfo& source); + + // The hash of the data represented by the object. + std::string hash_; + // The block ranges from the source image to read data from. This could be a subset of all the + // blocks represented by the object, or empty if all the data should be loaded from stash. + RangeSet ranges_; + // The location in the buffer to load ranges_ into. Empty if ranges_ alone covers all the blocks + // (i.e. nothing needs to be loaded from stash). + RangeSet location_; + // The info for the stashed blocks that are part of the source. Empty if there's none. + std::vector stashes_; + // Total number of blocks represented by the object. + size_t blocks_{ 0 }; +}; + +std::ostream& operator<<(std::ostream& os, const SourceInfo& source); + +class PatchInfo { + public: + PatchInfo() = default; + + PatchInfo(size_t offset, size_t length) : offset_(offset), length_(length) {} + + size_t offset() const { + return offset_; + } + + size_t length() const { + return length_; + } + + bool operator==(const PatchInfo& other) const { + return offset_ == other.offset_ && length_ == other.length_; + } + + private: + size_t offset_{ 0 }; + size_t length_{ 0 }; +}; + +// The arguments to build a hash tree from blocks on the block device. +class HashTreeInfo { + public: + HashTreeInfo() = default; + + HashTreeInfo(RangeSet hash_tree_ranges, RangeSet source_ranges, std::string hash_algorithm, + std::string salt_hex, std::string root_hash) + : hash_tree_ranges_(std::move(hash_tree_ranges)), + source_ranges_(std::move(source_ranges)), + hash_algorithm_(std::move(hash_algorithm)), + salt_hex_(std::move(salt_hex)), + root_hash_(std::move(root_hash)) {} + + const RangeSet& hash_tree_ranges() const { + return hash_tree_ranges_; + } + const RangeSet& source_ranges() const { + return source_ranges_; + } + + const std::string& hash_algorithm() const { + return hash_algorithm_; + } + const std::string& salt_hex() const { + return salt_hex_; + } + const std::string& root_hash() const { + return root_hash_; + } + + bool operator==(const HashTreeInfo& other) const { + return hash_tree_ranges_ == other.hash_tree_ranges_ && source_ranges_ == other.source_ranges_ && + hash_algorithm_ == other.hash_algorithm_ && salt_hex_ == other.salt_hex_ && + root_hash_ == other.root_hash_; + } + + private: + RangeSet hash_tree_ranges_; + RangeSet source_ranges_; + std::string hash_algorithm_; + std::string salt_hex_; + std::string root_hash_; +}; + +// Command class holds the info for an update command that performs block-based OTA (BBOTA). Each +// command consists of one or several args, namely TargetInfo, SourceInfo, StashInfo and PatchInfo. +// The currently used BBOTA version is v4. +// +// zero +// - Fill the indicated blocks with zeros. +// - Meaningful args: TargetInfo +// +// new +// - Fill the blocks with data read from the new_data file. +// - Meaningful args: TargetInfo +// +// erase +// - Mark the given blocks as empty. +// - Meaningful args: TargetInfo +// +// move <...> +// - Read the source blocks, write result to target blocks. +// - Meaningful args: TargetInfo, SourceInfo +// +// See the note below for <...>. +// +// bsdiff <...> +// imgdiff <...> +// - Read the source blocks, apply a patch, and write result to target blocks. +// - Meaningful args: PatchInfo, TargetInfo, SourceInfo +// +// It expects <...> in one of the following formats: +// +// - <[stash_id:stash_location] ...> +// (loads data from stashes only) +// +// +// (loads data from source image only) +// +// +// <[stash_id:stash_location] ...> +// (loads data from both of source image and stashes) +// +// stash +// - Load the given source blocks and stash the data in the given slot of the stash table. +// - Meaningful args: StashInfo +// +// free +// - Free the given stash data. +// - Meaningful args: StashInfo +// +// compute_hash_tree +// - Computes the hash_tree bytes and writes the result to the specified range on the +// block_device. +// +// abort +// - Abort the current update. Allowed for testing code only. +// +class Command { + public: + enum class Type { + ABORT, + BSDIFF, + COMPUTE_HASH_TREE, + ERASE, + FREE, + IMGDIFF, + MOVE, + NEW, + STASH, + ZERO, + LAST, // Not a valid type. + }; + + Command() = default; + + Command(Type type, size_t index, std::string cmdline, PatchInfo patch, TargetInfo target, + SourceInfo source, StashInfo stash) + : type_(type), + index_(index), + cmdline_(std::move(cmdline)), + patch_(std::move(patch)), + target_(std::move(target)), + source_(std::move(source)), + stash_(std::move(stash)) {} + + Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info); + + // Parses the given command 'line' into a Command object and returns it. The 'index' is specified + // by the caller to index the object. On parsing error, it returns an empty Command object that + // evaluates to false, and the specific error message will be set in 'err'. + static Command Parse(const std::string& line, size_t index, std::string* err); + + // Parses the command type from the given string. + static Type ParseType(const std::string& type_str); + + Type type() const { + return type_; + } + + size_t index() const { + return index_; + } + + const std::string& cmdline() const { + return cmdline_; + } + + const PatchInfo& patch() const { + return patch_; + } + + const TargetInfo& target() const { + return target_; + } + + const SourceInfo& source() const { + return source_; + } + + const StashInfo& stash() const { + return stash_; + } + + const HashTreeInfo& hash_tree_info() const { + return hash_tree_info_; + } + + size_t block_size() const { + return block_size_; + } + + constexpr explicit operator bool() const { + return type_ != Type::LAST; + } + + private: + friend class ResumableUpdaterTest; + friend class UpdaterTest; + + FRIEND_TEST(CommandsTest, Parse_ABORT_Allowed); + FRIEND_TEST(CommandsTest, Parse_InvalidNumberOfArgs); + FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput); + FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly); + FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes); + FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly); + + // Parses the target and source info from the given 'tokens' vector. Saves the parsed info into + // 'target' and 'source' objects. Returns the parsing result. Error message will be set in 'err' + // on parsing error, and the contents in 'target' and 'source' will be undefined. + static bool ParseTargetInfoAndSourceInfo(const std::vector& tokens, + const std::string& tgt_hash, TargetInfo* target, + const std::string& src_hash, SourceInfo* source, + std::string* err); + + // Allows parsing ABORT command, which should be used for testing purpose only. + static bool abort_allowed_; + + // The type of the command. + Type type_{ Type::LAST }; + // The index of the Command object, which is specified by the caller. + size_t index_{ 0 }; + // The input string that the Command object is parsed from. + std::string cmdline_; + // The patch info. Only meaningful for BSDIFF and IMGDIFF commands. + PatchInfo patch_; + // The target info, where the command should be written to. + TargetInfo target_; + // The source info to load the source blocks for the command. + SourceInfo source_; + // The stash info. Only meaningful for STASH and FREE commands. Note that although SourceInfo may + // also load data from stash, such info will be owned and managed by SourceInfo (i.e. in source_). + StashInfo stash_; + // The hash_tree info. Only meaningful for COMPUTE_HASH_TREE. + HashTreeInfo hash_tree_info_; + // The unit size of each block to be used in this command. + size_t block_size_{ 4096 }; +}; + +std::ostream& operator<<(std::ostream& os, const Command& command); + +// TransferList represents the info for a transfer list, which is parsed from input text lines +// containing commands to transfer data from one place to another on the target partition. +// +// The creator of the transfer list will guarantee that no block is read (i.e., used as the source +// for a patch or move) after it has been written. +// +// The creator will guarantee that a given stash is loaded (with a stash command) before it's used +// in a move/bsdiff/imgdiff command. +// +// Within one command the source and target ranges may overlap so in general we need to read the +// entire source into memory before writing anything to the target blocks. +// +// All the patch data is concatenated into one patch_data file in the update package. It must be +// stored uncompressed because we memory-map it in directly from the archive. (Since patches are +// already compressed, we lose very little by not compressing their concatenation.) +// +// Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more +// additional hashes before the range parameters, which are used to check if the command has +// already been completed and verify the integrity of the source data. +class TransferList { + public: + // Number of header lines. + static constexpr size_t kTransferListHeaderLines = 4; + + TransferList() = default; + + // Parses the given input string and returns a TransferList object. Sets error message if any. + static TransferList Parse(const std::string& transfer_list_str, std::string* err); + + int version() const { + return version_; + } + + size_t total_blocks() const { + return total_blocks_; + } + + size_t stash_max_entries() const { + return stash_max_entries_; + } + + size_t stash_max_blocks() const { + return stash_max_blocks_; + } + + const std::vector& commands() const { + return commands_; + } + + // Returns whether the TransferList is valid. + constexpr explicit operator bool() const { + return version_ != 0; + } + + private: + // BBOTA version. + int version_{ 0 }; + // Total number of blocks to be written in this transfer. + size_t total_blocks_; + // Maximum number of stashes that exist at the same time. + size_t stash_max_entries_; + // Maximum number of blocks to be stashed. + size_t stash_max_blocks_; + // Commands in this transfer. + std::vector commands_; +}; diff --git a/fuse_sdcard_provider.h b/updater/include/private/utils.h similarity index 77% rename from fuse_sdcard_provider.h rename to updater/include/private/utils.h index bdc60f2ba2..33cf6155d1 100644 --- a/fuse_sdcard_provider.h +++ b/updater/include/private/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,8 @@ * limitations under the License. */ -#ifndef __FUSE_SDCARD_PROVIDER_H -#define __FUSE_SDCARD_PROVIDER_H +#pragma once -bool start_sdcard_fuse(const char* path); +#include -#endif +bool SetUpdatedMarker(const std::string& marker); diff --git a/updater/include/updater/build_info.h b/updater/include/updater/build_info.h new file mode 100644 index 0000000000..0073bfa4a5 --- /dev/null +++ b/updater/include/updater/build_info.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +// This class serves as the aggregation of the fake block device information during update +// simulation on host. In specific, it has the name of the block device, its mount point, and the +// path to the temporary file that fakes this block device. +class FakeBlockDevice { + public: + FakeBlockDevice(std::string block_device, std::string mount_point, std::string temp_file_path) + : blockdev_name(std::move(block_device)), + mount_point(std::move(mount_point)), + mounted_file_path(std::move(temp_file_path)) {} + + std::string blockdev_name; + std::string mount_point; + std::string mounted_file_path; // path to the temp file that mocks the block device +}; + +// This class stores the information of the source build. For example, it creates and maintains +// the temporary files to simulate the block devices on host. Therefore, the simulator runtime can +// query the information and run the update on host. +class BuildInfo { + public: + BuildInfo(const std::string_view work_dir, bool keep_images) + : work_dir_(work_dir), keep_images_(keep_images) {} + // Returns the value of the build properties. + std::string GetProperty(const std::string_view key, const std::string_view default_value) const; + // Returns the path to the mock block device. + std::string FindBlockDeviceName(const std::string_view name) const; + // Parses the given target-file, initializes the build properties and extracts the images. + bool ParseTargetFile(const std::string_view target_file_path, bool extracted_input); + + std::string GetOemSettings() const { + return oem_settings_; + } + void SetOemSettings(const std::string_view oem_settings) { + oem_settings_ = oem_settings; + } + + private: + // A map to store the system properties during simulation. + std::map> build_props_; + // A file that contains the oem properties. + std::string oem_settings_; + // A map from the blockdev_name to the FakeBlockDevice object, which contains the path to the + // temporary file. + std::map> blockdev_map_; + + std::list temp_files_; + std::string work_dir_; // A temporary directory to store the extracted image files + bool keep_images_; +}; diff --git a/minadbd/minadbd.h b/updater/include/updater/dynamic_partitions.h similarity index 82% rename from minadbd/minadbd.h rename to updater/include/updater/dynamic_partitions.h index 3570a5da5c..31cf859c65 100644 --- a/minadbd/minadbd.h +++ b/updater/include/updater/dynamic_partitions.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,6 @@ * limitations under the License. */ -#ifndef MINADBD_H__ -#define MINADBD_H__ +#pragma once -int minadbd_main(); - -#endif +void RegisterDynamicPartitionsFunctions(); diff --git a/updater/include/updater/install.h b/updater/include/updater/install.h index 8d6ca4728d..9fe2031498 100644 --- a/updater/include/updater/install.h +++ b/updater/include/updater/install.h @@ -14,15 +14,6 @@ * limitations under the License. */ -#ifndef _UPDATER_INSTALL_H_ -#define _UPDATER_INSTALL_H_ - -struct State; +#pragma once void RegisterInstallFunctions(); - -// uiPrintf function prints msg to screen as well as logs -void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) - __attribute__((__format__(printf, 2, 3))); - -#endif diff --git a/updater/include/updater/simulator_runtime.h b/updater/include/updater/simulator_runtime.h new file mode 100644 index 0000000000..fa878db332 --- /dev/null +++ b/updater/include/updater/simulator_runtime.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "edify/updater_runtime_interface.h" +#include "updater/build_info.h" + +class SimulatorRuntime : public UpdaterRuntimeInterface { + public: + explicit SimulatorRuntime(BuildInfo* source) : source_(source) {} + + bool IsSimulator() const override { + return true; + } + + std::string GetProperty(const std::string_view key, + const std::string_view default_value) const override; + + int Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view fs_type, const std::string_view mount_options) override; + bool IsMounted(const std::string_view mount_point) const override; + std::pair Unmount(const std::string_view mount_point) override; + + bool ReadFileToString(const std::string_view filename, std::string* content) const override; + bool WriteStringToFile(const std::string_view content, + const std::string_view filename) const override; + + int WipeBlockDevice(const std::string_view filename, size_t len) const override; + int RunProgram(const std::vector& args, bool is_vfork) const override; + int Tune2Fs(const std::vector& args) const override; + + bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override; + bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override; + bool UpdateDynamicPartitions(const std::string_view op_list_value) override; + std::string AddSlotSuffix(const std::string_view arg) const override; + + private: + std::string FindBlockDeviceName(const std::string_view name) const override; + + BuildInfo* source_; + std::map> mounted_partitions_; +}; diff --git a/updater/include/updater/target_files.h b/updater/include/updater/target_files.h new file mode 100644 index 0000000000..860d47a359 --- /dev/null +++ b/updater/include/updater/target_files.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +// This class represents the mount information for each line in a fstab file. +class FstabInfo { + public: + FstabInfo(std::string blockdev_name, std::string mount_point, std::string fs_type) + : blockdev_name(std::move(blockdev_name)), + mount_point(std::move(mount_point)), + fs_type(std::move(fs_type)) {} + + std::string blockdev_name; + std::string mount_point; + std::string fs_type; +}; + +// This class parses a target file from a zip file or an extracted directory. It also provides the +// function to read the its content for simulation. +class TargetFile { + public: + TargetFile(std::string path, bool extracted_input) + : path_(std::move(path)), extracted_input_(extracted_input) {} + + // Opens the input target file (or extracted directory) and parses the misc_info.txt. + bool Open(); + // Parses the build properties in all possible locations and save them in |props_map| + bool GetBuildProps(std::map>* props_map) const; + // Parses the fstab and save the information about each partition to mount into |fstab_info_list|. + bool ParseFstabInfo(std::vector* fstab_info_list) const; + // Returns true if the given entry exists in the target file. + bool EntryExists(const std::string_view name) const; + // Extracts the image file |entry_name|. Returns true on success. + bool ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info, + const std::string_view work_dir, TemporaryFile* image_file) const; + + private: + // Wrapper functions to read the entry from either the zipped target-file, or the extracted input + // directory. + bool ReadEntryToString(const std::string_view name, std::string* content) const; + bool ExtractEntryToTempFile(const std::string_view name, TemporaryFile* temp_file) const; + + std::string path_; // Path to the zipped target-file or an extracted directory. + bool extracted_input_; // True if the target-file has been extracted. + ZipArchiveHandle handle_{ nullptr }; + + // The properties under META/misc_info.txt + std::map> misc_info_; +}; diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h index f4a2fe874f..8676b60384 100644 --- a/updater/include/updater/updater.h +++ b/updater/include/updater/updater.h @@ -14,22 +14,83 @@ * limitations under the License. */ -#ifndef _UPDATER_UPDATER_H_ -#define _UPDATER_UPDATER_H_ +#pragma once +#include #include + +#include +#include +#include + #include -typedef struct { - FILE* cmd_pipe; - ZipArchiveHandle package_zip; - int version; +#include "edify/expr.h" +#include "edify/updater_interface.h" +#include "otautil/error_code.h" +#include "otautil/sysutil.h" + +class Updater : public UpdaterInterface { + public: + explicit Updater(std::unique_ptr run_time) + : runtime_(std::move(run_time)) {} + + ~Updater() override; + + // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and + // UpdaterRuntime. + bool Init(int fd, const std::string_view package_filename, bool is_retry); + + // Parses and evaluates the updater-script in the OTA package. Reports the error code if the + // evaluation fails. + bool RunUpdate(); + + // Writes the message to command pipe, adds a new line in the end. + void WriteToCommandPipe(const std::string_view message, bool flush = false) const override; + + // Sends over the message to recovery to print it on the screen. + void UiPrint(const std::string_view message) const override; + + std::string FindBlockDeviceName(const std::string_view name) const override; + + UpdaterRuntimeInterface* GetRuntime() const override { + return runtime_.get(); + } + ZipArchiveHandle GetPackageHandle() const override { + return package_handle_; + } + std::string GetResult() const override { + return result_; + } + uint8_t* GetMappedPackageAddress() const override { + return mapped_package_.addr; + } + size_t GetMappedPackageLength() const override { + return mapped_package_.length; + } + + private: + friend class UpdaterTestBase; + friend class UpdaterTest; + // Where in the package we expect to find the edify script to execute. + // (Note it's "updateR-script", not the older "update-script".) + static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script"; + + // Reads the entry |name| in the zip archive and put the result in |content|. + bool ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, std::string* content); + + // Parses the error code embedded in state->errmsg; and reports the error code and cause code. + void ParseAndReportErrorCode(State* state); + + std::unique_ptr runtime_; - uint8_t* package_zip_addr; - size_t package_zip_len; -} UpdaterInfo; + MemMapping mapped_package_; + ZipArchiveHandle package_handle_{ nullptr }; + std::string updater_script_; -struct selabel_handle; -extern struct selabel_handle *sehandle; + bool is_retry_{ false }; + std::unique_ptr cmd_pipe_{ nullptr, fclose }; -#endif + std::string result_; + std::vector skipped_functions_; +}; diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h new file mode 100644 index 0000000000..b943dfcf18 --- /dev/null +++ b/updater/include/updater/updater_runtime.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "edify/updater_runtime_interface.h" + +struct selabel_handle; + +class UpdaterRuntime : public UpdaterRuntimeInterface { + public: + explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {} + ~UpdaterRuntime() override = default; + + bool IsSimulator() const override { + return false; + } + + std::string GetProperty(const std::string_view key, + const std::string_view default_value) const override; + + std::string FindBlockDeviceName(const std::string_view name) const override; + + int Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view fs_type, const std::string_view mount_options) override; + bool IsMounted(const std::string_view mount_point) const override; + std::pair Unmount(const std::string_view mount_point) override; + + bool ReadFileToString(const std::string_view filename, std::string* content) const override; + bool WriteStringToFile(const std::string_view content, + const std::string_view filename) const override; + + int WipeBlockDevice(const std::string_view filename, size_t len) const override; + int RunProgram(const std::vector& args, bool is_vfork) const override; + int Tune2Fs(const std::vector& args) const override; + + bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override; + bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override; + bool UpdateDynamicPartitions(const std::string_view op_list_value) override; + std::string AddSlotSuffix(const std::string_view arg) const override; + + private: + struct selabel_handle* sehandle_{ nullptr }; +}; diff --git a/updater/install.cpp b/updater/install.cpp index 9be7645f39..afa5195d07 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -46,52 +46,37 @@ #include #include #include +#include #include #include -#include #include #include #include #include -#include #include #include "edify/expr.h" -#include "mounts.h" -#include "otafault/ota_io.h" -#include "otautil/DirUtil.h" +#include "edify/updater_interface.h" +#include "edify/updater_runtime_interface.h" +#include "otautil/dirutil.h" #include "otautil/error_code.h" #include "otautil/print_sha1.h" -#include "updater/updater.h" +#include "otautil/sysutil.h" -// Send over the buffer to recovery though the command pipe. -static void uiPrint(State* state, const std::string& buffer) { - UpdaterInfo* ui = static_cast(state->cookie); +#ifndef __ANDROID__ +#include // for strlcpy +#endif - // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "". - // So skip sending empty strings to UI. - std::vector lines = android::base::Split(buffer, "\n"); - for (auto& line : lines) { - if (!line.empty()) { - fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str()); - } +static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) { + CHECK(updater); + std::string name = updater->FindBlockDeviceName(partition->name); + if (name.empty()) { + LOG(ERROR) << "Failed to find the block device " << partition->name; + return false; } - // On the updater side, we need to dump the contents to stderr (which has - // been redirected to the log file). Because the recovery will only print - // the contents to screen when processing pipe command ui_print. - LOG(INFO) << buffer; -} - -void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) { - std::string error_msg; - - va_list ap; - va_start(ap, format); - android::base::StringAppendV(&error_msg, format, ap); - va_end(ap); - - uiPrint(state, error_msg); + partition->name = std::move(name); + return true; } // This is the updater side handler for ui_print() in edify script. Contents will be sent over to @@ -103,7 +88,7 @@ Value* UIPrintFn(const char* name, State* state, const std::vectorupdater->UiPrint(buffer); return StringValue(buffer); } @@ -127,18 +112,24 @@ Value* PackageExtractFileFn(const char* name, State* state, argv.size()); } const std::string& zip_path = args[0]; - const std::string& dest_path = args[1]; + std::string dest_path = args[1]; - ZipArchiveHandle za = static_cast(state->cookie)->package_zip; - ZipString zip_string_path(zip_path.c_str()); + ZipArchiveHandle za = state->updater->GetPackageHandle(); ZipEntry entry; - if (FindEntry(za, zip_string_path, &entry) != 0) { + if (FindEntry(za, zip_path, &entry) != 0) { LOG(ERROR) << name << ": no " << zip_path << " in package"; return StringValue(""); } - unique_fd fd(TEMP_FAILURE_RETRY( - ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR))); + // Update the destination of package_extract_file if it's a block device. During simulation the + // destination will map to a fake file. + if (std::string block_device_name = state->updater->FindBlockDeviceName(dest_path); + !block_device_name.empty()) { + dest_path = block_device_name; + } + + android::base::unique_fd fd(TEMP_FAILURE_RETRY( + open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR))); if (fd == -1) { PLOG(ERROR) << name << ": can't open " << dest_path << " for write"; return StringValue(""); @@ -152,11 +143,12 @@ Value* PackageExtractFileFn(const char* name, State* state, << "\": " << ErrorCodeString(ret); success = false; } - if (ota_fsync(fd) == -1) { + if (fsync(fd) == -1) { PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed"; success = false; } - if (ota_close(fd) == -1) { + + if (close(fd.release()) != 0) { PLOG(ERROR) << "close of \"" << dest_path << "\" failed"; success = false; } @@ -172,10 +164,9 @@ Value* PackageExtractFileFn(const char* name, State* state, } const std::string& zip_path = args[0]; - ZipArchiveHandle za = static_cast(state->cookie)->package_zip; - ZipString zip_string_path(zip_path.c_str()); + ZipArchiveHandle za = state->updater->GetPackageHandle(); ZipEntry entry; - if (FindEntry(za, zip_string_path, &entry) != 0) { + if (FindEntry(za, zip_path, &entry) != 0) { return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name, zip_path.c_str()); } @@ -191,145 +182,96 @@ Value* PackageExtractFileFn(const char* name, State* state, zip_path.c_str(), buffer.size(), ErrorCodeString(ret)); } - return new Value(VAL_BLOB, buffer); + return new Value(Value::Type::BLOB, buffer); } } -// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...]) -// Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the -// same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1 -// hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a -// 40-character hex string) and a blob. The blob is the patch to be applied when the source -// file's current contents have the given SHA1. +// patch_partition_check(target_partition, source_partition) +// Checks if the target and source partitions have the desired checksums to be patched. It returns +// directly, if the target partition already has the expected checksum. Otherwise it in turn +// checks the integrity of the source partition and the backup file on /cache. // -// The patching is done in a safe manner that guarantees the target file either has the desired -// SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate -// state. If the process is interrupted during patching, the target file may be in an intermediate -// state; a copy exists in the cache partition so restarting the update can successfully update -// the file. -Value* ApplyPatchFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() < 6 || (argv.size() % 2) == 1) { +// For example, patch_partition_check( +// "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d", +// "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4") +Value* PatchPartitionCheckFn(const char* name, State* state, + const std::vector>& argv) { + if (argv.size() != 2) { return ErrorAbort(state, kArgsParsingFailure, - "%s(): expected at least 6 args and an " - "even number, got %zu", - name, argv.size()); + "%s(): Invalid number of args (expected 2, got %zu)", name, argv.size()); } std::vector args; - if (!ReadArgs(state, argv, &args, 0, 4)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& source_filename = args[0]; - const std::string& target_filename = args[1]; - const std::string& target_sha1 = args[2]; - const std::string& target_size_str = args[3]; - - size_t target_size; - if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name, - target_size_str.c_str()); + if (!ReadArgs(state, argv, &args, 0, 2)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); } - int patchcount = (argv.size() - 4) / 2; - std::vector> arg_values; - if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) { - return nullptr; + std::string err; + auto target = Partition::Parse(args[0], &err); + if (!target) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name, + args[0].c_str(), err.c_str()); } - for (int i = 0; i < patchcount; ++i) { - if (arg_values[i * 2]->type != VAL_STRING) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, i * 2); - } - if (arg_values[i * 2 + 1]->type != VAL_BLOB) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, i * 2 + 1); - } + auto source = Partition::Parse(args[1], &err); + if (!source) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name, + args[1].c_str(), err.c_str()); } - std::vector patch_sha_str; - std::vector> patches; - for (int i = 0; i < patchcount; ++i) { - patch_sha_str.push_back(arg_values[i * 2]->data); - patches.push_back(std::move(arg_values[i * 2 + 1])); + if (!UpdateBlockDeviceNameForPartition(state->updater, &source) || + !UpdateBlockDeviceNameForPartition(state->updater, &target)) { + return StringValue(""); } - int result = applypatch(source_filename.c_str(), target_filename.c_str(), target_sha1.c_str(), - target_size, patch_sha_str, patches, nullptr); - - return StringValue(result == 0 ? "t" : ""); + bool result = PatchPartitionCheck(target, source); + return StringValue(result ? "t" : ""); } -// apply_patch_check(filename, [sha1, ...]) -// Returns true if the contents of filename or the temporary copy in the cache partition (if -// present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are -// specified as 40 hex digits. This function differs from sha1_check(read_file(filename), -// sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will -// succeed even if the file was corrupted by an interrupted apply_patch() update. -Value* ApplyPatchCheckFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() < 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name, - argv.size()); +// patch_partition(target, source, patch) +// Applies the given patch to the source partition, and writes the result to the target partition. +// +// For example, patch_partition( +// "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d", +// "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4", +// package_extract_file("boot.img.p")) +Value* PatchPartitionFn(const char* name, State* state, + const std::vector>& argv) { + if (argv.size() != 3) { + return ErrorAbort(state, kArgsParsingFailure, + "%s(): Invalid number of args (expected 3, got %zu)", name, argv.size()); } std::vector args; - if (!ReadArgs(state, argv, &args, 0, 1)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + if (!ReadArgs(state, argv, &args, 0, 2)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); } - const std::string& filename = args[0]; - std::vector sha1s; - if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + std::string err; + auto target = Partition::Parse(args[0], &err); + if (!target) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name, + args[0].c_str(), err.c_str()); } - int result = applypatch_check(filename.c_str(), sha1s); - - return StringValue(result == 0 ? "t" : ""); -} -// sha1_check(data) -// to return the sha1 of the data (given in the format returned by -// read_file). -// -// sha1_check(data, sha1_hex, [sha1_hex, ...]) -// returns the sha1 of the file if it matches any of the hex -// strings passed, or "" if it does not equal any of them. -// -Value* Sha1CheckFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() < 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name); + auto source = Partition::Parse(args[1], &err); + if (!source) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name, + args[1].c_str(), err.c_str()); } - std::vector> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; + std::vector> values; + if (!ReadValueArgs(state, argv, &values, 2, 1) || values[0]->type != Value::Type::BLOB) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name); } - if (args[0]->type == VAL_INVALID) { + if (!UpdateBlockDeviceNameForPartition(state->updater, &source) || + !UpdateBlockDeviceNameForPartition(state->updater, &target)) { return StringValue(""); } - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(args[0]->data.c_str()), args[0]->data.size(), digest); - - if (argv.size() == 1) { - return StringValue(print_sha1(digest)); - } - - for (size_t i = 1; i < argv.size(); ++i) { - uint8_t arg_digest[SHA_DIGEST_LENGTH]; - if (args[i]->type != VAL_STRING) { - LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping"; - } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) { - // Warn about bad args and skip them. - LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping"; - } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) { - // Found a match. - return args[i].release(); - } - } - // Didn't match any of the hex strings; return false. - return StringValue(""); + bool result = PatchPartition(target, source, *values[0], nullptr, true); + return StringValue(result ? "t" : ""); } // mount(fs_type, partition_type, location, mount_point) @@ -371,26 +313,11 @@ Value* MountFn(const char* name, State* state, const std::vectorupdater; + if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) { + updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name, + location.c_str(), mount_point.c_str(), + strerror(errno))); return StringValue(""); } @@ -413,9 +340,8 @@ Value* IsMountedFn(const char* name, State* state, const std::vectorupdater->GetRuntime(); + if (!updater_runtime->IsMounted(mount_point)) { return StringValue(""); } @@ -436,36 +362,20 @@ Value* UnmountFn(const char* name, State* state, const std::vectorupdater; + auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point); + if (!mounted) { + updater->UiPrint( + android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str())); return nullptr; - } else { - int ret = unmount_mounted_volume(vol); - if (ret != 0) { - uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno)); - } + } else if (result != 0) { + updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(), + strerror(errno))); } return StringValue(mount_point); } -static int exec_cmd(const char* path, char* const argv[]) { - pid_t child; - if ((child = vfork()) == 0) { - execv(path, argv); - _exit(EXIT_FAILURE); - } - - int status; - waitpid(child, &status, 0); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status); - } - return WEXITSTATUS(status); -} - // format(fs_type, partition_type, location, fs_size, mount_point) // // fs_type="ext4" partition_type="EMMC" location=device fs_size= mount_point= @@ -510,67 +420,57 @@ Value* FormatFn(const char* name, State* state, const std::vectorupdater->GetRuntime(); if (fs_type == "ext4") { - const char* mke2fs_argv[] = { "/sbin/mke2fs_static", "-t", "ext4", "-b", "4096", - location.c_str(), nullptr, nullptr }; - std::string size_str; + std::vector mke2fs_args = { + "/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location + }; if (size != 0) { - size_str = std::to_string(size / 4096LL); - mke2fs_argv[6] = size_str.c_str(); + mke2fs_args.push_back(std::to_string(size / 4096LL)); } - int status = exec_cmd(mke2fs_argv[0], const_cast(mke2fs_argv)); - if (status != 0) { + if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) { LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location; return StringValue(""); } - const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", "-e", "-a", mount_point.c_str(), - location.c_str(), nullptr }; - status = exec_cmd(e2fsdroid_argv[0], const_cast(e2fsdroid_argv)); - if (status != 0) { + if (auto status = updater_runtime->RunProgram( + { "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true); + status != 0) { LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location; return StringValue(""); } return StringValue(location); - } else if (fs_type == "f2fs") { + } + + if (fs_type == "f2fs") { if (size < 0) { LOG(ERROR) << name << ": fs_size can't be negative for f2fs: " << fs_size; return StringValue(""); } - std::string num_sectors = std::to_string(size / 512); - - const char* f2fs_path = "/sbin/mkfs.f2fs"; - const char* f2fs_argv[] = { "mkfs.f2fs", - "-d1", - "-f", - "-O", "encrypt", - "-O", "quota", - "-O", "verity", - "-w", "512", - location.c_str(), - (size < 512) ? nullptr : num_sectors.c_str(), - nullptr }; - int status = exec_cmd(f2fs_path, const_cast(f2fs_argv)); - if (status != 0) { - LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location; + std::vector f2fs_args = { + "/system/bin/make_f2fs", "-g", "android", "-w", "512", location + }; + if (size >= 512) { + f2fs_args.push_back(std::to_string(size / 512)); + } + if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) { + LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location; return StringValue(""); } - const char* sload_argv[] = { "/sbin/sload.f2fs", "-t", mount_point.c_str(), location.c_str(), - nullptr }; - status = exec_cmd(sload_argv[0], const_cast(sload_argv)); - if (status != 0) { - LOG(ERROR) << name << ": sload.f2fs failed (" << status << ") on " << location; + if (auto status = updater_runtime->RunProgram( + { "/system/bin/sload_f2fs", "-t", mount_point, location }, true); + status != 0) { + LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location; return StringValue(""); } return StringValue(location); - } else { - LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \"" - << partition_type << "\""; } + LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \"" + << partition_type << "\""; return nullptr; } @@ -599,8 +499,7 @@ Value* ShowProgressFn(const char* name, State* state, sec_str.c_str()); } - UpdaterInfo* ui = static_cast(state->cookie); - fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec); + state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec)); return StringValue(frac_str); } @@ -623,8 +522,7 @@ Value* SetProgressFn(const char* name, State* state, frac_str.c_str()); } - UpdaterInfo* ui = static_cast(state->cookie); - fprintf(ui->cmd_pipe, "set_progress %f\n", frac); + state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac)); return StringValue(frac_str); } @@ -637,7 +535,9 @@ Value* GetPropFn(const char* name, State* state, const std::vectorupdater->GetRuntime(); + std::string value = updater_runtime->GetProperty(key, ""); return StringValue(value); } @@ -661,33 +561,13 @@ Value* FileGetPropFn(const char* name, State* state, const std::string& filename = args[0]; const std::string& key = args[1]; - struct stat st; - if (stat(filename.c_str(), &st) < 0) { - return ErrorAbort(state, kFileGetPropFailure, "%s: failed to stat \"%s\": %s", name, - filename.c_str(), strerror(errno)); - } - - constexpr off_t MAX_FILE_GETPROP_SIZE = 65536; - if (st.st_size > MAX_FILE_GETPROP_SIZE) { - return ErrorAbort(state, kFileGetPropFailure, "%s too large for %s (max %lld)", - filename.c_str(), name, static_cast(MAX_FILE_GETPROP_SIZE)); - } - - std::string buffer(st.st_size, '\0'); - unique_file f(ota_fopen(filename.c_str(), "rb")); - if (f == nullptr) { - return ErrorAbort(state, kFileOpenFailure, "%s: failed to open %s: %s", name, filename.c_str(), - strerror(errno)); - } - - if (ota_fread(&buffer[0], 1, st.st_size, f.get()) != static_cast(st.st_size)) { - ErrorAbort(state, kFreadFailure, "%s: failed to read %zu bytes from %s", name, - static_cast(st.st_size), filename.c_str()); + std::string buffer; + auto updater_runtime = state->updater->GetRuntime(); + if (!updater_runtime->ReadFileToString(filename, &buffer)) { + ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str()); return nullptr; } - ota_fclose(f); - std::vector lines = android::base::Split(buffer, "\n"); for (size_t i = 0; i < lines.size(); i++) { std::string line = android::base::Trim(lines[i]); @@ -733,7 +613,7 @@ Value* ApplyPatchSpaceFn(const char* name, State* state, } // Skip the cache size check if the update is a retry. - if (state->is_retry || CacheSizeCheck(bytes) == 0) { + if (state->is_retry || CheckAndFreeSpaceOnCache(bytes)) { return StringValue("t"); } return StringValue(""); @@ -744,7 +624,8 @@ Value* WipeCacheFn(const char* name, State* state, const std::vector(state->cookie)->cmd_pipe, "wipe_cache\n"); + + state->updater->WriteToCommandPipe("wipe_cache"); return StringValue("t"); } @@ -758,36 +639,13 @@ Value* RunProgramFn(const char* name, State* state, const std::vectorupdater->GetRuntime(); + auto status = updater_runtime->RunProgram(args, false); return StringValue(std::to_string(status)); } -// Read a local file and return its contents (the Value* returned -// is actually a FileContents*). +// read_file(filename) +// Reads a local file 'filename' and returns its contents as a string Value. Value* ReadFileFn(const char* name, State* state, const std::vector>& argv) { if (argv.size() != 1) { return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); @@ -795,18 +653,19 @@ Value* ReadFileFn(const char* name, State* state, const std::vector args; if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); } const std::string& filename = args[0]; - Value* v = new Value(VAL_INVALID, ""); - - FileContents fc; - if (LoadFileContents(filename.c_str(), &fc) == 0) { - v->type = VAL_BLOB; - v->data = std::string(fc.data.begin(), fc.data.end()); + std::string contents; + auto updater_runtime = state->updater->GetRuntime(); + if (updater_runtime->ReadFileToString(filename, &contents)) { + return new Value(Value::Type::STRING, std::move(contents)); } - return v; + + // Leave it to caller to handle the failure. + PLOG(ERROR) << name << ": Failed to read " << filename; + return StringValue(""); } // write_value(value, filename) @@ -829,12 +688,12 @@ Value* WriteValueFn(const char* name, State* state, const std::vectorupdater->GetRuntime(); + if (!updater_runtime->WriteStringToFile(value, filename)) { PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\""; return StringValue(""); - } else { - return StringValue("t"); } + return StringValue("t"); } // Immediately reboot the device. Recovery is not finished normally, @@ -872,13 +731,8 @@ Value* RebootNowFn(const char* name, State* state, const std::vectorupdater->GetRuntime(); + int status = updater_runtime->WipeBlockDevice(filename, len); + return StringValue(status == 0 ? "t" : ""); } Value* EnableRebootFn(const char* name, State* state, const std::vector>& argv) { @@ -976,8 +829,7 @@ Value* EnableRebootFn(const char* name, State* state, const std::vector(state->cookie); - fprintf(ui->cmd_pipe, "enable_reboot\n"); + state->updater->WriteToCommandPipe("enable_reboot"); return StringValue("t"); } @@ -991,25 +843,29 @@ Value* Tune2FsFn(const char* name, State* state, const std::vector(name); - if (args2[0] == nullptr) { - return nullptr; - } - for (size_t i = 0; i < argv.size(); ++i) { - args2[i + 1] = &args[i][0]; - } - - // tune2fs changes the file system parameters on an ext2 file system; it - // returns 0 on success. - int result = tune2fs_main(argv.size() + 1, args2); - if (result != 0) { + // tune2fs expects the program name as its first arg. + args.insert(args.begin(), "tune2fs"); + auto updater_runtime = state->updater->GetRuntime(); + if (auto result = updater_runtime->Tune2Fs(args); result != 0) { return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result); } return StringValue("t"); } +Value* AddSlotSuffixFn(const char* name, State* state, + const std::vector>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); + } + std::vector args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& arg = args[0]; + auto updater_runtime = state->updater->GetRuntime(); + return StringValue(updater_runtime->AddSlotSuffix(arg)); +} + void RegisterInstallFunctions() { RegisterFunction("mount", MountFn); RegisterFunction("is_mounted", IsMountedFn); @@ -1022,14 +878,13 @@ void RegisterInstallFunctions() { RegisterFunction("getprop", GetPropFn); RegisterFunction("file_getprop", FileGetPropFn); - RegisterFunction("apply_patch", ApplyPatchFn); - RegisterFunction("apply_patch_check", ApplyPatchCheckFn); RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); + RegisterFunction("patch_partition", PatchPartitionFn); + RegisterFunction("patch_partition_check", PatchPartitionCheckFn); RegisterFunction("wipe_block_device", WipeBlockDeviceFn); RegisterFunction("read_file", ReadFileFn); - RegisterFunction("sha1_check", Sha1CheckFn); RegisterFunction("write_value", WriteValueFn); RegisterFunction("wipe_cache", WipeCacheFn); @@ -1044,4 +899,6 @@ void RegisterInstallFunctions() { RegisterFunction("enable_reboot", EnableRebootFn); RegisterFunction("tune2fs", Tune2FsFn); + + RegisterFunction("add_slot_suffix", AddSlotSuffixFn); } diff --git a/mounts.cpp b/updater/mounts.cpp similarity index 61% rename from mounts.cpp rename to updater/mounts.cpp index 76fa657391..943d35c75f 100644 --- a/mounts.cpp +++ b/updater/mounts.cpp @@ -30,43 +30,43 @@ #include struct MountedVolume { - std::string device; - std::string mount_point; - std::string filesystem; - std::string flags; + std::string device; + std::string mount_point; + std::string filesystem; + std::string flags; }; -std::vector g_mounts_state; +static std::vector g_mounts_state; bool scan_mounted_volumes() { - for (size_t i = 0; i < g_mounts_state.size(); ++i) { - delete g_mounts_state[i]; - } - g_mounts_state.clear(); + for (size_t i = 0; i < g_mounts_state.size(); ++i) { + delete g_mounts_state[i]; + } + g_mounts_state.clear(); - // Open and read mount table entries. - FILE* fp = setmntent("/proc/mounts", "re"); - if (fp == NULL) { - return false; - } - mntent* e; - while ((e = getmntent(fp)) != NULL) { - MountedVolume* v = new MountedVolume; - v->device = e->mnt_fsname; - v->mount_point = e->mnt_dir; - v->filesystem = e->mnt_type; - v->flags = e->mnt_opts; - g_mounts_state.push_back(v); - } - endmntent(fp); - return true; + // Open and read mount table entries. + FILE* fp = setmntent("/proc/mounts", "re"); + if (fp == NULL) { + return false; + } + mntent* e; + while ((e = getmntent(fp)) != NULL) { + MountedVolume* v = new MountedVolume; + v->device = e->mnt_fsname; + v->mount_point = e->mnt_dir; + v->filesystem = e->mnt_type; + v->flags = e->mnt_opts; + g_mounts_state.push_back(v); + } + endmntent(fp); + return true; } MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) { - for (size_t i = 0; i < g_mounts_state.size(); ++i) { - if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i]; - } - return nullptr; + for (size_t i = 0; i < g_mounts_state.size(); ++i) { + if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i]; + } + return nullptr; } int unmount_mounted_volume(MountedVolume* volume) { diff --git a/mounts.h b/updater/mounts.h similarity index 94% rename from mounts.h rename to updater/mounts.h index 0de1ebd0a4..6786c8d2e4 100644 --- a/mounts.h +++ b/updater/mounts.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef MOUNTS_H_ -#define MOUNTS_H_ +#pragma once struct MountedVolume; @@ -24,5 +23,3 @@ bool scan_mounted_volumes(); MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point); int unmount_mounted_volume(MountedVolume* volume); - -#endif diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp new file mode 100644 index 0000000000..57dfb32d4d --- /dev/null +++ b/updater/simulator_runtime.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/simulator_runtime.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mounts.h" +#include "otautil/sysutil.h" + +std::string SimulatorRuntime::GetProperty(const std::string_view key, + const std::string_view default_value) const { + return source_->GetProperty(key, default_value); +} + +int SimulatorRuntime::Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view /* fs_type */, + const std::string_view /* mount_options */) { + if (auto mounted_location = mounted_partitions_.find(mount_point); + mounted_location != mounted_partitions_.end() && mounted_location->second != location) { + LOG(ERROR) << mount_point << " has been mounted at " << mounted_location->second; + return -1; + } + + mounted_partitions_.emplace(mount_point, location); + return 0; +} + +bool SimulatorRuntime::IsMounted(const std::string_view mount_point) const { + return mounted_partitions_.find(mount_point) != mounted_partitions_.end(); +} + +std::pair SimulatorRuntime::Unmount(const std::string_view mount_point) { + if (!IsMounted(mount_point)) { + return { false, -1 }; + } + + mounted_partitions_.erase(std::string(mount_point)); + return { true, 0 }; +} + +std::string SimulatorRuntime::FindBlockDeviceName(const std::string_view name) const { + return source_->FindBlockDeviceName(name); +} + +// TODO(xunchang) implement the utility functions in simulator. +int SimulatorRuntime::RunProgram(const std::vector& args, bool /* is_vfork */) const { + LOG(INFO) << "Running program with args " << android::base::Join(args, " "); + return 0; +} + +int SimulatorRuntime::Tune2Fs(const std::vector& args) const { + LOG(INFO) << "Running Tune2Fs with args " << android::base::Join(args, " "); + return 0; +} + +int SimulatorRuntime::WipeBlockDevice(const std::string_view filename, size_t /* len */) const { + LOG(INFO) << "SKip wiping block device " << filename; + return 0; +} + +bool SimulatorRuntime::ReadFileToString(const std::string_view filename, + std::string* content) const { + if (android::base::EndsWith(filename, "oem.prop")) { + return android::base::ReadFileToString(source_->GetOemSettings(), content); + } + + LOG(INFO) << "SKip reading filename " << filename; + return true; +} + +bool SimulatorRuntime::WriteStringToFile(const std::string_view content, + const std::string_view filename) const { + LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename; + return true; +} + +bool SimulatorRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name, + std::string* path) { + *path = partition_name; + return true; +} + +bool SimulatorRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) { + LOG(INFO) << "Skip unmapping " << partition_name; + return true; +} + +bool SimulatorRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) { + const std::unordered_set commands{ + "resize", "remove", "add", "move", + "add_group", "resize_group", "remove_group", "remove_all_groups", + }; + + std::vector lines = android::base::Split(std::string(op_list_value), "\n"); + for (const auto& line : lines) { + if (line.empty() || line[0] == '#') continue; + auto tokens = android::base::Split(line, " "); + if (commands.find(tokens[0]) == commands.end()) { + LOG(ERROR) << "Unknown operation in op_list: " << line; + return false; + } + } + return true; +} + +std::string SimulatorRuntime::AddSlotSuffix(const std::string_view arg) const { + LOG(INFO) << "Skip adding slot suffix to " << arg; + return std::string(arg); +} diff --git a/updater/target_files.cpp b/updater/target_files.cpp new file mode 100644 index 0000000000..919ec4e040 --- /dev/null +++ b/updater/target_files.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/target_files.h" + +#include + +#include +#include +#include + +#include +#include +#include + +static bool SimgToImg(int input_fd, int output_fd) { + if (lseek64(input_fd, 0, SEEK_SET) == -1) { + PLOG(ERROR) << "Failed to lseek64 on the input sparse image"; + return false; + } + + if (lseek64(output_fd, 0, SEEK_SET) == -1) { + PLOG(ERROR) << "Failed to lseek64 on the output raw image"; + return false; + } + + std::unique_ptr s_file( + sparse_file_import(input_fd, true, false), sparse_file_destroy); + if (!s_file) { + LOG(ERROR) << "Failed to import the sparse image."; + return false; + } + + if (sparse_file_write(s_file.get(), output_fd, false, false, false) < 0) { + PLOG(ERROR) << "Failed to output the raw image file."; + return false; + } + + return true; +} + +static bool ParsePropertyFile(const std::string_view prop_content, + std::map>* props_map) { + LOG(INFO) << "Start parsing build property\n"; + std::vector lines = android::base::Split(std::string(prop_content), "\n"); + for (const auto& line : lines) { + if (line.empty() || line[0] == '#') continue; + auto pos = line.find('='); + if (pos == std::string::npos) continue; + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + LOG(INFO) << key << ": " << value; + props_map->emplace(key, value); + } + + return true; +} + +static bool ParseFstab(const std::string_view fstab, std::vector* fstab_info_list) { + LOG(INFO) << "parsing fstab\n"; + std::vector lines = android::base::Split(std::string(fstab), "\n"); + for (const auto& line : lines) { + if (line.empty() || line[0] == '#') continue; + + // optional: + std::vector tokens = android::base::Split(line, " "); + tokens.erase(std::remove(tokens.begin(), tokens.end(), ""), tokens.end()); + if (tokens.size() != 4 && tokens.size() != 5) { + LOG(ERROR) << "Unexpected token size: " << tokens.size() << std::endl + << "Error parsing fstab line: " << line; + return false; + } + + const auto& blockdev = tokens[0]; + const auto& mount_point = tokens[1]; + const auto& fs_type = tokens[2]; + if (!android::base::StartsWith(mount_point, "/")) { + LOG(WARNING) << "mount point '" << mount_point << "' does not start with '/'"; + continue; + } + + // The simulator only supports ext4 and emmc for now. + if (fs_type != "ext4" && fs_type != "emmc") { + LOG(WARNING) << "Unsupported fs_type in " << line; + continue; + } + + fstab_info_list->emplace_back(blockdev, mount_point, fs_type); + } + + return true; +} + +bool TargetFile::EntryExists(const std::string_view name) const { + if (extracted_input_) { + std::string entry_path = path_ + "/" + std::string(name); + if (access(entry_path.c_str(), O_RDONLY) != 0) { + PLOG(WARNING) << "Failed to access " << entry_path; + return false; + } + return true; + } + + CHECK(handle_); + ZipEntry img_entry; + return FindEntry(handle_, name, &img_entry) == 0; +} + +bool TargetFile::ReadEntryToString(const std::string_view name, std::string* content) const { + if (extracted_input_) { + std::string entry_path = path_ + "/" + std::string(name); + return android::base::ReadFileToString(entry_path, content); + } + + CHECK(handle_); + ZipEntry entry; + if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) { + LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err); + return false; + } + + if (entry.uncompressed_length == 0) { + content->clear(); + return true; + } + + content->resize(entry.uncompressed_length); + if (auto extract_err = ExtractToMemory( + handle_, &entry, reinterpret_cast(&content->at(0)), entry.uncompressed_length); + extract_err != 0) { + LOG(ERROR) << "failed to read " << name << " from package: " << ErrorCodeString(extract_err); + return false; + } + + return true; +} + +bool TargetFile::ExtractEntryToTempFile(const std::string_view name, + TemporaryFile* temp_file) const { + if (extracted_input_) { + std::string entry_path = path_ + "/" + std::string(name); + return std::filesystem::copy_file(entry_path, temp_file->path, + std::filesystem::copy_options::overwrite_existing); + } + + CHECK(handle_); + ZipEntry entry; + if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) { + LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err); + return false; + } + + if (auto status = ExtractEntryToFile(handle_, &entry, temp_file->fd); status != 0) { + LOG(ERROR) << "Failed to extract zip entry " << name << " : " << ErrorCodeString(status); + return false; + } + return true; +} + +bool TargetFile::Open() { + if (!extracted_input_) { + if (auto ret = OpenArchive(path_.c_str(), &handle_); ret != 0) { + LOG(ERROR) << "failed to open source target file " << path_ << ": " << ErrorCodeString(ret); + return false; + } + } + + // Parse the misc info. + std::string misc_info_content; + if (!ReadEntryToString("META/misc_info.txt", &misc_info_content)) { + return false; + } + if (!ParsePropertyFile(misc_info_content, &misc_info_)) { + return false; + } + + return true; +} + +bool TargetFile::GetBuildProps(std::map>* props_map) const { + props_map->clear(); + // Parse the source zip to mock the system props and block devices. We try all the possible + // locations for build props. + constexpr std::string_view kPropLocations[] = { + "SYSTEM/build.prop", + "VENDOR/build.prop", + "PRODUCT/build.prop", + "SYSTEM_EXT/build.prop", + "SYSTEM/vendor/build.prop", + "SYSTEM/product/build.prop", + "SYSTEM/system_ext/build.prop", + "ODM/build.prop", // legacy + "ODM/etc/build.prop", + "VENDOR/odm/build.prop", // legacy + "VENDOR/odm/etc/build.prop", + }; + for (const auto& name : kPropLocations) { + std::string build_prop_content; + if (!ReadEntryToString(name, &build_prop_content)) { + continue; + } + std::map> props; + if (!ParsePropertyFile(build_prop_content, &props)) { + LOG(ERROR) << "Failed to parse build prop in " << name; + return false; + } + for (const auto& [key, value] : props) { + if (auto it = props_map->find(key); it != props_map->end() && it->second != value) { + LOG(WARNING) << "Property " << key << " has different values in property files, we got " + << it->second << " and " << value; + } + props_map->emplace(key, value); + } + } + + return true; +} + +bool TargetFile::ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info, + const std::string_view work_dir, TemporaryFile* image_file) const { + if (!EntryExists(entry_name)) { + return false; + } + + // We don't need extra work for 'emmc'; use the image file as the block device. + if (fstab_info.fs_type == "emmc" || misc_info_.find("extfs_sparse_flag") == misc_info_.end()) { + if (!ExtractEntryToTempFile(entry_name, image_file)) { + return false; + } + } else { // treated as ext4 sparse image + TemporaryFile sparse_image{ std::string(work_dir) }; + if (!ExtractEntryToTempFile(entry_name, &sparse_image)) { + return false; + } + + // Convert the sparse image to raw. + if (!SimgToImg(sparse_image.fd, image_file->fd)) { + LOG(ERROR) << "Failed to convert " << fstab_info.mount_point << " to raw."; + return false; + } + } + + return true; +} + +bool TargetFile::ParseFstabInfo(std::vector* fstab_info_list) const { + // Parse the fstab file and extract the image files. The location of the fstab actually depends + // on some flags e.g. "no_recovery", "recovery_as_boot". Here we just try all possibilities. + constexpr std::string_view kRecoveryFstabLocations[] = { + "RECOVERY/RAMDISK/system/etc/recovery.fstab", + "RECOVERY/RAMDISK/etc/recovery.fstab", + "BOOT/RAMDISK/system/etc/recovery.fstab", + "BOOT/RAMDISK/etc/recovery.fstab", + }; + std::string fstab_content; + for (const auto& name : kRecoveryFstabLocations) { + if (std::string content; ReadEntryToString(name, &content)) { + fstab_content = std::move(content); + break; + } + } + if (fstab_content.empty()) { + LOG(ERROR) << "Failed to parse the recovery fstab file"; + return false; + } + + // Extract the images and convert them to raw. + if (!ParseFstab(fstab_content, fstab_info_list)) { + LOG(ERROR) << "Failed to mount the block devices for source build."; + return false; + } + + return true; +} diff --git a/updater/update_simulator_main.cpp b/updater/update_simulator_main.cpp new file mode 100644 index 0000000000..6c6989bacb --- /dev/null +++ b/updater/update_simulator_main.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "edify/expr.h" +#include "otautil/error_code.h" +#include "otautil/paths.h" +#include "updater/blockimg.h" +#include "updater/build_info.h" +#include "updater/dynamic_partitions.h" +#include "updater/install.h" +#include "updater/simulator_runtime.h" +#include "updater/updater.h" + +using namespace std::string_literals; + +void Usage(std::string_view name) { + LOG(INFO) << "Usage: " << name << "[--oem_settings ]" + << "[--skip_functions ]" + << " --source " + << " --ota_package "; +} + +Value* SimulatorPlaceHolderFn(const char* name, State* /* state */, + const std::vector>& /* argv */) { + LOG(INFO) << "Skip function " << name << " in host simulation"; + return StringValue("t"); +} + +int main(int argc, char** argv) { + // Write the logs to stdout. + android::base::InitLogging(argv, &android::base::StderrLogger); + + std::string oem_settings; + std::string skip_function_file; + std::string source_target_file; + std::string package_name; + std::string work_dir; + bool keep_images = false; + + constexpr struct option OPTIONS[] = { + { "keep_images", no_argument, nullptr, 0 }, + { "oem_settings", required_argument, nullptr, 0 }, + { "ota_package", required_argument, nullptr, 0 }, + { "skip_functions", required_argument, nullptr, 0 }, + { "source", required_argument, nullptr, 0 }, + { "work_dir", required_argument, nullptr, 0 }, + { nullptr, 0, nullptr, 0 }, + }; + + int arg; + int option_index; + while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { + if (arg != 0) { + LOG(ERROR) << "Invalid command argument"; + Usage(argv[0]); + return EXIT_FAILURE; + } + auto option_name = OPTIONS[option_index].name; + // The same oem property file used during OTA generation. It's needed for file_getprop() to + // return the correct value for the source build. + if (option_name == "oem_settings"s) { + oem_settings = optarg; + } else if (option_name == "skip_functions"s) { + skip_function_file = optarg; + } else if (option_name == "source"s) { + source_target_file = optarg; + } else if (option_name == "ota_package"s) { + package_name = optarg; + } else if (option_name == "keep_images"s) { + keep_images = true; + } else if (option_name == "work_dir"s) { + work_dir = optarg; + } else { + Usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (source_target_file.empty() || package_name.empty()) { + Usage(argv[0]); + return EXIT_FAILURE; + } + + // Configure edify's functions. + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + RegisterDynamicPartitionsFunctions(); + + if (!skip_function_file.empty()) { + std::string content; + if (!android::base::ReadFileToString(skip_function_file, &content)) { + PLOG(ERROR) << "Failed to read " << skip_function_file; + return EXIT_FAILURE; + } + + auto lines = android::base::Split(content, "\n"); + for (const auto& line : lines) { + if (line.empty() || android::base::StartsWith(line, "#")) { + continue; + } + RegisterFunction(line, SimulatorPlaceHolderFn); + } + } + + TemporaryFile temp_saved_source; + TemporaryFile temp_last_command; + TemporaryDir temp_stash_base; + + Paths::Get().set_cache_temp_source(temp_saved_source.path); + Paths::Get().set_last_command_file(temp_last_command.path); + Paths::Get().set_stash_directory_base(temp_stash_base.path); + + TemporaryFile cmd_pipe; + TemporaryDir source_temp_dir; + if (work_dir.empty()) { + work_dir = source_temp_dir.path; + } + + BuildInfo source_build_info(work_dir, keep_images); + if (!source_build_info.ParseTargetFile(source_target_file, false)) { + LOG(ERROR) << "Failed to parse the target file " << source_target_file; + return EXIT_FAILURE; + } + + if (!oem_settings.empty()) { + CHECK_EQ(0, access(oem_settings.c_str(), R_OK)); + source_build_info.SetOemSettings(oem_settings); + } + + Updater updater(std::make_unique(&source_build_info)); + if (!updater.Init(cmd_pipe.release(), package_name, false)) { + return EXIT_FAILURE; + } + + if (!updater.RunUpdate()) { + return EXIT_FAILURE; + } + + LOG(INFO) << "\nscript succeeded, result: " << updater.GetResult(); + + return 0; +} diff --git a/updater/updater.cpp b/updater/updater.cpp index 1d6b172bb2..8f4a6ede5b 100644 --- a/updater/updater.cpp +++ b/updater/updater.cpp @@ -16,211 +16,169 @@ #include "updater/updater.h" -#include -#include -#include #include +#include #include #include #include -#include -#include -#include -#include - -#include "edify/expr.h" -#include "otafault/config.h" -#include "otautil/DirUtil.h" -#include "otautil/SysUtil.h" -#include "otautil/cache_location.h" -#include "otautil/error_code.h" -#include "updater/blockimg.h" -#include "updater/install.h" - -// Generated by the makefile, this function defines the -// RegisterDeviceExtensions() function, which calls all the -// registration functions for device-specific extensions. -#include "register.inc" - -// Where in the package we expect to find the edify script to execute. -// (Note it's "updateR-script", not the older "update-script".) -static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script"; - -extern bool have_eio_error; - -struct selabel_handle *sehandle; - -static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */, - const char* /* tag */, const char* /* file */, unsigned int /* line */, - const char* message) { - fprintf(stdout, "%s\n", message); -} -int main(int argc, char** argv) { - // Various things log information to stdout or stderr more or less - // at random (though we've tried to standardize on stdout). The - // log file makes more sense if buffering is turned off so things - // appear in the right order. - setbuf(stdout, nullptr); - setbuf(stderr, nullptr); - - // We don't have logcat yet under recovery. Update logs will always be written to stdout - // (which is redirected to recovery.log). - android::base::InitLogging(argv, &UpdaterLogger); - - if (argc != 4 && argc != 5) { - LOG(ERROR) << "unexpected number of arguments: " << argc; - return 1; - } +#include "edify/updater_runtime_interface.h" - char* version = argv[1]; - if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') { - // We support version 1, 2, or 3. - LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1]; - return 2; +Updater::~Updater() { + if (package_handle_) { + CloseArchive(package_handle_); } +} +bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) { // Set up the pipe for sending commands back to the parent process. + cmd_pipe_.reset(fdopen(fd, "wb")); + if (!cmd_pipe_) { + LOG(ERROR) << "Failed to open the command pipe"; + return false; + } - int fd = atoi(argv[2]); - FILE* cmd_pipe = fdopen(fd, "wb"); - setlinebuf(cmd_pipe); - - // Extract the script from the package. + setlinebuf(cmd_pipe_.get()); - const char* package_filename = argv[3]; - MemMapping map; - if (!map.MapFile(package_filename)) { - LOG(ERROR) << "failed to map package " << argv[3]; - return 3; + if (!mapped_package_.MapFile(std::string(package_filename))) { + LOG(ERROR) << "failed to map package " << package_filename; + return false; } - ZipArchiveHandle za; - int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za); - if (open_err != 0) { - LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err); - CloseArchive(za); - return 3; + if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length, + std::string(package_filename).c_str(), &package_handle_); + open_err != 0) { + LOG(ERROR) << "failed to open package " << package_filename << ": " + << ErrorCodeString(open_err); + return false; } - - ZipString script_name(SCRIPT_NAME); - ZipEntry script_entry; - int find_err = FindEntry(za, script_name, &script_entry); - if (find_err != 0) { - LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": " - << ErrorCodeString(find_err); - CloseArchive(za); - return 4; + if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) { + return false; } - std::string script; - script.resize(script_entry.uncompressed_length); - int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast(&script[0]), - script_entry.uncompressed_length); - if (extract_err != 0) { - LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err); - CloseArchive(za); - return 5; - } + is_retry_ = is_retry; - // Configure edify's functions. + return true; +} - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - RegisterDeviceExtensions(); +bool Updater::RunUpdate() { + CHECK(runtime_); // Parse the script. - std::unique_ptr root; int error_count = 0; - int error = parse_string(script.c_str(), &root, &error_count); + int error = ParseString(updater_script_, &root, &error_count); if (error != 0 || error_count > 0) { LOG(ERROR) << error_count << " parse errors"; - CloseArchive(za); - return 6; - } - - sehandle = selinux_android_file_context_handle(); - selinux_android_set_sehandle(sehandle); - - if (!sehandle) { - fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n"); + return false; } // Evaluate the parsed script. + State state(updater_script_, this); + state.is_retry = is_retry_; + + bool status = Evaluate(&state, root, &result_); + if (status) { + fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str()); + // Even though the script doesn't abort, still log the cause code if result is empty. + if (result_.empty() && state.cause_code != kNoCause) { + fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code); + } + for (const auto& func : skipped_functions_) { + LOG(WARNING) << "Skipped executing function " << func; + } + return true; + } - UpdaterInfo updater_info; - updater_info.cmd_pipe = cmd_pipe; - updater_info.package_zip = za; - updater_info.version = atoi(version); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; + ParseAndReportErrorCode(&state); + return false; +} - State state(script, &updater_info); +void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const { + fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str()); + if (flush) { + fflush(cmd_pipe_.get()); + } +} - if (argc == 5) { - if (strcmp(argv[4], "retry") == 0) { - state.is_retry = true; - } else { - printf("unexpected argument: %s", argv[4]); +void Updater::UiPrint(const std::string_view message) const { + // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "". + // so skip sending empty strings to ui. + std::vector lines = android::base::Split(std::string(message), "\n"); + for (const auto& line : lines) { + if (!line.empty()) { + fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str()); } } - ota_io_init(za, state.is_retry); - std::string result; - bool status = Evaluate(&state, root, &result); + // on the updater side, we need to dump the contents to stderr (which has + // been redirected to the log file). because the recovery will only print + // the contents to screen when processing pipe command ui_print. + LOG(INFO) << message; +} - if (have_eio_error) { - fprintf(cmd_pipe, "retry_update\n"); - } +std::string Updater::FindBlockDeviceName(const std::string_view name) const { + return runtime_->FindBlockDeviceName(name); +} - if (!status) { - if (state.errmsg.empty()) { - LOG(ERROR) << "script aborted (no error message)"; - fprintf(cmd_pipe, "ui_print script aborted (no error message)\n"); - } else { - LOG(ERROR) << "script aborted: " << state.errmsg; - const std::vector lines = android::base::Split(state.errmsg, "\n"); - for (const std::string& line : lines) { - // Parse the error code in abort message. - // Example: "E30: This package is for bullhead devices." - if (!line.empty() && line[0] == 'E') { - if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) { - LOG(ERROR) << "Failed to parse error code: [" << line << "]"; - } +void Updater::ParseAndReportErrorCode(State* state) { + CHECK(state); + if (state->errmsg.empty()) { + LOG(ERROR) << "script aborted (no error message)"; + fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n"); + } else { + LOG(ERROR) << "script aborted: " << state->errmsg; + const std::vector lines = android::base::Split(state->errmsg, "\n"); + for (const std::string& line : lines) { + // Parse the error code in abort message. + // Example: "E30: This package is for bullhead devices." + if (!line.empty() && line[0] == 'E') { + if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) { + LOG(ERROR) << "Failed to parse error code: [" << line << "]"; } - fprintf(cmd_pipe, "ui_print %s\n", line.c_str()); } + fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str()); } + } - // Installation has been aborted. Set the error code to kScriptExecutionFailure unless - // a more specific code has been set in errmsg. - if (state.error_code == kNoError) { - state.error_code = kScriptExecutionFailure; - } - fprintf(cmd_pipe, "log error: %d\n", state.error_code); - // Cause code should provide additional information about the abort. - if (state.cause_code != kNoCause) { - fprintf(cmd_pipe, "log cause: %d\n", state.cause_code); - if (state.cause_code == kPatchApplicationFailure) { - LOG(INFO) << "Patch application failed, retry update."; - fprintf(cmd_pipe, "retry_update\n"); - } + // Installation has been aborted. Set the error code to kScriptExecutionFailure unless + // a more specific code has been set in errmsg. + if (state->error_code == kNoError) { + state->error_code = kScriptExecutionFailure; + } + fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code); + // Cause code should provide additional information about the abort. + if (state->cause_code != kNoCause) { + fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code); + if (state->cause_code == kPatchApplicationFailure) { + LOG(INFO) << "Patch application failed, retry update."; + fprintf(cmd_pipe_.get(), "retry_update\n"); + } else if (state->cause_code == kEioFailure) { + LOG(INFO) << "Update failed due to EIO, retry update."; + fprintf(cmd_pipe_.get(), "retry_update\n"); } + } +} - if (updater_info.package_zip) { - CloseArchive(updater_info.package_zip); - } - return 7; - } else { - fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str()); +bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, + std::string* content) { + ZipEntry entry; + int find_err = FindEntry(za, entry_name, &entry); + if (find_err != 0) { + LOG(ERROR) << "failed to find " << entry_name + << " in the package: " << ErrorCodeString(find_err); + return false; } - if (updater_info.package_zip) { - CloseArchive(updater_info.package_zip); + content->resize(entry.uncompressed_length); + int extract_err = ExtractToMemory(za, &entry, reinterpret_cast(&content->at(0)), + entry.uncompressed_length); + if (extract_err != 0) { + LOG(ERROR) << "failed to read " << entry_name + << " from package: " << ErrorCodeString(extract_err); + return false; } - return 0; + return true; } diff --git a/updater/updater_main.cpp b/updater/updater_main.cpp new file mode 100644 index 0000000000..33d5b5b475 --- /dev/null +++ b/updater/updater_main.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "edify/expr.h" +#include "updater/blockimg.h" +#include "updater/dynamic_partitions.h" +#include "updater/install.h" +#include "updater/updater.h" +#include "updater/updater_runtime.h" + +// Generated by the makefile, this function defines the +// RegisterDeviceExtensions() function, which calls all the +// registration functions for device-specific extensions. +#include "register.inc" + +static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */, + const char* /* tag */, const char* /* file */, unsigned int /* line */, + const char* message) { + fprintf(stdout, "%s\n", message); +} + +int main(int argc, char** argv) { + // Various things log information to stdout or stderr more or less + // at random (though we've tried to standardize on stdout). The + // log file makes more sense if buffering is turned off so things + // appear in the right order. + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + // We don't have logcat yet under recovery. Update logs will always be written to stdout + // (which is redirected to recovery.log). + android::base::InitLogging(argv, &UpdaterLogger); + + // Run the libcrypto KAT(known answer tests) based self tests. + if (BORINGSSL_self_test() != 1) { + LOG(ERROR) << "Failed to run the boringssl self tests"; + return EXIT_FAILURE; + } + + if (argc != 4 && argc != 5) { + LOG(ERROR) << "unexpected number of arguments: " << argc; + return EXIT_FAILURE; + } + + char* version = argv[1]; + if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') { + // We support version 1, 2, or 3. + LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1]; + return EXIT_FAILURE; + } + + int fd; + if (!android::base::ParseInt(argv[2], &fd)) { + LOG(ERROR) << "Failed to parse fd in " << argv[2]; + return EXIT_FAILURE; + } + + std::string package_name = argv[3]; + + bool is_retry = false; + if (argc == 5) { + if (strcmp(argv[4], "retry") == 0) { + is_retry = true; + } else { + LOG(ERROR) << "unexpected argument: " << argv[4]; + return EXIT_FAILURE; + } + } + + // Configure edify's functions. + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + RegisterDynamicPartitionsFunctions(); + RegisterDeviceExtensions(); + + auto sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + + Updater updater(std::make_unique(sehandle)); + if (!updater.Init(fd, package_name, is_retry)) { + return EXIT_FAILURE; + } + + if (!updater.RunUpdate()) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp new file mode 100644 index 0000000000..e93830505c --- /dev/null +++ b/updater/updater_runtime.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/updater_runtime.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mounts.h" +#include "otautil/sysutil.h" + +std::string UpdaterRuntime::GetProperty(const std::string_view key, + const std::string_view default_value) const { + return android::base::GetProperty(std::string(key), std::string(default_value)); +} + +std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const { + return std::string(name); +} + +static struct { + const char* name; + unsigned flag; +} mount_flags_list[] = { + { "noatime", MS_NOATIME }, + { "noexec", MS_NOEXEC }, + { "nosuid", MS_NOSUID }, + { "nodev", MS_NODEV }, + { "nodiratime", MS_NODIRATIME }, + { "ro", MS_RDONLY }, + { "rw", 0 }, + { "remount", MS_REMOUNT }, + { "bind", MS_BIND }, + { "rec", MS_REC }, + { "unbindable", MS_UNBINDABLE }, + { "private", MS_PRIVATE }, + { "slave", MS_SLAVE }, + { "shared", MS_SHARED }, + { "defaults", 0 }, + { 0, 0 }, +}; + +static bool setMountFlag(const std::string& flag, unsigned* mount_flags) { + for (const auto& [name, value] : mount_flags_list) { + if (flag == name) { + *mount_flags |= value; + return true; + } + } + return false; +} + +static bool parseMountFlags(const std::string& flags, unsigned* mount_flags, + std::string* fs_options) { + bool is_flag_set = false; + std::vector flag_list; + for (const auto& flag : android::base::Split(flags, ",")) { + if (!setMountFlag(flag, mount_flags)) { + // Unknown flag, so it must be a filesystem specific option. + flag_list.push_back(flag); + } else { + is_flag_set = true; + } + } + *fs_options = android::base::Join(flag_list, ','); + return is_flag_set; +} + +int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view fs_type, const std::string_view mount_options) { + std::string mount_point_string(mount_point); + std::string mount_options_string(mount_options); + char* secontext = nullptr; + unsigned mount_flags = 0; + std::string fs_options; + + if (sehandle_) { + selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755); + setfscreatecon(secontext); + } + + mkdir(mount_point_string.c_str(), 0755); + + if (secontext) { + freecon(secontext); + setfscreatecon(nullptr); + } + + if (!parseMountFlags(mount_options_string, &mount_flags, &fs_options)) { + // Fall back to default + mount_flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME; + } + + return mount(std::string(location).c_str(), mount_point_string.c_str(), + std::string(fs_type).c_str(), mount_flags, fs_options.c_str()); +} + +bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const { + scan_mounted_volumes(); + MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str()); + return vol != nullptr; +} + +std::pair UpdaterRuntime::Unmount(const std::string_view mount_point) { + scan_mounted_volumes(); + MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str()); + if (vol == nullptr) { + return { false, -1 }; + } + + int ret = unmount_mounted_volume(vol); + return { true, ret }; +} + +bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const { + return android::base::ReadFileToString(std::string(filename), content); +} + +bool UpdaterRuntime::WriteStringToFile(const std::string_view content, + const std::string_view filename) const { + return android::base::WriteStringToFile(std::string(content), std::string(filename)); +} + +int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const { + android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY)); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << filename; + return false; + } + // The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure. + return wipe_block_device(fd, len); +} + +int UpdaterRuntime::RunProgram(const std::vector& args, bool is_vfork) const { + CHECK(!args.empty()); + auto argv = StringVectorToNullTerminatedArray(args); + LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args"; + + pid_t child = is_vfork ? vfork() : fork(); + if (child == 0) { + execv(argv[0], argv.data()); + PLOG(ERROR) << "run_program: execv failed"; + _exit(EXIT_FAILURE); + } + + int status; + waitpid(child, &status, 0); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status); + } + } else if (WIFSIGNALED(status)) { + LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status); + } + + return status; +} + +int UpdaterRuntime::Tune2Fs(const std::vector& args) const { + auto tune2fs_args = StringVectorToNullTerminatedArray(args); + // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success. + return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); +} + +std::string UpdaterRuntime::AddSlotSuffix(const std::string_view arg) const { + return std::string(arg) + fs_mgr_get_slot_suffix(); +} diff --git a/updater/updater_runtime_dynamic_partitions.cpp b/updater/updater_runtime_dynamic_partitions.cpp new file mode 100644 index 0000000000..6570cfffde --- /dev/null +++ b/updater/updater_runtime_dynamic_partitions.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/updater_runtime.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using android::dm::DeviceMapper; +using android::dm::DmDeviceState; +using android::fs_mgr::CreateLogicalPartition; +using android::fs_mgr::CreateLogicalPartitionParams; +using android::fs_mgr::DestroyLogicalPartition; +using android::fs_mgr::LpMetadata; +using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::Partition; +using android::fs_mgr::PartitionOpener; +using android::fs_mgr::SlotNumberForSlotSuffix; + +static constexpr std::chrono::milliseconds kMapTimeout{ 1000 }; + +static std::string GetSuperDevice() { + return "/dev/block/by-name/" + fs_mgr_get_super_partition_name(); +} + +static std::string AddSlotSuffix(const std::string& partition_name) { + return partition_name + fs_mgr_get_slot_suffix(); +} + +static bool UnmapPartitionWithSuffixOnDeviceMapper(const std::string& partition_name_suffix) { + auto state = DeviceMapper::Instance().GetState(partition_name_suffix); + if (state == DmDeviceState::INVALID) { + return true; + } + if (state == DmDeviceState::ACTIVE) { + return DestroyLogicalPartition(partition_name_suffix); + } + LOG(ERROR) << "Unknown device mapper state: " + << static_cast>(state); + return false; +} + +bool UpdaterRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name, + std::string* path) { + auto partition_name_suffix = AddSlotSuffix(partition_name); + auto state = DeviceMapper::Instance().GetState(partition_name_suffix); + if (state == DmDeviceState::INVALID) { + CreateLogicalPartitionParams params = { + .block_device = GetSuperDevice(), + // If device supports A/B, apply non-A/B update to the partition at current slot. Otherwise, + // SlotNumberForSlotSuffix("") returns 0. + .metadata_slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix()), + // If device supports A/B, apply non-A/B update to the partition at current slot. Otherwise, + // fs_mgr_get_slot_suffix() returns empty string. + .partition_name = partition_name_suffix, + .force_writable = true, + .timeout_ms = kMapTimeout, + }; + return CreateLogicalPartition(params, path); + } + + if (state == DmDeviceState::ACTIVE) { + return DeviceMapper::Instance().GetDmDevicePathByName(partition_name_suffix, path); + } + LOG(ERROR) << "Unknown device mapper state: " + << static_cast>(state); + return false; +} + +bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) { + return ::UnmapPartitionWithSuffixOnDeviceMapper(AddSlotSuffix(partition_name)); +} + +namespace { // Ops + +struct OpParameters { + std::vector tokens; + MetadataBuilder* builder; + + bool ExpectArgSize(size_t size) const { + CHECK(!tokens.empty()); + auto actual = tokens.size() - 1; + if (actual != size) { + LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual; + return false; + } + return true; + } + const std::string& op() const { + CHECK(!tokens.empty()); + return tokens[0]; + } + const std::string& arg(size_t pos) const { + CHECK_LE(pos + 1, tokens.size()); + return tokens[pos + 1]; + } + std::optional uint_arg(size_t pos, const std::string& name) const { + auto str = arg(pos); + uint64_t ret; + if (!android::base::ParseUint(str, &ret)) { + LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str; + return std::nullopt; + } + return ret; + } +}; + +using OpFunction = std::function; +using OpMap = std::map; + +bool PerformOpResize(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name_suffix = AddSlotSuffix(params.arg(0)); + auto size = params.uint_arg(1, "size"); + if (!size.has_value()) return false; + + auto partition = params.builder->FindPartition(partition_name_suffix); + if (partition == nullptr) { + LOG(ERROR) << "Failed to find partition " << partition_name_suffix + << " in dynamic partition metadata."; + return false; + } + if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) { + LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before resizing."; + return false; + } + if (!params.builder->ResizePartition(partition, size.value())) { + LOG(ERROR) << "Failed to resize partition " << partition_name_suffix << " to size " << *size + << "."; + return false; + } + return true; +} + +bool PerformOpRemove(const OpParameters& params) { + if (!params.ExpectArgSize(1)) return false; + const auto& partition_name_suffix = AddSlotSuffix(params.arg(0)); + + if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) { + LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before removing."; + return false; + } + params.builder->RemovePartition(partition_name_suffix); + return true; +} + +bool PerformOpAdd(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name_suffix = AddSlotSuffix(params.arg(0)); + const auto& group_name_suffix = AddSlotSuffix(params.arg(1)); + + if (params.builder->AddPartition(partition_name_suffix, group_name_suffix, + LP_PARTITION_ATTR_READONLY) == nullptr) { + LOG(ERROR) << "Failed to add partition " << partition_name_suffix << " to group " + << group_name_suffix << "."; + return false; + } + return true; +} + +bool PerformOpMove(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name_suffix = AddSlotSuffix(params.arg(0)); + const auto& new_group_name_suffix = AddSlotSuffix(params.arg(1)); + + auto partition = params.builder->FindPartition(partition_name_suffix); + if (partition == nullptr) { + LOG(ERROR) << "Cannot move partition " << partition_name_suffix << " to group " + << new_group_name_suffix << " because it is not found."; + return false; + } + + auto old_group_name_suffix = partition->group_name(); + if (old_group_name_suffix != new_group_name_suffix) { + if (!params.builder->ChangePartitionGroup(partition, new_group_name_suffix)) { + LOG(ERROR) << "Cannot move partition " << partition_name_suffix << " from group " + << old_group_name_suffix << " to group " << new_group_name_suffix << "."; + return false; + } + } + return true; +} + +bool PerformOpAddGroup(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& group_name_suffix = AddSlotSuffix(params.arg(0)); + auto maximum_size = params.uint_arg(1, "maximum_size"); + if (!maximum_size.has_value()) return false; + + auto group = params.builder->FindGroup(group_name_suffix); + if (group != nullptr) { + LOG(ERROR) << "Cannot add group " << group_name_suffix << " because it already exists."; + return false; + } + + if (maximum_size.value() == 0) { + LOG(WARNING) << "Adding group " << group_name_suffix << " with no size limits."; + } + + if (!params.builder->AddGroup(group_name_suffix, maximum_size.value())) { + LOG(ERROR) << "Failed to add group " << group_name_suffix << " with maximum size " + << maximum_size.value() << "."; + return false; + } + return true; +} + +bool PerformOpResizeGroup(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& group_name_suffix = AddSlotSuffix(params.arg(0)); + auto new_size = params.uint_arg(1, "maximum_size"); + if (!new_size.has_value()) return false; + + auto group = params.builder->FindGroup(group_name_suffix); + if (group == nullptr) { + LOG(ERROR) << "Cannot resize group " << group_name_suffix << " because it is not found."; + return false; + } + + auto old_size = group->maximum_size(); + if (old_size != new_size.value()) { + if (!params.builder->ChangeGroupSize(group_name_suffix, new_size.value())) { + LOG(ERROR) << "Cannot resize group " << group_name_suffix << " from " << old_size << " to " + << new_size.value() << "."; + return false; + } + } + return true; +} + +std::vector ListPartitionNamesInGroup(MetadataBuilder* builder, + const std::string& group_name_suffix) { + auto partitions = builder->ListPartitionsInGroup(group_name_suffix); + std::vector partition_names; + std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names), + [](Partition* partition) { return partition->name(); }); + return partition_names; +} + +bool PerformOpRemoveGroup(const OpParameters& params) { + if (!params.ExpectArgSize(1)) return false; + const auto& group_name_suffix = AddSlotSuffix(params.arg(0)); + + auto partition_names = ListPartitionNamesInGroup(params.builder, group_name_suffix); + if (!partition_names.empty()) { + LOG(ERROR) << "Cannot remove group " << group_name_suffix + << " because it still contains partitions [" + << android::base::Join(partition_names, ", ") << "]"; + return false; + } + params.builder->RemoveGroupAndPartitions(group_name_suffix); + return true; +} + +bool PerformOpRemoveAllGroups(const OpParameters& params) { + if (!params.ExpectArgSize(0)) return false; + + auto group_names = params.builder->ListGroups(); + for (const auto& group_name_suffix : group_names) { + auto partition_names = ListPartitionNamesInGroup(params.builder, group_name_suffix); + for (const auto& partition_name_suffix : partition_names) { + if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) { + LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before removing group " + << group_name_suffix << "."; + return false; + } + } + params.builder->RemoveGroupAndPartitions(group_name_suffix); + } + return true; +} + +} // namespace + +bool UpdaterRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) { + auto super_device = GetSuperDevice(); + auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0); + if (builder == nullptr) { + LOG(ERROR) << "Failed to load dynamic partition metadata."; + return false; + } + + static const OpMap op_map{ + // clang-format off + {"resize", PerformOpResize}, + {"remove", PerformOpRemove}, + {"add", PerformOpAdd}, + {"move", PerformOpMove}, + {"add_group", PerformOpAddGroup}, + {"resize_group", PerformOpResizeGroup}, + {"remove_group", PerformOpRemoveGroup}, + {"remove_all_groups", PerformOpRemoveAllGroups}, + // clang-format on + }; + + std::vector lines = android::base::Split(std::string(op_list_value), "\n"); + for (const auto& line : lines) { + auto comment_idx = line.find('#'); + auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx); + op_and_args = android::base::Trim(op_and_args); + if (op_and_args.empty()) continue; + + auto tokens = android::base::Split(op_and_args, " "); + const auto& op = tokens[0]; + auto it = op_map.find(op); + if (it == op_map.end()) { + LOG(ERROR) << "Unknown operation in op_list: " << op; + return false; + } + OpParameters params; + params.tokens = tokens; + params.builder = builder.get(); + if (!it->second(params)) { + return false; + } + } + + auto metadata = builder->Export(); + if (metadata == nullptr) { + LOG(ERROR) << "Failed to export metadata."; + return false; + } + + if (!UpdatePartitionTable(super_device, *metadata, 0)) { + LOG(ERROR) << "Failed to write metadata."; + return false; + } + + return true; +} diff --git a/updater_sample/.gitignore b/updater_sample/.gitignore new file mode 100644 index 0000000000..f846472457 --- /dev/null +++ b/updater_sample/.gitignore @@ -0,0 +1,10 @@ +*~ +*.bak +*.pyc +*.pyc-2.4 +Thumbs.db +*.iml +.idea/ +gen/ +.vscode +local.properties diff --git a/updater_sample/Android.bp b/updater_sample/Android.bp new file mode 100644 index 0000000000..a014248b06 --- /dev/null +++ b/updater_sample/Android.bp @@ -0,0 +1,32 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_app { + name: "SystemUpdaterSample", + sdk_version: "system_current", + + srcs: ["src/**/*.java"], + + static_libs: [ + "guava", + ], + + optimize: { + proguard_flags_files: [ + "proguard.flags", + ], + }, + + resource_dirs: ["res"], +} diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml new file mode 100644 index 0000000000..981cd8ebab --- /dev/null +++ b/updater_sample/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/updater_sample/README.md b/updater_sample/README.md new file mode 100644 index 0000000000..2e12a2fb97 --- /dev/null +++ b/updater_sample/README.md @@ -0,0 +1,271 @@ +# SystemUpdaterSample + +This app demonstrates how to use Android system updates APIs to install +[OTA updates](https://source.android.com/devices/tech/ota/). It contains a +sample client for `update_engine` to install A/B (seamless) updates. + +A/B (seamless) update is available since Android Nougat (API 24), but this sample +targets the latest android. + + +## Workflow + +SystemUpdaterSample app shows list of available updates on the UI. User is allowed +to select an update and apply it to the device. App shows installation progress, +logs can be found in `adb logcat`. User can stop or reset an update. Resetting +the update requests update engine to cancel any ongoing update, and revert +if the update has been applied. Stopping does not revert the applied update. + + +## Update Config file + +In this sample updates are defined in JSON update config files. +The structure of a config file is defined in +`com.example.android.systemupdatersample.UpdateConfig`, example file is located +at `res/raw/sample.json`. + +In real-life update system the config files expected to be served from a server +to the app, but in this sample, the config files are stored on the device. +The directory can be found in logs or on the UI. In most cases it should be located at +`/data/user/0/com.example.android.systemupdatersample/files/configs/`. + +SystemUpdaterSample app downloads OTA package from `url`. In this sample app +`url` is expected to point to file system, e.g. `file:///data/my-sample-ota-builds-dir/ota-002.zip`. + +If `ab_install_type` is `NON_STREAMING` then app checks if `url` starts +with `file://` and passes `url` to the `update_engine`. + +If `ab_install_type` is `STREAMING`, app downloads only the entries in need, as +opposed to the entire package, to initiate a streaming update. The `payload.bin` +entry, which takes up the majority of the space in an OTA package, will be +streamed by `update_engine` directly. The ZIP entries in such a package need to be +saved uncompressed (`ZIP_STORED`), so that their data can be downloaded directly +with the offset and length. As `payload.bin` itself is already in compressed +format, the size penalty is marginal. + +if `ab_config.force_switch_slot` set true device will boot to the +updated partition on next reboot; otherwise button "Switch Slot" will +become active, and user can manually set updated partition as the active slot. + +Config files can be generated using `tools/gen_update_config.py`. +Running `./tools/gen_update_config.py --help` shows usage of the script. + + +## Sample App State vs UpdateEngine Status + +UpdateEngine provides status for different stages of update application +process. But it lacks of proper status codes when update fails. + +This creates two problems: + +1. If sample app is unbound from update_engine (MainActivity is paused, destroyed), + app doesn't receive onStatusUpdate and onPayloadApplicationCompleted notifications. + If app binds to update_engine after update is completed, + only onStatusUpdate is called, but status becomes IDLE in most cases. + And there is no way to know if update was successful or not. + +2. This sample app demostrates suspend/resume using update_engins's + `cancel` and `applyPayload` (which picks up from where it left). + When `cancel` is called, status is set to `IDLE`, which doesn't allow + tracking suspended state properly. + +To solve these problems sample app implements its own separate update +state - `UpdaterState`. To solve the first problem, sample app persists +`UpdaterState` on a device. When app is resumed, it checks if `UpdaterState` +matches the update_engine's status (as onStatusUpdate is guaranteed to be called). +If they doesn't match, sample app calls `applyPayload` again with the same +parameters, and handles update completion properly using `onPayloadApplicationCompleted` +callback. The second problem is solved by adding `PAUSED` updater state. + + +## Sample App UI + +### Text fields + +- `Current Build:` - shows current active build. +- `Updater state:` - SystemUpdaterSample app state. +- `Engine status:` - last reported update_engine status. +- `Engine error:` - last reported payload application error. + +### Buttons + +- `Reload` - reloads update configs from device storage. +- `View config` - shows selected update config. +- `Apply` - applies selected update config. +- `Stop` - cancel running update, calls `UpdateEngine#cancel`. +- `Reset` - reset update, calls `UpdateEngine#resetStatus`, can be called + only when update is not running. +- `Suspend` - suspend running update, uses `UpdateEngine#cancel`. +- `Resume` - resumes suspended update, uses `UpdateEngine#applyPayload`. +- `Switch Slot` - if `ab_config.force_switch_slot` config set true, + this button will be enabled after payload is applied, + to switch A/B slot on next reboot. + + +## Sending HTTP headers from UpdateEngine + +Sometimes OTA package server might require some HTTP headers to be present, +e.g. `Authorization` header to contain valid auth token. While performing +streaming update, `UpdateEngine` allows passing on certain HTTP headers; +as of writing this sample app, these headers are `Authorization` and `User-Agent`. + +`android.os.UpdateEngine#applyPayload` contains information on +which HTTP headers are supported. + + +## Used update_engine APIs + +### UpdateEngine#bind + +Binds given callbacks to update_engine. When update_engine successfully +initialized, it's guaranteed to invoke callback onStatusUpdate. + +### UpdateEngine#applyPayload + +Start an update attempt to download an apply the provided `payload_url` if +no other update is running. The extra `key_value_pair_headers` will be +included when fetching the payload. + +`key_value_pair_headers` argument also accepts properties other than HTTP Headers. +List of allowed properties can be found in `system/update_engine/common/constants.cc`. + +### UpdateEngine#cancel + +Cancel the ongoing update. The update could be running or suspended, but it +can't be canceled after it was done. + +### UpdateEngine#resetStatus + +Reset the already applied update back to an idle state. This method can +only be called when no update attempt is going on, and it will reset the +status back to idle, deleting the currently applied update if any. + +### Callback: onStatusUpdate + +Called whenever the value of `status` or `progress` changes. For +`progress` values changes, this method will be called only if it changes significantly. +At this time of writing this doc, delta for `progress` is `0.005`. + +`onStatusUpdate` is always called when app binds to update_engine, +except when update_engine fails to initialize. + +### Callback: onPayloadApplicationComplete + +Called whenever an update attempt is completed or failed. + + +## Running on a device + +The commands are expected to be run from `$ANDROID_BUILD_TOP` and for demo +purpose only. + +### Without the privileged system permissions + +1. Compile the app `mmma -j bootable/recovery/updater_sample`. +2. Install the app to the device using `adb install `. +3. Change permissions on `/data/ota_package/` to `0777` on the device. +4. Set SELinux mode to permissive. See instructions below. +5. Add update config files; look above at [Update Config file](#Update-Config-file). +6. Push OTA packages to the device. +7. Run the sample app. + +### With the privileged system permissions + +To run sample app as a privileged system app, it needs to be installed in `/system/priv-app/`. +This directory is expected to be read-only, unless explicitly remounted. + +The recommended way to run the app is to build and install it as a +privileged system app, so it's granted the required permissions to access +`update_engine` service as well as OTA package files. Detailed steps are as follows: + +1. [Prepare to build](https://source.android.com/setup/build/building) +2. Add the module (SystemUpdaterSample) to the `PRODUCT_PACKAGES` list for the + lunch target. + e.g. add a line containing `PRODUCT_PACKAGES += SystemUpdaterSample` + to `device/google/marlin/device-common.mk`. +3. [Whitelist the sample app](https://source.android.com/devices/tech/config/perms-whitelist) + * Add + ``` + + + + ``` + to `frameworks/base/data/etc/privapp-permissions-platform.xml` +4. Add `privileged: true` to SystemUpdaterSample + [building rule](https://android.googlesource.com/platform/bootable/recovery/+/refs/heads/master/updater_sample/Android.bp). +5. Build sample app `make -j SystemUpdaterSample`. +6. Build Android `make -j` +7. [Flash the device](https://source.android.com/setup/build/running) +8. Add update config files; look above at `## Update Config file`; + `adb root` might be required. +9. Push OTA packages to the device if there is no server to stream packages from; + changing of SELinux labels of OTA packages directory might be required + `chcon -R u:object_r:ota_package_file:s0 /data/my-sample-ota-builds-dir` +10. Run the sample app. + + +## Development + +- [x] Create a UI with list of configs, current version, + control buttons, progress bar and log viewer +- [x] Add `PayloadSpec` and `PayloadSpecs` for working with + update zip file +- [x] Add `UpdateConfig` for working with json config files +- [x] Add applying non-streaming update +- [x] Prepare streaming update (partially downloading package) +- [x] Add applying streaming update +- [x] Add stop/reset the update +- [x] Add demo for passing HTTP headers to `UpdateEngine#applyPayload` +- [x] [Package compatibility check](https://source.android.com/devices/architecture/vintf/match-rules) +- [x] Deferred switch slot demo +- [x] Add UpdateManager; extract update logic from MainActivity +- [x] Add Sample app update state (separate from update_engine status) +- [x] Add smart update completion detection using onStatusUpdate +- [x] Add pause/resume demo +- [x] Verify system partition checksum for package + + +## Running tests + +The commands are expected to be run from `$ANDROID_BUILD_TOP`. + +1. Build `make -j SystemUpdaterSample` and `make -j SystemUpdaterSampleTests`. +2. Install app + `adb install $OUT/system/app/SystemUpdaterSample/SystemUpdaterSample.apk` +3. Install tests + `adb install $OUT/testcases/SystemUpdaterSampleTests/arm64/SystemUpdaterSampleTests.apk` +4. Run tests + `adb shell am instrument -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner` +5. Run a test file + ``` + adb shell am instrument \ + -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \ + com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner + ``` + + +## Accessing `android.os.UpdateEngine` API + +`android.os.UpdateEngine` APIs are marked as `@SystemApi`, meaning only system +apps can access them. + + +## Getting read/write access to `/data/ota_package/` + +Access to cache filesystem is granted only to system apps. + + +## Setting SELinux mode to permissive (0) + +```txt +local$ adb root +local$ adb shell +android# setenforce 0 +android# getenforce +``` + + +## License + +SystemUpdaterSample app is released under +[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/tools/dumpkey/Android.mk b/updater_sample/proguard.flags similarity index 61% rename from tools/dumpkey/Android.mk rename to updater_sample/proguard.flags index 31549146dd..97ab534fbe 100644 --- a/tools/dumpkey/Android.mk +++ b/updater_sample/proguard.flags @@ -1,4 +1,4 @@ -# Copyright (C) 2008 The Android Open Source Project +# Copyright (C) 2018 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -LOCAL_PATH := $(call my-dir) +# Keep, used in tests. +-keep public class com.example.android.systemupdatersample.UpdateManager { + public int getUpdaterState(); +} + +# Keep, used in tests. +-keep public class com.example.android.systemupdatersample.UpdateConfig { + public (java.lang.String, java.lang.String, int); +} -include $(CLEAR_VARS) -LOCAL_MODULE := dumpkey -LOCAL_SRC_FILES := DumpPublicKey.java -LOCAL_JAR_MANIFEST := DumpPublicKey.mf -LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host -include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/updater_sample/res/layout/activity_main.xml b/updater_sample/res/layout/activity_main.xml new file mode 100644 index 0000000000..b560827d85 --- /dev/null +++ b/updater_sample/res/layout/activity_main.xml @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + +