Merge up to android-8.1.0_r1 and fix conflicts
Change-Id: I2dc060134d15ec9f015a606cb24ef8276f6af1fc
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
subdirs = [
|
||||
// "bootloader_message",
|
||||
// "otautil",
|
||||
]
|
||||
+58
-11
@@ -590,7 +590,7 @@ endif
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_SRC_FILES := fuse_sideload.cpp
|
||||
LOCAL_CLANG := true
|
||||
LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter
|
||||
LOCAL_CFLAGS := -Wall -Werror
|
||||
LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
@@ -605,13 +605,35 @@ else
|
||||
endif
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
# static libfusesideload
|
||||
# =============================== (required to fix build errors in 8.1 due to use by tests)
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_SRC_FILES := fuse_sideload.cpp
|
||||
LOCAL_CLANG := true
|
||||
LOCAL_CFLAGS := -Wall -Werror
|
||||
LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
LOCAL_MODULE := libfusesideload
|
||||
LOCAL_SHARED_LIBRARIES := libcutils libc
|
||||
ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0)
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes
|
||||
LOCAL_STATIC_LIBRARIES += libmincrypttwrp
|
||||
LOCAL_CFLAGS += -DUSE_MINCRYPT
|
||||
else
|
||||
LOCAL_STATIC_LIBRARIES += libcrypto_static
|
||||
endif
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# libmounts (static library)
|
||||
# ===============================
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_SRC_FILES := mounts.cpp
|
||||
LOCAL_CLANG := true
|
||||
LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror
|
||||
LOCAL_CFLAGS := \
|
||||
-Wall \
|
||||
-Werror
|
||||
LOCAL_MODULE := libmounts
|
||||
LOCAL_STATIC_LIBRARIES := libbase
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# librecovery (static library)
|
||||
@@ -619,7 +641,7 @@ include $(BUILD_STATIC_LIBRARY)
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_SRC_FILES := \
|
||||
install.cpp
|
||||
LOCAL_CFLAGS := -Wno-unused-parameter -Werror
|
||||
LOCAL_CFLAGS := -Wall -Werror
|
||||
LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
|
||||
|
||||
ifeq ($(AB_OTA_UPDATER),true)
|
||||
@@ -632,7 +654,8 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
libvintf_recovery \
|
||||
libcrypto_utils \
|
||||
libcrypto \
|
||||
libbase
|
||||
libbase \
|
||||
libziparchive \
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
@@ -661,6 +684,7 @@ else
|
||||
LOCAL_SHARED_LIBRARIES += libcrypto libbase
|
||||
LOCAL_SRC_FILES += verifier.cpp asn1_decoder.cpp
|
||||
endif
|
||||
|
||||
ifeq ($(AB_OTA_UPDATER),true)
|
||||
LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
|
||||
endif
|
||||
@@ -679,7 +703,6 @@ include $(BUILD_SHARED_LIBRARY)
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_CLANG := true
|
||||
LOCAL_MODULE := libverifier
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_SRC_FILES := \
|
||||
asn1_decoder.cpp \
|
||||
verifier.cpp \
|
||||
@@ -687,16 +710,40 @@ LOCAL_SRC_FILES := \
|
||||
LOCAL_STATIC_LIBRARIES := libcrypto_static
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# Wear default device
|
||||
# ===============================
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_SRC_FILES := wear_device.cpp
|
||||
|
||||
# Should match TARGET_RECOVERY_UI_LIB in BoardConfig.mk.
|
||||
LOCAL_MODULE := librecovery_ui_wear
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# vr headset default device
|
||||
# ===============================
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := vr_device.cpp
|
||||
|
||||
# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
|
||||
LOCAL_MODULE := librecovery_ui_vr
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
commands_recovery_local_path := $(LOCAL_PATH)
|
||||
include $(LOCAL_PATH)/tests/Android.mk \
|
||||
$(LOCAL_PATH)/tools/Android.mk \
|
||||
|
||||
include \
|
||||
$(LOCAL_PATH)/applypatch/Android.mk \
|
||||
$(LOCAL_PATH)/boot_control/Android.mk \
|
||||
$(LOCAL_PATH)/edify/Android.mk \
|
||||
$(LOCAL_PATH)/otafault/Android.mk \
|
||||
$(LOCAL_PATH)/bootloader_message/Android.mk \
|
||||
$(LOCAL_PATH)/bootloader_message_twrp/Android.mk \
|
||||
$(LOCAL_PATH)/tests/Android.mk \
|
||||
$(LOCAL_PATH)/tools/Android.mk \
|
||||
$(LOCAL_PATH)/updater/Android.mk \
|
||||
$(LOCAL_PATH)/update_verifier/Android.mk \
|
||||
$(LOCAL_PATH)/applypatch/Android.mk
|
||||
$(LOCAL_PATH)/bootloader_message/Android.mk \
|
||||
$(LOCAL_PATH)/bootloader_message_twrp/Android.mk
|
||||
|
||||
ifeq ($(wildcard system/core/uncrypt/Android.mk),)
|
||||
include $(commands_recovery_local_path)/uncrypt/Android.mk
|
||||
|
||||
+133
-125
@@ -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 @@ static void maybe_restart_adbd() {
|
||||
int
|
||||
apply_from_adb(const char* install_file, pid_t* child_pid) {
|
||||
|
||||
stop_adbd();
|
||||
set_usb_driver(true);
|
||||
stop_adbd();
|
||||
set_usb_driver(true);
|
||||
/*
|
||||
int apply_from_adb(RecoveryUI* ui, bool* wipe_cache, const char* install_file) {
|
||||
modified_flash = true;
|
||||
modified_flash = true;
|
||||
|
||||
stop_adbd(ui);
|
||||
set_usb_driver(ui, true);
|
||||
stop_adbd(ui);
|
||||
set_usb_driver(ui, true);
|
||||
|
||||
ui->Print("\n\nNow send the package you want to apply\n"
|
||||
"to the device with \"adb sideload <filename>\"...\n");
|
||||
ui->Print("\n\nNow send the package you want to apply\n"
|
||||
"to the device with \"adb sideload <filename>\"...\n");
|
||||
*/
|
||||
pid_t child;
|
||||
if ((child = fork()) == 0) {
|
||||
execl("/sbin/recovery", "recovery", "--adbd", install_file, NULL);
|
||||
_exit(-1);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+20
-25
@@ -27,6 +27,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -45,7 +46,7 @@
|
||||
#include "print_sha1.h"
|
||||
|
||||
static int LoadPartitionContents(const std::string& filename, FileContents* file);
|
||||
static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token);
|
||||
static size_t FileSink(const unsigned char* data, size_t len, int fd);
|
||||
static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
|
||||
const std::string& target_filename,
|
||||
const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data);
|
||||
@@ -224,8 +225,8 @@ int SaveFileContents(const char* filename, const FileContents* file) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd);
|
||||
if (bytes_written != static_cast<ssize_t>(file->data.size())) {
|
||||
size_t bytes_written = FileSink(file->data.data(), file->data.size(), fd);
|
||||
if (bytes_written != file->data.size()) {
|
||||
printf("short write of \"%s\" (%zd bytes of %zu): %s\n", filename, bytes_written,
|
||||
file->data.size(), strerror(errno));
|
||||
return -1;
|
||||
@@ -531,25 +532,17 @@ int ShowLicenses() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) {
|
||||
int fd = *static_cast<int*>(token);
|
||||
ssize_t done = 0;
|
||||
ssize_t wrote;
|
||||
while (done < len) {
|
||||
wrote = TEMP_FAILURE_RETRY(ota_write(fd, data+done, len-done));
|
||||
if (wrote == -1) {
|
||||
printf("error writing %zd bytes: %s\n", (len-done), strerror(errno));
|
||||
return done;
|
||||
}
|
||||
done += wrote;
|
||||
static size_t FileSink(const unsigned char* data, size_t len, int fd) {
|
||||
size_t done = 0;
|
||||
while (done < len) {
|
||||
ssize_t wrote = TEMP_FAILURE_RETRY(ota_write(fd, data + done, len - done));
|
||||
if (wrote == -1) {
|
||||
printf("error writing %zd bytes: %s\n", (len - done), strerror(errno));
|
||||
return done;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
|
||||
std::string* s = static_cast<std::string*>(token);
|
||||
s->append(reinterpret_cast<const char*>(data), len);
|
||||
return len;
|
||||
done += wrote;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
// Return the amount of free space (in bytes) on the filesystem
|
||||
@@ -745,9 +738,11 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr
|
||||
}
|
||||
|
||||
// We store the decoded output in memory.
|
||||
SinkFn sink = MemorySink;
|
||||
std::string memory_sink_str; // Don't need to reserve space.
|
||||
void* token = &memory_sink_str;
|
||||
SinkFn sink = [&memory_sink_str](const unsigned char* data, size_t len) {
|
||||
memory_sink_str.append(reinterpret_cast<const char*>(data), len);
|
||||
return len;
|
||||
};
|
||||
|
||||
SHA_CTX ctx;
|
||||
SHA1_Init(&ctx);
|
||||
@@ -755,10 +750,10 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr
|
||||
int result;
|
||||
if (use_bsdiff) {
|
||||
result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch.get(), 0,
|
||||
sink, token, &ctx);
|
||||
sink, &ctx);
|
||||
} else {
|
||||
result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink,
|
||||
token, &ctx, bonus_data);
|
||||
&ctx, bonus_data);
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,19 +44,6 @@ static int CheckMode(int argc, const char** argv) {
|
||||
return applypatch_check(argv[2], sha1);
|
||||
}
|
||||
|
||||
static int SpaceMode(int argc, const char** argv) {
|
||||
if (argc != 3) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
size_t bytes;
|
||||
if (!android::base::ParseUint(argv[2], &bytes) || bytes == 0) {
|
||||
printf("can't parse \"%s\" as byte count\n\n", argv[2]);
|
||||
return 1;
|
||||
}
|
||||
return CacheSizeCheck(bytes);
|
||||
}
|
||||
|
||||
// Parse arguments (which should be of the form "<sha1>:<filename>" into the
|
||||
// new parallel arrays *sha1s and *files. Returns true on success.
|
||||
static bool ParsePatchArgs(int argc, const char** argv, std::vector<std::string>* sha1s,
|
||||
@@ -175,13 +162,12 @@ int applypatch_modes(int argc, const char** argv) {
|
||||
"usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> "
|
||||
"[<src-sha1>:<patch> ...]\n"
|
||||
" or %s -c <file> [<sha1> ...]\n"
|
||||
" or %s -s <bytes>\n"
|
||||
" or %s -l\n"
|
||||
"\n"
|
||||
"Filenames may be of the form\n"
|
||||
" EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n"
|
||||
"to specify reading from or writing to an EMMC partition.\n\n",
|
||||
argv[0], argv[0], argv[0], argv[0]);
|
||||
argv[0], argv[0], argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -191,8 +177,6 @@ int applypatch_modes(int argc, const char** argv) {
|
||||
result = ShowLicenses();
|
||||
} else if (strncmp(argv[1], "-c", 3) == 0) {
|
||||
result = CheckMode(argc, argv);
|
||||
} else if (strncmp(argv[1], "-s", 3) == 0) {
|
||||
result = SpaceMode(argc, argv);
|
||||
} else {
|
||||
result = PatchMode(argc, argv);
|
||||
}
|
||||
|
||||
+29
-19
@@ -23,10 +23,14 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <bspatch.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "applypatch/applypatch.h"
|
||||
#include "openssl/sha.h"
|
||||
#include "print_sha1.h"
|
||||
|
||||
void ShowBSDiffLicense() {
|
||||
puts("The bsdiff library used herein is:\n"
|
||||
@@ -60,25 +64,31 @@ void ShowBSDiffLicense() {
|
||||
);
|
||||
}
|
||||
|
||||
int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, const Value* patch,
|
||||
ssize_t patch_offset, SinkFn sink, void* token, SHA_CTX* ctx) {
|
||||
auto sha_sink = [&](const uint8_t* data, size_t len) {
|
||||
len = sink(data, len, token);
|
||||
int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch,
|
||||
size_t patch_offset, SinkFn sink, SHA_CTX* ctx) {
|
||||
auto sha_sink = [&sink, &ctx](const uint8_t* data, size_t len) {
|
||||
len = sink(data, len);
|
||||
if (ctx) SHA1_Update(ctx, data, len);
|
||||
return len;
|
||||
};
|
||||
return bsdiff::bspatch(old_data, old_size,
|
||||
reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]),
|
||||
patch->data.size(), sha_sink);
|
||||
}
|
||||
|
||||
int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, const Value* patch,
|
||||
ssize_t patch_offset, std::vector<unsigned char>* new_data) {
|
||||
auto vector_sink = [new_data](const uint8_t* data, size_t len) {
|
||||
new_data->insert(new_data->end(), data, data + len);
|
||||
return len;
|
||||
};
|
||||
return bsdiff::bspatch(old_data, old_size,
|
||||
reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]),
|
||||
patch->data.size(), vector_sink);
|
||||
}
|
||||
CHECK(patch != nullptr);
|
||||
CHECK_LE(patch_offset, patch->data.size());
|
||||
|
||||
int result = bsdiff::bspatch(old_data, old_size,
|
||||
reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]),
|
||||
patch->data.size() - patch_offset, sha_sink);
|
||||
if (result != 0) {
|
||||
LOG(ERROR) << "bspatch failed, result: " << result;
|
||||
// print SHA1 of the patch in the case of a data error.
|
||||
if (result == 2) {
|
||||
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||
SHA1(reinterpret_cast<const uint8_t*>(patch->data.data() + patch_offset),
|
||||
patch->data.size() - patch_offset, digest);
|
||||
std::string patch_sha1 = print_sha1(digest);
|
||||
LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: "
|
||||
<< patch_sha1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
+14
-11
@@ -693,6 +693,20 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
|
||||
continue;
|
||||
}
|
||||
|
||||
// The footer contains the size of the uncompressed data. Double-check to make sure that it
|
||||
// matches the size of the data we got when we actually did the decompression.
|
||||
size_t footer_index = pos + raw_data_len + GZIP_FOOTER_LEN - 4;
|
||||
if (sz - footer_index < 4) {
|
||||
printf("Warning: invalid footer position; treating as a nomal chunk\n");
|
||||
continue;
|
||||
}
|
||||
size_t footer_size = get_unaligned<uint32_t>(img->data() + footer_index);
|
||||
if (footer_size != uncompressed_len) {
|
||||
printf("Warning: footer size %zu != decompressed size %zu; treating as a nomal chunk\n",
|
||||
footer_size, uncompressed_len);
|
||||
continue;
|
||||
}
|
||||
|
||||
ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len);
|
||||
uncompressed_data.resize(uncompressed_len);
|
||||
body.SetUncompressedData(std::move(uncompressed_data));
|
||||
@@ -704,17 +718,6 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
|
||||
chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN);
|
||||
|
||||
pos += GZIP_FOOTER_LEN;
|
||||
|
||||
// The footer (that we just skipped over) contains the size of
|
||||
// the uncompressed data. Double-check to make sure that it
|
||||
// matches the size of the data we got when we actually did
|
||||
// the decompression.
|
||||
size_t footer_size = get_unaligned<uint32_t>(img->data() + pos - 4);
|
||||
if (footer_size != body.DataLengthForPatch()) {
|
||||
printf("Error: footer size %zu != decompressed size %zu\n", footer_size,
|
||||
body.GetRawDataLength());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Use a normal chunk to take all the contents until the next gzip chunk (or EOF); we expect
|
||||
// the number of chunks to be small (5 for typical boot and recovery images).
|
||||
|
||||
@@ -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
|
||||
|
||||
+99
-68
@@ -26,12 +26,14 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/memory.h>
|
||||
#include <applypatch/applypatch.h>
|
||||
#include <applypatch/imgdiff.h>
|
||||
#include <android-base/memory.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <zlib.h>
|
||||
|
||||
@@ -43,12 +45,91 @@ static inline int32_t Read4(const void *address) {
|
||||
return android::base::get_unaligned<int32_t>(address);
|
||||
}
|
||||
|
||||
int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
|
||||
const unsigned char* patch_data, ssize_t patch_size,
|
||||
SinkFn sink, void* token) {
|
||||
// This function is a wrapper of ApplyBSDiffPatch(). It has a custom sink function to deflate the
|
||||
// patched data and stream the deflated data to output.
|
||||
static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_len,
|
||||
const Value* patch, size_t patch_offset,
|
||||
const char* deflate_header, SinkFn sink, SHA_CTX* ctx) {
|
||||
size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32));
|
||||
int level = Read4(deflate_header + 40);
|
||||
int method = Read4(deflate_header + 44);
|
||||
int window_bits = Read4(deflate_header + 48);
|
||||
int mem_level = Read4(deflate_header + 52);
|
||||
int strategy = Read4(deflate_header + 56);
|
||||
|
||||
std::unique_ptr<z_stream, decltype(&deflateEnd)> strm(new z_stream(), deflateEnd);
|
||||
strm->zalloc = Z_NULL;
|
||||
strm->zfree = Z_NULL;
|
||||
strm->opaque = Z_NULL;
|
||||
strm->avail_in = 0;
|
||||
strm->next_in = nullptr;
|
||||
int ret = deflateInit2(strm.get(), level, method, window_bits, mem_level, strategy);
|
||||
if (ret != Z_OK) {
|
||||
LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Define a custom sink wrapper that feeds to bspatch. It deflates the available patch data on
|
||||
// the fly and outputs the compressed data to the given sink.
|
||||
size_t actual_target_length = 0;
|
||||
size_t total_written = 0;
|
||||
static constexpr size_t buffer_size = 32768;
|
||||
auto compression_sink = [&](const uint8_t* data, size_t len) -> size_t {
|
||||
// The input patch length for an update never exceeds INT_MAX.
|
||||
strm->avail_in = len;
|
||||
strm->next_in = data;
|
||||
do {
|
||||
std::vector<uint8_t> buffer(buffer_size);
|
||||
strm->avail_out = buffer_size;
|
||||
strm->next_out = buffer.data();
|
||||
if (actual_target_length + len < expected_target_length) {
|
||||
ret = deflate(strm.get(), Z_NO_FLUSH);
|
||||
} else {
|
||||
ret = deflate(strm.get(), Z_FINISH);
|
||||
}
|
||||
if (ret != Z_OK && ret != Z_STREAM_END) {
|
||||
LOG(ERROR) << "Failed to deflate stream: " << ret;
|
||||
// zero length indicates an error in the sink function of bspatch().
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t have = buffer_size - strm->avail_out;
|
||||
total_written += have;
|
||||
if (sink(buffer.data(), have) != have) {
|
||||
LOG(ERROR) << "Failed to write " << have << " compressed bytes to output.";
|
||||
return 0;
|
||||
}
|
||||
if (ctx) SHA1_Update(ctx, buffer.data(), have);
|
||||
} while ((strm->avail_in != 0 || strm->avail_out == 0) && ret != Z_STREAM_END);
|
||||
|
||||
actual_target_length += len;
|
||||
return len;
|
||||
};
|
||||
|
||||
if (ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
LOG(ERROR) << "ret is expected to be Z_STREAM_END, but it's " << ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected_target_length != actual_target_length) {
|
||||
LOG(ERROR) << "target length is expected to be " << expected_target_length << ", but it's "
|
||||
<< actual_target_length;
|
||||
return false;
|
||||
}
|
||||
LOG(DEBUG) << "bspatch writes " << total_written << " bytes in total to streaming output.";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
|
||||
size_t patch_size, SinkFn sink) {
|
||||
Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size));
|
||||
|
||||
return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr);
|
||||
return ApplyImagePatch(old_data, old_size, &patch, sink, nullptr, nullptr);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -57,8 +138,8 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
|
||||
* file, and update the SHA context with the output data as well.
|
||||
* Return 0 on success.
|
||||
*/
|
||||
int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch,
|
||||
SinkFn sink, void* token, SHA_CTX* ctx, const Value* bonus_data) {
|
||||
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink,
|
||||
SHA_CTX* ctx, const Value* bonus_data) {
|
||||
if (patch->data.size() < 12) {
|
||||
printf("patch too short to contain header\n");
|
||||
return -1;
|
||||
@@ -97,11 +178,14 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value
|
||||
size_t src_len = static_cast<size_t>(Read8(normal_header + 8));
|
||||
size_t patch_offset = static_cast<size_t>(Read8(normal_header + 16));
|
||||
|
||||
if (src_start + src_len > static_cast<size_t>(old_size)) {
|
||||
if (src_start + src_len > old_size) {
|
||||
printf("source data too short\n");
|
||||
return -1;
|
||||
}
|
||||
ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, token, ctx);
|
||||
if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) {
|
||||
printf("Failed to apply bsdiff patch.\n");
|
||||
return -1;
|
||||
}
|
||||
} else if (type == CHUNK_RAW) {
|
||||
const char* raw_header = &patch->data[pos];
|
||||
pos += 4;
|
||||
@@ -110,15 +194,14 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t data_len = Read4(raw_header);
|
||||
size_t data_len = static_cast<size_t>(Read4(raw_header));
|
||||
|
||||
if (pos + data_len > patch->data.size()) {
|
||||
printf("failed to read chunk %d raw data\n", i);
|
||||
return -1;
|
||||
}
|
||||
if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len);
|
||||
if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len, token) !=
|
||||
data_len) {
|
||||
if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len) != data_len) {
|
||||
printf("failed to write chunk %d raw data\n", i);
|
||||
return -1;
|
||||
}
|
||||
@@ -136,14 +219,8 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value
|
||||
size_t src_len = static_cast<size_t>(Read8(deflate_header + 8));
|
||||
size_t patch_offset = static_cast<size_t>(Read8(deflate_header + 16));
|
||||
size_t expanded_len = static_cast<size_t>(Read8(deflate_header + 24));
|
||||
size_t target_len = static_cast<size_t>(Read8(deflate_header + 32));
|
||||
int level = Read4(deflate_header + 40);
|
||||
int method = Read4(deflate_header + 44);
|
||||
int windowBits = Read4(deflate_header + 48);
|
||||
int memLevel = Read4(deflate_header + 52);
|
||||
int strategy = Read4(deflate_header + 56);
|
||||
|
||||
if (src_start + src_len > static_cast<size_t>(old_size)) {
|
||||
if (src_start + src_len > old_size) {
|
||||
printf("source data too short\n");
|
||||
return -1;
|
||||
}
|
||||
@@ -198,58 +275,12 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value
|
||||
}
|
||||
}
|
||||
|
||||
// Next, apply the bsdiff patch (in memory) to the uncompressed data.
|
||||
std::vector<unsigned char> uncompressed_target_data;
|
||||
// TODO(senj): Remove the only usage of ApplyBSDiffPatchMem here,
|
||||
// replace it with ApplyBSDiffPatch with a custom sink function that
|
||||
// wraps the given sink function to stream output to save memory.
|
||||
if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, patch, patch_offset,
|
||||
&uncompressed_target_data) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (uncompressed_target_data.size() != target_len) {
|
||||
printf("expected target len to be %zu, but it's %zu\n", target_len,
|
||||
uncompressed_target_data.size());
|
||||
if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch,
|
||||
patch_offset, deflate_header, sink, ctx)) {
|
||||
LOG(ERROR) << "Fail to apply streaming bspatch.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Now compress the target data and append it to the output.
|
||||
|
||||
// we're done with the expanded_source data buffer, so we'll
|
||||
// reuse that memory to receive the output of deflate.
|
||||
if (expanded_source.size() < 32768U) {
|
||||
expanded_source.resize(32768U);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<unsigned char>& temp_data = expanded_source;
|
||||
|
||||
// now the deflate stream
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = uncompressed_target_data.size();
|
||||
strm.next_in = uncompressed_target_data.data();
|
||||
int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
|
||||
if (ret != Z_OK) {
|
||||
printf("failed to init uncompressed data deflation: %d\n", ret);
|
||||
return -1;
|
||||
}
|
||||
do {
|
||||
strm.avail_out = temp_data.size();
|
||||
strm.next_out = temp_data.data();
|
||||
ret = deflate(&strm, Z_FINISH);
|
||||
ssize_t have = temp_data.size() - strm.avail_out;
|
||||
|
||||
if (sink(temp_data.data(), have, token) != have) {
|
||||
printf("failed to write %zd compressed bytes to output\n", have);
|
||||
return -1;
|
||||
}
|
||||
if (ctx) SHA1_Update(ctx, temp_data.data(), have);
|
||||
} while (ret != Z_STREAM_END);
|
||||
deflateEnd(&strm);
|
||||
}
|
||||
} else {
|
||||
printf("patch chunk %d is unknown type %d\n", i, type);
|
||||
return -1;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -41,7 +42,7 @@ struct FileContents {
|
||||
// and use it as the source instead.
|
||||
#define CACHE_TEMP_SOURCE "/cache/saved.file"
|
||||
|
||||
typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*);
|
||||
using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
|
||||
|
||||
// applypatch.cpp
|
||||
int ShowLicenses();
|
||||
@@ -66,18 +67,12 @@ int SaveFileContents(const char* filename, const FileContents* file);
|
||||
|
||||
// bspatch.cpp
|
||||
void ShowBSDiffLicense();
|
||||
int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch, ssize_t patch_offset,
|
||||
SinkFn sink, void* token, SHA_CTX* ctx);
|
||||
int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch, ssize_t patch_offset,
|
||||
std::vector<unsigned char>* new_data);
|
||||
int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch,
|
||||
size_t patch_offset, SinkFn sink, SHA_CTX* ctx);
|
||||
|
||||
// imgpatch.cpp
|
||||
int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
|
||||
const Value* patch,
|
||||
SinkFn sink, void* token, SHA_CTX* ctx,
|
||||
const Value* bonus_data);
|
||||
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink,
|
||||
SHA_CTX* ctx, const Value* bonus_data);
|
||||
|
||||
// freecache.cpp
|
||||
int MakeFreeSpaceOnCache(size_t bytes_needed);
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -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)
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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"],
|
||||
}
|
||||
@@ -144,6 +144,13 @@ static bool write_misc_partition(const void* p, size_t size, const std::string&
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string get_bootloader_message_blk_device(std::string* err) {
|
||||
std::string misc_blk_device = get_misc_blk_device(err);
|
||||
if (misc_blk_device.empty()) return "";
|
||||
if (!wait_for_device(misc_blk_device, err)) return "";
|
||||
return misc_blk_device;
|
||||
}
|
||||
|
||||
bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device,
|
||||
std::string* err) {
|
||||
return read_misc_partition(boot, sizeof(*boot), misc_blk_device,
|
||||
|
||||
@@ -191,6 +191,11 @@ static_assert(sizeof(struct bootloader_control) ==
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Return the block device name for the bootloader message partition and waits
|
||||
// for the device for up to 10 seconds. In case of error returns the empty
|
||||
// string.
|
||||
std::string get_bootloader_message_blk_device(std::string* err);
|
||||
|
||||
// Read bootloader message into boot. Error message will be set in err.
|
||||
bool read_bootloader_message(bootloader_message* boot, std::string* err);
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/macros.h>
|
||||
|
||||
#include "expr.h"
|
||||
#include "yydefs.h"
|
||||
#include "parser.h"
|
||||
@@ -121,6 +123,7 @@ arglist: /* empty */ {
|
||||
$$->emplace_back($1);
|
||||
}
|
||||
| arglist ',' expr {
|
||||
UNUSED($1);
|
||||
$$->push_back(std::unique_ptr<Expr>($3));
|
||||
}
|
||||
;
|
||||
|
||||
@@ -24,6 +24,7 @@ enum ErrorCode {
|
||||
kZipOpenFailure,
|
||||
kBootreasonInBlacklist,
|
||||
kPackageCompatibilityFailure,
|
||||
kScriptExecutionFailure,
|
||||
};
|
||||
|
||||
enum CauseCode {
|
||||
@@ -43,6 +44,7 @@ enum CauseCode {
|
||||
kTune2FsFailure,
|
||||
kRebootFailure,
|
||||
kPackageExtractFileFailure,
|
||||
kPatchApplicationFailure,
|
||||
kVendorFailure = 200
|
||||
};
|
||||
|
||||
|
||||
+143
-140
@@ -235,11 +235,13 @@ static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse
|
||||
return NO_STATUS;
|
||||
}
|
||||
|
||||
static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
|
||||
static int handle_flush(void* /* data */, struct fuse_data* /* fd */,
|
||||
const struct fuse_in_header* /* hdr */) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
|
||||
static int handle_release(void* /* data */, struct fuse_data* /* fd */,
|
||||
const struct fuse_in_header* /* hdr */) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -375,166 +377,167 @@ static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_he
|
||||
return NO_STATUS;
|
||||
}
|
||||
|
||||
int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
|
||||
uint64_t file_size, uint32_t block_size)
|
||||
{
|
||||
int result;
|
||||
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,
|
||||
|
||||
@@ -1370,13 +1370,19 @@ int PageManager::LoadPackage(std::string name, std::string package, std::string
|
||||
tw_h_offset = 0;
|
||||
if (!TWFunc::Path_Exists(package))
|
||||
return -1;
|
||||
#ifdef USE_MINZIP
|
||||
if (sysMapFile(package.c_str(), &map) != 0) {
|
||||
#else
|
||||
if (!map.MapFile(package)) {
|
||||
#endif
|
||||
LOGERR("Failed to map '%s'\n", package.c_str());
|
||||
goto error;
|
||||
}
|
||||
if (!zip.Open(package.c_str(), &map)) {
|
||||
LOGERR("Unable to open zip archive '%s'\n", package.c_str());
|
||||
#ifdef USE_MINZIP
|
||||
sysReleaseMap(&map);
|
||||
#endif
|
||||
goto error;
|
||||
}
|
||||
ctx.zip = &zip;
|
||||
@@ -1418,7 +1424,9 @@ int PageManager::LoadPackage(std::string name, std::string package, std::string
|
||||
|
||||
if (ctx.zip) {
|
||||
ctx.zip->Close();
|
||||
#ifdef USE_MINZIP
|
||||
sysReleaseMap(&map);
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -1426,7 +1434,9 @@ error:
|
||||
// Sometimes we get here without a real error
|
||||
if (ctx.zip) {
|
||||
ctx.zip->Close();
|
||||
#ifdef USE_MINZIP
|
||||
sysReleaseMap(&map);
|
||||
#endif
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
+194
-202
@@ -27,6 +27,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
@@ -49,97 +50,79 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "error_code.h"
|
||||
#include "minui/minui.h"
|
||||
#include "otautil/SysUtil.h"
|
||||
#include "otautil/ThermalUtil.h"
|
||||
#include "private/install.h"
|
||||
#include "roots.h"
|
||||
#include "ui.h"
|
||||
#include "verifier.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
#define PUBLIC_KEYS_FILE "/res/keys"
|
||||
static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
|
||||
static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
|
||||
|
||||
// Default allocation of progress bar segments to operations
|
||||
static constexpr int VERIFICATION_PROGRESS_TIME = 60;
|
||||
static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
|
||||
static constexpr float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
|
||||
static constexpr float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
|
||||
|
||||
static std::condition_variable finish_log_temperature;
|
||||
|
||||
// This function parses and returns the build.version.incremental
|
||||
static int parse_build_number(const std::string& str) {
|
||||
static std::string parse_build_number(const std::string& str) {
|
||||
size_t pos = str.find('=');
|
||||
if (pos != std::string::npos) {
|
||||
std::string num_string = android::base::Trim(str.substr(pos+1));
|
||||
int build_number;
|
||||
if (android::base::ParseInt(num_string.c_str(), &build_number, 0)) {
|
||||
return build_number;
|
||||
}
|
||||
return android::base::Trim(str.substr(pos+1));
|
||||
}
|
||||
|
||||
LOG(ERROR) << "Failed to parse build number in " << str;
|
||||
return -1;
|
||||
return "";
|
||||
}
|
||||
|
||||
bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data) {
|
||||
ZipString metadata_path(METADATA_PATH);
|
||||
ZipEntry meta_entry;
|
||||
if (meta_data == nullptr) {
|
||||
LOG(ERROR) << "string* meta_data can't be nullptr";
|
||||
return false;
|
||||
}
|
||||
if (FindEntry(zip, metadata_path, &meta_entry) != 0) {
|
||||
LOG(ERROR) << "Failed to find " << METADATA_PATH << " in update package";
|
||||
return false;
|
||||
}
|
||||
bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
|
||||
CHECK(metadata != nullptr);
|
||||
|
||||
meta_data->resize(meta_entry.uncompressed_length, '\0');
|
||||
if (ExtractToMemory(zip, &meta_entry, reinterpret_cast<uint8_t*>(&(*meta_data)[0]),
|
||||
meta_entry.uncompressed_length) != 0) {
|
||||
LOG(ERROR) << "Failed to read metadata in update package";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
|
||||
ZipString path(METADATA_PATH);
|
||||
ZipEntry entry;
|
||||
if (FindEntry(zip, path, &entry) != 0) {
|
||||
LOG(ERROR) << "Failed to find " << METADATA_PATH;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t length = entry.uncompressed_length;
|
||||
metadata->resize(length, '\0');
|
||||
int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&(*metadata)[0]), length);
|
||||
if (err != 0) {
|
||||
LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
|
||||
static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>& log_buffer) {
|
||||
std::string meta_data;
|
||||
if (!read_metadata_from_package(zip, &meta_data)) {
|
||||
return;
|
||||
}
|
||||
// Examples of the pre-build and post-build strings in metadata:
|
||||
// pre-build-incremental=2943039
|
||||
// post-build-incremental=2951741
|
||||
std::vector<std::string> lines = android::base::Split(meta_data, "\n");
|
||||
for (const std::string& line : lines) {
|
||||
std::string str = android::base::Trim(line);
|
||||
if (android::base::StartsWith(str, "pre-build-incremental")){
|
||||
int source_build = parse_build_number(str);
|
||||
if (source_build != -1) {
|
||||
log_buffer.push_back(android::base::StringPrintf("source_build: %d",
|
||||
source_build));
|
||||
}
|
||||
} else if (android::base::StartsWith(str, "post-build-incremental")) {
|
||||
int target_build = parse_build_number(str);
|
||||
if (target_build != -1) {
|
||||
log_buffer.push_back(android::base::StringPrintf("target_build: %d",
|
||||
target_build));
|
||||
}
|
||||
}
|
||||
static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>* log_buffer) {
|
||||
std::string metadata;
|
||||
if (!read_metadata_from_package(zip, &metadata)) {
|
||||
return;
|
||||
}
|
||||
// Examples of the pre-build and post-build strings in metadata:
|
||||
// pre-build-incremental=2943039
|
||||
// post-build-incremental=2951741
|
||||
std::vector<std::string> lines = android::base::Split(metadata, "\n");
|
||||
for (const std::string& line : lines) {
|
||||
std::string str = android::base::Trim(line);
|
||||
if (android::base::StartsWith(str, "pre-build-incremental")) {
|
||||
std::string source_build = parse_build_number(str);
|
||||
if (!source_build.empty()) {
|
||||
log_buffer->push_back("source_build: " + source_build);
|
||||
}
|
||||
} else if (android::base::StartsWith(str, "post-build-incremental")) {
|
||||
std::string target_build = parse_build_number(str);
|
||||
if (!target_build.empty()) {
|
||||
log_buffer->push_back("target_build: " + target_build);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the update binary from the open zip archive |zip| located at |path| and store into |cmd|
|
||||
// the command line that should be called. The |status_fd| is the file descriptor the child process
|
||||
// should use to report back the progress of the update.
|
||||
int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
|
||||
int status_fd, std::vector<std::string>* cmd);
|
||||
|
||||
#ifdef AB_OTA_UPDATER
|
||||
|
||||
// Parses the metadata of the OTA package in |zip| and checks whether we are
|
||||
@@ -220,8 +203,9 @@ static int check_newer_ab_build(ZipArchiveHandle zip) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
|
||||
int status_fd, std::vector<std::string>* cmd) {
|
||||
int update_binary_command(const std::string& package, ZipArchiveHandle zip,
|
||||
const std::string& binary_path, int /* retry_count */, int status_fd,
|
||||
std::vector<std::string>* cmd) {
|
||||
CHECK(cmd != nullptr);
|
||||
int ret = check_newer_ab_build(zip);
|
||||
if (ret != 0) {
|
||||
@@ -255,8 +239,8 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret
|
||||
}
|
||||
long payload_offset = payload_entry.offset;
|
||||
*cmd = {
|
||||
"/sbin/update_engine_sideload",
|
||||
"--payload=file://" + path,
|
||||
binary_path,
|
||||
"--payload=file://" + package,
|
||||
android::base::StringPrintf("--offset=%ld", payload_offset),
|
||||
"--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
|
||||
android::base::StringPrintf("--status_fd=%d", status_fd),
|
||||
@@ -266,8 +250,9 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret
|
||||
|
||||
#else // !AB_OTA_UPDATER
|
||||
|
||||
int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
|
||||
int status_fd, std::vector<std::string>* cmd) {
|
||||
int update_binary_command(const std::string& package, ZipArchiveHandle zip,
|
||||
const std::string& binary_path, int retry_count, int status_fd,
|
||||
std::vector<std::string>* cmd) {
|
||||
CHECK(cmd != nullptr);
|
||||
|
||||
// On traditional updates we extract the update binary from the package.
|
||||
@@ -279,11 +264,10 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret
|
||||
return INSTALL_CORRUPT;
|
||||
}
|
||||
|
||||
const char* binary = "/tmp/update_binary";
|
||||
unlink(binary);
|
||||
int fd = creat(binary, 0755);
|
||||
unlink(binary_path.c_str());
|
||||
int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755);
|
||||
if (fd == -1) {
|
||||
PLOG(ERROR) << "Failed to create " << binary;
|
||||
PLOG(ERROR) << "Failed to create " << binary_path;
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
|
||||
@@ -295,10 +279,10 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret
|
||||
}
|
||||
|
||||
*cmd = {
|
||||
binary,
|
||||
binary_path,
|
||||
EXPAND(RECOVERY_API_VERSION), // defined in Android.mk
|
||||
std::to_string(status_fd),
|
||||
path,
|
||||
package,
|
||||
};
|
||||
if (retry_count > 0) {
|
||||
cmd->push_back("retry");
|
||||
@@ -307,18 +291,19 @@ int update_binary_command(const std::string& path, ZipArchiveHandle zip, int ret
|
||||
}
|
||||
#endif // !AB_OTA_UPDATER
|
||||
|
||||
static void log_max_temperature(int* max_temperature) {
|
||||
static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) {
|
||||
CHECK(max_temperature != nullptr);
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lck(mtx);
|
||||
while (finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) {
|
||||
while (!logger_finished.load() &&
|
||||
finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) {
|
||||
*max_temperature = std::max(*max_temperature, GetMaxValueFromThermalZone());
|
||||
}
|
||||
}
|
||||
|
||||
// If the package contains an update binary, extract it and run it.
|
||||
static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
|
||||
std::vector<std::string>& log_buffer, int retry_count,
|
||||
static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
|
||||
std::vector<std::string>* log_buffer, int retry_count,
|
||||
int* max_temperature) {
|
||||
read_source_target_build(zip, log_buffer);
|
||||
|
||||
@@ -326,7 +311,13 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_
|
||||
pipe(pipefd);
|
||||
|
||||
std::vector<std::string> args;
|
||||
int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
|
||||
#ifdef AB_OTA_UPDATER
|
||||
int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count,
|
||||
pipefd[1], &args);
|
||||
#else
|
||||
int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1],
|
||||
&args);
|
||||
#endif
|
||||
if (ret) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
@@ -410,7 +401,8 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_
|
||||
}
|
||||
close(pipefd[1]);
|
||||
|
||||
std::thread temperature_logger(log_max_temperature, max_temperature);
|
||||
std::atomic<bool> logger_finished(false);
|
||||
std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished));
|
||||
|
||||
*wipe_cache = false;
|
||||
bool retry_update = false;
|
||||
@@ -461,7 +453,7 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_
|
||||
} else if (command == "log") {
|
||||
if (!args.empty()) {
|
||||
// Save the logging request from updater and write to last_install later.
|
||||
log_buffer.push_back(args);
|
||||
log_buffer->push_back(args);
|
||||
} else {
|
||||
LOG(ERROR) << "invalid \"log\" parameters: " << line;
|
||||
}
|
||||
@@ -474,6 +466,7 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
logger_finished.store(true);
|
||||
finish_log_temperature.notify_one();
|
||||
temperature_logger.join();
|
||||
|
||||
@@ -481,7 +474,7 @@ static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_
|
||||
return INSTALL_RETRY;
|
||||
}
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")";
|
||||
LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")";
|
||||
return INSTALL_ERROR;
|
||||
}
|
||||
|
||||
@@ -556,150 +549,149 @@ bool verify_package_compatibility(ZipArchiveHandle package_zip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static int
|
||||
really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
|
||||
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
|
||||
{
|
||||
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
|
||||
ui->Print("Finding update package...\n");
|
||||
// Give verification half the progress bar...
|
||||
ui->SetProgressType(RecoveryUI::DETERMINATE);
|
||||
ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
|
||||
LOG(INFO) << "Update location: " << path;
|
||||
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";
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
#include <string>
|
||||
#include <ziparchive/zip_archive.h>
|
||||
|
||||
// Install the package specified by root_path. If INSTALL_SUCCESS is
|
||||
// returned and *wipe_cache is true on exit, caller should wipe the
|
||||
// cache partition.
|
||||
int install_package(const char* root_path, bool* wipe_cache, const char* install_file,
|
||||
enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED,
|
||||
INSTALL_RETRY };
|
||||
|
||||
// Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on
|
||||
// exit, caller should wipe the cache partition.
|
||||
int install_package(const std::string& package, bool* wipe_cache, const std::string& install_file,
|
||||
bool needs_mount, int retry_count);
|
||||
|
||||
// Verify the package by ota keys. Return true if the package is verified successfully,
|
||||
@@ -32,9 +34,9 @@ bool verify_package(const unsigned char* package_data, size_t package_size);
|
||||
|
||||
// Read meta data file of the package, write its content in the string pointed by meta_data.
|
||||
// Return true if succeed, otherwise return false.
|
||||
bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data);
|
||||
bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata);
|
||||
|
||||
// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
|
||||
// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
|
||||
// entry doesn't exist.
|
||||
bool verify_package_compatibility(ZipArchiveHandle package_zip);
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CLANG := true
|
||||
LOCAL_MODULE := minadbd_test
|
||||
LOCAL_COMPATIBILITY_SUITE := device-tests
|
||||
LOCAL_SRC_FILES := fuse_adb_provider_test.cpp
|
||||
LOCAL_CFLAGS := $(minadbd_cflags)
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH) system/core/adb
|
||||
|
||||
@@ -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>
|
||||
@@ -14,17 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fuse_adb_provider.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "adb_io.h"
|
||||
#include "fuse_adb_provider.h"
|
||||
|
||||
TEST(fuse_adb_provider, read_block_adb) {
|
||||
adb_data data = {};
|
||||
@@ -46,9 +46,8 @@ TEST(fuse_adb_provider, read_block_adb) {
|
||||
|
||||
uint32_t block = 1234U;
|
||||
const char expected_block[] = "00001234";
|
||||
ASSERT_EQ(0, read_block_adb(reinterpret_cast<void*>(&data), block,
|
||||
reinterpret_cast<uint8_t*>(block_data),
|
||||
sizeof(expected_data) - 1));
|
||||
ASSERT_EQ(0, read_block_adb(static_cast<void*>(&data), block,
|
||||
reinterpret_cast<uint8_t*>(block_data), sizeof(expected_data) - 1));
|
||||
|
||||
// Check that read_block_adb requested the right block.
|
||||
char block_req[sizeof(expected_block)] = {};
|
||||
@@ -80,9 +79,12 @@ TEST(fuse_adb_provider, read_block_adb_fail_write) {
|
||||
|
||||
ASSERT_EQ(0, close(sockets[1]));
|
||||
|
||||
// write(2) raises SIGPIPE since the reading end has been closed. Ignore the signal to avoid
|
||||
// failing the test.
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
char buf[1];
|
||||
ASSERT_EQ(-EIO, read_block_adb(reinterpret_cast<void*>(&data), 0,
|
||||
reinterpret_cast<uint8_t*>(buf), 1));
|
||||
ASSERT_EQ(-EIO, read_block_adb(static_cast<void*>(&data), 0, reinterpret_cast<uint8_t*>(buf), 1));
|
||||
|
||||
close(sockets[0]);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ static void sideload_host_service(int sfd, const std::string& args) {
|
||||
int result = run_adb_fuse(sfd, file_size, block_size);
|
||||
|
||||
printf("sideload_host finished\n");
|
||||
sleep(1);
|
||||
exit(result == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -51,8 +51,11 @@ ifeq ($(TW_NEW_ION_HEAP), true)
|
||||
endif
|
||||
|
||||
LOCAL_STATIC_LIBRARIES += libpng
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES += \
|
||||
libdrm
|
||||
ifneq ($(wildcard external/libdrm/Android.common.mk),)
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES += libdrm_platform
|
||||
else
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES += libdrm
|
||||
endif
|
||||
ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0)
|
||||
LOCAL_CFLAGS += -DHAS_LIBSYNC
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES += libsync_recovery
|
||||
|
||||
+42
-9
@@ -57,40 +57,47 @@ static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
|
||||
return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
|
||||
}
|
||||
|
||||
#ifdef TW_USE_MINUI_WITH_OPTIONAL_TOUCH_EVENTS
|
||||
int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
|
||||
#else
|
||||
#ifdef TW_USE_MINUI_WITH_DATA
|
||||
int ev_init(ev_callback input_cb, void* data) {
|
||||
#else
|
||||
int ev_init(ev_callback input_cb) {
|
||||
#endif
|
||||
bool epollctlfail = false;
|
||||
bool allow_touch_inputs = false;
|
||||
#endif
|
||||
|
||||
g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
|
||||
if (g_epoll_fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool epollctlfail = false;
|
||||
DIR* dir = opendir("/dev/input");
|
||||
if (dir != NULL) {
|
||||
if (dir != nullptr) {
|
||||
dirent* de;
|
||||
while ((de = readdir(dir))) {
|
||||
// Use unsigned long to match ioctl's parameter type.
|
||||
unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
|
||||
|
||||
// fprintf(stderr,"/dev/input/%s\n", de->d_name);
|
||||
if (strncmp(de->d_name, "event", 5)) continue;
|
||||
int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
|
||||
if (fd == -1) continue;
|
||||
|
||||
// Use unsigned long to match ioctl's parameter type.
|
||||
unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
|
||||
|
||||
// Read the evbits of the input device.
|
||||
if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
|
||||
// We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
|
||||
// allowed if allow_touch_inputs is set.
|
||||
if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
|
||||
close(fd);
|
||||
continue;
|
||||
if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
epoll_event ev;
|
||||
@@ -261,3 +268,29 @@ void ev_iterate_available_keys(const std::function<void(int)>& f) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TW_USE_MINUI_WITH_OPTIONAL_TOUCH_EVENTS
|
||||
void ev_iterate_touch_inputs(const std::function<void(int)>& action) {
|
||||
for (size_t i = 0; i < ev_dev_count; ++i) {
|
||||
// Use unsigned long to match ioctl's parameter type.
|
||||
unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {}; // NOLINT
|
||||
if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
|
||||
continue;
|
||||
}
|
||||
if (!test_bit(EV_ABS, ev_bits)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)] = {}; // NOLINT
|
||||
if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_ABS, KEY_MAX), key_bits) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
|
||||
if (test_bit(key_code, key_bits)) {
|
||||
action(key_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -91,7 +91,12 @@ int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data);
|
||||
using ev_callback = std::function<int(int fd, uint32_t epevents)>;
|
||||
using ev_set_key_callback = std::function<int(int code, int value)>;
|
||||
|
||||
#ifdef TW_USE_MINUI_WITH_OPTIONAL_TOUCH_EVENTS
|
||||
int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
|
||||
void ev_iterate_touch_inputs(const std::function<void(int)>& action);
|
||||
#else
|
||||
int ev_init(ev_callback input_cb);
|
||||
#endif
|
||||
int ev_add_fd(int fd, ev_callback cb);
|
||||
int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr,
|
||||
|
||||
snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
|
||||
resPath[sizeof(resPath)-1] = '\0';
|
||||
FILE* fp = fopen(resPath, "rb");
|
||||
FILE* fp = fopen(resPath, "rbe");
|
||||
if (fp == NULL) {
|
||||
result = -1;
|
||||
goto exit;
|
||||
|
||||
+11
-18
@@ -27,6 +27,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
struct MountedVolume {
|
||||
std::string device;
|
||||
std::string mount_point;
|
||||
@@ -60,13 +62,6 @@ bool scan_mounted_volumes() {
|
||||
return true;
|
||||
}
|
||||
|
||||
MountedVolume* find_mounted_volume_by_device(const char* device) {
|
||||
for (size_t i = 0; i < g_mounts_state.size(); ++i) {
|
||||
if (g_mounts_state[i]->device == device) return g_mounts_state[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) {
|
||||
for (size_t i = 0; i < g_mounts_state.size(); ++i) {
|
||||
if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i];
|
||||
@@ -75,15 +70,13 @@ MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) {
|
||||
}
|
||||
|
||||
int unmount_mounted_volume(MountedVolume* volume) {
|
||||
// Intentionally pass the empty string to umount if the caller tries
|
||||
// to unmount a volume they already unmounted using this
|
||||
// function.
|
||||
std::string mount_point = volume->mount_point;
|
||||
volume->mount_point.clear();
|
||||
return umount(mount_point.c_str());
|
||||
}
|
||||
|
||||
int remount_read_only(MountedVolume* volume) {
|
||||
return mount(volume->device.c_str(), volume->mount_point.c_str(), volume->filesystem.c_str(),
|
||||
MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0);
|
||||
// Intentionally pass the empty string to umount if the caller tries to unmount a volume they
|
||||
// already unmounted using this function.
|
||||
std::string mount_point = volume->mount_point;
|
||||
volume->mount_point.clear();
|
||||
int result = umount(mount_point.c_str());
|
||||
if (result == -1) {
|
||||
PLOG(WARNING) << "Failed to umount " << mount_point;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -21,12 +21,8 @@ struct MountedVolume;
|
||||
|
||||
bool scan_mounted_volumes();
|
||||
|
||||
MountedVolume* find_mounted_volume_by_device(const char* device);
|
||||
|
||||
MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point);
|
||||
|
||||
int unmount_mounted_volume(MountedVolume* volume);
|
||||
|
||||
int remount_read_only(MountedVolume* volume);
|
||||
|
||||
#endif
|
||||
|
||||
+6
-1
@@ -29,7 +29,12 @@ otafault_static_libs := \
|
||||
libbase \
|
||||
liblog
|
||||
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_CFLAGS := \
|
||||
-Werror \
|
||||
-Wthread-safety \
|
||||
-Wthread-safety-negative \
|
||||
-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS
|
||||
|
||||
LOCAL_SRC_FILES := config.cpp ota_io.cpp
|
||||
LOCAL_MODULE_TAGS := eng
|
||||
LOCAL_MODULE := libotafault
|
||||
|
||||
+16
-2
@@ -24,10 +24,13 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include <android-base/thread_annotations.h>
|
||||
#include "config.h"
|
||||
|
||||
static std::map<intptr_t, const char*> filename_cache;
|
||||
static std::mutex filename_mutex;
|
||||
static std::map<intptr_t, const char*> filename_cache GUARDED_BY(filename_mutex);
|
||||
static std::string read_fault_file_name = "";
|
||||
static std::string write_fault_file_name = "";
|
||||
static std::string fsync_fault_file_name = "";
|
||||
@@ -55,23 +58,28 @@ bool have_eio_error = false;
|
||||
int ota_open(const char* path, int oflags) {
|
||||
// Let the caller handle errors; we do not care if open succeeds or fails
|
||||
int fd = open(path, oflags);
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
filename_cache[fd] = path;
|
||||
return fd;
|
||||
}
|
||||
|
||||
int ota_open(const char* path, int oflags, mode_t mode) {
|
||||
int fd = open(path, oflags, mode);
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
filename_cache[fd] = path;
|
||||
return fd; }
|
||||
return fd;
|
||||
}
|
||||
|
||||
FILE* ota_fopen(const char* path, const char* mode) {
|
||||
FILE* fh = fopen(path, mode);
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
filename_cache[(intptr_t)fh] = path;
|
||||
return fh;
|
||||
}
|
||||
|
||||
static int __ota_close(int fd) {
|
||||
// descriptors can be reused, so make sure not to leave them in the cache
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
filename_cache.erase(fd);
|
||||
return close(fd);
|
||||
}
|
||||
@@ -85,6 +93,7 @@ int ota_close(unique_fd& fd) {
|
||||
}
|
||||
|
||||
static int __ota_fclose(FILE* fh) {
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
filename_cache.erase(reinterpret_cast<intptr_t>(fh));
|
||||
return fclose(fh);
|
||||
}
|
||||
@@ -99,6 +108,7 @@ int ota_fclose(unique_file& fh) {
|
||||
|
||||
size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) {
|
||||
if (should_fault_inject(OTAIO_READ)) {
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
auto cached = filename_cache.find((intptr_t)stream);
|
||||
const char* cached_path = cached->second;
|
||||
if (cached != filename_cache.end() &&
|
||||
@@ -119,6 +129,7 @@ size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) {
|
||||
|
||||
ssize_t ota_read(int fd, void* buf, size_t nbyte) {
|
||||
if (should_fault_inject(OTAIO_READ)) {
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
auto cached = filename_cache.find(fd);
|
||||
const char* cached_path = cached->second;
|
||||
if (cached != filename_cache.end()
|
||||
@@ -138,6 +149,7 @@ ssize_t ota_read(int fd, void* buf, size_t nbyte) {
|
||||
|
||||
size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) {
|
||||
if (should_fault_inject(OTAIO_WRITE)) {
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
auto cached = filename_cache.find((intptr_t)stream);
|
||||
const char* cached_path = cached->second;
|
||||
if (cached != filename_cache.end() &&
|
||||
@@ -157,6 +169,7 @@ size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) {
|
||||
|
||||
ssize_t ota_write(int fd, const void* buf, size_t nbyte) {
|
||||
if (should_fault_inject(OTAIO_WRITE)) {
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
auto cached = filename_cache.find(fd);
|
||||
const char* cached_path = cached->second;
|
||||
if (cached != filename_cache.end() &&
|
||||
@@ -176,6 +189,7 @@ ssize_t ota_write(int fd, const void* buf, size_t nbyte) {
|
||||
|
||||
int ota_fsync(int fd) {
|
||||
if (should_fault_inject(OTAIO_FSYNC)) {
|
||||
std::lock_guard<std::mutex> lock(filename_mutex);
|
||||
auto cached = filename_cache.find(fd);
|
||||
const char* cached_path = cached->second;
|
||||
if (cached != filename_cache.end() &&
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
+58
-68
@@ -16,14 +16,12 @@
|
||||
|
||||
#include "SysUtil.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdint.h> // SIZE_MAX
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -32,9 +30,7 @@
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
static bool sysMapFD(int fd, MemMapping* pMap) {
|
||||
CHECK(pMap != nullptr);
|
||||
|
||||
bool MemMapping::MapFD(int fd) {
|
||||
struct stat sb;
|
||||
if (fstat(fd, &sb) == -1) {
|
||||
PLOG(ERROR) << "fstat(" << fd << ") failed";
|
||||
@@ -47,50 +43,49 @@ static bool sysMapFD(int fd, MemMapping* pMap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pMap->addr = static_cast<unsigned char*>(memPtr);
|
||||
pMap->length = sb.st_size;
|
||||
pMap->ranges.push_back({ memPtr, static_cast<size_t>(sb.st_size) });
|
||||
addr = static_cast<unsigned char*>(memPtr);
|
||||
length = sb.st_size;
|
||||
ranges_.clear();
|
||||
ranges_.emplace_back(MappedRange{ memPtr, static_cast<size_t>(sb.st_size) });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// A "block map" which looks like this (from uncrypt/uncrypt.cpp):
|
||||
//
|
||||
// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device
|
||||
// 49652 4096 # file size in bytes, block size
|
||||
// 3 # count of block ranges
|
||||
// 1000 1008 # block range 0
|
||||
// 2100 2102 # ... block range 1
|
||||
// 30 33 # ... block range 2
|
||||
// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device
|
||||
// 49652 4096 # file size in bytes, block size
|
||||
// 3 # count of block ranges
|
||||
// 1000 1008 # block range 0
|
||||
// 2100 2102 # ... block range 1
|
||||
// 30 33 # ... block range 2
|
||||
//
|
||||
// Each block range represents a half-open interval; the line "30 33"
|
||||
// reprents the blocks [30, 31, 32].
|
||||
static int sysMapBlockFile(const char* filename, MemMapping* pMap) {
|
||||
CHECK(pMap != nullptr);
|
||||
|
||||
// Each block range represents a half-open interval; the line "30 33" reprents the blocks
|
||||
// [30, 31, 32].
|
||||
bool MemMapping::MapBlockFile(const std::string& filename) {
|
||||
std::string content;
|
||||
if (!android::base::ReadFileToString(filename, &content)) {
|
||||
PLOG(ERROR) << "Failed to read " << filename;
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n");
|
||||
if (lines.size() < 4) {
|
||||
LOG(ERROR) << "Block map file is too short: " << lines.size();
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t size;
|
||||
unsigned int blksize;
|
||||
if (sscanf(lines[1].c_str(), "%zu %u", &size, &blksize) != 2) {
|
||||
size_t blksize;
|
||||
if (sscanf(lines[1].c_str(), "%zu %zu", &size, &blksize) != 2) {
|
||||
LOG(ERROR) << "Failed to parse file size and block size: " << lines[1];
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t range_count;
|
||||
if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) {
|
||||
LOG(ERROR) << "Failed to parse block map header: " << lines[2];
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t blocks;
|
||||
@@ -101,14 +96,14 @@ static int sysMapBlockFile(const char* filename, MemMapping* pMap) {
|
||||
lines.size() != 3 + range_count) {
|
||||
LOG(ERROR) << "Invalid data in block map file: size " << size << ", blksize " << blksize
|
||||
<< ", range_count " << range_count << ", lines " << lines.size();
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reserve enough contiguous address space for the whole file.
|
||||
void* reserve = mmap64(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
if (reserve == MAP_FAILED) {
|
||||
PLOG(ERROR) << "failed to reserve address space";
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& block_dev = lines[0];
|
||||
@@ -116,10 +111,10 @@ static int sysMapBlockFile(const char* filename, MemMapping* pMap) {
|
||||
if (fd == -1) {
|
||||
PLOG(ERROR) << "failed to open block device " << block_dev;
|
||||
munmap(reserve, blocks * blksize);
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
pMap->ranges.resize(range_count);
|
||||
ranges_.clear();
|
||||
|
||||
unsigned char* next = static_cast<unsigned char*>(reserve);
|
||||
size_t remaining_size = blocks * blksize;
|
||||
@@ -129,84 +124,79 @@ static int sysMapBlockFile(const char* filename, MemMapping* pMap) {
|
||||
|
||||
size_t start, end;
|
||||
if (sscanf(line.c_str(), "%zu %zu\n", &start, &end) != 2) {
|
||||
LOG(ERROR) << "failed to parse range " << i << " in block map: " << line;
|
||||
LOG(ERROR) << "failed to parse range " << i << ": " << line;
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
size_t length = (end - start) * blksize;
|
||||
if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) {
|
||||
LOG(ERROR) << "unexpected range in block map: " << start << " " << end;
|
||||
size_t range_size = (end - start) * blksize;
|
||||
if (end <= start || (end - start) > SIZE_MAX / blksize || range_size > remaining_size) {
|
||||
LOG(ERROR) << "Invalid range: " << start << " " << end;
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
|
||||
static_cast<off64_t>(start) * blksize);
|
||||
if (addr == MAP_FAILED) {
|
||||
PLOG(ERROR) << "failed to map block " << i;
|
||||
void* range_start = mmap64(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
|
||||
static_cast<off64_t>(start) * blksize);
|
||||
if (range_start == MAP_FAILED) {
|
||||
PLOG(ERROR) << "failed to map range " << i << ": " << line;
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
pMap->ranges[i].addr = addr;
|
||||
pMap->ranges[i].length = length;
|
||||
ranges_.emplace_back(MappedRange{ range_start, range_size });
|
||||
|
||||
next += length;
|
||||
remaining_size -= length;
|
||||
next += range_size;
|
||||
remaining_size -= range_size;
|
||||
}
|
||||
if (success && remaining_size != 0) {
|
||||
LOG(ERROR) << "ranges in block map are invalid: remaining_size = " << remaining_size;
|
||||
LOG(ERROR) << "Invalid ranges: remaining_size " << remaining_size;
|
||||
success = false;
|
||||
}
|
||||
if (!success) {
|
||||
munmap(reserve, blocks * blksize);
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
pMap->addr = static_cast<unsigned char*>(reserve);
|
||||
pMap->length = size;
|
||||
addr = static_cast<unsigned char*>(reserve);
|
||||
length = size;
|
||||
|
||||
LOG(INFO) << "mmapped " << range_count << " ranges";
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int sysMapFile(const char* fn, MemMapping* pMap) {
|
||||
if (fn == nullptr || pMap == nullptr) {
|
||||
LOG(ERROR) << "Invalid argument(s)";
|
||||
return -1;
|
||||
bool MemMapping::MapFile(const std::string& fn) {
|
||||
if (fn.empty()) {
|
||||
LOG(ERROR) << "Empty filename";
|
||||
return false;
|
||||
}
|
||||
|
||||
*pMap = {};
|
||||
|
||||
if (fn[0] == '@') {
|
||||
if (sysMapBlockFile(fn + 1, pMap) != 0) {
|
||||
// Block map file "@/cache/recovery/block.map".
|
||||
if (!MapBlockFile(fn.substr(1))) {
|
||||
LOG(ERROR) << "Map of '" << fn << "' failed";
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// This is a regular file.
|
||||
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn, O_RDONLY)));
|
||||
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY)));
|
||||
if (fd == -1) {
|
||||
PLOG(ERROR) << "Unable to open '" << fn << "'";
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sysMapFD(fd, pMap)) {
|
||||
if (!MapFD(fd)) {
|
||||
LOG(ERROR) << "Map of '" << fn << "' failed";
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release a memory mapping.
|
||||
*/
|
||||
void sysReleaseMap(MemMapping* pMap) {
|
||||
std::for_each(pMap->ranges.cbegin(), pMap->ranges.cend(), [](const MappedRange& range) {
|
||||
MemMapping::~MemMapping() {
|
||||
for (const auto& range : ranges_) {
|
||||
if (munmap(range.addr, range.length) == -1) {
|
||||
PLOG(ERROR) << "munmap(" << range.addr << ", " << range.length << ") failed";
|
||||
PLOG(ERROR) << "Failed to munmap(" << range.addr << ", " << range.length << ")";
|
||||
}
|
||||
});
|
||||
pMap->ranges.clear();
|
||||
};
|
||||
ranges_.clear();
|
||||
}
|
||||
|
||||
+23
-25
@@ -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
|
||||
|
||||
+6
-2
@@ -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);
|
||||
|
||||
+15
-15
@@ -59,21 +59,21 @@ static void check_and_fclose(FILE *fp, const char *name) {
|
||||
}
|
||||
|
||||
static void copy_file(const char* source, const char* destination) {
|
||||
FILE* dest_fp = fopen(destination, "w");
|
||||
if (dest_fp == nullptr) {
|
||||
PLOG(ERROR) << "Can't open " << destination;
|
||||
} else {
|
||||
FILE* source_fp = fopen(source, "r");
|
||||
if (source_fp != nullptr) {
|
||||
char buf[4096];
|
||||
size_t bytes;
|
||||
while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
|
||||
fwrite(buf, 1, bytes, dest_fp);
|
||||
}
|
||||
check_and_fclose(source_fp, source);
|
||||
}
|
||||
check_and_fclose(dest_fp, destination);
|
||||
FILE* dest_fp = fopen(destination, "we");
|
||||
if (dest_fp == nullptr) {
|
||||
PLOG(ERROR) << "Can't open " << destination;
|
||||
} else {
|
||||
FILE* source_fp = fopen(source, "re");
|
||||
if (source_fp != nullptr) {
|
||||
char buf[4096];
|
||||
size_t bytes;
|
||||
while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
|
||||
fwrite(buf, 1, bytes, dest_fp);
|
||||
}
|
||||
check_and_fclose(source_fp, source);
|
||||
}
|
||||
check_and_fclose(dest_fp, destination);
|
||||
}
|
||||
}
|
||||
|
||||
static bool rotated = false;
|
||||
@@ -120,7 +120,7 @@ int main(int argc, char **argv) {
|
||||
*/
|
||||
bool has_cache = false;
|
||||
static const char mounts_file[] = "/proc/mounts";
|
||||
FILE *fp = fopen(mounts_file, "r");
|
||||
FILE* fp = fopen(mounts_file, "re");
|
||||
if (!fp) {
|
||||
PLOG(ERROR) << "failed to open " << mounts_file;
|
||||
} else {
|
||||
|
||||
+80
-74
@@ -40,7 +40,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <adb.h>
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/parseint.h>
|
||||
@@ -114,8 +113,9 @@ static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
|
||||
static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
|
||||
static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
|
||||
static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
|
||||
// We will try to apply the update package 5 times at most in case of an I/O error.
|
||||
static const int EIO_RETRY_COUNT = 4;
|
||||
// We will try to apply the update package 5 times at most in case of an I/O error or
|
||||
// bspatch | imgpatch error.
|
||||
static const int RETRY_LIMIT = 4;
|
||||
static const int BATTERY_READ_TIMEOUT_IN_SEC = 10;
|
||||
// GmsCore enters recovery mode to install package when having enough battery
|
||||
// percentage. Normally, the threshold is 40% without charger and 20% with charger.
|
||||
@@ -250,7 +250,7 @@ static void redirect_stdio(const char* filename) {
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
// Child logger to actually write to the log file.
|
||||
FILE* log_fp = fopen(filename, "a");
|
||||
FILE* log_fp = fopen(filename, "ae");
|
||||
if (log_fp == nullptr) {
|
||||
PLOG(ERROR) << "fopen \"" << filename << "\" failed";
|
||||
close(pipefd[0]);
|
||||
@@ -419,27 +419,27 @@ static void copy_log_file_to_pmsg(const char* source, const char* destination) {
|
||||
static off_t tmplog_offset = 0;
|
||||
|
||||
static void copy_log_file(const char* source, const char* destination, bool append) {
|
||||
FILE* dest_fp = fopen_path(destination, append ? "a" : "w");
|
||||
if (dest_fp == nullptr) {
|
||||
PLOG(ERROR) << "Can't open " << destination;
|
||||
} else {
|
||||
FILE* source_fp = fopen(source, "r");
|
||||
if (source_fp != nullptr) {
|
||||
if (append) {
|
||||
fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write
|
||||
}
|
||||
char buf[4096];
|
||||
size_t bytes;
|
||||
while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
|
||||
fwrite(buf, 1, bytes, dest_fp);
|
||||
}
|
||||
if (append) {
|
||||
tmplog_offset = ftello(source_fp);
|
||||
}
|
||||
check_and_fclose(source_fp, source);
|
||||
}
|
||||
check_and_fclose(dest_fp, destination);
|
||||
FILE* dest_fp = fopen_path(destination, append ? "ae" : "we");
|
||||
if (dest_fp == nullptr) {
|
||||
PLOG(ERROR) << "Can't open " << destination;
|
||||
} else {
|
||||
FILE* source_fp = fopen(source, "re");
|
||||
if (source_fp != nullptr) {
|
||||
if (append) {
|
||||
fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write
|
||||
}
|
||||
char buf[4096];
|
||||
size_t bytes;
|
||||
while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
|
||||
fwrite(buf, 1, bytes, dest_fp);
|
||||
}
|
||||
if (append) {
|
||||
tmplog_offset = ftello(source_fp);
|
||||
}
|
||||
check_and_fclose(source_fp, source);
|
||||
}
|
||||
check_and_fclose(dest_fp, destination);
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_logs() {
|
||||
@@ -478,40 +478,38 @@ static void copy_logs() {
|
||||
sync();
|
||||
}
|
||||
|
||||
// clear the recovery command and prepare to boot a (hopefully working) system,
|
||||
// Clear the recovery command and prepare to boot a (hopefully working) system,
|
||||
// copy our log file to cache as well (for the system to read). This function is
|
||||
// idempotent: call it as many times as you like.
|
||||
static void finish_recovery() {
|
||||
// Save the locale to cache, so if recovery is next started up
|
||||
// without a --locale argument (eg, directly from the bootloader)
|
||||
// it will use the last-known locale.
|
||||
if (!locale.empty() && has_cache) {
|
||||
LOG(INFO) << "Saving locale \"" << locale << "\"";
|
||||
|
||||
FILE* fp = fopen_path(LOCALE_FILE, "w");
|
||||
if (!android::base::WriteStringToFd(locale, fileno(fp))) {
|
||||
PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
|
||||
}
|
||||
check_and_fclose(fp, LOCALE_FILE);
|
||||
// Save the locale to cache, so if recovery is next started up without a '--locale' argument
|
||||
// (e.g., directly from the bootloader) it will use the last-known locale.
|
||||
if (!locale.empty() && has_cache) {
|
||||
LOG(INFO) << "Saving locale \"" << locale << "\"";
|
||||
if (ensure_path_mounted(LOCALE_FILE) != 0) {
|
||||
LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
|
||||
} else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
|
||||
PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
copy_logs();
|
||||
copy_logs();
|
||||
|
||||
// Reset to normal system boot so recovery won't cycle indefinitely.
|
||||
std::string err;
|
||||
if (!clear_bootloader_message(&err)) {
|
||||
LOG(ERROR) << "Failed to clear BCB message: " << err;
|
||||
// Reset to normal system boot so recovery won't cycle indefinitely.
|
||||
std::string err;
|
||||
if (!clear_bootloader_message(&err)) {
|
||||
LOG(ERROR) << "Failed to clear BCB message: " << err;
|
||||
}
|
||||
|
||||
// Remove the command file, so recovery won't repeat indefinitely.
|
||||
if (has_cache) {
|
||||
if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
|
||||
LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
|
||||
}
|
||||
ensure_path_unmounted(CACHE_ROOT);
|
||||
}
|
||||
|
||||
// Remove the command file, so recovery won't repeat indefinitely.
|
||||
if (has_cache) {
|
||||
if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
|
||||
LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
|
||||
}
|
||||
ensure_path_unmounted(CACHE_ROOT);
|
||||
}
|
||||
|
||||
sync(); // For good measure.
|
||||
sync(); // For good measure.
|
||||
}
|
||||
|
||||
struct saved_log_file {
|
||||
@@ -552,7 +550,7 @@ static bool erase_volume(const char* volume) {
|
||||
}
|
||||
|
||||
std::string data(sb.st_size, '\0');
|
||||
FILE* f = fopen(path.c_str(), "rb");
|
||||
FILE* f = fopen(path.c_str(), "rbe");
|
||||
fread(&data[0], 1, data.size(), f);
|
||||
fclose(f);
|
||||
|
||||
@@ -580,7 +578,7 @@ static bool erase_volume(const char* volume) {
|
||||
ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
|
||||
return true;
|
||||
}
|
||||
FILE* f = fopen(CONVERT_FBE_FILE, "wb");
|
||||
FILE* f = fopen(CONVERT_FBE_FILE, "wbe");
|
||||
if (!f) {
|
||||
ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
|
||||
return true;
|
||||
@@ -760,12 +758,13 @@ static bool wipe_data(Device* device) {
|
||||
}
|
||||
|
||||
static bool prompt_and_wipe_data(Device* device) {
|
||||
// Use a single string and let ScreenRecoveryUI handles the wrapping.
|
||||
const char* const headers[] = {
|
||||
"Can't load Android system. Your data may be corrupt.",
|
||||
"If you continue to get this message, you may need to",
|
||||
"perform a factory data reset and erase all user data",
|
||||
"Can't load Android system. Your data may be corrupt. "
|
||||
"If you continue to get this message, you may need to "
|
||||
"perform a factory data reset and erase all user data "
|
||||
"stored on this device.",
|
||||
NULL
|
||||
nullptr
|
||||
};
|
||||
const char* const items[] = {
|
||||
"Try again",
|
||||
@@ -1157,7 +1156,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
|
||||
{
|
||||
bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
|
||||
if (adb) {
|
||||
status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
|
||||
status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
|
||||
} else {
|
||||
status = apply_from_sdcard(device, &should_wipe_cache);
|
||||
}
|
||||
@@ -1528,9 +1527,9 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
if (status != INSTALL_SUCCESS) {
|
||||
ui->Print("Installation aborted.\n");
|
||||
// When I/O error happens, reboot and retry installation EIO_RETRY_COUNT
|
||||
// When I/O error happens, reboot and retry installation RETRY_LIMIT
|
||||
// times before we abandon this OTA update.
|
||||
if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
|
||||
if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
|
||||
copy_logs();
|
||||
set_retry_bootloader_message(retry_count, args);
|
||||
// Print retry count on screen.
|
||||
@@ -1582,7 +1581,7 @@ int main(int argc, char **argv) {
|
||||
if (!sideload_auto_reboot) {
|
||||
ui->ShowText(true);
|
||||
}
|
||||
status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
|
||||
status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
|
||||
if (status == INSTALL_SUCCESS && should_wipe_cache) {
|
||||
if (!wipe_cache(false, device)) {
|
||||
status = INSTALL_ERROR;
|
||||
@@ -1593,25 +1592,32 @@ int main(int argc, char **argv) {
|
||||
ui->Print("Rebooting automatically.\n");
|
||||
}
|
||||
} else if (!just_exit) {
|
||||
status = INSTALL_NONE; // No command specified
|
||||
ui->SetBackground(RecoveryUI::NO_COMMAND);
|
||||
// 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;
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <ext4_utils/make_ext4fs.h>
|
||||
#include <android-base/properties.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <ext4_utils/wipe.h>
|
||||
#include <fs_mgr.h>
|
||||
|
||||
@@ -172,6 +174,23 @@ static int exec_cmd(const char* path, char* const argv[]) {
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
static ssize_t get_file_size(int fd, uint64_t reserve_len) {
|
||||
struct stat buf;
|
||||
int ret = fstat(fd, &buf);
|
||||
if (ret) return 0;
|
||||
|
||||
ssize_t computed_size;
|
||||
if (S_ISREG(buf.st_mode)) {
|
||||
computed_size = buf.st_size - reserve_len;
|
||||
} else if (S_ISBLK(buf.st_mode)) {
|
||||
computed_size = get_block_device_size(fd) - reserve_len;
|
||||
} else {
|
||||
computed_size = 0;
|
||||
}
|
||||
|
||||
return computed_size;
|
||||
}
|
||||
|
||||
int format_volume(const char* volume, const char* directory) {
|
||||
Volume* v = volume_for_path(volume);
|
||||
if (v == NULL) {
|
||||
@@ -211,35 +230,81 @@ int format_volume(const char* volume, const char* directory) {
|
||||
if (v->length != 0) {
|
||||
length = v->length;
|
||||
} else if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0) {
|
||||
length = -CRYPT_FOOTER_OFFSET;
|
||||
android::base::unique_fd fd(open(v->blk_device, O_RDONLY));
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "get_file_size: failed to open " << v->blk_device;
|
||||
return -1;
|
||||
}
|
||||
length = get_file_size(fd.get(), CRYPT_FOOTER_OFFSET);
|
||||
if (length <= 0) {
|
||||
LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
int result;
|
||||
if (strcmp(v->fs_type, "ext4") == 0) {
|
||||
if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
|
||||
result = make_ext4fs_directory_align(v->blk_device, length, volume, sehandle,
|
||||
directory, v->erase_blk_size, v->logical_blk_size);
|
||||
} else {
|
||||
result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory);
|
||||
}
|
||||
static constexpr int block_size = 4096;
|
||||
int raid_stride = v->logical_blk_size / block_size;
|
||||
int raid_stripe_width = v->erase_blk_size / block_size;
|
||||
|
||||
// stride should be the max of 8kb and logical block size
|
||||
if (v->logical_blk_size != 0 && v->logical_blk_size < 8192) {
|
||||
raid_stride = 8192 / block_size;
|
||||
}
|
||||
|
||||
const char* mke2fs_argv[] = { "/sbin/mke2fs_static",
|
||||
"-F",
|
||||
"-t",
|
||||
"ext4",
|
||||
"-b",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr };
|
||||
|
||||
int i = 5;
|
||||
std::string block_size_str = std::to_string(block_size);
|
||||
mke2fs_argv[i++] = block_size_str.c_str();
|
||||
|
||||
std::string ext_args;
|
||||
if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
|
||||
ext_args = android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride,
|
||||
raid_stripe_width);
|
||||
mke2fs_argv[i++] = "-E";
|
||||
mke2fs_argv[i++] = ext_args.c_str();
|
||||
}
|
||||
|
||||
mke2fs_argv[i++] = v->blk_device;
|
||||
|
||||
std::string size_str = std::to_string(length / block_size);
|
||||
if (length != 0) {
|
||||
mke2fs_argv[i++] = size_str.c_str();
|
||||
}
|
||||
|
||||
result = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
|
||||
if (result == 0 && directory != nullptr) {
|
||||
const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static",
|
||||
"-e",
|
||||
"-f",
|
||||
directory,
|
||||
"-a",
|
||||
volume,
|
||||
v->blk_device,
|
||||
nullptr };
|
||||
|
||||
result = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv));
|
||||
}
|
||||
} else { /* Has to be f2fs because we checked earlier. */
|
||||
if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) {
|
||||
LOG(ERROR) << "format_volume: crypt footer + negative length (" << length
|
||||
<< ") not supported on " << v->fs_type;
|
||||
return -1;
|
||||
}
|
||||
if (length < 0) {
|
||||
LOG(ERROR) << "format_volume: negative length (" << length
|
||||
<< ") not supported on " << v->fs_type;
|
||||
return -1;
|
||||
}
|
||||
char *num_sectors;
|
||||
if (asprintf(&num_sectors, "%zd", length / 512) <= 0) {
|
||||
char *num_sectors = nullptr;
|
||||
if (length >= 512 && asprintf(&num_sectors, "%zd", length / 512) <= 0) {
|
||||
LOG(ERROR) << "format_volume: failed to create " << v->fs_type
|
||||
<< " command for " << v->blk_device;
|
||||
return -1;
|
||||
}
|
||||
const char *f2fs_path = "/sbin/mkfs.f2fs";
|
||||
const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, NULL};
|
||||
const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, nullptr};
|
||||
|
||||
result = exec_cmd(f2fs_path, (char* const*)f2fs_argv);
|
||||
free(num_sectors);
|
||||
@@ -260,26 +325,29 @@ int format_volume(const char* volume) {
|
||||
}
|
||||
|
||||
int setup_install_mounts() {
|
||||
if (fstab == NULL) {
|
||||
LOG(ERROR) << "can't set up install mounts: no fstab loaded";
|
||||
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;
|
||||
}
|
||||
|
||||
+497
-462
File diff suppressed because it is too large
Load Diff
+123
-100
@@ -21,6 +21,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ui.h"
|
||||
|
||||
@@ -30,144 +31,166 @@ struct GRSurface;
|
||||
// Implementation of RecoveryUI appropriate for devices with a screen
|
||||
// (shows an icon + a progress bar, text logging, menu, etc.)
|
||||
class ScreenRecoveryUI : public RecoveryUI {
|
||||
public:
|
||||
ScreenRecoveryUI();
|
||||
public:
|
||||
ScreenRecoveryUI();
|
||||
|
||||
bool Init(const std::string& locale) override;
|
||||
bool Init(const std::string& locale) override;
|
||||
|
||||
// overall recovery state ("background image")
|
||||
void SetBackground(Icon icon);
|
||||
void SetSystemUpdateText(bool security_update);
|
||||
// overall recovery state ("background image")
|
||||
void SetBackground(Icon icon) override;
|
||||
void SetSystemUpdateText(bool security_update) override;
|
||||
|
||||
// progress indicator
|
||||
void SetProgressType(ProgressType type) override;
|
||||
void ShowProgress(float portion, float seconds) override;
|
||||
void SetProgress(float fraction) override;
|
||||
// progress indicator
|
||||
void SetProgressType(ProgressType type) override;
|
||||
void ShowProgress(float portion, float seconds) override;
|
||||
void SetProgress(float fraction) override;
|
||||
|
||||
void SetStage(int current, int max) override;
|
||||
void SetStage(int current, int max) override;
|
||||
|
||||
// text log
|
||||
void ShowText(bool visible) override;
|
||||
bool IsTextVisible() override;
|
||||
bool WasTextEverVisible() override;
|
||||
// text log
|
||||
void ShowText(bool visible) override;
|
||||
bool IsTextVisible() override;
|
||||
bool WasTextEverVisible() override;
|
||||
|
||||
// printing messages
|
||||
void Print(const char* fmt, ...) __printflike(2, 3);
|
||||
void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3);
|
||||
void ShowFile(const char* filename);
|
||||
// printing messages
|
||||
void Print(const char* fmt, ...) override __printflike(2, 3);
|
||||
void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3);
|
||||
void ShowFile(const char* filename) override;
|
||||
|
||||
// menu display
|
||||
void StartMenu(const char* const * headers, const char* const * items,
|
||||
int initial_selection);
|
||||
int SelectMenu(int sel);
|
||||
void EndMenu();
|
||||
// menu display
|
||||
void StartMenu(const char* const* headers, const char* const* items,
|
||||
int initial_selection) override;
|
||||
int SelectMenu(int sel) override;
|
||||
void EndMenu() override;
|
||||
|
||||
void KeyLongPress(int);
|
||||
void KeyLongPress(int) override;
|
||||
|
||||
void Redraw();
|
||||
void Redraw();
|
||||
|
||||
enum UIElement {
|
||||
HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO
|
||||
};
|
||||
void SetColor(UIElement e);
|
||||
enum UIElement {
|
||||
HEADER,
|
||||
MENU,
|
||||
MENU_SEL_BG,
|
||||
MENU_SEL_BG_ACTIVE,
|
||||
MENU_SEL_FG,
|
||||
LOG,
|
||||
TEXT_FILL,
|
||||
INFO
|
||||
};
|
||||
void SetColor(UIElement e) const;
|
||||
|
||||
protected:
|
||||
Icon currentIcon;
|
||||
protected:
|
||||
// The margin that we don't want to use for showing texts (e.g. round screen, or screen with
|
||||
// rounded corners).
|
||||
const int kMarginWidth;
|
||||
const int kMarginHeight;
|
||||
|
||||
// The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
|
||||
float density_;
|
||||
// The layout to use.
|
||||
int layout_;
|
||||
// Number of frames per sec (default: 30) for both parts of the animation.
|
||||
const int kAnimationFps;
|
||||
|
||||
GRSurface* error_icon;
|
||||
// The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
|
||||
const float density_;
|
||||
|
||||
GRSurface* erasing_text;
|
||||
GRSurface* error_text;
|
||||
GRSurface* installing_text;
|
||||
GRSurface* no_command_text;
|
||||
Icon currentIcon;
|
||||
|
||||
GRSurface** introFrames;
|
||||
GRSurface** loopFrames;
|
||||
// The layout to use.
|
||||
int layout_;
|
||||
|
||||
GRSurface* progressBarEmpty;
|
||||
GRSurface* progressBarFill;
|
||||
GRSurface* stageMarkerEmpty;
|
||||
GRSurface* stageMarkerFill;
|
||||
GRSurface* error_icon;
|
||||
|
||||
ProgressType progressBarType;
|
||||
GRSurface* erasing_text;
|
||||
GRSurface* error_text;
|
||||
GRSurface* installing_text;
|
||||
GRSurface* no_command_text;
|
||||
|
||||
float progressScopeStart, progressScopeSize, progress;
|
||||
double progressScopeTime, progressScopeDuration;
|
||||
GRSurface** introFrames;
|
||||
GRSurface** loopFrames;
|
||||
|
||||
// true when both graphics pages are the same (except for the progress bar).
|
||||
bool pagesIdentical;
|
||||
GRSurface* progressBarEmpty;
|
||||
GRSurface* progressBarFill;
|
||||
GRSurface* stageMarkerEmpty;
|
||||
GRSurface* stageMarkerFill;
|
||||
|
||||
size_t text_cols_, text_rows_;
|
||||
ProgressType progressBarType;
|
||||
|
||||
// Log text overlay, displayed when a magic key is pressed.
|
||||
char** text_;
|
||||
size_t text_col_, text_row_, text_top_;
|
||||
float progressScopeStart, progressScopeSize, progress;
|
||||
double progressScopeTime, progressScopeDuration;
|
||||
|
||||
bool show_text;
|
||||
bool show_text_ever; // has show_text ever been true?
|
||||
// true when both graphics pages are the same (except for the progress bar).
|
||||
bool pagesIdentical;
|
||||
|
||||
char** menu_;
|
||||
const char* const* menu_headers_;
|
||||
bool show_menu;
|
||||
int menu_items, menu_sel;
|
||||
size_t text_cols_, text_rows_;
|
||||
|
||||
// An alternate text screen, swapped with 'text_' when we're viewing a log file.
|
||||
char** file_viewer_text_;
|
||||
// Log text overlay, displayed when a magic key is pressed.
|
||||
char** text_;
|
||||
size_t text_col_, text_row_, text_top_;
|
||||
|
||||
pthread_t progress_thread_;
|
||||
bool show_text;
|
||||
bool show_text_ever; // has show_text ever been true?
|
||||
|
||||
// Number of intro frames and loop frames in the animation.
|
||||
size_t intro_frames;
|
||||
size_t loop_frames;
|
||||
std::vector<std::string> menu_;
|
||||
const char* const* menu_headers_;
|
||||
bool show_menu;
|
||||
int menu_items, menu_sel;
|
||||
|
||||
size_t current_frame;
|
||||
bool intro_done;
|
||||
// An alternate text screen, swapped with 'text_' when we're viewing a log file.
|
||||
char** file_viewer_text_;
|
||||
|
||||
// Number of frames per sec (default: 30) for both parts of the animation.
|
||||
int animation_fps;
|
||||
pthread_t progress_thread_;
|
||||
|
||||
int stage, max_stage;
|
||||
// Number of intro frames and loop frames in the animation.
|
||||
size_t intro_frames;
|
||||
size_t loop_frames;
|
||||
|
||||
int char_width_;
|
||||
int char_height_;
|
||||
pthread_mutex_t updateMutex;
|
||||
size_t current_frame;
|
||||
bool intro_done;
|
||||
|
||||
virtual bool InitTextParams();
|
||||
int stage, max_stage;
|
||||
|
||||
virtual void draw_background_locked();
|
||||
virtual void draw_foreground_locked();
|
||||
virtual void draw_screen_locked();
|
||||
virtual void update_screen_locked();
|
||||
virtual void update_progress_locked();
|
||||
int char_width_;
|
||||
int char_height_;
|
||||
|
||||
GRSurface* GetCurrentFrame();
|
||||
GRSurface* GetCurrentText();
|
||||
pthread_mutex_t updateMutex;
|
||||
|
||||
static void* ProgressThreadStartRoutine(void* data);
|
||||
void ProgressThreadLoop();
|
||||
virtual bool InitTextParams();
|
||||
|
||||
virtual void ShowFile(FILE*);
|
||||
virtual void PrintV(const char*, bool, va_list);
|
||||
void PutChar(char);
|
||||
void ClearText();
|
||||
virtual void draw_background_locked();
|
||||
virtual void draw_foreground_locked();
|
||||
virtual void draw_screen_locked();
|
||||
virtual void update_screen_locked();
|
||||
virtual void update_progress_locked();
|
||||
|
||||
void LoadAnimation();
|
||||
void LoadBitmap(const char* filename, GRSurface** surface);
|
||||
void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
|
||||
GRSurface* GetCurrentFrame() const;
|
||||
GRSurface* GetCurrentText() const;
|
||||
|
||||
int PixelsFromDp(int dp) const;
|
||||
virtual int GetAnimationBaseline();
|
||||
virtual int GetProgressBaseline();
|
||||
virtual int GetTextBaseline();
|
||||
static void* ProgressThreadStartRoutine(void* data);
|
||||
void ProgressThreadLoop();
|
||||
|
||||
void DrawHorizontalRule(int* y);
|
||||
void DrawTextLine(int x, int* y, const char* line, bool bold) const;
|
||||
void DrawTextLines(int x, int* y, const char* const* lines) const;
|
||||
virtual void ShowFile(FILE*);
|
||||
virtual void PrintV(const char*, bool, va_list);
|
||||
void PutChar(char);
|
||||
void ClearText();
|
||||
|
||||
void LoadAnimation();
|
||||
void LoadBitmap(const char* filename, GRSurface** surface);
|
||||
void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
|
||||
|
||||
int PixelsFromDp(int dp) const;
|
||||
virtual int GetAnimationBaseline() const;
|
||||
virtual int GetProgressBaseline() const;
|
||||
virtual int GetTextBaseline() const;
|
||||
|
||||
// Draws a highlight bar at (x, y) - (x + width, y + height).
|
||||
virtual void DrawHighlightBar(int x, int y, int width, int height) const;
|
||||
// Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
|
||||
virtual int DrawHorizontalRule(int y) const;
|
||||
// Draws a line of text. Returns the offset it should be moving along Y-axis.
|
||||
virtual int DrawTextLine(int x, int y, const char* line, bool bold) const;
|
||||
// Draws multiple text lines. Returns the offset it should be moving along Y-axis.
|
||||
int DrawTextLines(int x, int y, const char* const* lines) const;
|
||||
// Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines.
|
||||
// Returns the offset it should be moving along Y-axis.
|
||||
int DrawWrappedTextLines(int x, int y, const char* const* lines) const;
|
||||
};
|
||||
|
||||
#endif // RECOVERY_UI_H
|
||||
|
||||
+18
-7
@@ -26,11 +26,12 @@ endif
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_MODULE := recovery_unit_test
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
|
||||
LOCAL_COMPATIBILITY_SUITE := device-tests
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libverifier \
|
||||
libminui \
|
||||
libotautil \
|
||||
libupdater \
|
||||
libziparchive \
|
||||
libutils \
|
||||
libz \
|
||||
@@ -41,9 +42,9 @@ LOCAL_SRC_FILES := \
|
||||
unit/asn1_decoder_test.cpp \
|
||||
unit/dirutil_test.cpp \
|
||||
unit/locale_test.cpp \
|
||||
unit/rangeset_test.cpp \
|
||||
unit/sysutil_test.cpp \
|
||||
unit/zip_test.cpp \
|
||||
unit/ziputil_test.cpp
|
||||
|
||||
LOCAL_C_INCLUDES := $(RECOVERY_PATH)
|
||||
LOCAL_SHARED_LIBRARIES := liblog
|
||||
@@ -51,10 +52,8 @@ include $(BUILD_NATIVE_TEST)
|
||||
|
||||
# Manual tests
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_CLANG := true
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_MODULE := recovery_manual_test
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libminui \
|
||||
libbase
|
||||
@@ -91,13 +90,20 @@ LOCAL_CFLAGS := \
|
||||
-Werror \
|
||||
-D_FILE_OFFSET_BITS=64
|
||||
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
|
||||
|
||||
ifeq ($(AB_OTA_UPDATER),true)
|
||||
LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
|
||||
endif
|
||||
|
||||
ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
|
||||
LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
|
||||
endif
|
||||
|
||||
ifeq ($(BOARD_AVB_ENABLE),true)
|
||||
LOCAL_CFLAGS += -DBOARD_AVB_ENABLE=1
|
||||
endif
|
||||
|
||||
LOCAL_MODULE := recovery_component_test
|
||||
LOCAL_COMPATIBILITY_SUITE := device-tests
|
||||
LOCAL_C_INCLUDES := $(RECOVERY_PATH)
|
||||
LOCAL_SRC_FILES := \
|
||||
component/applypatch_test.cpp \
|
||||
@@ -108,9 +114,11 @@ LOCAL_SRC_FILES := \
|
||||
component/sideload_test.cpp \
|
||||
component/uncrypt_test.cpp \
|
||||
component/updater_test.cpp \
|
||||
component/update_verifier_test.cpp \
|
||||
component/verifier_test.cpp
|
||||
|
||||
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libhidlbase
|
||||
|
||||
tune2fs_static_libraries := \
|
||||
libext2_com_err \
|
||||
@@ -128,6 +136,7 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
libimgpatch \
|
||||
libbsdiff \
|
||||
libbspatch \
|
||||
libfusesideload \
|
||||
libotafault \
|
||||
librecovery \
|
||||
libupdater \
|
||||
@@ -135,6 +144,7 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
libverifier \
|
||||
libotautil \
|
||||
libmounts \
|
||||
libupdate_verifier \
|
||||
libdivsufsort \
|
||||
libdivsufsort64 \
|
||||
libfs_mgr \
|
||||
@@ -157,6 +167,7 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
libfec_rs \
|
||||
libsquashfs_utils \
|
||||
libcutils \
|
||||
libbrotli \
|
||||
$(tune2fs_static_libraries)
|
||||
|
||||
testdata_files := $(call find-subdir-files, testdata/*)
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -105,9 +105,6 @@ class ApplyPatchTest : public ::testing::Test {
|
||||
static size_t new_size;
|
||||
};
|
||||
|
||||
std::string ApplyPatchTest::old_file;
|
||||
std::string ApplyPatchTest::new_file;
|
||||
|
||||
static void cp(const std::string& src, const std::string& tgt) {
|
||||
std::string cmd = "cp " + src + " " + tgt;
|
||||
system(cmd.c_str());
|
||||
@@ -132,48 +129,8 @@ class ApplyPatchCacheTest : public ApplyPatchTest {
|
||||
}
|
||||
};
|
||||
|
||||
class ApplyPatchFullTest : public ApplyPatchCacheTest {
|
||||
public:
|
||||
static void SetUpTestCase() {
|
||||
ApplyPatchTest::SetUpTestCase();
|
||||
|
||||
output_f = new TemporaryFile();
|
||||
output_loc = std::string(output_f->path);
|
||||
|
||||
struct FileContents fc;
|
||||
|
||||
ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc));
|
||||
patches.push_back(
|
||||
std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
|
||||
|
||||
ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc));
|
||||
patches.push_back(
|
||||
std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
|
||||
}
|
||||
|
||||
static void TearDownTestCase() {
|
||||
delete output_f;
|
||||
patches.clear();
|
||||
}
|
||||
|
||||
static std::vector<std::unique_ptr<Value>> patches;
|
||||
static TemporaryFile* output_f;
|
||||
static std::string output_loc;
|
||||
};
|
||||
|
||||
class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest {
|
||||
public:
|
||||
virtual void SetUp() {
|
||||
ApplyPatchCacheTest::SetUp();
|
||||
cp(cache_file, "/cache/reallysaved.file");
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
cp("/cache/reallysaved.file", cache_file);
|
||||
ApplyPatchCacheTest::TearDown();
|
||||
}
|
||||
};
|
||||
|
||||
std::string ApplyPatchTest::old_file;
|
||||
std::string ApplyPatchTest::new_file;
|
||||
std::string ApplyPatchTest::rand_file;
|
||||
std::string ApplyPatchTest::patch_file;
|
||||
std::string ApplyPatchTest::cache_file;
|
||||
@@ -184,10 +141,6 @@ std::string ApplyPatchTest::bad_sha1_b;
|
||||
size_t ApplyPatchTest::old_size;
|
||||
size_t ApplyPatchTest::new_size;
|
||||
|
||||
std::vector<std::unique_ptr<Value>> ApplyPatchFullTest::patches;
|
||||
TemporaryFile* ApplyPatchFullTest::output_f;
|
||||
std::string ApplyPatchFullTest::output_loc;
|
||||
|
||||
TEST_F(ApplyPatchTest, CheckModeSkip) {
|
||||
std::vector<std::string> sha1s;
|
||||
ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
|
||||
@@ -424,20 +377,6 @@ TEST(ApplyPatchModesTest, CheckModeInvalidArgs) {
|
||||
ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
|
||||
}
|
||||
|
||||
TEST(ApplyPatchModesTest, SpaceModeInvalidArgs) {
|
||||
// Insufficient args.
|
||||
ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-s" }));
|
||||
|
||||
// Invalid bytes arg.
|
||||
ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "x" }));
|
||||
|
||||
// 0 is invalid.
|
||||
ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0" }));
|
||||
|
||||
// 0x10 is fine.
|
||||
ASSERT_EQ(0, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0x10" }));
|
||||
}
|
||||
|
||||
TEST(ApplyPatchModesTest, ShowLicenses) {
|
||||
ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -27,12 +29,6 @@
|
||||
|
||||
using android::base::get_unaligned;
|
||||
|
||||
static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
|
||||
std::string* s = static_cast<std::string*>(token);
|
||||
s->append(reinterpret_cast<const char*>(data), len);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Sanity check for the given imgdiff patch header.
|
||||
static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw,
|
||||
size_t* num_deflate) {
|
||||
@@ -79,6 +75,18 @@ static void verify_patch_header(const std::string& patch, size_t* num_normal, si
|
||||
if (num_deflate != nullptr) *num_deflate = deflate;
|
||||
}
|
||||
|
||||
static void verify_patched_image(const std::string& src, const std::string& patch,
|
||||
const std::string& tgt) {
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
[&patched](const unsigned char* data, size_t len) {
|
||||
patched.append(reinterpret_cast<const char*>(data), len);
|
||||
return len;
|
||||
}));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, invalid_args) {
|
||||
// Insufficient inputs.
|
||||
ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" }));
|
||||
@@ -124,11 +132,7 @@ TEST(ImgdiffTest, image_mode_smoke) {
|
||||
ASSERT_EQ(0U, num_deflate);
|
||||
ASSERT_EQ(1U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_smoke_store) {
|
||||
@@ -177,11 +181,7 @@ TEST(ImgdiffTest, zip_mode_smoke_store) {
|
||||
ASSERT_EQ(0U, num_deflate);
|
||||
ASSERT_EQ(1U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_smoke_compressed) {
|
||||
@@ -230,11 +230,7 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) {
|
||||
ASSERT_EQ(1U, num_deflate);
|
||||
ASSERT_EQ(2U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
|
||||
@@ -286,11 +282,7 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
|
||||
ASSERT_EQ(1U, num_deflate);
|
||||
ASSERT_EQ(2U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, image_mode_simple) {
|
||||
@@ -333,11 +325,40 @@ TEST(ImgdiffTest, image_mode_simple) {
|
||||
ASSERT_EQ(1U, num_deflate);
|
||||
ASSERT_EQ(2U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, image_mode_bad_gzip) {
|
||||
// Modify the uncompressed length in the gzip footer.
|
||||
const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
|
||||
'\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
|
||||
'\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
|
||||
'\xff', '\xff', '\xff' };
|
||||
const std::string src(src_data.cbegin(), src_data.cend());
|
||||
TemporaryFile src_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
|
||||
|
||||
// Modify the uncompressed length in the gzip footer.
|
||||
const std::vector<char> tgt_data = {
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b',
|
||||
'\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
|
||||
'\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\xff', '\xff', '\xff'
|
||||
};
|
||||
const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
|
||||
TemporaryFile tgt_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
|
||||
|
||||
TemporaryFile patch_file;
|
||||
std::vector<const char*> args = {
|
||||
"imgdiff", src_file.path, tgt_file.path, patch_file.path,
|
||||
};
|
||||
ASSERT_EQ(0, imgdiff(args.size(), args.data()));
|
||||
|
||||
// Verify.
|
||||
std::string patch;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, image_mode_different_num_chunks) {
|
||||
@@ -413,11 +434,7 @@ TEST(ImgdiffTest, image_mode_merge_chunks) {
|
||||
ASSERT_EQ(1U, num_deflate);
|
||||
ASSERT_EQ(2U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, image_mode_spurious_magic) {
|
||||
@@ -454,11 +471,7 @@ TEST(ImgdiffTest, image_mode_spurious_magic) {
|
||||
ASSERT_EQ(0U, num_deflate);
|
||||
ASSERT_EQ(1U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, image_mode_short_input1) {
|
||||
@@ -494,11 +507,7 @@ TEST(ImgdiffTest, image_mode_short_input1) {
|
||||
ASSERT_EQ(0U, num_deflate);
|
||||
ASSERT_EQ(1U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, image_mode_short_input2) {
|
||||
@@ -534,11 +543,7 @@ TEST(ImgdiffTest, image_mode_short_input2) {
|
||||
ASSERT_EQ(0U, num_deflate);
|
||||
ASSERT_EQ(1U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, image_mode_single_entry_long) {
|
||||
@@ -577,9 +582,44 @@ TEST(ImgdiffTest, image_mode_single_entry_long) {
|
||||
ASSERT_EQ(0U, num_deflate);
|
||||
ASSERT_EQ(0U, num_raw);
|
||||
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
MemorySink, &patched));
|
||||
ASSERT_EQ(tgt, patched);
|
||||
verify_patched_image(src, patch, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgpatchTest, image_mode_patch_corruption) {
|
||||
// src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
|
||||
const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
|
||||
'\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
|
||||
'\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
|
||||
'\x00', '\x00', '\x00' };
|
||||
const std::string src(src_data.cbegin(), src_data.cend());
|
||||
TemporaryFile src_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
|
||||
|
||||
// tgt: "abcdefgxyz" + gzipped "xxyyzz".
|
||||
const std::vector<char> tgt_data = {
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b',
|
||||
'\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
|
||||
'\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
|
||||
};
|
||||
const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
|
||||
TemporaryFile tgt_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
|
||||
|
||||
TemporaryFile patch_file;
|
||||
std::vector<const char*> args = {
|
||||
"imgdiff", src_file.path, tgt_file.path, patch_file.path,
|
||||
};
|
||||
ASSERT_EQ(0, imgdiff(args.size(), args.data()));
|
||||
|
||||
// Verify.
|
||||
std::string patch;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
|
||||
verify_patched_image(src, patch, tgt);
|
||||
|
||||
// Corrupt the end of the patch and expect the ApplyImagePatch to fail.
|
||||
patch.insert(patch.end() - 10, 10, '0');
|
||||
ASSERT_EQ(-1, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
[](const unsigned char* /*data*/, size_t len) { return len; }));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
@@ -65,6 +67,56 @@ TEST(InstallTest, verify_package_compatibility_invalid_entry) {
|
||||
CloseArchive(zip);
|
||||
}
|
||||
|
||||
TEST(InstallTest, read_metadata_from_package_smoke) {
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
ZipWriter writer(zip_file);
|
||||
ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
|
||||
const std::string content("abcdefg");
|
||||
ASSERT_EQ(0, writer.WriteBytes(content.data(), content.size()));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
ASSERT_EQ(0, writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file));
|
||||
|
||||
ZipArchiveHandle zip;
|
||||
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
|
||||
std::string metadata;
|
||||
ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
|
||||
ASSERT_EQ(content, metadata);
|
||||
CloseArchive(zip);
|
||||
|
||||
TemporaryFile temp_file2;
|
||||
FILE* zip_file2 = fdopen(temp_file2.fd, "w");
|
||||
ZipWriter writer2(zip_file2);
|
||||
ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
|
||||
ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
|
||||
ASSERT_EQ(0, writer2.FinishEntry());
|
||||
ASSERT_EQ(0, writer2.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file2));
|
||||
|
||||
ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip));
|
||||
metadata.clear();
|
||||
ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
|
||||
ASSERT_EQ(content, metadata);
|
||||
CloseArchive(zip);
|
||||
}
|
||||
|
||||
TEST(InstallTest, read_metadata_from_package_no_entry) {
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
ZipWriter writer(zip_file);
|
||||
ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
ASSERT_EQ(0, writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file));
|
||||
|
||||
ZipArchiveHandle zip;
|
||||
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
|
||||
std::string metadata;
|
||||
ASSERT_FALSE(read_metadata_from_package(zip, &metadata));
|
||||
CloseArchive(zip);
|
||||
}
|
||||
|
||||
TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
|
||||
TemporaryFile compatibility_zip_file;
|
||||
FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
|
||||
@@ -175,18 +227,62 @@ TEST(InstallTest, update_binary_command_smoke) {
|
||||
|
||||
ZipArchiveHandle zip;
|
||||
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
|
||||
ZipString payload_name("payload.bin");
|
||||
ZipEntry payload_entry;
|
||||
ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
|
||||
int status_fd = 10;
|
||||
std::string path = "/path/to/update.zip";
|
||||
std::string package = "/path/to/update.zip";
|
||||
std::string binary_path = "/sbin/update_engine_sideload";
|
||||
std::vector<std::string> cmd;
|
||||
ASSERT_EQ(0, update_binary_command(path, zip, 0, status_fd, &cmd));
|
||||
ASSERT_EQ("/sbin/update_engine_sideload", cmd[0]);
|
||||
ASSERT_EQ("--payload=file://" + path, cmd[1]);
|
||||
ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
|
||||
ASSERT_EQ(5U, cmd.size());
|
||||
ASSERT_EQ(binary_path, cmd[0]);
|
||||
ASSERT_EQ("--payload=file://" + package, cmd[1]);
|
||||
ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
|
||||
ASSERT_EQ("--headers=" + properties, cmd[3]);
|
||||
ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
|
||||
CloseArchive(zip);
|
||||
#else
|
||||
// Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
|
||||
GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
ZipWriter writer(zip_file);
|
||||
static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
|
||||
ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
ASSERT_EQ(0, writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file));
|
||||
|
||||
ZipArchiveHandle zip;
|
||||
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
|
||||
int status_fd = 10;
|
||||
std::string package = "/path/to/update.zip";
|
||||
TemporaryDir td;
|
||||
std::string binary_path = std::string(td.path) + "/update_binary";
|
||||
std::vector<std::string> cmd;
|
||||
ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
|
||||
ASSERT_EQ(4U, cmd.size());
|
||||
ASSERT_EQ(binary_path, cmd[0]);
|
||||
ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
|
||||
ASSERT_EQ(std::to_string(status_fd), cmd[2]);
|
||||
ASSERT_EQ(package, cmd[3]);
|
||||
struct stat sb;
|
||||
ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
|
||||
ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
|
||||
|
||||
// With non-zero retry count. update_binary will be removed automatically.
|
||||
cmd.clear();
|
||||
ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 2, status_fd, &cmd));
|
||||
ASSERT_EQ(5U, cmd.size());
|
||||
ASSERT_EQ(binary_path, cmd[0]);
|
||||
ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
|
||||
ASSERT_EQ(std::to_string(status_fd), cmd[2]);
|
||||
ASSERT_EQ(package, cmd[3]);
|
||||
ASSERT_EQ("retry", cmd[4]);
|
||||
sb = {};
|
||||
ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
|
||||
ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
|
||||
|
||||
CloseArchive(zip);
|
||||
#endif // AB_OTA_UPDATER
|
||||
}
|
||||
|
||||
@@ -217,12 +313,30 @@ TEST(InstallTest, update_binary_command_invalid) {
|
||||
ZipArchiveHandle zip;
|
||||
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
|
||||
int status_fd = 10;
|
||||
std::string path = "/path/to/update.zip";
|
||||
std::string package = "/path/to/update.zip";
|
||||
std::string binary_path = "/sbin/update_engine_sideload";
|
||||
std::vector<std::string> cmd;
|
||||
ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(path, zip, 0, status_fd, &cmd));
|
||||
ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
|
||||
CloseArchive(zip);
|
||||
#else
|
||||
// Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
|
||||
GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
ZipWriter writer(zip_file);
|
||||
// The archive must have something to be opened correctly.
|
||||
ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
ASSERT_EQ(0, writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file));
|
||||
|
||||
// Missing update binary.
|
||||
ZipArchiveHandle zip;
|
||||
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
|
||||
int status_fd = 10;
|
||||
std::string package = "/path/to/update.zip";
|
||||
TemporaryDir td;
|
||||
std::string binary_path = std::string(td.path) + "/update_binary";
|
||||
std::vector<std::string> cmd;
|
||||
ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
|
||||
CloseArchive(zip);
|
||||
#endif // AB_OTA_UPDATER
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -25,11 +25,12 @@
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/properties.h>
|
||||
#include <android-base/test_utils.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <bootloader_message/bootloader_message.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "common/component_test_util.h"
|
||||
using namespace std::string_literals;
|
||||
|
||||
static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt";
|
||||
static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb";
|
||||
@@ -62,131 +63,108 @@ class UncryptTest : public ::testing::Test {
|
||||
|
||||
ASSERT_TRUE(success) << "uncrypt service is not available.";
|
||||
|
||||
has_misc = parse_misc();
|
||||
std::string err;
|
||||
has_misc = !get_bootloader_message_blk_device(&err).empty();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Trigger the clear-bcb service.
|
||||
ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-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);
|
||||
|
||||
// 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)));
|
||||
SetupOrClearBcb(false, "", "");
|
||||
}
|
||||
|
||||
TEST_F(UncryptTest, setup_bcb_wipe_ab) {
|
||||
TemporaryFile wipe_package;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path));
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -15,10 +15,12 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -29,6 +31,7 @@
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/test_utils.h>
|
||||
#include <bootloader_message/bootloader_message.h>
|
||||
#include <brotli/encode.h>
|
||||
#include <bsdiff.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <ziparchive/zip_archive.h>
|
||||
@@ -224,102 +227,6 @@ TEST_F(UpdaterTest, file_getprop) {
|
||||
expect("", script6.c_str(), kNoCause);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, package_extract_dir) {
|
||||
// package_extract_dir expects 2 arguments.
|
||||
expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
|
||||
expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
|
||||
expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
|
||||
|
||||
std::string zip_path = from_testdata_base("ziptest_valid.zip");
|
||||
ZipArchiveHandle handle;
|
||||
ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
|
||||
|
||||
// Need to set up the ziphandle.
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.package_zip = handle;
|
||||
|
||||
// Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
|
||||
TemporaryDir td;
|
||||
std::string temp_dir(td.path);
|
||||
std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
|
||||
expect("t", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
// Verify.
|
||||
std::string data;
|
||||
std::string file_c = temp_dir + "/c.txt";
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
|
||||
ASSERT_EQ(kCTxtContents, data);
|
||||
|
||||
std::string file_d = temp_dir + "/d.txt";
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
|
||||
ASSERT_EQ(kDTxtContents, data);
|
||||
|
||||
// Modify the contents in order to retry. It's expected to be overwritten.
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
|
||||
|
||||
// Extract again and verify.
|
||||
expect("t", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
|
||||
ASSERT_EQ(kCTxtContents, data);
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
|
||||
ASSERT_EQ(kDTxtContents, data);
|
||||
|
||||
// Clean up the temp files under td.
|
||||
ASSERT_EQ(0, unlink(file_c.c_str()));
|
||||
ASSERT_EQ(0, unlink(file_d.c_str()));
|
||||
|
||||
// Extracting "b/" (with slash) should give the same result.
|
||||
script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
|
||||
expect("t", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
|
||||
ASSERT_EQ(kCTxtContents, data);
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
|
||||
ASSERT_EQ(kDTxtContents, data);
|
||||
|
||||
ASSERT_EQ(0, unlink(file_c.c_str()));
|
||||
ASSERT_EQ(0, unlink(file_d.c_str()));
|
||||
|
||||
// Extracting "" is allowed. The entries will carry the path name.
|
||||
script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
|
||||
expect("t", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
std::string file_a = temp_dir + "/a.txt";
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
|
||||
ASSERT_EQ(kATxtContents, data);
|
||||
std::string file_b = temp_dir + "/b.txt";
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
|
||||
ASSERT_EQ(kBTxtContents, data);
|
||||
std::string file_b_c = temp_dir + "/b/c.txt";
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
|
||||
ASSERT_EQ(kCTxtContents, data);
|
||||
std::string file_b_d = temp_dir + "/b/d.txt";
|
||||
ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
|
||||
ASSERT_EQ(kDTxtContents, data);
|
||||
|
||||
ASSERT_EQ(0, unlink(file_a.c_str()));
|
||||
ASSERT_EQ(0, unlink(file_b.c_str()));
|
||||
ASSERT_EQ(0, unlink(file_b_c.c_str()));
|
||||
ASSERT_EQ(0, unlink(file_b_d.c_str()));
|
||||
ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
|
||||
|
||||
// Extracting non-existent entry should still give "t".
|
||||
script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
|
||||
expect("t", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
// Only relative zip_path is allowed.
|
||||
script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
|
||||
expect("", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
// Only absolute dest_path is allowed.
|
||||
script = "package_extract_dir(\"b\", \"path\")";
|
||||
expect("", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
// TODO: Test extracting to block device.
|
||||
TEST_F(UpdaterTest, package_extract_file) {
|
||||
// package_extract_file expects 1 or 2 arguments.
|
||||
@@ -570,7 +477,7 @@ TEST_F(UpdaterTest, block_image_update) {
|
||||
ASSERT_EQ(0, fclose(zip_file_ptr));
|
||||
|
||||
MemMapping map;
|
||||
ASSERT_EQ(0, sysMapFile(zip_file.path, &map));
|
||||
ASSERT_TRUE(map.MapFile(zip_file.path));
|
||||
ZipArchiveHandle handle;
|
||||
ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
|
||||
|
||||
@@ -578,7 +485,7 @@ TEST_F(UpdaterTest, block_image_update) {
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.package_zip = handle;
|
||||
TemporaryFile temp_pipe;
|
||||
updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
|
||||
updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
|
||||
updater_info.package_zip_addr = map.addr;
|
||||
updater_info.package_zip_len = map.length;
|
||||
|
||||
@@ -607,3 +514,144 @@ TEST_F(UpdaterTest, block_image_update) {
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, new_data_short_write) {
|
||||
// Create a zip file with new_data.
|
||||
TemporaryFile zip_file;
|
||||
FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
|
||||
ZipWriter zip_writer(zip_file_ptr);
|
||||
|
||||
// Add the empty new data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("empty_new_data", 0));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
// Add the short written new data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("short_new_data", 0));
|
||||
std::string new_data_short = std::string(10, 'a');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(new_data_short.data(), new_data_short.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
// Add the data of exactly one block.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("exact_new_data", 0));
|
||||
std::string new_data_exact = std::string(4096, 'a');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(new_data_exact.data(), new_data_exact.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
// Add a dummy patch data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
|
||||
std::vector<std::string> transfer_list = {
|
||||
"4",
|
||||
"1",
|
||||
"0",
|
||||
"0",
|
||||
"new 2,0,1",
|
||||
};
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
|
||||
std::string commands = android::base::Join(transfer_list, '\n');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
ASSERT_EQ(0, zip_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file_ptr));
|
||||
|
||||
MemMapping map;
|
||||
ASSERT_TRUE(map.MapFile(zip_file.path));
|
||||
ZipArchiveHandle handle;
|
||||
ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
|
||||
|
||||
// Set up the handler, command_pipe, patch offset & length.
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.package_zip = handle;
|
||||
TemporaryFile temp_pipe;
|
||||
updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
|
||||
updater_info.package_zip_addr = map.addr;
|
||||
updater_info.package_zip_len = map.length;
|
||||
|
||||
// Updater should report the failure gracefully rather than stuck in deadlock.
|
||||
TemporaryFile update_file;
|
||||
std::string script_empty_data = "block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list"), "empty_new_data", "patch_data"))";
|
||||
expect("", script_empty_data.c_str(), kNoCause, &updater_info);
|
||||
|
||||
std::string script_short_data = "block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list"), "short_new_data", "patch_data"))";
|
||||
expect("", script_short_data.c_str(), kNoCause, &updater_info);
|
||||
|
||||
// Expect to write 1 block of new data successfully.
|
||||
std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))";
|
||||
expect("t", script_exact_data.c_str(), kNoCause, &updater_info);
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, brotli_new_data) {
|
||||
// Create a zip file with new_data.
|
||||
TemporaryFile zip_file;
|
||||
FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
|
||||
ZipWriter zip_writer(zip_file_ptr);
|
||||
|
||||
// Add a brotli compressed new data entry.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0));
|
||||
|
||||
auto generator = []() { return rand() % 128; };
|
||||
// Generate 100 blocks of random data.
|
||||
std::string brotli_new_data;
|
||||
brotli_new_data.reserve(4096 * 100);
|
||||
generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
|
||||
|
||||
size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
|
||||
std::vector<uint8_t> encoded_data(encoded_size);
|
||||
ASSERT_TRUE(BrotliEncoderCompress(
|
||||
BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(),
|
||||
reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size, encoded_data.data()));
|
||||
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(encoded_data.data(), encoded_size));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
// Add a dummy patch data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
|
||||
// Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
|
||||
// This helps us to catch potential short writes.
|
||||
std::vector<std::string> transfer_list = {
|
||||
"4",
|
||||
"100",
|
||||
"0",
|
||||
"0",
|
||||
"new 2,0,1",
|
||||
"new 2,1,2",
|
||||
"new 4,2,50,50,97",
|
||||
"new 2,97,98",
|
||||
"new 2,98,99",
|
||||
"new 2,99,100",
|
||||
};
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
|
||||
std::string commands = android::base::Join(transfer_list, '\n');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
ASSERT_EQ(0, zip_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file_ptr));
|
||||
|
||||
MemMapping map;
|
||||
ASSERT_TRUE(map.MapFile(zip_file.path));
|
||||
ZipArchiveHandle handle;
|
||||
ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
|
||||
|
||||
// Set up the handler, command_pipe, patch offset & length.
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.package_zip = handle;
|
||||
TemporaryFile temp_pipe;
|
||||
updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
|
||||
updater_info.package_zip_addr = map.addr;
|
||||
updater_info.package_zip_len = map.length;
|
||||
|
||||
// Check if we can decompress the new data correctly.
|
||||
TemporaryFile update_file;
|
||||
std::string script_new_data =
|
||||
"block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list"), "new.dat.br", "patch_data"))";
|
||||
expect("t", script_new_data.c_str(), kNoCause, &updater_info);
|
||||
|
||||
std::string updated_content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
|
||||
ASSERT_EQ(brotli_new_data, updated_content);
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
|
||||
void SetUp() override {
|
||||
std::vector<std::string> args = GetParam();
|
||||
std::string package = from_testdata_base(args[0]);
|
||||
if (sysMapFile(package.c_str(), &memmap) != 0) {
|
||||
if (!memmap.MapFile(package)) {
|
||||
FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
|
||||
}
|
||||
|
||||
@@ -117,6 +117,51 @@ TEST(VerifierTest, load_keys_invalid_keys) {
|
||||
ASSERT_FALSE(load_keys(key_file5.path, certs));
|
||||
}
|
||||
|
||||
TEST(VerifierTest, BadPackage_AlteredFooter) {
|
||||
std::string testkey_v3;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
|
||||
TemporaryFile key_file1;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path));
|
||||
std::vector<Certificate> certs;
|
||||
ASSERT_TRUE(load_keys(key_file1.path, certs));
|
||||
|
||||
std::string package;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
|
||||
ASSERT_EQ(std::string("\xc0\x06\xff\xff\xd2\x06", 6), package.substr(package.size() - 6, 6));
|
||||
|
||||
// Alter the footer.
|
||||
package[package.size() - 5] = '\x05';
|
||||
ASSERT_EQ(VERIFY_FAILURE,
|
||||
verify_file(reinterpret_cast<const unsigned char*>(package.data()), package.size(),
|
||||
certs));
|
||||
}
|
||||
|
||||
TEST(VerifierTest, BadPackage_AlteredContent) {
|
||||
std::string testkey_v3;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
|
||||
TemporaryFile key_file1;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path));
|
||||
std::vector<Certificate> certs;
|
||||
ASSERT_TRUE(load_keys(key_file1.path, certs));
|
||||
|
||||
std::string package;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
|
||||
ASSERT_GT(package.size(), static_cast<size_t>(100));
|
||||
|
||||
// Alter the content.
|
||||
std::string altered1(package);
|
||||
altered1[50] += 1;
|
||||
ASSERT_EQ(VERIFY_FAILURE,
|
||||
verify_file(reinterpret_cast<const unsigned char*>(altered1.data()), altered1.size(),
|
||||
certs));
|
||||
|
||||
std::string altered2(package);
|
||||
altered2[10] += 1;
|
||||
ASSERT_EQ(VERIFY_FAILURE,
|
||||
verify_file(reinterpret_cast<const unsigned char*>(altered2.data()), altered2.size(),
|
||||
certs));
|
||||
}
|
||||
|
||||
TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) {
|
||||
std::string testkey_v3;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
|
||||
@@ -174,6 +219,4 @@ INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest,
|
||||
INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest,
|
||||
::testing::Values(
|
||||
std::vector<std::string>({"random.zip", "v1"}),
|
||||
std::vector<std::string>({"fake-eocd.zip", "v1"}),
|
||||
std::vector<std::string>({"alter-metadata.zip", "v1"}),
|
||||
std::vector<std::string>({"alter-footer.zip", "v1"})));
|
||||
std::vector<std::string>({"fake-eocd.zip", "v1"})));
|
||||
|
||||
@@ -141,7 +141,7 @@ class ResourceTest : public testing::TestWithParam<std::string> {
|
||||
// under recovery.
|
||||
void SetUp() override {
|
||||
std::string file_path = GetParam();
|
||||
fp = fopen(file_path.c_str(), "rb");
|
||||
fp = fopen(file_path.c_str(), "rbe");
|
||||
ASSERT_NE(nullptr, fp);
|
||||
|
||||
unsigned char header[8];
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -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);
|
||||
}
|
||||
+25
-35
@@ -27,27 +27,23 @@ TEST(SysUtilTest, InvalidArgs) {
|
||||
MemMapping mapping;
|
||||
|
||||
// Invalid argument.
|
||||
ASSERT_EQ(-1, sysMapFile(nullptr, &mapping));
|
||||
ASSERT_EQ(-1, sysMapFile("/somefile", nullptr));
|
||||
ASSERT_FALSE(mapping.MapFile(""));
|
||||
}
|
||||
|
||||
TEST(SysUtilTest, sysMapFileRegularFile) {
|
||||
TEST(SysUtilTest, MapFileRegularFile) {
|
||||
TemporaryFile temp_file1;
|
||||
std::string content = "abc";
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file1.path));
|
||||
|
||||
// sysMapFile() should map the file to one range.
|
||||
// MemMapping::MapFile() should map the file to one range.
|
||||
MemMapping mapping;
|
||||
ASSERT_EQ(0, sysMapFile(temp_file1.path, &mapping));
|
||||
ASSERT_TRUE(mapping.MapFile(temp_file1.path));
|
||||
ASSERT_NE(nullptr, mapping.addr);
|
||||
ASSERT_EQ(content.size(), mapping.length);
|
||||
ASSERT_EQ(1U, mapping.ranges.size());
|
||||
|
||||
sysReleaseMap(&mapping);
|
||||
ASSERT_EQ(0U, mapping.ranges.size());
|
||||
ASSERT_EQ(1U, mapping.ranges());
|
||||
}
|
||||
|
||||
TEST(SysUtilTest, sysMapFileBlockMap) {
|
||||
TEST(SysUtilTest, MapFileBlockMap) {
|
||||
// Create a file that has 10 blocks.
|
||||
TemporaryFile package;
|
||||
std::string content;
|
||||
@@ -63,78 +59,72 @@ TEST(SysUtilTest, sysMapFileBlockMap) {
|
||||
std::string block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n";
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
|
||||
|
||||
ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_TRUE(mapping.MapFile(filename));
|
||||
ASSERT_EQ(file_size, mapping.length);
|
||||
ASSERT_EQ(1U, mapping.ranges.size());
|
||||
ASSERT_EQ(1U, mapping.ranges());
|
||||
|
||||
// It's okay to not have the trailing '\n'.
|
||||
block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10";
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
|
||||
|
||||
ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_TRUE(mapping.MapFile(filename));
|
||||
ASSERT_EQ(file_size, mapping.length);
|
||||
ASSERT_EQ(1U, mapping.ranges.size());
|
||||
ASSERT_EQ(1U, mapping.ranges());
|
||||
|
||||
// Or having multiple trailing '\n's.
|
||||
block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n\n\n";
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
|
||||
|
||||
ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_TRUE(mapping.MapFile(filename));
|
||||
ASSERT_EQ(file_size, mapping.length);
|
||||
ASSERT_EQ(1U, mapping.ranges.size());
|
||||
ASSERT_EQ(1U, mapping.ranges());
|
||||
|
||||
// Multiple ranges.
|
||||
block_map_content = std::string(package.path) + "\n40960 4096\n3\n0 3\n3 5\n5 10\n";
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
|
||||
|
||||
ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_TRUE(mapping.MapFile(filename));
|
||||
ASSERT_EQ(file_size, mapping.length);
|
||||
ASSERT_EQ(3U, mapping.ranges.size());
|
||||
|
||||
sysReleaseMap(&mapping);
|
||||
ASSERT_EQ(0U, mapping.ranges.size());
|
||||
ASSERT_EQ(3U, mapping.ranges());
|
||||
}
|
||||
|
||||
TEST(SysUtilTest, sysMapFileBlockMapInvalidBlockMap) {
|
||||
TEST(SysUtilTest, MapFileBlockMapInvalidBlockMap) {
|
||||
MemMapping mapping;
|
||||
TemporaryFile temp_file;
|
||||
std::string filename = std::string("@") + temp_file.path;
|
||||
|
||||
// Block map file is too short.
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
// Block map file has unexpected number of lines.
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n1\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n2\n0 1\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
// Invalid size/blksize/range_count.
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\nabc 4096\n1\n0 1\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n\n0 1\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
// size/blksize/range_count don't match.
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n0 4096\n1\n0 1\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 0\n1\n0 1\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n0 1\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
|
||||
// Invalid block dev path.
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("/doesntexist\n4096 4096\n1\n0 1\n", temp_file.path));
|
||||
ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
|
||||
|
||||
sysReleaseMap(&mapping);
|
||||
ASSERT_EQ(0U, mapping.ranges.size());
|
||||
ASSERT_FALSE(mapping.MapFile(filename));
|
||||
}
|
||||
|
||||
+2
-40
@@ -24,51 +24,14 @@
|
||||
#include <android-base/test_utils.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <otautil/SysUtil.h>
|
||||
#include <otautil/ZipUtil.h>
|
||||
#include <ziparchive/zip_archive.h>
|
||||
|
||||
#include "common/test_constants.h"
|
||||
|
||||
TEST(ZipTest, ExtractPackageRecursive) {
|
||||
std::string zip_path = from_testdata_base("ziptest_valid.zip");
|
||||
ZipArchiveHandle handle;
|
||||
ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
|
||||
|
||||
// Extract the whole package into a temp directory.
|
||||
TemporaryDir td;
|
||||
ASSERT_NE(nullptr, td.path);
|
||||
ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
|
||||
|
||||
// Make sure all the files are extracted correctly.
|
||||
std::string path(td.path);
|
||||
ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
|
||||
ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
|
||||
ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
|
||||
ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
|
||||
|
||||
// The content of the file is the same as expected.
|
||||
std::string content1;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
|
||||
ASSERT_EQ(kATxtContents, content1);
|
||||
|
||||
std::string content2;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
|
||||
ASSERT_EQ(kDTxtContents, content2);
|
||||
|
||||
CloseArchive(handle);
|
||||
|
||||
// Clean up.
|
||||
ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
|
||||
ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
|
||||
ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
|
||||
ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
|
||||
ASSERT_EQ(0, rmdir((path + "/b").c_str()));
|
||||
}
|
||||
|
||||
TEST(ZipTest, OpenFromMemory) {
|
||||
MemMapping map;
|
||||
std::string zip_path = from_testdata_base("ziptest_dummy-update.zip");
|
||||
ASSERT_EQ(0, sysMapFile(zip_path.c_str(), &map));
|
||||
MemMapping map;
|
||||
ASSERT_TRUE(map.MapFile(zip_path));
|
||||
|
||||
// Map an update package into memory and open the archive from there.
|
||||
ZipArchiveHandle handle;
|
||||
@@ -85,6 +48,5 @@ TEST(ZipTest, OpenFromMemory) {
|
||||
ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd));
|
||||
|
||||
CloseArchive(handle);
|
||||
sysReleaseMap(&map);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+17
-2
@@ -363,7 +363,11 @@ int TWinstall_zip(const char* path, int* wipe_cache) {
|
||||
DataManager::SetProgress(0);
|
||||
|
||||
MemMapping map;
|
||||
#ifdef USE_MINZIP
|
||||
if (sysMapFile(path, &map) != 0) {
|
||||
#else
|
||||
if (!map.MapFile(path)) {
|
||||
#endif
|
||||
gui_msg(Msg(msg::kError, "fail_sysmap=Failed to map file '{1}'")(path));
|
||||
return -1;
|
||||
}
|
||||
@@ -377,6 +381,9 @@ int TWinstall_zip(const char* path, int* wipe_cache) {
|
||||
if (!load_keys("/res/keys", loadedKeys)) {
|
||||
LOGINFO("Failed to load keys");
|
||||
gui_err("verify_zip_fail=Zip signature verification failed!");
|
||||
#ifdef USE_MINZIP
|
||||
sysReleaseMap(&map);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
ret_val = verify_file(map.addr, map.length, loadedKeys, std::bind(&DataManager::SetProgress, std::placeholders::_1));
|
||||
@@ -384,7 +391,9 @@ int TWinstall_zip(const char* path, int* wipe_cache) {
|
||||
if (ret_val != VERIFY_SUCCESS) {
|
||||
LOGINFO("Zip signature verification failed: %i\n", ret_val);
|
||||
gui_err("verify_zip_fail=Zip signature verification failed!");
|
||||
#ifdef USE_MINZIP
|
||||
sysReleaseMap(&map);
|
||||
#endif
|
||||
return -1;
|
||||
} else {
|
||||
gui_msg("verify_zip_done=Zip signature verified successfully.");
|
||||
@@ -393,7 +402,9 @@ int TWinstall_zip(const char* path, int* wipe_cache) {
|
||||
ZipWrap Zip;
|
||||
if (!Zip.Open(path, &map)) {
|
||||
gui_err("zip_corrupt=Zip file is corrupt!");
|
||||
sysReleaseMap(&map);
|
||||
#ifdef USE_MINZIP
|
||||
sysReleaseMap(&map);
|
||||
#endif
|
||||
return INSTALL_CORRUPT;
|
||||
}
|
||||
|
||||
@@ -404,8 +415,10 @@ int TWinstall_zip(const char* path, int* wipe_cache) {
|
||||
// Additionally verify the compatibility of the package.
|
||||
if (!verify_package_compatibility(&Zip)) {
|
||||
gui_err("zip_compatible_err=Zip Treble compatibility error!");
|
||||
sysReleaseMap(&map);
|
||||
Zip.Close();
|
||||
#ifdef USE_MINZIP
|
||||
sysReleaseMap(&map);
|
||||
#endif
|
||||
ret_val = INSTALL_CORRUPT;
|
||||
} else {
|
||||
ret_val = Prepare_Update_Binary(path, &Zip, wipe_cache);
|
||||
@@ -433,6 +446,8 @@ int TWinstall_zip(const char* path, int* wipe_cache) {
|
||||
} else {
|
||||
LOGINFO("Install took %i second(s).\n", total_time);
|
||||
}
|
||||
#ifdef USE_MINZIP
|
||||
sysReleaseMap(&map);
|
||||
#endif
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ RecoveryUI::RecoveryUI()
|
||||
rtl_locale_(false),
|
||||
brightness_normal_(50),
|
||||
brightness_dimmed_(25),
|
||||
touch_screen_allowed_(false),
|
||||
kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
|
||||
kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
|
||||
key_queue_len(0),
|
||||
key_last_down(-1),
|
||||
key_long_press(false),
|
||||
@@ -64,6 +67,9 @@ RecoveryUI::RecoveryUI()
|
||||
has_power_key(false),
|
||||
has_up_key(false),
|
||||
has_down_key(false),
|
||||
has_touch_screen(false),
|
||||
touch_slot_(0),
|
||||
is_bootreason_recovery_ui_(false),
|
||||
screensaver_state_(ScreensaverState::DISABLED) {
|
||||
pthread_mutex_init(&key_queue_mutex, nullptr);
|
||||
pthread_cond_init(&key_queue_cond, nullptr);
|
||||
@@ -71,23 +77,25 @@ RecoveryUI::RecoveryUI()
|
||||
}
|
||||
|
||||
void RecoveryUI::OnKeyDetected(int key_code) {
|
||||
if (key_code == KEY_POWER) {
|
||||
has_power_key = true;
|
||||
} else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) {
|
||||
has_down_key = true;
|
||||
} else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {
|
||||
has_up_key = true;
|
||||
}
|
||||
if (key_code == KEY_POWER) {
|
||||
has_power_key = true;
|
||||
} else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) {
|
||||
has_down_key = true;
|
||||
} else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {
|
||||
has_up_key = true;
|
||||
} else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) {
|
||||
has_touch_screen = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Reads input events, handles special hot keys, and adds to the key queue.
|
||||
static void* InputThreadLoop(void*) {
|
||||
while (true) {
|
||||
if (!ev_wait(-1)) {
|
||||
ev_dispatch();
|
||||
}
|
||||
while (true) {
|
||||
if (!ev_wait(-1)) {
|
||||
ev_dispatch();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool RecoveryUI::InitScreensaver() {
|
||||
@@ -128,10 +136,28 @@ bool RecoveryUI::Init(const std::string& locale) {
|
||||
// Set up the locale info.
|
||||
SetLocale(locale);
|
||||
|
||||
ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));
|
||||
ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2),
|
||||
touch_screen_allowed_);
|
||||
|
||||
ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
|
||||
|
||||
if (touch_screen_allowed_) {
|
||||
ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
|
||||
|
||||
// Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of
|
||||
// "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way
|
||||
// to turn on text mode. It will only be set if the recovery boot is triggered from fastboot, or
|
||||
// with 'adb reboot recovery'. Note that this applies to all build variants. Otherwise the text
|
||||
// mode will be turned on automatically on debuggable builds, even without a swipe.
|
||||
std::string cmdline;
|
||||
if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) {
|
||||
is_bootreason_recovery_ui_ = cmdline.find("bootreason=recovery_ui") != std::string::npos;
|
||||
} else {
|
||||
// Non-fatal, and won't affect Init() result.
|
||||
PLOG(WARNING) << "Failed to read /proc/cmdline";
|
||||
}
|
||||
}
|
||||
|
||||
if (!InitScreensaver()) {
|
||||
LOG(INFO) << "Screensaver disabled";
|
||||
}
|
||||
@@ -140,40 +166,157 @@ bool RecoveryUI::Init(const std::string& locale) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void RecoveryUI::OnTouchDetected(int dx, int dy) {
|
||||
enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction;
|
||||
|
||||
// We only consider a valid swipe if:
|
||||
// - the delta along one axis is below kTouchLowThreshold;
|
||||
// - and the delta along the other axis is beyond kTouchHighThreshold.
|
||||
if (abs(dy) < kTouchLowThreshold && abs(dx) > kTouchHighThreshold) {
|
||||
direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT;
|
||||
} else if (abs(dx) < kTouchLowThreshold && abs(dy) > kTouchHighThreshold) {
|
||||
direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN;
|
||||
} else {
|
||||
LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << kTouchLowThreshold
|
||||
<< ", high: " << kTouchHighThreshold << ")";
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui.
|
||||
if (is_bootreason_recovery_ui_ && !IsTextVisible()) {
|
||||
ShowText(true);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Swipe direction=" << direction;
|
||||
switch (direction) {
|
||||
case SwipeDirection::UP:
|
||||
ProcessKey(KEY_UP, 1); // press up key
|
||||
ProcessKey(KEY_UP, 0); // and release it
|
||||
break;
|
||||
|
||||
case SwipeDirection::DOWN:
|
||||
ProcessKey(KEY_DOWN, 1); // press down key
|
||||
ProcessKey(KEY_DOWN, 0); // and release it
|
||||
break;
|
||||
|
||||
case SwipeDirection::LEFT:
|
||||
case SwipeDirection::RIGHT:
|
||||
ProcessKey(KEY_POWER, 1); // press power key
|
||||
ProcessKey(KEY_POWER, 0); // and release it
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
|
||||
struct input_event ev;
|
||||
if (ev_get_input(fd, epevents, &ev) == -1) {
|
||||
return -1;
|
||||
}
|
||||
struct input_event ev;
|
||||
if (ev_get_input(fd, epevents, &ev) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
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 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
|
||||
//
|
||||
// updown == 1 for key down events; 0 for key up events
|
||||
void RecoveryUI::ProcessKey(int key_code, int updown) {
|
||||
bool register_key = false;
|
||||
bool long_press = false;
|
||||
bool reboot_enabled;
|
||||
bool register_key = false;
|
||||
bool long_press = false;
|
||||
bool reboot_enabled;
|
||||
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
key_pressed[key_code] = updown;
|
||||
if (updown) {
|
||||
++key_down_count;
|
||||
key_last_down = key_code;
|
||||
key_long_press = false;
|
||||
key_timer_t* info = new key_timer_t;
|
||||
info->ui = this;
|
||||
info->key_code = key_code;
|
||||
info->count = key_down_count;
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info);
|
||||
pthread_detach(thread);
|
||||
} else {
|
||||
if (key_last_down == key_code) {
|
||||
long_press = key_long_press;
|
||||
register_key = true;
|
||||
}
|
||||
key_last_down = -1;
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
key_pressed[key_code] = updown;
|
||||
if (updown) {
|
||||
++key_down_count;
|
||||
key_last_down = key_code;
|
||||
key_long_press = false;
|
||||
key_timer_t* info = new key_timer_t;
|
||||
info->ui = this;
|
||||
info->key_code = key_code;
|
||||
info->count = key_down_count;
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info);
|
||||
pthread_detach(thread);
|
||||
} else {
|
||||
if (key_last_down == key_code) {
|
||||
long_press = key_long_press;
|
||||
register_key = true;
|
||||
}
|
||||
reboot_enabled = enable_reboot;
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
key_last_down = -1;
|
||||
}
|
||||
reboot_enabled = enable_reboot;
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
|
||||
if (register_key) {
|
||||
switch (CheckKey(key_code, long_press)) {
|
||||
case RecoveryUI::IGNORE:
|
||||
break;
|
||||
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 @@ int RecoveryUI::WaitKey() {
|
||||
}
|
||||
|
||||
bool RecoveryUI::IsUsbConnected() {
|
||||
int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
printf("failed to open /sys/class/android_usb/android0/state: %s\n",
|
||||
strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
printf("failed to open /sys/class/android_usb/android0/state: %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
char buf;
|
||||
// USB is connected if android_usb state is CONNECTED or CONFIGURED.
|
||||
int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C');
|
||||
if (close(fd) < 0) {
|
||||
printf("failed to close /sys/class/android_usb/android0/state: %s\n",
|
||||
strerror(errno));
|
||||
}
|
||||
return connected;
|
||||
char buf;
|
||||
// USB is connected if android_usb state is CONNECTED or CONFIGURED.
|
||||
int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C');
|
||||
if (close(fd) < 0) {
|
||||
printf("failed to close /sys/class/android_usb/android0/state: %s\n", strerror(errno));
|
||||
}
|
||||
return connected;
|
||||
}
|
||||
|
||||
bool RecoveryUI::IsKeyPressed(int key) {
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
int pressed = key_pressed[key];
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
return pressed;
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
int pressed = key_pressed[key];
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
bool RecoveryUI::IsLongPress() {
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
bool result = key_long_press;
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
return result;
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
bool result = key_long_press;
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RecoveryUI::HasThreeButtons() {
|
||||
return has_power_key && has_up_key && has_down_key;
|
||||
return has_power_key && has_up_key && has_down_key;
|
||||
}
|
||||
|
||||
bool RecoveryUI::HasPowerKey() const {
|
||||
return has_power_key;
|
||||
}
|
||||
|
||||
bool RecoveryUI::HasTouchScreen() const {
|
||||
return has_touch_screen;
|
||||
}
|
||||
|
||||
void RecoveryUI::FlushKeys() {
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
key_queue_len = 0;
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
key_queue_len = 0;
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
}
|
||||
|
||||
RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
|
||||
pthread_mutex_lock(&key_queue_mutex);
|
||||
key_long_press = false;
|
||||
pthread_mutex_unlock(&key_queue_mutex);
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,12 +14,47 @@
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# libupdate_verifier (static library)
|
||||
# ===============================
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CLANG := true
|
||||
LOCAL_SRC_FILES := update_verifier.cpp
|
||||
LOCAL_SRC_FILES := \
|
||||
update_verifier.cpp
|
||||
|
||||
LOCAL_MODULE := libupdate_verifier
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libbase \
|
||||
libcutils \
|
||||
android.hardware.boot@1.0
|
||||
|
||||
LOCAL_CFLAGS := -Wall -Werror
|
||||
|
||||
LOCAL_EXPORT_C_INCLUDE_DIRS := \
|
||||
$(LOCAL_PATH)/include
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
$(LOCAL_PATH)/include
|
||||
|
||||
ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
|
||||
LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
|
||||
endif
|
||||
|
||||
ifeq ($(BOARD_AVB_ENABLE),true)
|
||||
LOCAL_CFLAGS += -DBOARD_AVB_ENABLE=1
|
||||
endif
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# update_verifier (executable)
|
||||
# ===============================
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
update_verifier_main.cpp
|
||||
|
||||
LOCAL_MODULE := update_verifier
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libupdate_verifier
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libbase \
|
||||
libcutils \
|
||||
@@ -29,13 +64,8 @@ LOCAL_SHARED_LIBRARIES := \
|
||||
libhidlbase \
|
||||
android.hardware.boot@1.0
|
||||
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
|
||||
LOCAL_CFLAGS := -Wall -Werror
|
||||
|
||||
LOCAL_INIT_RC := update_verifier.rc
|
||||
|
||||
ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
|
||||
LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
|
||||
endif
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
@@ -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);
|
||||
@@ -35,6 +35,8 @@
|
||||
* verifier reaches the end after the verification.
|
||||
*/
|
||||
|
||||
#include "update_verifier/update_verifier.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -42,6 +44,8 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <future>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -59,12 +63,6 @@ using android::hardware::boot::V1_0::IBootControl;
|
||||
using android::hardware::boot::V1_0::BoolResult;
|
||||
using android::hardware::boot::V1_0::CommandResult;
|
||||
|
||||
constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
|
||||
constexpr auto DM_PATH_PREFIX = "/sys/block/";
|
||||
constexpr auto DM_PATH_SUFFIX = "/dm/name";
|
||||
constexpr auto DEV_PATH = "/dev/block/";
|
||||
constexpr int BLOCKSIZE = 4096;
|
||||
|
||||
// Find directories in format of "/sys/block/dm-X".
|
||||
static int dm_name_filter(const dirent* de) {
|
||||
if (android::base::StartsWith(de->d_name, "dm-")) {
|
||||
@@ -82,6 +80,7 @@ static bool read_blocks(const std::string& partition, const std::string& range_s
|
||||
// (or "vendor"), then dm-X is a dm-wrapped system/vendor partition.
|
||||
// Afterwards, update_verifier will read every block on the care_map_file of
|
||||
// "/dev/block/dm-X" to ensure the partition's integrity.
|
||||
static constexpr auto DM_PATH_PREFIX = "/sys/block/";
|
||||
dirent** namelist;
|
||||
int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort);
|
||||
if (n == -1) {
|
||||
@@ -93,18 +92,29 @@ static bool read_blocks(const std::string& partition, const std::string& range_s
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr auto DM_PATH_SUFFIX = "/dm/name";
|
||||
static constexpr auto DEV_PATH = "/dev/block/";
|
||||
std::string dm_block_device;
|
||||
while (n--) {
|
||||
std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX;
|
||||
std::string content;
|
||||
if (!android::base::ReadFileToString(path, &content)) {
|
||||
PLOG(WARNING) << "Failed to read " << path;
|
||||
} else if (android::base::Trim(content) == partition) {
|
||||
dm_block_device = DEV_PATH + std::string(namelist[n]->d_name);
|
||||
while (n--) {
|
||||
free(namelist[n]);
|
||||
} else {
|
||||
std::string dm_block_name = android::base::Trim(content);
|
||||
#ifdef BOARD_AVB_ENABLE
|
||||
// AVB is using 'vroot' for the root block device but we're expecting 'system'.
|
||||
if (dm_block_name == "vroot") {
|
||||
dm_block_name = "system";
|
||||
}
|
||||
#endif
|
||||
if (dm_block_name == partition) {
|
||||
dm_block_device = DEV_PATH + std::string(namelist[n]->d_name);
|
||||
while (n--) {
|
||||
free(namelist[n]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
free(namelist[n]);
|
||||
}
|
||||
@@ -114,11 +124,6 @@ static bool read_blocks(const std::string& partition, const std::string& range_s
|
||||
LOG(ERROR) << "Failed to find dm block device for " << partition;
|
||||
return false;
|
||||
}
|
||||
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
|
||||
if (fd.get() == -1) {
|
||||
PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
|
||||
return false;
|
||||
}
|
||||
|
||||
// For block range string, first integer 'count' equals 2 * total number of valid ranges,
|
||||
// followed by 'count' number comma separated integers. Every two integers reprensent a
|
||||
@@ -132,69 +137,113 @@ static bool read_blocks(const std::string& partition, const std::string& range_s
|
||||
LOG(ERROR) << "Error in parsing range string.";
|
||||
return false;
|
||||
}
|
||||
range_count /= 2;
|
||||
|
||||
size_t blk_count = 0;
|
||||
for (size_t i = 1; i < ranges.size(); i += 2) {
|
||||
unsigned int range_start, range_end;
|
||||
bool parse_status = android::base::ParseUint(ranges[i], &range_start);
|
||||
parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
|
||||
if (!parse_status || range_start >= range_end) {
|
||||
LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
|
||||
return false;
|
||||
}
|
||||
std::vector<std::future<bool>> threads;
|
||||
size_t thread_num = std::thread::hardware_concurrency() ?: 4;
|
||||
thread_num = std::min(thread_num, range_count);
|
||||
size_t group_range_count = (range_count + thread_num - 1) / thread_num;
|
||||
|
||||
if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) {
|
||||
PLOG(ERROR) << "lseek to " << range_start << " failed";
|
||||
return false;
|
||||
}
|
||||
for (size_t t = 0; t < thread_num; t++) {
|
||||
auto thread_func = [t, group_range_count, &dm_block_device, &ranges, &partition]() {
|
||||
size_t blk_count = 0;
|
||||
static constexpr size_t kBlockSize = 4096;
|
||||
std::vector<uint8_t> buf(1024 * kBlockSize);
|
||||
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
|
||||
if (fd.get() == -1) {
|
||||
PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t size = (range_end - range_start) * BLOCKSIZE;
|
||||
std::vector<uint8_t> buf(size);
|
||||
if (!android::base::ReadFully(fd.get(), buf.data(), size)) {
|
||||
PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
|
||||
return false;
|
||||
}
|
||||
blk_count += (range_end - range_start);
|
||||
for (size_t i = group_range_count * 2 * t + 1;
|
||||
i < std::min(group_range_count * 2 * (t + 1) + 1, ranges.size()); i += 2) {
|
||||
unsigned int range_start, range_end;
|
||||
bool parse_status = android::base::ParseUint(ranges[i], &range_start);
|
||||
parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
|
||||
if (!parse_status || range_start >= range_end) {
|
||||
LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lseek64(fd.get(), static_cast<off64_t>(range_start) * kBlockSize, SEEK_SET) == -1) {
|
||||
PLOG(ERROR) << "lseek to " << range_start << " failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t remain = (range_end - range_start) * kBlockSize;
|
||||
while (remain > 0) {
|
||||
size_t to_read = std::min(remain, 1024 * kBlockSize);
|
||||
if (!android::base::ReadFully(fd.get(), buf.data(), to_read)) {
|
||||
PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
|
||||
return false;
|
||||
}
|
||||
remain -= to_read;
|
||||
}
|
||||
blk_count += (range_end - range_start);
|
||||
}
|
||||
LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
|
||||
return true;
|
||||
};
|
||||
|
||||
threads.emplace_back(std::async(std::launch::async, thread_func));
|
||||
}
|
||||
|
||||
LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
|
||||
return true;
|
||||
bool ret = true;
|
||||
for (auto& t : threads) {
|
||||
ret = t.get() && ret;
|
||||
}
|
||||
LOG(INFO) << "Finished reading blocks on " << dm_block_device << " with " << thread_num
|
||||
<< " threads.";
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool verify_image(const std::string& care_map_name) {
|
||||
android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY)));
|
||||
// If the device is flashed before the current boot, it may not have care_map.txt
|
||||
// in /data/ota_package. To allow the device to continue booting in this situation,
|
||||
// we should print a warning and skip the block verification.
|
||||
if (care_map_fd.get() == -1) {
|
||||
PLOG(WARNING) << "Failed to open " << care_map_name;
|
||||
return true;
|
||||
}
|
||||
// Care map file has four lines (two lines if vendor partition is not present):
|
||||
// First line has the block partition name (system/vendor).
|
||||
// Second line holds all ranges of blocks to verify.
|
||||
// The next two lines have the same format but for vendor partition.
|
||||
std::string file_content;
|
||||
if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) {
|
||||
LOG(ERROR) << "Error reading care map contents to string.";
|
||||
return false;
|
||||
}
|
||||
|
||||
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 @@ static int reboot_device() {
|
||||
while (true) pause();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int update_verifier(int argc, char** argv) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
LOG(INFO) << "Started with arg " << i << ": " << argv[i];
|
||||
}
|
||||
@@ -224,23 +273,37 @@ int main(int argc, char** argv) {
|
||||
if (is_successful == BoolResult::FALSE) {
|
||||
// The current slot has not booted successfully.
|
||||
|
||||
#ifdef PRODUCT_SUPPORTS_VERITY
|
||||
#if defined(PRODUCT_SUPPORTS_VERITY) || defined(BOARD_AVB_ENABLE)
|
||||
bool skip_verification = false;
|
||||
std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", "");
|
||||
if (verity_mode.empty()) {
|
||||
// With AVB it's possible to disable verification entirely and
|
||||
// in this case ro.boot.veritymode is empty.
|
||||
#if defined(BOARD_AVB_ENABLE)
|
||||
LOG(WARNING) << "verification has been disabled; marking without verification.";
|
||||
skip_verification = true;
|
||||
#else
|
||||
LOG(ERROR) << "Failed to get dm-verity mode.";
|
||||
return reboot_device();
|
||||
#endif
|
||||
} else if (android::base::EqualsIgnoreCase(verity_mode, "eio")) {
|
||||
// We shouldn't see verity in EIO mode if the current slot hasn't booted successfully before.
|
||||
// Continue the verification until we fail to read some blocks.
|
||||
LOG(WARNING) << "Found dm-verity in EIO mode.";
|
||||
} else if (android::base::EqualsIgnoreCase(verity_mode, "disabled")) {
|
||||
LOG(WARNING) << "dm-verity in disabled mode; marking without verification.";
|
||||
skip_verification = true;
|
||||
} else if (verity_mode != "enforcing") {
|
||||
LOG(ERROR) << "Unexpected dm-verity mode : " << verity_mode << ", expecting enforcing.";
|
||||
return reboot_device();
|
||||
}
|
||||
|
||||
if (!verify_image(CARE_MAP_FILE)) {
|
||||
LOG(ERROR) << "Failed to verify all blocks in care map file.";
|
||||
return reboot_device();
|
||||
if (!skip_verification) {
|
||||
static constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
|
||||
if (!verify_image(CARE_MAP_FILE)) {
|
||||
LOG(ERROR) << "Failed to verify all blocks in care map file.";
|
||||
return reboot_device();
|
||||
}
|
||||
}
|
||||
#else
|
||||
LOG(WARNING) << "dm-verity not enabled; marking without verification.";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -52,6 +52,7 @@ updater_common_static_libraries := \
|
||||
libcrypto_utils \
|
||||
libcutils \
|
||||
libtune2fs \
|
||||
libbrotli \
|
||||
$(tune2fs_static_libraries)
|
||||
|
||||
# libupdater (static library)
|
||||
|
||||
+848
-859
File diff suppressed because it is too large
Load Diff
@@ -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_;
|
||||
};
|
||||
+37
-45
@@ -74,7 +74,6 @@
|
||||
#endif
|
||||
|
||||
#include "otautil/DirUtil.h"
|
||||
#include "otautil/ZipUtil.h"
|
||||
#include "print_sha1.h"
|
||||
#include "tune2fs.h"
|
||||
#include "updater/updater.h"
|
||||
@@ -194,8 +193,8 @@ Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr
|
||||
|
||||
if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(),
|
||||
MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) {
|
||||
uiPrintf(state, "%s: failed to mount %s at %s: %s\n", name, location.c_str(),
|
||||
mount_point.c_str(), strerror(errno));
|
||||
uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(),
|
||||
strerror(errno));
|
||||
return StringValue("");
|
||||
}
|
||||
|
||||
@@ -244,12 +243,12 @@ Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_p
|
||||
scan_mounted_volumes();
|
||||
MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
|
||||
if (vol == nullptr) {
|
||||
uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point.c_str());
|
||||
uiPrintf(state, "Failed to unmount %s: No such volume", mount_point.c_str());
|
||||
return nullptr;
|
||||
} else {
|
||||
int ret = unmount_mounted_volume(vol);
|
||||
if (ret != 0) {
|
||||
uiPrintf(state, "unmount of %s failed (%d): %s\n", mount_point.c_str(), ret, strerror(errno));
|
||||
uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,9 +315,31 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
|
||||
}
|
||||
|
||||
if (fs_type == "ext4") {
|
||||
int status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle);
|
||||
const char* mke2fs_argv[] = { "/sbin/mke2fs_static", "-t", "ext4", "-b", "4096",
|
||||
location.c_str(), nullptr, nullptr };
|
||||
std::string size_str;
|
||||
if (size != 0) {
|
||||
size_str = std::to_string(size / 4096LL);
|
||||
mke2fs_argv[6] = size_str.c_str();
|
||||
}
|
||||
|
||||
int status = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
|
||||
if (status != 0) {
|
||||
LOG(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 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
|
||||
std::string num_sectors = std::to_string(size / 512);
|
||||
|
||||
const char* f2fs_path = "/sbin/mkfs.f2fs";
|
||||
const char* const f2fs_argv[] = { "mkfs.f2fs", "-t", "-d1", location.c_str(),
|
||||
num_sectors.c_str(), nullptr };
|
||||
int status = exec_cmd(f2fs_path, const_cast<char* const*>(f2fs_argv));
|
||||
const char* f2fs_argv[] = {
|
||||
"mkfs.f2fs", "-t", "-d1", location.c_str(), (size < 512) ? nullptr : num_sectors.c_str(),
|
||||
nullptr
|
||||
};
|
||||
int status = exec_cmd(f2fs_path, const_cast<char**>(f2fs_argv));
|
||||
if (status != 0) {
|
||||
LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location;
|
||||
return StringValue("");
|
||||
@@ -400,36 +423,6 @@ Value* SetProgressFn(const char* name, State* state, const std::vector<std::uniq
|
||||
return StringValue(frac_str);
|
||||
}
|
||||
|
||||
// package_extract_dir(package_dir, dest_dir)
|
||||
// Extracts all files from the package underneath package_dir and writes them to the
|
||||
// corresponding tree beneath dest_dir. Any existing files are overwritten.
|
||||
// Example: package_extract_dir("system", "/system")
|
||||
//
|
||||
// Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path.
|
||||
Value* PackageExtractDirFn(const char* name, State* state,
|
||||
const std::vector<std::unique_ptr<Expr>>&argv) {
|
||||
if (argv.size() != 2) {
|
||||
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
|
||||
argv.size());
|
||||
}
|
||||
|
||||
std::vector<std::string> args;
|
||||
if (!ReadArgs(state, argv, &args)) {
|
||||
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||
}
|
||||
const std::string& zip_path = args[0];
|
||||
const std::string& dest_path = args[1];
|
||||
|
||||
ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
|
||||
|
||||
// To create a consistent system image, never use the clock for timestamps.
|
||||
constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default
|
||||
|
||||
bool success = ExtractPackageRecursive(za, zip_path, dest_path, ×tamp, sehandle);
|
||||
|
||||
return StringValue(success ? "t" : "");
|
||||
}
|
||||
|
||||
// package_extract_file(package_file[, dest_file])
|
||||
// Extracts a single package_file from the update package and writes it to dest_file,
|
||||
// overwriting existing files if necessary. Without the dest_file argument, returns the
|
||||
@@ -712,15 +705,15 @@ Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std::
|
||||
return StringValue(result == 0 ? "t" : "");
|
||||
}
|
||||
|
||||
// This is the updater side handler for ui_print() in edify script. Contents
|
||||
// will be sent over to the recovery side for on-screen display.
|
||||
// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
|
||||
// the recovery side for on-screen display.
|
||||
Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||
std::vector<std::string> args;
|
||||
if (!ReadArgs(state, argv, &args)) {
|
||||
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
|
||||
}
|
||||
|
||||
std::string buffer = android::base::Join(args, "") + "\n";
|
||||
std::string buffer = android::base::Join(args, "");
|
||||
uiPrint(state, buffer);
|
||||
return StringValue(buffer);
|
||||
}
|
||||
@@ -1052,7 +1045,6 @@ void RegisterInstallFunctions() {
|
||||
RegisterFunction("format", FormatFn);
|
||||
RegisterFunction("show_progress", ShowProgressFn);
|
||||
RegisterFunction("set_progress", SetProgressFn);
|
||||
RegisterFunction("package_extract_dir", PackageExtractDirFn);
|
||||
RegisterFunction("package_extract_file", PackageExtractFileFn);
|
||||
|
||||
RegisterFunction("getprop", GetPropFn);
|
||||
|
||||
+13
-8
@@ -94,7 +94,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
const char* package_filename = argv[3];
|
||||
MemMapping map;
|
||||
if (sysMapFile(package_filename, &map) != 0) {
|
||||
if (!map.MapFile(package_filename)) {
|
||||
LOG(ERROR) << "failed to map package " << argv[3];
|
||||
return 3;
|
||||
}
|
||||
@@ -211,12 +211,18 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (state.error_code != kNoError) {
|
||||
fprintf(cmd_pipe, "log error: %d\n", state.error_code);
|
||||
// Cause code should provide additional information about the abort;
|
||||
// report only when an error exists.
|
||||
if (state.cause_code != kNoCause) {
|
||||
fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
|
||||
// Installation has been aborted. Set the error code to kScriptExecutionFailure unless
|
||||
// a more specific code has been set in errmsg.
|
||||
if (state.error_code == kNoError) {
|
||||
state.error_code = kScriptExecutionFailure;
|
||||
}
|
||||
fprintf(cmd_pipe, "log error: %d\n", state.error_code);
|
||||
// Cause code should provide additional information about the abort.
|
||||
if (state.cause_code != kNoCause) {
|
||||
fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
|
||||
if (state.cause_code == kPatchApplicationFailure) {
|
||||
LOG(INFO) << "Patch application failed, retry update.";
|
||||
fprintf(cmd_pipe, "retry_update\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +237,6 @@ int main(int argc, char** argv) {
|
||||
if (updater_info.package_zip) {
|
||||
CloseArchive(updater_info.package_zip);
|
||||
}
|
||||
sysReleaseMap(&map);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+72
-73
@@ -474,81 +474,80 @@ std::unique_ptr<EC_KEY, ECKEYDeleter> parse_ec_key(FILE* file) {
|
||||
// Otherwise returns false if the file failed to parse, or if it contains zero
|
||||
// keys. The contents in certs would be unspecified on failure.
|
||||
bool load_keys(const char* filename, std::vector<Certificate>& certs) {
|
||||
std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "r"), fclose);
|
||||
if (!f) {
|
||||
PLOG(ERROR) << "error opening " << filename;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
-177
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+241
-256
@@ -45,167 +45,164 @@ static WearRecoveryUI* self = NULL;
|
||||
|
||||
// Return the current time as a double (including fractions of a second).
|
||||
static double now() {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_sec + tv.tv_usec / 1000000.0;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_sec + tv.tv_usec / 1000000.0;
|
||||
}
|
||||
|
||||
WearRecoveryUI::WearRecoveryUI() :
|
||||
progress_bar_y(259),
|
||||
outer_height(0),
|
||||
outer_width(0),
|
||||
menu_unusable_rows(0) {
|
||||
intro_frames = 22;
|
||||
loop_frames = 60;
|
||||
animation_fps = 30;
|
||||
WearRecoveryUI::WearRecoveryUI()
|
||||
: kProgressBarBaseline(RECOVERY_UI_PROGRESS_BAR_BASELINE),
|
||||
kMenuUnusableRows(RECOVERY_UI_MENU_UNUSABLE_ROWS) {
|
||||
// TODO: kMenuUnusableRows should be computed based on the lines in draw_screen_locked().
|
||||
|
||||
for (size_t i = 0; i < 5; i++)
|
||||
backgroundIcon[i] = NULL;
|
||||
// TODO: The following three variables are likely not needed. The first two are detected
|
||||
// automatically in ScreenRecoveryUI::LoadAnimation(), based on the actual files seen on device.
|
||||
intro_frames = 22;
|
||||
loop_frames = 60;
|
||||
|
||||
self = this;
|
||||
touch_screen_allowed_ = true;
|
||||
|
||||
for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL;
|
||||
|
||||
self = this;
|
||||
}
|
||||
|
||||
int WearRecoveryUI::GetProgressBaseline() {
|
||||
return progress_bar_y;
|
||||
int WearRecoveryUI::GetProgressBaseline() const {
|
||||
return kProgressBarBaseline;
|
||||
}
|
||||
|
||||
// Draw background frame on the screen. Does not flip pages.
|
||||
// Should only be called with updateMutex locked.
|
||||
// TODO merge drawing routines with screen_ui
|
||||
void WearRecoveryUI::draw_background_locked()
|
||||
{
|
||||
pagesIdentical = false;
|
||||
gr_color(0, 0, 0, 255);
|
||||
gr_fill(0, 0, gr_fb_width(), gr_fb_height());
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
for (int i = menu_start; i < menu_end; ++i) {
|
||||
if (i == menu_sel) {
|
||||
// draw the highlight bar
|
||||
SetColor(MENU_SEL_BG);
|
||||
gr_fill(x, y - 2, gr_fb_width() - x, y + char_height_ + 2);
|
||||
// white text of selected item
|
||||
SetColor(MENU_SEL_FG);
|
||||
if (menu_[i][0]) {
|
||||
gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 1);
|
||||
}
|
||||
SetColor(MENU);
|
||||
} else if (menu_[i][0]) {
|
||||
gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 0);
|
||||
}
|
||||
y += char_height_ + 4;
|
||||
}
|
||||
SetColor(MENU);
|
||||
y += 4;
|
||||
gr_fill(0, y, gr_fb_width(), y + 2);
|
||||
y += 4;
|
||||
}
|
||||
|
||||
SetColor(LOG);
|
||||
|
||||
// display from the bottom up, until we hit the top of the
|
||||
// screen, the bottom of the menu, or we've displayed the
|
||||
// entire text buffer.
|
||||
int ty;
|
||||
int row = (text_top_ + text_rows_ - 1) % text_rows_;
|
||||
size_t count = 0;
|
||||
for (int ty = gr_fb_height() - char_height_ - kMarginHeight; ty > y + 2 && count < text_rows_;
|
||||
ty -= char_height_, ++count) {
|
||||
gr_text(gr_sys_font(), x + 4, ty, text_[row], 0);
|
||||
--row;
|
||||
if (row < 0) row = text_rows_ - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO merge drawing routines with screen_ui
|
||||
void WearRecoveryUI::update_progress_locked() {
|
||||
draw_screen_locked();
|
||||
gr_flip();
|
||||
draw_screen_locked();
|
||||
gr_flip();
|
||||
}
|
||||
|
||||
bool WearRecoveryUI::InitTextParams() {
|
||||
if (!ScreenRecoveryUI::InitTextParams()) {
|
||||
return false;
|
||||
}
|
||||
if (!ScreenRecoveryUI::InitTextParams()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_;
|
||||
text_cols_ = (gr_fb_width() - (kMarginWidth * 2)) / char_width_;
|
||||
|
||||
if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
|
||||
if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
|
||||
if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
|
||||
if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
|
||||
|
||||
visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_;
|
||||
return true;
|
||||
visible_text_rows = (gr_fb_height() - (kMarginHeight * 2)) / char_height_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WearRecoveryUI::Init(const std::string& locale) {
|
||||
@@ -222,7 +219,8 @@ bool WearRecoveryUI::Init(const std::string& locale) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void WearRecoveryUI::SetStage(int current, int max) {}
|
||||
void WearRecoveryUI::SetStage(int current, int max) {
|
||||
}
|
||||
|
||||
void WearRecoveryUI::Print(const char* fmt, ...) {
|
||||
char buf[256];
|
||||
@@ -252,165 +250,152 @@ void WearRecoveryUI::Print(const char* fmt, ...) {
|
||||
pthread_mutex_unlock(&updateMutex);
|
||||
}
|
||||
|
||||
void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
|
||||
void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
|
||||
int initial_selection) {
|
||||
pthread_mutex_lock(&updateMutex);
|
||||
if (text_rows_ > 0 && text_cols_ > 0) {
|
||||
menu_headers_ = headers;
|
||||
size_t i = 0;
|
||||
// "i < text_rows_" is removed from the loop termination condition,
|
||||
// which is different from the one in ScreenRecoveryUI::StartMenu().
|
||||
// Because WearRecoveryUI supports scrollable menu, it's fine to have
|
||||
// more entries than text_rows_. The menu may be truncated otherwise.
|
||||
// Bug: 23752519
|
||||
for (; items[i] != nullptr; i++) {
|
||||
strncpy(menu_[i], items[i], text_cols_ - 1);
|
||||
menu_[i][text_cols_ - 1] = '\0';
|
||||
}
|
||||
menu_items = i;
|
||||
show_menu = true;
|
||||
menu_sel = initial_selection;
|
||||
menu_start = 0;
|
||||
menu_end = visible_text_rows - 1 - menu_unusable_rows;
|
||||
if (menu_items <= menu_end)
|
||||
menu_end = menu_items;
|
||||
update_screen_locked();
|
||||
pthread_mutex_lock(&updateMutex);
|
||||
if (text_rows_ > 0 && text_cols_ > 0) {
|
||||
menu_headers_ = headers;
|
||||
menu_.clear();
|
||||
// "i < text_rows_" is removed from the loop termination condition,
|
||||
// which is different from the one in ScreenRecoveryUI::StartMenu().
|
||||
// Because WearRecoveryUI supports scrollable menu, it's fine to have
|
||||
// more entries than text_rows_. The menu may be truncated otherwise.
|
||||
// Bug: 23752519
|
||||
for (size_t i = 0; items[i] != nullptr; i++) {
|
||||
menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1)));
|
||||
}
|
||||
pthread_mutex_unlock(&updateMutex);
|
||||
menu_items = static_cast<int>(menu_.size());
|
||||
show_menu = true;
|
||||
menu_sel = initial_selection;
|
||||
menu_start = 0;
|
||||
menu_end = visible_text_rows - 1 - kMenuUnusableRows;
|
||||
if (menu_items <= menu_end) menu_end = menu_items;
|
||||
update_screen_locked();
|
||||
}
|
||||
pthread_mutex_unlock(&updateMutex);
|
||||
}
|
||||
|
||||
int WearRecoveryUI::SelectMenu(int sel) {
|
||||
int old_sel;
|
||||
pthread_mutex_lock(&updateMutex);
|
||||
if (show_menu) {
|
||||
old_sel = menu_sel;
|
||||
menu_sel = sel;
|
||||
if (menu_sel < 0) menu_sel = 0;
|
||||
if (menu_sel >= menu_items) menu_sel = menu_items-1;
|
||||
if (menu_sel < menu_start) {
|
||||
menu_start--;
|
||||
menu_end--;
|
||||
} else if (menu_sel >= menu_end && menu_sel < menu_items) {
|
||||
menu_end++;
|
||||
menu_start++;
|
||||
}
|
||||
sel = menu_sel;
|
||||
if (menu_sel != old_sel) update_screen_locked();
|
||||
int old_sel;
|
||||
pthread_mutex_lock(&updateMutex);
|
||||
if (show_menu) {
|
||||
old_sel = menu_sel;
|
||||
menu_sel = sel;
|
||||
if (menu_sel < 0) menu_sel = 0;
|
||||
if (menu_sel >= menu_items) menu_sel = menu_items - 1;
|
||||
if (menu_sel < menu_start) {
|
||||
menu_start--;
|
||||
menu_end--;
|
||||
} else if (menu_sel >= menu_end && menu_sel < menu_items) {
|
||||
menu_end++;
|
||||
menu_start++;
|
||||
}
|
||||
pthread_mutex_unlock(&updateMutex);
|
||||
return sel;
|
||||
sel = menu_sel;
|
||||
if (menu_sel != old_sel) update_screen_locked();
|
||||
}
|
||||
pthread_mutex_unlock(&updateMutex);
|
||||
return sel;
|
||||
}
|
||||
|
||||
void WearRecoveryUI::ShowFile(FILE* fp) {
|
||||
std::vector<off_t> offsets;
|
||||
offsets.push_back(ftello(fp));
|
||||
ClearText();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user