diff options
Diffstat (limited to '')
96 files changed, 5306 insertions, 4475 deletions
diff --git a/Android.bp b/Android.bp new file mode 100644 index 000000000..07fc27d95 --- /dev/null +++ b/Android.bp @@ -0,0 +1,4 @@ +subdirs = [ +// "bootloader_message", +// "otautil", +] diff --git a/Android.mk b/Android.mk index 11bf006b7..f42f89119 100644 --- a/Android.mk +++ b/Android.mk @@ -590,7 +590,7 @@ endif include $(CLEAR_VARS) LOCAL_SRC_FILES := fuse_sideload.cpp LOCAL_CLANG := true -LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter +LOCAL_CFLAGS := -Wall -Werror LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE LOCAL_MODULE_TAGS := optional @@ -605,13 +605,35 @@ else endif include $(BUILD_SHARED_LIBRARY) +# static libfusesideload +# =============================== (required to fix build errors in 8.1 due to use by tests) +include $(CLEAR_VARS) +LOCAL_SRC_FILES := fuse_sideload.cpp +LOCAL_CLANG := true +LOCAL_CFLAGS := -Wall -Werror +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := libfusesideload +LOCAL_SHARED_LIBRARIES := libcutils libc +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0) + LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes + LOCAL_STATIC_LIBRARIES += libmincrypttwrp + LOCAL_CFLAGS += -DUSE_MINCRYPT +else + LOCAL_STATIC_LIBRARIES += libcrypto_static +endif +include $(BUILD_STATIC_LIBRARY) + # libmounts (static library) # =============================== include $(CLEAR_VARS) LOCAL_SRC_FILES := mounts.cpp -LOCAL_CLANG := true -LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror +LOCAL_CFLAGS := \ + -Wall \ + -Werror LOCAL_MODULE := libmounts +LOCAL_STATIC_LIBRARIES := libbase include $(BUILD_STATIC_LIBRARY) # librecovery (static library) @@ -619,7 +641,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_SRC_FILES := \ install.cpp -LOCAL_CFLAGS := -Wno-unused-parameter -Werror +LOCAL_CFLAGS := -Wall -Werror LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) ifeq ($(AB_OTA_UPDATER),true) @@ -632,7 +654,8 @@ LOCAL_STATIC_LIBRARIES := \ libvintf_recovery \ libcrypto_utils \ libcrypto \ - libbase + libbase \ + libziparchive \ include $(BUILD_STATIC_LIBRARY) @@ -661,6 +684,7 @@ else LOCAL_SHARED_LIBRARIES += libcrypto libbase LOCAL_SRC_FILES += verifier.cpp asn1_decoder.cpp endif + ifeq ($(AB_OTA_UPDATER),true) LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 endif @@ -679,7 +703,6 @@ include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_MODULE := libverifier -LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ asn1_decoder.cpp \ verifier.cpp \ @@ -687,16 +710,40 @@ LOCAL_SRC_FILES := \ LOCAL_STATIC_LIBRARIES := libcrypto_static include $(BUILD_STATIC_LIBRARY) +# Wear default device +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := wear_device.cpp + +# Should match TARGET_RECOVERY_UI_LIB in BoardConfig.mk. +LOCAL_MODULE := librecovery_ui_wear + +include $(BUILD_STATIC_LIBRARY) + +# vr headset default device +# =============================== +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := vr_device.cpp + +# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk +LOCAL_MODULE := librecovery_ui_vr + +include $(BUILD_STATIC_LIBRARY) + commands_recovery_local_path := $(LOCAL_PATH) -include $(LOCAL_PATH)/tests/Android.mk \ - $(LOCAL_PATH)/tools/Android.mk \ + +include \ + $(LOCAL_PATH)/applypatch/Android.mk \ + $(LOCAL_PATH)/boot_control/Android.mk \ $(LOCAL_PATH)/edify/Android.mk \ $(LOCAL_PATH)/otafault/Android.mk \ - $(LOCAL_PATH)/bootloader_message/Android.mk \ - $(LOCAL_PATH)/bootloader_message_twrp/Android.mk \ + $(LOCAL_PATH)/tests/Android.mk \ + $(LOCAL_PATH)/tools/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ $(LOCAL_PATH)/update_verifier/Android.mk \ - $(LOCAL_PATH)/applypatch/Android.mk + $(LOCAL_PATH)/bootloader_message/Android.mk \ + $(LOCAL_PATH)/bootloader_message_twrp/Android.mk ifeq ($(wildcard system/core/uncrypt/Android.mk),) include $(commands_recovery_local_path)/uncrypt/Android.mk @@ -0,0 +1,3 @@ +enh+aosp-gerrit@google.com +tbao@google.com +xunchang@google.com diff --git a/adb_install.cpp b/adb_install.cpp index 7b0388279..291708c69 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -14,24 +14,27 @@ * limitations under the License. */ -#include <unistd.h> +#include "adb_install.h" + #include <dirent.h> #include <errno.h> +#include <fcntl.h> +#include <signal.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include <signal.h> #include <fcntl.h> #include <stdio.h> +#include <unistd.h> #include "ui.h" #include "cutils/properties.h" -#include "install.h" + #include "common.h" -#include "adb_install.h" -#include "minadbd/fuse_adb_provider.h" #include "fuse_sideload.h" #ifdef USE_OLD_VERIFIER #include "verifier24/verifier.h" @@ -40,86 +43,91 @@ #endif static void set_usb_driver(bool enabled) { - int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY); - if (fd < 0) { -/* These error messages show when built in older Android branches (e.g. Gingerbread) - It's not a critical error so we're disabling the error messages. - ui->Print("failed to open driver control: %s\n", strerror(errno)); + char configfs[PROPERTY_VALUE_MAX]; + property_get("sys.usb.configfs", configfs, "false"); + if (strcmp(configfs, "false") == 0 || strcmp(configfs, "0") == 0) + return; + + int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY); + if (fd < 0) { +/* These error messages show when built in older Android branches (e.g. Gingerbread) + It's not a critical error so we're disabling the error messages. + ui->Print("failed to open driver control: %s\n", strerror(errno)); */ - printf("failed to open driver control: %s\n", strerror(errno)); - return; - } + printf("failed to open driver control: %s\n", strerror(errno)); + return; + } - if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) { + if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) { /* - ui->Print("failed to set driver control: %s\n", strerror(errno)); + ui->Print("failed to set driver control: %s\n", strerror(errno)); */ - printf("failed to set driver control: %s\n", strerror(errno)); - } - if (close(fd) < 0) { + printf("failed to set driver control: %s\n", strerror(errno)); + } + if (close(fd) < 0) { /* - ui->Print("failed to close driver control: %s\n", strerror(errno)); + ui->Print("failed to close driver control: %s\n", strerror(errno)); */ - printf("failed to close driver control: %s\n", strerror(errno)); - } + printf("failed to close driver control: %s\n", strerror(errno)); + } } // On Android 8.0 for some reason init can't seem to completely stop adbd // so we have to kill it too if it doesn't die on its own. static void kill_adbd() { - DIR* dir = opendir("/proc"); - if (dir) { - struct dirent* de = 0; - - while ((de = readdir(dir)) != 0) { - if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) - continue; - - int pid = -1; - int ret = sscanf(de->d_name, "%d", &pid); - - if (ret == 1) { - char cmdpath[PATH_MAX]; - sprintf(cmdpath, "/proc/%d/cmdline", pid); - - FILE* file = fopen(cmdpath, "r"); - size_t task_size = PATH_MAX; - char task[PATH_MAX]; - char* p = task; - if (getline(&p, &task_size, file) > 0) { - if (strstr(task, "adbd") != 0) { - printf("adbd pid %d found, sending kill.\n", pid); - kill(pid, SIGINT); - usleep(5000); - kill(pid, SIGKILL); - } - } - fclose(file); - } + DIR* dir = opendir("/proc"); + if (dir) { + struct dirent* de = 0; + + while ((de = readdir(dir)) != 0) { + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + + int pid = -1; + int ret = sscanf(de->d_name, "%d", &pid); + + if (ret == 1) { + char cmdpath[PATH_MAX]; + sprintf(cmdpath, "/proc/%d/cmdline", pid); + + FILE* file = fopen(cmdpath, "r"); + size_t task_size = PATH_MAX; + char task[PATH_MAX]; + char* p = task; + if (getline(&p, &task_size, file) > 0) { + if (strstr(task, "adbd") != 0) { + printf("adbd pid %d found, sending kill.\n", pid); + kill(pid, SIGINT); + usleep(5000); + kill(pid, SIGKILL); + } } - closedir(dir); + fclose(file); + } } + closedir(dir); + } } static void stop_adbd() { - printf("Stopping adbd...\n"); - property_set("ctl.stop", "adbd"); - usleep(5000); - kill_adbd(); - set_usb_driver(false); + printf("Stopping adbd...\n"); + property_set("ctl.stop", "adbd"); + usleep(5000); + kill_adbd(); + set_usb_driver(false); } static bool is_ro_debuggable() { - char value[PROPERTY_VALUE_MAX+1]; - return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); + char value[PROPERTY_VALUE_MAX+1]; + return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); } static void maybe_restart_adbd() { - if (is_ro_debuggable()) { - printf("Restarting adbd...\n"); - set_usb_driver(true); - property_set("ctl.start", "adbd"); - } + if (is_ro_debuggable()) { + printf("Restarting adbd...\n"); + set_usb_driver(true); + property_set("ctl.start", "adbd"); + } } // How long (in seconds) we wait for the host to start sending us a @@ -129,83 +137,83 @@ static void maybe_restart_adbd() { int apply_from_adb(const char* install_file, pid_t* child_pid) { - stop_adbd(); - set_usb_driver(true); + stop_adbd(); + set_usb_driver(true); /* int apply_from_adb(RecoveryUI* ui, bool* wipe_cache, const char* install_file) { - modified_flash = true; + modified_flash = true; - stop_adbd(ui); - set_usb_driver(ui, true); + stop_adbd(ui); + set_usb_driver(ui, true); - ui->Print("\n\nNow send the package you want to apply\n" - "to the device with \"adb sideload <filename>\"...\n"); + ui->Print("\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload <filename>\"...\n"); */ - pid_t child; - if ((child = fork()) == 0) { - execl("/sbin/recovery", "recovery", "--adbd", install_file, NULL); - _exit(-1); - } - - *child_pid = child; - // caller can now kill the child thread from another thread - - // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host - // connects and starts serving a package. Poll for its - // appearance. (Note that inotify doesn't work with FUSE.) - int result = INSTALL_ERROR; - int status; - bool waited = false; - struct stat st; - for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { - if (waitpid(child, &status, WNOHANG) != 0) { - result = -1; - waited = true; - break; - } - - if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { - if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) { - sleep(1); - continue; - } else { - printf("\nTimed out waiting for package: %s\n\n", strerror(errno)); - result = -1; - kill(child, SIGKILL); - break; - } - } - // Install is handled elsewhere in TWRP - //install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false); - return 0; + pid_t child; + if ((child = fork()) == 0) { + execl("/sbin/recovery", "recovery", "--adbd", install_file, NULL); + _exit(-1); + } + + *child_pid = child; + // caller can now kill the child thread from another thread + + // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host + // connects and starts serving a package. Poll for its + // appearance. (Note that inotify doesn't work with FUSE.) + int result = INSTALL_ERROR; + int status; + bool waited = false; + struct stat st; + for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { + if (waitpid(child, &status, WNOHANG) != 0) { + result = -1; + waited = true; + break; } - // if we got here, something failed - *child_pid = 0; - - if (!waited) { - // Calling stat() on this magic filename signals the minadbd - // subprocess to shut down. - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); - - // TODO(dougz): there should be a way to cancel waiting for a - // package (by pushing some button combo on the device). For now - // you just have to 'adb sideload' a file that's not a valid - // package, like "/dev/null". - waitpid(child, &status, 0); + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { + if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) { + sleep(1); + continue; + } else { + printf("\nTimed out waiting for package: %s\n\n", strerror(errno)); + result = -1; + kill(child, SIGKILL); + break; + } } - - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - if (WEXITSTATUS(status) == 3) { - printf("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); - result = -2; - } else if (!WIFSIGNALED(status)) { - printf("status %d\n", WEXITSTATUS(status)); - } + // Install is handled elsewhere in TWRP + //install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false); + return 0; + } + + // if we got here, something failed + *child_pid = 0; + + if (!waited) { + // Calling stat() on this magic filename signals the minadbd + // subprocess to shut down. + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + + // TODO(dougz): there should be a way to cancel waiting for a + // package (by pushing some button combo on the device). For now + // you just have to 'adb sideload' a file that's not a valid + // package, like "/dev/null". + waitpid(child, &status, 0); + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (WEXITSTATUS(status) == 3) { + printf("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); + result = -2; + } else if (!WIFSIGNALED(status)) { + printf("adbd status %d\n", WEXITSTATUS(status)); } + } - set_usb_driver(false); - maybe_restart_adbd(); + set_usb_driver(false); + maybe_restart_adbd(); - return result; + return result; } diff --git a/adb_install.h b/adb_install.h index e9e88f755..121ae3cd2 100644 --- a/adb_install.h +++ b/adb_install.h @@ -17,6 +17,8 @@ #ifndef _ADB_INSTALL_H #define _ADB_INSTALL_H +#include <sys/types.h> + //class RecoveryUI; static void set_usb_driver(bool enabled); diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 54c37eb34..43e9b80dc 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -27,6 +27,7 @@ #include <sys/types.h> #include <unistd.h> +#include <functional> #include <memory> #include <string> #include <utility> @@ -45,7 +46,7 @@ #include "print_sha1.h" static int LoadPartitionContents(const std::string& filename, FileContents* file); -static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token); +static size_t FileSink(const unsigned char* data, size_t len, int fd); static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch, const std::string& target_filename, const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data); @@ -224,8 +225,8 @@ int SaveFileContents(const char* filename, const FileContents* file) { return -1; } - ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); - if (bytes_written != static_cast<ssize_t>(file->data.size())) { + 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; @@ -531,25 +532,17 @@ int ShowLicenses() { return 0; } -ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) { - int fd = *static_cast<int*>(token); - ssize_t done = 0; - ssize_t wrote; - while (done < len) { - wrote = TEMP_FAILURE_RETRY(ota_write(fd, data+done, len-done)); - if (wrote == -1) { - printf("error writing %zd bytes: %s\n", (len-done), strerror(errno)); - return done; - } - done += wrote; +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; } - return done; -} - -ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { - std::string* s = static_cast<std::string*>(token); - s->append(reinterpret_cast<const char*>(data), len); - return len; + done += wrote; + } + return done; } // Return the amount of free space (in bytes) on the filesystem @@ -745,9 +738,11 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr } // We store the decoded output in memory. - SinkFn sink = MemorySink; std::string memory_sink_str; // Don't need to reserve space. - void* token = &memory_sink_str; + SinkFn sink = [&memory_sink_str](const unsigned char* data, size_t len) { + memory_sink_str.append(reinterpret_cast<const char*>(data), len); + return len; + }; SHA_CTX ctx; SHA1_Init(&ctx); @@ -755,10 +750,10 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr int result; if (use_bsdiff) { result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch.get(), 0, - sink, token, &ctx); + sink, &ctx); } else { result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink, - token, &ctx, bonus_data); + &ctx, bonus_data); } if (result != 0) { diff --git a/applypatch/applypatch.sh b/applypatch/applypatch.sh deleted file mode 100755 index 8ea68a1a9..000000000 --- a/applypatch/applypatch.sh +++ /dev/null @@ -1,350 +0,0 @@ -#!/bin/bash -# -# A test suite for applypatch. Run in a client where you have done -# envsetup, choosecombo, etc. -# -# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your -# system partition. -# -# -# TODO: find some way to get this run regularly along with the rest of -# the tests. - -EMULATOR_PORT=5580 -DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/applypatch/testdata - -# This must be the filename that applypatch uses for its copies. -CACHE_TEMP_SOURCE=/cache/saved.file - -# Put all binaries and files here. We use /cache because it's a -# temporary filesystem in the emulator; it's created fresh each time -# the emulator starts. -WORK_DIR=/system - -# partition that WORK_DIR is located on, without the leading slash -WORK_FS=system - -# set to 0 to use a device instead -USE_EMULATOR=1 - -# ------------------------ - -tmpdir=$(mktemp -d) - -if [ "$USE_EMULATOR" == 1 ]; then - emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & - pid_emulator=$! - ADB="adb -s emulator-$EMULATOR_PORT " -else - ADB="adb -d " -fi - -echo "waiting to connect to device" -$ADB wait-for-device -echo "device is available" -$ADB remount -# free up enough space on the system partition for the test to run. -$ADB shell rm -r /system/media - -# run a command on the device; exit with the exit status of the device -# command. -run_command() { - $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' -} - -testname() { - echo - echo "$1"... - testname="$1" -} - -fail() { - echo - echo FAIL: $testname - echo - [ "$open_pid" == "" ] || kill $open_pid - [ "$pid_emulator" == "" ] || kill $pid_emulator - exit 1 -} - -sha1() { - sha1sum $1 | awk '{print $1}' -} - -free_space() { - run_command df | awk "/$1/ {print gensub(/K/, \"\", \"g\", \$6)}" -} - -cleanup() { - # not necessary if we're about to kill the emulator, but nice for - # running on real devices or already-running emulators. - testname "removing test files" - run_command rm $WORK_DIR/bloat.dat - run_command rm $WORK_DIR/old.file - run_command rm $WORK_DIR/foo - run_command rm $WORK_DIR/patch.bsdiff - run_command rm $WORK_DIR/applypatch - run_command rm $CACHE_TEMP_SOURCE - run_command rm /cache/bloat*.dat - - [ "$pid_emulator" == "" ] || kill $pid_emulator - - if [ $# == 0 ]; then - rm -rf $tmpdir - fi -} - -cleanup leave_tmp - -$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch - -BAD1_SHA1=$(printf "%040x" $RANDOM) -BAD2_SHA1=$(printf "%040x" $RANDOM) -OLD_SHA1=$(sha1 $DATA_DIR/old.file) -NEW_SHA1=$(sha1 $DATA_DIR/new.file) -NEW_SIZE=$(stat -c %s $DATA_DIR/new.file) - -# --------------- basic execution ---------------------- - -testname "usage message" -run_command $WORK_DIR/applypatch && fail - -testname "display license" -run_command $WORK_DIR/applypatch -l | grep -q -i copyright || fail - - -# --------------- check mode ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR - -testname "check mode single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "check mode cache (corrupted) single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode cache (corrupted) multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode cache (corrupted) failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - -# remove the old file entirely -run_command rm $WORK_DIR/old.file - -testname "check mode cache (missing) single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode cache (missing) multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode cache (missing) failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - - -# --------------- apply patch ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR -echo hello > $tmpdir/foo -$ADB push $tmpdir/foo $WORK_DIR - -# Check that the partition has enough space to apply the patch without -# copying. If it doesn't, we'll be testing the low-space condition -# when we intend to test the not-low-space condition. -testname "apply patches (with enough space)" -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS." -if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then - echo "Not enough space on /$WORK_FS to patch test file." - echo - echo "This doesn't mean that applypatch is necessarily broken;" - echo "just that /$WORK_FS doesn't have enough free space to" - echo "properly run this test." - exit 1 -fi - -testname "apply bsdiff patch" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - - -# --------------- apply patch in new location ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -# Check that the partition has enough space to apply the patch without -# copying. If it doesn't, we'll be testing the low-space condition -# when we intend to test the not-low-space condition. -testname "apply patch to new location (with enough space)" -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS." -if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then - echo "Not enough space on /$WORK_FS to patch test file." - echo - echo "This doesn't mean that applypatch is necessarily broken;" - echo "just that /$WORK_FS doesn't have enough free space to" - echo "properly run this test." - exit 1 -fi - -run_command rm $WORK_DIR/new.file -run_command rm $CACHE_TEMP_SOURCE - -testname "apply bsdiff patch to new location" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch to new location" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "apply bsdiff patch to new location with corrupted source" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -# put some junk in the cache copy, too -run_command dd if=/dev/urandom of=$CACHE_TEMP_SOURCE count=100 bs=1024 || fail - -run_command rm $WORK_DIR/new.file -testname "apply bsdiff patch to new location with corrupted source and copy (no new file)" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail - -# put some junk in the new file -run_command dd if=/dev/urandom of=$WORK_DIR/new.file count=100 bs=1024 || fail - -testname "apply bsdiff patch to new location with corrupted source and copy (bad new file)" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail - -# --------------- apply patch with low space on /system ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS; we'll soon fix that." -echo run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail -run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS now." - -testname "apply bsdiff patch with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -# --------------- apply patch with low space on /system and /cache ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS" - -run_command mkdir /cache/subdir -run_command 'echo > /cache/subdir/a.file' -run_command 'echo > /cache/a.file' -run_command mkdir /cache/recovery /cache/recovery/otatest -run_command 'echo > /cache/recovery/otatest/b.file' -run_command "echo > $CACHE_TEMP_SOURCE" -free_kb=$(free_space cache) -echo "${free_kb}kb free on /cache; we'll soon fix that." -run_command dd if=/dev/zero of=/cache/bloat_small.dat count=128 bs=1024 || fail -run_command dd if=/dev/zero of=/cache/bloat_large.dat count=$((free_kb-640)) bs=1024 || fail -free_kb=$(free_space cache) -echo "${free_kb}kb free on /cache now." - -testname "apply bsdiff patch with low space, full cache, can't delete enough" -$ADB shell 'cat >> /cache/bloat_large.dat' & open_pid=$! -echo "open_pid is $open_pid" - -# size check should fail even though it deletes some stuff -run_command $WORK_DIR/applypatch -s $NEW_SIZE && fail -run_command ls /cache/bloat_small.dat && fail # was deleted -run_command ls /cache/a.file && fail # was deleted -run_command ls /cache/recovery/otatest/b.file && fail # was deleted -run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open -run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -# should fail; not enough files can be deleted -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff && fail -run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open -run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -kill $open_pid # /cache/bloat_large.dat is no longer open - -testname "apply bsdiff patch with low space, full cache, can delete enough" - -# should succeed after deleting /cache/bloat_large.dat -run_command $WORK_DIR/applypatch -s $NEW_SIZE || fail -run_command ls /cache/bloat_large.dat && fail # was deleted -run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -# should succeed -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail -run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE && fail # was deleted because patching overwrote it, then deleted it - -# --------------- apply patch from cache ---------------------- - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "apply bsdiff patch from cache (corrupted source) with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# remove the old file entirely -run_command rm $WORK_DIR/old.file - -testname "apply bsdiff patch from cache (missing source) with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - - -# --------------- cleanup ---------------------- - -cleanup - -echo -echo PASS -echo - diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp index 7b191a801..aa32d57ef 100644 --- a/applypatch/applypatch_modes.cpp +++ b/applypatch/applypatch_modes.cpp @@ -44,19 +44,6 @@ static int CheckMode(int argc, const char** argv) { return applypatch_check(argv[2], sha1); } -static int SpaceMode(int argc, const char** argv) { - if (argc != 3) { - return 2; - } - - size_t bytes; - if (!android::base::ParseUint(argv[2], &bytes) || bytes == 0) { - printf("can't parse \"%s\" as byte count\n\n", argv[2]); - return 1; - } - return CacheSizeCheck(bytes); -} - // Parse arguments (which should be of the form "<sha1>:<filename>" into the // new parallel arrays *sha1s and *files. Returns true on success. static bool ParsePatchArgs(int argc, const char** argv, std::vector<std::string>* sha1s, @@ -175,13 +162,12 @@ int applypatch_modes(int argc, const char** argv) { "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> " "[<src-sha1>:<patch> ...]\n" " or %s -c <file> [<sha1> ...]\n" - " or %s -s <bytes>\n" " or %s -l\n" "\n" "Filenames may be of the form\n" " EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n" "to specify reading from or writing to an EMMC partition.\n\n", - argv[0], argv[0], argv[0], argv[0]); + argv[0], argv[0], argv[0]); return 2; } @@ -191,8 +177,6 @@ int applypatch_modes(int argc, const char** argv) { result = ShowLicenses(); } else if (strncmp(argv[1], "-c", 3) == 0) { result = CheckMode(argc, argv); - } else if (strncmp(argv[1], "-s", 3) == 0) { - result = SpaceMode(argc, argv); } else { result = PatchMode(argc, argv); } diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp index 9920c2be1..65ee614ef 100644 --- a/applypatch/bspatch.cpp +++ b/applypatch/bspatch.cpp @@ -23,10 +23,14 @@ #include <stdio.h> #include <sys/types.h> +#include <string> + +#include <android-base/logging.h> #include <bspatch.h> +#include <openssl/sha.h> #include "applypatch/applypatch.h" -#include "openssl/sha.h" +#include "print_sha1.h" void ShowBSDiffLicense() { puts("The bsdiff library used herein is:\n" @@ -60,25 +64,31 @@ void ShowBSDiffLicense() { ); } -int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, - ssize_t patch_offset, SinkFn sink, void* token, SHA_CTX* ctx) { - auto sha_sink = [&](const uint8_t* data, size_t len) { - len = sink(data, len, token); +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; }; - return bsdiff::bspatch(old_data, old_size, - reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]), - patch->data.size(), sha_sink); -} -int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, const Value* patch, - ssize_t patch_offset, std::vector<unsigned char>* new_data) { - auto vector_sink = [new_data](const uint8_t* data, size_t len) { - new_data->insert(new_data->end(), data, data + len); - return len; - }; - return bsdiff::bspatch(old_data, old_size, - reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]), - patch->data.size(), vector_sink); -} + CHECK(patch != nullptr); + CHECK_LE(patch_offset, patch->data.size()); + + int result = bsdiff::bspatch(old_data, old_size, + reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]), + patch->data.size() - patch_offset, sha_sink); + if (result != 0) { + LOG(ERROR) << "bspatch failed, result: " << result; + // print SHA1 of the patch in the case of a data error. + if (result == 2) { + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(patch->data.data() + patch_offset), + patch->data.size() - patch_offset, digest); + std::string patch_sha1 = print_sha1(digest); + LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: " + << patch_sha1; + } + } + return result; +}
\ No newline at end of file diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp index 41d73ab98..fc240644f 100644 --- a/applypatch/imgdiff.cpp +++ b/applypatch/imgdiff.cpp @@ -693,6 +693,20 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, continue; } + // The footer contains the size of the uncompressed data. Double-check to make sure that it + // matches the size of the data we got when we actually did the decompression. + size_t footer_index = pos + raw_data_len + GZIP_FOOTER_LEN - 4; + if (sz - footer_index < 4) { + printf("Warning: invalid footer position; treating as a nomal chunk\n"); + continue; + } + size_t footer_size = get_unaligned<uint32_t>(img->data() + footer_index); + if (footer_size != uncompressed_len) { + printf("Warning: footer size %zu != decompressed size %zu; treating as a nomal chunk\n", + footer_size, uncompressed_len); + continue; + } + ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len); uncompressed_data.resize(uncompressed_len); body.SetUncompressedData(std::move(uncompressed_data)); @@ -704,17 +718,6 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN); pos += GZIP_FOOTER_LEN; - - // The footer (that we just skipped over) contains the size of - // the uncompressed data. Double-check to make sure that it - // matches the size of the data we got when we actually did - // the decompression. - size_t footer_size = get_unaligned<uint32_t>(img->data() + pos - 4); - if (footer_size != body.DataLengthForPatch()) { - printf("Error: footer size %zu != decompressed size %zu\n", footer_size, - body.GetRawDataLength()); - return false; - } } else { // Use a normal chunk to take all the contents until the next gzip chunk (or EOF); we expect // the number of chunks to be small (5 for typical boot and recovery images). diff --git a/applypatch/imgdiff_test.sh b/applypatch/imgdiff_test.sh deleted file mode 100755 index dcdb922b4..000000000 --- a/applypatch/imgdiff_test.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash -# -# A script for testing imgdiff/applypatch. It takes two full OTA -# packages as arguments. It generates (on the host) patches for all -# the zip/jar/apk files they have in common, as well as boot and -# recovery images. It then applies the patches on the device (or -# emulator) and checks that the resulting file is correct. - -EMULATOR_PORT=5580 - -# set to 0 to use a device instead -USE_EMULATOR=0 - -# where on the device to do all the patching. -WORK_DIR=/data/local/tmp - -START_OTA_PACKAGE=$1 -END_OTA_PACKAGE=$2 - -# ------------------------ - -tmpdir=$(mktemp -d) - -if [ "$USE_EMULATOR" == 1 ]; then - emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & - pid_emulator=$! - ADB="adb -s emulator-$EMULATOR_PORT " -else - ADB="adb -d " -fi - -echo "waiting to connect to device" -$ADB wait-for-device - -# run a command on the device; exit with the exit status of the device -# command. -run_command() { - $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' -} - -testname() { - echo - echo "$1"... - testname="$1" -} - -fail() { - echo - echo FAIL: $testname - echo - [ "$open_pid" == "" ] || kill $open_pid - [ "$pid_emulator" == "" ] || kill $pid_emulator - exit 1 -} - -sha1() { - sha1sum $1 | awk '{print $1}' -} - -size() { - stat -c %s $1 | tr -d '\n' -} - -cleanup() { - # not necessary if we're about to kill the emulator, but nice for - # running on real devices or already-running emulators. - testname "removing test files" - run_command rm $WORK_DIR/applypatch - run_command rm $WORK_DIR/source - run_command rm $WORK_DIR/target - run_command rm $WORK_DIR/patch - - [ "$pid_emulator" == "" ] || kill $pid_emulator - - rm -rf $tmpdir -} - -$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch - -patch_and_apply() { - local fn=$1 - shift - - unzip -p $START_OTA_PACKAGE $fn > $tmpdir/source - unzip -p $END_OTA_PACKAGE $fn > $tmpdir/target - imgdiff "$@" $tmpdir/source $tmpdir/target $tmpdir/patch - bsdiff $tmpdir/source $tmpdir/target $tmpdir/patch.bs - echo "patch for $fn is $(size $tmpdir/patch) [of $(size $tmpdir/target)] ($(size $tmpdir/patch.bs) with bsdiff)" - echo "$fn $(size $tmpdir/patch) of $(size $tmpdir/target) bsdiff $(size $tmpdir/patch.bs)" >> /tmp/stats.txt - $ADB push $tmpdir/source $WORK_DIR/source || fail "source push failed" - run_command rm /data/local/tmp/target - $ADB push $tmpdir/patch $WORK_DIR/patch || fail "patch push failed" - run_command /data/local/tmp/applypatch /data/local/tmp/source \ - /data/local/tmp/target $(sha1 $tmpdir/target) $(size $tmpdir/target) \ - $(sha1 $tmpdir/source):/data/local/tmp/patch \ - || fail "applypatch of $fn failed" - $ADB pull /data/local/tmp/target $tmpdir/result - diff -q $tmpdir/target $tmpdir/result || fail "patch output not correct!" -} - -# --------------- basic execution ---------------------- - -for i in $((zipinfo -1 $START_OTA_PACKAGE; zipinfo -1 $END_OTA_PACKAGE) | \ - sort | uniq -d | egrep -e '[.](apk|jar|zip)$'); do - patch_and_apply $i -z -done -patch_and_apply boot.img -patch_and_apply system/recovery.img - - -# --------------- cleanup ---------------------- - -cleanup - -echo -echo PASS -echo - diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp index adcc61fd6..df75f98d4 100644 --- a/applypatch/imgpatch.cpp +++ b/applypatch/imgpatch.cpp @@ -26,12 +26,14 @@ #include <sys/stat.h> #include <unistd.h> +#include <memory> #include <string> #include <vector> +#include <android-base/logging.h> +#include <android-base/memory.h> #include <applypatch/applypatch.h> #include <applypatch/imgdiff.h> -#include <android-base/memory.h> #include <openssl/sha.h> #include <zlib.h> @@ -43,12 +45,91 @@ static inline int32_t Read4(const void *address) { return android::base::get_unaligned<int32_t>(address); } -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, - const unsigned char* patch_data, ssize_t patch_size, - SinkFn sink, void* token) { +// This function is a wrapper of ApplyBSDiffPatch(). It has a custom sink function to deflate the +// patched data and stream the deflated data to output. +static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_len, + const Value* patch, size_t patch_offset, + const char* deflate_header, SinkFn sink, SHA_CTX* ctx) { + size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32)); + int level = Read4(deflate_header + 40); + int method = Read4(deflate_header + 44); + int window_bits = Read4(deflate_header + 48); + int mem_level = Read4(deflate_header + 52); + int strategy = Read4(deflate_header + 56); + + std::unique_ptr<z_stream, decltype(&deflateEnd)> strm(new z_stream(), deflateEnd); + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + strm->avail_in = 0; + strm->next_in = nullptr; + int ret = deflateInit2(strm.get(), level, method, window_bits, mem_level, strategy); + if (ret != Z_OK) { + LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret; + return false; + } + + // Define a custom sink wrapper that feeds to bspatch. It deflates the available patch data on + // the fly and outputs the compressed data to the given sink. + size_t actual_target_length = 0; + size_t total_written = 0; + static constexpr size_t buffer_size = 32768; + auto compression_sink = [&](const uint8_t* data, size_t len) -> size_t { + // The input patch length for an update never exceeds INT_MAX. + strm->avail_in = len; + strm->next_in = data; + do { + std::vector<uint8_t> buffer(buffer_size); + strm->avail_out = buffer_size; + strm->next_out = buffer.data(); + if (actual_target_length + len < expected_target_length) { + ret = deflate(strm.get(), Z_NO_FLUSH); + } else { + ret = deflate(strm.get(), Z_FINISH); + } + if (ret != Z_OK && ret != Z_STREAM_END) { + LOG(ERROR) << "Failed to deflate stream: " << ret; + // zero length indicates an error in the sink function of bspatch(). + return 0; + } + + size_t have = buffer_size - strm->avail_out; + total_written += have; + if (sink(buffer.data(), have) != have) { + LOG(ERROR) << "Failed to write " << have << " compressed bytes to output."; + return 0; + } + if (ctx) SHA1_Update(ctx, buffer.data(), have); + } while ((strm->avail_in != 0 || strm->avail_out == 0) && ret != Z_STREAM_END); + + actual_target_length += len; + return len; + }; + + if (ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr) != 0) { + return false; + } + + if (ret != Z_STREAM_END) { + LOG(ERROR) << "ret is expected to be Z_STREAM_END, but it's " << ret; + return false; + } + + if (expected_target_length != actual_target_length) { + LOG(ERROR) << "target length is expected to be " << expected_target_length << ", but it's " + << actual_target_length; + return false; + } + LOG(DEBUG) << "bspatch writes " << total_written << " bytes in total to streaming output."; + + return true; +} + +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data, + size_t patch_size, SinkFn sink) { Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size)); - return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr); + return ApplyImagePatch(old_data, old_size, &patch, sink, nullptr, nullptr); } /* @@ -57,8 +138,8 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, * file, and update the SHA context with the output data as well. * Return 0 on success. */ -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, - SinkFn sink, void* token, SHA_CTX* ctx, const Value* bonus_data) { +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink, + SHA_CTX* ctx, const Value* bonus_data) { if (patch->data.size() < 12) { printf("patch too short to contain header\n"); return -1; @@ -97,11 +178,14 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value size_t src_len = static_cast<size_t>(Read8(normal_header + 8)); size_t patch_offset = static_cast<size_t>(Read8(normal_header + 16)); - if (src_start + src_len > static_cast<size_t>(old_size)) { + if (src_start + src_len > old_size) { printf("source data too short\n"); return -1; } - ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, token, ctx); + if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) { + printf("Failed to apply bsdiff patch.\n"); + return -1; + } } else if (type == CHUNK_RAW) { const char* raw_header = &patch->data[pos]; pos += 4; @@ -110,15 +194,14 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value return -1; } - ssize_t data_len = Read4(raw_header); + size_t data_len = static_cast<size_t>(Read4(raw_header)); if (pos + data_len > patch->data.size()) { printf("failed to read chunk %d raw data\n", i); return -1; } if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len); - if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len, token) != - data_len) { + if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len) != data_len) { printf("failed to write chunk %d raw data\n", i); return -1; } @@ -136,14 +219,8 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value size_t src_len = static_cast<size_t>(Read8(deflate_header + 8)); size_t patch_offset = static_cast<size_t>(Read8(deflate_header + 16)); size_t expanded_len = static_cast<size_t>(Read8(deflate_header + 24)); - size_t target_len = static_cast<size_t>(Read8(deflate_header + 32)); - int level = Read4(deflate_header + 40); - int method = Read4(deflate_header + 44); - int windowBits = Read4(deflate_header + 48); - int memLevel = Read4(deflate_header + 52); - int strategy = Read4(deflate_header + 56); - - if (src_start + src_len > static_cast<size_t>(old_size)) { + + if (src_start + src_len > old_size) { printf("source data too short\n"); return -1; } @@ -198,58 +275,12 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value } } - // Next, apply the bsdiff patch (in memory) to the uncompressed data. - std::vector<unsigned char> uncompressed_target_data; - // TODO(senj): Remove the only usage of ApplyBSDiffPatchMem here, - // replace it with ApplyBSDiffPatch with a custom sink function that - // wraps the given sink function to stream output to save memory. - if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, patch, patch_offset, - &uncompressed_target_data) != 0) { - return -1; - } - if (uncompressed_target_data.size() != target_len) { - printf("expected target len to be %zu, but it's %zu\n", target_len, - uncompressed_target_data.size()); + if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch, + patch_offset, deflate_header, sink, ctx)) { + LOG(ERROR) << "Fail to apply streaming bspatch."; return -1; } - // Now compress the target data and append it to the output. - - // we're done with the expanded_source data buffer, so we'll - // reuse that memory to receive the output of deflate. - if (expanded_source.size() < 32768U) { - expanded_source.resize(32768U); - } - - { - std::vector<unsigned char>& temp_data = expanded_source; - - // now the deflate stream - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = uncompressed_target_data.size(); - strm.next_in = uncompressed_target_data.data(); - int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy); - if (ret != Z_OK) { - printf("failed to init uncompressed data deflation: %d\n", ret); - return -1; - } - do { - strm.avail_out = temp_data.size(); - strm.next_out = temp_data.data(); - ret = deflate(&strm, Z_FINISH); - ssize_t have = temp_data.size() - strm.avail_out; - - if (sink(temp_data.data(), have, token) != have) { - printf("failed to write %zd compressed bytes to output\n", have); - return -1; - } - if (ctx) SHA1_Update(ctx, temp_data.data(), have); - } while (ret != Z_STREAM_END); - deflateEnd(&strm); - } } else { printf("patch chunk %d is unknown type %d\n", i, type); return -1; diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h index 4489decb6..581360ef1 100644 --- a/applypatch/include/applypatch/applypatch.h +++ b/applypatch/include/applypatch/applypatch.h @@ -20,6 +20,7 @@ #include <stdint.h> #include <sys/stat.h> +#include <functional> #include <memory> #include <string> #include <vector> @@ -41,7 +42,7 @@ struct FileContents { // and use it as the source instead. #define CACHE_TEMP_SOURCE "/cache/saved.file" -typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*); +using SinkFn = std::function<size_t(const unsigned char*, size_t)>; // applypatch.cpp int ShowLicenses(); @@ -66,18 +67,12 @@ int SaveFileContents(const char* filename, const FileContents* file); // bspatch.cpp void ShowBSDiffLicense(); -int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - SinkFn sink, void* token, SHA_CTX* ctx); -int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - std::vector<unsigned char>* new_data); +int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch, + size_t patch_offset, SinkFn sink, SHA_CTX* ctx); // imgpatch.cpp -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, - const Value* patch, - SinkFn sink, void* token, SHA_CTX* ctx, - const Value* bonus_data); +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink, + SHA_CTX* ctx, const Value* bonus_data); // freecache.cpp int MakeFreeSpaceOnCache(size_t bytes_needed); diff --git a/applypatch/include/applypatch/imgpatch.h b/applypatch/include/applypatch/imgpatch.h index 6549f79f0..07c66094f 100644 --- a/applypatch/include/applypatch/imgpatch.h +++ b/applypatch/include/applypatch/imgpatch.h @@ -19,10 +19,11 @@ #include <sys/types.h> -using SinkFn = ssize_t (*)(const unsigned char*, ssize_t, void*); +#include <functional> -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, - const unsigned char* patch_data, ssize_t patch_size, - SinkFn sink, void* token); +using SinkFn = std::function<size_t(const unsigned char*, size_t)>; + +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data, + size_t patch_size, SinkFn sink); #endif // _APPLYPATCH_IMGPATCH_H diff --git a/applypatch/testdata/new.file b/applypatch/testdata/new.file Binary files differdeleted file mode 100644 index cdeb8fd50..000000000 --- a/applypatch/testdata/new.file +++ /dev/null diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file Binary files differdeleted file mode 100644 index 166c8732e..000000000 --- a/applypatch/testdata/old.file +++ /dev/null diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff Binary files differdeleted file mode 100644 index b78d38573..000000000 --- a/applypatch/testdata/patch.bsdiff +++ /dev/null diff --git a/boot_control/Android.mk b/boot_control/Android.mk new file mode 100644 index 000000000..27e3d9765 --- /dev/null +++ b/boot_control/Android.mk @@ -0,0 +1,34 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := bootctrl.bcb +LOCAL_MODULE_RELATIVE_PATH := hw +LOCAL_SRC_FILES := boot_control.cpp +LOCAL_CFLAGS := \ + -D_FILE_OFFSET_BITS=64 \ + -Werror \ + -Wall \ + -Wextra \ + -Wno-unused-parameter +LOCAL_SHARED_LIBRARIES := liblog +LOCAL_STATIC_LIBRARIES := libbootloader_message libfs_mgr libbase +LOCAL_POST_INSTALL_CMD := \ + $(hide) mkdir -p $(TARGET_OUT_SHARED_LIBRARIES)/hw && \ + ln -sf bootctrl.bcb.so $(TARGET_OUT_SHARED_LIBRARIES)/hw/bootctrl.default.so +include $(BUILD_SHARED_LIBRARY) diff --git a/boot_control/boot_control.cpp b/boot_control/boot_control.cpp new file mode 100644 index 000000000..ec97b6ced --- /dev/null +++ b/boot_control/boot_control.cpp @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <endian.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> + +#include <string> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> +#include <hardware/boot_control.h> +#include <hardware/hardware.h> + +#include <bootloader_message/bootloader_message.h> + +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<const uint8_t*>(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<boot_control_private_t*>(module)); +} + +unsigned int BootControl_getNumberSlots(boot_control_module_t* module) { + return reinterpret_cast<boot_control_private_t*>(module)->num_slots; +} + +unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) { + return reinterpret_cast<boot_control_private_t*>(module)->current_slot; +} + +int BootControl_markBootSuccessful(boot_control_module_t* module) { + boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(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<boot_control_private_t*>(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<boot_control_private_t*>(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<boot_control_private_t*>(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<boot_control_private_t*>(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<boot_control_private_t*>(module)->num_slots) { + return NULL; + } + return kSlotSuffixes[slot]; +} + +static int BootControl_open(const hw_module_t* module __unused, const char* id __unused, + hw_device_t** device __unused) { + /* Nothing to do currently. */ + return 0; +} + +struct hw_module_methods_t BootControl_methods = { + .open = BootControl_open, +}; + +} // namespace + +boot_control_private_t HAL_MODULE_INFO_SYM = { + .base = + { + .common = + { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = BOOT_CONTROL_HARDWARE_MODULE_ID, + .name = "AOSP reference bootctrl HAL", + .author = "The Android Open Source Project", + .methods = &BootControl_methods, + }, + .init = BootControl_init, + .getNumberSlots = BootControl_getNumberSlots, + .getCurrentSlot = BootControl_getCurrentSlot, + .markBootSuccessful = BootControl_markBootSuccessful, + .setActiveBootSlot = BootControl_setActiveBootSlot, + .setSlotAsUnbootable = BootControl_setSlotAsUnbootable, + .isSlotBootable = BootControl_isSlotBootable, + .getSuffix = BootControl_getSuffix, + .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful, + }, + .initialized = false, + .misc_device = nullptr, + .num_slots = 0, + .current_slot = 0, +}; diff --git a/bootloader_message/Android.NObp b/bootloader_message/Android.NObp new file mode 100644 index 000000000..f0d76e718 --- /dev/null +++ b/bootloader_message/Android.NObp @@ -0,0 +1,26 @@ +// +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_library_static { + name: "libbootloader_message", + srcs: ["bootloader_message.cpp"], + cppflags: ["-Werror"], + static_libs: [ + "libbase", + "libfs_mgr", + ], + export_include_dirs: ["include"], +} diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index dcaeb794f..6c237e620 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -144,6 +144,13 @@ static bool write_misc_partition(const void* p, size_t size, const std::string& return true; } +std::string get_bootloader_message_blk_device(std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) return ""; + if (!wait_for_device(misc_blk_device, err)) return ""; + return misc_blk_device; +} + bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device, std::string* err) { return read_misc_partition(boot, sizeof(*boot), misc_blk_device, diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index 4ad18f2d9..4da1171cd 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -191,6 +191,11 @@ static_assert(sizeof(struct bootloader_control) == #include <string> #include <vector> +// Return the block device name for the bootloader message partition and waits +// for the device for up to 10 seconds. In case of error returns the empty +// string. +std::string get_bootloader_message_blk_device(std::string* err); + // Read bootloader message into boot. Error message will be set in err. bool read_bootloader_message(bootloader_message* boot, std::string* err); diff --git a/edify/parser.yy b/edify/parser.yy index 97205fe3b..b1685eb1f 100644 --- a/edify/parser.yy +++ b/edify/parser.yy @@ -23,6 +23,8 @@ #include <string> #include <vector> +#include <android-base/macros.h> + #include "expr.h" #include "yydefs.h" #include "parser.h" @@ -121,6 +123,7 @@ arglist: /* empty */ { $$->emplace_back($1); } | arglist ',' expr { + UNUSED($1); $$->push_back(std::unique_ptr<Expr>($3)); } ; diff --git a/error_code.h b/error_code.h index cde4ee6de..9fe047c91 100644 --- a/error_code.h +++ b/error_code.h @@ -24,6 +24,7 @@ enum ErrorCode { kZipOpenFailure, kBootreasonInBlacklist, kPackageCompatibilityFailure, + kScriptExecutionFailure, }; enum CauseCode { @@ -43,6 +44,7 @@ enum CauseCode { kTune2FsFailure, kRebootFailure, kPackageExtractFileFailure, + kPatchApplicationFailure, kVendorFailure = 200 }; diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp index f57d479b0..f667cd4a1 100644 --- a/fuse_sideload.cpp +++ b/fuse_sideload.cpp @@ -235,11 +235,13 @@ static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse return NO_STATUS; } -static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { +static int handle_flush(void* /* data */, struct fuse_data* /* fd */, + const struct fuse_in_header* /* hdr */) { return 0; } -static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { +static int handle_release(void* /* data */, struct fuse_data* /* fd */, + const struct fuse_in_header* /* hdr */) { return 0; } @@ -375,166 +377,167 @@ static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_he return NO_STATUS; } -int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, - uint64_t file_size, uint32_t block_size) -{ - int result; - - // If something's already mounted on our mountpoint, try to remove - // it. (Mostly in case of a previous abnormal exit.) - umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE); - - if (block_size < 1024) { - fprintf(stderr, "block size (%u) is too small\n", block_size); - return -1; - } - if (block_size > (1<<22)) { // 4 MiB - fprintf(stderr, "block size (%u) is too large\n", block_size); - return -1; - } - - struct fuse_data fd; - memset(&fd, 0, sizeof(fd)); - fd.vtab = vtab; - fd.cookie = cookie; - fd.file_size = file_size; - fd.block_size = block_size; - fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1); - - if (fd.file_blocks > (1<<18)) { - fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); - result = -1; - goto done; - } - - fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH); - if (fd.hashes == NULL) { - fprintf(stderr, "failed to allocate %d bites for hashes\n", - fd.file_blocks * SHA256_DIGEST_LENGTH); - result = -1; - goto done; - } - - fd.uid = getuid(); - fd.gid = getgid(); - - fd.curr_block = -1; - fd.block_data = (uint8_t*)malloc(block_size); - if (fd.block_data == NULL) { - fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); - result = -1; - goto done; - } - fd.extra_block = (uint8_t*)malloc(block_size); - if (fd.extra_block == NULL) { - fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); - result = -1; - goto done; - } - - fd.ffd = open("/dev/fuse", O_RDWR); - if (fd.ffd < 0) { - perror("open /dev/fuse"); - result = -1; - goto done; - } - +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size, + uint32_t block_size) { + // If something's already mounted on our mountpoint, try to remove it. (Mostly in case of a + // previous abnormal exit.) + umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE); + + // fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read. + if (block_size < 4096) { + fprintf(stderr, "block size (%u) is too small\n", block_size); + return -1; + } + if (block_size > (1 << 22)) { // 4 MiB + fprintf(stderr, "block size (%u) is too large\n", block_size); + return -1; + } + + struct fuse_data fd = {}; + fd.vtab = vtab; + fd.cookie = cookie; + fd.file_size = file_size; + fd.block_size = block_size; + fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1); + + int result; + if (fd.file_blocks > (1 << 18)) { + fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); + result = -1; + goto done; + } + + fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH); + if (fd.hashes == NULL) { + fprintf(stderr, "failed to allocate %d bites for hashes\n", + fd.file_blocks * SHA256_DIGEST_LENGTH); + result = -1; + goto done; + } + + fd.uid = getuid(); + fd.gid = getgid(); + + fd.curr_block = -1; + fd.block_data = (uint8_t*)malloc(block_size); + if (fd.block_data == NULL) { + fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); + result = -1; + goto done; + } + fd.extra_block = (uint8_t*)malloc(block_size); + if (fd.extra_block == NULL) { + fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); + result = -1; + goto done; + } + + fd.ffd = open("/dev/fuse", O_RDWR); + if (fd.ffd < 0) { + perror("open /dev/fuse"); + result = -1; + goto done; + } + + { char opts[256]; snprintf(opts, sizeof(opts), ("fd=%d,user_id=%d,group_id=%d,max_read=%u," "allow_other,rootmode=040000"), fd.ffd, fd.uid, fd.gid, block_size); - result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, - "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); + result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse", + MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); if (result < 0) { - perror("mount"); - goto done; + perror("mount"); + goto done; + } + } + + uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX * 8]; + for (;;) { + ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); + if (len == -1) { + perror("read request"); + if (errno == ENODEV) { + result = -1; + break; + } + continue; } - uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8]; - for (;;) { - ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); - if (len == -1) { - perror("read request"); - if (errno == ENODEV) { - result = -1; - break; - } - continue; - } - if ((size_t)len < sizeof(struct fuse_in_header)) { - fprintf(stderr, "request too short: len=%zu\n", (size_t)len); - continue; - } + if (static_cast<size_t>(len) < sizeof(struct fuse_in_header)) { + fprintf(stderr, "request too short: len=%zd\n", len); + continue; + } - struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer; - void* data = request_buffer + sizeof(struct fuse_in_header); + struct fuse_in_header* hdr = reinterpret_cast<struct fuse_in_header*>(request_buffer); + void* data = request_buffer + sizeof(struct fuse_in_header); - result = -ENOSYS; + result = -ENOSYS; - switch (hdr->opcode) { - case FUSE_INIT: - result = handle_init(data, &fd, hdr); - break; + switch (hdr->opcode) { + case FUSE_INIT: + result = handle_init(data, &fd, hdr); + break; - case FUSE_LOOKUP: - result = handle_lookup(data, &fd, hdr); - break; + case FUSE_LOOKUP: + result = handle_lookup(data, &fd, hdr); + break; - case FUSE_GETATTR: - result = handle_getattr(data, &fd, hdr); - break; + case FUSE_GETATTR: + result = handle_getattr(data, &fd, hdr); + break; - case FUSE_OPEN: - result = handle_open(data, &fd, hdr); - break; + case FUSE_OPEN: + result = handle_open(data, &fd, hdr); + break; - case FUSE_READ: - result = handle_read(data, &fd, hdr); - break; + case FUSE_READ: + result = handle_read(data, &fd, hdr); + break; - case FUSE_FLUSH: - result = handle_flush(data, &fd, hdr); - break; + case FUSE_FLUSH: + result = handle_flush(data, &fd, hdr); + break; - case FUSE_RELEASE: - result = handle_release(data, &fd, hdr); - break; + case FUSE_RELEASE: + result = handle_release(data, &fd, hdr); + break; - default: - fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); - break; - } + default: + fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); + break; + } - if (result == NO_STATUS_EXIT) { - result = 0; - break; - } + if (result == NO_STATUS_EXIT) { + result = 0; + break; + } - if (result != NO_STATUS) { - struct fuse_out_header outhdr; - outhdr.len = sizeof(outhdr); - outhdr.error = result; - outhdr.unique = hdr->unique; - TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr))); - } + if (result != NO_STATUS) { + struct fuse_out_header outhdr; + outhdr.len = sizeof(outhdr); + outhdr.error = result; + outhdr.unique = hdr->unique; + TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr))); } + } - done: - fd.vtab->close(fd.cookie); +done: + fd.vtab->close(fd.cookie); - result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); - if (result < 0) { - printf("fuse_sideload umount failed: %s\n", strerror(errno)); - } + result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); + if (result < 0) { + printf("fuse_sideload umount failed: %s\n", strerror(errno)); + } - if (fd.ffd) close(fd.ffd); - free(fd.hashes); - free(fd.block_data); - free(fd.extra_block); + if (fd.ffd) close(fd.ffd); + free(fd.hashes); + free(fd.block_data); + free(fd.extra_block); - return result; + return result; } extern "C" int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, diff --git a/gui/pages.cpp b/gui/pages.cpp index 1199a2876..a3a1df325 100644 --- a/gui/pages.cpp +++ b/gui/pages.cpp @@ -1370,13 +1370,19 @@ int PageManager::LoadPackage(std::string name, std::string package, std::string tw_h_offset = 0; if (!TWFunc::Path_Exists(package)) return -1; +#ifdef USE_MINZIP if (sysMapFile(package.c_str(), &map) != 0) { +#else + if (!map.MapFile(package)) { +#endif LOGERR("Failed to map '%s'\n", package.c_str()); goto error; } if (!zip.Open(package.c_str(), &map)) { LOGERR("Unable to open zip archive '%s'\n", package.c_str()); +#ifdef USE_MINZIP sysReleaseMap(&map); +#endif goto error; } ctx.zip = &zip; @@ -1418,7 +1424,9 @@ int PageManager::LoadPackage(std::string name, std::string package, std::string if (ctx.zip) { ctx.zip->Close(); +#ifdef USE_MINZIP sysReleaseMap(&map); +#endif } return ret; @@ -1426,7 +1434,9 @@ error: // Sometimes we get here without a real error if (ctx.zip) { ctx.zip->Close(); +#ifdef USE_MINZIP sysReleaseMap(&map); +#endif } return -1; } diff --git a/install.cpp b/install.cpp index ffeba2e18..7fbf5c01f 100644 --- a/install.cpp +++ b/install.cpp @@ -27,6 +27,7 @@ #include <unistd.h> #include <algorithm> +#include <atomic> #include <chrono> #include <condition_variable> #include <functional> @@ -49,97 +50,79 @@ #include "common.h" #include "error_code.h" -#include "minui/minui.h" #include "otautil/SysUtil.h" #include "otautil/ThermalUtil.h" +#include "private/install.h" #include "roots.h" #include "ui.h" #include "verifier.h" using namespace std::chrono_literals; -#define PUBLIC_KEYS_FILE "/res/keys" -static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata"; -static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status"; - // Default allocation of progress bar segments to operations static constexpr int VERIFICATION_PROGRESS_TIME = 60; static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25; -static constexpr float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; -static constexpr float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; static std::condition_variable finish_log_temperature; // This function parses and returns the build.version.incremental -static int parse_build_number(const std::string& str) { +static std::string parse_build_number(const std::string& str) { size_t pos = str.find('='); if (pos != std::string::npos) { - std::string num_string = android::base::Trim(str.substr(pos+1)); - int build_number; - if (android::base::ParseInt(num_string.c_str(), &build_number, 0)) { - return build_number; - } + return android::base::Trim(str.substr(pos+1)); } LOG(ERROR) << "Failed to parse build number in " << str; - return -1; + return ""; } -bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data) { - ZipString metadata_path(METADATA_PATH); - ZipEntry meta_entry; - if (meta_data == nullptr) { - LOG(ERROR) << "string* meta_data can't be nullptr"; - return false; - } - if (FindEntry(zip, metadata_path, &meta_entry) != 0) { - LOG(ERROR) << "Failed to find " << METADATA_PATH << " in update package"; - return false; - } +bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) { + CHECK(metadata != nullptr); - meta_data->resize(meta_entry.uncompressed_length, '\0'); - if (ExtractToMemory(zip, &meta_entry, reinterpret_cast<uint8_t*>(&(*meta_data)[0]), - meta_entry.uncompressed_length) != 0) { - LOG(ERROR) << "Failed to read metadata in update package"; - return false; - } - return true; + static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata"; + ZipString path(METADATA_PATH); + ZipEntry entry; + if (FindEntry(zip, 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<uint8_t*>(&(*metadata)[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<std::string>& log_buffer) { - std::string meta_data; - if (!read_metadata_from_package(zip, &meta_data)) { - return; - } - // Examples of the pre-build and post-build strings in metadata: - // pre-build-incremental=2943039 - // post-build-incremental=2951741 - std::vector<std::string> lines = android::base::Split(meta_data, "\n"); - for (const std::string& line : lines) { - std::string str = android::base::Trim(line); - if (android::base::StartsWith(str, "pre-build-incremental")){ - int source_build = parse_build_number(str); - if (source_build != -1) { - log_buffer.push_back(android::base::StringPrintf("source_build: %d", - source_build)); - } - } else if (android::base::StartsWith(str, "post-build-incremental")) { - int target_build = parse_build_number(str); - if (target_build != -1) { - log_buffer.push_back(android::base::StringPrintf("target_build: %d", - target_build)); - } - } +static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>* 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<std::string> 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); + } } + } } -// Extract the update binary from the open zip archive |zip| located at |path| and store into |cmd| -// the command line that should be called. The |status_fd| is the file descriptor the child process -// should use to report back the progress of the update. -int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, - int status_fd, std::vector<std::string>* cmd); - #ifdef AB_OTA_UPDATER // Parses the metadata of the OTA package in |zip| and checks whether we are @@ -220,8 +203,9 @@ static int check_newer_ab_build(ZipArchiveHandle zip) { return 0; } -int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, - int status_fd, std::vector<std::string>* cmd) { +int update_binary_command(const std::string& package, ZipArchiveHandle zip, + const std::string& binary_path, int /* retry_count */, int status_fd, + std::vector<std::string>* cmd) { CHECK(cmd != nullptr); int ret = check_newer_ab_build(zip); if (ret != 0) { @@ -255,8 +239,8 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret } long payload_offset = payload_entry.offset; *cmd = { - "/sbin/update_engine_sideload", - "--payload=file://" + path, + binary_path, + "--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), @@ -266,8 +250,9 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret #else // !AB_OTA_UPDATER -int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, - int status_fd, std::vector<std::string>* cmd) { +int update_binary_command(const std::string& package, ZipArchiveHandle zip, + const std::string& binary_path, int retry_count, int status_fd, + std::vector<std::string>* cmd) { CHECK(cmd != nullptr); // On traditional updates we extract the update binary from the package. @@ -279,11 +264,10 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret return INSTALL_CORRUPT; } - const char* binary = "/tmp/update_binary"; - unlink(binary); - int fd = creat(binary, 0755); + unlink(binary_path.c_str()); + int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755); if (fd == -1) { - PLOG(ERROR) << "Failed to create " << binary; + PLOG(ERROR) << "Failed to create " << binary_path; return INSTALL_ERROR; } @@ -295,10 +279,10 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret } *cmd = { - binary, + binary_path, EXPAND(RECOVERY_API_VERSION), // defined in Android.mk std::to_string(status_fd), - path, + package, }; if (retry_count > 0) { cmd->push_back("retry"); @@ -307,18 +291,19 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret } #endif // !AB_OTA_UPDATER -static void log_max_temperature(int* max_temperature) { +static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) { CHECK(max_temperature != nullptr); std::mutex mtx; std::unique_lock<std::mutex> lck(mtx); - while (finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) { + while (!logger_finished.load() && + finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) { *max_temperature = std::max(*max_temperature, GetMaxValueFromThermalZone()); } } // If the package contains an update binary, extract it and run it. -static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache, - std::vector<std::string>& log_buffer, int retry_count, +static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache, + std::vector<std::string>* log_buffer, int retry_count, int* max_temperature) { read_source_target_build(zip, log_buffer); @@ -326,7 +311,13 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_ pipe(pipefd); std::vector<std::string> args; - int ret = update_binary_command(path, zip, retry_count, pipefd[1], &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]); @@ -410,7 +401,8 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_ } close(pipefd[1]); - std::thread temperature_logger(log_max_temperature, max_temperature); + std::atomic<bool> logger_finished(false); + std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished)); *wipe_cache = false; bool retry_update = false; @@ -461,7 +453,7 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_ } else if (command == "log") { if (!args.empty()) { // Save the logging request from updater and write to last_install later. - log_buffer.push_back(args); + log_buffer->push_back(args); } else { LOG(ERROR) << "invalid \"log\" parameters: " << line; } @@ -474,6 +466,7 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_ int status; waitpid(pid, &status, 0); + logger_finished.store(true); finish_log_temperature.notify_one(); temperature_logger.join(); @@ -481,7 +474,7 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_ return INSTALL_RETRY; } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")"; + LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")"; return INSTALL_ERROR; } @@ -556,150 +549,149 @@ bool verify_package_compatibility(ZipArchiveHandle package_zip) { return false; } -static int -really_install_package(const char *path, bool* wipe_cache, bool needs_mount, - std::vector<std::string>& log_buffer, int retry_count, int* max_temperature) -{ - ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); - ui->Print("Finding update package...\n"); - // Give verification half the progress bar... - ui->SetProgressType(RecoveryUI::DETERMINATE); - ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); - LOG(INFO) << "Update location: " << path; - - // Map the update package into memory. - ui->Print("Opening update package...\n"); - - if (path && needs_mount) { - if (path[0] == '@') { - ensure_path_mounted(path+1); - } else { - ensure_path_mounted(path); - } +static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount, + std::vector<std::string>* log_buffer, int retry_count, + int* max_temperature) { + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + ui->Print("Finding update package...\n"); + // Give verification half the progress bar... + ui->SetProgressType(RecoveryUI::DETERMINATE); + ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); + 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 (sysMapFile(path, &map) != 0) { - LOG(ERROR) << "failed to map file"; - return INSTALL_CORRUPT; - } + MemMapping map; + if (!map.MapFile(path)) { + LOG(ERROR) << "failed to map file"; + return INSTALL_CORRUPT; + } - // Verify package. - if (!verify_package(map.addr, map.length)) { - log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure)); - sysReleaseMap(&map); - return INSTALL_CORRUPT; - } + // Verify package. + if (!verify_package(map.addr, map.length)) { + 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, &zip); - if (err != 0) { - LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err); - log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure)); + // 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)); - sysReleaseMap(&map); - CloseArchive(zip); - return INSTALL_CORRUPT; - } + CloseArchive(zip); + return INSTALL_CORRUPT; + } - // Additionally verify the compatibility of the package. - if (!verify_package_compatibility(zip)) { - log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure)); - sysReleaseMap(&map); - CloseArchive(zip); - return INSTALL_CORRUPT; - } + // 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); - ui->SetEnableReboot(true); - ui->Print("\n"); + // 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); + ui->SetEnableReboot(true); + ui->Print("\n"); - sysReleaseMap(&map); - CloseArchive(zip); - return result; + CloseArchive(zip); + return result; } -int -install_package(const char* path, bool* wipe_cache, const char* install_file, - bool needs_mount, int retry_count) -{ - modified_flash = true; - auto start = std::chrono::system_clock::now(); - - int start_temperature = GetMaxValueFromThermalZone(); - int max_temperature = start_temperature; - - int result; - std::vector<std::string> log_buffer; - if (setup_install_mounts() != 0) { - LOG(ERROR) << "failed to set up expected mounts for install; aborting"; - result = INSTALL_ERROR; +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; + auto start = std::chrono::system_clock::now(); + + int start_temperature = GetMaxValueFromThermalZone(); + int max_temperature = start_temperature; + + int result; + std::vector<std::string> log_buffer; + if (setup_install_mounts() != 0) { + 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); + } + + // Measure the time spent to apply OTA update in seconds. + std::chrono::duration<double> duration = std::chrono::system_clock::now() - start; + int time_total = static_cast<int>(duration.count()); + + bool has_cache = volume_for_path("/cache") != nullptr; + // Skip logging the uncrypt_status on devices without /cache. + if (has_cache) { + static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status"; + if (ensure_path_mounted(UNCRYPT_STATUS) != 0) { + LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS; } else { - result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count, - &max_temperature); - } - - // Measure the time spent to apply OTA update in seconds. - std::chrono::duration<double> duration = std::chrono::system_clock::now() - start; - int time_total = static_cast<int>(duration.count()); - - bool has_cache = volume_for_path("/cache") != nullptr; - // Skip logging the uncrypt_status on devices without /cache. - if (has_cache) { - if (ensure_path_mounted(UNCRYPT_STATUS) != 0) { - LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS; + std::string uncrypt_status; + if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) { + PLOG(WARNING) << "failed to read uncrypt status"; + } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) { + LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status; } else { - std::string uncrypt_status; - if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) { - PLOG(WARNING) << "failed to read uncrypt status"; - } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) { - LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status; - } else { - log_buffer.push_back(android::base::Trim(uncrypt_status)); - } + log_buffer.push_back(android::base::Trim(uncrypt_status)); } } + } - // The first two lines need to be the package name and install result. - std::vector<std::string> log_header = { - path, - result == INSTALL_SUCCESS ? "1" : "0", - "time_total: " + std::to_string(time_total), - "retry: " + std::to_string(retry_count), - }; - - int end_temperature = GetMaxValueFromThermalZone(); - max_temperature = std::max(end_temperature, max_temperature); - if (start_temperature > 0) { - log_buffer.push_back("temperature_start: " + std::to_string(start_temperature)); - } - if (end_temperature > 0) { - log_buffer.push_back("temperature_end: " + std::to_string(end_temperature)); - } - if (max_temperature > 0) { - log_buffer.push_back("temperature_max: " + std::to_string(max_temperature)); - } + // The first two lines need to be the package name and install result. + std::vector<std::string> log_header = { + path, + result == INSTALL_SUCCESS ? "1" : "0", + "time_total: " + std::to_string(time_total), + "retry: " + std::to_string(retry_count), + }; - std::string log_content = android::base::Join(log_header, "\n") + "\n" + - android::base::Join(log_buffer, "\n") + "\n"; - if (!android::base::WriteStringToFile(log_content, install_file)) { - PLOG(ERROR) << "failed to write " << install_file; - } + int end_temperature = GetMaxValueFromThermalZone(); + max_temperature = std::max(end_temperature, max_temperature); + if (start_temperature > 0) { + log_buffer.push_back("temperature_start: " + std::to_string(start_temperature)); + } + if (end_temperature > 0) { + log_buffer.push_back("temperature_end: " + std::to_string(end_temperature)); + } + if (max_temperature > 0) { + log_buffer.push_back("temperature_max: " + std::to_string(max_temperature)); + } + + std::string log_content = + android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n"; + if (!android::base::WriteStringToFile(log_content, install_file)) { + PLOG(ERROR) << "failed to write " << install_file; + } - // Write a copy into last_log. - LOG(INFO) << log_content; + // Write a copy into last_log. + LOG(INFO) << log_content; - return result; + 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<Certificate> loadedKeys; if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) { LOG(ERROR) << "Failed to load keys"; @@ -20,10 +20,12 @@ #include <string> #include <ziparchive/zip_archive.h> -// Install the package specified by root_path. If INSTALL_SUCCESS is -// returned and *wipe_cache is true on exit, caller should wipe the -// cache partition. -int install_package(const char* root_path, bool* wipe_cache, const char* install_file, +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, @@ -32,9 +34,9 @@ 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* meta_data); +bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata); -// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the +// 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); diff --git a/minadbd/Android.mk b/minadbd/Android.mk index 40e0519af..dfeb85ef6 100644 --- a/minadbd/Android.mk +++ b/minadbd/Android.mk @@ -67,6 +67,7 @@ include $(CLEAR_VARS) LOCAL_CLANG := true 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 diff --git a/minadbd/AndroidTest.xml b/minadbd/AndroidTest.xml new file mode 100644 index 000000000..7ea235b7c --- /dev/null +++ b/minadbd/AndroidTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Config for minadbd_test"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="minadbd_test->/data/local/tmp/minadbd_test" /> + </target_preparer> + <option name="test-suite-tag" value="apct" /> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="minadbd_test" /> + </test> +</configuration>
\ No newline at end of file diff --git a/minadbd/fuse_adb_provider_test.cpp b/minadbd/fuse_adb_provider_test.cpp index 0f2e881c7..31be2a64e 100644 --- a/minadbd/fuse_adb_provider_test.cpp +++ b/minadbd/fuse_adb_provider_test.cpp @@ -14,17 +14,17 @@ * limitations under the License. */ -#include "fuse_adb_provider.h" - -#include <gtest/gtest.h> - #include <errno.h> #include <fcntl.h> +#include <signal.h> #include <sys/socket.h> #include <string> +#include <gtest/gtest.h> + #include "adb_io.h" +#include "fuse_adb_provider.h" TEST(fuse_adb_provider, read_block_adb) { adb_data data = {}; @@ -46,9 +46,8 @@ TEST(fuse_adb_provider, read_block_adb) { uint32_t block = 1234U; const char expected_block[] = "00001234"; - ASSERT_EQ(0, read_block_adb(reinterpret_cast<void*>(&data), block, - reinterpret_cast<uint8_t*>(block_data), - sizeof(expected_data) - 1)); + ASSERT_EQ(0, read_block_adb(static_cast<void*>(&data), block, + reinterpret_cast<uint8_t*>(block_data), sizeof(expected_data) - 1)); // Check that read_block_adb requested the right block. char block_req[sizeof(expected_block)] = {}; @@ -80,9 +79,12 @@ TEST(fuse_adb_provider, read_block_adb_fail_write) { ASSERT_EQ(0, close(sockets[1])); + // 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(reinterpret_cast<void*>(&data), 0, - reinterpret_cast<uint8_t*>(buf), 1)); + ASSERT_EQ(-EIO, read_block_adb(static_cast<void*>(&data), 0, reinterpret_cast<uint8_t*>(buf), 1)); close(sockets[0]); } diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 40f2cea7b..6919cdd40 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -69,7 +69,6 @@ static void sideload_host_service(int sfd, const std::string& args) { int result = run_adb_fuse(sfd, file_size, block_size); printf("sideload_host finished\n"); - sleep(1); exit(result == 0 ? 0 : 1); } diff --git a/minui/Android.mk b/minui/Android.mk index 8df5a4914..cb56b739a 100644 --- a/minui/Android.mk +++ b/minui/Android.mk @@ -51,8 +51,11 @@ ifeq ($(TW_NEW_ION_HEAP), true) endif LOCAL_STATIC_LIBRARIES += libpng -LOCAL_WHOLE_STATIC_LIBRARIES += \ - libdrm +ifneq ($(wildcard external/libdrm/Android.common.mk),) +LOCAL_WHOLE_STATIC_LIBRARIES += libdrm_platform +else +LOCAL_WHOLE_STATIC_LIBRARIES += libdrm +endif ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0) LOCAL_CFLAGS += -DHAS_LIBSYNC LOCAL_WHOLE_STATIC_LIBRARIES += libsync_recovery diff --git a/minui/events.cpp b/minui/events.cpp index 470a17a69..e9383ca8a 100644 --- a/minui/events.cpp +++ b/minui/events.cpp @@ -57,40 +57,47 @@ static bool test_bit(size_t bit, unsigned long* array) { // NOLINT return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0; } +#ifdef TW_USE_MINUI_WITH_OPTIONAL_TOUCH_EVENTS +int ev_init(ev_callback input_cb, bool allow_touch_inputs) { +#else #ifdef TW_USE_MINUI_WITH_DATA int ev_init(ev_callback input_cb, void* data) { #else int ev_init(ev_callback input_cb) { #endif - bool epollctlfail = false; + bool allow_touch_inputs = false; +#endif g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS); if (g_epoll_fd == -1) { return -1; } + bool epollctlfail = false; DIR* dir = opendir("/dev/input"); - if (dir != NULL) { + if (dir != nullptr) { dirent* de; while ((de = readdir(dir))) { - // Use unsigned long to match ioctl's parameter type. - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT - - // fprintf(stderr,"/dev/input/%s\n", de->d_name); if (strncmp(de->d_name, "event", 5)) continue; int fd = openat(dirfd(dir), de->d_name, O_RDONLY); if (fd == -1) continue; + // 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; } - // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. + // 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)) { - close(fd); - continue; + if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) { + close(fd); + continue; + } } epoll_event ev; @@ -261,3 +268,29 @@ void ev_iterate_available_keys(const std::function<void(int)>& f) { } } } + +#ifdef TW_USE_MINUI_WITH_OPTIONAL_TOUCH_EVENTS +void ev_iterate_touch_inputs(const std::function<void(int)>& action) { + for (size_t i = 0; i < 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) { + continue; + } + if (!test_bit(EV_ABS, ev_bits)) { + continue; + } + + unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)] = {}; // NOLINT + if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_ABS, KEY_MAX), key_bits) == -1) { + continue; + } + + for (int key_code = 0; key_code <= KEY_MAX; ++key_code) { + if (test_bit(key_code, key_bits)) { + action(key_code); + } + } + } +} +#endif diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp index 79f4db8b5..72b563b42 100644 --- a/minui/graphics_adf.cpp +++ b/minui/graphics_adf.cpp @@ -30,7 +30,8 @@ #include "minui/minui.h" -MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), n_surfaces(0), surfaces() {} +MinuiBackendAdf::MinuiBackendAdf() + : intf_fd(-1), dev(), current_surface(0), n_surfaces(0), surfaces() {} int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) { *surf = {}; diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h index 766b943ff..bd8c3865d 100644 --- a/minui/include/minui/minui.h +++ b/minui/include/minui/minui.h @@ -91,7 +91,12 @@ int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data); using ev_callback = std::function<int(int fd, uint32_t epevents)>; using ev_set_key_callback = std::function<int(int code, int value)>; +#ifdef TW_USE_MINUI_WITH_OPTIONAL_TOUCH_EVENTS +int ev_init(ev_callback input_cb, bool allow_touch_inputs = false); +void ev_iterate_touch_inputs(const std::function<void(int)>& action); +#else int ev_init(ev_callback input_cb); +#endif int ev_add_fd(int fd, ev_callback cb); int ev_sync_key_state(const ev_set_key_callback& set_key_cb); #endif diff --git a/minui/resources.cpp b/minui/resources.cpp index 026e1dc2b..b9797b905 100644 --- a/minui/resources.cpp +++ b/minui/resources.cpp @@ -56,7 +56,7 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name); resPath[sizeof(resPath)-1] = '\0'; - FILE* fp = fopen(resPath, "rb"); + FILE* fp = fopen(resPath, "rbe"); if (fp == NULL) { result = -1; goto exit; diff --git a/mounts.cpp b/mounts.cpp index f23376b06..76fa65739 100644 --- a/mounts.cpp +++ b/mounts.cpp @@ -27,6 +27,8 @@ #include <string> #include <vector> +#include <android-base/logging.h> + struct MountedVolume { std::string device; std::string mount_point; @@ -60,13 +62,6 @@ bool scan_mounted_volumes() { return true; } -MountedVolume* find_mounted_volume_by_device(const char* device) { - for (size_t i = 0; i < g_mounts_state.size(); ++i) { - if (g_mounts_state[i]->device == device) return g_mounts_state[i]; - } - return nullptr; -} - MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) { for (size_t i = 0; i < g_mounts_state.size(); ++i) { if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i]; @@ -75,15 +70,13 @@ MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) { } int unmount_mounted_volume(MountedVolume* volume) { - // Intentionally pass the empty string to umount if the caller tries - // to unmount a volume they already unmounted using this - // function. - std::string mount_point = volume->mount_point; - volume->mount_point.clear(); - return umount(mount_point.c_str()); -} - -int remount_read_only(MountedVolume* volume) { - return mount(volume->device.c_str(), volume->mount_point.c_str(), volume->filesystem.c_str(), - MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0); + // Intentionally pass the empty string to umount if the caller tries to unmount a volume they + // already unmounted using this function. + std::string mount_point = volume->mount_point; + volume->mount_point.clear(); + int result = umount(mount_point.c_str()); + if (result == -1) { + PLOG(WARNING) << "Failed to umount " << mount_point; + } + return result; } @@ -21,12 +21,8 @@ struct MountedVolume; bool scan_mounted_volumes(); -MountedVolume* find_mounted_volume_by_device(const char* device); - MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point); int unmount_mounted_volume(MountedVolume* volume); -int remount_read_only(MountedVolume* volume); - #endif diff --git a/otafault/Android.mk b/otafault/Android.mk index 1898e179f..7d65b55f6 100644 --- a/otafault/Android.mk +++ b/otafault/Android.mk @@ -29,7 +29,12 @@ otafault_static_libs := \ libbase \ liblog -LOCAL_CFLAGS := -Werror +LOCAL_CFLAGS := \ + -Werror \ + -Wthread-safety \ + -Wthread-safety-negative \ + -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS + LOCAL_SRC_FILES := config.cpp ota_io.cpp LOCAL_MODULE_TAGS := eng LOCAL_MODULE := libotafault diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp index 3a89bb5dd..faae5275d 100644 --- a/otafault/ota_io.cpp +++ b/otafault/ota_io.cpp @@ -24,10 +24,13 @@ #include <map> #include <memory> +#include <mutex> +#include <android-base/thread_annotations.h> #include "config.h" -static std::map<intptr_t, const char*> filename_cache; +static std::mutex filename_mutex; +static std::map<intptr_t, const char*> 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 = ""; @@ -55,23 +58,28 @@ 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<std::mutex> 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<std::mutex> lock(filename_mutex); filename_cache[fd] = path; - return fd; } + return fd; +} FILE* ota_fopen(const char* path, const char* mode) { FILE* fh = fopen(path, mode); + std::lock_guard<std::mutex> 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<std::mutex> lock(filename_mutex); filename_cache.erase(fd); return close(fd); } @@ -85,6 +93,7 @@ int ota_close(unique_fd& fd) { } static int __ota_fclose(FILE* fh) { + std::lock_guard<std::mutex> lock(filename_mutex); filename_cache.erase(reinterpret_cast<intptr_t>(fh)); return fclose(fh); } @@ -99,6 +108,7 @@ int ota_fclose(unique_file& fh) { size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) { if (should_fault_inject(OTAIO_READ)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find((intptr_t)stream); const char* cached_path = cached->second; if (cached != filename_cache.end() && @@ -119,6 +129,7 @@ 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) { if (should_fault_inject(OTAIO_READ)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find(fd); const char* cached_path = cached->second; if (cached != filename_cache.end() @@ -138,6 +149,7 @@ 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) { if (should_fault_inject(OTAIO_WRITE)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find((intptr_t)stream); const char* cached_path = cached->second; if (cached != filename_cache.end() && @@ -157,6 +169,7 @@ 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) { if (should_fault_inject(OTAIO_WRITE)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find(fd); const char* cached_path = cached->second; if (cached != filename_cache.end() && @@ -176,6 +189,7 @@ ssize_t ota_write(int fd, const void* buf, size_t nbyte) { int ota_fsync(int fd) { if (should_fault_inject(OTAIO_FSYNC)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find(fd); const char* cached_path = cached->second; if (cached != filename_cache.end() && diff --git a/otautil/Android.bp b/otautil/Android.bp new file mode 100644 index 000000000..a2eaa0402 --- /dev/null +++ b/otautil/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_library_static { + name: "libotautil", + + srcs: [ + "SysUtil.cpp", + "DirUtil.cpp", + "ThermalUtil.cpp", + ], + + static_libs: [ + "libselinux", + "libbase", + ], + + cflags: [ + "-Werror", + "-Wall", + ], +} diff --git a/otautil/SysUtil.cpp b/otautil/SysUtil.cpp index a2133b953..dfa215073 100644 --- a/otautil/SysUtil.cpp +++ b/otautil/SysUtil.cpp @@ -16,14 +16,12 @@ #include "SysUtil.h" -#include <errno.h> #include <fcntl.h> -#include <stdint.h> +#include <stdint.h> // SIZE_MAX #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> -#include <algorithm> #include <string> #include <vector> @@ -32,9 +30,7 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> -static bool sysMapFD(int fd, MemMapping* pMap) { - CHECK(pMap != nullptr); - +bool MemMapping::MapFD(int fd) { struct stat sb; if (fstat(fd, &sb) == -1) { PLOG(ERROR) << "fstat(" << fd << ") failed"; @@ -47,50 +43,49 @@ static bool sysMapFD(int fd, MemMapping* pMap) { return false; } - pMap->addr = static_cast<unsigned char*>(memPtr); - pMap->length = sb.st_size; - pMap->ranges.push_back({ memPtr, static_cast<size_t>(sb.st_size) }); + addr = static_cast<unsigned char*>(memPtr); + length = sb.st_size; + ranges_.clear(); + ranges_.emplace_back(MappedRange{ memPtr, static_cast<size_t>(sb.st_size) }); return true; } // A "block map" which looks like this (from uncrypt/uncrypt.cpp): // -// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device -// 49652 4096 # file size in bytes, block size -// 3 # count of block ranges -// 1000 1008 # block range 0 -// 2100 2102 # ... block range 1 -// 30 33 # ... block range 2 +// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device +// 49652 4096 # file size in bytes, block size +// 3 # count of block ranges +// 1000 1008 # block range 0 +// 2100 2102 # ... block range 1 +// 30 33 # ... block range 2 // -// Each block range represents a half-open interval; the line "30 33" -// reprents the blocks [30, 31, 32]. -static int sysMapBlockFile(const char* filename, MemMapping* pMap) { - CHECK(pMap != nullptr); - +// 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 -1; + return false; } std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n"); if (lines.size() < 4) { LOG(ERROR) << "Block map file is too short: " << lines.size(); - return -1; + return false; } size_t size; - unsigned int blksize; - if (sscanf(lines[1].c_str(), "%zu %u", &size, &blksize) != 2) { + 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 -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 -1; + return false; } size_t blocks; @@ -101,14 +96,14 @@ static int sysMapBlockFile(const char* filename, MemMapping* pMap) { lines.size() != 3 + range_count) { LOG(ERROR) << "Invalid data in block map file: size " << size << ", blksize " << blksize << ", range_count " << range_count << ", lines " << lines.size(); - return -1; + return false; } // Reserve enough contiguous address space for the whole file. void* reserve = mmap64(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); if (reserve == MAP_FAILED) { PLOG(ERROR) << "failed to reserve address space"; - return -1; + return false; } const std::string& block_dev = lines[0]; @@ -116,10 +111,10 @@ static int sysMapBlockFile(const char* filename, MemMapping* pMap) { if (fd == -1) { PLOG(ERROR) << "failed to open block device " << block_dev; munmap(reserve, blocks * blksize); - return -1; + return false; } - pMap->ranges.resize(range_count); + ranges_.clear(); unsigned char* next = static_cast<unsigned char*>(reserve); size_t remaining_size = blocks * blksize; @@ -129,84 +124,79 @@ static int sysMapBlockFile(const char* filename, MemMapping* pMap) { size_t start, end; if (sscanf(line.c_str(), "%zu %zu\n", &start, &end) != 2) { - LOG(ERROR) << "failed to parse range " << i << " in block map: " << line; + LOG(ERROR) << "failed to parse range " << i << ": " << line; success = false; break; } - size_t length = (end - start) * blksize; - if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) { - LOG(ERROR) << "unexpected range in block map: " << start << " " << end; + 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* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, - static_cast<off64_t>(start) * blksize); - if (addr == MAP_FAILED) { - PLOG(ERROR) << "failed to map block " << i; + void* range_start = mmap64(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, + static_cast<off64_t>(start) * blksize); + if (range_start == MAP_FAILED) { + PLOG(ERROR) << "failed to map range " << i << ": " << line; success = false; break; } - pMap->ranges[i].addr = addr; - pMap->ranges[i].length = length; + ranges_.emplace_back(MappedRange{ range_start, range_size }); - next += length; - remaining_size -= length; + next += range_size; + remaining_size -= range_size; } if (success && remaining_size != 0) { - LOG(ERROR) << "ranges in block map are invalid: remaining_size = " << remaining_size; + LOG(ERROR) << "Invalid ranges: remaining_size " << remaining_size; success = false; } if (!success) { munmap(reserve, blocks * blksize); - return -1; + return false; } - pMap->addr = static_cast<unsigned char*>(reserve); - pMap->length = size; + addr = static_cast<unsigned char*>(reserve); + length = size; LOG(INFO) << "mmapped " << range_count << " ranges"; - return 0; + return true; } -int sysMapFile(const char* fn, MemMapping* pMap) { - if (fn == nullptr || pMap == nullptr) { - LOG(ERROR) << "Invalid argument(s)"; - return -1; +bool MemMapping::MapFile(const std::string& fn) { + if (fn.empty()) { + LOG(ERROR) << "Empty filename"; + return false; } - *pMap = {}; - if (fn[0] == '@') { - if (sysMapBlockFile(fn + 1, pMap) != 0) { + // Block map file "@/cache/recovery/block.map". + if (!MapBlockFile(fn.substr(1))) { LOG(ERROR) << "Map of '" << fn << "' failed"; - return -1; + return false; } } else { // This is a regular file. - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn, O_RDONLY))); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY))); if (fd == -1) { PLOG(ERROR) << "Unable to open '" << fn << "'"; - return -1; + return false; } - if (!sysMapFD(fd, pMap)) { + if (!MapFD(fd)) { LOG(ERROR) << "Map of '" << fn << "' failed"; - return -1; + return false; } } - return 0; + return true; } -/* - * Release a memory mapping. - */ -void sysReleaseMap(MemMapping* pMap) { - std::for_each(pMap->ranges.cbegin(), pMap->ranges.cend(), [](const MappedRange& range) { +MemMapping::~MemMapping() { + for (const auto& range : ranges_) { if (munmap(range.addr, range.length) == -1) { - PLOG(ERROR) << "munmap(" << range.addr << ", " << range.length << ") failed"; + PLOG(ERROR) << "Failed to munmap(" << range.addr << ", " << range.length << ")"; } - }); - pMap->ranges.clear(); + }; + ranges_.clear(); } diff --git a/otautil/SysUtil.h b/otautil/SysUtil.h index 6a79bf31f..52f6d20a7 100644 --- a/otautil/SysUtil.h +++ b/otautil/SysUtil.h @@ -19,37 +19,35 @@ #include <sys/types.h> +#include <string> #include <vector> -struct MappedRange { - void* addr; - size_t length; -}; - /* * Use this to keep track of mapped segments. */ -struct MemMapping { - unsigned char* addr; /* start of data */ - size_t length; /* length of data */ - - std::vector<MappedRange> ranges; +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<MappedRange> ranges_; }; -/* - * Map a file into a private, read-only memory segment. If 'fn' - * begins with an '@' character, it is a map of blocks to be mapped, - * otherwise it is treated as an ordinary file. - * - * On success, "pMap" is filled in, and zero is returned. - */ -int sysMapFile(const char* fn, MemMapping* pMap); - -/* - * Release the pages associated with a shared memory segment. - * - * This does not free "pMap"; it just releases the memory. - */ -void sysReleaseMap(MemMapping* pMap); - #endif // _OTAUTIL_SYSUTIL diff --git a/private/install.h b/private/install.h index 12d303b01..ef64bd41d 100644 --- a/private/install.h +++ b/private/install.h @@ -23,5 +23,9 @@ #include <ziparchive/zip_archive.h> -int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, - int status_fd, std::vector<std::string>* cmd); +// 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<std::string>* cmd); diff --git a/recovery-persist.cpp b/recovery-persist.cpp index d706ccac8..dbce7ff74 100644 --- a/recovery-persist.cpp +++ b/recovery-persist.cpp @@ -59,21 +59,21 @@ static void check_and_fclose(FILE *fp, const char *name) { } static void copy_file(const char* source, const char* destination) { - FILE* dest_fp = fopen(destination, "w"); - if (dest_fp == nullptr) { - PLOG(ERROR) << "Can't open " << destination; - } else { - FILE* source_fp = fopen(source, "r"); - if (source_fp != nullptr) { - char buf[4096]; - size_t bytes; - while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { - fwrite(buf, 1, bytes, dest_fp); - } - check_and_fclose(source_fp, source); - } - check_and_fclose(dest_fp, destination); + FILE* dest_fp = fopen(destination, "we"); + if (dest_fp == nullptr) { + PLOG(ERROR) << "Can't open " << destination; + } else { + FILE* source_fp = fopen(source, "re"); + if (source_fp != nullptr) { + char buf[4096]; + size_t bytes; + while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { + fwrite(buf, 1, bytes, dest_fp); + } + check_and_fclose(source_fp, source); } + check_and_fclose(dest_fp, destination); + } } static bool rotated = false; @@ -120,7 +120,7 @@ int main(int argc, char **argv) { */ bool has_cache = false; static const char mounts_file[] = "/proc/mounts"; - FILE *fp = fopen(mounts_file, "r"); + FILE* fp = fopen(mounts_file, "re"); if (!fp) { PLOG(ERROR) << "failed to open " << mounts_file; } else { diff --git a/recovery.cpp b/recovery.cpp index c1a31b6a8..07bd7b9d4 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -40,7 +40,6 @@ #include <string> #include <vector> -#include <adb.h> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/parseint.h> @@ -114,8 +113,9 @@ 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. -static const int EIO_RETRY_COUNT = 4; +// 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. @@ -250,7 +250,7 @@ static void redirect_stdio(const char* filename) { auto start = std::chrono::steady_clock::now(); // Child logger to actually write to the log file. - FILE* log_fp = fopen(filename, "a"); + FILE* log_fp = fopen(filename, "ae"); if (log_fp == nullptr) { PLOG(ERROR) << "fopen \"" << filename << "\" failed"; close(pipefd[0]); @@ -419,27 +419,27 @@ static void copy_log_file_to_pmsg(const char* source, const char* destination) { static off_t tmplog_offset = 0; static void copy_log_file(const char* source, const char* destination, bool append) { - FILE* dest_fp = fopen_path(destination, append ? "a" : "w"); - if (dest_fp == nullptr) { - PLOG(ERROR) << "Can't open " << destination; - } else { - FILE* source_fp = fopen(source, "r"); - 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); + 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() { @@ -478,40 +478,38 @@ static void copy_logs() { sync(); } -// clear the recovery command and prepare to boot a (hopefully working) system, +// 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() { - // Save the locale to cache, so if recovery is next started up - // without a --locale argument (eg, directly from the bootloader) - // it will use the last-known locale. - if (!locale.empty() && has_cache) { - LOG(INFO) << "Saving locale \"" << locale << "\""; - - FILE* fp = fopen_path(LOCALE_FILE, "w"); - if (!android::base::WriteStringToFd(locale, fileno(fp))) { - PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE; - } - check_and_fclose(fp, LOCALE_FILE); + // 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) { + LOG(INFO) << "Saving locale \"" << locale << "\""; + if (ensure_path_mounted(LOCALE_FILE) != 0) { + LOG(ERROR) << "Failed to mount " << LOCALE_FILE; + } else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) { + PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE; } + } - copy_logs(); + copy_logs(); - // Reset to normal system boot so recovery won't cycle indefinitely. - std::string err; - if (!clear_bootloader_message(&err)) { - LOG(ERROR) << "Failed to clear BCB message: " << err; - } + // Reset to normal system boot so recovery won't cycle indefinitely. + std::string err; + if (!clear_bootloader_message(&err)) { + LOG(ERROR) << "Failed to clear BCB message: " << err; + } - // Remove the command file, so recovery won't repeat indefinitely. - if (has_cache) { - if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { - LOG(WARNING) << "Can't unlink " << COMMAND_FILE; - } - ensure_path_unmounted(CACHE_ROOT); + // Remove the command file, so recovery won't repeat indefinitely. + if (has_cache) { + if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { + LOG(WARNING) << "Can't unlink " << COMMAND_FILE; } + ensure_path_unmounted(CACHE_ROOT); + } - sync(); // For good measure. + sync(); // For good measure. } struct saved_log_file { @@ -552,7 +550,7 @@ static bool erase_volume(const char* volume) { } std::string data(sb.st_size, '\0'); - FILE* f = fopen(path.c_str(), "rb"); + FILE* f = fopen(path.c_str(), "rbe"); fread(&data[0], 1, data.size(), f); fclose(f); @@ -580,7 +578,7 @@ static bool erase_volume(const char* volume) { ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno)); return true; } - FILE* f = fopen(CONVERT_FBE_FILE, "wb"); + FILE* f = fopen(CONVERT_FBE_FILE, "wbe"); if (!f) { ui->Print("Failed to convert to file encryption %s\n", strerror(errno)); return true; @@ -760,12 +758,13 @@ static bool wipe_data(Device* device) { } static bool prompt_and_wipe_data(Device* device) { + // Use a single string and let ScreenRecoveryUI handles the wrapping. const char* const headers[] = { - "Can't load Android system. Your data may be corrupt.", - "If you continue to get this message, you may need to", - "perform a factory data reset and erase all user data", + "Can't load Android system. Your data may be corrupt. " + "If you continue to get this message, you may need to " + "perform a factory data reset and erase all user data " "stored on this device.", - NULL + nullptr }; const char* const items[] = { "Try again", @@ -1157,7 +1156,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { { bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); if (adb) { - status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); + status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE); } else { status = apply_from_sdcard(device, &should_wipe_cache); } @@ -1528,9 +1527,9 @@ int main(int argc, char **argv) { } if (status != INSTALL_SUCCESS) { ui->Print("Installation aborted.\n"); - // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT + // When I/O error happens, reboot and retry installation RETRY_LIMIT // times before we abandon this OTA update. - if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) { + if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) { copy_logs(); set_retry_bootloader_message(retry_count, args); // Print retry count on screen. @@ -1582,7 +1581,7 @@ int main(int argc, char **argv) { if (!sideload_auto_reboot) { ui->ShowText(true); } - status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); + 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; @@ -1593,25 +1592,32 @@ int main(int argc, char **argv) { ui->Print("Rebooting automatically.\n"); } } else if (!just_exit) { - status = INSTALL_NONE; // No command specified - ui->SetBackground(RecoveryUI::NO_COMMAND); - - // http://b/17489952 - // If this is an eng or userdebug build, automatically turn on the - // text display if no command is specified. - if (is_ro_debuggable()) { - ui->ShowText(true); - } + // 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()) { + ui->ShowText(true); + } + status = INSTALL_NONE; // No command specified + ui->SetBackground(RecoveryUI::NO_COMMAND); } - if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) { - copy_logs(); + if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) { ui->SetBackground(RecoveryUI::ERROR); + if (!ui->IsTextVisible()) { + sleep(5); + } } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; - if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) || - ui->IsTextVisible()) { + // 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; @@ -27,7 +27,9 @@ #include <fcntl.h> #include <android-base/logging.h> -#include <ext4_utils/make_ext4fs.h> +#include <android-base/properties.h> +#include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> #include <ext4_utils/wipe.h> #include <fs_mgr.h> @@ -172,6 +174,23 @@ static int exec_cmd(const char* path, char* const argv[]) { return WEXITSTATUS(status); } +static ssize_t get_file_size(int fd, uint64_t reserve_len) { + struct stat buf; + int ret = fstat(fd, &buf); + if (ret) return 0; + + ssize_t computed_size; + if (S_ISREG(buf.st_mode)) { + computed_size = buf.st_size - reserve_len; + } else if (S_ISBLK(buf.st_mode)) { + computed_size = get_block_device_size(fd) - reserve_len; + } else { + computed_size = 0; + } + + return computed_size; +} + int format_volume(const char* volume, const char* directory) { Volume* v = volume_for_path(volume); if (v == NULL) { @@ -211,35 +230,81 @@ int format_volume(const char* volume, const char* directory) { if (v->length != 0) { length = v->length; } else if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0) { - length = -CRYPT_FOOTER_OFFSET; + android::base::unique_fd fd(open(v->blk_device, O_RDONLY)); + if (fd < 0) { + PLOG(ERROR) << "get_file_size: failed to open " << v->blk_device; + return -1; + } + length = get_file_size(fd.get(), CRYPT_FOOTER_OFFSET); + if (length <= 0) { + LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device; + return -1; + } } int result; if (strcmp(v->fs_type, "ext4") == 0) { - if (v->erase_blk_size != 0 && v->logical_blk_size != 0) { - result = make_ext4fs_directory_align(v->blk_device, length, volume, sehandle, - directory, v->erase_blk_size, v->logical_blk_size); - } else { - result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory); - } + static constexpr int block_size = 4096; + int raid_stride = v->logical_blk_size / block_size; + int raid_stripe_width = v->erase_blk_size / block_size; + + // 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 / block_size; + } + + const char* mke2fs_argv[] = { "/sbin/mke2fs_static", + "-F", + "-t", + "ext4", + "-b", + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr }; + + int i = 5; + std::string block_size_str = std::to_string(block_size); + mke2fs_argv[i++] = block_size_str.c_str(); + + std::string ext_args; + if (v->erase_blk_size != 0 && v->logical_blk_size != 0) { + ext_args = android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride, + raid_stripe_width); + mke2fs_argv[i++] = "-E"; + mke2fs_argv[i++] = ext_args.c_str(); + } + + mke2fs_argv[i++] = v->blk_device; + + std::string size_str = std::to_string(length / block_size); + if (length != 0) { + mke2fs_argv[i++] = size_str.c_str(); + } + + result = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv)); + if (result == 0 && directory != nullptr) { + const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", + "-e", + "-f", + directory, + "-a", + volume, + v->blk_device, + nullptr }; + + result = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv)); + } } else { /* Has to be f2fs because we checked earlier. */ - if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) { - LOG(ERROR) << "format_volume: crypt footer + negative length (" << length - << ") not supported on " << v->fs_type; - return -1; - } - if (length < 0) { - LOG(ERROR) << "format_volume: negative length (" << length - << ") not supported on " << v->fs_type; - return -1; - } - char *num_sectors; - if (asprintf(&num_sectors, "%zd", length / 512) <= 0) { + char *num_sectors = nullptr; + if (length >= 512 && asprintf(&num_sectors, "%zd", length / 512) <= 0) { LOG(ERROR) << "format_volume: failed to create " << v->fs_type << " command for " << v->blk_device; return -1; } const char *f2fs_path = "/sbin/mkfs.f2fs"; - const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, NULL}; + const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, nullptr}; result = exec_cmd(f2fs_path, (char* const*)f2fs_argv); free(num_sectors); @@ -260,26 +325,29 @@ int format_volume(const char* volume) { } int setup_install_mounts() { - if (fstab == NULL) { - LOG(ERROR) << "can't set up install mounts: no fstab loaded"; - return -1; - } - for (int i = 0; i < fstab->num_entries; ++i) { - Volume* v = fstab->recs + i; + 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; - 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; - } + // We don't want to do anything with "/". + if (strcmp(v->mount_point, "/") == 0) { + continue; + } - } else { - if (ensure_path_unmounted(v->mount_point) != 0) { - LOG(ERROR) << "failed to unmount " << v->mount_point; - return -1; - } - } + 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; + } + return 0; } diff --git a/screen_ui.cpp b/screen_ui.cpp index bb2772dd8..b8f6ea28b 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -43,17 +43,19 @@ #include "screen_ui.h" #include "ui.h" -#define TEXT_INDENT 4 - // 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; + struct timeval tv; + gettimeofday(&tv, nullptr); + return tv.tv_sec + tv.tv_usec / 1000000.0; } ScreenRecoveryUI::ScreenRecoveryUI() - : currentIcon(NONE), + : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH), + kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT), + kAnimationFps(RECOVERY_UI_ANIMATION_FPS), + density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), + currentIcon(NONE), progressBarType(EMPTY), progressScopeStart(0), progressScopeSize(0), @@ -67,7 +69,7 @@ ScreenRecoveryUI::ScreenRecoveryUI() text_top_(0), show_text(false), show_text_ever(false), - menu_(nullptr), + menu_headers_(nullptr), show_menu(false), menu_items(0), menu_sel(0), @@ -76,102 +78,107 @@ ScreenRecoveryUI::ScreenRecoveryUI() loop_frames(0), current_frame(0), intro_done(false), - animation_fps(30), // TODO: there's currently no way to infer this. stage(-1), max_stage(-1), updateMutex(PTHREAD_MUTEX_INITIALIZER) {} -GRSurface* ScreenRecoveryUI::GetCurrentFrame() { - if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - return intro_done ? loopFrames[current_frame] : introFrames[current_frame]; - } - return error_icon; -} - -GRSurface* ScreenRecoveryUI::GetCurrentText() { - 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(); - } +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 * density_; + return dp * density_; } // Here's the intended layout: // | portrait large landscape large // ---------+------------------------------------------------- -// gap | 220dp 366dp 142dp 284dp +// gap | // icon | (200dp) // gap | 68dp 68dp 56dp 112dp // text | (14sp) // gap | 32dp 32dp 26dp 52dp // progress | (2dp) -// gap | 194dp 340dp 131dp 262dp +// 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. +// 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 { PROGRESS = 0, TEXT = 1, ICON = 2, DIMENSION_MAX }; +enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX }; static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { - { 194, 32, 68, }, // PORTRAIT - { 340, 32, 68, }, // PORTRAIT_LARGE - { 131, 26, 56, }, // LANDSCAPE - { 262, 52, 112, }, // LANDSCAPE_LARGE + { 32, 68, }, // PORTRAIT + { 32, 68, }, // PORTRAIT_LARGE + { 26, 56, }, // LANDSCAPE + { 52, 112, }, // LANDSCAPE_LARGE }; -int ScreenRecoveryUI::GetAnimationBaseline() { - return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - - gr_get_height(loopFrames[0]); +int ScreenRecoveryUI::GetAnimationBaseline() const { + return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]); } -int ScreenRecoveryUI::GetTextBaseline() { - return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - - gr_get_height(installing_text); +int ScreenRecoveryUI::GetTextBaseline() const { + return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - + gr_get_height(installing_text); } -int ScreenRecoveryUI::GetProgressBaseline() { - return gr_fb_height() - PixelsFromDp(kLayouts[layout_][PROGRESS]) - - gr_get_height(progressBarFill); +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 = (gr_fb_height() - elements_sum) / 2; + return gr_fb_height() - 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 = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; - int y = gr_fb_height() - stage_height; - for (int i = 0; i < max_stage; ++i) { - GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty; - gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y); - x += stage_width; - } - } + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_clear(); - GRSurface* text_surface = GetCurrentText(); - int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2; - int text_y = GetTextBaseline(); - gr_color(255, 255, 255, 255); - gr_texticon(text_x, text_y, text_surface); + if (currentIcon != NONE) { + if (max_stage != -1) { + int stage_height = gr_get_height(stageMarkerEmpty); + int stage_width = gr_get_width(stageMarkerEmpty); + int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; + int y = gr_fb_height() - stage_height; + for (int i = 0; i < max_stage; ++i) { + GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty; + gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y); + x += stage_width; + } } + + GRSurface* text_surface = GetCurrentText(); + int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2; + int text_y = GetTextBaseline(); + gr_color(255, 255, 255, 255); + gr_texticon(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. +// 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(); @@ -219,204 +226,236 @@ void ScreenRecoveryUI::draw_foreground_locked() { } } -void ScreenRecoveryUI::SetColor(UIElement e) { - 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::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::DrawHorizontalRule(int* y) { - SetColor(MENU); - *y += 4; - gr_fill(0, *y, gr_fb_width(), *y + 2); - *y += 4; +int ScreenRecoveryUI::DrawHorizontalRule(int y) const { + gr_fill(0, y + 4, gr_fb_width(), 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::DrawTextLine(int x, int* y, const char* line, bool bold) const { - gr_text(gr_sys_font(), x, *y, line, bold); - *y += char_height_ + 4; +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; } -void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) const { - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - DrawTextLine(x, y, lines[i], false); +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 + "Use volume up/down and power.", + NULL }; static const char* LONG_PRESS_HELP[] = { - "Any button cycles highlight.", - "Long-press activates.", - NULL + "Any button cycles highlight.", + "Long-press activates.", + NULL }; -// Redraw everything on the screen. Does not flip pages. -// Should only be called with updateMutex locked. +// 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(); - } else { - gr_color(0, 0, 0, 255); - gr_clear(); - - int y = 0; - if (show_menu) { - std::string recovery_fingerprint = - android::base::GetProperty("ro.bootimage.build.fingerprint", ""); - - SetColor(INFO); - DrawTextLine(TEXT_INDENT, &y, "Android Recovery", true); - for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(TEXT_INDENT, &y, chunk.c_str(), false); - } - DrawTextLines(TEXT_INDENT, &y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); - - SetColor(HEADER); - DrawTextLines(TEXT_INDENT, &y, menu_headers_); - - SetColor(MENU); - DrawHorizontalRule(&y); - 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); - gr_fill(0, y - 2, gr_fb_width(), y + char_height_ + 2); - // Bold white text for the selected item. - SetColor(MENU_SEL_FG); - gr_text(gr_sys_font(), 4, y, menu_[i], true); - SetColor(MENU); - } else { - gr_text(gr_sys_font(), 4, y, menu_[i], false); - } - y += char_height_ + 4; - } - DrawHorizontalRule(&y); - } + if (!show_text) { + draw_background_locked(); + draw_foreground_locked(); + return; + } - // 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_top_ + text_rows_ - 1) % text_rows_; - size_t count = 0; - for (int ty = gr_fb_height() - char_height_; - ty >= y && count < text_rows_; - ty -= char_height_, ++count) { - gr_text(gr_sys_font(), 0, ty, text_[row], false); - --row; - if (row < 0) row = text_rows_ - 1; - } + 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, gr_fb_width(), 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_top_ + text_rows_ - 1) % text_rows_; + size_t count = 0; + for (int ty = gr_fb_height() - 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(); + 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(); + 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<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); - return nullptr; + reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); + return nullptr; } void ScreenRecoveryUI::ProgressThreadLoop() { - double interval = 1.0 / animation_fps; - 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; - } + double interval = 1.0 / kAnimationFps; + while (true) { + double start = now(); + pthread_mutex_lock(&updateMutex); - // 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; - } + 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; + } - if (redraw) update_progress_locked(); + redraw = true; + } - 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<useconds_t>(delay * 1000000)); + // 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<useconds_t>(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 << ")"; - } + 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) { @@ -427,33 +466,33 @@ void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** sur } 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; + 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(); + 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; - } + if (gr_init() < 0) { + return false; + } - gr_font_size(gr_sys_font(), &char_width_, &char_height_); - text_rows_ = gr_fb_height() / char_height_; - text_cols_ = gr_fb_width() / char_width_; - return true; + gr_font_size(gr_sys_font(), &char_width_, &char_height_); + text_rows_ = (gr_fb_height() - kMarginHeight * 2) / char_height_; + text_cols_ = (gr_fb_width() - kMarginWidth * 2) / char_width_; + return true; } bool ScreenRecoveryUI::Init(const std::string& locale) { @@ -462,8 +501,6 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { return false; } - density_ = static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f; - // Are we portrait or landscape? layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; // Are we the large variant of our base layout? @@ -471,7 +508,6 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { text_ = Alloc2d(text_rows_, text_cols_ + 1); file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); - menu_ = Alloc2d(text_rows_, text_cols_ + 1); text_col_ = text_row_ = 0; text_top_ = 1; @@ -500,309 +536,308 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { } void ScreenRecoveryUI::LoadAnimation() { - std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir); - dirent* de; - std::vector<std::string> intro_frame_names; - std::vector<std::string> 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); - } + std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir); + dirent* de; + std::vector<std::string> intro_frame_names; + std::vector<std::string> 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(); + 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(); + // 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()); + 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]); - } + 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]); - } + 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); + pthread_mutex_lock(&updateMutex); - currentIcon = icon; - update_screen_locked(); + currentIcon = icon; + update_screen_locked(); - pthread_mutex_unlock(&updateMutex); + 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); + 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); + 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_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); + } + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetStage(int current, int max) { - pthread_mutex_lock(&updateMutex); - stage = current; - max_stage = max; - pthread_mutex_unlock(&updateMutex); + 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); + std::string str; + android::base::StringAppendV(&str, fmt, ap); - if (copy_to_stdout) { - fputs(str.c_str(), stdout); - } + 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 (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; - } - if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; - } + 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'; - update_screen_locked(); + text_col_ = 0; + text_row_ = (text_row_ + 1) % text_rows_; + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + } + if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; } - pthread_mutex_unlock(&updateMutex); + text_[text_row_][text_col_] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::Print(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, true, ap); - va_end(ap); + 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); + 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_lock(&updateMutex); + if (ch != '\n') text_[text_row_][text_col_++] = ch; + if (ch == '\n' || text_col_ >= text_cols_) { + text_col_ = 0; + ++text_row_; - if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; - } - pthread_mutex_unlock(&updateMutex); + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + } + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ClearText() { - pthread_mutex_lock(&updateMutex); - text_col_ = 0; - text_row_ = 0; - text_top_ = 1; - for (size_t i = 0; i < text_rows_; ++i) { - memset(text_[i], 0, text_cols_ + 1); - } - pthread_mutex_unlock(&updateMutex); + pthread_mutex_lock(&updateMutex); + text_col_ = 0; + text_row_ = 0; + text_top_ = 1; + 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<off_t> 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<int>(100 * (double(ftello(fp)) / double(sb.st_size))), - static_cast<int>(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'); + std::vector<off_t> 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<int>(100 * (double(ftello(fp)) / double(sb.st_size))), + static_cast<int>(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 { - PutChar(ch); - if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { - show_prompt = true; - } + 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; - } + 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_; - size_t old_text_top = text_top_; + char** old_text = text_; + size_t old_text_col = text_col_; + size_t old_text_row = text_row_; + size_t old_text_top = text_top_; - // Swap in the alternate screen and clear it. - text_ = file_viewer_text_; - ClearText(); + // Swap in the alternate screen and clear it. + text_ = file_viewer_text_; + ClearText(); - ShowFile(fp); - fclose(fp); + ShowFile(fp); + fclose(fp); - text_ = old_text; - text_col_ = old_text_col; - text_row_ = old_text_row; - text_top_ = old_text_top; + text_ = old_text; + text_col_ = old_text_col; + text_row_ = old_text_row; + text_top_ = old_text_top; } -void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items, +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; - size_t i = 0; - for (; i < text_rows_ && items[i] != nullptr; ++i) { - strncpy(menu_[i], items[i], text_cols_ - 1); - menu_[i][text_cols_ - 1] = '\0'; - } - menu_items = i; - show_menu = true; - menu_sel = initial_selection; - update_screen_locked(); + 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))); } - pthread_mutex_unlock(&updateMutex); + menu_items = static_cast<int>(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; + 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; + // 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; + 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); + 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; + 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; + 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); + 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); + 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(); + // Redraw so that if we're in the menu, the highlight + // will change color to indicate a successful long press. + Redraw(); } diff --git a/screen_ui.h b/screen_ui.h index a2322c36c..8231a2ba0 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -21,6 +21,7 @@ #include <stdio.h> #include <string> +#include <vector> #include "ui.h" @@ -30,144 +31,166 @@ 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: - ScreenRecoveryUI(); + public: + ScreenRecoveryUI(); - bool Init(const std::string& locale) override; + bool Init(const std::string& locale) override; - // overall recovery state ("background image") - void SetBackground(Icon icon); - void SetSystemUpdateText(bool security_update); + // 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; + // 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; + void SetStage(int current, int max) override; - // text log - void ShowText(bool visible) override; - bool IsTextVisible() override; - bool WasTextEverVisible() override; + // text log + void ShowText(bool visible) override; + bool IsTextVisible() override; + bool WasTextEverVisible() override; - // printing messages - void Print(const char* fmt, ...) __printflike(2, 3); - void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3); - void ShowFile(const char* filename); + // 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); - int SelectMenu(int sel); - void EndMenu(); + // 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); + void KeyLongPress(int) override; - void Redraw(); + void Redraw(); - enum UIElement { - HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO - }; - void SetColor(UIElement e); + enum UIElement { + HEADER, + MENU, + MENU_SEL_BG, + MENU_SEL_BG_ACTIVE, + MENU_SEL_FG, + LOG, + TEXT_FILL, + INFO + }; + void SetColor(UIElement e) const; - protected: - Icon currentIcon; + 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; - // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. - float density_; - // The layout to use. - int layout_; + // Number of frames per sec (default: 30) for both parts of the animation. + const int kAnimationFps; - GRSurface* error_icon; + // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. + const float density_; - GRSurface* erasing_text; - GRSurface* error_text; - GRSurface* installing_text; - GRSurface* no_command_text; + Icon currentIcon; - GRSurface** introFrames; - GRSurface** loopFrames; + // The layout to use. + int layout_; - GRSurface* progressBarEmpty; - GRSurface* progressBarFill; - GRSurface* stageMarkerEmpty; - GRSurface* stageMarkerFill; + GRSurface* error_icon; - ProgressType progressBarType; + GRSurface* erasing_text; + GRSurface* error_text; + GRSurface* installing_text; + GRSurface* no_command_text; - float progressScopeStart, progressScopeSize, progress; - double progressScopeTime, progressScopeDuration; + GRSurface** introFrames; + GRSurface** loopFrames; - // true when both graphics pages are the same (except for the progress bar). - bool pagesIdentical; + GRSurface* progressBarEmpty; + GRSurface* progressBarFill; + GRSurface* stageMarkerEmpty; + GRSurface* stageMarkerFill; - size_t text_cols_, text_rows_; + ProgressType progressBarType; - // Log text overlay, displayed when a magic key is pressed. - char** text_; - size_t text_col_, text_row_, text_top_; + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; - bool show_text; - bool show_text_ever; // has show_text ever been true? + // true when both graphics pages are the same (except for the progress bar). + bool pagesIdentical; - char** menu_; - const char* const* menu_headers_; - bool show_menu; - int menu_items, menu_sel; + size_t text_cols_, text_rows_; - // An alternate text screen, swapped with 'text_' when we're viewing a log file. - char** file_viewer_text_; + // Log text overlay, displayed when a magic key is pressed. + char** text_; + size_t text_col_, text_row_, text_top_; - pthread_t progress_thread_; + bool show_text; + bool show_text_ever; // has show_text ever been true? - // Number of intro frames and loop frames in the animation. - size_t intro_frames; - size_t loop_frames; + std::vector<std::string> menu_; + const char* const* menu_headers_; + bool show_menu; + int menu_items, menu_sel; - size_t current_frame; - bool intro_done; + // An alternate text screen, swapped with 'text_' when we're viewing a log file. + char** file_viewer_text_; - // Number of frames per sec (default: 30) for both parts of the animation. - int animation_fps; + pthread_t progress_thread_; - int stage, max_stage; + // Number of intro frames and loop frames in the animation. + size_t intro_frames; + size_t loop_frames; - int char_width_; - int char_height_; - pthread_mutex_t updateMutex; + size_t current_frame; + bool intro_done; - virtual bool InitTextParams(); + int stage, max_stage; - 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(); + int char_width_; + int char_height_; - GRSurface* GetCurrentFrame(); - GRSurface* GetCurrentText(); + pthread_mutex_t updateMutex; - static void* ProgressThreadStartRoutine(void* data); - void ProgressThreadLoop(); + virtual bool InitTextParams(); - virtual void ShowFile(FILE*); - virtual void PrintV(const char*, bool, va_list); - void PutChar(char); - void ClearText(); + 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(); - void LoadAnimation(); - void LoadBitmap(const char* filename, GRSurface** surface); - void LoadLocalizedBitmap(const char* filename, GRSurface** surface); + GRSurface* GetCurrentFrame() const; + GRSurface* GetCurrentText() const; - int PixelsFromDp(int dp) const; - virtual int GetAnimationBaseline(); - virtual int GetProgressBaseline(); - virtual int GetTextBaseline(); + static void* ProgressThreadStartRoutine(void* data); + void ProgressThreadLoop(); - void DrawHorizontalRule(int* y); - void DrawTextLine(int x, int* y, const char* line, bool bold) const; - void DrawTextLines(int x, int* y, const char* const* lines) const; + 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; + + // 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 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; }; #endif // RECOVERY_UI_H diff --git a/tests/Android.mk b/tests/Android.mk index e4f252127..ff8f3a38e 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -26,11 +26,12 @@ endif include $(CLEAR_VARS) LOCAL_CFLAGS := -Werror LOCAL_MODULE := recovery_unit_test -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_STATIC_LIBRARIES := \ libverifier \ libminui \ libotautil \ + libupdater \ libziparchive \ libutils \ libz \ @@ -41,9 +42,9 @@ 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 \ - unit/ziputil_test.cpp LOCAL_C_INCLUDES := $(RECOVERY_PATH) LOCAL_SHARED_LIBRARIES := liblog @@ -51,10 +52,8 @@ include $(BUILD_NATIVE_TEST) # Manual tests include $(CLEAR_VARS) -LOCAL_CLANG := true LOCAL_CFLAGS := -Werror LOCAL_MODULE := recovery_manual_test -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_STATIC_LIBRARIES := \ libminui \ libbase @@ -91,13 +90,20 @@ LOCAL_CFLAGS := \ -Werror \ -D_FILE_OFFSET_BITS=64 -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk - ifeq ($(AB_OTA_UPDATER),true) LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 endif +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 := $(RECOVERY_PATH) LOCAL_SRC_FILES := \ component/applypatch_test.cpp \ @@ -108,9 +114,11 @@ LOCAL_SRC_FILES := \ component/sideload_test.cpp \ component/uncrypt_test.cpp \ component/updater_test.cpp \ + component/update_verifier_test.cpp \ component/verifier_test.cpp -LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_SHARED_LIBRARIES := \ + libhidlbase tune2fs_static_libraries := \ libext2_com_err \ @@ -128,6 +136,7 @@ LOCAL_STATIC_LIBRARIES := \ libimgpatch \ libbsdiff \ libbspatch \ + libfusesideload \ libotafault \ librecovery \ libupdater \ @@ -135,6 +144,7 @@ LOCAL_STATIC_LIBRARIES := \ libverifier \ libotautil \ libmounts \ + libupdate_verifier \ libdivsufsort \ libdivsufsort64 \ libfs_mgr \ @@ -157,6 +167,7 @@ LOCAL_STATIC_LIBRARIES := \ libfec_rs \ libsquashfs_utils \ libcutils \ + libbrotli \ $(tune2fs_static_libraries) testdata_files := $(call find-subdir-files, testdata/*) diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml new file mode 100644 index 000000000..3999aa57d --- /dev/null +++ b/tests/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Config for recovery_component_test and recovery_unit_test"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test" /> + <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test" /> + </target_preparer> + <option name="test-suite-tag" value="apct" /> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="recovery_component_test" /> + </test> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="recovery_unit_test" /> + </test> +</configuration> diff --git a/tests/common/component_test_util.h b/tests/common/component_test_util.h deleted file mode 100644 index 3fee32d62..000000000 --- a/tests/common/component_test_util.h +++ /dev/null @@ -1,43 +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. - */ - -#ifndef _COMPONENT_TEST_UTIL_H -#define _COMPONENT_TEST_UTIL_H - -#include <string> - -#include <android-base/properties.h> -#include <fs_mgr.h> - -// Check if the /misc entry exists in the fstab. -static bool parse_misc() { - std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(), - fs_mgr_free_fstab); - if (!fstab) { - GTEST_LOG_(INFO) << "Failed to read default fstab"; - return false; - } - - fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc"); - if (record == nullptr) { - GTEST_LOG_(INFO) << "Failed to find /misc in fstab."; - return false; - } - return true; -} - -#endif //_COMPONENT_TEST_UTIL_H - diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp index 5cba68f8a..016fed9b1 100644 --- a/tests/component/applypatch_test.cpp +++ b/tests/component/applypatch_test.cpp @@ -105,9 +105,6 @@ class ApplyPatchTest : public ::testing::Test { static size_t new_size; }; -std::string ApplyPatchTest::old_file; -std::string ApplyPatchTest::new_file; - static void cp(const std::string& src, const std::string& tgt) { std::string cmd = "cp " + src + " " + tgt; system(cmd.c_str()); @@ -132,48 +129,8 @@ class ApplyPatchCacheTest : public ApplyPatchTest { } }; -class ApplyPatchFullTest : public ApplyPatchCacheTest { - public: - static void SetUpTestCase() { - ApplyPatchTest::SetUpTestCase(); - - output_f = new TemporaryFile(); - output_loc = std::string(output_f->path); - - struct FileContents fc; - - ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc)); - patches.push_back( - std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end()))); - - ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc)); - patches.push_back( - std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end()))); - } - - static void TearDownTestCase() { - delete output_f; - patches.clear(); - } - - static std::vector<std::unique_ptr<Value>> patches; - static TemporaryFile* output_f; - static std::string output_loc; -}; - -class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest { - public: - virtual void SetUp() { - ApplyPatchCacheTest::SetUp(); - cp(cache_file, "/cache/reallysaved.file"); - } - - virtual void TearDown() { - cp("/cache/reallysaved.file", cache_file); - ApplyPatchCacheTest::TearDown(); - } -}; - +std::string ApplyPatchTest::old_file; +std::string ApplyPatchTest::new_file; std::string ApplyPatchTest::rand_file; std::string ApplyPatchTest::patch_file; std::string ApplyPatchTest::cache_file; @@ -184,10 +141,6 @@ std::string ApplyPatchTest::bad_sha1_b; size_t ApplyPatchTest::old_size; size_t ApplyPatchTest::new_size; -std::vector<std::unique_ptr<Value>> ApplyPatchFullTest::patches; -TemporaryFile* ApplyPatchFullTest::output_f; -std::string ApplyPatchFullTest::output_loc; - TEST_F(ApplyPatchTest, CheckModeSkip) { std::vector<std::string> sha1s; ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); @@ -424,20 +377,6 @@ TEST(ApplyPatchModesTest, CheckModeInvalidArgs) { ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" })); } -TEST(ApplyPatchModesTest, SpaceModeInvalidArgs) { - // Insufficient args. - ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-s" })); - - // Invalid bytes arg. - ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "x" })); - - // 0 is invalid. - ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0" })); - - // 0x10 is fine. - ASSERT_EQ(0, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0x10" })); -} - TEST(ApplyPatchModesTest, ShowLicenses) { ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" })); } diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp index 0357accfe..b38bc7134 100644 --- a/tests/component/bootloader_message_test.cpp +++ b/tests/component/bootloader_message_test.cpp @@ -21,14 +21,13 @@ #include <bootloader_message/bootloader_message.h> #include <gtest/gtest.h> -#include "common/component_test_util.h" - class BootloaderMessageTest : public ::testing::Test { protected: BootloaderMessageTest() : has_misc(true) {} virtual void SetUp() override { - has_misc = parse_misc(); + std::string err; + has_misc = !get_bootloader_message_blk_device(&err).empty(); } virtual void TearDown() override { diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp index 2f648501c..bf25aebb0 100644 --- a/tests/component/imgdiff_test.cpp +++ b/tests/component/imgdiff_test.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <stdio.h> + #include <string> #include <vector> @@ -27,12 +29,6 @@ using android::base::get_unaligned; -static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { - std::string* s = static_cast<std::string*>(token); - s->append(reinterpret_cast<const char*>(data), len); - return len; -} - // Sanity check for the given imgdiff patch header. static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw, size_t* num_deflate) { @@ -79,6 +75,18 @@ static void verify_patch_header(const std::string& patch, size_t* num_normal, si if (num_deflate != nullptr) *num_deflate = deflate; } +static void verify_patched_image(const std::string& src, const std::string& patch, + const std::string& tgt) { + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + [&patched](const unsigned char* data, size_t len) { + patched.append(reinterpret_cast<const char*>(data), len); + return len; + })); + ASSERT_EQ(tgt, patched); +} + TEST(ImgdiffTest, invalid_args) { // Insufficient inputs. ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" })); @@ -124,11 +132,7 @@ TEST(ImgdiffTest, image_mode_smoke) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, zip_mode_smoke_store) { @@ -177,11 +181,7 @@ TEST(ImgdiffTest, zip_mode_smoke_store) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, zip_mode_smoke_compressed) { @@ -230,11 +230,7 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) { ASSERT_EQ(1U, num_deflate); ASSERT_EQ(2U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { @@ -286,11 +282,7 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { ASSERT_EQ(1U, num_deflate); ASSERT_EQ(2U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_simple) { @@ -333,11 +325,40 @@ TEST(ImgdiffTest, image_mode_simple) { ASSERT_EQ(1U, num_deflate); ASSERT_EQ(2U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); +} + +TEST(ImgdiffTest, image_mode_bad_gzip) { + // Modify the uncompressed length in the gzip footer. + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', + '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', + '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', + '\xff', '\xff', '\xff' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // Modify the uncompressed length in the gzip footer. + const std::vector<char> tgt_data = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b', + '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac', + '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\xff', '\xff', '\xff' + }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_different_num_chunks) { @@ -413,11 +434,7 @@ TEST(ImgdiffTest, image_mode_merge_chunks) { ASSERT_EQ(1U, num_deflate); ASSERT_EQ(2U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_spurious_magic) { @@ -454,11 +471,7 @@ TEST(ImgdiffTest, image_mode_spurious_magic) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_short_input1) { @@ -494,11 +507,7 @@ TEST(ImgdiffTest, image_mode_short_input1) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_short_input2) { @@ -534,11 +543,7 @@ TEST(ImgdiffTest, image_mode_short_input2) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_single_entry_long) { @@ -577,9 +582,44 @@ TEST(ImgdiffTest, image_mode_single_entry_long) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(0U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); +} + +TEST(ImgpatchTest, image_mode_patch_corruption) { + // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd). + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', + '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', + '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', + '\x00', '\x00', '\x00' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // tgt: "abcdefgxyz" + gzipped "xxyyzz". + const std::vector<char> tgt_data = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b', + '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac', + '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00' + }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + verify_patched_image(src, patch, tgt); + + // Corrupt the end of the patch and expect the ApplyImagePatch to fail. + patch.insert(patch.end() - 10, 10, '0'); + ASSERT_EQ(-1, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + [](const unsigned char* /*data*/, size_t len) { return len; })); } diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp index a5c0c1025..968196fc0 100644 --- a/tests/component/install_test.cpp +++ b/tests/component/install_test.cpp @@ -15,6 +15,8 @@ */ #include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> #include <unistd.h> #include <string> @@ -65,6 +67,56 @@ TEST(InstallTest, verify_package_compatibility_invalid_entry) { CloseArchive(zip); } +TEST(InstallTest, read_metadata_from_package_smoke) { + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "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.fd, "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.fd, "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.fd, "w"); @@ -175,18 +227,62 @@ TEST(InstallTest, update_binary_command_smoke) { 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 path = "/path/to/update.zip"; + std::string package = "/path/to/update.zip"; + std::string binary_path = "/sbin/update_engine_sideload"; std::vector<std::string> cmd; - ASSERT_EQ(0, update_binary_command(path, zip, 0, status_fd, &cmd)); - ASSERT_EQ("/sbin/update_engine_sideload", cmd[0]); - ASSERT_EQ("--payload=file://" + path, cmd[1]); + ASSERT_EQ(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]); CloseArchive(zip); #else - // Cannot test update_binary_command() because it tries to extract update-binary to /tmp. - GTEST_LOG_(INFO) << "Test skipped on non-A/B device."; + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "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<std::string> 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<mode_t>(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<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + + CloseArchive(zip); #endif // AB_OTA_UPDATER } @@ -217,12 +313,30 @@ TEST(InstallTest, update_binary_command_invalid) { ZipArchiveHandle zip; ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); int status_fd = 10; - std::string path = "/path/to/update.zip"; + std::string package = "/path/to/update.zip"; + std::string binary_path = "/sbin/update_engine_sideload"; std::vector<std::string> cmd; - ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(path, zip, 0, status_fd, &cmd)); + ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd)); CloseArchive(zip); #else - // Cannot test update_binary_command() because it tries to extract update-binary to /tmp. - GTEST_LOG_(INFO) << "Test skipped on non-A/B device."; + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "w"); + ZipWriter writer(zip_file); + // The archive must have something to be opened correctly. + ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0)); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); + + // 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<std::string> cmd; + ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd)); + CloseArchive(zip); #endif // AB_OTA_UPDATER } diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp index ea93e9b84..40cfc6975 100644 --- a/tests/component/sideload_test.cpp +++ b/tests/component/sideload_test.cpp @@ -13,9 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #include <unistd.h> + #include <gtest/gtest.h> -TEST(SideloadTest, fusedevice) { - ASSERT_NE(-1, access("/dev/fuse", R_OK | W_OK)); +#include "fuse_sideload.h" + +TEST(SideloadTest, fuse_device) { + ASSERT_EQ(0, access("/dev/fuse", R_OK | W_OK)); +} + +TEST(SideloadTest, run_fuse_sideload_wrong_parameters) { + provider_vtab vtab; + vtab.close = [](void*) {}; + + ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, 4095)); + ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, (1 << 22) + 1)); + + // Too many blocks. + ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, ((1 << 18) + 1) * 4096, 4096)); } diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp index 4f2b8164f..3925236a5 100644 --- a/tests/component/uncrypt_test.cpp +++ b/tests/component/uncrypt_test.cpp @@ -25,11 +25,12 @@ #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/properties.h> +#include <android-base/test_utils.h> #include <android-base/unique_fd.h> #include <bootloader_message/bootloader_message.h> #include <gtest/gtest.h> -#include "common/component_test_util.h" +using namespace std::string_literals; static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt"; static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb"; @@ -62,131 +63,108 @@ class UncryptTest : public ::testing::Test { ASSERT_TRUE(success) << "uncrypt service is not available."; - has_misc = parse_misc(); + std::string err; + has_misc = !get_bootloader_message_blk_device(&err).empty(); } - bool has_misc; -}; - -TEST_F(UncryptTest, setup_bcb) { - if (!has_misc) { - GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; - return; - } - - // Trigger the setup-bcb service. - ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb")); - - // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected"). - sleep(1); - - struct sockaddr_un un = {}; - un.sun_family = AF_UNIX; - strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path)); - - int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); - ASSERT_NE(-1, sockfd); - - // Connect to the uncrypt socket. - bool success = false; - for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { - if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) { - success = true; - break; + void SetupOrClearBcb(bool isSetup, const std::string& message, + const std::string& message_in_bcb) const { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; } - sleep(1); - } - ASSERT_TRUE(success); - - // Send out the BCB message. - std::string message = "--update_message=abc value"; - std::string message_in_bcb = "recovery\n--update_message=abc value\n"; - int length = static_cast<int>(message.size()); - int length_out = htonl(length); - ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int))) - << "Failed to write length: " << strerror(errno); - ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length)) - << "Failed to write message: " << strerror(errno); - // Check the status code from uncrypt. - int status; - ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int))); - ASSERT_EQ(100U, ntohl(status)); + // Trigger the setup-bcb service. + ASSERT_TRUE(android::base::SetProperty("ctl.start", isSetup ? "setup-bcb" : "clear-bcb")); - // Ack having received the status code. - int code = 0; - ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int))); - - ASSERT_EQ(0, close(sockfd)); + // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected"). + sleep(1); - ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb")); + sockaddr_un un = {}; + un.sun_family = AF_UNIX; + strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path)); - // Verify the message by reading from BCB directly. - bootloader_message boot; - std::string err; - ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; + int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_NE(-1, sockfd); - ASSERT_EQ("boot-recovery", std::string(boot.command)); - ASSERT_EQ(message_in_bcb, std::string(boot.recovery)); + // Connect to the uncrypt socket. + bool success = false; + for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { + if (connect(sockfd, reinterpret_cast<sockaddr*>(&un), sizeof(sockaddr_un)) != 0) { + success = true; + break; + } + sleep(1); + } + ASSERT_TRUE(success); + + if (isSetup) { + // Send out the BCB message. + int length = static_cast<int>(message.size()); + int length_out = htonl(length); + ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int))) + << "Failed to write length: " << strerror(errno); + ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length)) + << "Failed to write message: " << strerror(errno); + } - // The rest of the boot.recovery message should be zero'd out. - ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery)); - size_t left = sizeof(boot.recovery) - message_in_bcb.size(); - ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left)); + // Check the status code from uncrypt. + int status; + ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int))); + ASSERT_EQ(100U, ntohl(status)); - // Clear the BCB. - ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err; -} + // Ack having received the status code. + int code = 0; + ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int))); -TEST_F(UncryptTest, clear_bcb) { - if (!has_misc) { - GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; - return; - } + ASSERT_EQ(0, close(sockfd)); - // Trigger the clear-bcb service. - ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb")); + ASSERT_TRUE(android::base::SetProperty("ctl.stop", isSetup ? "setup-bcb" : "clear-bcb")); - // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected"). - sleep(1); + // Verify the message by reading from BCB directly. + bootloader_message boot; + std::string err; + ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; - struct sockaddr_un un = {}; - un.sun_family = AF_UNIX; - strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path)); + if (isSetup) { + ASSERT_EQ("boot-recovery", std::string(boot.command)); + ASSERT_EQ(message_in_bcb, std::string(boot.recovery)); - int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); - ASSERT_NE(-1, sockfd); + // The rest of the boot.recovery message should be zero'd out. + ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery)); + size_t left = sizeof(boot.recovery) - message_in_bcb.size(); + ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left)); - // Connect to the uncrypt socket. - bool success = false; - for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { - if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) { - success = true; - break; + // Clear the BCB. + ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err; + } else { + // All the bytes should be cleared. + ASSERT_EQ(std::string(sizeof(boot), '\0'), + std::string(reinterpret_cast<const char*>(&boot), sizeof(boot))); } - sleep(1); } - ASSERT_TRUE(success); - // Check the status code from uncrypt. - int status; - ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int))); - ASSERT_EQ(100U, ntohl(status)); - - // Ack having received the status code. - int code = 0; - ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int))); + bool has_misc; +}; - ASSERT_EQ(0, close(sockfd)); +TEST_F(UncryptTest, setup_bcb) { + std::string message = "--update_message=abc value"; + std::string message_in_bcb = "recovery\n--update_message=abc value\n"; + SetupOrClearBcb(true, message, message_in_bcb); +} - ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb")); +TEST_F(UncryptTest, clear_bcb) { + SetupOrClearBcb(false, "", ""); +} - // Verify the content by reading from BCB directly. - bootloader_message boot; - std::string err; - ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; +TEST_F(UncryptTest, setup_bcb_wipe_ab) { + TemporaryFile wipe_package; + ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path)); - // All the bytes should be cleared. - ASSERT_EQ(std::string(sizeof(boot), '\0'), - std::string(reinterpret_cast<const char*>(&boot), sizeof(boot))); + // It's expected to store a wipe package in /misc, with the package size passed to recovery. + std::string message = + "--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s; + std::string message_in_bcb = + "recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n"; + SetupOrClearBcb(true, message, message_in_bcb); } diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp new file mode 100644 index 000000000..b04e1185e --- /dev/null +++ b/tests/component/update_verifier_test.cpp @@ -0,0 +1,96 @@ +/* + * 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 <string> + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <gtest/gtest.h> +#include <update_verifier/update_verifier.h> + +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; + } + + // The care map file can have only two or four lines. + 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 two or four 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 index 5652ddf46..6c341c111 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -15,10 +15,12 @@ */ #include <stdio.h> +#include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <algorithm> #include <memory> #include <string> #include <vector> @@ -29,6 +31,7 @@ #include <android-base/strings.h> #include <android-base/test_utils.h> #include <bootloader_message/bootloader_message.h> +#include <brotli/encode.h> #include <bsdiff.h> #include <gtest/gtest.h> #include <ziparchive/zip_archive.h> @@ -224,102 +227,6 @@ TEST_F(UpdaterTest, file_getprop) { expect("", script6.c_str(), kNoCause); } -TEST_F(UpdaterTest, package_extract_dir) { - // package_extract_dir expects 2 arguments. - expect(nullptr, "package_extract_dir()", kArgsParsingFailure); - expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Need to set up the ziphandle. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - - // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>"). - TemporaryDir td; - std::string temp_dir(td.path); - std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")"); - expect("t", script.c_str(), kNoCause, &updater_info); - - // Verify. - std::string data; - std::string file_c = temp_dir + "/c.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); - ASSERT_EQ(kCTxtContents, data); - - std::string file_d = temp_dir + "/d.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); - ASSERT_EQ(kDTxtContents, data); - - // Modify the contents in order to retry. It's expected to be overwritten. - ASSERT_TRUE(android::base::WriteStringToFile("random", file_c)); - ASSERT_TRUE(android::base::WriteStringToFile("random", file_d)); - - // Extract again and verify. - expect("t", script.c_str(), kNoCause, &updater_info); - - ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); - ASSERT_EQ(kCTxtContents, data); - ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); - ASSERT_EQ(kDTxtContents, data); - - // Clean up the temp files under td. - ASSERT_EQ(0, unlink(file_c.c_str())); - ASSERT_EQ(0, unlink(file_d.c_str())); - - // Extracting "b/" (with slash) should give the same result. - script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")"; - expect("t", script.c_str(), kNoCause, &updater_info); - - ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); - ASSERT_EQ(kCTxtContents, data); - ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); - ASSERT_EQ(kDTxtContents, data); - - ASSERT_EQ(0, unlink(file_c.c_str())); - ASSERT_EQ(0, unlink(file_d.c_str())); - - // Extracting "" is allowed. The entries will carry the path name. - script = "package_extract_dir(\"\", \"" + temp_dir + "\")"; - expect("t", script.c_str(), kNoCause, &updater_info); - - std::string file_a = temp_dir + "/a.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_a, &data)); - ASSERT_EQ(kATxtContents, data); - std::string file_b = temp_dir + "/b.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_b, &data)); - ASSERT_EQ(kBTxtContents, data); - std::string file_b_c = temp_dir + "/b/c.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data)); - ASSERT_EQ(kCTxtContents, data); - std::string file_b_d = temp_dir + "/b/d.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data)); - ASSERT_EQ(kDTxtContents, data); - - ASSERT_EQ(0, unlink(file_a.c_str())); - ASSERT_EQ(0, unlink(file_b.c_str())); - ASSERT_EQ(0, unlink(file_b_c.c_str())); - ASSERT_EQ(0, unlink(file_b_d.c_str())); - ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str())); - - // Extracting non-existent entry should still give "t". - script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")"; - expect("t", script.c_str(), kNoCause, &updater_info); - - // Only relative zip_path is allowed. - script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")"; - expect("", script.c_str(), kNoCause, &updater_info); - - // Only absolute dest_path is allowed. - script = "package_extract_dir(\"b\", \"path\")"; - expect("", script.c_str(), kNoCause, &updater_info); - - CloseArchive(handle); -} - // TODO: Test extracting to block device. TEST_F(UpdaterTest, package_extract_file) { // package_extract_file expects 1 or 2 arguments. @@ -570,7 +477,7 @@ TEST_F(UpdaterTest, block_image_update) { ASSERT_EQ(0, fclose(zip_file_ptr)); MemMapping map; - ASSERT_EQ(0, sysMapFile(zip_file.path, &map)); + ASSERT_TRUE(map.MapFile(zip_file.path)); ZipArchiveHandle handle; ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); @@ -578,7 +485,7 @@ TEST_F(UpdaterTest, block_image_update) { UpdaterInfo updater_info; updater_info.package_zip = handle; TemporaryFile temp_pipe; - updater_info.cmd_pipe = fopen(temp_pipe.path, "wb"); + updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe"); updater_info.package_zip_addr = map.addr; updater_info.package_zip_len = map.length; @@ -607,3 +514,144 @@ TEST_F(UpdaterTest, block_image_update) { ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); CloseArchive(handle); } + +TEST_F(UpdaterTest, new_data_short_write) { + // Create a zip file with new_data. + TemporaryFile zip_file; + FILE* zip_file_ptr = fdopen(zip_file.fd, "wb"); + ZipWriter zip_writer(zip_file_ptr); + + // Add the empty new data. + ASSERT_EQ(0, zip_writer.StartEntry("empty_new_data", 0)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + // Add the short written new data. + ASSERT_EQ(0, zip_writer.StartEntry("short_new_data", 0)); + std::string new_data_short = std::string(10, 'a'); + ASSERT_EQ(0, zip_writer.WriteBytes(new_data_short.data(), new_data_short.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + // Add the data of exactly one block. + ASSERT_EQ(0, zip_writer.StartEntry("exact_new_data", 0)); + std::string new_data_exact = std::string(4096, 'a'); + ASSERT_EQ(0, zip_writer.WriteBytes(new_data_exact.data(), new_data_exact.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + // Add a dummy patch data. + ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + + std::vector<std::string> transfer_list = { + "4", + "1", + "0", + "0", + "new 2,0,1", + }; + ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0)); + std::string commands = android::base::Join(transfer_list, '\n'); + ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + ASSERT_EQ(0, zip_writer.Finish()); + ASSERT_EQ(0, fclose(zip_file_ptr)); + + 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 = fopen(temp_pipe.path, "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); + CloseArchive(handle); +} + +TEST_F(UpdaterTest, brotli_new_data) { + // Create a zip file with new_data. + TemporaryFile zip_file; + FILE* zip_file_ptr = fdopen(zip_file.fd, "wb"); + ZipWriter zip_writer(zip_file_ptr); + + // Add a brotli compressed new data entry. + ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0)); + + 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::vector<uint8_t> encoded_data(encoded_size); + ASSERT_TRUE(BrotliEncoderCompress( + BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(), + reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size, encoded_data.data())); + + ASSERT_EQ(0, zip_writer.WriteBytes(encoded_data.data(), encoded_size)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + // Add a dummy patch data. + ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + + // 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<std::string> 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", + }; + ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0)); + std::string commands = android::base::Join(transfer_list, '\n'); + ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + ASSERT_EQ(0, zip_writer.Finish()); + ASSERT_EQ(0, fclose(zip_file_ptr)); + + 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 = fopen(temp_pipe.path, "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); + CloseArchive(handle); +} diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp index 4c0648714..2ef3828ad 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/component/verifier_test.cpp @@ -40,7 +40,7 @@ class VerifierTest : public testing::TestWithParam<std::vector<std::string>> { void SetUp() override { std::vector<std::string> args = GetParam(); std::string package = from_testdata_base(args[0]); - if (sysMapFile(package.c_str(), &memmap) != 0) { + if (!memmap.MapFile(package)) { FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n"; } @@ -117,6 +117,51 @@ TEST(VerifierTest, load_keys_invalid_keys) { 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<Certificate> 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<const unsigned char*>(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<Certificate> 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<size_t>(100)); + + // Alter the content. + std::string altered1(package); + altered1[50] += 1; + ASSERT_EQ(VERIFY_FAILURE, + verify_file(reinterpret_cast<const unsigned char*>(altered1.data()), altered1.size(), + certs)); + + std::string altered2(package); + altered2[10] += 1; + ASSERT_EQ(VERIFY_FAILURE, + verify_file(reinterpret_cast<const unsigned char*>(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)); @@ -174,6 +219,4 @@ INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest, INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest, ::testing::Values( std::vector<std::string>({"random.zip", "v1"}), - std::vector<std::string>({"fake-eocd.zip", "v1"}), - std::vector<std::string>({"alter-metadata.zip", "v1"}), - std::vector<std::string>({"alter-footer.zip", "v1"}))); + std::vector<std::string>({"fake-eocd.zip", "v1"}))); diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp index d36dd331e..92c6ef2d4 100644 --- a/tests/manual/recovery_test.cpp +++ b/tests/manual/recovery_test.cpp @@ -141,7 +141,7 @@ class ResourceTest : public testing::TestWithParam<std::string> { // under recovery. void SetUp() override { std::string file_path = GetParam(); - fp = fopen(file_path.c_str(), "rb"); + fp = fopen(file_path.c_str(), "rbe"); ASSERT_NE(nullptr, fp); unsigned char header[8]; diff --git a/tests/testdata/alter-footer.zip b/tests/testdata/alter-footer.zip Binary files differdeleted file mode 100644 index f497ec000..000000000 --- a/tests/testdata/alter-footer.zip +++ /dev/null diff --git a/tests/testdata/alter-metadata.zip b/tests/testdata/alter-metadata.zip Binary files differdeleted file mode 100644 index 1c71fbc49..000000000 --- a/tests/testdata/alter-metadata.zip +++ /dev/null diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp new file mode 100644 index 000000000..3c6d77ef5 --- /dev/null +++ b/tests/unit/rangeset_test.cpp @@ -0,0 +1,112 @@ +/* + * 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 <signal.h> +#include <sys/types.h> + +#include <vector> + +#include <gtest/gtest.h> + +#include "updater/rangeset.h" + +TEST(RangeSetTest, Parse_smoke) { + RangeSet rs = RangeSet::Parse("2,1,10"); + ASSERT_EQ(static_cast<size_t>(1), rs.size()); + ASSERT_EQ((Range{ 1, 10 }), rs[0]); + ASSERT_EQ(static_cast<size_t>(9), rs.blocks()); + + RangeSet rs2 = RangeSet::Parse("4,15,20,1,10"); + ASSERT_EQ(static_cast<size_t>(2), rs2.size()); + ASSERT_EQ((Range{ 15, 20 }), rs2[0]); + ASSERT_EQ((Range{ 1, 10 }), rs2[1]); + ASSERT_EQ(static_cast<size_t>(14), rs2.blocks()); + + // Leading zeros are fine. But android::base::ParseUint() doesn't like trailing zeros like "10 ". + ASSERT_EQ(rs, RangeSet::Parse(" 2, 1, 10")); + ASSERT_EXIT(RangeSet::Parse("2,1,10 "), ::testing::KilledBySignal(SIGABRT), ""); +} + +TEST(RangeSetTest, Parse_InvalidCases) { + // Insufficient number of tokens. + ASSERT_EXIT(RangeSet::Parse(""), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("2,1"), ::testing::KilledBySignal(SIGABRT), ""); + + // The first token (i.e. the number of following tokens) is invalid. + ASSERT_EXIT(RangeSet::Parse("a,1,1"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("3,1,1"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("-3,1,1"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("2,1,2,3"), ::testing::KilledBySignal(SIGABRT), ""); + + // Invalid tokens. + ASSERT_EXIT(RangeSet::Parse("2,1,10a"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("2,,10"), ::testing::KilledBySignal(SIGABRT), ""); + + // Empty or negative range. + ASSERT_EXIT(RangeSet::Parse("2,2,2"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("2,2,1"), ::testing::KilledBySignal(SIGABRT), ""); +} + +TEST(RangeSetTest, Overlaps) { + RangeSet r1 = RangeSet::Parse("2,1,6"); + RangeSet r2 = RangeSet::Parse("2,5,10"); + ASSERT_TRUE(r1.Overlaps(r2)); + ASSERT_TRUE(r2.Overlaps(r1)); + + r2 = RangeSet::Parse("2,6,10"); + ASSERT_FALSE(r1.Overlaps(r2)); + ASSERT_FALSE(r2.Overlaps(r1)); + + ASSERT_FALSE(RangeSet::Parse("2,3,5").Overlaps(RangeSet::Parse("2,5,7"))); + ASSERT_FALSE(RangeSet::Parse("2,5,7").Overlaps(RangeSet::Parse("2,3,5"))); +} + +TEST(RangeSetTest, GetBlockNumber) { + RangeSet rs = RangeSet::Parse("2,1,10"); + ASSERT_EQ(static_cast<size_t>(1), rs.GetBlockNumber(0)); + ASSERT_EQ(static_cast<size_t>(6), rs.GetBlockNumber(5)); + ASSERT_EQ(static_cast<size_t>(9), rs.GetBlockNumber(8)); + + // Out of bound. + ASSERT_EXIT(rs.GetBlockNumber(9), ::testing::KilledBySignal(SIGABRT), ""); +} + +TEST(RangeSetTest, equality) { + ASSERT_EQ(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,1,6")); + + ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,1,7")); + ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,2,7")); + + // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5". + ASSERT_NE(RangeSet::Parse("4,1,5,8,10"), RangeSet::Parse("4,8,10,1,5")); +} + +TEST(RangeSetTest, iterators) { + RangeSet rs = RangeSet::Parse("4,1,5,8,10"); + std::vector<Range> ranges; + for (const auto& range : rs) { + ranges.push_back(range); + } + ASSERT_EQ((std::vector<Range>{ Range{ 1, 5 }, Range{ 8, 10 } }), ranges); + + ranges.clear(); + + // Reverse iterators. + for (auto it = rs.crbegin(); it != rs.crend(); it++) { + ranges.push_back(*it); + } + ASSERT_EQ((std::vector<Range>{ Range{ 8, 10 }, Range{ 1, 5 } }), ranges); +} diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp index f4699664b..434ee25bf 100644 --- a/tests/unit/sysutil_test.cpp +++ b/tests/unit/sysutil_test.cpp @@ -27,27 +27,23 @@ TEST(SysUtilTest, InvalidArgs) { MemMapping mapping; // Invalid argument. - ASSERT_EQ(-1, sysMapFile(nullptr, &mapping)); - ASSERT_EQ(-1, sysMapFile("/somefile", nullptr)); + ASSERT_FALSE(mapping.MapFile("")); } -TEST(SysUtilTest, sysMapFileRegularFile) { +TEST(SysUtilTest, MapFileRegularFile) { TemporaryFile temp_file1; std::string content = "abc"; ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file1.path)); - // sysMapFile() should map the file to one range. + // MemMapping::MapFile() should map the file to one range. MemMapping mapping; - ASSERT_EQ(0, sysMapFile(temp_file1.path, &mapping)); + ASSERT_TRUE(mapping.MapFile(temp_file1.path)); ASSERT_NE(nullptr, mapping.addr); ASSERT_EQ(content.size(), mapping.length); - ASSERT_EQ(1U, mapping.ranges.size()); - - sysReleaseMap(&mapping); - ASSERT_EQ(0U, mapping.ranges.size()); + ASSERT_EQ(1U, mapping.ranges()); } -TEST(SysUtilTest, sysMapFileBlockMap) { +TEST(SysUtilTest, MapFileBlockMap) { // Create a file that has 10 blocks. TemporaryFile package; std::string content; @@ -63,78 +59,72 @@ TEST(SysUtilTest, sysMapFileBlockMap) { std::string block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n"; ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path)); - ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping)); + ASSERT_TRUE(mapping.MapFile(filename)); ASSERT_EQ(file_size, mapping.length); - ASSERT_EQ(1U, mapping.ranges.size()); + ASSERT_EQ(1U, mapping.ranges()); // It's okay to not have the trailing '\n'. block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10"; ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path)); - ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping)); + ASSERT_TRUE(mapping.MapFile(filename)); ASSERT_EQ(file_size, mapping.length); - ASSERT_EQ(1U, mapping.ranges.size()); + ASSERT_EQ(1U, mapping.ranges()); // Or having multiple trailing '\n's. block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n\n\n"; ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path)); - ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping)); + ASSERT_TRUE(mapping.MapFile(filename)); ASSERT_EQ(file_size, mapping.length); - ASSERT_EQ(1U, mapping.ranges.size()); + ASSERT_EQ(1U, mapping.ranges()); // Multiple ranges. block_map_content = std::string(package.path) + "\n40960 4096\n3\n0 3\n3 5\n5 10\n"; ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path)); - ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping)); + ASSERT_TRUE(mapping.MapFile(filename)); ASSERT_EQ(file_size, mapping.length); - ASSERT_EQ(3U, mapping.ranges.size()); - - sysReleaseMap(&mapping); - ASSERT_EQ(0U, mapping.ranges.size()); + ASSERT_EQ(3U, mapping.ranges()); } -TEST(SysUtilTest, sysMapFileBlockMapInvalidBlockMap) { +TEST(SysUtilTest, MapFileBlockMapInvalidBlockMap) { MemMapping mapping; TemporaryFile temp_file; std::string filename = std::string("@") + temp_file.path; // Block map file is too short. ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); // Block map file has unexpected number of lines. ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n1\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n2\n0 1\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); // Invalid size/blksize/range_count. ASSERT_TRUE(android::base::WriteStringToFile("/somefile\nabc 4096\n1\n0 1\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n\n0 1\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); // size/blksize/range_count don't match. ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n0 4096\n1\n0 1\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 0\n1\n0 1\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n0 1\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + ASSERT_FALSE(mapping.MapFile(filename)); // Invalid block dev path. ASSERT_TRUE(android::base::WriteStringToFile("/doesntexist\n4096 4096\n1\n0 1\n", temp_file.path)); - ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); - - sysReleaseMap(&mapping); - ASSERT_EQ(0U, mapping.ranges.size()); + ASSERT_FALSE(mapping.MapFile(filename)); } diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp index 4a1a49b97..827668521 100644 --- a/tests/unit/zip_test.cpp +++ b/tests/unit/zip_test.cpp @@ -24,51 +24,14 @@ #include <android-base/test_utils.h> #include <gtest/gtest.h> #include <otautil/SysUtil.h> -#include <otautil/ZipUtil.h> #include <ziparchive/zip_archive.h> #include "common/test_constants.h" -TEST(ZipTest, ExtractPackageRecursive) { - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Extract the whole package into a temp directory. - TemporaryDir td; - ASSERT_NE(nullptr, td.path); - ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr); - - // Make sure all the files are extracted correctly. - std::string path(td.path); - ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK)); - ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK)); - ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK)); - ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK)); - - // The content of the file is the same as expected. - std::string content1; - ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1)); - ASSERT_EQ(kATxtContents, content1); - - std::string content2; - ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2)); - ASSERT_EQ(kDTxtContents, content2); - - CloseArchive(handle); - - // Clean up. - ASSERT_EQ(0, unlink((path + "/a.txt").c_str())); - ASSERT_EQ(0, unlink((path + "/b.txt").c_str())); - ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str())); - ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str())); - ASSERT_EQ(0, rmdir((path + "/b").c_str())); -} - TEST(ZipTest, OpenFromMemory) { - MemMapping map; std::string zip_path = from_testdata_base("ziptest_dummy-update.zip"); - ASSERT_EQ(0, sysMapFile(zip_path.c_str(), &map)); + MemMapping map; + ASSERT_TRUE(map.MapFile(zip_path)); // Map an update package into memory and open the archive from there. ZipArchiveHandle handle; @@ -85,6 +48,5 @@ TEST(ZipTest, OpenFromMemory) { ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd)); CloseArchive(handle); - sysReleaseMap(&map); } diff --git a/tests/unit/ziputil_test.cpp b/tests/unit/ziputil_test.cpp deleted file mode 100644 index 14e541690..000000000 --- a/tests/unit/ziputil_test.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <errno.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <string> - -#include <android-base/file.h> -#include <android-base/test_utils.h> -#include <gtest/gtest.h> -#include <otautil/ZipUtil.h> -#include <ziparchive/zip_archive.h> - -#include "common/test_constants.h" - -TEST(ZipUtilTest, invalid_args) { - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // zip_path must be a relative path. - ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr)); - - // dest_path must be an absolute path. - ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr)); - ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr)); - - CloseArchive(handle); -} - -TEST(ZipUtilTest, extract_all) { - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Extract the whole package into a temp directory. - TemporaryDir td; - ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr); - - // Make sure all the files are extracted correctly. - std::string path(td.path); - ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK)); - ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK)); - ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK)); - ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK)); - - // The content of the file is the same as expected. - std::string content1; - ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1)); - ASSERT_EQ(kATxtContents, content1); - - std::string content2; - ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2)); - ASSERT_EQ(kDTxtContents, content2); - - // Clean up the temp files under td. - ASSERT_EQ(0, unlink((path + "/a.txt").c_str())); - ASSERT_EQ(0, unlink((path + "/b.txt").c_str())); - ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str())); - ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str())); - ASSERT_EQ(0, rmdir((path + "/b").c_str())); - - CloseArchive(handle); -} - -TEST(ZipUtilTest, extract_prefix_with_slash) { - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Extract all the entries starting with "b/". - TemporaryDir td; - ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr); - - // Make sure all the files with "b/" prefix are extracted correctly. - std::string path(td.path); - ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK)); - ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK)); - - // And the rest are not extracted. - ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK)); - ASSERT_EQ(ENOENT, errno); - ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK)); - ASSERT_EQ(ENOENT, errno); - - // The content of the file is the same as expected. - std::string content1; - ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1)); - ASSERT_EQ(kCTxtContents, content1); - - std::string content2; - ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2)); - ASSERT_EQ(kDTxtContents, content2); - - // Clean up the temp files under td. - ASSERT_EQ(0, unlink((path + "/c.txt").c_str())); - ASSERT_EQ(0, unlink((path + "/d.txt").c_str())); - - CloseArchive(handle); -} - -TEST(ZipUtilTest, extract_prefix_without_slash) { - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Extract all the file entries starting with "b/". - TemporaryDir td; - ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr); - - // Make sure all the files with "b/" prefix are extracted correctly. - std::string path(td.path); - ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK)); - ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK)); - - // And the rest are not extracted. - ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK)); - ASSERT_EQ(ENOENT, errno); - ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK)); - ASSERT_EQ(ENOENT, errno); - - // The content of the file is the same as expected. - std::string content1; - ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1)); - ASSERT_EQ(kCTxtContents, content1); - - std::string content2; - ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2)); - ASSERT_EQ(kDTxtContents, content2); - - // Clean up the temp files under td. - ASSERT_EQ(0, unlink((path + "/c.txt").c_str())); - ASSERT_EQ(0, unlink((path + "/d.txt").c_str())); - - CloseArchive(handle); -} - -TEST(ZipUtilTest, set_timestamp) { - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Set the timestamp to 8/1/2008. - constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; - - // Extract all the entries starting with "b/". - TemporaryDir td; - ExtractPackageRecursive(handle, "b", td.path, ×tamp, nullptr); - - // Make sure all the files with "b/" prefix are extracted correctly. - std::string path(td.path); - std::string file_c = path + "/c.txt"; - std::string file_d = path + "/d.txt"; - ASSERT_EQ(0, access(file_c.c_str(), F_OK)); - ASSERT_EQ(0, access(file_d.c_str(), F_OK)); - - // Verify the timestamp. - timespec time; - time.tv_sec = 1217592000; - time.tv_nsec = 0; - - struct stat sb; - ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno); - ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime)); - ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime)); - - ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno); - ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime)); - ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime)); - - // Clean up the temp files under td. - ASSERT_EQ(0, unlink(file_c.c_str())); - ASSERT_EQ(0, unlink(file_d.c_str())); - - CloseArchive(handle); -} diff --git a/tools/recovery_l10n/res/values-en-rCA/strings.xml b/tools/recovery_l10n/res/values-en-rCA/strings.xml new file mode 100644 index 000000000..dc75c2374 --- /dev/null +++ b/tools/recovery_l10n/res/values-en-rCA/strings.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="recovery_installing" msgid="2013591905463558223">"Installing system update"</string> + <string name="recovery_erasing" msgid="7334826894904037088">"Erasing"</string> + <string name="recovery_no_command" msgid="4465476568623024327">"No command"</string> + <string name="recovery_error" msgid="5748178989622716736">"Error!"</string> + <string name="recovery_installing_security" msgid="9184031299717114342">"Installing security update"</string> +</resources> diff --git a/tools/recovery_l10n/res/values-en-rXC/strings.xml b/tools/recovery_l10n/res/values-en-rXC/strings.xml new file mode 100644 index 000000000..2d528b3fb --- /dev/null +++ b/tools/recovery_l10n/res/values-en-rXC/strings.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="recovery_installing" msgid="2013591905463558223">"Installing system update"</string> + <string name="recovery_erasing" msgid="7334826894904037088">"Erasing"</string> + <string name="recovery_no_command" msgid="4465476568623024327">"No command"</string> + <string name="recovery_error" msgid="5748178989622716736">"Error!"</string> + <string name="recovery_installing_security" msgid="9184031299717114342">"Installing security update"</string> +</resources> diff --git a/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml index a8a876ee4..65d003352 100644 --- a/tools/recovery_l10n/res/values-hi/strings.xml +++ b/tools/recovery_l10n/res/values-hi/strings.xml @@ -3,7 +3,7 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अपडेट इंस्टॉल किया जा रहा है"</string> <string name="recovery_erasing" msgid="7334826894904037088">"मिटाया जा रहा है"</string> - <string name="recovery_no_command" msgid="4465476568623024327">"कोई आदेश नहीं"</string> + <string name="recovery_no_command" msgid="4465476568623024327">"कोई निर्देश नहीं मिला"</string> <string name="recovery_error" msgid="5748178989622716736">"गड़बड़ी!"</string> <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अपडेट इंस्टॉल किया जा रहा है"</string> </resources> diff --git a/tools/recovery_l10n/res/values-mr/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml index 8cf86f773..5f820336f 100644 --- a/tools/recovery_l10n/res/values-mr/strings.xml +++ b/tools/recovery_l10n/res/values-mr/strings.xml @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अद्यतन स्थापित करीत आहे"</string> + <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अपडेट इंस्टॉल करत आहे"</string> <string name="recovery_erasing" msgid="7334826894904037088">"मिटवत आहे"</string> - <string name="recovery_no_command" msgid="4465476568623024327">"कोणताही आदेश नाही"</string> - <string name="recovery_error" msgid="5748178989622716736">"त्रुटी!"</string> - <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अद्यतन स्थापित करीत आहे"</string> + <string name="recovery_no_command" msgid="4465476568623024327">"कोणतीही कमांड नाही"</string> + <string name="recovery_error" msgid="5748178989622716736">"एरर!"</string> + <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अपडेट इंस्टॉल करत आहे"</string> </resources> diff --git a/tools/recovery_l10n/res/values-pa/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml index 8564c9c36..27972d117 100644 --- a/tools/recovery_l10n/res/values-pa/strings.xml +++ b/tools/recovery_l10n/res/values-pa/strings.xml @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="recovery_installing" msgid="2013591905463558223">"ਸਿਸਟਮ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string> + <string name="recovery_installing" msgid="2013591905463558223">"ਸਿਸਟਮ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="recovery_erasing" msgid="7334826894904037088">"ਮਿਟਾਈ ਜਾ ਰਹੀ ਹੈ"</string> - <string name="recovery_no_command" msgid="4465476568623024327">"ਕੋਈ ਕਮਾਂਡ ਨਹੀਂ"</string> + <string name="recovery_no_command" msgid="4465476568623024327">"ਕੋਈ ਆਦੇਸ਼ ਨਹੀਂ"</string> <string name="recovery_error" msgid="5748178989622716736">"ਅਸ਼ੁੱਧੀ!"</string> - <string name="recovery_installing_security" msgid="9184031299717114342">"ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string> + <string name="recovery_installing_security" msgid="9184031299717114342">"ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> </resources> diff --git a/tools/recovery_l10n/res/values-te/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml index cfb02c915..e35c82bc4 100644 --- a/tools/recovery_l10n/res/values-te/strings.xml +++ b/tools/recovery_l10n/res/values-te/strings.xml @@ -4,6 +4,6 @@ <string name="recovery_installing" msgid="2013591905463558223">"సిస్టమ్ నవీకరణను ఇన్స్టాల్ చేస్తోంది"</string> <string name="recovery_erasing" msgid="7334826894904037088">"డేటాను తొలగిస్తోంది"</string> <string name="recovery_no_command" msgid="4465476568623024327">"ఆదేశం లేదు"</string> - <string name="recovery_error" msgid="5748178989622716736">"లోపం సంభవించింది!"</string> + <string name="recovery_error" msgid="5748178989622716736">"ఎర్రర్ సంభవించింది!"</string> <string name="recovery_installing_security" msgid="9184031299717114342">"భద్రతా నవీకరణను ఇన్స్టాల్ చేస్తోంది"</string> </resources> diff --git a/twinstall.cpp b/twinstall.cpp index ab715291a..7351e6326 100644 --- a/twinstall.cpp +++ b/twinstall.cpp @@ -363,7 +363,11 @@ int TWinstall_zip(const char* path, int* wipe_cache) { DataManager::SetProgress(0); MemMapping map; +#ifdef USE_MINZIP if (sysMapFile(path, &map) != 0) { +#else + if (!map.MapFile(path)) { +#endif gui_msg(Msg(msg::kError, "fail_sysmap=Failed to map file '{1}'")(path)); return -1; } @@ -377,6 +381,9 @@ int TWinstall_zip(const char* path, int* wipe_cache) { if (!load_keys("/res/keys", loadedKeys)) { LOGINFO("Failed to load keys"); gui_err("verify_zip_fail=Zip signature verification failed!"); +#ifdef USE_MINZIP + sysReleaseMap(&map); +#endif return -1; } ret_val = verify_file(map.addr, map.length, loadedKeys, std::bind(&DataManager::SetProgress, std::placeholders::_1)); @@ -384,7 +391,9 @@ int TWinstall_zip(const char* path, int* wipe_cache) { if (ret_val != VERIFY_SUCCESS) { LOGINFO("Zip signature verification failed: %i\n", ret_val); gui_err("verify_zip_fail=Zip signature verification failed!"); +#ifdef USE_MINZIP sysReleaseMap(&map); +#endif return -1; } else { gui_msg("verify_zip_done=Zip signature verified successfully."); @@ -393,7 +402,9 @@ int TWinstall_zip(const char* path, int* wipe_cache) { ZipWrap Zip; if (!Zip.Open(path, &map)) { gui_err("zip_corrupt=Zip file is corrupt!"); - sysReleaseMap(&map); +#ifdef USE_MINZIP + sysReleaseMap(&map); +#endif return INSTALL_CORRUPT; } @@ -404,8 +415,10 @@ int TWinstall_zip(const char* path, int* wipe_cache) { // Additionally verify the compatibility of the package. if (!verify_package_compatibility(&Zip)) { gui_err("zip_compatible_err=Zip Treble compatibility error!"); - sysReleaseMap(&map); Zip.Close(); +#ifdef USE_MINZIP + sysReleaseMap(&map); +#endif ret_val = INSTALL_CORRUPT; } else { ret_val = Prepare_Update_Binary(path, &Zip, wipe_cache); @@ -433,6 +446,8 @@ int TWinstall_zip(const char* path, int* wipe_cache) { } else { LOGINFO("Install took %i second(s).\n", total_time); } +#ifdef USE_MINZIP sysReleaseMap(&map); +#endif return ret_val; } @@ -54,6 +54,9 @@ RecoveryUI::RecoveryUI() rtl_locale_(false), brightness_normal_(50), brightness_dimmed_(25), + touch_screen_allowed_(false), + kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD), + kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD), key_queue_len(0), key_last_down(-1), key_long_press(false), @@ -64,6 +67,9 @@ RecoveryUI::RecoveryUI() has_power_key(false), has_up_key(false), has_down_key(false), + has_touch_screen(false), + 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); @@ -71,23 +77,25 @@ RecoveryUI::RecoveryUI() } void RecoveryUI::OnKeyDetected(int key_code) { - if (key_code == KEY_POWER) { - has_power_key = true; - } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { - has_down_key = true; - } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { - has_up_key = true; - } + if (key_code == KEY_POWER) { + has_power_key = true; + } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { + has_down_key = true; + } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { + has_up_key = true; + } else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) { + has_touch_screen = true; + } } // 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(); - } + while (true) { + if (!ev_wait(-1)) { + ev_dispatch(); } - return nullptr; + } + return nullptr; } bool RecoveryUI::InitScreensaver() { @@ -128,10 +136,28 @@ bool RecoveryUI::Init(const std::string& locale) { // Set up the locale info. SetLocale(locale); - ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2)); + ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2), + touch_screen_allowed_); ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); + if (touch_screen_allowed_) { + ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); + + // Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of + // "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way + // to turn on text mode. It will only be set if the recovery boot is triggered from fastboot, or + // with 'adb reboot recovery'. Note that this applies to all build variants. Otherwise the text + // mode will be turned on automatically on debuggable builds, even without a swipe. + std::string cmdline; + if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) { + is_bootreason_recovery_ui_ = cmdline.find("bootreason=recovery_ui") != std::string::npos; + } else { + // Non-fatal, and won't affect Init() result. + PLOG(WARNING) << "Failed to read /proc/cmdline"; + } + } + if (!InitScreensaver()) { LOG(INFO) << "Screensaver disabled"; } @@ -140,40 +166,157 @@ bool RecoveryUI::Init(const std::string& locale) { return true; } +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) { + direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT; + } else if (abs(dx) < kTouchLowThreshold && abs(dy) > kTouchHighThreshold) { + direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN; + } else { + LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << kTouchLowThreshold + << ", high: " << kTouchHighThreshold << ")"; + return; + } + + // Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui. + if (is_bootreason_recovery_ui_ && !IsTextVisible()) { + ShowText(true); + return; + } + + LOG(DEBUG) << "Swipe direction=" << direction; + switch (direction) { + case SwipeDirection::UP: + ProcessKey(KEY_UP, 1); // press up key + ProcessKey(KEY_UP, 0); // and release it + break; + + case SwipeDirection::DOWN: + ProcessKey(KEY_DOWN, 1); // press down key + ProcessKey(KEY_DOWN, 0); // and release it + break; + + case SwipeDirection::LEFT: + case SwipeDirection::RIGHT: + ProcessKey(KEY_POWER, 1); // press power key + ProcessKey(KEY_POWER, 0); // and release it + break; + }; +} + int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { - struct input_event ev; - if (ev_get_input(fd, epevents, &ev) == -1) { - return -1; + struct input_event ev; + if (ev_get_input(fd, epevents, &ev) == -1) { + return -1; + } + + // Touch inputs handling. + // + // We handle the touch inputs by tracking the position changes between initial contacting and + // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon + // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes. + // + // Per the doc Multi-touch Protocol at below, there are two protocols. + // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt + // + // The main difference between the stateless type A protocol and the stateful type B slot protocol + // lies in the usage of identifiable contacts to reduce the amount of data sent to userspace. The + // slot protocol (i.e. type B) sends ABS_MT_TRACKING_ID with a unique id on initial contact, and + // sends ABS_MT_TRACKING_ID -1 upon lifting the contact. Protocol A doesn't send + // ABS_MT_TRACKING_ID -1 on lifting, but the driver may additionally report BTN_TOUCH event. + // + // For protocol A, we rely on BTN_TOUCH to recognize lifting, while for protocol B we look for + // ABS_MT_TRACKING_ID being -1. + // + // Touch input events will only be available if touch_screen_allowed_ is set. + + if (ev.type == EV_SYN) { + if (touch_screen_allowed_ && ev.code == SYN_REPORT) { + // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the + // contact. + if (touch_finger_down_ && !touch_swiping_) { + touch_start_X_ = touch_X_; + touch_start_Y_ = touch_Y_; + touch_swiping_ = true; + } else if (!touch_finger_down_ && touch_swiping_) { + touch_swiping_ = false; + OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_); + } } + return 0; + } - if (ev.type == EV_SYN) { - return 0; - } else if (ev.type == EV_REL) { - if (ev.code == REL_Y) { - // accumulate the up or down motion reported by - // the trackball. When it exceeds a threshold - // (positive or negative), fake an up/down - // key event. - rel_sum += ev.value; - if (rel_sum > 3) { - ProcessKey(KEY_DOWN, 1); // press down key - ProcessKey(KEY_DOWN, 0); // and release it - rel_sum = 0; - } else if (rel_sum < -3) { - ProcessKey(KEY_UP, 1); // press up key - ProcessKey(KEY_UP, 0); // and release it - rel_sum = 0; - } - } - } else { + if (ev.type == EV_REL) { + if (ev.code == REL_Y) { + // accumulate the up or down motion reported by + // the trackball. When it exceeds a threshold + // (positive or negative), fake an up/down + // key event. + rel_sum += ev.value; + if (rel_sum > 3) { + ProcessKey(KEY_DOWN, 1); // press down key + ProcessKey(KEY_DOWN, 0); // and release it + rel_sum = 0; + } else if (rel_sum < -3) { + ProcessKey(KEY_UP, 1); // press up key + ProcessKey(KEY_UP, 0); // and release it rel_sum = 0; + } } + } else { + rel_sum = 0; + } - if (ev.type == EV_KEY && ev.code <= KEY_MAX) { - ProcessKey(ev.code, ev.value); + if (touch_screen_allowed_ && ev.type == EV_ABS) { + if (ev.code == ABS_MT_SLOT) { + touch_slot_ = ev.value; + } + // Ignore other fingers. + if (touch_slot_ > 0) return 0; + + switch (ev.code) { + case ABS_MT_POSITION_X: + touch_X_ = ev.value; + touch_finger_down_ = true; + break; + + case ABS_MT_POSITION_Y: + touch_Y_ = ev.value; + touch_finger_down_ = true; + break; + + case ABS_MT_TRACKING_ID: + // Protocol B: -1 marks lifting the contact. + if (ev.value < 0) touch_finger_down_ = false; + break; } - return 0; + } + + if (ev.type == EV_KEY && ev.code <= KEY_MAX) { + if (touch_screen_allowed_) { + if (ev.code == BTN_TOUCH) { + // A BTN_TOUCH with value 1 indicates the start of contact (protocol A), with 0 means + // lifting the contact. + touch_finger_down_ = (ev.value == 1); + } + + // Intentionally ignore BTN_TOUCH and BTN_TOOL_FINGER, which would otherwise trigger + // additional scrolling (because in ScreenRecoveryUI::ShowFile(), we consider keys other than + // KEY_POWER and KEY_UP as KEY_DOWN). + if (ev.code == BTN_TOUCH || ev.code == BTN_TOOL_FINGER) { + return 0; + } + } + + ProcessKey(ev.code, ev.value); + } + + return 0; } // Process a key-up or -down event. A key is "registered" when it is @@ -189,82 +332,84 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { // // 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; + 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; - } - key_last_down = -1; + 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; } - reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); + key_last_down = -1; + } + reboot_enabled = enable_reboot; + pthread_mutex_unlock(&key_queue_mutex); - if (register_key) { - switch (CheckKey(key_code, long_press)) { - case RecoveryUI::IGNORE: - break; - - case RecoveryUI::TOGGLE: - ShowText(!IsTextVisible()); - break; - - case RecoveryUI::REBOOT: - if (reboot_enabled) { - reboot("reboot,"); - while (true) { pause(); } - } - break; - - case RecoveryUI::ENQUEUE: - EnqueueKey(key_code); - break; + if (register_key) { + switch (CheckKey(key_code, long_press)) { + case RecoveryUI::IGNORE: + break; + + case RecoveryUI::TOGGLE: + ShowText(!IsTextVisible()); + break; + + case RecoveryUI::REBOOT: + if (reboot_enabled) { + reboot("reboot,"); + while (true) { + pause(); + } } + break; + + case RecoveryUI::ENQUEUE: + EnqueueKey(key_code); + break; } + } } void* RecoveryUI::time_key_helper(void* cookie) { - key_timer_t* info = static_cast<key_timer_t*>(cookie); - info->ui->time_key(info->key_code, info->count); - delete info; - return nullptr; + key_timer_t* info = static_cast<key_timer_t*>(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" - 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; - } - pthread_mutex_unlock(&key_queue_mutex); - if (long_press) KeyLongPress(key_code); + usleep(750000); // 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; + } + pthread_mutex_unlock(&key_queue_mutex); + if (long_press) KeyLongPress(key_code); } void RecoveryUI::EnqueueKey(int key_code) { - pthread_mutex_lock(&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); - } - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&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); + } + pthread_mutex_unlock(&key_queue_mutex); } int RecoveryUI::WaitKey() { @@ -330,98 +475,104 @@ int RecoveryUI::WaitKey() { } bool RecoveryUI::IsUsbConnected() { - int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); - if (fd < 0) { - printf("failed to open /sys/class/android_usb/android0/state: %s\n", - strerror(errno)); - return 0; - } + int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); + if (fd < 0) { + printf("failed to open /sys/class/android_usb/android0/state: %s\n", strerror(errno)); + return 0; + } - char buf; - // USB is connected if android_usb state is CONNECTED or CONFIGURED. - int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C'); - if (close(fd) < 0) { - printf("failed to close /sys/class/android_usb/android0/state: %s\n", - strerror(errno)); - } - return connected; + char buf; + // USB is connected if android_usb state is CONNECTED or CONFIGURED. + int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C'); + if (close(fd) < 0) { + printf("failed to close /sys/class/android_usb/android0/state: %s\n", strerror(errno)); + } + return connected; } bool RecoveryUI::IsKeyPressed(int key) { - pthread_mutex_lock(&key_queue_mutex); - int pressed = key_pressed[key]; - pthread_mutex_unlock(&key_queue_mutex); - return pressed; + pthread_mutex_lock(&key_queue_mutex); + int pressed = key_pressed[key]; + pthread_mutex_unlock(&key_queue_mutex); + return pressed; } bool RecoveryUI::IsLongPress() { - pthread_mutex_lock(&key_queue_mutex); - bool result = key_long_press; - pthread_mutex_unlock(&key_queue_mutex); - return result; + pthread_mutex_lock(&key_queue_mutex); + bool result = key_long_press; + pthread_mutex_unlock(&key_queue_mutex); + return result; } bool RecoveryUI::HasThreeButtons() { - return has_power_key && has_up_key && has_down_key; + return has_power_key && has_up_key && has_down_key; +} + +bool RecoveryUI::HasPowerKey() const { + return has_power_key; +} + +bool RecoveryUI::HasTouchScreen() const { + return has_touch_screen; } void RecoveryUI::FlushKeys() { - pthread_mutex_lock(&key_queue_mutex); - key_queue_len = 0; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&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); + pthread_mutex_lock(&key_queue_mutex); + key_long_press = false; + pthread_mutex_unlock(&key_queue_mutex); - // If we have power and volume up keys, that chord is the signal to toggle the text display. - if (HasThreeButtons()) { - if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) { - return TOGGLE; - } - } else { - // Otherwise long press of any button toggles to the text display, - // and there's no way to toggle back (but that's pretty useless anyway). - if (is_long_press && !IsTextVisible()) { - return TOGGLE; - } + // 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_)) { + if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) { + return TOGGLE; + } + } else { + // Otherwise long press of any button toggles to the text display, + // and there's no way to toggle back (but that's pretty useless anyway). + if (is_long_press && !IsTextVisible()) { + return TOGGLE; + } - // Also, for button-limited devices, a long press is translated to KEY_ENTER. - if (is_long_press && IsTextVisible()) { - EnqueueKey(KEY_ENTER); - return IGNORE; - } + // Also, for button-limited devices, a long press is translated to KEY_ENTER. + if (is_long_press && IsTextVisible()) { + EnqueueKey(KEY_ENTER); + return IGNORE; } + } - // 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); + // 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; - if (consecutive_power_keys >= 7) { - return REBOOT; - } - } - } else { - consecutive_power_keys = 0; + if (reboot_enabled) { + ++consecutive_power_keys; + if (consecutive_power_keys >= 7) { + return REBOOT; + } } + } else { + consecutive_power_keys = 0; + } - last_key = key; - return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; + last_key = key; + return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; } void RecoveryUI::KeyLongPress(int) { } void RecoveryUI::SetEnableReboot(bool enabled) { - pthread_mutex_lock(&key_queue_mutex); - enable_reboot = enabled; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&key_queue_mutex); + enable_reboot = enabled; + pthread_mutex_unlock(&key_queue_mutex); } void RecoveryUI::SetLocale(const std::string& new_locale) { @@ -25,163 +25,180 @@ // Abstract class for controlling the user interface during recovery. class RecoveryUI { - public: - RecoveryUI(); + public: + RecoveryUI(); - virtual ~RecoveryUI() { } + virtual ~RecoveryUI() {} - // Initialize the object; called before anything else. UI texts will be - // initialized according to the given locale. Returns true on success. - virtual bool Init(const std::string& locale); + // 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); - // Show a stage indicator. Call immediately after Init(). - virtual void SetStage(int current, int max) = 0; + // Shows a stage indicator. Called immediately after Init(). + virtual void SetStage(int current, int max) = 0; - // Set the overall recovery state ("background image"). - enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; - virtual void SetBackground(Icon icon) = 0; - virtual void SetSystemUpdateText(bool security_update) = 0; + // Sets the overall recovery state ("background image"). + enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; + virtual void SetBackground(Icon icon) = 0; + virtual void SetSystemUpdateText(bool security_update) = 0; - // --- progress indicator --- - enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE }; - virtual void SetProgressType(ProgressType determinate) = 0; - - // Show a progress bar and define the scope of the next operation: - // portion - fraction of the progress bar the next operation will use - // seconds - expected time interval (progress bar moves at this minimum rate) - virtual void ShowProgress(float portion, float seconds) = 0; - - // Set progress bar position (0.0 - 1.0 within the scope defined - // by the last call to ShowProgress). - virtual void SetProgress(float fraction) = 0; - - // --- text log --- - - virtual void ShowText(bool visible) = 0; - - virtual bool IsTextVisible() = 0; - - virtual bool WasTextEverVisible() = 0; - - // Write a message to the on-screen log (shown if the user has - // toggled on the text display). Print() will also dump the message - // to stdout / log file, while PrintOnScreenOnly() not. - 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; + // --- progress indicator --- + enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE }; + virtual void SetProgressType(ProgressType determinate) = 0; - // --- key handling --- + // Shows a progress bar and define the scope of the next operation: + // portion - fraction of the progress bar the next operation will use + // seconds - expected time interval (progress bar moves at this minimum rate) + virtual void ShowProgress(float portion, float seconds) = 0; - // Wait for a key and return it. May return -1 after timeout. - virtual int WaitKey(); + // Sets progress bar position (0.0 - 1.0 within the scope defined by the last call to + // ShowProgress). + virtual void SetProgress(float fraction) = 0; - virtual bool IsKeyPressed(int key); - virtual bool IsLongPress(); + // --- text log --- - // Returns true if you have the volume up/down and power trio typical - // of phones and tablets, false otherwise. - virtual bool HasThreeButtons(); - - // Erase any queued-up keys. - virtual void FlushKeys(); - - // Called on each key press, even while operations are in progress. - // Return value indicates whether an immediate operation should be - // triggered (toggling the display, rebooting the device), or if - // the key should be enqueued for use by the main thread. - enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE }; - virtual KeyAction CheckKey(int key, bool is_long_press); - - // Called when a key is held down long enough to have been a - // long-press (but before the key is released). This means that - // if the key is eventually registered (released without any other - // keys being pressed in the meantime), CheckKey will be called with - // 'is_long_press' true. - virtual void KeyLongPress(int key); - - // Normally in recovery there's a key sequence that triggers - // immediate reboot of the device, regardless of what recovery is - // doing (with the default CheckKey implementation, it's pressing - // the power button 7 times in row). Call this to enable or - // disable that feature. It is enabled by default. - virtual void SetEnableReboot(bool enabled); - - // --- 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; - - // Set the menu highlight to the given index, wrapping if necessary. - // Returns the actual item selected. - virtual int SelectMenu(int sel) = 0; - - // End menu mode, resetting the text overlay so that ui_print() - // statements will be displayed. - virtual void EndMenu() = 0; - - protected: - void EnqueueKey(int key_code); - - // The locale that's used to show the rendered texts. - std::string locale_; - bool rtl_locale_; - - // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% - // of the max_brightness). Because the absolute values may vary across devices. These two - // values can be configured via subclassing. Setting brightness_normal_ to 0 to disable - // screensaver. - unsigned int brightness_normal_; - unsigned int brightness_dimmed_; - - private: - // Key event input queue - pthread_mutex_t key_queue_mutex; - pthread_cond_t key_queue_cond; - 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; - - int consecutive_power_keys; - int last_key; - - bool has_power_key; - bool has_up_key; - bool has_down_key; - - struct key_timer_t { - RecoveryUI* ui; - int key_code; - int count; - }; - - pthread_t input_thread_; - - void OnKeyDetected(int key_code); - int OnInputEvent(int fd, uint32_t epevents); - void ProcessKey(int key_code, int updown); - - bool IsUsbConnected(); - - static void* time_key_helper(void* cookie); - void time_key(int key_code, int count); - - void SetLocale(const std::string&); - - enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF }; - ScreensaverState screensaver_state_; - // The following two contain the absolute values computed from brightness_normal_ and - // brightness_dimmed_ respectively. - unsigned int brightness_normal_value_; - unsigned int brightness_dimmed_value_; - bool InitScreensaver(); + virtual void ShowText(bool visible) = 0; + + virtual bool IsTextVisible() = 0; + + virtual bool WasTextEverVisible() = 0; + + // Writes a message to the on-screen log (shown if the user has toggled on the text display). + // Print() will also dump the message to stdout / log file, while PrintOnScreenOnly() not. + 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; + + // --- key handling --- + + // Waits for a key and return it. May return -1 after timeout. + virtual int WaitKey(); + + 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(); + + // Returns true if it has a power key. + virtual bool HasPowerKey() const; + + // Returns true if it supports touch inputs. + virtual bool HasTouchScreen() const; + + // Erases any queued-up keys. + virtual void FlushKeys(); + + // Called on each key press, even while operations are in progress. Return value indicates whether + // an immediate operation should be triggered (toggling the display, rebooting the device), or if + // the key should be enqueued for use by the main thread. + enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE }; + virtual KeyAction CheckKey(int key, bool is_long_press); + + // Called when a key is held down long enough to have been a long-press (but before the key is + // released). This means that if the key is eventually registered (released without any other keys + // being pressed in the meantime), CheckKey will be called with 'is_long_press' true. + virtual void KeyLongPress(int key); + + // Normally in recovery there's a key sequence that triggers immediate reboot of the device, + // regardless of what recovery is doing (with the default CheckKey implementation, it's pressing + // the power button 7 times in row). Call this to enable or disable that feature. It is enabled by + // default. + virtual void SetEnableReboot(bool enabled); + + // --- 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; + + protected: + void EnqueueKey(int key_code); + + // The locale that's used to show the rendered texts. + std::string locale_; + bool rtl_locale_; + + // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of + // the max_brightness). Because the absolute values may vary across devices. These two values can + // be configured via subclassing. Setting brightness_normal_ to 0 to disable screensaver. + unsigned int brightness_normal_; + unsigned int brightness_dimmed_; + + // Whether we should listen for touch inputs (default: false). + bool touch_screen_allowed_; + + private: + // The sensitivity when detecting a swipe. + const int kTouchLowThreshold; + const int kTouchHighThreshold; + + // Key event input queue + pthread_mutex_t key_queue_mutex; + pthread_cond_t key_queue_cond; + 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; + + int consecutive_power_keys; + int last_key; + + bool has_power_key; + bool has_up_key; + bool has_down_key; + bool has_touch_screen; + + // Touch event related variables. See the comments in RecoveryUI::OnInputEvent(). + int touch_slot_; + int touch_X_; + int touch_Y_; + int touch_start_X_; + int touch_start_Y_; + bool touch_finger_down_; + bool touch_swiping_; + bool is_bootreason_recovery_ui_; + + struct key_timer_t { + RecoveryUI* ui; + int key_code; + int count; + }; + + pthread_t input_thread_; + + 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); + + bool IsUsbConnected(); + + static void* time_key_helper(void* cookie); + void time_key(int key_code, int count); + + void SetLocale(const std::string&); + + enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF }; + ScreensaverState screensaver_state_; + // The following two contain the absolute values computed from brightness_normal_ and + // brightness_dimmed_ respectively. + unsigned int brightness_normal_value_; + unsigned int brightness_dimmed_value_; + bool InitScreensaver(); }; #endif // RECOVERY_UI_H diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk index 1acd5eca0..33c5fe9e7 100644 --- a/update_verifier/Android.mk +++ b/update_verifier/Android.mk @@ -14,12 +14,47 @@ LOCAL_PATH := $(call my-dir) +# libupdate_verifier (static library) +# =============================== include $(CLEAR_VARS) -LOCAL_CLANG := true -LOCAL_SRC_FILES := update_verifier.cpp +LOCAL_SRC_FILES := \ + update_verifier.cpp + +LOCAL_MODULE := libupdate_verifier +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 LOCAL_SHARED_LIBRARIES := \ libbase \ libcutils \ @@ -29,13 +64,8 @@ LOCAL_SHARED_LIBRARIES := \ libhidlbase \ android.hardware.boot@1.0 -LOCAL_CFLAGS := -Werror -LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. +LOCAL_CFLAGS := -Wall -Werror LOCAL_INIT_RC := update_verifier.rc -ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true) - LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1 -endif - include $(BUILD_EXECUTABLE) diff --git a/update_verifier/include/update_verifier/update_verifier.h b/update_verifier/include/update_verifier/update_verifier.h new file mode 100644 index 000000000..16b394e98 --- /dev/null +++ b/update_verifier/include/update_verifier/update_verifier.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <string> + +int update_verifier(int argc, char** argv); + +// Exposed for testing purpose. +bool verify_image(const std::string& care_map_name); diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp index 350020f13..ba7b7aec4 100644 --- a/update_verifier/update_verifier.cpp +++ b/update_verifier/update_verifier.cpp @@ -35,6 +35,8 @@ * verifier reaches the end after the verification. */ +#include "update_verifier/update_verifier.h" + #include <dirent.h> #include <errno.h> #include <fcntl.h> @@ -42,6 +44,8 @@ #include <string.h> #include <unistd.h> +#include <algorithm> +#include <future> #include <string> #include <vector> @@ -59,12 +63,6 @@ using android::hardware::boot::V1_0::IBootControl; using android::hardware::boot::V1_0::BoolResult; using android::hardware::boot::V1_0::CommandResult; -constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt"; -constexpr auto DM_PATH_PREFIX = "/sys/block/"; -constexpr auto DM_PATH_SUFFIX = "/dm/name"; -constexpr auto DEV_PATH = "/dev/block/"; -constexpr int BLOCKSIZE = 4096; - // 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-")) { @@ -82,6 +80,7 @@ static bool read_blocks(const std::string& partition, const std::string& range_s // (or "vendor"), then dm-X is a dm-wrapped system/vendor partition. // Afterwards, update_verifier will read every block on the care_map_file of // "/dev/block/dm-X" to ensure the partition's integrity. + static constexpr auto DM_PATH_PREFIX = "/sys/block/"; dirent** namelist; int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort); if (n == -1) { @@ -93,18 +92,29 @@ static bool read_blocks(const std::string& partition, const std::string& range_s return false; } + static constexpr auto DM_PATH_SUFFIX = "/dm/name"; + static constexpr auto DEV_PATH = "/dev/block/"; std::string dm_block_device; while (n--) { std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX; std::string content; if (!android::base::ReadFileToString(path, &content)) { PLOG(WARNING) << "Failed to read " << path; - } else if (android::base::Trim(content) == partition) { - dm_block_device = DEV_PATH + std::string(namelist[n]->d_name); - while (n--) { - free(namelist[n]); + } 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"; + } +#endif + if (dm_block_name == partition) { + dm_block_device = DEV_PATH + std::string(namelist[n]->d_name); + while (n--) { + free(namelist[n]); + } + break; } - break; } free(namelist[n]); } @@ -114,11 +124,6 @@ static bool read_blocks(const std::string& partition, const std::string& range_s LOG(ERROR) << "Failed to find dm block device for " << partition; return false; } - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY))); - if (fd.get() == -1) { - PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition; - return false; - } // 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 @@ -132,69 +137,113 @@ static bool read_blocks(const std::string& partition, const std::string& range_s LOG(ERROR) << "Error in parsing range string."; return false; } + range_count /= 2; - size_t blk_count = 0; - for (size_t i = 1; i < ranges.size(); i += 2) { - unsigned int range_start, range_end; - bool parse_status = android::base::ParseUint(ranges[i], &range_start); - parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end); - if (!parse_status || range_start >= range_end) { - LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1]; - return false; - } + std::vector<std::future<bool>> threads; + size_t thread_num = std::thread::hardware_concurrency() ?: 4; + thread_num = std::min(thread_num, range_count); + size_t group_range_count = (range_count + thread_num - 1) / thread_num; - if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) { - PLOG(ERROR) << "lseek to " << range_start << " failed"; - return false; - } + for (size_t t = 0; t < thread_num; t++) { + auto thread_func = [t, group_range_count, &dm_block_device, &ranges, &partition]() { + size_t blk_count = 0; + static constexpr size_t kBlockSize = 4096; + std::vector<uint8_t> buf(1024 * kBlockSize); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY))); + if (fd.get() == -1) { + PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition; + return false; + } - size_t size = (range_end - range_start) * BLOCKSIZE; - std::vector<uint8_t> buf(size); - if (!android::base::ReadFully(fd.get(), buf.data(), size)) { - PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end; - return false; - } - blk_count += (range_end - range_start); + for (size_t i = group_range_count * 2 * t + 1; + i < std::min(group_range_count * 2 * (t + 1) + 1, ranges.size()); i += 2) { + unsigned int range_start, range_end; + bool parse_status = android::base::ParseUint(ranges[i], &range_start); + parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end); + if (!parse_status || range_start >= range_end) { + LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1]; + return false; + } + + if (lseek64(fd.get(), static_cast<off64_t>(range_start) * kBlockSize, SEEK_SET) == -1) { + PLOG(ERROR) << "lseek to " << range_start << " failed"; + return false; + } + + size_t remain = (range_end - range_start) * kBlockSize; + while (remain > 0) { + size_t to_read = std::min(remain, 1024 * kBlockSize); + if (!android::base::ReadFully(fd.get(), buf.data(), to_read)) { + PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end; + return false; + } + remain -= to_read; + } + blk_count += (range_end - range_start); + } + LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device; + return true; + }; + + threads.emplace_back(std::async(std::launch::async, thread_func)); } - LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device; - return true; + bool ret = true; + for (auto& t : threads) { + ret = t.get() && ret; + } + LOG(INFO) << "Finished reading blocks on " << dm_block_device << " with " << thread_num + << " threads."; + return ret; } -static bool verify_image(const std::string& care_map_name) { - 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 (care_map_fd.get() == -1) { - PLOG(WARNING) << "Failed to open " << care_map_name; - return true; - } - // Care map file has four lines (two lines if vendor partition is not present): - // First line has the block partition name (system/vendor). - // Second line holds all ranges of blocks to verify. - // The next two lines have the same format but for vendor partition. - std::string file_content; - if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) { - LOG(ERROR) << "Error reading care map contents to string."; - return false; - } +// 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) { + 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 (care_map_fd.get() == -1) { + PLOG(WARNING) << "Failed to open " << care_map_name; + return true; + } + // Care map file has four lines (two lines if vendor partition is not present): + // First line has the block partition name (system/vendor). + // Second line holds all ranges of blocks to verify. + // The next two lines have the same format but for vendor partition. + std::string file_content; + if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) { + LOG(ERROR) << "Error reading care map contents to string."; + return false; + } - std::vector<std::string> lines; - lines = android::base::Split(android::base::Trim(file_content), "\n"); - if (lines.size() != 2 && lines.size() != 4) { - LOG(ERROR) << "Invalid lines in care_map: found " << lines.size() - << " lines, expecting 2 or 4 lines."; - return false; - } + std::vector<std::string> lines; + lines = android::base::Split(android::base::Trim(file_content), "\n"); + if (lines.size() != 2 && lines.size() != 4) { + LOG(ERROR) << "Invalid lines in care_map: found " << lines.size() + << " lines, expecting 2 or 4 lines."; + return false; + } - for (size_t i = 0; i < lines.size(); i += 2) { - if (!read_blocks(lines[i], lines[i+1])) { - 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; + } + if (!read_blocks(lines[i], lines[i+1])) { + return false; } + } - return true; + return true; } static int reboot_device() { @@ -205,7 +254,7 @@ static int reboot_device() { while (true) pause(); } -int main(int argc, char** argv) { +int update_verifier(int argc, char** argv) { for (int i = 1; i < argc; i++) { LOG(INFO) << "Started with arg " << i << ": " << argv[i]; } @@ -224,23 +273,37 @@ int main(int argc, char** argv) { if (is_successful == BoolResult::FALSE) { // The current slot has not booted successfully. -#ifdef PRODUCT_SUPPORTS_VERITY +#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_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. LOG(WARNING) << "Found dm-verity in EIO mode."; + } else if (android::base::EqualsIgnoreCase(verity_mode, "disabled")) { + 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."; return reboot_device(); } - if (!verify_image(CARE_MAP_FILE)) { - LOG(ERROR) << "Failed to verify all blocks in care map file."; - return reboot_device(); + if (!skip_verification) { + static constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt"; + if (!verify_image(CARE_MAP_FILE)) { + LOG(ERROR) << "Failed to verify all blocks in care map file."; + return reboot_device(); + } } #else LOG(WARNING) << "dm-verity not enabled; marking without verification."; diff --git a/update_verifier/update_verifier_main.cpp b/update_verifier/update_verifier_main.cpp new file mode 100644 index 000000000..46e8bbb59 --- /dev/null +++ b/update_verifier/update_verifier_main.cpp @@ -0,0 +1,23 @@ +/* + * 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. + */ + +// See the comments in update_verifier.cpp. + +#include "update_verifier/update_verifier.h" + +int main(int argc, char** argv) { + return update_verifier(argc, argv); +} diff --git a/updater/Android.mk b/updater/Android.mk index 30b053559..cef6b963f 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -52,6 +52,7 @@ updater_common_static_libraries := \ libcrypto_utils \ libcutils \ libtune2fs \ + libbrotli \ $(tune2fs_static_libraries) # libupdater (static library) diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index c614ccc47..a0b9ad233 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -18,6 +18,7 @@ #include <errno.h> #include <dirent.h> #include <fcntl.h> +#include <inttypes.h> #include <linux/fs.h> #include <pthread.h> #include <stdarg.h> @@ -43,15 +44,17 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <applypatch/applypatch.h> +#include <brotli/decode.h> #include <openssl/sha.h> #include <private/android_filesystem_config.h> #include <ziparchive/zip_archive.h> #include "edify/expr.h" #include "error_code.h" -#include "updater/install.h" #include "ota_io.h" #include "print_sha1.h" +#include "updater/install.h" +#include "updater/rangeset.h" #include "updater/updater.h" // Set this to 0 to interpret 'erase' transfers to mean do a @@ -64,100 +67,10 @@ static constexpr const char* STASH_DIRECTORY_BASE = "/cache/recovery"; static constexpr mode_t STASH_DIRECTORY_MODE = 0700; static constexpr mode_t STASH_FILE_MODE = 0600; -struct RangeSet { - size_t count; // Limit is INT_MAX. - size_t size; - std::vector<size_t> pos; // Actual limit is INT_MAX. - - // Get the block number for the ith(starting from 0) block in the range set. - int get_block(size_t idx) const { - if (idx >= size) { - LOG(ERROR) << "index: " << idx << " is greater than range set size: " << size; - return -1; - } - for (size_t i = 0; i < pos.size(); i += 2) { - if (idx < pos[i + 1] - pos[i]) { - return pos[i] + idx; - } - idx -= (pos[i + 1] - pos[i]); - } - return -1; - } -}; - static CauseCode failure_type = kNoCause; static bool is_retry = false; static std::unordered_map<std::string, RangeSet> stash_map; -static RangeSet parse_range(const std::string& range_text) { - RangeSet rs; - - std::vector<std::string> pieces = android::base::Split(range_text, ","); - if (pieces.size() < 3) { - goto err; - } - - size_t num; - if (!android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) { - goto err; - } - - if (num == 0 || num % 2) { - goto err; // must be even - } else if (num != pieces.size() - 1) { - goto err; - } - - rs.pos.resize(num); - rs.count = num / 2; - rs.size = 0; - - for (size_t i = 0; i < num; i += 2) { - if (!android::base::ParseUint(pieces[i + 1], &rs.pos[i], static_cast<size_t>(INT_MAX))) { - goto err; - } - - if (!android::base::ParseUint(pieces[i + 2], &rs.pos[i + 1], static_cast<size_t>(INT_MAX))) { - goto err; - } - - if (rs.pos[i] >= rs.pos[i + 1]) { - goto err; // empty or negative range - } - - size_t sz = rs.pos[i + 1] - rs.pos[i]; - if (rs.size > SIZE_MAX - sz) { - goto err; // overflow - } - - rs.size += sz; - } - - return rs; - -err: - LOG(ERROR) << "failed to parse range '" << range_text << "'"; - exit(EXIT_FAILURE); -} - -static bool range_overlaps(const RangeSet& r1, const RangeSet& r2) { - for (size_t i = 0; i < r1.count; ++i) { - size_t r1_0 = r1.pos[i * 2]; - size_t r1_1 = r1.pos[i * 2 + 1]; - - for (size_t j = 0; j < r2.count; ++j) { - size_t r2_0 = r2.pos[j * 2]; - size_t r2_1 = r2.pos[j * 2 + 1]; - - if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) { - return true; - } - } - } - - return false; -} - static int read_all(int fd, uint8_t* data, size_t size) { size_t so_far = 0; while (so_far < size) { @@ -200,18 +113,17 @@ static int write_all(int fd, const std::vector<uint8_t>& buffer, size_t size) { } 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) { - return true; - } - - uint64_t args[2] = {static_cast<uint64_t>(offset), size}; - int status = ioctl(fd, BLKDISCARD, &args); - if (status == -1) { - PLOG(ERROR) << "BLKDISCARD ioctl failed"; - return false; - } + // Don't discard blocks unless the update is a retry run. + if (!is_retry) { return true; + } + + uint64_t args[2] = { static_cast<uint64_t>(offset), size }; + if (ioctl(fd, BLKDISCARD, &args) == -1) { + PLOG(ERROR) << "BLKDISCARD ioctl failed"; + return false; + } + return true; } static bool check_lseek(int fd, off64_t offset, int whence) { @@ -231,180 +143,286 @@ static void allocate(size_t size, std::vector<uint8_t>& buffer) { buffer.resize(size); } -struct RangeSinkState { - explicit RangeSinkState(RangeSet& rs) : tgt(rs) { }; - - int fd; - const RangeSet& tgt; - size_t p_block; - size_t p_remain; -}; +/** + * RangeSinkWriter reads data from the given FD, and writes them to the destination specified by the + * given RangeSet. + */ +class RangeSinkWriter { + public: + RangeSinkWriter(int fd, const RangeSet& tgt) + : fd_(fd), + tgt_(tgt), + next_range_(0), + current_range_left_(0), + bytes_written_(0) { + CHECK_NE(tgt.size(), static_cast<size_t>(0)); + }; + + bool Finished() const { + return next_range_ == tgt_.size() && current_range_left_ == 0; + } -static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { - RangeSinkState* rss = reinterpret_cast<RangeSinkState*>(token); + size_t AvailableSpace() const { + return tgt_.blocks() * BLOCKSIZE - bytes_written_; + } - if (rss->p_remain == 0) { - LOG(ERROR) << "range sink write overrun"; - return 0; + // Return number of bytes written; and 0 indicates a writing failure. + size_t Write(const uint8_t* data, size_t size) { + if (Finished()) { + LOG(ERROR) << "range sink write overrun; can't write " << size << " bytes"; + return 0; } - ssize_t written = 0; + size_t written = 0; while (size > 0) { - size_t write_now = size; + // Move to the next range as needed. + if (!SeekToOutputRange()) { + break; + } - if (rss->p_remain < write_now) { - write_now = rss->p_remain; - } + size_t write_now = size; + if (current_range_left_ < write_now) { + write_now = current_range_left_; + } - if (write_all(rss->fd, data, write_now) == -1) { - break; - } + if (write_all(fd_, data, write_now) == -1) { + break; + } - data += write_now; - size -= write_now; - - rss->p_remain -= write_now; - written += write_now; - - if (rss->p_remain == 0) { - // move to the next block - ++rss->p_block; - if (rss->p_block < rss->tgt.count) { - rss->p_remain = (rss->tgt.pos[rss->p_block * 2 + 1] - - rss->tgt.pos[rss->p_block * 2]) * BLOCKSIZE; - - off64_t offset = static_cast<off64_t>(rss->tgt.pos[rss->p_block*2]) * BLOCKSIZE; - if (!discard_blocks(rss->fd, offset, rss->p_remain)) { - break; - } - - if (!check_lseek(rss->fd, offset, SEEK_SET)) { - break; - } - - } else { - // we can't write any more; return how many bytes have - // been written so far. - break; - } - } + data += write_now; + size -= write_now; + + current_range_left_ -= write_now; + written += write_now; } + bytes_written_ += written; return written; -} + } + + size_t BytesWritten() const { + return bytes_written_; + } + + private: + // Set up the output cursor, move to next range if needed. + bool SeekToOutputRange() { + // We haven't finished the current range yet. + if (current_range_left_ != 0) { + return true; + } + // We can't write any more; let the write function return how many bytes have been written + // so far. + if (next_range_ >= tgt_.size()) { + return false; + } -// All of the data for all the 'new' transfers is contained in one -// file in the update package, concatenated together in the order in -// which transfers.list will need it. We want to stream it out of the -// archive (it's compressed) without writing it to a temp file, but we -// can't write each section until it's that transfer's turn to go. -// -// To achieve this, we expand the new data from the archive in a -// background thread, and block that threads 'receive uncompressed -// data' function until the main thread has reached a point where we -// want some new data to be written. We signal the background thread -// with the destination for the data and block the main thread, -// waiting for the background thread to complete writing that section. -// Then it signals the main thread to wake up and goes back to -// blocking waiting for a transfer. -// -// NewThreadInfo is the struct used to pass information back and forth -// between the two threads. When the main thread wants some data -// written, it sets rss to the destination location and signals the -// condition. When the background thread is done writing, it clears -// rss and signals the condition again. + const Range& range = tgt_[next_range_]; + off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE; + current_range_left_ = (range.second - range.first) * BLOCKSIZE; + next_range_++; + + if (!discard_blocks(fd_, offset, current_range_left_)) { + return false; + } + if (!check_lseek(fd_, offset, SEEK_SET)) { + return false; + } + return true; + } + + // The output file descriptor. + int fd_; + // The destination ranges for the data. + const RangeSet& tgt_; + // The next range that we should write to. + size_t next_range_; + // The number of bytes to write before moving to the next range. + size_t current_range_left_; + // Total bytes written by the writer. + size_t bytes_written_; +}; +/** + * All of the data for all the 'new' transfers is contained in one file in the update package, + * concatenated together in the order in which transfers.list will need it. We want to stream it out + * of the archive (it's compressed) without writing it to a temp file, but we can't write each + * section until it's that transfer's turn to go. + * + * To achieve this, we expand the new data from the archive in a background thread, and block that + * threads 'receive uncompressed data' function until the main thread has reached a point where we + * want some new data to be written. We signal the background thread with the destination for the + * data and block the main thread, waiting for the background thread to complete writing that + * section. Then it signals the main thread to wake up and goes back to blocking waiting for a + * transfer. + * + * NewThreadInfo is the struct used to pass information back and forth between the two threads. When + * the main thread wants some data written, it sets writer to the destination location and signals + * the condition. When the background thread is done writing, it clears writer and signals the + * condition again. + */ struct NewThreadInfo { - ZipArchiveHandle za; - ZipEntry entry; + ZipArchiveHandle za; + ZipEntry entry; + bool brotli_compressed; - RangeSinkState* rss; + std::unique_ptr<RangeSinkWriter> writer; + BrotliDecoderState* brotli_decoder_state; + bool receiver_available; - pthread_mutex_t mu; - pthread_cond_t cv; + pthread_mutex_t mu; + pthread_cond_t cv; }; static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) { - NewThreadInfo* nti = reinterpret_cast<NewThreadInfo*>(cookie); + NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); - while (size > 0) { - // Wait for nti->rss to be non-null, indicating some of this - // data is wanted. - pthread_mutex_lock(&nti->mu); - while (nti->rss == nullptr) { - pthread_cond_wait(&nti->cv, &nti->mu); - } - pthread_mutex_unlock(&nti->mu); + while (size > 0) { + // Wait for nti->writer to be non-null, indicating some of this data is wanted. + pthread_mutex_lock(&nti->mu); + while (nti->writer == nullptr) { + pthread_cond_wait(&nti->cv, &nti->mu); + } + pthread_mutex_unlock(&nti->mu); - // At this point nti->rss is set, and we own it. The main - // thread is waiting for it to disappear from nti. - ssize_t written = RangeSinkWrite(data, size, nti->rss); - data += written; - size -= written; + // At this point nti->writer is set, and we own it. The main thread is waiting for it to + // disappear from nti. + size_t write_now = std::min(size, nti->writer->AvailableSpace()); + if (nti->writer->Write(data, write_now) != write_now) { + LOG(ERROR) << "Failed to write " << write_now << " bytes."; + return false; + } - if (nti->rss->p_block == nti->rss->tgt.count) { - // we have written all the bytes desired by this rss. + data += write_now; + size -= write_now; - pthread_mutex_lock(&nti->mu); - nti->rss = nullptr; - pthread_cond_broadcast(&nti->cv); - pthread_mutex_unlock(&nti->mu); - } + if (nti->writer->Finished()) { + // We have written all the bytes desired by this writer. + + pthread_mutex_lock(&nti->mu); + nti->writer = nullptr; + pthread_cond_broadcast(&nti->cv); + pthread_mutex_unlock(&nti->mu); } + } - return true; + return true; } -static void* unzip_new_data(void* cookie) { - NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); - ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti); - return nullptr; -} +static bool receive_brotli_new_data(const uint8_t* data, size_t size, void* cookie) { + NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); -static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>& buffer, int fd) { - size_t p = 0; - uint8_t* data = buffer.data(); + while (size > 0 || BrotliDecoderHasMoreOutput(nti->brotli_decoder_state)) { + // Wait for nti->writer to be non-null, indicating some of this data is wanted. + pthread_mutex_lock(&nti->mu); + while (nti->writer == nullptr) { + pthread_cond_wait(&nti->cv, &nti->mu); + } + pthread_mutex_unlock(&nti->mu); - for (size_t i = 0; i < src.count; ++i) { - if (!check_lseek(fd, (off64_t) src.pos[i * 2] * BLOCKSIZE, SEEK_SET)) { - return -1; - } + // At this point nti->writer is set, and we own it. The main thread is waiting for it to + // disappear from nti. - size_t size = (src.pos[i * 2 + 1] - src.pos[i * 2]) * BLOCKSIZE; + size_t buffer_size = std::min<size_t>(32768, nti->writer->AvailableSpace()); + if (buffer_size == 0) { + LOG(ERROR) << "No space left in output range"; + return false; + } + uint8_t buffer[buffer_size]; + size_t available_in = size; + size_t available_out = buffer_size; + uint8_t* next_out = buffer; - if (read_all(fd, data + p, size) == -1) { - return -1; - } + // The brotli decoder will update |data|, |available_in|, |next_out| and |available_out|. + BrotliDecoderResult result = BrotliDecoderDecompressStream( + nti->brotli_decoder_state, &available_in, &data, &available_out, &next_out, nullptr); - p += size; + if (result == BROTLI_DECODER_RESULT_ERROR) { + LOG(ERROR) << "Decompression failed with " + << BrotliDecoderErrorString(BrotliDecoderGetErrorCode(nti->brotli_decoder_state)); + return false; } - return 0; + LOG(DEBUG) << "bytes to write: " << buffer_size - available_out << ", bytes consumed " + << size - available_in << ", decoder status " << result; + + size_t write_now = buffer_size - available_out; + if (nti->writer->Write(buffer, write_now) != write_now) { + LOG(ERROR) << "Failed to write " << write_now << " bytes."; + return false; + } + + // Update the remaining size. The input data ptr is already updated by brotli decoder function. + size = available_in; + + if (nti->writer->Finished()) { + // We have written all the bytes desired by this writer. + + pthread_mutex_lock(&nti->mu); + nti->writer = nullptr; + pthread_cond_broadcast(&nti->cv); + pthread_mutex_unlock(&nti->mu); + } + } + + return true; } -static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, int fd) { - const uint8_t* data = buffer.data(); +static void* unzip_new_data(void* cookie) { + NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); + if (nti->brotli_compressed) { + ProcessZipEntryContents(nti->za, &nti->entry, receive_brotli_new_data, nti); + } else { + ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti); + } + pthread_mutex_lock(&nti->mu); + nti->receiver_available = false; + if (nti->writer != nullptr) { + pthread_cond_broadcast(&nti->cv); + } + pthread_mutex_unlock(&nti->mu); + return nullptr; +} - size_t p = 0; - for (size_t i = 0; i < tgt.count; ++i) { - off64_t offset = static_cast<off64_t>(tgt.pos[i * 2]) * BLOCKSIZE; - size_t size = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * BLOCKSIZE; - if (!discard_blocks(fd, offset, size)) { - return -1; - } +static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>& buffer, int fd) { + size_t p = 0; + for (const auto& range : src) { + if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) { + return -1; + } - if (!check_lseek(fd, offset, SEEK_SET)) { - return -1; - } + size_t size = (range.second - range.first) * BLOCKSIZE; + if (read_all(fd, buffer.data() + p, size) == -1) { + return -1; + } - if (write_all(fd, data + p, size) == -1) { - return -1; - } + p += size; + } - p += size; + return 0; +} + +static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, int fd) { + size_t written = 0; + for (const auto& range : tgt) { + off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE; + size_t size = (range.second - range.first) * BLOCKSIZE; + if (!discard_blocks(fd, offset, size)) { + return -1; } - return 0; + if (!check_lseek(fd, offset, SEEK_SET)) { + return -1; + } + + if (write_all(fd, buffer.data() + written, size) == -1) { + return -1; + } + + written += size; + } + + return 0; } // Parameters for transfer list command functions @@ -463,31 +481,26 @@ static void PrintHashForCorruptedSourceBlocks(const CommandParameters& params, return; } - RangeSet src = parse_range(params.tokens[pos++]); + RangeSet src = RangeSet::Parse(params.tokens[pos++]); RangeSet locs; // If there's no stashed blocks, content in the buffer is consecutive and has the same // order as the source blocks. if (pos == params.tokens.size()) { - locs.count = 1; - locs.size = src.size; - locs.pos = { 0, src.size }; + locs = RangeSet(std::vector<Range>{ Range{ 0, src.blocks() } }); } else { // Otherwise, the next token is the offset of the source blocks in the target range. // Example: for the tokens <4,63946,63947,63948,63979> <4,6,7,8,39> <stashed_blocks>; // We want to print SHA-1 for the data in buffer[6], buffer[8], buffer[9] ... buffer[38]; // this corresponds to the 32 src blocks #63946, #63948, #63949 ... #63978. - locs = parse_range(params.tokens[pos++]); - CHECK_EQ(src.size, locs.size); - CHECK_EQ(locs.pos.size() % 2, static_cast<size_t>(0)); + locs = RangeSet::Parse(params.tokens[pos++]); + CHECK_EQ(src.blocks(), locs.blocks()); } - LOG(INFO) << "printing hash in hex for " << src.size << " source blocks"; - for (size_t i = 0; i < src.size; i++) { - int block_num = src.get_block(i); - CHECK_NE(block_num, -1); - int buffer_index = locs.get_block(i); - CHECK_NE(buffer_index, -1); + LOG(INFO) << "printing hash in hex for " << src.blocks() << " source blocks"; + for (size_t i = 0; i < src.blocks(); i++) { + size_t block_num = src.GetBlockNumber(i); + size_t buffer_index = locs.GetBlockNumber(i); CHECK_LE((buffer_index + 1) * BLOCKSIZE, buffer.size()); uint8_t digest[SHA_DIGEST_LENGTH]; @@ -503,11 +516,10 @@ static void PrintHashForCorruptedStashedBlocks(const std::string& id, const std::vector<uint8_t>& buffer, const RangeSet& src) { LOG(INFO) << "printing hash in hex for stash_id: " << id; - CHECK_EQ(src.size * BLOCKSIZE, buffer.size()); + CHECK_EQ(src.blocks() * BLOCKSIZE, buffer.size()); - for (size_t i = 0; i < src.size; i++) { - int block_num = src.get_block(i); - CHECK_NE(block_num, -1); + for (size_t i = 0; i < src.blocks(); i++) { + size_t block_num = src.GetBlockNumber(i); uint8_t digest[SHA_DIGEST_LENGTH]; SHA1(buffer.data() + i * BLOCKSIZE, BLOCKSIZE, digest); @@ -526,7 +538,7 @@ 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<uint8_t> buffer(src.size * BLOCKSIZE); + std::vector<uint8_t> buffer(src.blocks() * BLOCKSIZE); if (ReadBlocks(src, buffer, fd) == -1) { LOG(ERROR) << "failed to read source blocks for stash: " << id; return; @@ -618,85 +630,81 @@ static void DeleteStash(const std::string& base) { static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks, std::vector<uint8_t>& buffer, bool printnoent) { - // In verify mode, if source range_set was saved for the given hash, - // check contents in the source blocks first. If the check fails, - // search for the stashed files on /cache as usual. - if (!params.canwrite) { - if (stash_map.find(id) != stash_map.end()) { - const RangeSet& src = stash_map[id]; - allocate(src.size * BLOCKSIZE, buffer); - - if (ReadBlocks(src, buffer, params.fd) == -1) { - LOG(ERROR) << "failed to read source blocks in stash map."; - return -1; - } - if (VerifyBlocks(id, buffer, src.size, true) != 0) { - LOG(ERROR) << "failed to verify loaded source blocks in stash map."; - PrintHashForCorruptedStashedBlocks(id, buffer, src); - return -1; - } - return 0; - } - } - - size_t blockcount = 0; + // 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) { + if (stash_map.find(id) != stash_map.end()) { + const RangeSet& src = stash_map[id]; + allocate(src.blocks() * BLOCKSIZE, buffer); - if (!blocks) { - blocks = &blockcount; + if (ReadBlocks(src, buffer, params.fd) == -1) { + LOG(ERROR) << "failed to read source blocks in stash map."; + return -1; + } + if (VerifyBlocks(id, buffer, src.blocks(), true) != 0) { + LOG(ERROR) << "failed to verify loaded source blocks in stash map."; + PrintHashForCorruptedStashedBlocks(id, buffer, src); + return -1; + } + return 0; } + } - std::string fn = GetStashFileName(params.stashbase, id, ""); + size_t blockcount = 0; + if (!blocks) { + blocks = &blockcount; + } - struct stat sb; - int res = stat(fn.c_str(), &sb); + std::string fn = GetStashFileName(params.stashbase, id, ""); - if (res == -1) { - if (errno != ENOENT || printnoent) { - PLOG(ERROR) << "stat \"" << fn << "\" failed"; - PrintHashForMissingStashedBlocks(id, params.fd); - } - return -1; + struct stat sb; + if (stat(fn.c_str(), &sb) == -1) { + if (errno != ENOENT || printnoent) { + PLOG(ERROR) << "stat \"" << fn << "\" failed"; + PrintHashForMissingStashedBlocks(id, params.fd); } + return -1; + } - LOG(INFO) << " loading " << fn; + LOG(INFO) << " loading " << fn; - if ((sb.st_size % BLOCKSIZE) != 0) { - LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE; - return -1; - } + if ((sb.st_size % BLOCKSIZE) != 0) { + LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE; + return -1; + } - android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY))); - if (fd == -1) { - PLOG(ERROR) << "open \"" << fn << "\" failed"; - return -1; - } + android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY))); + if (fd == -1) { + PLOG(ERROR) << "open \"" << fn << "\" failed"; + return -1; + } - allocate(sb.st_size, buffer); + allocate(sb.st_size, buffer); - if (read_all(fd, buffer, sb.st_size) == -1) { - return -1; - } + if (read_all(fd, buffer, sb.st_size) == -1) { + return -1; + } - *blocks = sb.st_size / BLOCKSIZE; + *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); - } - DeleteFile(fn); - return -1; + 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); } + DeleteFile(fn); + return -1; + } - return 0; + return 0; } static int WriteStash(const std::string& base, const std::string& id, int blocks, - std::vector<uint8_t>& buffer, bool checkspace, bool *exists) { + std::vector<uint8_t>& buffer, bool checkspace, bool* exists) { if (base.empty()) { return -1; } @@ -866,113 +874,96 @@ static int FreeStash(const std::string& base, const std::string& id) { return 0; } +// Source contains packed data, which we want to move to the locations given in locs in the dest +// buffer. source and dest may be the same buffer. static void MoveRange(std::vector<uint8_t>& dest, const RangeSet& locs, - const std::vector<uint8_t>& source) { - // source contains packed data, which we want to move to the - // locations given in locs in the dest buffer. source and dest - // may be the same buffer. - - const uint8_t* from = source.data(); - uint8_t* to = dest.data(); - size_t start = locs.size; - for (int i = locs.count-1; i >= 0; --i) { - size_t blocks = locs.pos[i*2+1] - locs.pos[i*2]; - start -= blocks; - memmove(to + (locs.pos[i*2] * BLOCKSIZE), from + (start * BLOCKSIZE), - blocks * BLOCKSIZE); - } + const std::vector<uint8_t>& source) { + 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 * BLOCKSIZE), from + (start * BLOCKSIZE), blocks * BLOCKSIZE); + } } -// Do a source/target load for move/bsdiff/imgdiff in version 2. -// We expect to parse the remainder of the parameter tokens as one of: -// -// <tgt_range> <src_block_count> <src_range> -// (loads data from source image only) -// -// <tgt_range> <src_block_count> - <[stash_id:stash_range] ...> -// (loads data from stashes only) -// -// <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> -// (loads data from both source image and stashes) -// -// On return, params.buffer is filled with the loaded source data (rearranged and combined with -// stashed data as necessary). buffer may be reallocated if needed to accommodate the source data. -// *tgt is the target RangeSet. Any stashes required are loaded using LoadStash. - -static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, - bool* overlap) { - - // At least it needs to provide three parameters: <tgt_range>, - // <src_block_count> and "-"/<src_range>. - if (params.cpos + 2 >= params.tokens.size()) { - LOG(ERROR) << "invalid parameters"; - return -1; - } - - // <tgt_range> - tgt = parse_range(params.tokens[params.cpos++]); - - // <src_block_count> - const std::string& token = params.tokens[params.cpos++]; - if (!android::base::ParseUint(token.c_str(), &src_blocks)) { - LOG(ERROR) << "invalid src_block_count \"" << token << "\""; - return -1; - } - - allocate(src_blocks * BLOCKSIZE, params.buffer); - - // "-" or <src_range> [<src_loc>] - if (params.tokens[params.cpos] == "-") { - // no source ranges, only stashes - params.cpos++; - } else { - RangeSet src = parse_range(params.tokens[params.cpos++]); - int res = ReadBlocks(src, params.buffer, params.fd); - - if (overlap) { - *overlap = range_overlaps(src, tgt); - } +/** + * We expect to parse the remainder of the parameter tokens as one of: + * + * <src_block_count> <src_range> + * (loads data from source image only) + * + * <src_block_count> - <[stash_id:stash_range] ...> + * (loads data from stashes only) + * + * <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> + * (loads data from both source image and stashes) + * + * On return, params.buffer is filled with the loaded source data (rearranged and combined with + * stashed data as necessary). buffer may be reallocated if needed to accommodate the source data. + * tgt is the target RangeSet for detecting overlaps. Any stashes required are loaded using + * LoadStash. + */ +static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size_t* src_blocks, + bool* overlap) { + CHECK(src_blocks != nullptr); + CHECK(overlap != nullptr); + + // <src_block_count> + const std::string& token = params.tokens[params.cpos++]; + if (!android::base::ParseUint(token, src_blocks)) { + LOG(ERROR) << "invalid src_block_count \"" << token << "\""; + return -1; + } - if (res == -1) { - return -1; - } + allocate(*src_blocks * BLOCKSIZE, params.buffer); - if (params.cpos >= params.tokens.size()) { - // no stashes, only source range - return 0; - } + // "-" or <src_range> [<src_loc>] + if (params.tokens[params.cpos] == "-") { + // no source ranges, only stashes + params.cpos++; + } else { + RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]); + *overlap = src.Overlaps(tgt); - RangeSet locs = parse_range(params.tokens[params.cpos++]); - MoveRange(params.buffer, locs, params.buffer); + if (ReadBlocks(src, params.buffer, params.fd) == -1) { + return -1; } - // <[stash_id:stash_range]> - while (params.cpos < params.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<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":"); - if (tokens.size() != 2) { - LOG(ERROR) << "invalid parameter"; - return -1; - } - - std::vector<uint8_t> stash; - int res = LoadStash(params, tokens[0], false, nullptr, stash, true); + if (params.cpos >= params.tokens.size()) { + // no stashes, only source range + return 0; + } - if (res == -1) { - // These source blocks will fail verification if used later, but we - // will let the caller decide if this is a fatal failure - LOG(ERROR) << "failed to load stash " << tokens[0]; - continue; - } + RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]); + MoveRange(params.buffer, locs, params.buffer); + } - RangeSet locs = parse_range(tokens[1]); + // <[stash_id:stash_range]> + while (params.cpos < params.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<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":"); + if (tokens.size() != 2) { + LOG(ERROR) << "invalid parameter"; + return -1; + } - MoveRange(params.buffer, locs, stash); + std::vector<uint8_t> stash; + if (LoadStash(params, tokens[0], false, nullptr, 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]; + continue; } - return 0; + RangeSet locs = RangeSet::Parse(tokens[1]); + MoveRange(params.buffer, locs, stash); + } + + return 0; } /** @@ -989,9 +980,8 @@ static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& * <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> * (loads data from both source image and stashes) * - * Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which tells the function - * whether to expect separate source and targe block hashes, or if they are both the same and only - * one hash should be expected, and 'isunresumable', which receives a non-zero value if block + * 'onehash' tells whether to expect separate source and targe block hashes, or if they are both the + * same and only one hash should be expected. params.isunresumable will be set to true if block * verification fails in a way that the update cannot be resumed anymore. * * If the function is unable to load the necessary blocks or their contents don't match the hashes, @@ -1002,87 +992,100 @@ static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& * * 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) { - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing source hash"; - return -1; - } +static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* src_blocks, + bool onehash, bool* overlap) { + CHECK(src_blocks != nullptr); + CHECK(overlap != nullptr); - std::string srchash = params.tokens[params.cpos++]; - std::string tgthash; + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing source hash"; + return -1; + } - if (onehash) { - tgthash = srchash; - } else { - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target hash"; - return -1; - } - tgthash = params.tokens[params.cpos++]; - } + std::string srchash = params.tokens[params.cpos++]; + std::string tgthash; - if (LoadSrcTgtVersion2(params, tgt, src_blocks, &overlap) == -1) { - return -1; + if (onehash) { + tgthash = srchash; + } else { + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing target hash"; + return -1; } + tgthash = params.tokens[params.cpos++]; + } - std::vector<uint8_t> tgtbuffer(tgt.size * BLOCKSIZE); + // At least it needs to provide three parameters: <tgt_range>, <src_block_count> and + // "-"/<src_range>. + if (params.cpos + 2 >= params.tokens.size()) { + LOG(ERROR) << "invalid parameters"; + return -1; + } - if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) { - return -1; - } + // <tgt_range> + tgt = RangeSet::Parse(params.tokens[params.cpos++]); - if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) { - // Target blocks already have expected content, command should be skipped. - return 1; - } + std::vector<uint8_t> tgtbuffer(tgt.blocks() * BLOCKSIZE); + if (ReadBlocks(tgt, tgtbuffer, params.fd) == -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) { - LOG(INFO) << "stashing " << src_blocks << " overlapping blocks to " << srchash; + // Return now if target blocks already have expected content. + if (VerifyBlocks(tgthash, tgtbuffer, tgt.blocks(), false) == 0) { + return 1; + } - bool stash_exists = false; - if (WriteStash(params.stashbase, srchash, src_blocks, params.buffer, true, - &stash_exists) != 0) { - LOG(ERROR) << "failed to stash overlapping source blocks"; - return -1; - } + // Load source blocks. + if (LoadSourceBlocks(params, tgt, src_blocks, overlap) == -1) { + return -1; + } - params.stashed += src_blocks; - // Can be deleted when the write has completed. - if (!stash_exists) { - params.freestash = srchash; - } - } + 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) { + LOG(INFO) << "stashing " << *src_blocks << " overlapping blocks to " << srchash; + + bool stash_exists = false; + if (WriteStash(params.stashbase, srchash, *src_blocks, params.buffer, true, + &stash_exists) != 0) { + LOG(ERROR) << "failed to stash overlapping source blocks"; + return -1; + } - // Source blocks have expected content, command can proceed. - return 0; + params.stashed += *src_blocks; + // Can be deleted when the write has completed. + if (!stash_exists) { + params.freestash = srchash; + } } - if (overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) { - // Overlapping source blocks were previously stashed, command can proceed. - // We are recovering from an interrupted command, so we don't know if the - // stash can safely be deleted after this command. - return 0; - } + // Source blocks have expected content, command can proceed. + return 0; + } - // Valid source data not available, update cannot be resumed. - LOG(ERROR) << "partition has unexpected contents"; - PrintHashForCorruptedSourceBlocks(params, params.buffer); + if (*overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) { + // Overlapping source blocks were previously stashed, command can proceed. We are recovering + // from an interrupted command, so we don't know if the stash can safely be deleted after this + // command. + return 0; + } - params.isunresumable = true; + // Valid source data not available, update cannot be resumed. + LOG(ERROR) << "partition has unexpected contents"; + PrintHashForCorruptedSourceBlocks(params, params.buffer); - return -1; + params.isunresumable = true; + + return -1; } 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, &overlap); if (status == -1) { LOG(ERROR) << "failed to read blocks for move"; @@ -1112,7 +1115,7 @@ static int PerformCommandMove(CommandParameters& params) { params.freestash.clear(); } - params.written += tgt.size; + params.written += tgt.blocks(); return 0; } @@ -1132,13 +1135,13 @@ static int PerformCommandStash(CommandParameters& params) { return 0; } - RangeSet src = parse_range(params.tokens[params.cpos++]); + RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]); - allocate(src.size * BLOCKSIZE, params.buffer); + allocate(src.blocks() * BLOCKSIZE, params.buffer); if (ReadBlocks(src, params.buffer, params.fd) == -1) { return -1; } - blocks = src.size; + blocks = src.blocks(); stash_map[id] = src; if (VerifyBlocks(id, params.buffer, blocks, true) != 0) { @@ -1177,220 +1180,203 @@ static int PerformCommandFree(CommandParameters& params) { } static int PerformCommandZero(CommandParameters& params) { + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing target blocks for zero"; + return -1; + } - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for zero"; - return -1; - } + RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - RangeSet tgt = parse_range(params.tokens[params.cpos++]); + LOG(INFO) << " zeroing " << tgt.blocks() << " blocks"; - LOG(INFO) << " zeroing " << tgt.size << " blocks"; + allocate(BLOCKSIZE, params.buffer); + memset(params.buffer.data(), 0, BLOCKSIZE); - allocate(BLOCKSIZE, params.buffer); - memset(params.buffer.data(), 0, BLOCKSIZE); + if (params.canwrite) { + for (const auto& range : tgt) { + off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE; + size_t size = (range.second - range.first) * BLOCKSIZE; + if (!discard_blocks(params.fd, offset, size)) { + return -1; + } - if (params.canwrite) { - for (size_t i = 0; i < tgt.count; ++i) { - off64_t offset = static_cast<off64_t>(tgt.pos[i * 2]) * BLOCKSIZE; - size_t size = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * BLOCKSIZE; - if (!discard_blocks(params.fd, offset, size)) { - return -1; - } - - if (!check_lseek(params.fd, offset, SEEK_SET)) { - return -1; - } - - for (size_t j = tgt.pos[i * 2]; j < tgt.pos[i * 2 + 1]; ++j) { - if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) { - return -1; - } - } + if (!check_lseek(params.fd, offset, SEEK_SET)) { + return -1; + } + + for (size_t j = range.first; j < range.second; ++j) { + if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) { + return -1; } + } } + } - if (params.cmdname[0] == 'z') { - // Update only for the zero command, as the erase command will call - // this if DEBUG_ERASE is defined. - params.written += tgt.size; - } + if (params.cmdname[0] == 'z') { + // Update only for the zero command, as the erase command will call + // this if DEBUG_ERASE is defined. + params.written += tgt.blocks(); + } - return 0; + return 0; } static int PerformCommandNew(CommandParameters& params) { + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing target blocks for new"; + return -1; + } - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for new"; - return -1; - } - - RangeSet tgt = parse_range(params.tokens[params.cpos++]); - - if (params.canwrite) { - LOG(INFO) << " writing " << tgt.size << " blocks of new data"; - - RangeSinkState rss(tgt); - rss.fd = params.fd; - rss.p_block = 0; - rss.p_remain = (tgt.pos[1] - tgt.pos[0]) * BLOCKSIZE; - - off64_t offset = static_cast<off64_t>(tgt.pos[0]) * BLOCKSIZE; - if (!discard_blocks(params.fd, offset, tgt.size * BLOCKSIZE)) { - return -1; - } + RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - if (!check_lseek(params.fd, offset, SEEK_SET)) { - return -1; - } - - pthread_mutex_lock(¶ms.nti.mu); - params.nti.rss = &rss; - pthread_cond_broadcast(¶ms.nti.cv); + if (params.canwrite) { + LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data"; - while (params.nti.rss) { - pthread_cond_wait(¶ms.nti.cv, ¶ms.nti.mu); - } + pthread_mutex_lock(¶ms.nti.mu); + params.nti.writer = std::make_unique<RangeSinkWriter>(params.fd, tgt); + pthread_cond_broadcast(¶ms.nti.cv); + while (params.nti.writer != nullptr) { + if (!params.nti.receiver_available) { + LOG(ERROR) << "missing " << (tgt.blocks() * BLOCKSIZE - params.nti.writer->BytesWritten()) + << " bytes of new data"; pthread_mutex_unlock(¶ms.nti.mu); + return -1; + } + pthread_cond_wait(¶ms.nti.cv, ¶ms.nti.mu); } - params.written += tgt.size; + pthread_mutex_unlock(¶ms.nti.mu); + } - return 0; + params.written += tgt.blocks(); + + return 0; } static int PerformCommandDiff(CommandParameters& params) { + // <offset> <length> + if (params.cpos + 1 >= params.tokens.size()) { + LOG(ERROR) << "missing patch offset or length for " << params.cmdname; + return -1; + } - // <offset> <length> - if (params.cpos + 1 >= params.tokens.size()) { - LOG(ERROR) << "missing patch offset or length for " << params.cmdname; - return -1; - } + size_t offset; + if (!android::base::ParseUint(params.tokens[params.cpos++], &offset)) { + LOG(ERROR) << "invalid patch offset"; + return -1; + } - size_t offset; - if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &offset)) { - LOG(ERROR) << "invalid patch offset"; - return -1; - } + size_t len; + if (!android::base::ParseUint(params.tokens[params.cpos++], &len)) { + LOG(ERROR) << "invalid patch len"; + return -1; + } - size_t len; - if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &len)) { - LOG(ERROR) << "invalid patch len"; - return -1; - } + RangeSet tgt; + size_t blocks = 0; + bool overlap = false; + int status = LoadSrcTgtVersion3(params, tgt, &blocks, false, &overlap); - RangeSet tgt; - size_t blocks = 0; - bool overlap = false; - int status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap); + if (status == -1) { + LOG(ERROR) << "failed to read blocks for diff"; + return -1; + } - if (status == -1) { - LOG(ERROR) << "failed to read blocks for diff"; - return -1; - } + if (status == 0) { + params.foundwrites = true; + } else if (params.foundwrites) { + LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; + } + if (params.canwrite) { if (status == 0) { - params.foundwrites = true; - } else if (params.foundwrites) { - LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; - } - - if (params.canwrite) { - if (status == 0) { - LOG(INFO) << "patching " << blocks << " blocks to " << tgt.size; - Value patch_value(VAL_BLOB, - std::string(reinterpret_cast<const char*>(params.patch_start + offset), len)); - RangeSinkState rss(tgt); - rss.fd = params.fd; - rss.p_block = 0; - rss.p_remain = (tgt.pos[1] - tgt.pos[0]) * BLOCKSIZE; - - off64_t offset = static_cast<off64_t>(tgt.pos[0]) * BLOCKSIZE; - if (!discard_blocks(params.fd, offset, rss.p_remain)) { - return -1; - } - - if (!check_lseek(params.fd, offset, SEEK_SET)) { - return -1; - } - - if (params.cmdname[0] == 'i') { // imgdiff - if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, - &RangeSinkWrite, &rss, nullptr, nullptr) != 0) { - LOG(ERROR) << "Failed to apply image patch."; - return -1; - } - } else { - if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, - 0, &RangeSinkWrite, &rss, nullptr) != 0) { - LOG(ERROR) << "Failed to apply bsdiff patch."; - return -1; - } - } - - // We expect the output of the patcher to fill the tgt ranges exactly. - if (rss.p_block != tgt.count || rss.p_remain != 0) { - LOG(ERROR) << "range sink underrun?"; - } - } else { - LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.size - << " [" << params.cmdline << "]"; + LOG(INFO) << "patching " << blocks << " blocks to " << tgt.blocks(); + Value patch_value( + VAL_BLOB, std::string(reinterpret_cast<const char*>(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) { + LOG(ERROR) << "Failed to apply image patch."; + failure_type = kPatchApplicationFailure; + return -1; } - } + } else { + if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, 0, + std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1, + std::placeholders::_2), + nullptr) != 0) { + LOG(ERROR) << "Failed to apply bsdiff patch."; + failure_type = kPatchApplicationFailure; + return -1; + } + } - if (!params.freestash.empty()) { - FreeStash(params.stashbase, params.freestash); - params.freestash.clear(); + // We expect the output of the patcher to fill the tgt ranges exactly. + if (!writer.Finished()) { + LOG(ERROR) << "range sink underrun?"; + } + } else { + LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " [" + << params.cmdline << "]"; } + } - params.written += tgt.size; + if (!params.freestash.empty()) { + FreeStash(params.stashbase, params.freestash); + params.freestash.clear(); + } - return 0; + params.written += tgt.blocks(); + + return 0; } static int PerformCommandErase(CommandParameters& params) { - if (DEBUG_ERASE) { - return PerformCommandZero(params); - } + if (DEBUG_ERASE) { + return PerformCommandZero(params); + } - struct stat sb; - if (fstat(params.fd, &sb) == -1) { - PLOG(ERROR) << "failed to fstat device to erase"; - return -1; - } + struct stat sb; + if (fstat(params.fd, &sb) == -1) { + PLOG(ERROR) << "failed to fstat device to erase"; + return -1; + } - if (!S_ISBLK(sb.st_mode)) { - LOG(ERROR) << "not a block device; skipping erase"; - return -1; - } + if (!S_ISBLK(sb.st_mode)) { + LOG(ERROR) << "not a block device; skipping erase"; + return -1; + } - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for erase"; - return -1; - } + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing target blocks for erase"; + return -1; + } - RangeSet tgt = parse_range(params.tokens[params.cpos++]); + RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - if (params.canwrite) { - LOG(INFO) << " erasing " << tgt.size << " blocks"; - - for (size_t i = 0; i < tgt.count; ++i) { - uint64_t blocks[2]; - // offset in bytes - blocks[0] = tgt.pos[i * 2] * (uint64_t) BLOCKSIZE; - // length in bytes - blocks[1] = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * (uint64_t) BLOCKSIZE; - - if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) { - PLOG(ERROR) << "BLKDISCARD ioctl failed"; - return -1; - } - } + 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<uint64_t>(BLOCKSIZE); + // length in bytes + blocks[1] = (range.second - range.first) * static_cast<uint64_t>(BLOCKSIZE); + + if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) { + PLOG(ERROR) << "BLKDISCARD ioctl failed"; + return -1; + } } + } - return 0; + return 0; } // Definitions for transfer list command functions @@ -1429,10 +1415,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, return nullptr; } - const Value* blockdev_filename = args[0].get(); - const Value* transfer_list_value = args[1].get(); - const Value* new_data_fn = args[2].get(); - const Value* patch_data_fn = args[3].get(); + const std::unique_ptr<Value>& blockdev_filename = args[0]; + const std::unique_ptr<Value>& transfer_list_value = args[1]; + const std::unique_ptr<Value>& new_data_fn = args[2]; + const std::unique_ptr<Value>& patch_data_fn = args[3]; if (blockdev_filename->type != VAL_STRING) { ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); @@ -1487,6 +1473,12 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, 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); @@ -1628,6 +1620,10 @@ pbiudone: } // params.fd will be automatically closed because it's a unique_fd. + if (params.nti.brotli_decoder_state != nullptr) { + BrotliDecoderDestroyInstance(params.nti.brotli_decoder_state); + } + // Only delete the stash if the update cannot be resumed, or it's a verification run and we // created the stash. if (params.isunresumable || (!params.canwrite && params.createdstash)) { @@ -1722,64 +1718,62 @@ Value* BlockImageUpdateFn(const char* name, State* state, } Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() != 2) { - ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu", - argv.size()); - return StringValue(""); - } + if (argv.size() != 2) { + ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu", argv.size()); + return StringValue(""); + } - std::vector<std::unique_ptr<Value>> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } - const Value* blockdev_filename = args[0].get(); - const Value* ranges = args[1].get(); + const std::unique_ptr<Value>& blockdev_filename = args[0]; + const std::unique_ptr<Value>& ranges = args[1]; - if (blockdev_filename->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", - name); - return StringValue(""); - } - if (ranges->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); - return StringValue(""); - } + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); + return StringValue(""); + } + if (ranges->type != VAL_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)); - if (fd == -1) { - ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", - blockdev_filename->data.c_str(), strerror(errno)); - return StringValue(""); - } + android::base::unique_fd fd(ota_open(blockdev_filename->data.c_str(), O_RDWR)); + if (fd == -1) { + ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", blockdev_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - RangeSet rs = parse_range(ranges->data); + RangeSet rs = RangeSet::Parse(ranges->data); - SHA_CTX ctx; - SHA1_Init(&ctx); + SHA_CTX ctx; + SHA1_Init(&ctx); - std::vector<uint8_t> buffer(BLOCKSIZE); - for (size_t i = 0; i < rs.count; ++i) { - if (!check_lseek(fd, (off64_t)rs.pos[i*2] * BLOCKSIZE, SEEK_SET)) { - ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", - blockdev_filename->data.c_str(), strerror(errno)); - return StringValue(""); - } + std::vector<uint8_t> buffer(BLOCKSIZE); + for (const auto& range : rs) { + if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) { + ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - for (size_t j = rs.pos[i*2]; j < rs.pos[i*2+1]; ++j) { - if (read_all(fd, buffer, BLOCKSIZE) == -1) { - ErrorAbort(state, kFreadFailure, "failed to read %s: %s", - blockdev_filename->data.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(), + strerror(errno)); + return StringValue(""); + } - SHA1_Update(&ctx, buffer.data(), BLOCKSIZE); - } + SHA1_Update(&ctx, buffer.data(), BLOCKSIZE); } - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1_Final(digest, &ctx); + } + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1_Final(digest, &ctx); - return StringValue(print_sha1(digest)); + return StringValue(print_sha1(digest)); } // This function checks if a device has been remounted R/W prior to an incremental @@ -1789,145 +1783,140 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique Value* CheckFirstBlockFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() != 1) { - ErrorAbort(state, kArgsParsingFailure, "check_first_block expects 1 argument, got %zu", - argv.size()); - return StringValue(""); - } + if (argv.size() != 1) { + ErrorAbort(state, kArgsParsingFailure, "check_first_block expects 1 argument, got %zu", + argv.size()); + return StringValue(""); + } - std::vector<std::unique_ptr<Value>> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } - const Value* arg_filename = args[0].get(); + const std::unique_ptr<Value>& arg_filename = args[0]; - if (arg_filename->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); - return StringValue(""); - } + if (arg_filename->type != VAL_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)); - if (fd == -1) { - ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data.c_str(), - strerror(errno)); - return StringValue(""); - } + android::base::unique_fd fd(ota_open(arg_filename->data.c_str(), O_RDONLY)); + if (fd == -1) { + ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - RangeSet blk0 {1 /*count*/, 1/*size*/, std::vector<size_t> {0, 1}/*position*/}; - std::vector<uint8_t> block0_buffer(BLOCKSIZE); + RangeSet blk0(std::vector<Range>{ Range{ 0, 1 } }); + std::vector<uint8_t> block0_buffer(BLOCKSIZE); - if (ReadBlocks(blk0, block0_buffer, fd) == -1) { - ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(), - strerror(errno)); - return StringValue(""); - } + if (ReadBlocks(blk0, block0_buffer, fd) == -1) { + ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout - // Super block starts from block 0, offset 0x400 - // 0x2C: len32 Mount time - // 0x30: len32 Write time - // 0x34: len16 Number of mounts since the last fsck - // 0x38: len16 Magic signature 0xEF53 + // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout + // Super block starts from block 0, offset 0x400 + // 0x2C: len32 Mount time + // 0x30: len32 Write time + // 0x34: len16 Number of mounts since the last fsck + // 0x38: len16 Magic signature 0xEF53 - time_t mount_time = *reinterpret_cast<uint32_t*>(&block0_buffer[0x400+0x2C]); - uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400+0x34]); + time_t mount_time = *reinterpret_cast<uint32_t*>(&block0_buffer[0x400 + 0x2C]); + uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]); - if (mount_count > 0) { - uiPrintf(state, "Device was remounted R/W %d times\n", mount_count); - uiPrintf(state, "Last remount happened on %s", ctime(&mount_time)); - } + 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)); + } - return StringValue("t"); + return StringValue("t"); } - Value* BlockImageRecoverFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() != 2) { - ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu", - argv.size()); - return StringValue(""); - } + if (argv.size() != 2) { + ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu", + argv.size()); + return StringValue(""); + } - std::vector<std::unique_ptr<Value>> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } - const Value* filename = args[0].get(); - const Value* ranges = args[1].get(); + const std::unique_ptr<Value>& filename = args[0]; + const std::unique_ptr<Value>& ranges = args[1]; - if (filename->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); - return StringValue(""); - } - if (ranges->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); - return StringValue(""); - } + if (filename->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); + return StringValue(""); + } + if (ranges->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); + return StringValue(""); + } - // Output notice to log when recover is attempted - LOG(INFO) << filename->data << " image corrupted, attempting to recover..."; + // Output notice to log when recover is attempted + LOG(INFO) << filename->data << " image corrupted, attempting to recover..."; - // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read - fec::io fh(filename->data.c_str(), O_RDWR); + // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read + fec::io fh(filename->data, O_RDWR); - if (!fh) { - ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(), - strerror(errno)); - return StringValue(""); - } + if (!fh) { + ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - if (!fh.has_ecc() || !fh.has_verity()) { - ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors"); - return StringValue(""); - } + if (!fh.has_ecc() || !fh.has_verity()) { + ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors"); + return StringValue(""); + } - fec_status status; + fec_status status; + if (!fh.get_status(status)) { + ErrorAbort(state, kLibfecFailure, "failed to read FEC status"); + return StringValue(""); + } - if (!fh.get_status(status)) { - ErrorAbort(state, kLibfecFailure, "failed to read FEC status"); + uint8_t buffer[BLOCKSIZE]; + for (const auto& range : RangeSet::Parse(ranges->data)) { + for (size_t j = range.first; j < range.second; ++j) { + // Stay within the data area, libfec validates and corrects metadata + if (status.data_size <= static_cast<uint64_t>(j) * BLOCKSIZE) { + continue; + } + + if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) { + ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s", + filename->data.c_str(), j, strerror(errno)); return StringValue(""); - } + } - RangeSet rs = parse_range(ranges->data); - - uint8_t buffer[BLOCKSIZE]; - - for (size_t i = 0; i < rs.count; ++i) { - for (size_t j = rs.pos[i * 2]; j < rs.pos[i * 2 + 1]; ++j) { - // Stay within the data area, libfec validates and corrects metadata - if (status.data_size <= (uint64_t)j * BLOCKSIZE) { - continue; - } - - if (fh.pread(buffer, BLOCKSIZE, (off64_t)j * BLOCKSIZE) != BLOCKSIZE) { - ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s", - filename->data.c_str(), j, strerror(errno)); - return StringValue(""); - } - - // If we want to be able to recover from a situation where rewriting a corrected - // block doesn't guarantee the same data will be returned when re-read later, we - // can save a copy of corrected blocks to /cache. Note: - // - // 1. Maximum space required from /cache is the same as the maximum number of - // corrupted blocks we can correct. For RS(255, 253) and a 2 GiB partition, - // this would be ~16 MiB, for example. - // - // 2. To find out if this block was corrupted, call fec_get_status after each - // read and check if the errors field value has increased. - } + // If we want to be able to recover from a situation where rewriting a corrected + // block doesn't guarantee the same data will be returned when re-read later, we + // can save a copy of corrected blocks to /cache. Note: + // + // 1. Maximum space required from /cache is the same as the maximum number of + // corrupted blocks we can correct. For RS(255, 253) and a 2 GiB partition, + // this would be ~16 MiB, for example. + // + // 2. To find out if this block was corrupted, call fec_get_status after each + // read and check if the errors field value has increased. } - LOG(INFO) << "..." << filename->data << " image recovered successfully."; - return StringValue("t"); + } + LOG(INFO) << "..." << filename->data << " image recovered successfully."; + return StringValue("t"); } void RegisterBlockImageFunctions() { - RegisterFunction("block_image_verify", BlockImageVerifyFn); - RegisterFunction("block_image_update", BlockImageUpdateFn); - RegisterFunction("block_image_recover", BlockImageRecoverFn); - RegisterFunction("check_first_block", CheckFirstBlockFn); - RegisterFunction("range_sha1", RangeSha1Fn); + RegisterFunction("block_image_verify", BlockImageVerifyFn); + RegisterFunction("block_image_update", BlockImageUpdateFn); + RegisterFunction("block_image_recover", BlockImageRecoverFn); + RegisterFunction("check_first_block", CheckFirstBlockFn); + RegisterFunction("range_sha1", RangeSha1Fn); } diff --git a/updater/include/updater/rangeset.h b/updater/include/updater/rangeset.h new file mode 100644 index 000000000..fad038043 --- /dev/null +++ b/updater/include/updater/rangeset.h @@ -0,0 +1,164 @@ +/* + * 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. + */ + +#pragma once + +#include <stddef.h> + +#include <string> +#include <utility> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/strings.h> + +using Range = std::pair<size_t, size_t>; + +class RangeSet { + public: + RangeSet() : blocks_(0) {} + + explicit RangeSet(std::vector<Range>&& pairs) { + CHECK_NE(pairs.size(), static_cast<size_t>(0)) << "Invalid number of tokens"; + + // Sanity check the input. + size_t result = 0; + for (const auto& range : pairs) { + CHECK_LT(range.first, range.second) + << "Empty or negative range: " << range.first << ", " << range.second; + size_t sz = range.second - range.first; + CHECK_LE(result, SIZE_MAX - sz) << "RangeSet size overflow"; + result += sz; + } + + ranges_ = pairs; + blocks_ = result; + } + + static RangeSet Parse(const std::string& range_text) { + std::vector<std::string> pieces = android::base::Split(range_text, ","); + CHECK_GE(pieces.size(), static_cast<size_t>(3)) << "Invalid range text: " << range_text; + + size_t num; + CHECK(android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) + << "Failed to parse the number of tokens: " << range_text; + + CHECK_NE(num, static_cast<size_t>(0)) << "Invalid number of tokens: " << range_text; + CHECK_EQ(num % 2, static_cast<size_t>(0)) << "Number of tokens must be even: " << range_text; + CHECK_EQ(num, pieces.size() - 1) << "Mismatching number of tokens: " << range_text; + + std::vector<Range> pairs; + for (size_t i = 0; i < num; i += 2) { + size_t first; + CHECK(android::base::ParseUint(pieces[i + 1], &first, static_cast<size_t>(INT_MAX))); + size_t second; + CHECK(android::base::ParseUint(pieces[i + 2], &second, static_cast<size_t>(INT_MAX))); + + pairs.emplace_back(first, second); + } + + return RangeSet(std::move(pairs)); + } + + // Get the block number for the i-th (starting from 0) block in the RangeSet. + size_t 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; + } + idx -= (range.second - range.first); + } + + CHECK(false) << "Failed to find block number for index " << idx; + return 0; // Unreachable, but to make compiler happy. + } + + // 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 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)) { + return true; + } + } + } + return false; + } + + // size() gives the number of Range's in this RangeSet. + size_t size() const { + return ranges_.size(); + } + + // blocks() gives the number of all blocks in this RangeSet. + size_t blocks() const { + return blocks_; + } + + // We provide const iterators only. + std::vector<Range>::const_iterator cbegin() const { + return ranges_.cbegin(); + } + + std::vector<Range>::const_iterator cend() const { + return ranges_.cend(); + } + + // Need to provide begin()/end() since range-based loop expects begin()/end(). + std::vector<Range>::const_iterator begin() const { + return ranges_.cbegin(); + } + + std::vector<Range>::const_iterator end() const { + return ranges_.cend(); + } + + // Reverse const iterators for MoveRange(). + std::vector<Range>::const_reverse_iterator crbegin() const { + return ranges_.crbegin(); + } + + std::vector<Range>::const_reverse_iterator crend() const { + return ranges_.crend(); + } + + const Range& operator[](size_t i) const { + return ranges_[i]; + } + + bool operator==(const RangeSet& other) const { + // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5". + return (ranges_ == other.ranges_); + } + + bool operator!=(const RangeSet& other) const { + return ranges_ != other.ranges_; + } + + private: + // Actual limit for each value and the total number are both INT_MAX. + std::vector<Range> ranges_; + size_t blocks_; +}; diff --git a/updater/install.cpp b/updater/install.cpp index 3adb37f95..0d473fce4 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -74,7 +74,6 @@ #endif #include "otautil/DirUtil.h" -#include "otautil/ZipUtil.h" #include "print_sha1.h" #include "tune2fs.h" #include "updater/updater.h" @@ -194,8 +193,8 @@ Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) { - uiPrintf(state, "%s: failed to mount %s at %s: %s\n", name, location.c_str(), - mount_point.c_str(), strerror(errno)); + uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(), + strerror(errno)); return StringValue(""); } @@ -244,12 +243,12 @@ Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_p scan_mounted_volumes(); MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str()); if (vol == nullptr) { - uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point.c_str()); + uiPrintf(state, "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, "unmount of %s failed (%d): %s\n", mount_point.c_str(), ret, strerror(errno)); + uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno)); } } @@ -316,9 +315,31 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt } if (fs_type == "ext4") { - int status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle); + const char* mke2fs_argv[] = { "/sbin/mke2fs_static", "-t", "ext4", "-b", "4096", + location.c_str(), nullptr, nullptr }; + std::string size_str; + if (size != 0) { + size_str = std::to_string(size / 4096LL); + mke2fs_argv[6] = size_str.c_str(); + } + + int status = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv)); + if (status != 0) { + LOG(WARNING) << name << ": mke2fs failed (" << status << ") on " << location + << ", falling back to make_ext4fs"; + status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle); + if (status != 0) { + LOG(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location; + return StringValue(""); + } + return StringValue(location); + } + + 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<char**>(e2fsdroid_argv)); if (status != 0) { - LOG(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location; + LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location; return StringValue(""); } return StringValue(location); @@ -330,9 +351,11 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt std::string num_sectors = std::to_string(size / 512); const char* f2fs_path = "/sbin/mkfs.f2fs"; - const char* const f2fs_argv[] = { "mkfs.f2fs", "-t", "-d1", location.c_str(), - num_sectors.c_str(), nullptr }; - int status = exec_cmd(f2fs_path, const_cast<char* const*>(f2fs_argv)); + const char* f2fs_argv[] = { + "mkfs.f2fs", "-t", "-d1", location.c_str(), (size < 512) ? nullptr : num_sectors.c_str(), + nullptr + }; + int status = exec_cmd(f2fs_path, const_cast<char**>(f2fs_argv)); if (status != 0) { LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location; return StringValue(""); @@ -400,36 +423,6 @@ Value* SetProgressFn(const char* name, State* state, const std::vector<std::uniq return StringValue(frac_str); } -// package_extract_dir(package_dir, dest_dir) -// Extracts all files from the package underneath package_dir and writes them to the -// corresponding tree beneath dest_dir. Any existing files are overwritten. -// Example: package_extract_dir("system", "/system") -// -// Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path. -Value* PackageExtractDirFn(const char* name, State* state, - const std::vector<std::unique_ptr<Expr>>&argv) { - if (argv.size() != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, - argv.size()); - } - - std::vector<std::string> args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& zip_path = args[0]; - const std::string& dest_path = args[1]; - - ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; - - // To create a consistent system image, never use the clock for timestamps. - constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default - - bool success = ExtractPackageRecursive(za, zip_path, dest_path, ×tamp, sehandle); - - return StringValue(success ? "t" : ""); -} - // package_extract_file(package_file[, dest_file]) // Extracts a single package_file from the update package and writes it to dest_file, // overwriting existing files if necessary. Without the dest_file argument, returns the @@ -712,15 +705,15 @@ Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std:: return StringValue(result == 0 ? "t" : ""); } -// This is the updater side handler for ui_print() in edify script. Contents -// will be sent over to the recovery side for on-screen display. +// This is the updater side handler for ui_print() in edify script. Contents will be sent over to +// the recovery side for on-screen display. Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { std::vector<std::string> args; if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); } - std::string buffer = android::base::Join(args, "") + "\n"; + std::string buffer = android::base::Join(args, ""); uiPrint(state, buffer); return StringValue(buffer); } @@ -1052,7 +1045,6 @@ void RegisterInstallFunctions() { RegisterFunction("format", FormatFn); RegisterFunction("show_progress", ShowProgressFn); RegisterFunction("set_progress", SetProgressFn); - RegisterFunction("package_extract_dir", PackageExtractDirFn); RegisterFunction("package_extract_file", PackageExtractFileFn); RegisterFunction("getprop", GetPropFn); diff --git a/updater/updater.cpp b/updater/updater.cpp index e4b8e4641..25bd541b0 100644 --- a/updater/updater.cpp +++ b/updater/updater.cpp @@ -94,7 +94,7 @@ int main(int argc, char** argv) { const char* package_filename = argv[3]; MemMapping map; - if (sysMapFile(package_filename, &map) != 0) { + if (!map.MapFile(package_filename)) { LOG(ERROR) << "failed to map package " << argv[3]; return 3; } @@ -211,12 +211,18 @@ int main(int argc, char** argv) { } } - if (state.error_code != kNoError) { - fprintf(cmd_pipe, "log error: %d\n", state.error_code); - // Cause code should provide additional information about the abort; - // report only when an error exists. - if (state.cause_code != kNoCause) { - fprintf(cmd_pipe, "log cause: %d\n", state.cause_code); + // 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"); } } @@ -231,7 +237,6 @@ int main(int argc, char** argv) { if (updater_info.package_zip) { CloseArchive(updater_info.package_zip); } - sysReleaseMap(&map); return 0; } diff --git a/verifier.cpp b/verifier.cpp index 2ef9c4c37..18437fb7a 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -474,81 +474,80 @@ std::unique_ptr<EC_KEY, ECKEYDeleter> parse_ec_key(FILE* file) { // Otherwise returns false if the file failed to parse, or if it contains zero // keys. The contents in certs would be unspecified on failure. bool load_keys(const char* filename, std::vector<Certificate>& certs) { - std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "r"), fclose); - if (!f) { - PLOG(ERROR) << "error opening " << filename; - return false; - } - - while (true) { - certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); - Certificate& cert = certs.back(); - uint32_t exponent = 0; - - char start_char; - if (fscanf(f.get(), " %c", &start_char) != 1) return false; - if (start_char == '{') { - // a version 1 key has no version specifier. - cert.key_type = Certificate::KEY_TYPE_RSA; - exponent = 3; - cert.hash_len = SHA_DIGEST_LENGTH; - } else if (start_char == 'v') { - int version; - if (fscanf(f.get(), "%d {", &version) != 1) return false; - switch (version) { - case 2: - cert.key_type = Certificate::KEY_TYPE_RSA; - exponent = 65537; - cert.hash_len = SHA_DIGEST_LENGTH; - break; - case 3: - cert.key_type = Certificate::KEY_TYPE_RSA; - exponent = 3; - cert.hash_len = SHA256_DIGEST_LENGTH; - break; - case 4: - cert.key_type = Certificate::KEY_TYPE_RSA; - exponent = 65537; - cert.hash_len = SHA256_DIGEST_LENGTH; - break; - case 5: - cert.key_type = Certificate::KEY_TYPE_EC; - cert.hash_len = SHA256_DIGEST_LENGTH; - break; - default: - return false; - } - } + std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "re"), fclose); + if (!f) { + PLOG(ERROR) << "error opening " << filename; + return false; + } - if (cert.key_type == Certificate::KEY_TYPE_RSA) { - cert.rsa = parse_rsa_key(f.get(), exponent); - if (!cert.rsa) { - return false; - } + while (true) { + certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + Certificate& cert = certs.back(); + uint32_t exponent = 0; + + char start_char; + if (fscanf(f.get(), " %c", &start_char) != 1) return false; + if (start_char == '{') { + // a version 1 key has no version specifier. + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 3; + cert.hash_len = SHA_DIGEST_LENGTH; + } else if (start_char == 'v') { + int version; + if (fscanf(f.get(), "%d {", &version) != 1) return false; + switch (version) { + case 2: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 65537; + cert.hash_len = SHA_DIGEST_LENGTH; + break; + case 3: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 3; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + case 4: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 65537; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + case 5: + cert.key_type = Certificate::KEY_TYPE_EC; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + default: + return false; + } + } - LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len; - } else if (cert.key_type == Certificate::KEY_TYPE_EC) { - cert.ec = parse_ec_key(f.get()); - if (!cert.ec) { - return false; - } - } else { - LOG(ERROR) << "Unknown key type " << cert.key_type; - return false; - } + if (cert.key_type == Certificate::KEY_TYPE_RSA) { + cert.rsa = parse_rsa_key(f.get(), exponent); + if (!cert.rsa) { + return false; + } - // if the line ends in a comma, this file has more keys. - int ch = fgetc(f.get()); - if (ch == ',') { - // more keys to come. - continue; - } else if (ch == EOF) { - break; - } else { - LOG(ERROR) << "unexpected character between keys"; - return false; - } + LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len; + } else if (cert.key_type == Certificate::KEY_TYPE_EC) { + cert.ec = parse_ec_key(f.get()); + if (!cert.ec) { + return false; + } + } else { + LOG(ERROR) << "Unknown key type " << cert.key_type; + return false; } - return true; + // if the line ends in a comma, this file has more keys. + int ch = fgetc(f.get()); + if (ch == ',') { + // more keys to come. + continue; + } else if (ch == EOF) { + break; + } else { + LOG(ERROR) << "unexpected character between keys"; + return false; + } + } + return true; } diff --git a/vr_device.cpp b/vr_device.cpp new file mode 100644 index 000000000..61e15cbb6 --- /dev/null +++ b/vr_device.cpp @@ -0,0 +1,23 @@ +/* + * 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 "device.h" +#include "vr_ui.h" + +Device* make_device() { + return new Device(new VrRecoveryUI); +} + diff --git a/vr_ui.cpp b/vr_ui.cpp new file mode 100644 index 000000000..125167268 --- /dev/null +++ b/vr_ui.cpp @@ -0,0 +1,35 @@ +/* + * 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 "vr_ui.h" + +#include <minui/minui.h> + +VrRecoveryUI::VrRecoveryUI() : kStereoOffset(RECOVERY_UI_VR_STEREO_OFFSET) {} + +bool VrRecoveryUI::InitTextParams() { + if (!ScreenRecoveryUI::InitTextParams()) return false; + int mid_divide = gr_fb_width() / 2; + text_cols_ = (mid_divide - kMarginWidth - kStereoOffset) / char_width_; + return true; +} + +int VrRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const { + int mid_divide = gr_fb_width() / 2; + gr_text(gr_sys_font(), x + kStereoOffset, y, line, bold); + gr_text(gr_sys_font(), x - kStereoOffset + mid_divide, y, line, bold); + return char_height_ + 4; +} diff --git a/vr_ui.h b/vr_ui.h new file mode 100644 index 000000000..d996c145f --- /dev/null +++ b/vr_ui.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_VR_UI_H +#define RECOVERY_VR_UI_H + +#include "screen_ui.h" + +class VrRecoveryUI : public ScreenRecoveryUI { + public: + VrRecoveryUI(); + + protected: + // Pixel offsets to move drawing functions to visible range. + // Can vary per device depending on screen size and lens distortion. + const int kStereoOffset; + + bool InitTextParams() override; + + int DrawTextLine(int x, int y, const char* line, bool bold) const override; +}; + +#endif // RECOVERY_VR_UI_H diff --git a/wear_device.cpp b/wear_device.cpp new file mode 100644 index 000000000..3268130b0 --- /dev/null +++ b/wear_device.cpp @@ -0,0 +1,23 @@ +/* + * 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 "device.h" +#include "wear_ui.h" + +Device* make_device() { + return new Device(new WearRecoveryUI); +} + diff --git a/wear_touch.cpp b/wear_touch.cpp deleted file mode 100644 index e2ab44d2d..000000000 --- a/wear_touch.cpp +++ /dev/null @@ -1,177 +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 <dirent.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> - -#include <android-base/logging.h> -#include <linux/input.h> - -#include "wear_touch.h" - -#define DEVICE_PATH "/dev/input" - -WearSwipeDetector::WearSwipeDetector(int low, int high, OnSwipeCallback callback, void* cookie): - mLowThreshold(low), - mHighThreshold(high), - mCallback(callback), - mCookie(cookie), - mCurrentSlot(-1) { - pthread_create(&mThread, NULL, touch_thread, this); -} - -WearSwipeDetector::~WearSwipeDetector() { -} - -void WearSwipeDetector::detect(int dx, int dy) { - enum SwipeDirection direction; - - if (abs(dy) < mLowThreshold && abs(dx) > mHighThreshold) { - direction = dx < 0 ? LEFT : RIGHT; - } else if (abs(dx) < mLowThreshold && abs(dy) > mHighThreshold) { - direction = dy < 0 ? UP : DOWN; - } else { - LOG(DEBUG) << "Ignore " << dx << " " << dy; - return; - } - - LOG(DEBUG) << "Swipe direction=" << direction; - mCallback(mCookie, direction); -} - -void WearSwipeDetector::process(struct input_event *event) { - if (mCurrentSlot < 0) { - mCallback(mCookie, UP); - mCurrentSlot = 0; - } - - if (event->type == EV_ABS) { - if (event->code == ABS_MT_SLOT) - mCurrentSlot = event->value; - - // Ignore other fingers - if (mCurrentSlot > 0) { - return; - } - - switch (event->code) { - case ABS_MT_POSITION_X: - mX = event->value; - mFingerDown = true; - break; - - case ABS_MT_POSITION_Y: - mY = event->value; - mFingerDown = true; - break; - - case ABS_MT_TRACKING_ID: - if (event->value < 0) - mFingerDown = false; - break; - } - } else if (event->type == EV_SYN) { - if (event->code == SYN_REPORT) { - if (mFingerDown && !mSwiping) { - mStartX = mX; - mStartY = mY; - mSwiping = true; - } else if (!mFingerDown && mSwiping) { - mSwiping = false; - detect(mX - mStartX, mY - mStartY); - } - } - } -} - -void WearSwipeDetector::run() { - int fd = findDevice(DEVICE_PATH); - if (fd < 0) { - LOG(ERROR) << "no input devices found"; - return; - } - - struct input_event event; - while (read(fd, &event, sizeof(event)) == sizeof(event)) { - process(&event); - } - - close(fd); -} - -void* WearSwipeDetector::touch_thread(void* cookie) { - (static_cast<WearSwipeDetector*>(cookie))->run(); - return NULL; -} - -#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)%8))) - -int WearSwipeDetector::openDevice(const char *device) { - int fd = open(device, O_RDONLY); - if (fd < 0) { - PLOG(ERROR) << "could not open " << device; - return false; - } - - char name[80]; - name[sizeof(name) - 1] = '\0'; - if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) { - PLOG(ERROR) << "could not get device name for " << device; - name[0] = '\0'; - } - - uint8_t bits[512]; - memset(bits, 0, sizeof(bits)); - int ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits); - if (ret > 0) { - if (test_bit(ABS_MT_POSITION_X, bits) && test_bit(ABS_MT_POSITION_Y, bits)) { - LOG(DEBUG) << "Found " << device << " " << name; - return fd; - } - } - - close(fd); - return -1; -} - -int WearSwipeDetector::findDevice(const char* path) { - DIR* dir = opendir(path); - if (dir == NULL) { - PLOG(ERROR) << "Could not open directory " << path; - return false; - } - - struct dirent* entry; - int ret = -1; - while (ret < 0 && (entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.') continue; - - char device[PATH_MAX]; - device[PATH_MAX-1] = '\0'; - snprintf(device, PATH_MAX-1, "%s/%s", path, entry->d_name); - - ret = openDevice(device); - } - - closedir(dir); - return ret; -} - diff --git a/wear_touch.h b/wear_touch.h deleted file mode 100644 index 9a1d3150c..000000000 --- a/wear_touch.h +++ /dev/null @@ -1,58 +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. - */ - -#ifndef __WEAR_TOUCH_H -#define __WEAR_TOUCH_H - -#include <pthread.h> - -class WearSwipeDetector { - -public: - enum SwipeDirection { UP, DOWN, RIGHT, LEFT }; - typedef void (*OnSwipeCallback)(void* cookie, enum SwipeDirection direction); - - WearSwipeDetector(int low, int high, OnSwipeCallback cb, void* cookie); - ~WearSwipeDetector(); - -private: - void run(); - void process(struct input_event *event); - void detect(int dx, int dy); - - pthread_t mThread; - static void* touch_thread(void* cookie); - - int findDevice(const char* path); - int openDevice(const char* device); - - int mLowThreshold; - int mHighThreshold; - - OnSwipeCallback mCallback; - void *mCookie; - - int mX; - int mY; - int mStartX; - int mStartY; - - int mCurrentSlot; - bool mFingerDown; - bool mSwiping; -}; - -#endif // __WEAR_TOUCH_H diff --git a/wear_ui.cpp b/wear_ui.cpp index 6c0286558..624116c0c 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -45,167 +45,164 @@ static WearRecoveryUI* self = NULL; // Return the current time as a double (including fractions of a second). static double now() { - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec + tv.tv_usec / 1000000.0; + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; } -WearRecoveryUI::WearRecoveryUI() : - progress_bar_y(259), - outer_height(0), - outer_width(0), - menu_unusable_rows(0) { - intro_frames = 22; - loop_frames = 60; - animation_fps = 30; +WearRecoveryUI::WearRecoveryUI() + : kProgressBarBaseline(RECOVERY_UI_PROGRESS_BAR_BASELINE), + kMenuUnusableRows(RECOVERY_UI_MENU_UNUSABLE_ROWS) { + // TODO: kMenuUnusableRows should be computed based on the lines in draw_screen_locked(). - for (size_t i = 0; i < 5; i++) - backgroundIcon[i] = NULL; + // TODO: The following three variables are likely not needed. The first two are detected + // automatically in ScreenRecoveryUI::LoadAnimation(), based on the actual files seen on device. + intro_frames = 22; + loop_frames = 60; - self = this; + touch_screen_allowed_ = true; + + for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL; + + self = this; } -int WearRecoveryUI::GetProgressBaseline() { - return progress_bar_y; +int WearRecoveryUI::GetProgressBaseline() const { + return kProgressBarBaseline; } // 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 (currentIcon != NONE) { - GRSurface* surface; - if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - if (!intro_done) { - surface = introFrames[current_frame]; - } else { - surface = loopFrames[current_frame]; - } - } - else { - surface = backgroundIcon[currentIcon]; - } +void WearRecoveryUI::draw_background_locked() { + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (currentIcon != NONE) { + GRSurface* surface; + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + if (!intro_done) { + surface = introFrames[current_frame]; + } else { + surface = loopFrames[current_frame]; + } + } else { + surface = backgroundIcon[currentIcon]; + } - int width = gr_get_width(surface); - int height = gr_get_height(surface); + int width = gr_get_width(surface); + int height = gr_get_height(surface); - int x = (gr_fb_width() - width) / 2; - int y = (gr_fb_height() - height) / 2; + int x = (gr_fb_width() - width) / 2; + int y = (gr_fb_height() - height) / 2; - gr_blit(surface, 0, 0, width, height, x, y); - } + gr_blit(surface, 0, 0, width, height, x, y); + } } -static const char* HEADERS[] = { - "Swipe up/down to move.", - "Swipe left/right to select.", - "", - NULL +static const char* SWIPE_HELP[] = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + NULL }; // TODO merge drawing routines with screen_ui -void WearRecoveryUI::draw_screen_locked() -{ - char cur_selection_str[50]; +void WearRecoveryUI::draw_screen_locked() { + char cur_selection_str[50]; + + draw_background_locked(); + if (!show_text) { + draw_foreground_locked(); + } else { + SetColor(TEXT_FILL); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - draw_background_locked(); - if (!show_text) { - draw_foreground_locked(); - } else { - SetColor(TEXT_FILL); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - int y = outer_height; - int x = outer_width; - if (show_menu) { - std::string recovery_fingerprint = - android::base::GetProperty("ro.bootimage.build.fingerprint", ""); - SetColor(HEADER); - DrawTextLine(x + 4, &y, "Android Recovery", true); - for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(x +4, &y, chunk.c_str(), false); - } - - // This is actually the help strings. - DrawTextLines(x + 4, &y, HEADERS); - SetColor(HEADER); - DrawTextLines(x + 4, &y, menu_headers_); - - // Show the current menu item number in relation to total number if - // items don't fit on the screen. - if (menu_items > menu_end - menu_start) { - sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); - gr_text(gr_sys_font(), x+4, y, cur_selection_str, 1); - y += char_height_+4; - } - - // Menu begins here - SetColor(MENU); - - for (int i = menu_start; i < menu_end; ++i) { - - if (i == menu_sel) { - // draw the highlight bar - SetColor(MENU_SEL_BG); - gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2); - // white text of selected item - SetColor(MENU_SEL_FG); - if (menu_[i][0]) { - gr_text(gr_sys_font(), x + 4, y, menu_[i], 1); - } - SetColor(MENU); - } else if (menu_[i][0]) { - gr_text(gr_sys_font(), x + 4, y, menu_[i], 0); - } - y += char_height_+4; - } - SetColor(MENU); - y += 4; - gr_fill(0, y, gr_fb_width(), y+2); - y += 4; - } + int y = kMarginHeight; + int x = kMarginWidth; + if (show_menu) { + std::string recovery_fingerprint = + android::base::GetProperty("ro.bootimage.build.fingerprint", ""); + SetColor(HEADER); + y += DrawTextLine(x + 4, y, "Android Recovery", true); + for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { + y += DrawTextLine(x + 4, y, chunk.c_str(), false); + } + + // This is actually the help strings. + y += DrawTextLines(x + 4, y, SWIPE_HELP); + SetColor(HEADER); + y += DrawTextLines(x + 4, y, menu_headers_); + + // Show the current menu item number in relation to total number if + // items don't fit on the screen. + if (menu_items > menu_end - menu_start) { + sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); + gr_text(gr_sys_font(), x + 4, y, cur_selection_str, 1); + y += char_height_ + 4; + } - SetColor(LOG); - - // 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. - int ty; - int row = (text_top_ + text_rows_ - 1) % text_rows_; - size_t count = 0; - for (int ty = gr_fb_height() - char_height_ - outer_height; - ty > y + 2 && count < text_rows_; - ty -= char_height_, ++count) { - gr_text(gr_sys_font(), x+4, ty, text_[row], 0); - --row; - if (row < 0) row = text_rows_ - 1; + // Menu begins here + SetColor(MENU); + + for (int i = menu_start; i < menu_end; ++i) { + if (i == menu_sel) { + // draw the highlight bar + SetColor(MENU_SEL_BG); + gr_fill(x, y - 2, gr_fb_width() - x, y + char_height_ + 2); + // white text of selected item + SetColor(MENU_SEL_FG); + if (menu_[i][0]) { + gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 1); + } + SetColor(MENU); + } else if (menu_[i][0]) { + gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 0); } + y += char_height_ + 4; + } + SetColor(MENU); + y += 4; + gr_fill(0, y, gr_fb_width(), y + 2); + y += 4; } + + SetColor(LOG); + + // 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. + int ty; + int row = (text_top_ + text_rows_ - 1) % text_rows_; + size_t count = 0; + for (int ty = gr_fb_height() - char_height_ - kMarginHeight; ty > y + 2 && count < text_rows_; + ty -= char_height_, ++count) { + gr_text(gr_sys_font(), x + 4, ty, text_[row], 0); + --row; + if (row < 0) row = text_rows_ - 1; + } + } } // TODO merge drawing routines with screen_ui void WearRecoveryUI::update_progress_locked() { - draw_screen_locked(); - gr_flip(); + draw_screen_locked(); + gr_flip(); } bool WearRecoveryUI::InitTextParams() { - if (!ScreenRecoveryUI::InitTextParams()) { - return false; - } + if (!ScreenRecoveryUI::InitTextParams()) { + return false; + } - text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_; + text_cols_ = (gr_fb_width() - (kMarginWidth * 2)) / char_width_; - if (text_rows_ > kMaxRows) text_rows_ = kMaxRows; - if (text_cols_ > kMaxCols) text_cols_ = kMaxCols; + if (text_rows_ > kMaxRows) text_rows_ = kMaxRows; + if (text_cols_ > kMaxCols) text_cols_ = kMaxCols; - visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_; - return true; + visible_text_rows = (gr_fb_height() - (kMarginHeight * 2)) / char_height_; + return true; } bool WearRecoveryUI::Init(const std::string& locale) { @@ -222,7 +219,8 @@ bool WearRecoveryUI::Init(const std::string& locale) { return true; } -void WearRecoveryUI::SetStage(int current, int max) {} +void WearRecoveryUI::SetStage(int current, int max) { +} void WearRecoveryUI::Print(const char* fmt, ...) { char buf[256]; @@ -252,165 +250,152 @@ void WearRecoveryUI::Print(const char* fmt, ...) { pthread_mutex_unlock(&updateMutex); } -void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items, +void WearRecoveryUI::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; - size_t i = 0; - // "i < text_rows_" is removed from the loop termination condition, - // which is different from the one in ScreenRecoveryUI::StartMenu(). - // Because WearRecoveryUI supports scrollable menu, it's fine to have - // more entries than text_rows_. The menu may be truncated otherwise. - // Bug: 23752519 - for (; items[i] != nullptr; i++) { - strncpy(menu_[i], items[i], text_cols_ - 1); - menu_[i][text_cols_ - 1] = '\0'; - } - menu_items = i; - show_menu = true; - menu_sel = initial_selection; - menu_start = 0; - menu_end = visible_text_rows - 1 - menu_unusable_rows; - if (menu_items <= menu_end) - menu_end = menu_items; - update_screen_locked(); + pthread_mutex_lock(&updateMutex); + if (text_rows_ > 0 && text_cols_ > 0) { + menu_headers_ = headers; + menu_.clear(); + // "i < text_rows_" is removed from the loop termination condition, + // which is different from the one in ScreenRecoveryUI::StartMenu(). + // Because WearRecoveryUI supports scrollable menu, it's fine to have + // more entries than text_rows_. The menu may be truncated otherwise. + // Bug: 23752519 + for (size_t i = 0; items[i] != nullptr; i++) { + menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1))); } - pthread_mutex_unlock(&updateMutex); + menu_items = static_cast<int>(menu_.size()); + show_menu = true; + menu_sel = initial_selection; + menu_start = 0; + menu_end = visible_text_rows - 1 - kMenuUnusableRows; + if (menu_items <= menu_end) menu_end = menu_items; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } int WearRecoveryUI::SelectMenu(int sel) { - int old_sel; - pthread_mutex_lock(&updateMutex); - if (show_menu) { - old_sel = menu_sel; - menu_sel = sel; - if (menu_sel < 0) menu_sel = 0; - if (menu_sel >= menu_items) menu_sel = menu_items-1; - if (menu_sel < menu_start) { - menu_start--; - menu_end--; - } else if (menu_sel >= menu_end && menu_sel < menu_items) { - menu_end++; - menu_start++; - } - sel = menu_sel; - if (menu_sel != old_sel) update_screen_locked(); + int old_sel; + pthread_mutex_lock(&updateMutex); + if (show_menu) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items - 1; + if (menu_sel < menu_start) { + menu_start--; + menu_end--; + } else if (menu_sel >= menu_end && menu_sel < menu_items) { + menu_end++; + menu_start++; } - pthread_mutex_unlock(&updateMutex); - return sel; + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; } void WearRecoveryUI::ShowFile(FILE* fp) { - std::vector<off_t> offsets; - offsets.push_back(ftello(fp)); - ClearText(); - - struct stat sb; - fstat(fileno(fp), &sb); - - bool show_prompt = false; - while (true) { - if (show_prompt) { - Print("--(%d%% of %d bytes)--", - static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), - static_cast<int>(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) { - text_row_ = text_top_ = text_rows_ - 2; + std::vector<off_t> offsets; + offsets.push_back(ftello(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + Print("--(%d%% of %d bytes)--", + static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), + static_cast<int>(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 { - PutChar(ch); - if (text_col_ == 0 && text_row_ >= text_rows_ - 2) { - text_top_ = text_row_; - show_prompt = true; - } + if (feof(fp)) { + return; + } + offsets.push_back(ftello(fp)); } + } + ClearText(); } -} -void WearRecoveryUI::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_; + int ch = getc(fp); + if (ch == EOF) { + text_row_ = text_top_ = text_rows_ - 2; + show_prompt = true; + } else { + PutChar(ch); + if (text_col_ == 0 && text_row_ >= text_rows_ - 2) { + text_top_ = text_row_; + show_prompt = true; + } } - pthread_mutex_unlock(&updateMutex); + } } -void WearRecoveryUI::ShowFile(const char* filename) { - FILE* fp = fopen_path(filename, "re"); - if (fp == nullptr) { - Print(" Unable to open %s: %s\n", filename, strerror(errno)); - return; - } - ShowFile(fp); - fclose(fp); +void WearRecoveryUI::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 WearRecoveryUI::ClearText() { - pthread_mutex_lock(&updateMutex); - text_col_ = 0; - text_row_ = 0; - text_top_ = 1; - for (size_t i = 0; i < text_rows_; ++i) { - memset(text_[i], 0, text_cols_ + 1); - } - pthread_mutex_unlock(&updateMutex); +void WearRecoveryUI::ShowFile(const char* filename) { + FILE* fp = fopen_path(filename, "re"); + if (fp == nullptr) { + Print(" Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + ShowFile(fp); + fclose(fp); } void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, false, ap); - va_end(ap); + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); } void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { - std::string str; - android::base::StringAppendV(&str, fmt, ap); + std::string str; + android::base::StringAppendV(&str, fmt, ap); - if (copy_to_stdout) { - fputs(str.c_str(), stdout); - } + 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 (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; - } - if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; - } + 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'; - update_screen_locked(); + text_col_ = 0; + text_row_ = (text_row_ + 1) % text_rows_; + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + } + if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; } - pthread_mutex_unlock(&updateMutex); + text_[text_row_][text_col_] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } @@ -22,64 +22,58 @@ #include <string> class WearRecoveryUI : public ScreenRecoveryUI { - public: - WearRecoveryUI(); + public: + WearRecoveryUI(); - bool Init(const std::string& locale) override; + bool Init(const std::string& locale) override; - void SetStage(int current, int max) override; + void SetStage(int current, int max) override; - // printing messages - void Print(const char* fmt, ...) override; - void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); - void ShowFile(const char* filename) override; - void ShowFile(FILE* fp) override; + // printing messages + void Print(const char* fmt, ...) override; + void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); + void ShowFile(const char* filename) override; + void ShowFile(FILE* fp) override; - // menu display - void StartMenu(const char* const * headers, const char* const * items, - int initial_selection) override; - int SelectMenu(int sel) 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 - int progress_bar_y; + protected: + // progress bar vertical position, it's centered horizontally + const int kProgressBarBaseline; - // outer of window - int outer_height, outer_width; + // 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; - // 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. - int menu_unusable_rows; + int GetProgressBaseline() const override; - int GetProgressBaseline() override; + bool InitTextParams() override; - bool InitTextParams() override; + void update_progress_locked() override; - void update_progress_locked() override; + void PrintV(const char*, bool, va_list) override; - void PrintV(const char*, bool, va_list) override; + private: + GRSurface* backgroundIcon[5]; - private: - GRSurface* backgroundIcon[5]; + static const int kMaxCols = 96; + static const int kMaxRows = 96; - static const int kMaxCols = 96; - static const int kMaxRows = 96; + // Number of text rows seen on screen + int visible_text_rows; - // Number of text rows seen on screen - int visible_text_rows; + const char* const* menu_headers_; + int menu_start, menu_end; - const char* const* menu_headers_; - int menu_start, menu_end; + pthread_t progress_t; - pthread_t progress_t; + void draw_background_locked() override; + void draw_screen_locked() override; - void draw_background_locked() override; - void draw_screen_locked() override; - void draw_progress_locked(); - - void PutChar(char); - void ClearText(); + void PutChar(char); }; #endif // RECOVERY_WEAR_UI_H |