Merge up to android-8.1.0_r1 and fix conflicts
Change-Id: I2dc060134d15ec9f015a606cb24ef8276f6af1fc
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..07fc27d
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,4 @@
+subdirs = [
+// "bootloader_message",
+// "otautil",
+]
diff --git a/Android.mk b/Android.mk
index 11bf006..f42f891 100644
--- a/Android.mk
+++ b/Android.mk
@@ -590,7 +590,7 @@
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 @@
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 $(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 @@
libvintf_recovery \
libcrypto_utils \
libcrypto \
- libbase
+ libbase \
+ libziparchive \
include $(BUILD_STATIC_LIBRARY)
@@ -661,6 +684,7 @@
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 $(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_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
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..09754c6
--- /dev/null
+++ b/OWNERS
@@ -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 7b03882..291708c 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));
-*/
- printf("failed to open driver control: %s\n", strerror(errno));
- return;
- }
+ char configfs[PROPERTY_VALUE_MAX];
+ property_get("sys.usb.configfs", configfs, "false");
+ if (strcmp(configfs, "false") == 0 || strcmp(configfs, "0") == 0)
+ return;
- if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) {
-/*
- ui->Print("failed to set driver control: %s\n", strerror(errno));
+ 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 set driver control: %s\n", strerror(errno));
- }
- if (close(fd) < 0) {
+ printf("failed to open driver control: %s\n", strerror(errno));
+ return;
+ }
+
+ if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) {
/*
- ui->Print("failed to close driver control: %s\n", strerror(errno));
+ ui->Print("failed to set driver control: %s\n", strerror(errno));
*/
- printf("failed to close driver control: %s\n", strerror(errno));
- }
+ 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));
+*/
+ 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;
+ 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;
+ 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);
+ int pid = -1;
+ int ret = sscanf(de->d_name, "%d", &pid);
- if (ret == 1) {
- char cmdpath[PATH_MAX];
- sprintf(cmdpath, "/proc/%d/cmdline", 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);
- }
+ 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 @@
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);
+ 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;
}
- *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;
+ 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;
+ }
- // if we got here, something failed
- *child_pid = 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);
+ 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);
+ // 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));
}
+ }
- 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));
- }
- }
+ 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 e9e88f7..121ae3c 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 54c37eb..43e9b80 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 @@
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 @@
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 @@
}
// 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 @@
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 8ea68a1..0000000
--- 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 7b191a8..aa32d57 100644
--- a/applypatch/applypatch_modes.cpp
+++ b/applypatch/applypatch_modes.cpp
@@ -44,19 +44,6 @@
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 @@
"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 @@
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 9920c2b..65ee614 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 @@
);
}
-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 41d73ab..fc24064 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -693,6 +693,20 @@
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 @@
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 dcdb922..0000000
--- 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 adcc61f..df75f98 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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
}
}
- // 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 4489dec..581360e 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 @@
// 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 @@
// 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 6549f79..07c6609 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
deleted file mode 100644
index cdeb8fd..0000000
--- a/applypatch/testdata/new.file
+++ /dev/null
Binary files differ
diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file
deleted file mode 100644
index 166c873..0000000
--- a/applypatch/testdata/old.file
+++ /dev/null
Binary files differ
diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff
deleted file mode 100644
index b78d385..0000000
--- a/applypatch/testdata/patch.bsdiff
+++ /dev/null
Binary files differ
diff --git a/boot_control/Android.mk b/boot_control/Android.mk
new file mode 100644
index 0000000..27e3d97
--- /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 0000000..ec97b6c
--- /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 0000000..f0d76e7
--- /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 dcaeb79..6c237e6 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -144,6 +144,13 @@
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 4ad18f2..4da1171 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -191,6 +191,11 @@
#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 97205fe..b1685eb 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 @@
$$->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 cde4ee6..9fe047c 100644
--- a/error_code.h
+++ b/error_code.h
@@ -24,6 +24,7 @@
kZipOpenFailure,
kBootreasonInBlacklist,
kPackageCompatibilityFailure,
+ kScriptExecutionFailure,
};
enum CauseCode {
@@ -43,6 +44,7 @@
kTune2FsFailure,
kRebootFailure,
kPackageExtractFileFailure,
+ kPatchApplicationFailure,
kVendorFailure = 200
};
diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp
index f57d479..f667cd4 100644
--- a/fuse_sideload.cpp
+++ b/fuse_sideload.cpp
@@ -235,11 +235,13 @@
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 @@
return NO_STATUS;
}
-int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
- uint64_t file_size, uint32_t block_size)
-{
- int result;
+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);
- // 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;
+ }
- 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 = {};
+ 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);
- 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);
+ int result;
+ if (fd.file_blocks > (1 << 18)) {
+ fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks);
+ result = -1;
+ goto done;
+ }
- 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.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.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.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;
+ }
- 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;
- }
+ }
- if ((size_t)len < sizeof(struct fuse_in_header)) {
- fprintf(stderr, "request too short: len=%zu\n", (size_t)len);
- continue;
- }
-
- struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer;
- void* data = request_buffer + sizeof(struct fuse_in_header);
-
- result = -ENOSYS;
-
- 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_GETATTR:
- result = handle_getattr(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_FLUSH:
- result = handle_flush(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;
- }
-
- 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)));
- }
+ 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;
}
- 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));
+ if (static_cast<size_t>(len) < sizeof(struct fuse_in_header)) {
+ fprintf(stderr, "request too short: len=%zd\n", len);
+ continue;
}
- if (fd.ffd) close(fd.ffd);
- free(fd.hashes);
- free(fd.block_data);
- free(fd.extra_block);
+ struct fuse_in_header* hdr = reinterpret_cast<struct fuse_in_header*>(request_buffer);
+ void* data = request_buffer + sizeof(struct fuse_in_header);
- return result;
+ result = -ENOSYS;
+
+ 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_GETATTR:
+ result = handle_getattr(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_FLUSH:
+ result = handle_flush(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;
+ }
+
+ 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)));
+ }
+ }
+
+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));
+ }
+
+ if (fd.ffd) close(fd.ffd);
+ free(fd.hashes);
+ free(fd.block_data);
+ free(fd.extra_block);
+
+ 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 1199a28..a3a1df3 100644
--- a/gui/pages.cpp
+++ b/gui/pages.cpp
@@ -1370,13 +1370,19 @@
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 @@
if (ctx.zip) {
ctx.zip->Close();
+#ifdef USE_MINZIP
sysReleaseMap(&map);
+#endif
}
return ret;
@@ -1426,7 +1434,9 @@
// 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 ffeba2e..7fbf5c0 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;
+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);
+ }
}
- // 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));
- }
- }
- }
+ }
}
-// 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 @@
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 @@
}
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 @@
#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 @@
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 @@
}
*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 @@
}
#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 @@
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 @@
}
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 @@
} 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 @@
int status;
waitpid(pid, &status, 0);
+ logger_finished.store(true);
finish_log_temperature.notify_one();
temperature_logger.join();
@@ -481,7 +474,7 @@
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 @@
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;
+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");
+ // 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);
- }
+ 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;
- }
-
- // Additionally verify the compatibility of the package.
- if (!verify_package_compatibility(zip)) {
- log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
- sysReleaseMap(&map);
- CloseArchive(zip);
- return INSTALL_CORRUPT;
- }
-
- // Verify and install the contents of the package.
- ui->Print("Installing update...\n");
- if (retry_count > 0) {
- ui->Print("Retry attempt: %d\n", retry_count);
- }
- ui->SetEnableReboot(false);
- int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
- ui->SetEnableReboot(true);
- ui->Print("\n");
-
- sysReleaseMap(&map);
CloseArchive(zip);
- return result;
+ 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");
+
+ 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 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);
- int start_temperature = GetMaxValueFromThermalZone();
- int max_temperature = start_temperature;
+ modified_flash = true;
+ auto start = std::chrono::system_clock::now();
- 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 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),
- };
+ // 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));
- }
+ 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;
- }
+ 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";
diff --git a/install.h b/install.h
index fd68c3a..f3fda30 100644
--- a/install.h
+++ b/install.h
@@ -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 @@
// 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 40e0519..dfeb85e 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -67,6 +67,7 @@
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 0000000..7ea235b
--- /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 0f2e881..31be2a6 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 @@
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 @@
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 40f2cea..6919cdd 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -69,7 +69,6 @@
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 8df5a49..cb56b73 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -51,8 +51,11 @@
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 470a17a..e9383ca 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -57,40 +57,47 @@
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 @@
}
}
}
+
+#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 79f4db8..72b563b 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 766b943..bd8c386 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -91,7 +91,12 @@
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 026e1dc..b9797b9 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -56,7 +56,7 @@
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 f23376b..76fa657 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 @@
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 @@
}
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;
}
diff --git a/mounts.h b/mounts.h
index 1b76703..0de1ebd 100644
--- a/mounts.h
+++ b/mounts.h
@@ -21,12 +21,8 @@
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 1898e17..7d65b55 100644
--- a/otafault/Android.mk
+++ b/otafault/Android.mk
@@ -29,7 +29,12 @@
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 3a89bb5..faae527 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..a2eaa04
--- /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 a2133b9..dfa2150 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 @@
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 @@
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 @@
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 @@
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 6a79bf3..52f6d20 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 */
+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();
+ };
- std::vector<MappedRange> ranges;
+ 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 12d303b..ef64bd4 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 d706cca..dbce7ff 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -59,21 +59,21 @@
}
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 @@
*/
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 c1a31b6..07bd7b9 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_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 @@
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 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 @@
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 @@
}
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 @@
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 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 @@
{
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 @@
}
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 @@
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 @@
ui->Print("Rebooting automatically.\n");
}
} else if (!just_exit) {
- status = INSTALL_NONE; // No command specified
- ui->SetBackground(RecoveryUI::NO_COMMAND);
+ // 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);
+ }
- // 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 (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+ ui->SetBackground(RecoveryUI::ERROR);
+ if (!ui->IsTextVisible()) {
+ sleep(5);
}
}
- if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {
- copy_logs();
- ui->SetBackground(RecoveryUI::ERROR);
- }
-
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;
diff --git a/roots.cpp b/roots.cpp
index 6e5ef98..29f55b9 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -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 @@
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 @@
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 setup_install_mounts() {
- if (fstab == NULL) {
- LOG(ERROR) << "can't set up install mounts: no fstab loaded";
+ if (fstab == nullptr) {
+ LOG(ERROR) << "can't set up install mounts: no fstab loaded";
+ return -1;
+ }
+ for (int i = 0; i < fstab->num_entries; ++i) {
+ const Volume* v = fstab->recs + i;
+
+ // We don't want to do anything with "/".
+ if (strcmp(v->mount_point, "/") == 0) {
+ continue;
+ }
+
+ if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) {
+ if (ensure_path_mounted(v->mount_point) != 0) {
+ LOG(ERROR) << "failed to mount " << v->mount_point;
return -1;
+ }
+ } else {
+ if (ensure_path_unmounted(v->mount_point) != 0) {
+ LOG(ERROR) << "failed to unmount " << v->mount_point;
+ return -1;
+ }
}
- for (int i = 0; i < fstab->num_entries; ++i) {
- 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;
- }
-
- } 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 bb2772d..b8f6ea2 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 @@
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 @@
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::GetCurrentFrame() const {
+ 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::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();
+ 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;
- }
- }
-
- 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::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;
+ }
+}
+
+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);
+}
+
+int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const {
+ gr_text(gr_sys_font(), x, y, line, bold);
+ return char_height_ + 4;
+}
+
+int ScreenRecoveryUI::DrawTextLines(int x, int y, const char* const* lines) const {
+ int offset = 0;
+ for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
+ offset += DrawTextLine(x, y + offset, lines[i], false);
+ }
+ return offset;
+}
+
+int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* lines) const {
+ int offset = 0;
+ for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
+ // The line will be wrapped if it exceeds text_cols_.
+ std::string line(lines[i]);
+ size_t next_start = 0;
+ while (next_start < line.size()) {
+ std::string sub = line.substr(next_start, text_cols_ + 1);
+ if (sub.size() <= text_cols_) {
+ next_start += sub.size();
+ } else {
+ // Line too long and must be wrapped to text_cols_ columns.
+ size_t last_space = sub.find_last_of(" \t\n");
+ if (last_space == std::string::npos) {
+ // No space found, just draw as much as we can
+ sub.resize(text_cols_);
+ next_start += text_cols_;
+ } else {
+ sub.resize(last_space);
+ next_start += last_space + 1;
+ }
+ }
+ offset += DrawTextLine(x, y + offset, sub.c_str(), false);
}
-}
-
-void ScreenRecoveryUI::DrawHorizontalRule(int* y) {
- SetColor(MENU);
- *y += 4;
- gr_fill(0, *y, gr_fb_width(), *y + 2);
- *y += 4;
-}
-
-void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const {
- gr_text(gr_sys_font(), x, *y, line, bold);
- *y += char_height_ + 4;
-}
-
-void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) const {
- for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
- DrawTextLine(x, y, lines[i], 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();
+ if (!show_text) {
+ draw_background_locked();
+ draw_foreground_locked();
+ return;
+ }
- int y = 0;
- if (show_menu) {
- std::string recovery_fingerprint =
- android::base::GetProperty("ro.bootimage.build.fingerprint", "");
+ gr_color(0, 0, 0, 255);
+ gr_clear();
- 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);
+ int y = kMarginHeight;
+ if (show_menu) {
+ static constexpr int kMenuIndent = 4;
+ int x = kMarginWidth + kMenuIndent;
- 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);
- }
-
- // 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;
- }
+ 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);
+ double interval = 1.0 / kAnimationFps;
+ while (true) {
+ double start = now();
+ pthread_mutex_lock(&updateMutex);
- bool redraw = false;
+ 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;
+ // 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;
+ }
- // 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));
+ redraw = true;
}
+
+ // move the progress bar forward on timed intervals, if configured
+ int duration = progressScopeDuration;
+ if (progressBarType == DETERMINATE && duration > 0) {
+ double elapsed = now() - progressScopeTime;
+ float p = 1.0 * elapsed / duration;
+ if (p > 1.0) p = 1.0;
+ if (p > progress) {
+ progress = p;
+ redraw = true;
+ }
+ }
+
+ if (redraw) update_progress_locked();
+
+ pthread_mutex_unlock(&updateMutex);
+ double end = now();
+ // minimum of 20ms delay between frames
+ double delay = interval - (end - start);
+ if (delay < 0.02) delay = 0.02;
+ usleep(static_cast<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 @@
}
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 @@
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 @@
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 @@
}
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;
+ 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);
- }
+ 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();
+ std::vector<off_t> offsets;
+ offsets.push_back(ftello(fp));
+ ClearText();
- struct stat sb;
- fstat(fileno(fp), &sb);
+ 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');
+ 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 a2322c3..8231a2b 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 @@
// 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 e4f2521..ff8f3a3 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -26,11 +26,12 @@
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 @@
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 @@
# 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 @@
-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 @@
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 @@
libimgpatch \
libbsdiff \
libbspatch \
+ libfusesideload \
libotafault \
librecovery \
libupdater \
@@ -135,6 +144,7 @@
libverifier \
libotautil \
libmounts \
+ libupdate_verifier \
libdivsufsort \
libdivsufsort64 \
libfs_mgr \
@@ -157,6 +167,7 @@
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 0000000..3999aa5
--- /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 3fee32d..0000000
--- 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 5cba68f..016fed9 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -105,9 +105,6 @@
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 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 @@
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 @@
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 0357acc..b38bc71 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 2f64850..bf25aeb 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 a5c0c10..968196f 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 @@
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 @@
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 @@
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 ea93e9b..40cfc69 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 4f2b816..3925236 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 @@
ASSERT_TRUE(success) << "uncrypt service is not available.";
- has_misc = parse_misc();
+ std::string err;
+ has_misc = !get_bootloader_message_blk_device(&err).empty();
+ }
+
+ 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;
+ }
+
+ // Trigger the setup-bcb service.
+ ASSERT_TRUE(android::base::SetProperty("ctl.start", isSetup ? "setup-bcb" : "clear-bcb"));
+
+ // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+ sleep(1);
+
+ 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<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);
+ }
+
+ // Check the status code from uncrypt.
+ int status;
+ ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+ ASSERT_EQ(100U, ntohl(status));
+
+ // Ack having received the status code.
+ int code = 0;
+ ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+ ASSERT_EQ(0, close(sockfd));
+
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", isSetup ? "setup-bcb" : "clear-bcb"));
+
+ // Verify the message by reading from BCB directly.
+ bootloader_message boot;
+ std::string err;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ if (isSetup) {
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
+
+ // The rest of the boot.recovery message should be zero'd out.
+ ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery));
+ size_t left = sizeof(boot.recovery) - message_in_bcb.size();
+ ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left));
+
+ // Clear the BCB.
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+ } else {
+ // All the bytes should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot), '\0'),
+ std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+ }
}
bool has_misc;
};
TEST_F(UncryptTest, setup_bcb) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
-
- // Trigger the setup-bcb service.
- ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb"));
-
- // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
- sleep(1);
-
- struct sockaddr_un un = {};
- un.sun_family = AF_UNIX;
- strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
-
- int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
- ASSERT_NE(-1, sockfd);
-
- // Connect to the uncrypt socket.
- bool success = false;
- for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
- if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
- success = true;
- break;
- }
- sleep(1);
- }
- ASSERT_TRUE(success);
-
- // Send out the BCB message.
std::string message = "--update_message=abc value";
std::string message_in_bcb = "recovery\n--update_message=abc value\n";
- int length = static_cast<int>(message.size());
- int length_out = htonl(length);
- ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int)))
- << "Failed to write length: " << strerror(errno);
- ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length))
- << "Failed to write message: " << strerror(errno);
-
- // Check the status code from uncrypt.
- int status;
- ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
- ASSERT_EQ(100U, ntohl(status));
-
- // Ack having received the status code.
- int code = 0;
- ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
-
- ASSERT_EQ(0, close(sockfd));
-
- ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
-
- // Verify the message by reading from BCB directly.
- bootloader_message boot;
- std::string err;
- ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
-
- ASSERT_EQ("boot-recovery", std::string(boot.command));
- ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
-
- // The rest of the boot.recovery message should be zero'd out.
- ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery));
- size_t left = sizeof(boot.recovery) - message_in_bcb.size();
- ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left));
-
- // Clear the BCB.
- ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+ SetupOrClearBcb(true, message, message_in_bcb);
}
TEST_F(UncryptTest, clear_bcb) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
+ SetupOrClearBcb(false, "", "");
+}
- // Trigger the clear-bcb service.
- ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb"));
+TEST_F(UncryptTest, setup_bcb_wipe_ab) {
+ TemporaryFile wipe_package;
+ ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path));
- // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
- sleep(1);
-
- struct sockaddr_un un = {};
- un.sun_family = AF_UNIX;
- strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
-
- int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
- ASSERT_NE(-1, sockfd);
-
- // Connect to the uncrypt socket.
- bool success = false;
- for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
- if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
- success = true;
- break;
- }
- sleep(1);
- }
- ASSERT_TRUE(success);
-
- // Check the status code from uncrypt.
- int status;
- ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
- ASSERT_EQ(100U, ntohl(status));
-
- // Ack having received the status code.
- int code = 0;
- ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
-
- ASSERT_EQ(0, close(sockfd));
-
- ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
-
- // Verify the content by reading from BCB directly.
- bootloader_message boot;
- std::string err;
- ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
-
- // All the bytes should be cleared.
- ASSERT_EQ(std::string(sizeof(boot), '\0'),
- std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+ // 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 0000000..b04e118
--- /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 5652ddf..6c341c1 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 @@
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 @@
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 @@
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 @@
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 4c06487..2ef3828 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -40,7 +40,7 @@
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 @@
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(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 d36dd33..92c6ef2 100644
--- a/tests/manual/recovery_test.cpp
+++ b/tests/manual/recovery_test.cpp
@@ -141,7 +141,7 @@
// 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
deleted file mode 100644
index f497ec0..0000000
--- a/tests/testdata/alter-footer.zip
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/alter-metadata.zip b/tests/testdata/alter-metadata.zip
deleted file mode 100644
index 1c71fbc..0000000
--- a/tests/testdata/alter-metadata.zip
+++ /dev/null
Binary files differ
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
new file mode 100644
index 0000000..3c6d77e
--- /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 f469966..434ee25 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -27,27 +27,23 @@
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 @@
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 4a1a49b..8276685 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 @@
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 14e5416..0000000
--- 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 0000000..dc75c23
--- /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 0000000..2d528b3
--- /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 a8a876e..65d0033 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 8cf86f7..5f82033 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 8564c9c..27972d1 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 cfb02c9..e35c82b 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 ab71529..7351e63 100644
--- a/twinstall.cpp
+++ b/twinstall.cpp
@@ -363,7 +363,11 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
} else {
LOGINFO("Install took %i second(s).\n", total_time);
}
+#ifdef USE_MINZIP
sysReleaseMap(&map);
+#endif
return ret_val;
}
diff --git a/ui.cpp b/ui.cpp
index cad7449..e80d7ed 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -54,6 +54,9 @@
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 @@
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 @@
}
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 @@
// 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 @@
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;
+ }
- 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 {
- rel_sum = 0;
- }
+ // 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_KEY && ev.code <= KEY_MAX) {
- ProcessKey(ev.code, ev.value);
+ 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_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 (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 @@
//
// 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;
+ if (register_key) {
+ switch (CheckKey(key_code, long_press)) {
+ case RecoveryUI::IGNORE:
+ break;
- case RecoveryUI::TOGGLE:
- ShowText(!IsTextVisible());
- 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;
+ 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 @@
}
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);
+
+ // 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;
+ }
+ }
+
+ // Press power seven times in a row to reboot.
+ if (key == KEY_POWER) {
pthread_mutex_lock(&key_queue_mutex);
- key_long_press = false;
+ bool reboot_enabled = enable_reboot;
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;
- }
-
- // Also, for button-limited devices, a long press is translated to KEY_ENTER.
- if (is_long_press && IsTextVisible()) {
- EnqueueKey(KEY_ENTER);
- return IGNORE;
- }
+ if (reboot_enabled) {
+ ++consecutive_power_keys;
+ if (consecutive_power_keys >= 7) {
+ return REBOOT;
+ }
}
+ } else {
+ consecutive_power_keys = 0;
+ }
- // 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;
- }
-
- 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) {
diff --git a/ui.h b/ui.h
index 823eb65..3d9afec 100644
--- a/ui.h
+++ b/ui.h
@@ -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;
+ // --- 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;
+ // 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;
- // Set progress bar position (0.0 - 1.0 within the scope defined
- // by the last call to ShowProgress).
- virtual void SetProgress(float fraction) = 0;
+ // Sets 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 ---
+ // --- text log ---
- virtual void ShowText(bool visible) = 0;
+ virtual void ShowText(bool visible) = 0;
- virtual bool IsTextVisible() = 0;
+ virtual bool IsTextVisible() = 0;
- virtual bool WasTextEverVisible() = 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;
+ // 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;
+ virtual void ShowFile(const char* filename) = 0;
- // --- key handling ---
+ // --- key handling ---
- // Wait for a key and return it. May return -1 after timeout.
- virtual int WaitKey();
+ // Waits for a key and return it. May return -1 after timeout.
+ virtual int WaitKey();
- virtual bool IsKeyPressed(int key);
- virtual bool IsLongPress();
+ 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 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();
+ // Returns true if it has a power key.
+ virtual bool HasPowerKey() const;
- // 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);
+ // Returns true if it supports touch inputs.
+ virtual bool HasTouchScreen() const;
- // 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);
+ // Erases any queued-up keys.
+ virtual void FlushKeys();
- // 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);
+ // 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);
- // --- menu display ---
+ // 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);
- // 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;
+ // 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);
- // Set the menu highlight to the given index, wrapping if necessary.
- // Returns the actual item selected.
- virtual int SelectMenu(int sel) = 0;
+ // --- menu display ---
- // End menu mode, resetting the text overlay so that ui_print()
- // statements will be displayed.
- virtual void EndMenu() = 0;
+ // 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;
- protected:
- void EnqueueKey(int key_code);
+ // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item
+ // selected.
+ virtual int SelectMenu(int sel) = 0;
- // The locale that's used to show the rendered texts.
- std::string locale_;
- bool rtl_locale_;
+ // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed.
+ virtual void EndMenu() = 0;
- // 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_;
+ protected:
+ void EnqueueKey(int key_code);
- 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;
+ // The locale that's used to show the rendered texts.
+ std::string locale_;
+ bool rtl_locale_;
- int consecutive_power_keys;
- int last_key;
+ // 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_;
- bool has_power_key;
- bool has_up_key;
- bool has_down_key;
+ // Whether we should listen for touch inputs (default: false).
+ bool touch_screen_allowed_;
- struct key_timer_t {
- RecoveryUI* ui;
- int key_code;
- int count;
- };
+ private:
+ // The sensitivity when detecting a swipe.
+ const int kTouchLowThreshold;
+ const int kTouchHighThreshold;
- pthread_t input_thread_;
+ // 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;
- void OnKeyDetected(int key_code);
- int OnInputEvent(int fd, uint32_t epevents);
- void ProcessKey(int key_code, int updown);
+ int consecutive_power_keys;
+ int last_key;
- bool IsUsbConnected();
+ bool has_power_key;
+ bool has_up_key;
+ bool has_down_key;
+ bool has_touch_screen;
- static void* time_key_helper(void* cookie);
- void time_key(int key_code, int count);
+ // 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_;
- void SetLocale(const std::string&);
+ struct key_timer_t {
+ RecoveryUI* ui;
+ int key_code;
+ int count;
+ };
- 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();
+ 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 1acd5ec..33c5fe9 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 @@
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 0000000..16b394e
--- /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 350020f..ba7b7ae 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::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 @@
// (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 @@
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";
}
- break;
+#endif
+ if (dm_block_name == partition) {
+ dm_block_device = DEV_PATH + std::string(namelist[n]->d_name);
+ while (n--) {
+ free(namelist[n]);
+ }
+ break;
+ }
}
free(namelist[n]);
}
@@ -114,11 +124,6 @@
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 @@
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;
- }
-
- 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;
- }
- }
-
+// 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;
+ }
+
+ 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;
}
static int reboot_device() {
@@ -205,7 +254,7 @@
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 @@
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 0000000..46e8bbb
--- /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 30b0535..cef6b96 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -52,6 +52,7 @@
libcrypto_utils \
libcutils \
libtune2fs \
+ libbrotli \
$(tune2fs_static_libraries)
# libupdater (static library)
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index c614ccc..a0b9ad2 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 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 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 @@
buffer.resize(size);
}
-struct RangeSinkState {
- explicit RangeSinkState(RangeSet& rs) : tgt(rs) { };
+/**
+ * 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));
+ };
- int fd;
- const RangeSet& tgt;
- size_t p_block;
- size_t p_remain;
+ bool Finished() const {
+ return next_range_ == tgt_.size() && current_range_left_ == 0;
+ }
+
+ size_t AvailableSpace() const {
+ return tgt_.blocks() * BLOCKSIZE - bytes_written_;
+ }
+
+ // 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;
+ }
+
+ size_t written = 0;
+ while (size > 0) {
+ // Move to the next range as needed.
+ if (!SeekToOutputRange()) {
+ break;
+ }
+
+ size_t write_now = size;
+ if (current_range_left_ < write_now) {
+ write_now = current_range_left_;
+ }
+
+ if (write_all(fd_, data, write_now) == -1) {
+ 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;
+ }
+
+ 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_;
};
-static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) {
- RangeSinkState* rss = reinterpret_cast<RangeSinkState*>(token);
-
- if (rss->p_remain == 0) {
- LOG(ERROR) << "range sink write overrun";
- return 0;
- }
-
- ssize_t written = 0;
- while (size > 0) {
- size_t write_now = size;
-
- if (rss->p_remain < write_now) {
- write_now = rss->p_remain;
- }
-
- if (write_all(rss->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;
- }
- }
- }
-
- return 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 rss to the destination location and signals the
-// condition. When the background thread is done writing, it clears
-// rss and signals the condition again.
-
+/**
+ * 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;
-
- if (nti->rss->p_block == nti->rss->tgt.count) {
- // we have written all the bytes desired by this rss.
-
- pthread_mutex_lock(&nti->mu);
- nti->rss = nullptr;
- pthread_cond_broadcast(&nti->cv);
- pthread_mutex_unlock(&nti->mu);
- }
+ // 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;
}
- return true;
+ data += write_now;
+ size -= write_now;
+
+ 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 bool receive_brotli_new_data(const uint8_t* data, size_t size, void* cookie) {
+ NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
+
+ 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);
+
+ // At this point nti->writer is set, and we own it. The main thread is waiting for it to
+ // disappear from nti.
+
+ 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;
+
+ // 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);
+
+ if (result == BROTLI_DECODER_RESULT_ERROR) {
+ LOG(ERROR) << "Decompression failed with "
+ << BrotliDecoderErrorString(BrotliDecoderGetErrorCode(nti->brotli_decoder_state));
+ return false;
+ }
+
+ 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 void* unzip_new_data(void* cookie) {
- NewThreadInfo* nti = static_cast<NewThreadInfo*>(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);
- return nullptr;
+ }
+ 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;
}
static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>& buffer, int fd) {
- size_t p = 0;
- uint8_t* data = buffer.data();
-
- for (size_t i = 0; i < src.count; ++i) {
- if (!check_lseek(fd, (off64_t) src.pos[i * 2] * BLOCKSIZE, SEEK_SET)) {
- return -1;
- }
-
- size_t size = (src.pos[i * 2 + 1] - src.pos[i * 2]) * BLOCKSIZE;
-
- if (read_all(fd, data + p, size) == -1) {
- return -1;
- }
-
- p += size;
+ size_t p = 0;
+ for (const auto& range : src) {
+ if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) {
+ return -1;
}
- return 0;
+ size_t size = (range.second - range.first) * BLOCKSIZE;
+ if (read_all(fd, buffer.data() + p, size) == -1) {
+ return -1;
+ }
+
+ p += size;
+ }
+
+ return 0;
}
static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, int fd) {
- const uint8_t* data = buffer.data();
-
- 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;
- }
-
- if (!check_lseek(fd, offset, SEEK_SET)) {
- return -1;
- }
-
- if (write_all(fd, data + p, size) == -1) {
- return -1;
- }
-
- p += size;
+ 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 @@
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 @@
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 @@
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 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);
+ // 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 (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;
-
- if (!blocks) {
- blocks = &blockcount;
- }
-
- std::string fn = GetStashFileName(params.stashbase, id, "");
-
- struct stat sb;
- int res = stat(fn.c_str(), &sb);
-
- if (res == -1) {
- if (errno != ENOENT || printnoent) {
- PLOG(ERROR) << "stat \"" << fn << "\" failed";
- PrintHashForMissingStashedBlocks(id, params.fd);
- }
+ if (ReadBlocks(src, buffer, params.fd) == -1) {
+ LOG(ERROR) << "failed to read source blocks in stash map.";
return -1;
- }
-
- LOG(INFO) << " loading " << fn;
-
- if ((sb.st_size % BLOCKSIZE) != 0) {
- LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE;
+ }
+ 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;
}
+ }
- 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;
+ size_t blockcount = 0;
+ if (!blocks) {
+ blocks = &blockcount;
+ }
+
+ std::string fn = GetStashFileName(params.stashbase, id, "");
+
+ 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;
+ }
- allocate(sb.st_size, buffer);
+ LOG(INFO) << " loading " << fn;
- if (read_all(fd, buffer, sb.st_size) == -1) {
- 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;
+ }
+
+ allocate(sb.st_size, buffer);
+
+ if (read_all(fd, buffer, sb.st_size) == -1) {
+ return -1;
+ }
+
+ *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;
+ }
- *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;
- }
-
- 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 @@
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.
+/**
+ * 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);
-static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
- bool* overlap) {
+ // <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;
+ }
- // 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;
+ 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 = RangeSet::Parse(params.tokens[params.cpos++]);
+ *overlap = src.Overlaps(tgt);
+
+ if (ReadBlocks(src, params.buffer, params.fd) == -1) {
+ 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;
+ if (params.cpos >= params.tokens.size()) {
+ // no stashes, only source range
+ return 0;
}
- allocate(src_blocks * BLOCKSIZE, params.buffer);
+ RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]);
+ MoveRange(params.buffer, locs, 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);
- }
-
- if (res == -1) {
- return -1;
- }
-
- if (params.cpos >= params.tokens.size()) {
- // no stashes, only source range
- return 0;
- }
-
- RangeSet locs = parse_range(params.tokens[params.cpos++]);
- MoveRange(params.buffer, locs, params.buffer);
+ // <[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;
}
- // <[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 (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 = parse_range(tokens[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 @@
* <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 @@
*
* 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 (onehash) {
- tgthash = srchash;
- } else {
- if (params.cpos >= params.tokens.size()) {
- LOG(ERROR) << "missing target hash";
- return -1;
- }
- tgthash = params.tokens[params.cpos++];
- }
-
- if (LoadSrcTgtVersion2(params, tgt, src_blocks, &overlap) == -1) {
- return -1;
- }
-
- std::vector<uint8_t> tgtbuffer(tgt.size * BLOCKSIZE);
-
- if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
- return -1;
- }
-
- if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) {
- // Target blocks already have expected content, command should be skipped.
- 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;
-
- 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;
- }
-
- params.stashed += src_blocks;
- // Can be deleted when the write has completed.
- if (!stash_exists) {
- params.freestash = srchash;
- }
- }
-
- // Source blocks have expected content, command can proceed.
- return 0;
- }
-
- if (overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) {
- // Overlapping source blocks were previously stashed, command can proceed.
- // We are recovering from an interrupted command, so we don't know if the
- // stash can safely be deleted after this command.
- return 0;
- }
-
- // Valid source data not available, update cannot be resumed.
- LOG(ERROR) << "partition has unexpected contents";
- PrintHashForCorruptedSourceBlocks(params, params.buffer);
-
- params.isunresumable = true;
-
+ if (params.cpos >= params.tokens.size()) {
+ LOG(ERROR) << "missing source hash";
return -1;
+ }
+
+ std::string srchash = params.tokens[params.cpos++];
+ std::string tgthash;
+
+ if (onehash) {
+ tgthash = srchash;
+ } else {
+ if (params.cpos >= params.tokens.size()) {
+ LOG(ERROR) << "missing target hash";
+ return -1;
+ }
+ tgthash = params.tokens[params.cpos++];
+ }
+
+ // 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 = RangeSet::Parse(params.tokens[params.cpos++]);
+
+ std::vector<uint8_t> tgtbuffer(tgt.blocks() * BLOCKSIZE);
+ if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
+ return -1;
+ }
+
+ // Return now if target blocks already have expected content.
+ if (VerifyBlocks(tgthash, tgtbuffer, tgt.blocks(), false) == 0) {
+ return 1;
+ }
+
+ // Load source blocks.
+ if (LoadSourceBlocks(params, tgt, src_blocks, overlap) == -1) {
+ return -1;
+ }
+
+ if (VerifyBlocks(srchash, params.buffer, *src_blocks, true) == 0) {
+ // If source and target blocks overlap, stash the source blocks so we can
+ // resume from possible write errors. In verify mode, we can skip stashing
+ // because the source blocks won't be overwritten.
+ if (*overlap && params.canwrite) {
+ 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;
+ }
+
+ params.stashed += *src_blocks;
+ // Can be deleted when the write has completed.
+ if (!stash_exists) {
+ params.freestash = srchash;
+ }
+ }
+
+ // Source blocks have expected content, command can proceed.
+ return 0;
+ }
+
+ if (*overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) {
+ // Overlapping source blocks were previously stashed, command can proceed. We are recovering
+ // from an interrupted command, so we don't know if the stash can safely be deleted after this
+ // command.
+ return 0;
+ }
+
+ // Valid source data not available, update cannot be resumed.
+ LOG(ERROR) << "partition has unexpected contents";
+ PrintHashForCorruptedSourceBlocks(params, params.buffer);
+
+ params.isunresumable = true;
+
+ return -1;
}
static int PerformCommandMove(CommandParameters& params) {
size_t blocks = 0;
bool overlap = false;
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 @@
params.freestash.clear();
}
- params.written += tgt.size;
+ params.written += tgt.blocks();
return 0;
}
@@ -1132,13 +1135,13 @@
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 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";
+ RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+
+ LOG(INFO) << " zeroing " << tgt.blocks() << " blocks";
+
+ 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;
- }
+ }
- RangeSet tgt = parse_range(params.tokens[params.cpos++]);
+ if (!check_lseek(params.fd, offset, SEEK_SET)) {
+ return -1;
+ }
- LOG(INFO) << " zeroing " << tgt.size << " blocks";
-
- allocate(BLOCKSIZE, params.buffer);
- memset(params.buffer.data(), 0, BLOCKSIZE);
-
- 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;
- }
- }
+ 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 = RangeSet::Parse(params.tokens[params.cpos++]);
- RangeSet tgt = parse_range(params.tokens[params.cpos++]);
+ if (params.canwrite) {
+ LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data";
- if (params.canwrite) {
- LOG(INFO) << " writing " << tgt.size << " blocks of new data";
+ pthread_mutex_lock(¶ms.nti.mu);
+ params.nti.writer = std::make_unique<RangeSinkWriter>(params.fd, tgt);
+ pthread_cond_broadcast(¶ms.nti.cv);
- 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;
- }
-
- 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);
-
- while (params.nti.rss) {
- pthread_cond_wait(¶ms.nti.cv, ¶ms.nti.mu);
- }
-
+ 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 << "]";
- }
+ LOG(INFO) << "patching " << blocks << " blocks to " << tgt.blocks();
+ Value patch_value(
+ VAL_BLOB, std::string(reinterpret_cast<const char*>(params.patch_start + offset), len));
- 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 << "]";
+ 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;
+ }
+ }
+
+ // 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 << "]";
}
+ }
- if (!params.freestash.empty()) {
- FreeStash(params.stashbase, params.freestash);
- params.freestash.clear();
- }
+ if (!params.freestash.empty()) {
+ FreeStash(params.stashbase, params.freestash);
+ params.freestash.clear();
+ }
- params.written += tgt.size;
+ params.written += tgt.blocks();
- return 0;
+ 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";
+ 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 (params.cpos >= params.tokens.size()) {
+ LOG(ERROR) << "missing target blocks for erase";
+ return -1;
+ }
+
+ RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+
+ 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;
+ }
}
+ }
- 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;
- }
-
- RangeSet tgt = parse_range(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;
- }
- }
- }
-
- return 0;
+ return 0;
}
// Definitions for transfer list command functions
@@ -1429,10 +1415,10 @@
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 @@
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 @@
}
// 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* 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());
+ 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;
+ }
+
+ 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("");
+ }
+
+ 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 = RangeSet::Parse(ranges->data);
+
+ SHA_CTX ctx;
+ SHA1_Init(&ctx);
+
+ 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 = 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);
}
+ }
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1_Final(digest, &ctx);
- 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();
-
- 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("");
- }
-
- RangeSet rs = parse_range(ranges->data);
-
- 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("");
- }
-
- 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("");
- }
-
- SHA1_Update(&ctx, buffer.data(), BLOCKSIZE);
- }
- }
- 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* 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());
+ 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;
+ }
+
+ 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("");
+ }
+
+ // 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, O_RDWR);
+
+ 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("");
+ }
+
+ fec_status status;
+ if (!fh.get_status(status)) {
+ ErrorAbort(state, kLibfecFailure, "failed to read FEC status");
+ return StringValue("");
+ }
+
+ 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("");
+ }
+
+ // 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.
}
-
- 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();
-
- 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...";
-
- // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read
- fec::io fh(filename->data.c_str(), O_RDWR);
-
- 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("");
- }
-
- fec_status status;
-
- if (!fh.get_status(status)) {
- ErrorAbort(state, kLibfecFailure, "failed to read FEC status");
- 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.
- }
- }
- 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 0000000..fad0380
--- /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 3adb37f..0d473fc 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 @@
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 @@
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 @@
}
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(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location;
+ 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 << ": e2fsdroid failed (" << status << ") on " << location;
return StringValue("");
}
return StringValue(location);
@@ -330,9 +351,11 @@
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 @@
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 @@
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 @@
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 e4b8e46..25bd541 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -94,7 +94,7 @@
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 @@
}
}
- 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 @@
if (updater_info.package_zip) {
CloseArchive(updater_info.package_zip);
}
- sysReleaseMap(&map);
return 0;
}
diff --git a/verifier.cpp b/verifier.cpp
index 2ef9c4c..18437fb 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -474,81 +474,80 @@
// 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;
+ std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "re"), 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;
+ }
+ }
+
+ if (cert.key_type == Certificate::KEY_TYPE_RSA) {
+ cert.rsa = parse_rsa_key(f.get(), exponent);
+ if (!cert.rsa) {
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;
}
- 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;
- }
- }
-
- if (cert.key_type == Certificate::KEY_TYPE_RSA) {
- cert.rsa = parse_rsa_key(f.get(), exponent);
- if (!cert.rsa) {
- 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 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;
- }
+ // 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;
+ }
+ return true;
}
diff --git a/vr_device.cpp b/vr_device.cpp
new file mode 100644
index 0000000..61e15cb
--- /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 0000000..1251672
--- /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 0000000..d996c14
--- /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 0000000..3268130
--- /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 e2ab44d..0000000
--- 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 9a1d315..0000000
--- 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 6c02865..624116c 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -45,167 +45,164 @@
// 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());
+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 x = (gr_fb_width() - width) / 2;
- int y = (gr_fb_height() - height) / 2;
-
- gr_blit(surface, 0, 0, width, height, x, y);
+ 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 x = (gr_fb_width() - width) / 2;
+ int y = (gr_fb_height() - height) / 2;
+
+ 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);
- }
+ 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.
- DrawTextLines(x + 4, &y, HEADERS);
- SetColor(HEADER);
- DrawTextLines(x + 4, &y, menu_headers_);
+ // 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;
- }
+ // 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);
+ // 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;
+ 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);
}
-
- 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;
- }
+ 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 @@
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 @@
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();
+ std::vector<off_t> offsets;
+ offsets.push_back(ftello(fp));
+ ClearText();
- struct stat sb;
- fstat(fileno(fp), &sb);
+ 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;
+ 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();
}
+
+ 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;
+ }
+ }
+ }
}
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);
+ 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::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::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);
+ 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);
}
diff --git a/wear_ui.h b/wear_ui.h
index 4cd852f..3bd90b6 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -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