diff options
80 files changed, 4728 insertions, 424 deletions
@@ -1,4 +1,5 @@ # Copyright (C) 2007 The Android Open Source Project +# Copyright (C) 2015 The CyanogenMod Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +ifeq ($(call my-dir),$(call project-path-for,recovery)) + LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) @@ -41,17 +44,18 @@ LOCAL_SRC_FILES := \ verifier.cpp \ wear_ui.cpp \ wear_touch.cpp \ + voldclient.cpp + +# External tools +LOCAL_SRC_FILES += \ + ../../system/core/toolbox/newfs_msdos.c \ + ../../system/core/toolbox/start_stop.cpp \ + ../../system/vold/vdc.cpp LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -ifeq ($(TARGET_USERIMAGES_USE_F2FS),true) -ifeq ($(HOST_OS),linux) -LOCAL_REQUIRED_MODULES := mkfs.f2fs -endif -endif - RECOVERY_API_VERSION := 3 RECOVERY_FSTAB_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) @@ -62,43 +66,92 @@ LOCAL_C_INCLUDES += \ system/vold \ system/extras/ext4_utils \ system/core/adb \ + external/e2fsprogs/lib LOCAL_STATIC_LIBRARIES := \ libbatterymonitor \ libbootloader_message \ + libminivold_static \ libext4_utils_static \ + libmake_ext4fs_static \ + libminizip_static \ + libminiunz_static \ libsparse_static \ + libfsck_msdos \ + libminipigz_static \ + libzopfli \ + libreboot_static \ + libsdcard \ libminzip \ libz \ libmtdutils \ libminadbd \ + libtoybox_driver \ + libmksh_static \ libfusesideload \ libminui \ libpng \ + libf2fs_sparseblock \ + libdiskconfig \ + libsysutils \ libfs_mgr \ + libcrypto_utils_static \ libcrypto_static \ libbase \ - libcutils \ libutils \ liblog \ + liblogwrap \ libselinux \ + libscrypt_static \ + libnl \ + libc++_static \ libm \ - libc + libc \ + libext2_blkid \ + libext2_uuid \ + libfec \ + libfec_rs \ + libsquashfs_utils LOCAL_HAL_STATIC_LIBRARIES := libhealthd - -ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) - LOCAL_CFLAGS += -DUSE_EXT4 - LOCAL_C_INCLUDES += system/extras/ext4_utils - LOCAL_STATIC_LIBRARIES += libext4_utils_static libz -endif +LOCAL_WHOLE_STATIC_LIBRARIES += libcutils ifeq ($(AB_OTA_UPDATER),true) LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 endif +# OEMLOCK support requires a device specific liboemlock be supplied. +# See comments in recovery.cpp for the API. +ifeq ($(TARGET_HAVE_OEMLOCK), true) + LOCAL_CFLAGS += -DHAVE_OEMLOCK + LOCAL_STATIC_LIBRARIES += liboemlock +endif + LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +# Handling for EV_REL is disabled by default because some accelerometers +# send EV_REL events. Actual EV_REL devices are rare on modern hardware +# so it's cleaner just to disable it by default. +ifneq ($(BOARD_RECOVERY_NEEDS_REL_INPUT),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_NEEDS_REL_INPUT +endif + +ifeq ($(TARGET_USE_MDTP), true) + LOCAL_CFLAGS += -DUSE_MDTP +endif + +ifeq ($(BOARD_HAS_DOWNLOAD_MODE), true) + LOCAL_CFLAGS += -DDOWNLOAD_MODE +endif + +ifeq ($(TARGET_BUILD_VARIANT),user) + LOCAL_CFLAGS += -DRELEASE_BUILD +endif + +LOCAL_CFLAGS += -DUSE_EXT4 -DMINIVOLD +LOCAL_C_INCLUDES += system/extras/ext4_utils system/core/fs_mgr/include external/fsck_msdos +LOCAL_C_INCLUDES += system/vold + ifeq ($(TARGET_RECOVERY_UI_LIB),) LOCAL_SRC_FILES += default_device.cpp else @@ -109,8 +162,149 @@ ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),) LOCAL_REQUIRED_MODULES := recovery-persist recovery-refresh endif +LOCAL_C_INCLUDES += system/extras/ext4_utils +LOCAL_C_INCLUDES += external/boringssl/include + +ifeq ($(ONE_SHOT_MAKEFILE),) +LOCAL_ADDITIONAL_DEPENDENCIES += \ + fstools \ + recovery_mkshrc \ + bu_recovery \ + toybox_recovery_links + +endif + +TOYBOX_INSTLIST := $(HOST_OUT_EXECUTABLES)/toybox-instlist + +# Set up the static symlinks +RECOVERY_TOOLS := \ + gunzip gzip make_ext4fs minivold reboot setup_adbd sh start stop toybox unzip vdc zip +LOCAL_POST_INSTALL_CMD := \ + $(hide) $(foreach t,$(RECOVERY_TOOLS),ln -sf recovery $(TARGET_RECOVERY_ROOT_OUT)/sbin/$(t);) + +ifneq ($(TARGET_RECOVERY_DEVICE_MODULES),) + LOCAL_ADDITIONAL_DEPENDENCIES += $(TARGET_RECOVERY_DEVICE_MODULES) +endif + include $(BUILD_EXECUTABLE) +# Run toybox-instlist and generate the rest of the symlinks +toybox_recovery_links: $(TOYBOX_INSTLIST) +toybox_recovery_links: TOYBOX_BINARY := $(TARGET_RECOVERY_ROOT_OUT)/sbin/toybox +toybox_recovery_links: + @echo -e ${CL_CYN}"Generate Toybox links:"${CL_RST} $$($(TOYBOX_INSTLIST)) + @mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/sbin + $(hide) $(TOYBOX_INSTLIST) | xargs -I'{}' ln -sf toybox '$(TARGET_RECOVERY_ROOT_OUT)/sbin/{}' + +# mkshrc +include $(CLEAR_VARS) +LOCAL_MODULE := recovery_mkshrc +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/etc +LOCAL_SRC_FILES := etc/mkshrc +LOCAL_MODULE_STEM := mkshrc +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := bu_recovery +LOCAL_MODULE_STEM := bu +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_SRC_FILES := \ + bu.cpp \ + backup.cpp \ + restore.cpp \ + roots.cpp \ + voldclient.cpp +LOCAL_CFLAGS += -DMINIVOLD +LOCAL_CFLAGS += -Wno-unused-parameter +#ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) + LOCAL_CFLAGS += -DUSE_EXT4 + LOCAL_C_INCLUDES += system/extras/ext4_utils + LOCAL_STATIC_LIBRARIES += libext4_utils_static libz +#endif +LOCAL_STATIC_LIBRARIES += \ + libsparse_static \ + libz \ + libmtdutils \ + libminadbd \ + libminui \ + libfs_mgr \ + libtar \ + libcrypto_utils_static \ + libcrypto_static \ + libselinux \ + libutils \ + libcutils \ + liblog \ + libm \ + libc \ + libext2_blkid \ + libext2_uuid + +LOCAL_C_INCLUDES += \ + system/core/fs_mgr/include \ + system/core/include \ + system/core/libcutils \ + system/vold \ + external/libtar \ + external/libtar/listhash \ + external/openssl/include \ + external/zlib \ + bionic/libc/bionic \ + external/e2fsprogs/lib + + +include $(BUILD_EXECUTABLE) + +# make_ext4fs +include $(CLEAR_VARS) +LOCAL_MODULE := libmake_ext4fs_static +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS := -Dmain=make_ext4fs_main +LOCAL_SRC_FILES := \ + ../../system/extras/ext4_utils/make_ext4fs_main.c \ + ../../system/core/libcutils/canned_fs_config.c +LOCAL_STATIC_LIBRARIES += libselinux +include $(BUILD_STATIC_LIBRARY) + +# Minizip static library +include $(CLEAR_VARS) +LOCAL_MODULE := libminizip_static +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS := -Dmain=minizip_main -D__ANDROID__ -DIOAPI_NO_64 -Wno-unknown-attributes +LOCAL_C_INCLUDES := external/zlib +LOCAL_SRC_FILES := \ + ../../external/zlib/src/contrib/minizip/ioapi.c \ + ../../external/zlib/src/contrib/minizip/minizip.c \ + ../../external/zlib/src/contrib/minizip/zip.c +include $(BUILD_STATIC_LIBRARY) + +# Miniunz static library +include $(CLEAR_VARS) +LOCAL_MODULE := libminiunz_static +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS := -Dmain=miniunz_main -D__ANDROID__ -DIOAPI_NO_64 -Wno-unknown-attributes +LOCAL_C_INCLUDES := external/zlib bionic/libc/include +LOCAL_SRC_FILES := \ + ../../external/zlib/src/contrib/minizip/ioapi.c \ + ../../external/zlib/src/contrib/minizip/miniunz.c \ + ../../external/zlib/src/contrib/minizip/unzip.c +LOCAL_STATIC_LIBRARIES += libc +include $(BUILD_STATIC_LIBRARY) + +# Reboot static library +include $(CLEAR_VARS) +LOCAL_MODULE := libreboot_static +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS := -Dmain=reboot_main +LOCAL_SRC_FILES := ../../system/core/reboot/reboot.c +include $(BUILD_STATIC_LIBRARY) + + # recovery-persist (system partition dynamic executable run after /data mounts) # =============================== include $(CLEAR_VARS) @@ -140,7 +334,8 @@ LOCAL_SRC_FILES := \ asn1_decoder.cpp \ verifier.cpp \ ui.cpp -LOCAL_STATIC_LIBRARIES := libcrypto_static +LOCAL_C_INCLUDES := system/core/fs_mgr/include +LOCAL_STATIC_LIBRARIES := libcrypto_utils_static libcrypto_static include $(BUILD_STATIC_LIBRARY) include \ @@ -157,3 +352,6 @@ include \ $(LOCAL_PATH)/uncrypt/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ $(LOCAL_PATH)/update_verifier/Android.mk \ + $(LOCAL_PATH)/fstools/Android.mk + +endif diff --git a/adb_install.cpp b/adb_install.cpp index 4aed9d4b..69858960 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -34,6 +34,7 @@ #include "fuse_sideload.h" static RecoveryUI* ui = NULL; +static pthread_t sideload_thread; static void set_usb_driver(bool enabled) { @@ -66,69 +67,80 @@ maybe_restart_adbd() { } } +struct sideload_data { + bool* wipe_cache; + const char* install_file; + bool cancel; + int result; +}; + +static struct sideload_data sideload_data; + // How long (in seconds) we wait for the host to start sending us a // package, before timing out. #define ADB_INSTALL_TIMEOUT 300 -int -apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) { - modified_flash = true; - - ui = ui_; - - stop_adbd(); - set_usb_driver(true); - - ui->Print("\n\nNow send the package you want to apply\n" - "to the device with \"adb sideload <filename>\"...\n"); - +void *adb_sideload_thread(void* v) { pid_t child; if ((child = fork()) == 0) { execl("/sbin/recovery", "recovery", "--adbd", NULL); _exit(-1); } + time_t start_time = time(NULL); + time_t now = start_time; + // 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; + int result = INSTALL_NONE; + int status = -1; struct stat st; - for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { - if (waitpid(child, &status, WNOHANG) != 0) { + while (now - start_time < ADB_INSTALL_TIMEOUT) { + /* + * Exit if either: + * - The adb child process dies, or + * - The ui tells us to cancel + */ + if (kill(child, 0) != 0) { result = INSTALL_ERROR; - waited = true; break; } - if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { - if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) { - sleep(1); - continue; - } else { - ui->Print("\nTimed out waiting for package.\n\n"); - result = INSTALL_ERROR; - kill(child, SIGKILL); - break; - } + if (sideload_data.cancel) { + break; + } + + status = stat(FUSE_SIDELOAD_HOST_PATHNAME, &st); + if (status == 0) { + break; } - result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false, 0); - break; + if (errno != ENOENT && errno != ENOTCONN) { + ui->Print("\nError %s waiting for package\n\n", strerror(errno)); + result = INSTALL_ERROR; + break; + } + + sleep(1); + now = time(NULL); } - if (!waited) { - // Calling stat() on this magic filename signals the minadbd - // subprocess to shut down. - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + if (status == 0) { + // Signal UI thread that we can no longer cancel + ui->CancelWaitKey(); - // 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); + result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, + sideload_data.wipe_cache, + sideload_data.install_file, + false, 0); + + sideload_data.result = result; } + // Ensure adb exits + kill(child, SIGTERM); + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { if (WEXITSTATUS(status) == 3) { ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); @@ -137,8 +149,44 @@ apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) { } } - set_usb_driver(false); + LOGI("sideload thread finished\n"); + return NULL; +} + +void +start_sideload(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) { + modified_flash = true; + + ui = ui_; + + stop_adbd(); + set_usb_driver(true); + + ui->Print("\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload <filename>\"...\n"); + + sideload_data.wipe_cache = wipe_cache; + sideload_data.install_file = install_file; + sideload_data.cancel = false; + sideload_data.result = INSTALL_NONE; + + pthread_create(&sideload_thread, NULL, &adb_sideload_thread, NULL); +} + +void stop_sideload() { + sideload_data.cancel = true; +} + +int wait_sideload() { + set_perf_mode(true); + + pthread_join(sideload_thread, NULL); + + ui->FlushKeys(); + maybe_restart_adbd(); - return result; + set_perf_mode(false); + + return sideload_data.result; } diff --git a/adb_install.h b/adb_install.h index efad436f..7c9d7bcb 100644 --- a/adb_install.h +++ b/adb_install.h @@ -19,6 +19,8 @@ class RecoveryUI; -int apply_from_adb(RecoveryUI* h, bool* wipe_cache, const char* install_file); +void start_sideload(RecoveryUI* h, bool* wipe_cache, const char* install_file); +void stop_sideload(); +int wait_sideload(); #endif diff --git a/backup.cpp b/backup.cpp new file mode 100644 index 00000000..28b023b0 --- /dev/null +++ b/backup.cpp @@ -0,0 +1,300 @@ +#include <stdlib.h> +#include <stdio.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/vfs.h> +#include <time.h> + +#include "cutils/properties.h" + +#include "roots.h" + +#include "bu.h" + +#include "voldclient.h" + +using namespace android; + +static int append_sod(const char* opt_hash) +{ + const char* key; + char value[PROPERTY_VALUE_MAX]; + int len; + char buf[PROP_LINE_LEN]; + char sodbuf[PROP_LINE_LEN*10]; + char* p = sodbuf; + + key = "hash.name"; + strcpy(value, opt_hash); + p += sprintf(p, "%s=%s\n", key, value); + + key = "ro.product.device"; + property_get(key, value, ""); + p += sprintf(p, "%s=%s\n", key, value); + + for (int i = 0; i < MAX_PART; ++i) { + partspec* part = part_get(i); + if (!part) + break; + if (!volume_is_mountable(part->vol) || + volume_is_readonly(part->vol) || + volume_is_verity(part->vol)) { + int fd = open(part->vol->blk_device, O_RDONLY); + part->size = part->used = lseek64(fd, 0, SEEK_END); + close(fd); + } + else { + if (ensure_path_mounted(part->path) == 0) { + struct statfs stfs; + memset(&stfs, 0, sizeof(stfs)); + if (statfs(part->path, &stfs) == 0) { + part->size = (stfs.f_blocks) * stfs.f_bsize; + part->used = (stfs.f_blocks - stfs.f_bfree) * stfs.f_bsize; + } + else { + logmsg("Failed to statfs %s: %s\n", part->path, strerror(errno)); + } + ensure_path_unmounted(part->path); + } + else { + int fd = open(part->vol->blk_device, O_RDONLY); + part->size = part->used = lseek64(fd, 0, SEEK_END); + close(fd); + } + } + p += sprintf(p, "fs.%s.size=%llu\n", part->name, part->size); + p += sprintf(p, "fs.%s.used=%llu\n", part->name, part->used); + } + + int rc = tar_append_file_contents(tar, "SOD", 0600, + getuid(), getgid(), sodbuf, p-sodbuf); + return rc; +} + +static int append_eod(const char* opt_hash) +{ + char buf[PROP_LINE_LEN]; + char eodbuf[PROP_LINE_LEN*10]; + char* p = eodbuf; + int n; + + p += sprintf(p, "hash.datalen=%u\n", hash_datalen); + + unsigned char digest[HASH_MAX_LENGTH]; + char hexdigest[HASH_MAX_STRING_LENGTH]; + + if (!strcasecmp(opt_hash, "sha1")) { + SHA1_Final(digest, &sha_ctx); + for (n = 0; n < SHA_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + p += sprintf(p, "hash.value=%s\n", hexdigest); + } + else { // default to md5 + MD5_Final(digest, &md5_ctx); + for (n = 0; n < MD5_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + p += sprintf(p, "hash.value=%s\n", hexdigest); + } + + int rc = tar_append_file_contents(tar, "EOD", 0600, + getuid(), getgid(), eodbuf, p-eodbuf); + return rc; +} + +static int do_backup_tree(const String8& path) +{ + int rc = 0; + bool path_is_data = !strcmp(path.string(), "/data"); + DIR *dp; + + dp = opendir(path.string()); + struct dirent *de; + while ((de = readdir(dp)) != NULL) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { + continue; + } + if (path_is_data && !strcmp(de->d_name, "media") && vdc->isEmulatedStorage()) { + logmsg("do_backup_tree: skipping datamedia\n"); + continue; + } + struct stat st; + String8 filepath(path); + filepath += "/"; + filepath += de->d_name; + + memset(&st, 0, sizeof(st)); + rc = lstat(filepath.string(), &st); + if (rc != 0) { + logmsg("do_backup_tree: path=%s, lstat failed, rc=%d\n", path.string(), rc); + break; + } + + if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) { + logmsg("do_backup_tree: path=%s, ignoring special file\n", path.string()); + continue; + } + + if (S_ISDIR(st.st_mode)) { + rc = tar_append_file(tar, filepath.string(), filepath.string()); + if (rc != 0) { + logmsg("do_backup_tree: path=%s, tar_append_file failed, rc=%d\n", path.string(), rc); + break; + } + rc = do_backup_tree(filepath); + if (rc != 0) { + logmsg("do_backup_tree: path=%s, recursion failed, rc=%d\n", path.string(), rc); + break; + } + } + else { + rc = tar_append_file(tar, filepath.string(), filepath.string()); + if (rc != 0) { + logmsg("do_backup_tree: path=%s, tar_append_file failed, rc=%d\n", path.string(), rc); + break; + } + } + } + closedir(dp); + return rc; +} + +static int tar_append_device_contents(TAR* t, const char* devname, const char* savename) +{ + struct stat st; + memset(&st, 0, sizeof(st)); + if (lstat(devname, &st) != 0) { + logmsg("tar_append_device_contents: lstat %s failed\n", devname); + return -1; + } + st.st_mode = 0644 | S_IFREG; + + int fd = open(devname, O_RDONLY); + if (fd < 0) { + logmsg("tar_append_device_contents: open %s failed\n", devname); + return -1; + } + st.st_size = lseek64(fd, 0, SEEK_END); + close(fd); + + th_set_from_stat(t, &st); + th_set_path(t, savename); + if (th_write(t) != 0) { + logmsg("tar_append_device_contents: th_write failed\n"); + return -1; + } + if (tar_append_regfile(t, devname) != 0) { + logmsg("tar_append_device_contents: tar_append_regfile %s failed\n", devname); + return -1; + } + return 0; +} + +int do_backup(int argc, char **argv) +{ + int rc = 1; + int n; + int i; + + int len; + int written; + + const char* opt_compress = "gzip"; + const char* opt_hash = "md5"; + + int optidx = 0; + while (optidx < argc && argv[optidx][0] == '-' && argv[optidx][1] == '-') { + char* optname = &argv[optidx][2]; + ++optidx; + char* optval = strchr(optname, '='); + if (optval) { + *optval = '\0'; + ++optval; + } + else { + if (optidx >= argc) { + logmsg("No argument to --%s\n", optname); + return -1; + } + optval = argv[optidx]; + ++optidx; + } + if (!strcmp(optname, "compress")) { + opt_compress = optval; + logmsg("do_backup: compress=%s\n", opt_compress); + } + else if (!strcmp(optname, "hash")) { + opt_hash = optval; + logmsg("do_backup: hash=%s\n", opt_hash); + } + else { + logmsg("do_backup: invalid option name \"%s\"\n", optname); + return -1; + } + } + for (n = optidx; n < argc; ++n) { + const char* partname = argv[n]; + if (*partname == '-') + ++partname; + if (part_add(partname) != 0) { + logmsg("Failed to add partition %s\n", partname); + return -1; + } + } + + rc = create_tar(adb_ofd, opt_compress, "w"); + if (rc != 0) { + logmsg("do_backup: cannot open tar stream\n"); + return rc; + } + + append_sod(opt_hash); + + hash_name = strdup(opt_hash); + + for (i = 0; i < MAX_PART; ++i) { + partspec* curpart = part_get(i); + if (!curpart) + break; + + part_set(curpart); + if (!volume_is_mountable(curpart->vol) || + volume_is_readonly(curpart->vol) || + volume_is_verity(curpart->vol)) { + rc = tar_append_device_contents(tar, curpart->vol->blk_device, curpart->name); + } + else { + if (ensure_path_mounted(curpart->path) != 0) { + rc = tar_append_device_contents(tar, curpart->vol->blk_device, curpart->name); + if (rc != 0) { + logmsg("do_backup: cannot backup %s\n", curpart->path); + continue; + } + } + String8 path(curpart->path); + rc = do_backup_tree(path); + ensure_path_unmounted(curpart->path); + } + } + + free(hash_name); + hash_name = NULL; + + append_eod(opt_hash); + + tar_append_eof(tar); + + if (opt_compress) + gzflush(gzf, Z_FINISH); + + logmsg("backup complete: rc=%d\n", rc); + + return rc; +} + diff --git a/bootloader_message/Android.mk b/bootloader_message/Android.mk index 815ac67d..20889a53 100644 --- a/bootloader_message/Android.mk +++ b/bootloader_message/Android.mk @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +ifneq ($(BOARD_PROVIDES_BOOTLOADER_MESSAGE),true) + LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) @@ -22,3 +24,5 @@ LOCAL_STATIC_LIBRARIES := libbase libfs_mgr LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include include $(BUILD_STATIC_LIBRARY) + +endif diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index c63aeca6..dc588b92 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -25,8 +25,13 @@ // 16K - 64K Used by uncrypt and recovery to store wipe_package for A/B devices // Note that these offsets are admitted by bootloader,recovery and uncrypt, so they // are not configurable without changing all of them. +#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET +static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = BOARD_RECOVERY_BLDRMSG_OFFSET; +static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024 + BOOTLOADER_MESSAGE_OFFSET_IN_MISC; +#else static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; +#endif /* Bootloader Message * @@ -0,0 +1,386 @@ +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/vfs.h> + +#include <cutils/properties.h> +#include <cutils/log.h> + +#include <selinux/label.h> + +#include "roots.h" + +#include "bu.h" + +#include "voldclient.h" + +#define PATHNAME_RC "/tmp/burc" + +#define PATHNAME_XCOMP_ENABLE "/sys/fs/xcomp/enable" + +using namespace std; + +using namespace android; + +struct selabel_handle *sehandle; + +int adb_ifd; +int adb_ofd; +TAR* tar; +gzFile gzf; + +char* hash_name; +size_t hash_datalen; +SHA_CTX sha_ctx; +MD5_CTX md5_ctx; + +void +ui_print(const char* format, ...) { + char buffer[256]; + + va_list ap; + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + fputs(buffer, stdout); +} + +void logmsg(const char *fmt, ...) +{ + char msg[1024]; + FILE* fp; + va_list ap; + + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + fp = fopen("/tmp/bu.log", "a"); + if (fp) { + fprintf(fp, "[%d] %s", getpid(), msg); + fclose(fp); + } +} + +static int xcomp_enable_get(void) +{ + int val = 0; + int fd; + char buf[12+1+1]; + + fd = open(PATHNAME_XCOMP_ENABLE, O_RDONLY); + if (fd < 0) + return 0; + memset(buf, 0, sizeof(buf)); + if (read(fd, buf, sizeof(buf)) > 0) { + val = atoi(buf); + } + close(fd); + return val; +} + +static void xcomp_enable_set(int val) +{ + int fd; + char buf[12+1+1]; + int len; + + fd = open(PATHNAME_XCOMP_ENABLE, O_RDWR); + if (fd < 0) + return; + len = sprintf(buf, "%d\n", val); + write(fd, buf, len); + close(fd); +} + +static partspec partlist[MAX_PART]; +static partspec* curpart; + +int part_add(const char* name) +{ + Volume* vol = NULL; + char* path = NULL; + int i; + + path = (char*)malloc(1+strlen(name)+1); + sprintf(path, "/%s", name); + vol = volume_for_path(path); + if (vol == NULL || vol->fs_type == NULL) { + logmsg("missing vol info for %s, ignoring\n", name); + goto err; + } + + for (i = 0; i < MAX_PART; ++i) { + if (partlist[i].name == NULL) { + partlist[i].name = strdup(name); + partlist[i].path = path; + partlist[i].vol = vol; + logmsg("part_add: i=%d, name=%s, path=%s\n", i, name, path); + return 0; + } + if (strcmp(partlist[i].name, name) == 0) { + logmsg("duplicate partition %s, ignoring\n", name); + goto err; + } + } + +err: + free(path); + return -1; +} + +partspec* part_get(int i) +{ + if (i >= 0 && i < MAX_PART) { + if (partlist[i].name != NULL) { + return &partlist[i]; + } + } + return NULL; +} + +partspec* part_find(const char* name) +{ + for (int i = 0; i < MAX_PART; ++i) { + if (partlist[i].name && !strcmp(name, partlist[i].name)) { + return &partlist[i]; + } + } + return NULL; +} + +void part_set(partspec* part) +{ + curpart = part; + curpart->off = 0; +} + +int update_progress(uint64_t off) +{ + static time_t last_time = 0; + static int last_pct = 0; + if (curpart) { + curpart->off += off; + time_t now = time(NULL); + int pct = min(100, (int)((uint64_t)100*curpart->off/curpart->used)); + if (now != last_time && pct != last_pct) { + char msg[256]; + sprintf(msg, "%s: %d%% complete", curpart->name, pct); + ui_print(msg); + last_time = now; + last_pct = pct; + } + } + return 0; +} + +static int tar_cb_open(const char* path, int mode, ...) +{ + errno = EINVAL; + return -1; +} + +static int tar_cb_close(int fd) +{ + return 0; +} + +static ssize_t tar_cb_read(int fd, void* buf, size_t len) +{ + ssize_t nread; + nread = ::read(fd, buf, len); + if (nread > 0 && hash_name) { + SHA1_Update(&sha_ctx, (u_char*)buf, nread); + MD5_Update(&md5_ctx, buf, nread); + hash_datalen += nread; + } + update_progress(nread); + return nread; +} + +static ssize_t tar_cb_write(int fd, const void* buf, size_t len) +{ + ssize_t written = 0; + + if (hash_name) { + SHA1_Update(&sha_ctx, (u_char*)buf, len); + MD5_Update(&md5_ctx, buf, len); + hash_datalen += len; + } + + while (len > 0) { + ssize_t n = ::write(fd, buf, len); + if (n < 0) { + logmsg("tar_cb_write: error: n=%d\n", n); + return n; + } + if (n == 0) + break; + buf = (const char *)buf + n; + len -= n; + written += n; + } + update_progress(written); + return written; +} + +static tartype_t tar_io = { + tar_cb_open, + tar_cb_close, + tar_cb_read, + tar_cb_write +}; + +static ssize_t tar_gz_cb_read(int fd, void* buf, size_t len) +{ + int nread; + nread = gzread(gzf, buf, len); + if (nread > 0 && hash_name) { + SHA1_Update(&sha_ctx, (u_char*)buf, nread); + MD5_Update(&md5_ctx, buf, nread); + hash_datalen += nread; + } + update_progress(nread); + return nread; +} + +static ssize_t tar_gz_cb_write(int fd, const void* buf, size_t len) +{ + ssize_t written = 0; + + if (hash_name) { + SHA1_Update(&sha_ctx, (u_char*)buf, len); + MD5_Update(&md5_ctx, buf, len); + hash_datalen += len; + } + + while (len > 0) { + ssize_t n = gzwrite(gzf, buf, len); + if (n < 0) { + logmsg("tar_gz_cb_write: error: n=%d\n", n); + return n; + } + if (n == 0) + break; + buf = (const char *)buf + n; + len -= n; + written += n; + } + update_progress(written); + return written; +} + +static tartype_t tar_io_gz = { + tar_cb_open, + tar_cb_close, + tar_gz_cb_read, + tar_gz_cb_write +}; + +int create_tar(int fd, const char* compress, const char* mode) +{ + int rc = -1; + + SHA1_Init(&sha_ctx); + MD5_Init(&md5_ctx); + + if (!compress || strcasecmp(compress, "none") == 0) { + rc = tar_fdopen(&tar, fd, "foobar", &tar_io, + 0, /* oflags: unused */ + 0, /* mode: unused */ + TAR_GNU | TAR_STORE_SELINUX /* options */); + } + else if (strcasecmp(compress, "gzip") == 0) { + gzf = gzdopen(fd, mode); + if (gzf != NULL) { + rc = tar_fdopen(&tar, 0, "foobar", &tar_io_gz, + 0, /* oflags: unused */ + 0, /* mode: unused */ + TAR_GNU | TAR_STORE_SELINUX /* options */); + } + } + return rc; +} + +static void do_exit(int rc) +{ + char rcstr[80]; + int len; + len = sprintf(rcstr, "%d\n", rc); + + unlink(PATHNAME_RC); + int fd = open(PATHNAME_RC, O_RDWR|O_CREAT, 0644); + write(fd, rcstr, len); + close(fd); + exit(rc); +} + +int main(int argc, char **argv) +{ + int n; + int rc = 1; + int xcomp_enable; + + const char* logfile = "/tmp/recovery.log"; + adb_ifd = dup(STDIN_FILENO); + adb_ofd = dup(STDOUT_FILENO); + freopen(logfile, "a", stdout); setbuf(stdout, NULL); + freopen(logfile, "a", stderr); setbuf(stderr, NULL); + + logmsg("bu: invoked with %d args\n", argc); + + if (argc < 2) { + logmsg("Not enough args (%d)\n", argc); + do_exit(1); + } + + // progname args... + int optidx = 1; + const char* opname = argv[optidx++]; + + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + + xcomp_enable = xcomp_enable_get(); + xcomp_enable_set(0); + + load_volume_table(); + vdc = new VoldClient(); + vdc->start(); + + if (!strcmp(opname, "backup")) { + ui_print("Backup in progress..."); + rc = do_backup(argc-optidx, &argv[optidx]); + } + else if (!strcmp(opname, "restore")) { + ui_print("Restore in progress..."); + rc = do_restore(argc-optidx, &argv[optidx]); + } + else { + logmsg("Unknown operation %s\n", opname); + rc = 1; + } + + xcomp_enable_set(xcomp_enable); + + close(adb_ofd); + close(adb_ifd); + + sleep(1); + + logmsg("bu exiting\n"); + + do_exit(rc); + + return rc; +} @@ -0,0 +1,54 @@ +#include <utils/String8.h> + +#include <lib/libtar.h> +#include <zlib.h> + +extern "C" { +#include <openssl/sha.h> +#include <openssl/md5.h> +#ifndef MD5_DIGEST_STRING_LENGTH +#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH*2+1) +#endif +#ifndef SHA_DIGEST_STRING_LENGTH +#define SHA_DIGEST_STRING_LENGTH (SHA_DIGEST_LENGTH*2+1) +#endif +} + +#define HASH_MAX_LENGTH SHA_DIGEST_LENGTH +#define HASH_MAX_STRING_LENGTH SHA_DIGEST_STRING_LENGTH + +#define PROP_LINE_LEN (PROPERTY_KEY_MAX+1+PROPERTY_VALUE_MAX+1+1) + +extern int adb_ifd; +extern int adb_ofd; +extern TAR* tar; +extern gzFile gzf; + +extern char* hash_name; +extern size_t hash_datalen; +extern SHA_CTX sha_ctx; +extern MD5_CTX md5_ctx; + +struct partspec { + char* name; + char* path; + Volume* vol; + uint64_t size; + uint64_t used; + uint64_t off; +}; +#define MAX_PART 8 + +extern void logmsg(const char* fmt, ...); + +extern int part_add(const char* name); +extern partspec* part_get(int i); +extern partspec* part_find(const char* name); +extern void part_set(partspec* part); + +extern int update_progress(uint64_t off); + +extern int create_tar(int fd, const char* compress, const char* mode); + +extern int do_backup(int argc, char** argv); +extern int do_restore(int argc, char** argv); @@ -16,48 +16,141 @@ #include "device.h" -static const char* MENU_ITEMS[] = { - "Reboot system now", - "Reboot to bootloader", - "Apply update from ADB", - "Apply update from SD card", - "Wipe data/factory reset", +enum menu_action_type { + ACTION_NONE, + ACTION_SUBMENU, + ACTION_INVOKE +}; + +struct menu_entry; +struct menu { + const char** names; + const menu_entry* entries; +}; + +union menu_action { + const menu* submenu; + Device::BuiltinAction action; +}; + +struct menu_entry { + menu_action_type action_type; + const menu_action action; +}; + +static const char* WIPE_MENU_NAMES[] = { +#ifndef RELEASE_BUILD + "Wipe data (keep media)", +#endif + "Full factory reset", #ifndef AB_OTA_UPDATER "Wipe cache partition", #endif // !AB_OTA_UPDATER + nullptr +}; +static const menu_entry WIPE_MENU_ENTRIES[] = { +#ifndef RELEASE_BUILD + { ACTION_INVOKE, { .action = Device::WIPE_DATA } }, +#endif + { ACTION_INVOKE, { .action = Device::WIPE_FULL } }, +#ifndef AB_OTA_UPDATER + { ACTION_INVOKE, { .action = Device::WIPE_CACHE } }, +#endif // !AB_OTA_UPDATER + { ACTION_NONE, { .action = Device::NO_ACTION } } +}; +static const menu WIPE_MENU = { + WIPE_MENU_NAMES, + WIPE_MENU_ENTRIES +}; + +static const char* ADVANCED_MENU_NAMES[] = { + "Reboot recovery", +#ifdef DOWNLOAD_MODE + "Reboot to download mode", +#else + "Reboot to bootloader", +#endif +#ifndef RELEASE_BUILD "Mount /system", + "Wipe system partition", +#endif "View recovery logs", "Run graphics test", "Power off", - NULL, + nullptr +}; +static const menu_entry ADVANCED_MENU_ENTRIES[] = { + { ACTION_INVOKE, { .action = Device::REBOOT_RECOVERY } }, +#ifdef DOWNLOAD_MODE + { ACTION_INVOKE, { .action = Device::REBOOT_BOOTLOADER } }, +#else + { ACTION_INVOKE, { .action = Device::REBOOT_BOOTLOADER } }, +#endif +#ifndef RELEASE_BUILD + { ACTION_INVOKE, { .action = Device::MOUNT_SYSTEM } }, + { ACTION_INVOKE, { .action = Device::WIPE_SYSTEM } }, +#endif + { ACTION_INVOKE, { .action = Device::VIEW_RECOVERY_LOGS } }, + { ACTION_INVOKE, { .action = Device::RUN_GRAPHICS_TEST } }, + { ACTION_INVOKE, { .action = Device::SHUTDOWN } }, + { ACTION_NONE, { .action = Device::NO_ACTION } } +}; +static const menu ADVANCED_MENU = { + ADVANCED_MENU_NAMES, + ADVANCED_MENU_ENTRIES }; -static const Device::BuiltinAction MENU_ACTIONS[] = { - Device::REBOOT, - Device::REBOOT_BOOTLOADER, - Device::APPLY_ADB_SIDELOAD, - Device::APPLY_SDCARD, - Device::WIPE_DATA, -#ifndef AB_OTA_UPDATER - Device::WIPE_CACHE, -#endif // !AB_OTA_UPDATER - Device::MOUNT_SYSTEM, - Device::VIEW_RECOVERY_LOGS, - Device::RUN_GRAPHICS_TEST, - Device::SHUTDOWN, +static const char* MAIN_MENU_NAMES[] = { + "Reboot system now", + "Apply update", + "Factory reset", + "Advanced", + nullptr +}; +static const menu_entry MAIN_MENU_ENTRIES[] = { + { ACTION_INVOKE, { .action = Device::REBOOT } }, + { ACTION_INVOKE, { .action = Device::APPLY_UPDATE } }, + { ACTION_SUBMENU, { .submenu = &WIPE_MENU } }, + { ACTION_SUBMENU, { .submenu = &ADVANCED_MENU } }, + { ACTION_NONE, { .action = Device::NO_ACTION } } +}; +static const menu MAIN_MENU = { + MAIN_MENU_NAMES, + MAIN_MENU_ENTRIES }; -static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) == - sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1, - "MENU_ITEMS and MENU_ACTIONS should have the same length, " - "except for the extra NULL entry in MENU_ITEMS."); +Device::Device(RecoveryUI* ui) : + ui_(ui) { + menu_stack.push(&MAIN_MENU); +} const char* const* Device::GetMenuItems() { - return MENU_ITEMS; + const menu* m = menu_stack.top(); + return m->names; } Device::BuiltinAction Device::InvokeMenuItem(int menu_position) { - return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position]; + if (menu_position < 0) { + if (menu_position == Device::kGoBack) { + if (menu_stack.size() > 1) { + menu_stack.pop(); + } + } + return NO_ACTION; + } + const menu* m = menu_stack.top(); + const menu_entry* entry = m->entries + menu_position; + if (entry->action_type == ACTION_SUBMENU) { + menu_stack.push(entry->action.submenu); + return NO_ACTION; + } + return entry->action.action; +} + +void Device::GoHome() { + while (menu_stack.size() > 1) { + menu_stack.pop(); + } } int Device::HandleMenuKey(int key, int visible) { @@ -65,19 +158,37 @@ int Device::HandleMenuKey(int key, int visible) { return kNoAction; } + if (key & KEY_FLAG_ABS) { + return key; + } + switch (key) { + case KEY_RIGHTSHIFT: case KEY_DOWN: case KEY_VOLUMEDOWN: + case KEY_MENU: return kHighlightDown; + case KEY_LEFTSHIFT: case KEY_UP: case KEY_VOLUMEUP: + case KEY_SEARCH: return kHighlightUp; case KEY_ENTER: case KEY_POWER: + case BTN_MOUSE: + case KEY_SEND: return kInvokeItem; + case KEY_BACKSPACE: + case KEY_BACK: + return kGoBack; + + case KEY_HOME: + case KEY_HOMEPAGE: + return kGoHome; + default: // If you have all of the above buttons, any other buttons // are ignored. Otherwise, any button cycles the highlight. @@ -19,9 +19,15 @@ #include "ui.h" -class Device { +#include <stack> + +#define KEY_FLAG_ABS 0x8000 + +struct menu; + +class Device : public VoldWatcher { public: - Device(RecoveryUI* ui) : ui_(ui) { } + explicit Device(RecoveryUI* ui); virtual ~Device() { } // Called to obtain the UI object that should be used to display @@ -57,18 +63,19 @@ class Device { virtual int HandleMenuKey(int key, int visible); enum BuiltinAction { - NO_ACTION = 0, - REBOOT = 1, - APPLY_SDCARD = 2, - // APPLY_CACHE was 3. - APPLY_ADB_SIDELOAD = 4, - WIPE_DATA = 5, - WIPE_CACHE = 6, - REBOOT_BOOTLOADER = 7, - SHUTDOWN = 8, - VIEW_RECOVERY_LOGS = 9, - MOUNT_SYSTEM = 10, - RUN_GRAPHICS_TEST = 11, + NO_ACTION, + REBOOT, + APPLY_UPDATE, + WIPE_DATA, + WIPE_FULL, + WIPE_CACHE, + REBOOT_RECOVERY, + REBOOT_BOOTLOADER, + SHUTDOWN, + VIEW_RECOVERY_LOGS, + MOUNT_SYSTEM, + RUN_GRAPHICS_TEST, + WIPE_SYSTEM, }; // Return the list of menu items (an array of strings, @@ -87,10 +94,15 @@ class Device { // actually perform it here and return NO_ACTION. virtual BuiltinAction InvokeMenuItem(int menu_position); + virtual void GoHome(); + static const int kNoAction = -1; static const int kHighlightUp = -2; static const int kHighlightDown = -3; static const int kInvokeItem = -4; + static const int kGoBack = -5; + static const int kGoHome = -6; + static const int kRefresh = -7; // Called before and after we do a wipe data/factory reset operation, // either via a reboot from the main system with the --wipe_data flag, @@ -103,8 +115,18 @@ class Device { virtual bool PreWipeData() { return true; } virtual bool PostWipeData() { return true; } + virtual bool PreWipeMedia() { return true; } + virtual bool PostWipeMedia() { return true; } + + // Called before reboot + virtual char const* GetRebootReason() { return ""; } + + virtual void onVolumeChanged() { ui_->onVolumeChanged(); } + private: RecoveryUI* ui_; + + std::stack<const menu*> menu_stack; }; // The device-specific library must define this function (or the diff --git a/etc/init.rc b/etc/init.rc index 5915b8d8..a58fa70c 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -19,15 +19,38 @@ on init mkdir /data mkdir /cache mkdir /sideload + mkdir /firmware mount tmpfs tmpfs /tmp chown root shell /tmp chmod 0775 /tmp + mkdir /mnt 0775 root system + mkdir /storage 0050 root sdcard_r + mount tmpfs tmpfs /storage mode=0050,uid=0,gid=1028 + + # See storage config details at http://source.android.com/tech/storage/ + mkdir /mnt/shell 0700 shell shell + + # Directory for putting things only root should see. + mkdir /mnt/secure 0700 root root + + # Create private mountpoint so we can MS_MOVE from staging + mount tmpfs tmpfs /mnt/secure mode=0700,uid=0,gid=0 + + # Directory for staging bindmounts + mkdir /mnt/secure/staging 0700 root root + + # Fuse public mount points. + mkdir /mnt/fuse 0700 root system + mount tmpfs tmpfs /mnt/fuse mode=0775,gid=1000 + write /proc/sys/kernel/panic_on_oops 1 write /proc/sys/vm/max_map_count 1000000 on fs + mount pstore pstore /sys/fs/pstore + mkdir /dev/usb-ffs 0770 shell shell mkdir /dev/usb-ffs/adb 0770 shell shell mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000 @@ -52,6 +75,9 @@ on boot on load_system_props_action load_system_props +on load_persist_props_action + load_persist_props + on firmware_mounts_complete rm /dev/.booting @@ -67,6 +93,9 @@ on late-init # issued fs triggers have completed. trigger load_system_props_action + # Vendor init lives here + trigger load_persist_props_action + # Remove a file to wake up anything waiting for firmware trigger firmware_mounts_complete @@ -87,18 +116,40 @@ service healthd /sbin/healthd -r service recovery /sbin/recovery seclabel u:r:recovery:s0 +service setup_adbd /sbin/setup_adbd + oneshot + seclabel u:r:recovery:s0 + disabled + service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery disabled socket adbd stream 660 system system seclabel u:r:adbd:s0 -# Always start adbd on userdebug and eng builds +service vold /sbin/minivold \ + --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \ + --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0 + socket vold stream 0660 root mount + socket cryptd stream 0660 root mount + ioprio be 2 + setenv BLKID_FILE /tmp/vold_blkid.tab + seclabel u:r:vold:s0 + +# setup_adbd will start adb once it has checked the keys on property:ro.debuggable=1 - write /sys/class/android_usb/android0/enable 1 - start adbd + start setup_adbd -# Restart adbd so it can run as root on property:service.adb.root=1 write /sys/class/android_usb/android0/enable 0 restart adbd write /sys/class/android_usb/android0/enable 1 + +on property:sys.storage.ums_enabled=1 + write /sys/class/android_usb/android0/enable 0 + write /sys/class/android_usb/android0/functions adb,mass_storage + write /sys/class/android_usb/android0/enable 1 + +on property:sys.storage.ums_enabled=0 + write /sys/class/android_usb/android0/enable 0 + write /sys/class/android_usb/android0/functions adb + write /sys/class/android_usb/android0/enable ${service.adb.root} diff --git a/etc/mkshrc b/etc/mkshrc new file mode 100644 index 00000000..b2923c6f --- /dev/null +++ b/etc/mkshrc @@ -0,0 +1,70 @@ +# Copyright (c) 2010, 2012, 2013, 2014 +# Thorsten Glaser <tg@mirbsd.org> +# This file is provided under the same terms as mksh. +#- +# Minimal /system/etc/mkshrc for Android +# +# Support: https://launchpad.net/mksh + +: ${HOME:=/} +: ${HOSTNAME:=$(getprop ro.product.device)} +: ${HOSTNAME:=android} +: ${MKSH:=/sbin/sh} +: ${SHELL:=$MKSH} +: ${TERM:=linux} +: ${TMPDIR:=/tmp} +: ${USER:=$(id -un)} +export HOME HOSTNAME MKSH SHELL TERM TMPDIR USER + +if (( USER_ID )); then PS1='$'; else PS1='#'; fi +PS4='[$EPOCHREALTIME] '; PS1='${| + local e=$? + + (( e )) && REPLY+="$e|" + + return $e +}$USER@$HOSTNAME:${PWD:-?} '"$PS1 " + +function hd { + local -Uui16 -Z11 pos=0 + local -Uui16 -Z5 hv=2147483647 + local dasc line i + + cat "$@" | { set +U; if read -arN -1 line; then + typeset -i1 'line[*]' + i=0 + while (( i < ${#line[*]} )); do + hv=${line[i++]} + if (( (pos & 15) == 0 )); then + (( pos )) && print -r -- "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + print -n "${hv#16#} " + if (( (hv < 32) || (hv > 126) )); then + dasc+=. + else + dasc+=${line[i-1]#1#} + fi + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + (( hv == 2147483647 )) || print -r -- "$dasc|" + fi; } +} + +function setenv { + eval export "\"$1\""'="$2"' +} + +for p in ~/bin; do + [[ -d $p/. ]] || continue + [[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH +done + +unset p + +: place customisations above this line diff --git a/fstools/Android.mk b/fstools/Android.mk new file mode 100644 index 00000000..680e5725 --- /dev/null +++ b/fstools/Android.mk @@ -0,0 +1,70 @@ +LOCAL_PATH := $(call my-dir) + +# This is a multi-call static binary which contains the +# GPL filesystem tools. + +include $(CLEAR_VARS) +LOCAL_MODULE := fstools +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := fstools.cpp +LOCAL_FORCE_STATIC_EXECUTABLE := true + +LOCAL_WHOLE_STATIC_LIBRARIES += \ + libfuse_static + +LOCAL_WHOLE_STATIC_LIBRARIES += \ + libntfs-3g_static \ + libntfs3g_fsck_static \ + libntfs3g_mkfs_main \ + libntfs3g_mount_static + +LOCAL_WHOLE_STATIC_LIBRARIES += \ + libext2fs \ + libe2fsck_static \ + libmke2fs_static \ + libtune2fs + +LOCAL_WHOLE_STATIC_LIBRARIES += \ + libf2fs_static \ + libf2fs_fsck_static \ + libf2fs_mkfs_static + +LOCAL_WHOLE_STATIC_LIBRARIES += \ + libsgdisk_static + +LOCAL_STATIC_LIBRARIES := \ + libext2_blkid \ + libext2_uuid \ + libext2_profile \ + libext2_quota \ + libext2_com_err \ + libext2_e2p \ + libc++_static \ + libc \ + libm \ + libselinux + +FSTOOLS_LINKS := \ + e2fsck mke2fs tune2fs fsck.ext4 mkfs.ext4 \ + fsck.ntfs mkfs.ntfs mount.ntfs \ + mkfs.f2fs fsck.f2fs + +ifeq ($(TARGET_USES_EXFAT),true) +LOCAL_CFLAGS += -DHAVE_EXFAT +LOCAL_WHOLE_STATIC_LIBRARIES += \ + libexfat_static \ + libexfat_fsck_static \ + libexfat_mkfs_static \ + libexfat_mount_static +FSTOOLS_LINKS += \ + fsck.exfat mkfs.exfat mount.exfat +endif + +FSTOOLS_LINKS += \ + sgdisk + +LOCAL_POST_INSTALL_CMD := \ + $(hide) $(foreach t,$(FSTOOLS_LINKS),ln -sf fstools $(TARGET_RECOVERY_ROOT_OUT)/sbin/$(t);) +include $(BUILD_EXECUTABLE) + diff --git a/fstools/fstools.cpp b/fstools/fstools.cpp new file mode 100644 index 00000000..b1cc7adf --- /dev/null +++ b/fstools/fstools.cpp @@ -0,0 +1,23 @@ +#include <stdlib.h> + +extern "C" { +#include "fstools.h" +} + + +int +main(int argc, char **argv) { + + // Handle alternative invocations + char* command = argv[0]; + char* stripped = strrchr(argv[0], '/'); + if (stripped) + command = stripped + 1; + + if (strcmp(command, "fstools") != 0) { + struct fstools_cmd cmd = get_command(command); + if (cmd.name) + return cmd.main_func(argc, argv); + } + return -1; +} diff --git a/fstools/fstools.h b/fstools/fstools.h new file mode 100644 index 00000000..d99b3825 --- /dev/null +++ b/fstools/fstools.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 _FSTOOLS_CMDS_H +#define _FSTOOLS_CMDS_H + +#include <stdio.h> +#include <string.h> + +int e2fsck_main(int argc, char **argv); +int mke2fs_main(int argc, char **argv); +int tune2fs_main(int argc, char **argv); + +#ifdef HAVE_EXFAT +int fsck_exfat_main(int argc, char **argv); +int mkfs_exfat_main(int argc, char **argv); +int mount_exfat_main(int argc, char **argv); +#endif + +int fsck_ntfs3g_main(int argc, char **argv); +int mkfs_ntfs3g_main(int argc, char **argv); +int mount_ntfs3g_main(int argc, char **argv); + +int mkfs_f2fs_main(int argc, char **argv); +int fsck_f2fs_main(int argc, char **argv); +int fibmap_main(int argc, char **argv); + +int sgdisk_main(int argc, char **argv); + +struct fstools_cmd { + const char *name; + int (*main_func)(int argc, char **argv); +}; + +static const struct fstools_cmd fstools_cmds[] = { + { "e2fsck", e2fsck_main }, + { "mke2fs", mke2fs_main }, + { "tune2fs", tune2fs_main }, + { "fsck.ext4", e2fsck_main }, + { "mkfs.ext4", mke2fs_main }, +#ifdef HAVE_EXFAT + { "fsck.exfat", fsck_exfat_main }, + { "mkfs.exfat", mkfs_exfat_main }, + { "mount.exfat", mount_exfat_main }, +#endif + { "fsck.ntfs", fsck_ntfs3g_main }, + { "mkfs.ntfs", mkfs_ntfs3g_main }, + { "mount.ntfs", mount_ntfs3g_main }, + { "mkfs.f2fs", mkfs_f2fs_main }, + { "fsck.f2fs", fsck_f2fs_main }, + { "sgdisk", sgdisk_main }, + { NULL, NULL }, +}; + +struct fstools_cmd get_command(char* command) { + int i; + + for (i = 0; fstools_cmds[i].name; i++) { + if (strcmp(command, fstools_cmds[i].name) == 0) + break; + } + + return fstools_cmds[i]; +} +#endif diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp index df963127..27e442c4 100644 --- a/fuse_sdcard_provider.cpp +++ b/fuse_sdcard_provider.cpp @@ -20,6 +20,7 @@ #include <errno.h> #include <sys/mount.h> #include <sys/stat.h> +#include <sys/wait.h> #include <unistd.h> #include <fcntl.h> @@ -79,10 +80,5 @@ bool start_sdcard_fuse(const char* path) { vtab.read_block = read_block_file; vtab.close = close_file; - // The installation process expects to find the sdcard unmounted. - // Unmount it with MNT_DETACH so that our open file continues to - // work but new references see it as unmounted. - umount2("/sdcard", MNT_DETACH); - return run_fuse_sideload(&vtab, &fd, fd.file_size, fd.block_size) == 0; } diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp index 1725e882..31b0b968 100644 --- a/fuse_sideload.cpp +++ b/fuse_sideload.cpp @@ -66,10 +66,10 @@ #include "fuse_sideload.h" #define PACKAGE_FILE_ID (FUSE_ROOT_ID+1) -#define EXIT_FLAG_ID (FUSE_ROOT_ID+2) #define NO_STATUS 1 -#define NO_STATUS_EXIT 2 + +#define INSTALL_REQUIRED_MEMORY (100*1024*1024) struct fuse_data { int ffd; // file descriptor for the fuse socket @@ -85,7 +85,7 @@ struct fuse_data { uid_t uid; gid_t gid; - uint32_t curr_block; // cache the block most recently read from the host + uint32_t curr_block; // cache the block most recently used uint8_t* block_data; uint8_t* extra_block; // another block of storage for reads that @@ -93,8 +93,80 @@ struct fuse_data { uint8_t* hashes; // SHA-256 hash of each block (all zeros // if block hasn't been read yet) + + // Block cache + uint32_t block_cache_max_size; // Max allowed block cache size + uint32_t block_cache_size; // Current block cache size + uint8_t** block_cache; // Block cache data }; +static uint64_t free_memory() { + uint64_t mem = 0; + FILE* fp = fopen("/proc/meminfo", "r"); + if (fp) { + char buf[256]; + char* linebuf = buf; + size_t buflen = sizeof(buf); + while (getline(&linebuf, &buflen, fp) > 0) { + char* key = buf; + char* val = strchr(buf, ':'); + *val = '\0'; + ++val; + if (strcmp(key, "MemFree") == 0) { + mem += strtoul(val, NULL, 0) * 1024; + } + if (strcmp(key, "Buffers") == 0) { + mem += strtoul(val, NULL, 0) * 1024; + } + if (strcmp(key, "Cached") == 0) { + mem += strtoul(val, NULL, 0) * 1024; + } + } + fclose(fp); + } + return mem; +} + +static int block_cache_fetch(struct fuse_data* fd, uint32_t block) +{ + if (fd->block_cache == NULL) { + return -1; + } + if (fd->block_cache[block] == NULL) { + return -1; + } + memcpy(fd->block_data, fd->block_cache[block], fd->block_size); + return 0; +} + +static void block_cache_enter(struct fuse_data* fd, uint32_t block) +{ + if (!fd->block_cache) + return; + if (fd->block_cache_size == fd->block_cache_max_size) { + // Evict a block from the cache. Since the file is typically read + // sequentially, start looking from the block behind the current + // block and proceed backward. + int n; + for (n = fd->curr_block - 1; n != (int)fd->curr_block; --n) { + if (n < 0) { + n = fd->file_blocks - 1; + } + if (fd->block_cache[n]) { + free(fd->block_cache[n]); + fd->block_cache[n] = NULL; + fd->block_cache_size--; + break; + } + } + } + + fd->block_cache[block] = (uint8_t*)malloc(fd->block_size); + memcpy(fd->block_cache[block], fd->block_data, fd->block_size); + + fd->block_cache_size++; +} + static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t len) { struct fuse_out_header hdr; @@ -180,14 +252,12 @@ static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct f fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555); } else if (hdr->nodeid == PACKAGE_FILE_ID) { fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); - } else if (hdr->nodeid == EXIT_FLAG_ID) { - fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); } else { return -ENOENT; } fuse_reply(fd, hdr->unique, &out, sizeof(out)); - return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; + return NO_STATUS; } static int handle_lookup(void* data, struct fuse_data* fd, @@ -202,21 +272,15 @@ static int handle_lookup(void* data, struct fuse_data* fd, out.nodeid = PACKAGE_FILE_ID; out.generation = PACKAGE_FILE_ID; fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); - } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, reinterpret_cast<const char*>(data), - sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) { - out.nodeid = EXIT_FLAG_ID; - out.generation = EXIT_FLAG_ID; - fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); } else { return -ENOENT; } fuse_reply(fd, hdr->unique, &out, sizeof(out)); - return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; + return NO_STATUS; } static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { - if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; struct fuse_open_out out; @@ -247,6 +311,11 @@ static int fetch_block(struct fuse_data* fd, uint32_t block) { return 0; } + if (block_cache_fetch(fd, block) == 0) { + fd->curr_block = block; + return 0; + } + size_t fetch_size = fd->block_size; if (block * fd->block_size + fetch_size > fd->file_size) { // If we're reading the last (partial) block of the file, @@ -286,6 +355,7 @@ static int fetch_block(struct fuse_data* fd, uint32_t block) { } memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); + block_cache_enter(fd, block); return 0; } @@ -362,6 +432,12 @@ static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_he return NO_STATUS; } +static volatile int terminated = 0; +static void sig_term(int sig) +{ + terminated = 1; +} + int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size, uint32_t block_size) { @@ -388,6 +464,9 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, fd.block_size = block_size; fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1); + uint64_t mem = free_memory(); + uint64_t avail = mem - (INSTALL_REQUIRED_MEMORY + fd.file_blocks * sizeof(uint8_t*)); + if (fd.file_blocks > (1<<18)) { fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); result = -1; @@ -419,6 +498,24 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, goto done; } + fd.block_cache_max_size = 0; + fd.block_cache_size = 0; + fd.block_cache = NULL; + if (mem > avail) { + uint32_t max_size = avail / fd.block_size; + if (max_size > fd.file_blocks) { + max_size = fd.file_blocks; + } + // The cache must be at least 1% of the file size or two blocks, + // whichever is larger. + if (max_size >= fd.file_blocks/100 && max_size >= 2) { + fd.block_cache_max_size = max_size; + fd.block_cache = (uint8_t**)calloc(fd.file_blocks, sizeof(uint8_t*)); + } + } + + signal(SIGTERM, sig_term); + fd.ffd = open("/dev/fuse", O_RDWR); if (fd.ffd < 0) { perror("open /dev/fuse"); @@ -439,7 +536,17 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, goto done; } uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8]; - for (;;) { + while (!terminated) { + fd_set fds; + struct timeval tv; + FD_ZERO(&fds); + FD_SET(fd.ffd, &fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + int rc = select(fd.ffd+1, &fds, NULL, NULL, &tv); + if (rc <= 0) { + continue; + } ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); if (len == -1) { perror("read request"); @@ -494,11 +601,6 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, break; } - if (result == NO_STATUS_EXIT) { - result = 0; - break; - } - if (result != NO_STATUS) { struct fuse_out_header outhdr; outhdr.len = sizeof(outhdr); @@ -517,6 +619,13 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, } if (fd.ffd) close(fd.ffd); + if (fd.block_cache) { + uint32_t n; + for (n = 0; n < fd.file_blocks; ++n) { + free(fd.block_cache[n]); + } + free(fd.block_cache); + } free(fd.hashes); free(fd.block_data); free(fd.extra_block); diff --git a/install.cpp b/install.cpp index d30890a9..a24ef6f1 100644 --- a/install.cpp +++ b/install.cpp @@ -23,6 +23,8 @@ #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> +#include <setjmp.h> +#include <sys/mount.h> #include <chrono> #include <limits> @@ -48,6 +50,8 @@ #include "ui.h" #include "verifier.h" +#include "cutils/properties.h" + extern RecoveryUI* ui; #define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" @@ -297,6 +301,12 @@ update_binary_command(const char* path, ZipArchive* zip, int retry_count, } #endif // !AB_OTA_UPDATER +static jmp_buf jb; +static void sig_bus(int sig) +{ + longjmp(jb, 1); +} + // If the package contains an update binary, extract it and run it. static int try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache, @@ -438,22 +448,112 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache, return INSTALL_RETRY; } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); + if (WEXITSTATUS(status) != 7) { + LOGE("Installation error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); + } else { + LOGE("Failed to install %s\n", path); + LOGE("Please take note of all the above lines for reports\n"); + } return INSTALL_ERROR; } return INSTALL_SUCCESS; } +#ifdef USE_MDTP +static int +mdtp_update() +{ + const char** args = (const char**)malloc(sizeof(char*) * 2); + + if (args == NULL) { + LOGE("Failed to allocate memory for MDTP FOTA app arguments\n"); + return 0; + } + + args[0] = "/sbin/mdtp_fota"; + args[1] = NULL; + int status = 0; + + ui->Print("Running MDTP integrity verification and update...\n"); + + /* Make sure system partition is mounted, so MDTP can process its content. */ + status = mount("/dev/block/bootdevice/by-name/system", "/system", "ext4", + MS_NOATIME | MS_NODEV | MS_NODIRATIME | + MS_RDONLY, ""); + + if (status) { + LOGE("Failed to mount the system partition, error=%s.\n", strerror(errno)); + free(args); + return 0; + } + + status = mount("/dev/block/bootdevice/by-name/modem", "/firmware", "vfat", + MS_NOATIME | MS_NODEV | MS_NODIRATIME | + MS_RDONLY, ""); + + if (status) { + LOGE("Failed to mount the modem (firmware) partition, error=%s.\n", strerror(errno)); + free(args); + return 0; + } + + status = 0; + + pid_t pid = fork(); + if (pid == 0) { + execv(args[0], (char* const*)args); + LOGE("Can't run %s (%s)\n", args[0], strerror(errno)); + _exit(-1); + } + if (pid > 0) { + LOGE("Waiting for MDTP FOTA to complete...\n"); + pid = waitpid(pid, &status, 0); + LOGE("MDTP FOTA completed, status: %d\n", status); + } + + /* Leave the system partition unmounted before we finish. */ + umount("/system"); + umount("/firmware"); + + free(args); + + return (status > 0) ? 1 : 0; +} +#endif /* USE_MDTP */ + static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount, std::vector<std::string>& log_buffer, int retry_count) { + int ret = 0; + 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); + + // Resolve symlink in case legacy /sdcard path is used + // Requires: symlink uses absolute path + char new_path[PATH_MAX]; + if (strlen(path) > 1) { + char *rest = strchr(path + 1, '/'); + if (rest != NULL) { + int readlink_length; + int root_length = rest - path; + char *root = (char *)malloc(root_length + 1); + strncpy(root, path, root_length); + root[root_length] = 0; + readlink_length = readlink(root, new_path, PATH_MAX); + if (readlink_length > 0) { + strncpy(new_path + readlink_length, rest, PATH_MAX - readlink_length); + path = new_path; + } + free(root); + } + } + LOGI("Update location: %s\n", path); // Map the update package into memory. @@ -464,6 +564,7 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount, ensure_path_mounted(path+1); } else { ensure_path_mounted(path); + remount_no_selinux(path); } } @@ -473,10 +574,13 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount, return INSTALL_CORRUPT; } + set_perf_mode(true); + // Verify package. if (!verify_package(map.addr, map.length)) { log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure)); sysReleaseMap(&map); + set_perf_mode(false); return INSTALL_CORRUPT; } @@ -488,6 +592,7 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount, log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure)); sysReleaseMap(&map); + set_perf_mode(false); return INSTALL_CORRUPT; } @@ -497,13 +602,25 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount, ui->Print("Retry attempt: %d\n", retry_count); } ui->SetEnableReboot(false); - int result = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count); + ret = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count); ui->SetEnableReboot(true); ui->Print("\n"); sysReleaseMap(&map); - return result; +#ifdef USE_MDTP + /* If MDTP update failed, return an error such that recovery will not finish. */ + if (ret == INSTALL_SUCCESS) { + if (!mdtp_update()) { + ui->Print("Unable to verify integrity of /system for MDTP, update aborted.\n"); + return INSTALL_ERROR; + } + ui->Print("Successfully verified integrity of /system for MDTP.\n"); + } +#endif /* USE_MDTP */ + + set_perf_mode(false); + return ret; } int @@ -513,9 +630,11 @@ install_package(const char* path, bool* wipe_cache, const char* install_file, modified_flash = true; auto start = std::chrono::system_clock::now(); - int result; + int result = 0; std::vector<std::string> log_buffer; - if (setup_install_mounts() != 0) { + if (needs_mount == true) + result = setup_install_mounts(); + if (result != 0) { LOGE("failed to set up expected mounts for install; aborting\n"); result = INSTALL_ERROR; } else { @@ -569,9 +688,20 @@ bool verify_package(const unsigned char* package_data, size_t package_size) { // Verify package. ui->Print("Verifying update package...\n"); auto t0 = std::chrono::system_clock::now(); - int err = verify_file(const_cast<unsigned char*>(package_data), package_size, loadedKeys); - std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0; - ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err); + int err; + + // Because we mmap() the update file which is backed by FUSE, we get + // SIGBUS when the host aborts the transfer. We handle this by using + // setjmp/longjmp. + signal(SIGBUS, sig_bus); + if (setjmp(jb) == 0) { + err = verify_file(const_cast<unsigned char*>(package_data), package_size, loadedKeys); + std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0; + ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err); + } else { + err = VERIFY_FAILURE; + } + signal(SIGBUS, SIG_DFL); if (err != VERIFY_SUCCESS) { LOGE("Signature verification failed\n"); LOGE("error: %d\n", kZipVerificationFailure); @@ -579,3 +709,8 @@ bool verify_package(const unsigned char* package_data, size_t package_size) { } return true; } + +void +set_perf_mode(bool enable) { + property_set("recovery.perf.mode", enable ? "1" : "0"); +} @@ -38,4 +38,6 @@ bool verify_package(const unsigned char* package_data, size_t package_size); // Return true if succeed, otherwise return false. bool read_metadata_from_package(ZipArchive* zip, std::string* meta_data); +void set_perf_mode(bool enable); + #endif // RECOVERY_INSTALL_H_ diff --git a/minui/Android.mk b/minui/Android.mk index 3057f452..a8d0aa41 100644 --- a/minui/Android.mk +++ b/minui/Android.mk @@ -27,6 +27,9 @@ endif ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888) LOCAL_CFLAGS += -DRECOVERY_RGBX endif +ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBA_8888) + LOCAL_CFLAGS += -DRECOVERY_RGBX +endif ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888) LOCAL_CFLAGS += -DRECOVERY_BGRA endif @@ -37,6 +40,14 @@ else LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0 endif +ifneq ($(BOARD_RECOVERY_NEEDS_FBIOPAN_DISPLAY),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_NEEDS_FBIOPAN_DISPLAY +endif + +ifneq ($(BOARD_RECOVERY_NEEDS_REL_INPUT),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_NEEDS_REL_INPUT +endif + include $(BUILD_STATIC_LIBRARY) # Used by OEMs for factory test images. diff --git a/minui/events.cpp b/minui/events.cpp index 3b2262a4..85afe471 100644 --- a/minui/events.cpp +++ b/minui/events.cpp @@ -78,8 +78,13 @@ int ev_init(ev_callback input_cb, void* data) { continue; } - // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. - if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { + // We assume that only EV_KEY, EV_SW, and EV_ABS event types are ever needed. + // EV_REL should be enabled explicitly in device tree. +#ifdef BOARD_RECOVERY_NEEDS_REL_INPUT + if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits) && !test_bit(EV_ABS, ev_bits)) { +#else + if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_SW, ev_bits) && !test_bit(EV_ABS, ev_bits)) { +#endif close(fd); continue; } diff --git a/minui/graphics.cpp b/minui/graphics.cpp index c0eea9e3..1f255311 100644 --- a/minui/graphics.cpp +++ b/minui/graphics.cpp @@ -41,6 +41,20 @@ struct GRFont { int cheight; }; +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#endif + +typedef struct { + char name[80]; + GRFont* font; +} font_item; + +static font_item gr_fonts[] = { + { "menu", NULL }, + { "log", NULL }, +}; + static GRFont* gr_font = NULL; static minui_backend* gr_backend = NULL; @@ -103,6 +117,36 @@ static void text_blend(unsigned char* src_p, int src_row_bytes, } } +static int rainbow_index = 0; +static int rainbow_enabled = 0; +static int rainbow_colors[] = { 255, 0, 0, // red + 255, 127, 0, // orange + 255, 255, 0, // yellow + 0, 255, 0, // green + 60, 80, 255, // blue + 143, 0, 255 }; // violet +static int num_rb_colors = + (sizeof(rainbow_colors)/sizeof(rainbow_colors[0])) / 3; + +static void rainbow(int col) { + int rainbow_color = ((rainbow_index + col) % num_rb_colors) * 3; + gr_color(rainbow_colors[rainbow_color], rainbow_colors[rainbow_color+1], + rainbow_colors[rainbow_color+2], 255); +} + +void set_rainbow_mode(int enabled) { + rainbow_enabled = enabled; +} + +void move_rainbow(int x) { + rainbow_index += x; + if (rainbow_index < 0) { + rainbow_index = num_rb_colors - 1; + } else if (rainbow_index >= num_rb_colors) { + rainbow_index = 0; + } +} + void gr_text(int x, int y, const char *s, bool bold) { GRFont* font = gr_font; @@ -116,6 +160,8 @@ void gr_text(int x, int y, const char *s, bool bold) unsigned char ch; while ((ch = *s++)) { + if (rainbow_enabled) rainbow(x / font->cwidth); + if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break; if (ch < ' ' || ch > '~') { @@ -229,6 +275,17 @@ void gr_fill(int x1, int y1, int x2, int y2) } } +void gr_set_font(const char* name) +{ + unsigned int idx; + for (idx = 0; idx < ARRAY_SIZE(gr_fonts); ++idx) { + if (strcmp(name, gr_fonts[idx].name) == 0) { + gr_font = gr_fonts[idx].font; + break; + } + } +} + void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { if (source == NULL) return; @@ -267,11 +324,14 @@ unsigned int gr_get_height(GRSurface* surface) { return surface->height; } -static void gr_init_font(void) +static void gr_init_one_font(int idx) { - gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1)); + char name[80]; + GRFont* gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1)); + snprintf(name, sizeof(name), "font_%s", gr_fonts[idx].name); + gr_fonts[idx].font = gr_font; - int res = res_create_alpha_surface("font", &(gr_font->texture)); + int res = res_create_alpha_surface(name, &(gr_font->texture)); if (res == 0) { // The font image should be a 96x2 array of character images. The // columns are the printable ASCII characters 0x20 - 0x7f. The @@ -303,6 +363,15 @@ static void gr_init_font(void) } } +static void gr_init_font() +{ + unsigned int idx; + for (idx = 0; idx < ARRAY_SIZE(gr_fonts); ++idx) { + gr_init_one_font(idx); + } + gr_font = gr_fonts[0].font; +} + #if 0 // Exercises many of the gr_*() functions; useful for testing. static void gr_test() { diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp index 0788f755..b017ff21 100644 --- a/minui/graphics_fbdev.cpp +++ b/minui/graphics_fbdev.cpp @@ -73,9 +73,15 @@ static void set_displayed_framebuffer(unsigned n) vi.yres_virtual = gr_framebuffer[0].height * 2; vi.yoffset = n * gr_framebuffer[0].height; vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8; + if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { perror("active fb swap failed"); } +#ifdef BOARD_RECOVERY_NEEDS_FBIOPAN_DISPLAY + if (ioctl(fb_fd, FBIOPAN_DISPLAY, &vi) < 0) { + perror("pan failed"); + } +#endif displayed_buffer = n; } diff --git a/minui/minui.h b/minui/minui.h index fb0bbe10..2d5e8483 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -45,6 +45,7 @@ void gr_fb_blank(bool blank); void gr_clear(); // clear entire surface to current color void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); void gr_fill(int x1, int y1, int x2, int y2); +void gr_set_font(const char* name); void gr_text(int x, int y, const char *s, bool bold); void gr_texticon(int x, int y, GRSurface* icon); int gr_measure(const char *s); @@ -122,4 +123,7 @@ int res_create_localized_alpha_surface(const char* name, const char* locale, // functions. void res_free_surface(GRSurface* surface); +void set_rainbow_mode(int enabled); +void move_rainbow(int x); + #endif diff --git a/minui/resources.cpp b/minui/resources.cpp index 40d3c2c8..1aa00b01 100644 --- a/minui/resources.cpp +++ b/minui/resources.cpp @@ -268,7 +268,7 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps, printf(" found fps = %d\n", *fps); } - if (frames <= 0 || fps <= 0) { + if (*frames <= 0 || *fps <= 0) { printf("bad number of frames (%d) and/or FPS (%d)\n", *frames, *fps); result = -10; goto exit; diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c index 6a9b03d3..41efa376 100644 --- a/mtdutils/mounts.c +++ b/mtdutils/mounts.c @@ -153,6 +153,21 @@ unmount_mounted_volume(const MountedVolume *volume) } int +unmount_mounted_volume_detach(const MountedVolume *volume) +{ + /* Intentionally pass NULL to umount if the caller tries + * to unmount a volume they already unmounted using this + * function. + */ + int ret = umount2(volume->mount_point, MNT_DETACH); + if (ret == 0) { + free_volume_internals(volume, 1); + return 0; + } + return ret; +} + +int remount_read_only(const MountedVolume* volume) { return mount(volume->device, volume->mount_point, volume->filesystem, diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h index d721355b..c8318c0f 100644 --- a/mtdutils/mounts.h +++ b/mtdutils/mounts.h @@ -31,6 +31,7 @@ const MountedVolume * find_mounted_volume_by_mount_point(const char *mount_point); int unmount_mounted_volume(const MountedVolume *volume); +int unmount_mounted_volume_detach(const MountedVolume *volume); int remount_read_only(const MountedVolume* volume); diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c index cd4f52cd..ed74d0f4 100644 --- a/mtdutils/mtdutils.c +++ b/mtdutils/mtdutils.c @@ -28,6 +28,9 @@ #include "mtdutils.h" +static const char mtdprefix[] = "/dev/block/mtd/by-name/"; +#define MTD_BASENAME_OFFSET (sizeof(mtdprefix)-1+1) + struct MtdPartition { int device_index; unsigned int size; @@ -144,7 +147,7 @@ mtd_scan_partitions() p->device_index = mtdnum; p->size = mtdsize; p->erase_size = mtderasesize; - p->name = strdup(mtdname); + asprintf(&p->name, "%s%s", mtdprefix, mtdname); if (p->name == NULL) { errno = ENOMEM; goto bail; @@ -183,6 +186,9 @@ mtd_find_partition_by_name(const char *name) if (strcmp(p->name, name) == 0) { return p; } + if (strcmp(p->name+MTD_BASENAME_OFFSET, name) == 0) { + return p; + } } } } diff --git a/recovery.cpp b/recovery.cpp index 0f0b978e..4bad568b 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -31,6 +31,8 @@ #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> +#include <sys/mount.h> +#include <fs_mgr.h> #include <time.h> #include <unistd.h> @@ -67,14 +69,56 @@ #include "unique_fd.h" #include "screen_ui.h" +#include "voldclient.h" + +extern "C" { +#include "recovery_cmds.h" +} + +#define UFS_DEV_SDCARD_BLK_PATH "/dev/block/mmcblk0p1" + struct selabel_handle *sehandle; +#ifdef HAVE_OEMLOCK + +/* + * liboemlock must supply the following C symbols: + * + * - int oemlock_get() + * + * Returns the current state of the OEM lock, if available. + * -1: Not available and/or error + * 0: Unlocked + * 1: Locked + * + * - int oemlock_set(int lock) + * + * Sets the state of the OEM lock. The "lock" parameter will be set + * to 0 for unlock and 1 for lock. + * + * Returns 0 on success, -1 on error + */ +extern "C" { +int oemlock_get(); +int oemlock_set(int lock); +} + +enum OemLockOp { + OEM_LOCK_NONE, + OEM_LOCK_UNLOCK +}; + +static OemLockOp oem_lock = OEM_LOCK_NONE; + +#endif + static const struct option OPTIONS[] = { { "send_intent", required_argument, NULL, 'i' }, { "update_package", required_argument, NULL, 'u' }, { "retry_count", required_argument, NULL, 'n' }, { "wipe_data", no_argument, NULL, 'w' }, { "wipe_cache", no_argument, NULL, 'c' }, + { "wipe_media", no_argument, NULL, 'm' }, { "show_text", no_argument, NULL, 't' }, { "sideload", no_argument, NULL, 's' }, { "sideload_auto_reboot", no_argument, NULL, 'a' }, @@ -105,7 +149,6 @@ static const char *CONVERT_FBE_DIR = "/tmp/convert_fbe"; static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe"; static const char *CACHE_ROOT = "/cache"; static const char *DATA_ROOT = "/data"; -static const char *SDCARD_ROOT = "/sdcard"; static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; @@ -128,6 +171,10 @@ char* reason = NULL; bool modified_flash = false; static bool has_cache = false; +extern "C" int toybox_driver(int argc, char **argv); + +#include "mtdutils/mounts.h" + /* * The recovery tool communicates with the main system through /cache files. * /cache/recovery/command - INPUT - command line for tool, one arg per line @@ -190,6 +237,48 @@ static bool has_cache = false; static const int MAX_ARG_LENGTH = 4096; static const int MAX_ARGS = 100; +#ifdef USE_MDTP + +#define MAX_CMD_LINE_LEN (2048) + +static int is_mdtp_activated() +{ + char cmdline[MAX_CMD_LINE_LEN]; + char *ptr; + int fd; + int mdtp_activated = 0; + + fd = open("/proc/cmdline", O_RDONLY); + if (fd >= 0) { + int n = read(fd, cmdline, sizeof(cmdline) - 1); + if (n < 0) + n = 0; + /* get rid of trailing newline, it happens */ + if (n > 0 && cmdline[n-1] == '\n') n--; + cmdline[n] = 0; + close(fd); + } else { + cmdline[0] = 0; + } + + /* Look for the string "mdtp" in kernel cmdline, indicating that MDTP is activated.*/ + ptr = cmdline; + while (ptr && *ptr) { + char *x = strchr(ptr, ' '); + if (x != 0) + *x++ = 0; + if (!strcmp(ptr,"mdtp")) { + mdtp_activated = 1; + break; + } + + ptr = x; + } + + return mdtp_activated; +} +#endif /* USE_MDTP */ + // open a given path, mounting partitions as necessary FILE* fopen_path(const char *path, const char *mode) { if (ensure_path_mounted(path) != 0) { @@ -335,6 +424,14 @@ get_args(int *argc, char ***argv) { (*argv)[0] = strdup(arg); for (*argc = 1; *argc < MAX_ARGS; ++*argc) { if ((arg = strtok(NULL, "\n")) == NULL) break; + // Arguments that may only be passed in BCB +#ifdef HAVE_OEMLOCK + if (strcmp(arg, "--oemunlock") == 0) { + oem_lock = OEM_LOCK_UNLOCK; + --*argc; + continue; + } +#endif (*argv)[*argc] = strdup(arg); } LOGI("Got arguments from boot message\n"); @@ -575,16 +672,16 @@ typedef struct _saved_log_file { struct _saved_log_file* next; } saved_log_file; -static bool erase_volume(const char* volume) { +static bool erase_volume(const char* volume, bool force = false) { bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); bool is_data = (strcmp(volume, DATA_ROOT) == 0); + saved_log_file* head = NULL; + ui->SetBackground(RecoveryUI::ERASING); ui->SetProgressType(RecoveryUI::INDETERMINATE); - saved_log_file* head = NULL; - - if (is_cache) { + if (!force && is_cache) { // If we're reformatting /cache, we load any past logs // (i.e. "/cache/recovery/last_*") and the current log // ("/cache/recovery/log") into memory, so we can restore them after @@ -631,7 +728,9 @@ static bool erase_volume(const char* volume) { ui->Print("Formatting %s...\n", volume); - ensure_path_unmounted(volume); + if (volume[0] == '/') { + ensure_path_unmounted(volume); + } int result; @@ -652,10 +751,10 @@ static bool erase_volume(const char* volume) { remove(CONVERT_FBE_FILE); rmdir(CONVERT_FBE_DIR); } else { - result = format_volume(volume); + result = format_volume(volume, force); } - if (is_cache) { + if (!force && is_cache) { while (head) { FILE* f = fopen_path(head->name, "wb"); if (f) { @@ -678,21 +777,37 @@ static bool erase_volume(const char* volume) { copy_logs(); } + ui->SetBackground(RecoveryUI::NONE); + ui->SetProgressType(RecoveryUI::EMPTY); + return (result == 0); } -static int +int get_menu_selection(const char* const * headers, const char* const * items, int menu_only, int initial_selection, Device* device) { // throw away keys pressed previously, so user doesn't // accidentally trigger menu items. ui->FlushKeys(); + // Count items to detect valid values for absolute selection + int header_count = 0; + if (headers) { + while (headers[header_count] != NULL) + ++header_count; + } + int item_count = 0; + while (items[item_count] != NULL) + ++item_count; + ui->StartMenu(headers, items, initial_selection); int selected = initial_selection; int chosen_item = -1; - while (chosen_item < 0) { + while (chosen_item < 0 && + chosen_item != Device::kGoBack && + chosen_item != Device::kGoHome && + chosen_item != Device::kRefresh) { int key = ui->WaitKey(); int visible = ui->IsTextVisible(); @@ -704,10 +819,29 @@ get_menu_selection(const char* const * headers, const char* const * items, ui->EndMenu(); return 0; // XXX fixme } + } else if (key == -2) { // we are returning from ui_cancel_wait_key(): no action + return Device::kNoAction; + } + else if (key == -6) { + return Device::kRefresh; } int action = device->HandleMenuKey(key, visible); + if (action >= 0) { + action &= ~KEY_FLAG_ABS; + if (action < header_count || action >= header_count + item_count) { + action = Device::kNoAction; + } + else { + // Absolute selection. Update selected item and give some + // feedback in the UI by selecting the item for a short time. + selected = ui->SelectMenu(action, true); + action = Device::kInvokeItem; + usleep(50*1000); + } + } + if (action < 0) { switch (action) { case Device::kHighlightUp: @@ -721,6 +855,15 @@ get_menu_selection(const char* const * headers, const char* const * items, break; case Device::kNoAction: break; + case Device::kGoBack: + chosen_item = Device::kGoBack; + break; + case Device::kGoHome: + chosen_item = Device::kGoHome; + break; + case Device::kRefresh: + chosen_item = Device::kRefresh; + break; } } else if (!menu_only) { chosen_item = action; @@ -728,6 +871,9 @@ get_menu_selection(const char* const * headers, const char* const * items, } ui->EndMenu(); + if (chosen_item == Device::kGoHome) { + device->GoHome(); + } return chosen_item; } @@ -737,8 +883,6 @@ static int compare_string(const void* a, const void* b) { // Returns a malloc'd path, or NULL. static char* browse_directory(const char* path, Device* device) { - ensure_path_mounted(path); - DIR* d = opendir(path); if (d == NULL) { LOGE("error opening %s: %s\n", path, strerror(errno)); @@ -797,21 +941,26 @@ static char* browse_directory(const char* path, Device* device) { z_size += d_size; zips[z_size] = NULL; - const char* headers[] = { "Choose a package to install:", path, NULL }; + const char* headers[] = { path, NULL }; char* result; int chosen_item = 0; while (true) { chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device); - - char* item = zips[chosen_item]; - int item_len = strlen(item); - if (chosen_item == 0) { // item 0 is always "../" + if (chosen_item == Device::kGoHome) { + // go up and stop browsing + result = strdup(""); + break; + } + if (chosen_item == 0 || chosen_item == Device::kGoBack) { // go up but continue browsing (if the caller is update_directory) result = NULL; break; } + char* item = zips[chosen_item]; + int item_len = strlen(item); + char new_path[PATH_MAX]; strlcpy(new_path, path, PATH_MAX); strlcat(new_path, "/", PATH_MAX); @@ -844,7 +993,7 @@ static bool yes_no(Device* device, const char* question1, const char* question2) } // Return true on success. -static bool wipe_data(int should_confirm, Device* device) { +static bool wipe_data(int should_confirm, Device* device, bool force = false) { if (should_confirm && !yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!")) { return false; } @@ -852,15 +1001,39 @@ static bool wipe_data(int should_confirm, Device* device) { modified_flash = true; ui->Print("\n-- Wiping data...\n"); - bool success = + bool success; +retry: + success = device->PreWipeData() && - erase_volume("/data") && + erase_volume("/data", force) && (has_cache ? erase_volume("/cache") : true) && device->PostWipeData(); + if (!success && !force) { + if (!should_confirm || yes_no(device, "Wipe failed, format instead?", " THIS CAN NOT BE UNDONE!")) { + force = true; + goto retry; + } + } ui->Print("Data wipe %s.\n", success ? "complete" : "failed"); return success; } +static bool wipe_media(int should_confirm, Device* device) { + if (should_confirm && !yes_no(device, "Wipe all user media?", " THIS CAN NOT BE UNDONE!")) { + return false; + } + + modified_flash = true; + + ui->Print("\n-- Wiping media...\n"); + bool success = + device->PreWipeMedia() && + erase_volume("media") && + device->PostWipeMedia(); + ui->Print("Media wipe %s.\n", success ? "complete" : "failed"); + return success; +} + // Return true on success. static bool wipe_cache(bool should_confirm, Device* device) { if (!has_cache) { @@ -880,6 +1053,20 @@ static bool wipe_cache(bool should_confirm, Device* device) { return success; } +// Return true on success. +static bool wipe_system(Device* device) { + if (!yes_no(device, "Wipe system?", " THIS CAN NOT BE UNDONE!")) { + return false; + } + + modified_flash = true; + + ui->Print("\n-- Wiping system...\n"); + bool success = erase_volume("/system"); + ui->Print("System wipe %s.\n", success ? "complete" : "failed"); + return success; +} + // Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. // Otherwise, it goes with BLKDISCARD (if device supports BLKDISCARDZEROES) or // BLKZEROOUT. @@ -1067,7 +1254,9 @@ static void choose_recovery_file(Device* device) { while (true) { int chosen_item = get_menu_selection(headers, entries, 1, 0, device); - if (strcmp(entries[chosen_item], "Back") == 0) break; + if (chosen_item == Device::kGoHome) break; + if (chosen_item == Device::kGoBack) break; + if (chosen_item >= 0 && strcmp(entries[chosen_item], "Back") == 0) break; ui->ShowFile(entries[chosen_item]); } @@ -1108,25 +1297,78 @@ static void run_graphics_test(Device* device) { ui->ShowText(true); } +static int is_ufs_dev() +{ + char bootdevice[PROPERTY_VALUE_MAX] = {0}; + property_get("ro.boot.bootdevice", bootdevice, "N/A"); + ui->Print("ro.boot.bootdevice is: %s\n",bootdevice); + if (strlen(bootdevice) < strlen(".ufshc") + 1) + return 0; + return (!strncmp(&bootdevice[strlen(bootdevice) - strlen(".ufshc")], + ".ufshc", + sizeof(".ufshc"))); +} + +static int do_sdcard_mount_for_ufs() +{ + int rc = 0; + ui->Print("Update via sdcard on UFS dev.Mounting card\n"); + Volume *v = volume_for_path("/sdcard"); + if (v == NULL) { + ui->Print("Unknown volume for /sdcard.Check fstab\n"); + goto error; + } + if (strncmp(v->fs_type, "vfat", sizeof("vfat"))) { + ui->Print("Unsupported format on the sdcard: %s\n", + v->fs_type); + goto error; + } + rc = mount(UFS_DEV_SDCARD_BLK_PATH, + v->mount_point, + v->fs_type, + v->flags, + v->fs_options); + if (rc) { + ui->Print("Failed to mount sdcard : %s\n", + strerror(errno)); + goto error; + } + ui->Print("Done mounting sdcard\n"); + return 0; +error: + return -1; +} + // How long (in seconds) we wait for the fuse-provided package file to // appear, before timing out. #define SDCARD_INSTALL_TIMEOUT 10 -static int apply_from_sdcard(Device* device, bool* wipe_cache) { +static int apply_from_storage(Device* device, const std::string& id, bool* wipe_cache) { modified_flash = true; - if (ensure_path_mounted(SDCARD_ROOT) != 0) { - ui->Print("\n-- Couldn't mount %s.\n", SDCARD_ROOT); - return INSTALL_ERROR; + if (is_ufs_dev()) { + if (do_sdcard_mount_for_ufs() != 0) { + return INSTALL_ERROR; + } + } else { + if (!vdc->volumeMount(id)) { + return INSTALL_ERROR; + } } - char* path = browse_directory(SDCARD_ROOT, device); - if (path == NULL) { + VolumeInfo vi = vdc->getVolume(id); + + char* path = browse_directory(vi.mInternalPath.c_str(), device); + if (path == NULL || *path == '\0') { ui->Print("\n-- No package file selected.\n"); - ensure_path_unmounted(SDCARD_ROOT); - return INSTALL_ERROR; + vdc->volumeUnmount(vi.mId); + free(path); + return INSTALL_NONE; } + ui->ClearText(); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + ui->Print("\n-- Install %s ...\n", path); set_sdcard_update_bootloader_message(); @@ -1145,7 +1387,10 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { int result = INSTALL_ERROR; int status; bool waited = false; - for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { + time_t start_time = time(NULL); + time_t now = start_time; + + while (now - start_time < SDCARD_INSTALL_TIMEOUT) { if (waitpid(child, &status, WNOHANG) == -1) { result = INSTALL_ERROR; waited = true; @@ -1154,8 +1399,9 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { struct stat sb; if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &sb) == -1) { - if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT-1) { + if (errno == ENOENT) { sleep(1); + now = time(NULL); continue; } else { LOGE("Timed out waiting for the fuse-provided package.\n"); @@ -1165,6 +1411,8 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { } } + vdc->volumeUnmount(vi.mId, true); + result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, TEMPORARY_INSTALL_FILE, false, 0/*retry_count*/); break; @@ -1183,10 +1431,71 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { LOGE("Error exit from the fuse process: %d\n", WEXITSTATUS(status)); } - ensure_path_unmounted(SDCARD_ROOT); return result; } +static int +show_apply_update_menu(Device* device) { + static const char* headers[] = { "Apply update", NULL }; + char* menu_items[MAX_NUM_MANAGED_VOLUMES + 1 + 1]; + std::vector<VolumeInfo> volumes = vdc->getVolumes(); + + const int item_sideload = 0; + int n, i; + std::vector<VolumeInfo>::iterator vitr; + +refresh: + menu_items[item_sideload] = strdup("Apply from ADB"); + + n = item_sideload + 1; + for (vitr = volumes.begin(); vitr != volumes.end(); ++vitr) { + menu_items[n] = (char*)malloc(256); + sprintf(menu_items[n], "Choose from %s", vitr->mLabel.c_str()); + ++n; + } + menu_items[n] = NULL; + + bool wipe_cache; + int status = INSTALL_ERROR; + + int chosen = get_menu_selection(headers, menu_items, 0, 0, device); + for (i = 0; i < n; ++i) { + free(menu_items[i]); + } + if (chosen == Device::kRefresh) { + goto refresh; + } + if (chosen == Device::kGoHome) { + return INSTALL_NONE; + } + if (chosen == Device::kGoBack) { + return INSTALL_NONE; + } + if (chosen == item_sideload) { + static const char* headers[] = { "ADB Sideload", + NULL + }; + static const char* list[] = { "Cancel sideload", NULL }; + + start_sideload(ui, &wipe_cache, TEMPORARY_INSTALL_FILE); + int item = get_menu_selection(headers, list, 0, 0, device); + if (item != Device::kNoAction) { + stop_sideload(); + } + status = wait_sideload(); + } + else { + std::string id = volumes[chosen - 1].mId; + status = apply_from_storage(device, id, &wipe_cache); + } + + if (status != INSTALL_SUCCESS && status != INSTALL_NONE) { + ui->Print("Install failed"); + } + + return status; +} + // Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION // means to take the default, which is to reboot or shutdown depending // on if the --shutdown_after flag was passed to recovery. @@ -1197,7 +1506,7 @@ prompt_and_wait(Device* device, int status) { switch (status) { case INSTALL_SUCCESS: case INSTALL_NONE: - ui->SetBackground(RecoveryUI::NO_COMMAND); + ui->SetBackground(RecoveryUI::NONE); break; case INSTALL_ERROR: @@ -1215,81 +1524,101 @@ prompt_and_wait(Device* device, int status) { Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item); bool should_wipe_cache = false; - switch (chosen_action) { - case Device::NO_ACTION: - break; + for (;;) { + switch (chosen_action) { + case Device::NO_ACTION: + break; - case Device::REBOOT: - case Device::SHUTDOWN: - case Device::REBOOT_BOOTLOADER: - return chosen_action; + case Device::REBOOT: + case Device::SHUTDOWN: + case Device::REBOOT_RECOVERY: + case Device::REBOOT_BOOTLOADER: + return chosen_action; - case Device::WIPE_DATA: - wipe_data(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; + case Device::WIPE_DATA: + wipe_data(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - case Device::WIPE_CACHE: - wipe_cache(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; + case Device::WIPE_FULL: + wipe_data(ui->IsTextVisible(), device, true); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - case Device::APPLY_ADB_SIDELOAD: - case Device::APPLY_SDCARD: - { - bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); - if (adb) { - status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); - } else { - status = apply_from_sdcard(device, &should_wipe_cache); - } + case Device::WIPE_CACHE: + wipe_cache(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; + case Device::APPLY_UPDATE: + { + status = show_apply_update_menu(device); + + if (status == INSTALL_SUCCESS && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } } - } - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - copy_logs(); - } else if (!ui->IsTextVisible()) { - return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); + if (status >= 0 && status != INSTALL_NONE) { + if (status != INSTALL_SUCCESS) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + copy_logs(); + } else if (!ui->IsTextVisible()) { + return Device::NO_ACTION; // reboot if logs aren't visible + } else { + ui->Print("\nInstall complete.\n"); + } + } } - } - break; + break; - case Device::VIEW_RECOVERY_LOGS: - choose_recovery_file(device); - break; + case Device::VIEW_RECOVERY_LOGS: + choose_recovery_file(device); + break; - case Device::RUN_GRAPHICS_TEST: - run_graphics_test(device); - break; + case Device::RUN_GRAPHICS_TEST: + run_graphics_test(device); + break; - case Device::MOUNT_SYSTEM: - char system_root_image[PROPERTY_VALUE_MAX]; - property_get("ro.build.system_root_image", system_root_image, ""); - - // For a system image built with the root directory (i.e. - // system_root_image == "true"), we mount it to /system_root, and symlink /system - // to /system_root/system to make adb shell work (the symlink is created through - // the build system). - // Bug: 22855115 - if (strcmp(system_root_image, "true") == 0) { - if (ensure_path_mounted_at("/", "/system_root") != -1) { - ui->Print("Mounted /system.\n"); + case Device::MOUNT_SYSTEM: +#ifdef USE_MDTP + if (is_mdtp_activated()) { + ui->Print("Mounting /system forbidden by MDTP.\n"); } - } else { - if (ensure_path_mounted("/system") != -1) { - ui->Print("Mounted /system.\n"); + else +#endif + { + char system_root_image[PROPERTY_VALUE_MAX]; + property_get("ro.build.system_root_image", system_root_image, ""); + + // For a system image built with the root directory (i.e. + // system_root_image == "true"), we mount it to /system_root, and symlink /system + // to /system_root/system to make adb shell work (the symlink is created through + // the build system). + // Bug: 22855115 + if (strcmp(system_root_image, "true") == 0) { + if (ensure_path_mounted_at("/", "/system_root") != -1) { + ui->Print("Mounted /system.\n"); + } + } else { + if (ensure_path_mounted("/system") != -1) { + ui->Print("Mounted /system.\n"); + } + } } - } + break; - break; + case Device::WIPE_SYSTEM: + wipe_system(device); + break; + } + if (status == Device::kRefresh) { + status = 0; + continue; + } + break; } } } @@ -1318,6 +1647,40 @@ load_locale_from_cache() { } } +static const char *key_src = "/data/misc/adb/adb_keys"; +static const char *key_dest = "/adb_keys"; + + +static void +setup_adbd() { + struct stat f; + // Mount /data and copy adb_keys to root if it exists + ensure_path_mounted("/data"); + if (stat(key_src, &f) == 0) { + FILE *file_src = fopen(key_src, "r"); + if (file_src == NULL) { + LOGE("Can't open %s\n", key_src); + } else { + FILE *file_dest = fopen(key_dest, "w"); + if (file_dest == NULL) { + LOGE("Can't open %s\n", key_dest); + } else { + char buf[4096]; + while (fgets(buf, sizeof(buf), file_src)) fputs(buf, file_dest); + check_and_fclose(file_dest, key_dest); + + // Enable secure adbd + property_set("ro.adb.secure", "1"); + } + check_and_fclose(file_src, key_src); + } + } + ensure_path_unmounted("/data"); + + // Trigger (re)start of adb daemon + property_set("service.adb.root", "1"); +} + static RecoveryUI* gCurrentUI = NULL; void @@ -1493,6 +1856,28 @@ static ssize_t logrotate( return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len); } +static int write_file(const char *path, const char *value) +{ + int fd, ret, len; + + fd = open(path, O_WRONLY|O_CREAT, 0622); + if (fd < 0) + return -errno; + + len = strlen(value); + + do { + ret = write(fd, value, len); + } while (ret < 0 && errno == EINTR); + + close(fd); + if (ret < 0) { + return -errno; + } else { + return 0; + } +} + int main(int argc, char **argv) { // Take last pmsg contents and rewrite it to the current pmsg session. static const char filter[] = "recovery/"; @@ -1518,6 +1903,37 @@ int main(int argc, char **argv) { return 0; } + // Handle alternative invocations + char* command = argv[0]; + char* stripped = strrchr(argv[0], '/'); + if (stripped) + command = stripped + 1; + + if (strcmp(command, "recovery") != 0) { + struct recovery_cmd cmd = get_command(command); + if (cmd.name) + return cmd.main_func(argc, argv); + + if (!strcmp(command, "setup_adbd")) { + load_volume_table(); + setup_adbd(); + return 0; + } + if (strstr(argv[0], "start")) { + property_set("ctl.start", argv[1]); + return 0; + } + if (strstr(argv[0], "stop")) { + property_set("ctl.stop", argv[1]); + return 0; + } + return toybox_driver(argc, argv); + } + + // Clear umask for packages that copy files out to /tmp and then over + // to /system without properly setting all permissions (eg. gapps). + umask(0); + time_t start = time(NULL); // redirect_stdio should be called only in non-sideload mode. Otherwise @@ -1537,6 +1953,7 @@ int main(int argc, char **argv) { bool should_wipe_cache = false; bool should_wipe_ab = false; size_t wipe_package_size = 0; + bool should_wipe_media = false; bool show_text = false; bool sideload = false; bool sideload_auto_reboot = false; @@ -1544,6 +1961,8 @@ int main(int argc, char **argv) { bool shutdown_after = false; int retry_count = 0; bool security_update = false; + int status = INSTALL_SUCCESS; + bool mount_required = true; int arg; int option_index; @@ -1554,6 +1973,7 @@ int main(int argc, char **argv) { case 'u': update_package = optarg; break; case 'w': should_wipe_data = true; break; case 'c': should_wipe_cache = true; break; + case 'm': should_wipe_media = true; break; case 't': show_text = true; break; case 's': sideload = true; break; case 'a': sideload = true; sideload_auto_reboot = true; break; @@ -1597,6 +2017,9 @@ int main(int argc, char **argv) { ui = device->GetUI(); gCurrentUI = ui; + vdc = new VoldClient(device); + vdc->start(); + ui->SetLocale(locale); ui->Init(); // Set background string to "installing security update" for security update, @@ -1611,6 +2034,9 @@ int main(int argc, char **argv) { ui->SetBackground(RecoveryUI::NONE); if (show_text) ui->ShowText(true); + /*enable the backlight*/ + write_file("/sys/class/leds/lcd-backlight/brightness", "128"); + struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "/file_contexts" } }; @@ -1646,16 +2072,38 @@ int main(int argc, char **argv) { else printf("modified_path allocation failed\n"); } + if (!strncmp("/sdcard", update_package, 7)) { + //If this is a UFS device lets mount the sdcard ourselves.Depending + //on if the device is UFS or EMMC based the path to the sdcard + //device changes so we cannot rely on the block dev path from + //recovery.fstab + if (is_ufs_dev()) { + if(do_sdcard_mount_for_ufs() != 0) { + status = INSTALL_ERROR; + goto error; + } + mount_required = false; + } else { + ui->Print("Update via sdcard on EMMC dev. Using path from fstab\n"); + } + } } printf("\n"); - property_list(print_property, NULL); printf("\n"); - ui->Print("Supported API: %d\n", RECOVERY_API_VERSION); - - int status = INSTALL_SUCCESS; - +#ifdef HAVE_OEMLOCK + if (oem_lock == OEM_LOCK_UNLOCK) { + device->PreWipeData(); + if (erase_volume("/data", true)) status = INSTALL_ERROR; + if (should_wipe_cache && erase_volume("/cache", true)) status = INSTALL_ERROR; + device->PostWipeData(); + if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n"); + if (oemlock_set(0)) status = INSTALL_ERROR; + // Force reboot regardless of actual status + status = INSTALL_SUCCESS; + } else +#endif if (update_package != NULL) { // It's not entirely true that we will modify the flash. But we want // to log the update attempt since update_package is non-NULL. @@ -1675,7 +2123,7 @@ int main(int argc, char **argv) { status = INSTALL_SKIPPED; } else { status = install_package(update_package, &should_wipe_cache, - TEMPORARY_INSTALL_FILE, true, retry_count); + TEMPORARY_INSTALL_FILE, mount_required, retry_count); if (status == INSTALL_SUCCESS && should_wipe_cache) { wipe_cache(false, device); } @@ -1708,7 +2156,7 @@ int main(int argc, char **argv) { } } } else if (should_wipe_data) { - if (!wipe_data(false, device)) { + if (!wipe_data(false, device, should_wipe_media)) { status = INSTALL_ERROR; } } else if (should_wipe_cache) { @@ -1719,6 +2167,10 @@ int main(int argc, char **argv) { if (!wipe_ab_device(wipe_package_size)) { status = INSTALL_ERROR; } + } else if (should_wipe_media) { + if (!wipe_media(false, device)) { + status = INSTALL_ERROR; + } } else if (sideload) { // 'adb reboot sideload' acts the same as user presses key combinations // to enter the sideload mode. When 'sideload-auto-reboot' is used, text @@ -1729,7 +2181,8 @@ int main(int argc, char **argv) { if (!sideload_auto_reboot) { ui->ShowText(true); } - status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); + start_sideload(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); + status = wait_sideload(); if (status == INSTALL_SUCCESS && should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; @@ -1743,14 +2196,10 @@ int main(int argc, char **argv) { 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); - } + // Always show menu if no command is specified + ui->ShowText(true); } - +error: if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) { copy_logs(); ui->SetBackground(RecoveryUI::ERROR); @@ -1768,20 +2217,40 @@ int main(int argc, char **argv) { // Save logs and clean up before rebooting or shutting down. finish_recovery(send_intent); + vdc->unmountAll(); + vdc->stop(); + + sync(); + + write_file("/sys/class/leds/lcd-backlight/brightness", "0"); + gr_fb_blank(true); + switch (after) { case Device::SHUTDOWN: ui->Print("Shutting down...\n"); property_set(ANDROID_RB_PROPERTY, "shutdown,"); break; + case Device::REBOOT_RECOVERY: + ui->Print("Rebooting recovery...\n"); + property_set(ANDROID_RB_PROPERTY, "reboot,recovery"); + break; + case Device::REBOOT_BOOTLOADER: +#ifdef DOWNLOAD_MODE + ui->Print("Rebooting to download mode...\n"); + property_set(ANDROID_RB_PROPERTY, "reboot,download"); +#else ui->Print("Rebooting to bootloader...\n"); property_set(ANDROID_RB_PROPERTY, "reboot,bootloader"); +#endif break; default: + char reason[PROPERTY_VALUE_MAX]; + snprintf(reason, PROPERTY_VALUE_MAX, "reboot,%s", device->GetRebootReason()); ui->Print("Rebooting...\n"); - property_set(ANDROID_RB_PROPERTY, "reboot,"); + property_set(ANDROID_RB_PROPERTY, reason); break; } while (true) { diff --git a/recovery_cmds.h b/recovery_cmds.h new file mode 100644 index 00000000..8fc4d3e1 --- /dev/null +++ b/recovery_cmds.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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_CMDS_H +#define _RECOVERY_CMDS_H + +#include <stdio.h> +#include <string.h> + +int vold_main(int argc, char **argv); +int minizip_main(int argc, char **argv); +int miniunz_main(int argc, char **argv); +int make_ext4fs_main(int argc, char **argv); +int reboot_main(int argc, char **argv); +int poweroff_main(int argc, char **argv); +int fsck_msdos_main(int argc, char **argv); +int newfs_msdos_main(int argc, char **argv); +int pigz_main(int argc, char **argv); +int start_main(int argc, char **argv); +int stop_main(int argc, char **argv); +int mksh_main(int argc, char **argv); +int vdc_main(int argc, char **argv); + +int toybox_driver(int argc, char **argv); + +struct recovery_cmd { + const char *name; + int (*main_func)(int argc, char **argv); +}; + +static const struct recovery_cmd recovery_cmds[] = { + { "minivold", vold_main }, + { "minizip", minizip_main }, + { "make_ext4fs", make_ext4fs_main }, + { "reboot", reboot_main }, + { "poweroff", reboot_main }, + { "fsck_msdos", fsck_msdos_main }, + { "newfs_msdos", newfs_msdos_main }, + { "pigz", pigz_main }, + { "gzip", pigz_main }, + { "gunzip", pigz_main }, + { "zip", minizip_main }, + { "unzip", miniunz_main }, + { "start", start_main }, + { "stop", stop_main }, + { "sh", mksh_main }, + { "vdc", vdc_main }, + { NULL, NULL }, +}; + +struct recovery_cmd get_command(char* command) { + int i; + + for (i = 0; recovery_cmds[i].name; i++) { + if (strcmp(command, recovery_cmds[i].name) == 0) + break; + } + + return recovery_cmds[i]; +} +#endif diff --git a/res-hdpi/images/font_log.png b/res-hdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..59b990ae --- /dev/null +++ b/res-hdpi/images/font_log.png diff --git a/res-hdpi/images/font_menu.png b/res-hdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..d1aad6da --- /dev/null +++ b/res-hdpi/images/font_menu.png diff --git a/res-hdpi/images/icon_header.png b/res-hdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..1abd92d3 --- /dev/null +++ b/res-hdpi/images/icon_header.png diff --git a/res-hdpi/images/icon_sysbar_back.png b/res-hdpi/images/icon_sysbar_back.png Binary files differnew file mode 100644 index 00000000..5aa8e62f --- /dev/null +++ b/res-hdpi/images/icon_sysbar_back.png diff --git a/res-hdpi/images/icon_sysbar_back_highlight.png b/res-hdpi/images/icon_sysbar_back_highlight.png Binary files differnew file mode 100644 index 00000000..58365041 --- /dev/null +++ b/res-hdpi/images/icon_sysbar_back_highlight.png diff --git a/res-hdpi/images/icon_sysbar_home.png b/res-hdpi/images/icon_sysbar_home.png Binary files differnew file mode 100644 index 00000000..b37422da --- /dev/null +++ b/res-hdpi/images/icon_sysbar_home.png diff --git a/res-hdpi/images/icon_sysbar_home_highlight.png b/res-hdpi/images/icon_sysbar_home_highlight.png Binary files differnew file mode 100644 index 00000000..ed0ccfaf --- /dev/null +++ b/res-hdpi/images/icon_sysbar_home_highlight.png diff --git a/res-mdpi/images/font_log.png b/res-mdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..bcbe242d --- /dev/null +++ b/res-mdpi/images/font_log.png diff --git a/res-mdpi/images/font_menu.png b/res-mdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..59b990ae --- /dev/null +++ b/res-mdpi/images/font_menu.png diff --git a/res-mdpi/images/icon_header.png b/res-mdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..c5fe5726 --- /dev/null +++ b/res-mdpi/images/icon_header.png diff --git a/res-mdpi/images/icon_sysbar_back.png b/res-mdpi/images/icon_sysbar_back.png Binary files differnew file mode 100644 index 00000000..81e46375 --- /dev/null +++ b/res-mdpi/images/icon_sysbar_back.png diff --git a/res-mdpi/images/icon_sysbar_back_highlight.png b/res-mdpi/images/icon_sysbar_back_highlight.png Binary files differnew file mode 100644 index 00000000..d173042e --- /dev/null +++ b/res-mdpi/images/icon_sysbar_back_highlight.png diff --git a/res-mdpi/images/icon_sysbar_home.png b/res-mdpi/images/icon_sysbar_home.png Binary files differnew file mode 100644 index 00000000..d9e3a43e --- /dev/null +++ b/res-mdpi/images/icon_sysbar_home.png diff --git a/res-mdpi/images/icon_sysbar_home_highlight.png b/res-mdpi/images/icon_sysbar_home_highlight.png Binary files differnew file mode 100644 index 00000000..ef65d610 --- /dev/null +++ b/res-mdpi/images/icon_sysbar_home_highlight.png diff --git a/res-xhdpi/images/font_log.png b/res-xhdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..d1aad6da --- /dev/null +++ b/res-xhdpi/images/font_log.png diff --git a/res-xhdpi/images/font_menu.png b/res-xhdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..6aead738 --- /dev/null +++ b/res-xhdpi/images/font_menu.png diff --git a/res-xhdpi/images/icon_header.png b/res-xhdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..7ec2745a --- /dev/null +++ b/res-xhdpi/images/icon_header.png diff --git a/res-xhdpi/images/icon_sysbar_back.png b/res-xhdpi/images/icon_sysbar_back.png Binary files differnew file mode 100644 index 00000000..415715ec --- /dev/null +++ b/res-xhdpi/images/icon_sysbar_back.png diff --git a/res-xhdpi/images/icon_sysbar_back_highlight.png b/res-xhdpi/images/icon_sysbar_back_highlight.png Binary files differnew file mode 100644 index 00000000..237af6ab --- /dev/null +++ b/res-xhdpi/images/icon_sysbar_back_highlight.png diff --git a/res-xhdpi/images/icon_sysbar_home.png b/res-xhdpi/images/icon_sysbar_home.png Binary files differnew file mode 100644 index 00000000..425c0dc5 --- /dev/null +++ b/res-xhdpi/images/icon_sysbar_home.png diff --git a/res-xhdpi/images/icon_sysbar_home_highlight.png b/res-xhdpi/images/icon_sysbar_home_highlight.png Binary files differnew file mode 100644 index 00000000..f7b6ce37 --- /dev/null +++ b/res-xhdpi/images/icon_sysbar_home_highlight.png diff --git a/res-xxhdpi/images/font_log.png b/res-xxhdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..6aead738 --- /dev/null +++ b/res-xxhdpi/images/font_log.png diff --git a/res-xxhdpi/images/font_menu.png b/res-xxhdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..f3b54b38 --- /dev/null +++ b/res-xxhdpi/images/font_menu.png diff --git a/res-xxhdpi/images/icon_header.png b/res-xxhdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..6f3a4296 --- /dev/null +++ b/res-xxhdpi/images/icon_header.png diff --git a/res-xxhdpi/images/icon_sysbar_back.png b/res-xxhdpi/images/icon_sysbar_back.png Binary files differnew file mode 100644 index 00000000..0b2e866e --- /dev/null +++ b/res-xxhdpi/images/icon_sysbar_back.png diff --git a/res-xxhdpi/images/icon_sysbar_back_highlight.png b/res-xxhdpi/images/icon_sysbar_back_highlight.png Binary files differnew file mode 100644 index 00000000..03cb8a1f --- /dev/null +++ b/res-xxhdpi/images/icon_sysbar_back_highlight.png diff --git a/res-xxhdpi/images/icon_sysbar_home.png b/res-xxhdpi/images/icon_sysbar_home.png Binary files differnew file mode 100644 index 00000000..73488413 --- /dev/null +++ b/res-xxhdpi/images/icon_sysbar_home.png diff --git a/res-xxhdpi/images/icon_sysbar_home_highlight.png b/res-xxhdpi/images/icon_sysbar_home_highlight.png Binary files differnew file mode 100644 index 00000000..a9814959 --- /dev/null +++ b/res-xxhdpi/images/icon_sysbar_home_highlight.png diff --git a/res-xxxhdpi/images/font_log.png b/res-xxxhdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..ec8d899b --- /dev/null +++ b/res-xxxhdpi/images/font_log.png diff --git a/res-xxxhdpi/images/font_menu.png b/res-xxxhdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..d7c326ad --- /dev/null +++ b/res-xxxhdpi/images/font_menu.png diff --git a/res-xxxhdpi/images/icon_header.png b/res-xxxhdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..3a723079 --- /dev/null +++ b/res-xxxhdpi/images/icon_header.png diff --git a/res-xxxhdpi/images/icon_sysbar_back.png b/res-xxxhdpi/images/icon_sysbar_back.png Binary files differnew file mode 100644 index 00000000..f630553d --- /dev/null +++ b/res-xxxhdpi/images/icon_sysbar_back.png diff --git a/res-xxxhdpi/images/icon_sysbar_back_highlight.png b/res-xxxhdpi/images/icon_sysbar_back_highlight.png Binary files differnew file mode 100644 index 00000000..92110933 --- /dev/null +++ b/res-xxxhdpi/images/icon_sysbar_back_highlight.png diff --git a/res-xxxhdpi/images/icon_sysbar_home.png b/res-xxxhdpi/images/icon_sysbar_home.png Binary files differnew file mode 100644 index 00000000..9ee96ced --- /dev/null +++ b/res-xxxhdpi/images/icon_sysbar_home.png diff --git a/res-xxxhdpi/images/icon_sysbar_home_highlight.png b/res-xxxhdpi/images/icon_sysbar_home_highlight.png Binary files differnew file mode 100644 index 00000000..d63ecb03 --- /dev/null +++ b/res-xxxhdpi/images/icon_sysbar_home_highlight.png diff --git a/restore.cpp b/restore.cpp new file mode 100644 index 00000000..8c15f6f8 --- /dev/null +++ b/restore.cpp @@ -0,0 +1,305 @@ +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/vfs.h> + +#include <cutils/properties.h> + +#include <lib/libtar.h> +#include <zlib.h> + +#include "roots.h" + +#include "bu.h" + +using namespace android; + +static int verify_sod() +{ + const char* key; + char value[PROPERTY_VALUE_MAX]; + char sodbuf[PROP_LINE_LEN*10]; + size_t len; + + len = sizeof(sodbuf); + if (tar_extract_file_contents(tar, sodbuf, &len) != 0) { + logmsg("tar_verify_sod: failed to extract file\n"); + return -1; + } + + int partidx = 0; + + char val_hashname[PROPERTY_VALUE_MAX]; + memset(val_hashname, 0, sizeof(val_hashname)); + char val_product[PROPERTY_VALUE_MAX]; + memset(val_product, 0, sizeof(val_product)); + char* p = sodbuf; + char* q; + while ((q = strchr(p, '\n')) != NULL) { + char* key = p; + *q = '\0'; + logmsg("verify_sod: line=%s\n", p); + p = q+1; + char* val = strchr(key, '='); + if (val) { + *val = '\0'; + ++val; + if (strcmp(key, "hash.name") == 0) { + strncpy(val_hashname, val, sizeof(val_hashname)); + } + if (strcmp(key, "ro.product.device") == 0) { + strncpy(val_product, val, sizeof(val_product)); + } + if (strncmp(key, "fs.", 3) == 0) { + char* name = key+3; + char* attr = strchr(name, '.'); + if (attr) { + *attr = '\0'; + ++attr; + part_add(name); + struct partspec* part = part_find(name); + if (!strcmp(attr, "size")) { + part->size = strtoul(val, NULL, 0); + } + if (!strcmp(attr, "used")) { + part->used = strtoul(val, NULL, 0); + } + } + } + } + } + + if (!val_hashname[0]) { + logmsg("verify_sod: did not find hash.name\n"); + return -1; + } + hash_name = strdup(val_hashname); + + if (!val_product[0]) { + logmsg("verify_sod: did not find ro.product.device\n"); + return -1; + } + key = "ro.product.device"; + property_get(key, value, ""); + if (strcmp(val_product, value) != 0) { + logmsg("verify_sod: product does not match\n"); + return -1; + } + + return 0; +} + +static int verify_eod(size_t actual_hash_datalen, + SHA_CTX* actual_sha_ctx, MD5_CTX* actual_md5_ctx) +{ + int rc = -1; + char eodbuf[PROP_LINE_LEN*10]; + size_t len; + + len = sizeof(eodbuf); + if (tar_extract_file_contents(tar, eodbuf, &len) != 0) { + logmsg("verify_eod: failed to extract file\n"); + return -1; + } + + size_t reported_datalen = 0; + char reported_hash[HASH_MAX_STRING_LENGTH]; + memset(reported_hash, 0, sizeof(reported_hash)); + char* p = eodbuf; + char* q; + while ((q = strchr(p, '\n')) != NULL) { + char* key = p; + *q = '\0'; + logmsg("verify_eod: line=%s\n", p); + p = q+1; + char* val = strchr(key, '='); + if (val) { + *val = '\0'; + ++val; + if (strcmp(key, "hash.datalen") == 0) { + reported_datalen = strtoul(val, NULL, 0); + } + if (strcmp(key, "hash.value") == 0) { + memset(reported_hash, 0, sizeof(reported_hash)); + strncpy(reported_hash, val, sizeof(reported_hash)); + } + } + } + + unsigned char digest[HASH_MAX_LENGTH]; + char hexdigest[HASH_MAX_STRING_LENGTH]; + + int n; + if (hash_name != NULL && !strcasecmp(hash_name, "sha1")) { + SHA1_Final(digest, actual_sha_ctx); + for (n = 0; n < SHA_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + } + else { // default to md5 + MD5_Final(digest, actual_md5_ctx); + for (n = 0; n < MD5_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + } + + logmsg("verify_eod: expected=%d,%s\n", actual_hash_datalen, hexdigest); + + logmsg("verify_eod: reported=%d,%s\n", reported_datalen, reported_hash); + + if ((reported_datalen == actual_hash_datalen) && + (memcmp(hexdigest, reported_hash, strlen(hexdigest)) == 0)) { + rc = 0; + } + + return rc; +} + +static int do_restore_tree() +{ + int rc = 0; + ssize_t len; + const char* compress = "none"; + char buf[512]; + char rootpath[] = "/"; + + logmsg("do_restore_tree: enter\n"); + + len = recv(adb_ifd, buf, sizeof(buf), MSG_PEEK); + if (len < 0) { + logmsg("do_restore_tree: peek failed (%d:%s)\n", rc, strerror(errno)); + return -1; + } + if (len < 2) { + logmsg("do_restore_tree: peek returned %d\n", len); + return -1; + } + if (buf[0] == 0x1f && buf[1] == 0x8b) { + logmsg("do_restore_tree: is gzip\n"); + compress = "gzip"; + } + + create_tar(adb_ifd, compress, "r"); + + size_t save_hash_datalen; + SHA_CTX save_sha_ctx; + MD5_CTX save_md5_ctx; + + char cur_mount[PATH_MAX]; + cur_mount[0] = '\0'; + while (1) { + save_hash_datalen = hash_datalen; + memcpy(&save_sha_ctx, &sha_ctx, sizeof(SHA_CTX)); + memcpy(&save_md5_ctx, &md5_ctx, sizeof(MD5_CTX)); + rc = th_read(tar); + if (rc != 0) { + if (rc == 1) { // EOF + rc = 0; + } + break; + } + char* pathname = th_get_pathname(tar); + logmsg("do_restore_tree: extract %s\n", pathname); + if (pathname[0] == '/') { + const char* mntend = strchr(&pathname[1], '/'); + if (!mntend) { + mntend = pathname + strlen(pathname); + } + if (memcmp(cur_mount, pathname, mntend-pathname) != 0) { + // New mount + if (cur_mount[0]) { + logmsg("do_restore_tree: unmounting %s\n", cur_mount); + ensure_path_unmounted(cur_mount); + } + memcpy(cur_mount, pathname, mntend-pathname); + cur_mount[mntend-pathname] = '\0'; + + // XXX: Assume paths are not interspersed + logmsg("do_restore_tree: switching to %s\n", cur_mount); + rc = ensure_path_unmounted(cur_mount); + if (rc != 0) { + logmsg("do_restore_tree: cannot unmount %s\n", cur_mount); + break; + } + logmsg("do_restore_tree: formatting %s\n", cur_mount); + rc = format_volume(cur_mount); + if (rc != 0) { + logmsg("do_restore_tree: cannot format %s\n", cur_mount); + break; + } + rc = ensure_path_mounted(cur_mount, true); + if (rc != 0) { + logmsg("do_restore_tree: cannot mount %s\n", cur_mount); + break; + } + partspec* curpart = part_find(&cur_mount[1]); + part_set(curpart); + } + rc = tar_extract_file(tar, pathname); + if (rc != 0) { + logmsg("do_restore_tree: failed to restore %s", pathname); + } + } + else if (!strcmp(pathname, "SOD")) { + rc = verify_sod(); + logmsg("do_restore_tree: tar_verify_sod returned %d\n", rc); + } + else if (!strcmp(pathname, "EOD")) { + rc = verify_eod(save_hash_datalen, &save_sha_ctx, &save_md5_ctx); + logmsg("do_restore_tree: tar_verify_eod returned %d\n", rc); + } + else { + char mnt[PATH_MAX]; + snprintf(mnt, sizeof(mnt), "/%s", pathname); + Volume* vol = volume_for_path(mnt); + if (vol != NULL && vol->fs_type != NULL) { + partspec* curpart = part_find(pathname); + part_set(curpart); + rc = tar_extract_file(tar, vol->blk_device); + } + else { + logmsg("do_restore_tree: cannot find volume for %s\n", mnt); + } + } + free(pathname); + if (rc != 0) { + logmsg("extract failed, rc=%d\n", rc); + break; + } + } + + if (cur_mount[0]) { + logmsg("do_restore_tree: unmounting %s\n", cur_mount); + ensure_path_unmounted(cur_mount); + } + + tar_close(tar); + + return rc; +} + +int do_restore(int argc, char **argv) +{ + int rc = 1; + int n; + + char buf[256]; + int len; + int written; + + rc = do_restore_tree(); + logmsg("do_restore: rc=%d\n", rc); + + free(hash_name); + hash_name = NULL; + + return rc; +} + @@ -23,6 +23,7 @@ #include <unistd.h> #include <ctype.h> #include <fcntl.h> +#include <dirent.h> #include <fs_mgr.h> #include "mtdutils/mtdutils.h" @@ -33,10 +34,53 @@ #include "wipe.h" #include "cryptfs.h" +#include "voldclient.h" +#include <blkid/blkid.h> + static struct fstab *fstab = NULL; extern struct selabel_handle *sehandle; +static int mkdir_p(const char* path, mode_t mode) +{ + char dir[PATH_MAX]; + char* p; + strcpy(dir, path); + for (p = strchr(&dir[1], '/'); p != NULL; p = strchr(p+1, '/')) { + *p = '\0'; + if (mkdir(dir, mode) != 0 && errno != EEXIST) { + return -1; + } + *p = '/'; + } + if (mkdir(dir, mode) != 0 && errno != EEXIST) { + return -1; + } + return 0; +} + +static void write_fstab_entry(Volume *v, FILE *file) +{ + if (NULL != v && strcmp(v->fs_type, "mtd") != 0 && strcmp(v->fs_type, "emmc") != 0 + && strcmp(v->fs_type, "bml") != 0 && !fs_mgr_is_voldmanaged(v) + && strncmp(v->blk_device, "/", 1) == 0 + && strncmp(v->mount_point, "/", 1) == 0) { + + fprintf(file, "%s ", v->blk_device); + fprintf(file, "%s ", v->mount_point); + fprintf(file, "%s ", v->fs_type); + fprintf(file, "%s 0 0\n", v->fs_options == NULL ? "defaults" : v->fs_options); + } +} + +int get_num_volumes() { + return fstab->num_entries; +} + +Volume* get_device_volumes() { + return fstab->recs; +} + void load_volume_table() { int i; @@ -56,22 +100,84 @@ void load_volume_table() return; } + // Create a boring /etc/fstab so tools like Busybox work + FILE *file = fopen("/etc/fstab", "w"); + if (file == NULL) { + LOGW("Unable to create /etc/fstab!\n"); + return; + } + printf("recovery filesystem table\n"); printf("=========================\n"); for (i = 0; i < fstab->num_entries; ++i) { Volume* v = &fstab->recs[i]; printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, v->blk_device, v->length); + + write_fstab_entry(v, file); } + + fclose(file); + printf("\n"); } +bool volume_is_mountable(Volume *v) +{ + return (fs_mgr_is_voldmanaged(v) || + !strcmp(v->fs_type, "yaffs2") || + !strcmp(v->fs_type, "ext4") || + !strcmp(v->fs_type, "f2fs") || + !strcmp(v->fs_type, "vfat")); +} + +bool volume_is_readonly(Volume *v) +{ + return (v->flags & MS_RDONLY); +} + +bool volume_is_verity(Volume *v) +{ + return fs_mgr_is_verified(v); +} + Volume* volume_for_path(const char* path) { - return fs_mgr_get_entry_for_mount_point(fstab, path); + Volume *rec = fs_mgr_get_entry_for_mount_point(fstab, path); + + if (rec == NULL) + return rec; + + if (strcmp(rec->fs_type, "ext4") == 0 || strcmp(rec->fs_type, "f2fs") == 0 || + strcmp(rec->fs_type, "vfat") == 0) { + char *detected_fs_type = blkid_get_tag_value(NULL, "TYPE", rec->blk_device); + + if (detected_fs_type == NULL) + return rec; + + Volume *fetched_rec = rec; + while (rec != NULL && strcmp(rec->fs_type, detected_fs_type) != 0) + rec = fs_mgr_get_entry_for_mount_point_after(rec, fstab, path); + + if (rec == NULL) + return fetched_rec; + } + + return rec; +} + +Volume* volume_for_label(const char* label) { + int i; + for (i = 0; i < get_num_volumes(); i++) { + Volume* v = get_device_volumes() + i; + if (v->label && !strcmp(v->label, label)) { + return v; + } + } + return NULL; } // Mount the volume specified by path at the given mount_point. -int ensure_path_mounted_at(const char* path, const char* mount_point) { +int ensure_path_mounted_at(const char* path, const char* mount_point, bool force_rw) { Volume* v = volume_for_path(path); if (v == NULL) { LOGE("unknown volume for path [%s]\n", path); @@ -93,14 +199,16 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { mount_point = v->mount_point; } - const MountedVolume* mv = - find_mounted_volume_by_mount_point(mount_point); - if (mv) { - // volume is already mounted - return 0; + if (!fs_mgr_is_voldmanaged(v)) { + const MountedVolume* mv = + find_mounted_volume_by_mount_point(mount_point); + if (mv) { + // volume is already mounted + return 0; + } } - mkdir(mount_point, 0755); // in case it doesn't already exist + mkdir_p(mount_point, 0755); // in case it doesn't already exist if (strcmp(v->fs_type, "yaffs2") == 0) { // mount an MTD partition as a YAFFS2 filesystem. @@ -114,10 +222,17 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { } return mtd_mount_partition(partition, mount_point, v->fs_type, 0); } else if (strcmp(v->fs_type, "ext4") == 0 || + strcmp(v->fs_type, "f2fs") == 0 || strcmp(v->fs_type, "squashfs") == 0 || strcmp(v->fs_type, "vfat") == 0) { + unsigned long mntflags = v->flags; + if (!force_rw) { + if ((v->flags & MS_RDONLY) || fs_mgr_is_verified(v)) { + mntflags |= MS_RDONLY; + } + } result = mount(v->blk_device, mount_point, v->fs_type, - v->flags, v->fs_options); + mntflags, v->fs_options); if (result == 0) return 0; LOGE("failed to mount %s (%s)\n", mount_point, strerror(errno)); @@ -128,17 +243,76 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { return -1; } -int ensure_path_mounted(const char* path) { +int ensure_volume_mounted(Volume* v, bool force_rw) { + if (v == NULL) { + LOGE("cannot mount unknown volume\n"); + return -1; + } + return ensure_path_mounted_at(v->mount_point, nullptr, force_rw); +} + +int remount_no_selinux(const char* path) { + int ret; + + char *old_fs_options; + char *new_fs_options; + + char se_context[] = ",context=u:object_r:app_data_file:s0"; + Volume *v; + + // Backup original mount options + v = volume_for_path(path); + old_fs_options = v->fs_options; + + // Add SELinux mount override + asprintf(&new_fs_options, "%s%s", v->fs_options, se_context); + v->fs_options = new_fs_options; + + ensure_path_unmounted(path); + ret = ensure_path_mounted(path); + + // Restore original mount options + v->fs_options = old_fs_options; + free(new_fs_options); + + return ret; +} + +int ensure_path_mounted(const char* path, bool force_rw) { // Mount at the default mount point. - return ensure_path_mounted_at(path, nullptr); + return ensure_path_mounted_at(path, nullptr, force_rw); } -int ensure_path_unmounted(const char* path) { - Volume* v = volume_for_path(path); +int ensure_path_unmounted(const char* path, bool detach /* = false */) { + Volume* v; + if (memcmp(path, "/storage/", 9) == 0) { + char label[PATH_MAX]; + const char* p = path+9; + const char* q = strchr(p, '/'); + memset(label, 0, sizeof(label)); + if (q) { + memcpy(label, p, q-p); + } + else { + strcpy(label, p); + } + v = volume_for_label(label); + } + else { + v = volume_for_path(path); + } if (v == NULL) { LOGE("unknown volume for path [%s]\n", path); return -1; } + return ensure_volume_unmounted(v, detach); +} + +int ensure_volume_unmounted(Volume* v, bool detach /* = false */) { + if (v == NULL) { + LOGE("cannot unmount unknown volume\n"); + return -1; + } if (strcmp(v->fs_type, "ramdisk") == 0) { // the ramdisk is always mounted; you can't unmount it. return -1; @@ -158,7 +332,14 @@ int ensure_path_unmounted(const char* path) { return 0; } - return unmount_mounted_volume(mv); + if (detach) { + result = unmount_mounted_volume_detach(mv); + } + else { + result = unmount_mounted_volume(mv); + } + + return result; } static int exec_cmd(const char* path, char* const argv[]) { @@ -175,7 +356,61 @@ static int exec_cmd(const char* path, char* const argv[]) { return WEXITSTATUS(status); } -int format_volume(const char* volume, const char* directory) { +static int rmtree_except(const char* path, const char* except) +{ + char pathbuf[PATH_MAX]; + int rc = 0; + DIR* dp = opendir(path); + if (dp == NULL) { + return -1; + } + struct dirent* de; + while ((de = readdir(dp)) != NULL) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + if (except && !strcmp(de->d_name, except)) + continue; + struct stat st; + snprintf(pathbuf, sizeof(pathbuf), "%s/%s", path, de->d_name); + rc = lstat(pathbuf, &st); + if (rc != 0) { + LOGE("Failed to stat %s\n", pathbuf); + break; + } + if (S_ISDIR(st.st_mode)) { + rc = rmtree_except(pathbuf, NULL); + if (rc != 0) + break; + rc = rmdir(pathbuf); + } + else { + rc = unlink(pathbuf); + } + if (rc != 0) { + LOGI("Failed to remove %s: %s\n", pathbuf, strerror(errno)); + break; + } + } + closedir(dp); + return rc; +} + +int format_volume(const char* volume, const char* directory, bool force) { + if (strcmp(volume, "media") == 0) { + if (!vdc->isEmulatedStorage()) { + return 0; + } + if (ensure_path_mounted("/data") != 0) { + LOGE("format_volume failed to mount /data\n"); + return -1; + } + remount_no_selinux("/data"); + int rc = 0; + rc = rmtree_except("/data/media", NULL); + ensure_path_unmounted("/data"); + return rc; + } + Volume* v = volume_for_path(volume); if (v == NULL) { LOGE("unknown volume \"%s\"\n", volume); @@ -191,11 +426,52 @@ int format_volume(const char* volume, const char* directory) { return -1; } + if (!force && strcmp(volume, "/data") == 0 && vdc->isEmulatedStorage()) { + if (ensure_path_mounted("/data") == 0) { + remount_no_selinux("/data"); + // Preserve .layout_version to avoid "nesting bug" + LOGI("Preserving layout version\n"); + unsigned char layout_buf[256]; + ssize_t layout_buflen = -1; + int fd; + fd = open("/data/.layout_version", O_RDONLY); + if (fd != -1) { + layout_buflen = read(fd, layout_buf, sizeof(layout_buf)); + close(fd); + } + + int rc = rmtree_except("/data", "media"); + + // Restore .layout_version + if (layout_buflen > 0) { + LOGI("Restoring layout version\n"); + fd = open("/data/.layout_version", O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd != -1) { + write(fd, layout_buf, layout_buflen); + close(fd); + } + } + + ensure_path_unmounted(volume); + + return rc; + } + else { + LOGE("format_volume failed to mount /data\n"); + return -1; + } + } + if (ensure_path_unmounted(volume) != 0) { LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point); return -1; } + if (fs_mgr_is_voldmanaged(v)) { + LOGE("can't format vold volume \"%s\"", volume); + return -1; + } + if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) { mtd_scan_partitions(); const MtdPartition* partition = mtd_find_partition_by_name(v->blk_device); @@ -243,24 +519,24 @@ int format_volume(const char* volume, const char* directory) { if (strcmp(v->fs_type, "ext4") == 0) { result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory); } else { /* Has to be f2fs because we checked earlier. */ - if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) { - LOGE("format_volume: crypt footer + negative length (%zd) not supported on %s\n", length, v->fs_type); - return -1; - } + char bytes_reserved[20], num_sectors[20]; + const char* f2fs_argv[6] = {"mkfs.f2fs", "-t1"}; if (length < 0) { - LOGE("format_volume: negative length (%zd) not supported on %s\n", length, v->fs_type); - return -1; - } - char *num_sectors; - if (asprintf(&num_sectors, "%zd", length / 512) <= 0) { - LOGE("format_volume: failed to create %s command for %s\n", v->fs_type, v->blk_device); - return -1; + snprintf(bytes_reserved, sizeof(bytes_reserved), "%zd", -length); + f2fs_argv[2] = "-r"; + f2fs_argv[3] = bytes_reserved; + f2fs_argv[4] = v->blk_device; + f2fs_argv[5] = NULL; + } else { + /* num_sectors can be zero which mean whole device space */ + snprintf(num_sectors, sizeof(num_sectors), "%zd", length / 512); + f2fs_argv[2] = v->blk_device; + f2fs_argv[3] = num_sectors; + f2fs_argv[4] = NULL; } const char *f2fs_path = "/sbin/mkfs.f2fs"; - const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, NULL}; result = exec_cmd(f2fs_path, (char* const*)f2fs_argv); - free(num_sectors); } if (result != 0) { LOGE("format_volume: make %s failed on %s with %d(%s)\n", v->fs_type, v->blk_device, result, strerror(errno)); @@ -273,8 +549,8 @@ int format_volume(const char* volume, const char* directory) { return -1; } -int format_volume(const char* volume) { - return format_volume(volume, NULL); +int format_volume(const char* volume, bool force) { + return format_volume(volume, NULL, force); } int setup_install_mounts() { @@ -293,7 +569,13 @@ int setup_install_mounts() { } } else { - if (ensure_path_unmounted(v->mount_point) != 0) { + // datamedia and anything managed by vold must be unmounted + // with the detach flag to ensure that FUSE works. + bool detach = false; + if (vdc->isEmulatedStorage() && strcmp(v->mount_point, "/data") == 0) { + detach = true; + } + if (ensure_volume_unmounted(v, detach) != 0) { LOGE("failed to unmount %s\n", v->mount_point); return -1; } @@ -18,6 +18,7 @@ #define RECOVERY_ROOTS_H_ #include "common.h" +#include <fs_mgr.h> // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); @@ -27,28 +28,40 @@ Volume* volume_for_path(const char* path); // Make sure that the volume 'path' is on is mounted. Returns 0 on // success (volume is mounted). -int ensure_path_mounted(const char* path); +int ensure_volume_mounted(Volume* v, bool force_rw=false); +int ensure_path_mounted(const char* path, bool force_rw=false); +// Above, plus override SELinux default context +int remount_no_selinux(const char* path); // Similar to ensure_path_mounted, but allows one to specify the mount_point. -int ensure_path_mounted_at(const char* path, const char* mount_point); +int ensure_path_mounted_at(const char* path, const char* mount_point, bool force_rw=false); // Make sure that the volume 'path' is on is unmounted. Returns 0 on // success (volume is unmounted); -int ensure_path_unmounted(const char* path); +int ensure_volume_unmounted(Volume *v, bool detach=false); +int ensure_path_unmounted(const char* path, bool detach=false); // Reformat the given volume (must be the mount point only, eg // "/cache"), no paths permitted. Attempts to unmount the volume if // it is mounted. -int format_volume(const char* volume); +int format_volume(const char* volume, bool force = false); // Reformat the given volume (must be the mount point only, eg // "/cache"), no paths permitted. Attempts to unmount the volume if // it is mounted. // Copies 'directory' to root of the newly formatted volume -int format_volume(const char* volume, const char* directory); +int format_volume(const char* volume, const char* directory, bool force = false); // Ensure that all and only the volumes that packages expect to find // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); +int get_num_volumes(); + +bool volume_is_mountable(Volume *v); +bool volume_is_readonly(Volume *v); +bool volume_is_verity(Volume *v); + +#define MAX_NUM_MANAGED_VOLUMES 10 + #endif // RECOVERY_ROOTS_H_ diff --git a/screen_ui.cpp b/screen_ui.cpp index 2a0769e4..a4dd390b 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -40,6 +40,7 @@ #include "minui/minui.h" #include "screen_ui.h" #include "ui.h" +#include "cutils/properties.h" #define TEXT_INDENT 4 @@ -60,6 +61,8 @@ ScreenRecoveryUI::ScreenRecoveryUI() : progressScopeSize(0), progress(0), pagesIdentical(false), + log_text_cols_(0), + log_text_rows_(0), text_cols_(0), text_rows_(0), text_(nullptr), @@ -69,17 +72,30 @@ ScreenRecoveryUI::ScreenRecoveryUI() : show_text(false), show_text_ever(false), menu_(nullptr), + menu_headers_(nullptr), + header_items(0), show_menu(false), menu_items(0), menu_sel(0), + menu_show_start_(0), + sysbar_state(0), file_viewer_text_(nullptr), + progressCondition(PTHREAD_COND_INITIALIZER), intro_frames(0), loop_frames(0), animation_fps(30), // TODO: there's currently no way to infer this. stage(-1), max_stage(-1), updateMutex(PTHREAD_MUTEX_INITIALIZER), - rtl_locale(false) { + rtl_locale(false), + rainbow(false), + wrap_count(0) { + + headerIcon = nullptr; + sysbarBackIcon = nullptr; + sysbarBackHighlightIcon = nullptr; + sysbarHomeIcon = nullptr; + sysbarHomeHighlightIcon = nullptr; } GRSurface* ScreenRecoveryUI::GetCurrentFrame() { @@ -231,13 +247,13 @@ void ScreenRecoveryUI::SetColor(UIElement e) { break; case MENU: case MENU_SEL_BG: - gr_color(0, 106, 157, 255); + gr_color(84, 81, 80, 255); break; case MENU_SEL_BG_ACTIVE: - gr_color(0, 156, 100, 255); + gr_color(138, 135, 134, 255); break; case MENU_SEL_FG: - gr_color(255, 255, 255, 255); + gr_color(22, 124, 128, 255); break; case LOG: gr_color(196, 196, 196, 255); @@ -280,63 +296,129 @@ static const char* LONG_PRESS_HELP[] = { NULL }; +int ScreenRecoveryUI::draw_header_icon() +{ + GRSurface* surface = headerIcon; + int iw = header_width_; + int ih = header_height_; + int ix = (gr_fb_width() - iw) / 2; + int iy = 0; + gr_blit(surface, 0, 0, iw, ih, ix, iy); + return ih; +} + +void ScreenRecoveryUI::draw_menu_item(int textrow, const char *text, int selected) +{ + if (selected) { + SetColor(MENU_SEL_BG); + gr_fill(0, (textrow) * char_height_, + gr_fb_width(), (textrow+3) * char_height_ - 1); + SetColor(MENU_SEL_FG); + gr_text(4, (textrow+1) * char_height_ - 1, text, 0); + SetColor(MENU); + } + else { + SetColor(MENU); + gr_text(4, (textrow+1) * char_height_ - 1, text, 0); + } +} + +void ScreenRecoveryUI::draw_sysbar() +{ + GRSurface* surface; + int sw = gr_fb_width(); + int sh = gr_fb_height(); + int iw; + int ih; + SetColor(TEXT_FILL); + gr_fill(0, sh - sysbar_height_, sw, sh); + + // Left third is back button + if (!HasBackKey()) { + if (sysbar_state & SYSBAR_BACK) { + surface = sysbarBackHighlightIcon; + } + else { + surface = sysbarBackIcon; + } + iw = gr_get_width(surface); + ih = gr_get_height(surface); + gr_blit(surface, 0, 0, iw, ih, + 1 * (sw / 6) - (iw / 2), sh - ih); + } + + // Middle third is home button + if (!HasHomeKey()) { + if (sysbar_state & SYSBAR_HOME) { + surface = sysbarHomeHighlightIcon; + } + else { + surface = sysbarHomeIcon; + } + iw = gr_get_width(surface); + ih = gr_get_height(surface); + gr_blit(surface, 0, 0, iw, ih, + 3 * (sw / 6) - (iw / 2), sh - ih); + } +} + // Redraw everything on the screen. Does not flip pages. // Should only be called with updateMutex locked. void ScreenRecoveryUI::draw_screen_locked() { - if (!show_text) { - draw_background_locked(); - draw_foreground_locked(); - } else { - gr_color(0, 0, 0, 255); - gr_clear(); + draw_background_locked(); + + if (show_text) { + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + size_t y = currentIcon == INSTALLING_UPDATE + ? 3 * gr_fb_height() / 4 : header_height_ + 4; + + SetColor(LOG); + int cx, cy; + gr_set_font("log"); + gr_font_size(&cx, &cy); + // display from the bottom up, until we hit the top of the + // screen or we've displayed the entire text buffer. + size_t ty, count; + int row = (text_row_ + log_text_rows_ - 1) % log_text_rows_; + for (ty = gr_fb_height() - cy, count = 0; + ty > y + 2 && count < log_text_rows_; + ty -= (cy + 2), ++count) { + gr_text(4, ty, text_[row], 0); + --row; + if (row < 0) row = log_text_rows_ - 1; + } + gr_set_font("menu"); + return; + } - int y = 0; if (show_menu) { - char recovery_fingerprint[PROPERTY_VALUE_MAX]; - property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); - - SetColor(INFO); - DrawTextLine(TEXT_INDENT, &y, "Android Recovery", true); - for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(TEXT_INDENT, &y, chunk.c_str(), false); - } - DrawTextLines(TEXT_INDENT, &y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); - - SetColor(HEADER); - DrawTextLines(TEXT_INDENT, &y, menu_headers_); - - SetColor(MENU); - DrawHorizontalRule(&y); - y += 4; - for (int i = 0; i < menu_items; ++i) { - if (i == menu_sel) { - // Draw the highlight bar. - SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); - gr_fill(0, y - 2, gr_fb_width(), y + char_height_ + 2); - // Bold white text for the selected item. - SetColor(MENU_SEL_FG); - gr_text(4, y, menu_[i], true); - SetColor(MENU); - } else { - gr_text(4, y, menu_[i], false); + int i, y; + draw_header_icon(); + draw_sysbar(); + + // Divider + y = text_first_row_ * char_height_; + SetColor(MENU_SEL_FG); + gr_fill(0, y - 1, gr_fb_width(), y); + + if (header_items > 0) { + for (i = 0; i < header_items; ++i) { + draw_menu_item(text_first_row_ + 3*i, + menu_headers_[i], false); } - y += char_height_ + 4; + y = (text_first_row_ + 3*header_items) * char_height_; + SetColor(MENU_SEL_FG); + gr_fill(0, y - 1, gr_fb_width(), y); + } + int nr_items = menu_items - menu_show_start_; + if (header_items + nr_items > max_menu_rows_) + nr_items = max_menu_rows_ - header_items; + for (i = 0; i < nr_items; ++i) { + const char* text = menu_[menu_show_start_ + i]; + draw_menu_item(text_first_row_ + 3 * (header_items + i), + menu_[menu_show_start_ + i], + ((menu_show_start_ + i) == menu_sel)); } - DrawHorizontalRule(&y); - } - - // display from the bottom up, until we hit the top of the - // screen, the bottom of the menu, or we've displayed the - // entire text buffer. - SetColor(LOG); - int row = (text_top_ + text_rows_ - 1) % text_rows_; - size_t count = 0; - for (int ty = gr_fb_height() - char_height_; - ty >= y && count < text_rows_; - ty -= char_height_, ++count) { - gr_text(0, ty, text_[row], false); - --row; - if (row < 0) row = text_rows_ - 1; } } } @@ -344,20 +426,9 @@ void ScreenRecoveryUI::draw_screen_locked() { // Redraw everything on the screen and flip the screen (make it visible). // Should only be called with updateMutex locked. void ScreenRecoveryUI::update_screen_locked() { - draw_screen_locked(); - gr_flip(); -} - -// Updates only the progress bar, if possible, otherwise redraws the screen. -// Should only be called with updateMutex locked. -void ScreenRecoveryUI::update_progress_locked() { - if (show_text || !pagesIdentical) { - draw_screen_locked(); // Must redraw the whole screen - pagesIdentical = true; - } else { - draw_foreground_locked(); // Draw only the progress bar and overlays - } - gr_flip(); + update_waiting = true; + pthread_cond_signal(&progressCondition); + LOGV("%s: %p\n", __func__, __builtin_return_address(0)); } // Keeps the progress bar updated, even when the process is otherwise busy. @@ -366,17 +437,26 @@ void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { return nullptr; } +void ScreenRecoveryUI::OMGRainbows() +{ + rainbow = rainbow ? false : true; + set_rainbow_mode(rainbow); + property_set("sys.rainbow.recovery", rainbow ? "1" : "0"); +} + void ScreenRecoveryUI::ProgressThreadLoop() { double interval = 1.0 / animation_fps; while (true) { - double start = now(); pthread_mutex_lock(&updateMutex); + if (progressBarType == EMPTY && !update_waiting) + pthread_cond_wait(&progressCondition, &updateMutex); bool redraw = false; + double start = now(); // update the installation animation, if active // skip this if we have a text overlay (too expensive to update) - if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) { + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING)) { if (!intro_done) { if (current_frame == intro_frames - 1) { intro_done = true; @@ -403,9 +483,22 @@ void ScreenRecoveryUI::ProgressThreadLoop() { } } - if (redraw) update_progress_locked(); + if (update_waiting || !pagesIdentical) { + LOGV("call draw_screen_locked\n"); + draw_screen_locked(); + if (!update_waiting) + pagesIdentical = true; + } + if (redraw) { + LOGV("call draw_foreground_locked\n"); + draw_foreground_locked(); + } + gr_flip(); + + update_waiting = false; pthread_mutex_unlock(&updateMutex); + double end = now(); // minimum of 20ms delay between frames double delay = interval - (end-start); @@ -457,19 +550,41 @@ void ScreenRecoveryUI::Init() { // Are we the large variant of our base layout? if (gr_fb_height() > PixelsFromDp(800)) ++layout_; + gr_set_font("log"); + gr_font_size(&log_char_width_, &log_char_height_); + gr_set_font("menu"); gr_font_size(&char_width_, &char_height_); - text_rows_ = gr_fb_height() / char_height_; - text_cols_ = gr_fb_width() / char_width_; - - text_ = Alloc2d(text_rows_, text_cols_ + 1); - file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); - menu_ = Alloc2d(text_rows_, text_cols_ + 1); text_col_ = text_row_ = 0; text_top_ = 1; LoadBitmap("icon_error", &error_icon); + LoadBitmap("icon_header", &headerIcon); + LoadBitmap("icon_sysbar_back", &sysbarBackIcon); + LoadBitmap("icon_sysbar_back_highlight", &sysbarBackHighlightIcon); + LoadBitmap("icon_sysbar_home", &sysbarHomeIcon); + LoadBitmap("icon_sysbar_home_highlight", &sysbarHomeHighlightIcon); + + header_height_ = gr_get_height(headerIcon); + header_width_ = gr_get_width(headerIcon); + + sysbar_height_ = gr_get_height(sysbarBackIcon); + + text_rows_ = (gr_fb_height() - sysbar_height_) / char_height_; + text_cols_ = gr_fb_width() / char_width_; + + log_text_rows_ = (gr_fb_height() - sysbar_height_) / log_char_height_; + log_text_cols_ = gr_fb_width() / log_char_width_; + + text_ = Alloc2d(log_text_rows_, log_text_cols_ + 1); + file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); + menu_ = Alloc2d(text_rows_, text_cols_ + 1); + + text_first_row_ = (header_height_ / char_height_) + 1; + menu_item_start_ = text_first_row_ * char_height_; + max_menu_rows_ = (text_rows_ - text_first_row_) / 3; + LoadBitmap("progress_empty", &progressBarEmpty); LoadBitmap("progress_fill", &progressBarFill); @@ -563,7 +678,8 @@ void ScreenRecoveryUI::SetProgressType(ProgressType type) { progressScopeStart = 0; progressScopeSize = 0; progress = 0; - update_progress_locked(); + + update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -575,7 +691,8 @@ void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { progressScopeTime = now(); progressScopeDuration = seconds; progress = 0; - update_progress_locked(); + + update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -589,7 +706,7 @@ void ScreenRecoveryUI::SetProgress(float fraction) { float scale = width * progressScopeSize; if ((int) (progress * scale) != (int) (fraction * scale)) { progress = fraction; - update_progress_locked(); + update_screen_locked(); } } pthread_mutex_unlock(&updateMutex); @@ -611,13 +728,13 @@ void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) } pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { + if (log_text_rows_ > 0 && log_text_cols_ > 0) { for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col_ >= text_cols_) { + if (*ptr == '\n' || text_col_ >= log_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_; + text_row_ = (text_row_ + 1) % log_text_rows_; + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % log_text_rows_; } if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; } @@ -658,8 +775,8 @@ void ScreenRecoveryUI::ClearText() { 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); + for (size_t i = 0; i < log_text_rows_; ++i) { + memset(text_[i], 0, log_text_cols_ + 1); } pthread_mutex_unlock(&updateMutex); } @@ -739,11 +856,29 @@ void ScreenRecoveryUI::ShowFile(const char* filename) { text_top_ = old_text_top; } +void ScreenRecoveryUI::SetSysbarState(int state) +{ + if (HasBackKey()) { + state &= ~SYSBAR_BACK; + } + if (HasHomeKey()) { + state &= ~SYSBAR_HOME; + } + sysbar_state = state; + Redraw(); +} + void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items, int initial_selection) { pthread_mutex_lock(&updateMutex); if (text_rows_ > 0 && text_cols_ > 0) { + header_items = 0; menu_headers_ = headers; + if (menu_headers_) { + while (menu_headers_[header_items]) { + ++header_items; + } + } size_t i = 0; for (; i < text_rows_ && items[i] != nullptr; ++i) { strncpy(menu_[i], items[i], text_cols_ - 1); @@ -752,22 +887,59 @@ void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const menu_items = i; show_menu = true; menu_sel = initial_selection; + if (menu_show_start_ <= menu_sel - max_menu_rows_ || + menu_show_start_ > menu_sel) { + menu_show_start_ = menu_sel; + } update_screen_locked(); } pthread_mutex_unlock(&updateMutex); } -int ScreenRecoveryUI::SelectMenu(int sel) { +int ScreenRecoveryUI::SelectMenu(int sel, bool abs /* = false */) { + int wrapped = 0; pthread_mutex_lock(&updateMutex); + if (abs) { + sel += menu_show_start_ - header_items; + } if (show_menu) { int old_sel = menu_sel; menu_sel = sel; // Wrap at top and bottom. - if (menu_sel < 0) menu_sel = menu_items - 1; - if (menu_sel >= menu_items) menu_sel = 0; - + if (rainbow) { + if (menu_sel > old_sel) { + move_rainbow(1); + } else if (menu_sel < old_sel) { + move_rainbow(-1); + } + } + if (menu_sel < 0) { + wrapped = -1; + menu_sel = menu_items + menu_sel; + } + if (menu_sel >= menu_items) { + wrapped = 1; + menu_sel = menu_sel - menu_items; + } + if (menu_sel < menu_show_start_ && menu_show_start_ > 0) { + menu_show_start_ = menu_sel; + } + if (menu_sel - menu_show_start_ >= max_menu_rows_ - header_items) { + menu_show_start_ = menu_sel - (max_menu_rows_ - header_items) + 1; + } sel = menu_sel; + if (wrapped != 0) { + if (wrap_count / wrapped > 0) { + wrap_count += wrapped; + } else { + wrap_count = wrapped; + } + if (wrap_count / wrapped >= 5) { + wrap_count = 0; + OMGRainbows(); + } + } if (menu_sel != old_sel) update_screen_locked(); } pthread_mutex_unlock(&updateMutex); @@ -778,7 +950,6 @@ void ScreenRecoveryUI::EndMenu() { pthread_mutex_lock(&updateMutex); if (show_menu && text_rows_ > 0 && text_cols_ > 0) { show_menu = false; - update_screen_locked(); } pthread_mutex_unlock(&updateMutex); } diff --git a/screen_ui.h b/screen_ui.h index 89877577..caf6612b 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -23,11 +23,15 @@ #include "ui.h" #include "minui/minui.h" +#define SYSBAR_BACK 0x01 +#define SYSBAR_HOME 0x02 + // 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(); + virtual ~ScreenRecoveryUI() { } void Init(); void SetLocale(const char* locale); @@ -53,10 +57,17 @@ class ScreenRecoveryUI : public RecoveryUI { void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3); void ShowFile(const char* filename); + // sysbar + int GetSysbarHeight() { return gr_get_height(sysbarBackHighlightIcon); } + int GetSysbarState() { return sysbar_state; } + void SetSysbarState(int state); + // menu display + virtual int MenuItemStart() const { return menu_item_start_; } + virtual int MenuItemHeight() const { return 3 * char_height_; } void StartMenu(const char* const * headers, const char* const * items, int initial_selection); - int SelectMenu(int sel); + int SelectMenu(int sel, bool abs = false); void EndMenu(); void KeyLongPress(int); @@ -90,6 +101,11 @@ class ScreenRecoveryUI : public RecoveryUI { GRSurface** introFrames; GRSurface** loopFrames; + GRSurface* headerIcon; + GRSurface* sysbarBackIcon; + GRSurface* sysbarBackHighlightIcon; + GRSurface* sysbarHomeIcon; + GRSurface* sysbarHomeHighlightIcon; GRSurface* progressBarEmpty; GRSurface* progressBarFill; GRSurface* stageMarkerEmpty; @@ -103,6 +119,7 @@ class ScreenRecoveryUI : public RecoveryUI { // true when both graphics pages are the same (except for the progress bar). bool pagesIdentical; + size_t log_text_cols_, log_text_rows_; size_t text_cols_, text_rows_; // Log text overlay, displayed when a magic key is pressed. @@ -114,13 +131,22 @@ class ScreenRecoveryUI : public RecoveryUI { char** menu_; const char* const* menu_headers_; + int header_items; bool show_menu; int menu_items, menu_sel; + int menu_show_start_; + int max_menu_rows_; + + int sysbar_state; + // An alternate text screen, swapped with 'text_' when we're viewing a log file. char** file_viewer_text_; + int menu_item_start_; + pthread_t progress_thread_; + pthread_cond_t progressCondition; // Number of intro frames and loop frames in the animation. int intro_frames; @@ -131,16 +157,29 @@ class ScreenRecoveryUI : public RecoveryUI { int stage, max_stage; - int char_width_; - int char_height_; pthread_mutex_t updateMutex; bool rtl_locale; void draw_background_locked(); void draw_foreground_locked(); + + bool rainbow; + int wrap_count; + + int log_char_height_, log_char_width_; + int char_height_, char_width_; + + int header_height_, header_width_; + int sysbar_height_; + int text_first_row_; + + bool update_waiting; + + int draw_header_icon(); + void draw_menu_item(int textrow, const char *text, int selected); + void draw_sysbar(); void draw_screen_locked(); void update_screen_locked(); - void update_progress_locked(); GRSurface* GetCurrentFrame(); GRSurface* GetCurrentText(); @@ -165,6 +204,8 @@ class ScreenRecoveryUI : public RecoveryUI { void DrawHorizontalRule(int* y); void DrawTextLine(int x, int* y, const char* line, bool bold); void DrawTextLines(int x, int* y, const char* const* lines); + + void OMGRainbows(); }; #endif // RECOVERY_UI_H diff --git a/tests/Android.mk b/tests/Android.mk index a66991b2..4509c420 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -49,6 +49,7 @@ LOCAL_STATIC_LIBRARIES := \ libmtdutils \ libbase \ libverifier \ + libcrypto_utils_static \ libcrypto_static \ libminui \ libminzip \ @@ -30,6 +30,7 @@ #include <cutils/properties.h> #include <cutils/android_reboot.h> +#include <cutils/properties.h> #include "common.h" #include "roots.h" @@ -38,19 +39,29 @@ #include "screen_ui.h" #include "ui.h" +#include "voldclient.h" + #define UI_WAIT_KEY_TIMEOUT_SEC 120 +/* Some extra input defines */ +#ifndef ABS_MT_ANGLE +#define ABS_MT_ANGLE 0x38 +#endif + RecoveryUI::RecoveryUI() : key_queue_len(0), key_last_down(-1), key_long_press(false), key_down_count(0), enable_reboot(true), + v_changed(0), consecutive_power_keys(0), last_key(-1), has_power_key(false), has_up_key(false), - has_down_key(false) { + has_down_key(false), + has_back_key(false), + has_home_key(false) { pthread_mutex_init(&key_queue_mutex, nullptr); pthread_cond_init(&key_queue_cond, nullptr); memset(key_pressed, 0, sizeof(key_pressed)); @@ -63,6 +74,12 @@ void RecoveryUI::OnKeyDetected(int key_code) { has_down_key = true; } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { has_up_key = true; + } else if (key_code == KEY_BACK) { + has_back_key = true; + LOGI("Detected back key, disabling virtual back button\n"); + } else if (key_code == KEY_HOMEPAGE || key_code == KEY_HOME) { + has_home_key = true; + LOGI("Detected home key, disabling virtual home button\n"); } } @@ -81,6 +98,7 @@ static void* InputThreadLoop(void*) { } void RecoveryUI::Init() { + calibrate_swipe(); ev_init(InputCallback, this); ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); @@ -94,31 +112,45 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { 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; - } + input_device* dev = NULL; + int n; + for (n = 0; n < MAX_NR_INPUT_DEVICES; ++n) { + if (input_devices[n].fd == fd) { + dev = &input_devices[n]; + break; } - } else { - rel_sum = 0; + if (input_devices[n].fd == -1) { + dev = &input_devices[n]; + memset(dev, 0, sizeof(input_device)); + dev->fd = fd; + dev->tracking_id = -1; + calibrate_touch(dev); + setup_vkeys(dev); + break; + } + } + if (!dev) { + LOGE("input_callback: no more available input devices\n"); + return -1; + } + + if (ev.type != EV_REL) { + dev->rel_sum = 0; } - if (ev.type == EV_KEY && ev.code <= KEY_MAX) { - ProcessKey(ev.code, ev.value); + switch (ev.type) { + case EV_SYN: + ProcessSyn(dev, ev.code, ev.value); + break; + case EV_ABS: + ProcessAbs(dev, ev.code, ev.value); + break; + case EV_REL: + ProcessRel(dev, ev.code, ev.value); + break; + case EV_KEY: + ProcessKey(dev, ev.code, ev.value); + break; } return 0; @@ -136,11 +168,14 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { // a key is registered. // // updown == 1 for key down events; 0 for key up events -void RecoveryUI::ProcessKey(int key_code, int updown) { +void RecoveryUI::ProcessKey(input_device* dev, int key_code, int updown) { bool register_key = false; bool long_press = false; bool reboot_enabled; + if (key_code > KEY_MAX) + return; + pthread_mutex_lock(&key_queue_mutex); key_pressed[key_code] = updown; if (updown) { @@ -174,6 +209,9 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { break; case RecoveryUI::REBOOT: +#ifndef VERIFIER_TEST + vdc->unmountAll(); +#endif if (reboot_enabled) { property_set(ANDROID_RB_PROPERTY, "reboot,"); while (true) { pause(); } @@ -187,6 +225,146 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { } } +void RecoveryUI::ProcessSyn(input_device* dev, int code, int value) { + /* + * Type A device release: + * 1. Lack of position update + * 2. BTN_TOUCH | ABS_PRESSURE | SYN_MT_REPORT + * 3. SYN_REPORT + * + * Type B device release: + * 1. ABS_MT_TRACKING_ID == -1 for "first" slot + * 2. SYN_REPORT + */ + + if (code == SYN_MT_REPORT) { + if (!dev->in_touch && (dev->saw_pos_x && dev->saw_pos_y)) { +#ifdef DEBUG_TOUCH + LOGI("process_syn: type a press\n"); +#endif + handle_press(dev); + } + dev->saw_mt_report = true; + return; + } + if (code == SYN_REPORT) { + if (dev->in_touch) { + handle_gestures(dev); + } + else { + if (dev->saw_tracking_id) { +#ifdef DEBUG_TOUCH + LOGI("process_syn: type b press\n"); +#endif + handle_press(dev); + } + } + + /* Detect release */ + if (dev->saw_mt_report) { + if (dev->in_touch && !dev->saw_pos_x && !dev->saw_pos_y) { + /* type A release */ +#ifdef DEBUG_TOUCH + LOGI("process_syn: type a release\n"); +#endif + handle_release(dev); + dev->slot_first = 0; + } + } + else { + if (dev->in_touch && dev->saw_tracking_id && dev->tracking_id == -1 && + dev->slot_current == dev->slot_first) { + /* type B release */ +#ifdef DEBUG_TOUCH + LOGI("process_syn: type b release\n"); +#endif + handle_release(dev); + dev->slot_first = 0; + } + } + + dev->saw_pos_x = dev->saw_pos_y = false; + dev->saw_mt_report = dev->saw_tracking_id = false; + } +} + +void RecoveryUI::ProcessAbs(input_device* dev, int code, int value) { + if (code == ABS_MT_SLOT) { + dev->slot_current = value; + if (dev->slot_first == -1) { + dev->slot_first = value; + } + return; + } + if (code == ABS_MT_TRACKING_ID) { + /* + * Some devices send an initial ABS_MT_SLOT event before switching + * to type B events, so discard any type A state related to slot. + */ + dev->saw_tracking_id = true; + dev->slot_first = dev->slot_current = 0; + + if (value != dev->tracking_id) { + dev->tracking_id = value; + if (dev->tracking_id < 0) { + dev->slot_nr_active--; + } + else { + dev->slot_nr_active++; + } + } + return; + } + /* + * For type A devices, we "lock" onto the first coordinates by ignoring + * position updates from the time we see a SYN_MT_REPORT until the next + * SYN_REPORT + * + * For type B devices, we "lock" onto the first slot seen until all slots + * are released + */ + if (dev->slot_nr_active == 0) { + /* type A */ + if (dev->saw_pos_x && dev->saw_pos_y) { + return; + } + } + else { + if (dev->slot_current != dev->slot_first) { + return; + } + } + if (code == ABS_MT_POSITION_X) { + dev->saw_pos_x = true; + dev->touch_pos.x = value * fb_dimensions.x / (dev->touch_max.x - dev->touch_min.x); + } + else if (code == ABS_MT_POSITION_Y) { + dev->saw_pos_y = true; + dev->touch_pos.y = value * fb_dimensions.y / (dev->touch_max.y - dev->touch_min.y); + } +} + +void RecoveryUI::ProcessRel(input_device* dev, int code, int value) { +#ifdef BOARD_RECOVERY_NEEDS_REL_INPUT + if (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. + dev->rel_sum += value; + if (dev->rel_sum > 3) { + ProcessKey(dev, KEY_DOWN, 1); // press down key + ProcessKey(dev, KEY_DOWN, 0); // and release it + dev->rel_sum = 0; + } else if (dev->rel_sum < -3) { + ProcessKey(dev, KEY_UP, 1); // press up key + ProcessKey(dev, KEY_UP, 0); // and release it + dev->rel_sum = 0; + } + } +#endif +} + void* RecoveryUI::time_key_helper(void* cookie) { key_timer_t* info = (key_timer_t*) cookie; info->ui->time_key(info->key_code, info->count); @@ -205,6 +383,196 @@ void RecoveryUI::time_key(int key_code, int count) { if (long_press) KeyLongPress(key_code); } +void RecoveryUI::calibrate_touch(input_device* dev) { + fb_dimensions.x = gr_fb_width(); + fb_dimensions.y = gr_fb_height(); + + struct input_absinfo info; + memset(&info, 0, sizeof(info)); + if (ioctl(dev->fd, EVIOCGABS(ABS_MT_POSITION_X), &info) == 0) { + dev->touch_min.x = info.minimum; + dev->touch_max.x = info.maximum; + dev->touch_pos.x = info.value; + } + memset(&info, 0, sizeof(info)); + if (ioctl(dev->fd, EVIOCGABS(ABS_MT_POSITION_Y), &info) == 0) { + dev->touch_min.y = info.minimum; + dev->touch_max.y = info.maximum; + dev->touch_pos.y = info.value; + } +#ifdef DEBUG_TOUCH + LOGI("calibrate_touch: fd=%d, (%d,%d)-(%d,%d) pos (%d,%d)\n", dev->fd, + dev->touch_min.x, dev->touch_min.y, + dev->touch_max.x, dev->touch_max.y, + dev->touch_pos.x, dev->touch_pos.y); +#endif +} + +void RecoveryUI::setup_vkeys(input_device* dev) { + int n; + char name[256]; + char path[PATH_MAX]; + char buf[64*MAX_NR_VKEYS]; + + for (n = 0; n < MAX_NR_VKEYS; ++n) { + dev->virtual_keys[n].keycode = -1; + } + + memset(name, 0, sizeof(name)); + if (ioctl(dev->fd, EVIOCGNAME(sizeof(name)), name) < 0) { + LOGI("setup_vkeys: no vkeys\n"); + return; + } + sprintf(path, "/sys/board_properties/virtualkeys.%s", name); + int vkfd = open(path, O_RDONLY); + if (vkfd < 0) { + LOGI("setup_vkeys: could not open %s\n", path); + return; + } + ssize_t len = read(vkfd, buf, sizeof(buf)); + close(vkfd); + if (len <= 0) { + LOGE("setup_vkeys: could not read %s\n", path); + return; + } + buf[len] = '\0'; + + char* p = buf; + char* endp; + for (n = 0; n < MAX_NR_VKEYS && p < buf+len && *p == '0'; ++n) { + int val[6]; + int f; + for (f = 0; *p && f < 6; ++f) { + val[f] = strtol(p, &endp, 0); + if (p == endp) + break; + p = endp+1; + } + if (f != 6 || val[0] != 0x01) + break; + dev->virtual_keys[n].keycode = val[1]; + dev->virtual_keys[n].min.x = val[2] - val[4]/2; + dev->virtual_keys[n].min.y = val[3] - val[5]/2; + dev->virtual_keys[n].max.x = val[2] + val[4]/2; + dev->virtual_keys[n].max.y = val[3] + val[5]/2; + +#ifdef DEBUG_TOUCH + LOGI("vkey: fd=%d, [%d]=(%d,%d)-(%d,%d)\n", dev->fd, + dev->virtual_keys[n].keycode, + dev->virtual_keys[n].min.x, dev->virtual_keys[n].min.y, + dev->virtual_keys[n].max.x, dev->virtual_keys[n].max.y); +#endif + } +} + +void RecoveryUI::calibrate_swipe() { + char strvalue[PROPERTY_VALUE_MAX]; + int intvalue; + property_get("ro.sf.lcd_density", strvalue, "160"); + intvalue = atoi(strvalue); + int screen_density = (intvalue >= 160 ? intvalue : 160); + min_swipe_px.x = screen_density * 50 / 100; // Roughly 0.5in + min_swipe_px.y = screen_density * 30 / 100; // Roughly 0.3in +#ifdef DEBUG_TOUCH + LOGI("calibrate_swipe: density=%d, min_swipe=(%d,%d)\n", + screen_density, min_swipe_px.x, min_swipe_px.y); +#endif +} + +void RecoveryUI::handle_press(input_device* dev) { + dev->touch_start = dev->touch_track = dev->touch_pos; + dev->in_touch = true; + dev->in_swipe = false; + if (dev->touch_pos.y >= gr_fb_height() - GetSysbarHeight()) { + SetSysbarState(1 << (3 * dev->touch_pos.x / gr_fb_width())); + } + else { + SetSysbarState(0); + } +} + +void RecoveryUI::handle_release(input_device* dev) { + struct point diff = dev->touch_pos - dev->touch_start; + bool in_touch = dev->in_touch; + bool in_swipe = dev->in_swipe; + + dev->in_touch = dev->in_swipe = false; + + if (!in_swipe) { + int n; + for (n = 0; dev->virtual_keys[n].keycode != -1 && n < MAX_NR_VKEYS; ++n) { + vkey* vk = &dev->virtual_keys[n]; + if (dev->touch_start.x >= vk->min.x && dev->touch_start.x < vk->max.x && + dev->touch_start.y >= vk->min.y && dev->touch_start.y < vk->max.y) { +#ifdef DEBUG_TOUCH + LOGI("handle_release: vkey %d\n", vk->keycode); +#endif + EnqueueKey(vk->keycode); + return; + } + } + + int sysbar_state = GetSysbarState(); + SetSysbarState(0); + if (sysbar_state == SYSBAR_BACK) { + ProcessKey(dev, KEY_BACK, 1); + ProcessKey(dev, KEY_BACK, 0); + return; + } + if (sysbar_state == SYSBAR_HOME) { + ProcessKey(dev, KEY_HOME, 1); + ProcessKey(dev, KEY_HOME, 0); + return; + } + } + + if (in_swipe) { + if (abs(diff.x) > abs(diff.y)) { + if (abs(diff.x) > min_swipe_px.x) { + int key = (diff.x > 0 ? KEY_ENTER : KEY_BACK); + ProcessKey(dev, key, 1); + ProcessKey(dev, key, 0); + } + } + else { + /* Vertical swipe, handled realtime */ + } + } + else { + int sel, start_menu_pos; + // Make sure touch pos is not less than menu start pos. + // No need to check if beyond end of menu items, since + // that is checked by get_menu_selection(). + start_menu_pos = MenuItemStart(); + if (dev->touch_pos.y >= start_menu_pos) { + sel = (dev->touch_pos.y - start_menu_pos)/MenuItemHeight(); + EnqueueKey(KEY_FLAG_ABS | sel); + } + } +} + +void RecoveryUI::handle_gestures(input_device* dev) { + struct point diff; + diff = dev->touch_pos - dev->touch_start; + + if (abs(diff.x) > abs(diff.y)) { + if (abs(diff.x) > min_swipe_px.x) { + /* Horizontal swipe, handle it on release */ + dev->in_swipe = true; + } + } + else { + diff.y = dev->touch_pos.y - dev->touch_track.y; + if (abs(diff.y) > MenuItemHeight()) { + dev->in_swipe = true; + dev->touch_track = dev->touch_pos; + int key = (diff.y < 0) ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; + ProcessKey(dev, key, 1); + ProcessKey(dev, key, 0); + } + } +} + void RecoveryUI::EnqueueKey(int key_code) { pthread_mutex_lock(&key_queue_mutex); const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); @@ -217,6 +585,7 @@ void RecoveryUI::EnqueueKey(int key_code) { int RecoveryUI::WaitKey() { pthread_mutex_lock(&key_queue_mutex); + int timeouts = UI_WAIT_KEY_TIMEOUT_SEC; // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is // plugged in. @@ -226,13 +595,18 @@ int RecoveryUI::WaitKey() { gettimeofday(&now, nullptr); timeout.tv_sec = now.tv_sec; timeout.tv_nsec = now.tv_usec * 1000; - timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; + timeout.tv_sec += 1; int rc = 0; while (key_queue_len == 0 && rc != ETIMEDOUT) { rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + if (VolumesChanged()) { + pthread_mutex_unlock(&key_queue_mutex); + return Device::kRefresh; + } + timeouts--; } - } while (IsUsbConnected() && key_queue_len == 0); + } while ((timeouts || IsUsbConnected()) && key_queue_len == 0); int key = -1; if (key_queue_len > 0) { @@ -243,6 +617,15 @@ int RecoveryUI::WaitKey() { return key; } +void RecoveryUI::CancelWaitKey() +{ + pthread_mutex_lock(&key_queue_mutex); + key_queue[key_queue_len] = -2; + key_queue_len++; + pthread_cond_signal(&key_queue_cond); + pthread_mutex_unlock(&key_queue_mutex); +} + bool RecoveryUI::IsUsbConnected() { int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); if (fd < 0) { @@ -337,3 +720,10 @@ void RecoveryUI::SetEnableReboot(bool enabled) { enable_reboot = enabled; pthread_mutex_unlock(&key_queue_mutex); } + +bool RecoveryUI::VolumesChanged() { + int ret = v_changed; + if (v_changed > 0) + v_changed = 0; + return ret == 1; +} @@ -21,6 +21,74 @@ #include <pthread.h> #include <time.h> +#include "voldclient.h" + +#define MAX_NR_INPUT_DEVICES 8 +#define MAX_NR_VKEYS 8 + +/* + * Simple representation of a (x,y) coordinate with convenience operators + */ +struct point { + point() : x(0), y(0) {} + point operator+(const point& rhs) const { + point tmp; + tmp.x = x + rhs.x; + tmp.y = y + rhs.y; + return tmp; + } + point operator-(const point& rhs) const { + point tmp; + tmp.x = x - rhs.x; + tmp.y = y - rhs.y; + return tmp; + } + + int x; + int y; +}; + +/* + * Virtual key representation. Valid when keycode != -1. + */ +struct vkey { + vkey() : keycode(-1) {} + int keycode; + point min; + point max; +}; + +/* + * Input device representation. Valid when fd != -1. + * This holds all information and state related to a given input device. + */ +struct input_device { + input_device() : fd(-1) {} + + int fd; + vkey virtual_keys[MAX_NR_VKEYS]; + point touch_min; + point touch_max; + + int rel_sum; // Accumulated relative movement + + bool saw_pos_x; // Did sequence have ABS_MT_POSITION_X? + bool saw_pos_y; // Did sequence have ABS_MT_POSITION_Y? + bool saw_mt_report; // Did sequence have SYN_MT_REPORT? + bool saw_tracking_id; // Did sequence have SYN_TRACKING_ID? + bool in_touch; // Are we in a touch event? + bool in_swipe; // Are we in a swipe event? + + point touch_pos; // Current touch coordinates + point touch_start; // Coordinates of touch start + point touch_track; // Last tracked coordinates + + int slot_nr_active; + int slot_first; + int slot_current; + int tracking_id; +}; + // Abstract class for controlling the user interface during recovery. class RecoveryUI { public: @@ -69,12 +137,16 @@ class RecoveryUI { virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; virtual void ShowFile(const char* filename) = 0; + virtual void ClearText() = 0; // --- key handling --- // Wait for a key and return it. May return -1 after timeout. virtual int WaitKey(); + // Cancel a WaitKey() + virtual void CancelWaitKey(); + virtual bool IsKeyPressed(int key); virtual bool IsLongPress(); @@ -82,6 +154,9 @@ class RecoveryUI { // of phones and tablets, false otherwise. virtual bool HasThreeButtons(); + virtual bool HasBackKey() const { return has_back_key; } + virtual bool HasHomeKey() const { return has_home_key; } + // Erase any queued-up keys. virtual void FlushKeys(); @@ -108,6 +183,13 @@ class RecoveryUI { // --- menu display --- + virtual int MenuItemStart() const = 0; + virtual int MenuItemHeight() const = 0; + + virtual int GetSysbarHeight() = 0; + virtual int GetSysbarState() = 0; + virtual void SetSysbarState(int state) = 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). @@ -116,12 +198,15 @@ class RecoveryUI { // Set the menu highlight to the given index, wrapping if necessary. // Returns the actual item selected. - virtual int SelectMenu(int sel) = 0; + virtual int SelectMenu(int sel, bool abs = false) = 0; // End menu mode, resetting the text overlay so that ui_print() // statements will be displayed. virtual void EndMenu() = 0; + // Notify of volume state change + void onVolumeChanged() { v_changed = 1; } + protected: void EnqueueKey(int key_code); @@ -136,6 +221,7 @@ private: int key_down_count; // under key_queue_mutex bool enable_reboot; // under key_queue_mutex int rel_sum; + int v_changed; int consecutive_power_keys; int last_key; @@ -143,6 +229,13 @@ private: bool has_power_key; bool has_up_key; bool has_down_key; + bool has_back_key; + bool has_home_key; + + input_device input_devices[MAX_NR_INPUT_DEVICES]; + + point fb_dimensions; + point min_swipe_px; struct key_timer_t { RecoveryUI* ui; @@ -156,12 +249,25 @@ private: static int InputCallback(int fd, uint32_t epevents, void* data); int OnInputEvent(int fd, uint32_t epevents); - void ProcessKey(int key_code, int updown); + void ProcessKey(input_device* dev, int key_code, int updown); + void ProcessSyn(input_device* dev, int code, int value); + void ProcessAbs(input_device* dev, int code, int value); + void ProcessRel(input_device* dev, int code, int value); bool IsUsbConnected(); + bool VolumesChanged(); + static void* time_key_helper(void* cookie); void time_key(int key_code, int count); + + void process_touch(int fd, struct input_event *ev); + void calibrate_touch(input_device* dev); + void setup_vkeys(input_device* dev); + void calibrate_swipe(); + void handle_press(input_device* dev); + void handle_release(input_device* dev); + void handle_gestures(input_device* dev); }; #endif // RECOVERY_UI_H diff --git a/updater/Android.mk b/updater/Android.mk index d7aa613e..e3bd34dc 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -33,15 +33,24 @@ LOCAL_CLANG := true LOCAL_SRC_FILES := $(updater_src_files) -LOCAL_STATIC_LIBRARIES += libfec libfec_rs libext4_utils_static libsquashfs_utils libcrypto_static +LOCAL_STATIC_LIBRARIES += libfec \ + libfec_rs \ + libext4_utils_static \ + libsquashfs_utils \ + libcrypto_utils_static \ + libcrypto_static -ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) -LOCAL_CFLAGS += -DUSE_EXT4 LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_C_INCLUDES += system/extras/ext4_utils LOCAL_STATIC_LIBRARIES += \ libsparse_static \ libz + +LOCAL_C_INCLUDES += external/e2fsprogs/lib +LOCAL_STATIC_LIBRARIES += libext2_blkid libext2_uuid + +ifeq ($(BOARD_SUPPRESS_EMMC_WIPE),true) + LOCAL_CFLAGS += -DSUPPRESS_EMMC_WIPE endif LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) @@ -72,7 +81,11 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. # any subsidiary static libraries required for your registered # extension libs. +ifeq ($(TARGET_ARCH),arm64) +inc := $(call intermediates-dir-for,PACKAGING,updater_extensions,,,32)/register.inc +else inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc +endif # Encode the value of TARGET_RECOVERY_UPDATER_LIBS into the filename of the dependency. # So if TARGET_RECOVERY_UPDATER_LIBS is changed, a new dependency file will be generated. @@ -94,13 +107,18 @@ $(inc) : $(inc_dep_file) $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;) $(hide) echo "}" >> $@ -$(call intermediates-dir-for,EXECUTABLES,updater,,,$(TARGET_PREFER_32_BIT))/updater.o : $(inc) +ifeq ($(TARGET_ARCH),arm64) +$(call intermediates-dir-for,EXECUTABLES,updater,,,32)/updater.o : $(inc) +else +$(call intermediates-dir-for,EXECUTABLES,updater)/updater.o : $(inc) +endif LOCAL_C_INCLUDES += $(dir $(inc)) inc := inc_dep_file := LOCAL_MODULE := updater +LOCAL_32_BIT_ONLY := true LOCAL_FORCE_STATIC_EXECUTABLE := true diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index a80180a9..7b7e3bbf 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -1334,10 +1334,12 @@ static int PerformCommandErase(CommandParameters& params) { // length in bytes blocks[1] = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * (uint64_t) BLOCKSIZE; +#ifndef SUPPRESS_EMMC_WIPE if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) { fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno)); return -1; } +#endif } } diff --git a/updater/install.cpp b/updater/install.cpp index 005f9f97..e4ad9b2b 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -33,6 +33,7 @@ #include <sys/xattr.h> #include <linux/xattr.h> #include <inttypes.h> +#include <blkid/blkid.h> #include <memory> #include <vector> @@ -56,11 +57,8 @@ #include "updater.h" #include "install.h" #include "tune2fs.h" - -#ifdef USE_EXT4 #include "make_ext4fs.h" #include "wipe.h" -#endif // Send over the buffer to recovery though the command pipe. static void uiPrint(State* state, const std::string& buffer) { @@ -189,6 +187,16 @@ Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { } result = mount_point; } else { + char *detected_fs_type = blkid_get_tag_value(NULL, "TYPE", location); + if (detected_fs_type) { + uiPrintf(state, "detected filesystem %s for %s\n", + detected_fs_type, location); + fs_type = detected_fs_type; + } else { + uiPrintf(state, "could not detect filesystem for %s, assuming %s\n", + location, fs_type); + } + if (mount(location, mount_point, fs_type, MS_NOATIME | MS_NODEV | MS_NODIRATIME, has_mount_options ? mount_options : "") < 0) { @@ -361,7 +369,6 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { goto done; } result = location; -#ifdef USE_EXT4 } else if (strcmp(fs_type, "ext4") == 0) { int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle); if (status != 0) { @@ -389,7 +396,6 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { goto done; } result = location; -#endif } else { printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"", name, fs_type, partition_type); @@ -966,6 +972,74 @@ Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(strdup(value)); } +//Check to confirm if this is the same hardware as the one the package was +//generated on or not. 32 vs 64 bit variants are upgrade compatible but have +//names such as msmWXYZ msmWXYZ_32 vs msmWXYZ_64.Input to this +//function is the BuildProp value that gets stored in the update package +//at the time it it created. +Value* ConfirmDevVariant(const char* name, State* state, int argc, Expr* argv[]) +{ + //ro.product.device that was on the build that the update package was made + //from + char* package_dev_variant; + //ro.product.device on the current hardware + char current_dev_variant[PROPERTY_VALUE_MAX]; + int comparison_len; + int package_dev_variant_len; + int current_dev_variant_len; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + package_dev_variant = Evaluate(state, argv[0]); + if (!package_dev_variant) goto error; + property_get("ro.product.device", current_dev_variant, "n/a"); + if (!strncmp(current_dev_variant,"n/a",3)) { + ErrorAbort(state, "Failed to get valid ro.product.device"); + goto error; + } + package_dev_variant_len = strlen(package_dev_variant); + current_dev_variant_len = strlen(current_dev_variant); + //Ensure device variant lengths are atleast 3 characters long + if ((package_dev_variant_len < 3) || (current_dev_variant_len < 3)) { + ErrorAbort(state, "Device Variant length is less than 3 characters"); + goto error; + } + //Length of the largest string - 3(for _32/64) + comparison_len = + (package_dev_variant_len >= current_dev_variant_len ? + package_dev_variant_len : + current_dev_variant_len) - 3; + //Complete match + if (!strncmp(current_dev_variant, package_dev_variant, + strlen(current_dev_variant))) + goto success; + //Match except for the last 3 char's of either string which are _32 or _64 + if (!strncmp(current_dev_variant, package_dev_variant, comparison_len)) { + if (package_dev_variant_len >= current_dev_variant_len) { + if (!strncmp(&package_dev_variant[package_dev_variant_len-3], + "_32", 3) || + !strncmp(&package_dev_variant[package_dev_variant_len-3], + "_64", 3)) + goto success; + } else { + if (!strncmp(¤t_dev_variant[current_dev_variant_len-3], + "_32", 3) || + !strncmp(¤t_dev_variant[current_dev_variant_len-3], + "_64", 3)) + goto success; + } + ErrorAbort(state, "Invalid target for update package"); + goto error; + } +success: + free(package_dev_variant); + return StringValue(strdup("OK")); +error: + if (package_dev_variant) { + free(package_dev_variant); + } + return StringValue(strdup("ERROR")); +} // file_getprop(file, key) // @@ -1433,10 +1507,11 @@ Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { // current package (because nothing has cleared the copy of the // arguments stored in the BCB). // -// The argument is the partition name passed to the android reboot -// property. It can be "recovery" to boot from the recovery -// partition, or "" (empty string) to boot from the regular boot -// partition. +// The first argument is the block device for the misc partition +// ("/misc" in the fstab). The second argument is the argument +// passed to the android reboot property. It can be "recovery" to +// boot from the recovery partition, or "" (empty string) to boot +// from the regular boot partition. Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 2) { return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); @@ -1452,6 +1527,7 @@ Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command)); FILE* f = fopen(filename, "r+b"); fseek(f, offsetof(struct bootloader_message, command), SEEK_SET); + fseek(f, BOOTLOADER_MESSAGE_OFFSET_IN_MISC, SEEK_CUR); ota_fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f); fclose(f); free(filename); @@ -1464,6 +1540,11 @@ Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { property_set(ANDROID_RB_PROPERTY, buffer); sleep(5); + // Attempt to reboot using older methods in case the recovery + // that we are updating does not support init reboots + android_reboot(ANDROID_RB_RESTART, 0, 0); + + sleep(5); free(property); ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name); return NULL; @@ -1494,6 +1575,7 @@ Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) { // package installation. FILE* f = fopen(filename, "r+b"); fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); + fseek(f, BOOTLOADER_MESSAGE_OFFSET_IN_MISC, SEEK_CUR); int to_write = strlen(stagestr)+1; int max_size = sizeof(((struct bootloader_message*)0)->stage); if (to_write > max_size) { @@ -1520,6 +1602,7 @@ Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) { char buffer[sizeof(((struct bootloader_message*)0)->stage)]; FILE* f = fopen(filename, "rb"); fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); + fseek(f, BOOTLOADER_MESSAGE_OFFSET_IN_MISC, SEEK_CUR); ota_fread(buffer, sizeof(buffer), 1, f); fclose(f); buffer[sizeof(buffer)-1] = '\0'; @@ -1640,4 +1723,5 @@ void RegisterInstallFunctions() { RegisterFunction("enable_reboot", EnableRebootFn); RegisterFunction("tune2fs", Tune2FsFn); + RegisterFunction("get_device_compatible", ConfirmDevVariant); } diff --git a/voldclient.cpp b/voldclient.cpp new file mode 100644 index 00000000..0c8462a5 --- /dev/null +++ b/voldclient.cpp @@ -0,0 +1,466 @@ +/* + * Copyright (c) 2013 The CyanogenMod 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <pthread.h> + +#include <string> +#include <sstream> + +#include <cutils/properties.h> +#include <cutils/sockets.h> + +#include "common.h" +#include "roots.h" +#include "voldclient.h" + +#include "VolumeBase.h" +#include "ResponseCode.h" + +using namespace android::vold; + +VoldClient* vdc = NULL; + +static void* threadfunc(void* arg) +{ + VoldClient* self = (VoldClient*)arg; + self->run(); + return NULL; +} + +VoldClient::VoldClient(VoldWatcher* watcher /* = nullptr */) : + mRunning(false), + mSock(-1), + mSockMutex(PTHREAD_MUTEX_INITIALIZER), + mSockCond(PTHREAD_COND_INITIALIZER), + mInFlight(0), + mResult(0), + mWatcher(watcher), + mVolumeLock(PTHREAD_RWLOCK_INITIALIZER), + mVolumeChanged(false), + mEmulatedStorage(true) +{ +} + +void VoldClient::start(void) +{ + mRunning = true; + pthread_create(&mThread, NULL, threadfunc, this); + while (mSock == -1) { + sleep(1); + } + while (mInFlight != 0) { + sleep(1); + } + LOGI("VoldClient initialized, storage is %s\n", + vdc->isEmulatedStorage() ? "emulated" : "physical"); +} + +void VoldClient::stop(void) +{ + if (mRunning) { + mRunning = false; + close(mSock); + mSock = -1; + void* retval; + pthread_join(mThread, &retval); + } +} + +VolumeInfo VoldClient::getVolume(const std::string& id) +{ + pthread_rwlock_wrlock(&mVolumeLock); + VolumeInfo* info = getVolumeLocked(id); + pthread_rwlock_unlock(&mVolumeLock); + return *info; +} + +bool VoldClient::reset(void) +{ + const char *cmd[2] = { "volume", "reset" }; + return sendCommand(2, cmd); +} + +bool VoldClient::mountAll(void) +{ + bool ret = true; + pthread_rwlock_rdlock(&mVolumeLock); + for (auto& info : mVolumes) { + if (info.mState == (int)VolumeBase::State::kUnmounted) { + if (!volumeMount(info.mId)) { + ret = false; + } + } + } + pthread_rwlock_unlock(&mVolumeLock); + return ret; +} + +bool VoldClient::unmountAll(void) +{ + bool ret = true; + pthread_rwlock_rdlock(&mVolumeLock); + for (auto& info : mVolumes) { + if (info.mState == (int)VolumeBase::State::kMounted) { + if (!volumeUnmount(info.mId)) { + ret = false; + } + } + } + pthread_rwlock_unlock(&mVolumeLock); + return ret; +} + +bool VoldClient::volumeMount(const std::string& id) +{ + // Special case for emulated storage + if (id == "emulated") { + pthread_rwlock_wrlock(&mVolumeLock); + VolumeInfo* info = getVolumeLocked(id); + if (!info) { + pthread_rwlock_unlock(&mVolumeLock); + return false; + } + info->mPath = "/storage/emulated"; + info->mInternalPath = "/data/media"; + pthread_rwlock_unlock(&mVolumeLock); + return ensure_path_mounted("/data") == 0; + } + const char *cmd[3] = { "volume", "mount", id.c_str() }; + return sendCommand(3, cmd); +} + +// NB: can only force or detach, not both +bool VoldClient::volumeUnmount(const std::string& id, bool detach /* = false */) +{ + // Special case for emulated storage + if (id == "emulated") { + if (ensure_path_unmounted("/data", detach) != 0) { + return false; + } + return true; + } + const char *cmd[4] = { "volume", "unmount", id.c_str(), NULL }; + int cmdlen = 3; + if (detach) { + cmd[3] = "detach"; + cmdlen = 4; + } + return sendCommand(cmdlen, cmd); +} + +bool VoldClient::volumeFormat(const std::string& id) +{ + const char* cmd[3] = { "volume", "format", id.c_str() }; + return sendCommand(3, cmd); +} + +void VoldClient::resetVolumeState(void) +{ + pthread_rwlock_wrlock(&mVolumeLock); + mVolumes.clear(); + mVolumeChanged = false; + mEmulatedStorage = true; + pthread_rwlock_unlock(&mVolumeLock); + if (mWatcher) { + mWatcher->onVolumeChanged(); + } + const char *cmd[2] = { "volume", "reset" }; + sendCommand(2, cmd, false); +} + +VolumeInfo* VoldClient::getVolumeLocked(const std::string& id) +{ + for (auto iter = mVolumes.begin(); iter != mVolumes.end(); ++iter) { + if (iter->mId == id) { + return &(*iter); + } + } + return nullptr; +} + +bool VoldClient::sendCommand(unsigned int len, const char** command, bool wait /* = true */) +{ + char line[4096]; + char* p; + unsigned int i; + size_t sz; + bool ret = true; + + p = line; + p += sprintf(p, "0 "); /* 0 is a (now required) sequence number */ + for (i = 0; i < len; i++) { + const char* cmd = command[i]; + if (!cmd[0] || !strchr(cmd, ' ')) + p += sprintf(p, "%s", cmd); + else + p += sprintf(p, "\"%s\"", cmd); + if (i < len - 1) + *p++ = ' '; + if (p >= line + sizeof(line)) { + LOGE("vold command line too long\n"); + exit(1); + } + } + + // only one writer at a time + pthread_mutex_lock(&mSockMutex); + if (write(mSock, line, (p - line) + 1) < 0) { + LOGE("Unable to send command to vold!\n"); + pthread_mutex_unlock(&mSockMutex); + return false; + } + ++mInFlight; + + if (wait) { + while (mInFlight) { + // wait for completion + pthread_cond_wait(&mSockCond, &mSockMutex); + } + ret = (mResult >= 200 && mResult < 300); + } + pthread_mutex_unlock(&mSockMutex); + + return ret; +} + +void VoldClient::handleCommandOkay(void) +{ + bool changed = false; + pthread_rwlock_wrlock(&mVolumeLock); + if (mVolumeChanged) { + mVolumeChanged = false; + changed = true; + } + pthread_rwlock_unlock(&mVolumeLock); + if (changed) { + mWatcher->onVolumeChanged(); + } +} + +void VoldClient::handleVolumeCreated(const std::string& id, const std::string& type, + const std::string& disk, const std::string& guid) +{ + pthread_rwlock_wrlock(&mVolumeLock); + // Ignore emulated storage if primary storage is physical + if (id == "emulated") { + char value[PROPERTY_VALUE_MAX]; + property_get("ro.vold.primary_physical", value, "0"); + if (value[0] == '1' || value[0] == 'y' || !strcmp(value, "true")) { + mEmulatedStorage = false; + return; + } + mEmulatedStorage = true; + } + VolumeInfo info; + info.mId = id; + mVolumes.push_back(info); + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumeStateChanged(const std::string& id, const std::string& state) +{ + pthread_rwlock_wrlock(&mVolumeLock); + auto info = getVolumeLocked(id); + if (info) { + info->mState = atoi(state.c_str()); + } + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumeFsLabelChanged(const std::string& id, const std::string& label) +{ + pthread_rwlock_wrlock(&mVolumeLock); + auto info = getVolumeLocked(id); + if (info) { + info->mLabel = label; + } + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumePathChanged(const std::string& id, const std::string& path) +{ + pthread_rwlock_wrlock(&mVolumeLock); + auto info = getVolumeLocked(id); + if (info) { + info->mPath = path; + } + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumeInternalPathChanged(const std::string& id, const std::string& path) +{ + pthread_rwlock_wrlock(&mVolumeLock); + auto info = getVolumeLocked(id); + if (info) { + info->mInternalPath = path; + } + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumeDestroyed(const std::string& id) +{ + pthread_rwlock_wrlock(&mVolumeLock); + for (auto iter = mVolumes.begin(); iter != mVolumes.end(); ++iter) { + if (iter->mId == id) { + mVolumes.erase(iter); + break; + } + } + pthread_rwlock_unlock(&mVolumeLock); +} + +static std::vector<std::string> split(const std::string& line) +{ + std::vector<std::string> tokens; + const char* tok = line.c_str(); + + while (*tok) { + unsigned int toklen; + const char* next; + if (*tok == '"') { + ++tok; + const char* q = strchr(tok, '"'); + if (!q) { + LOGE("vold line <%s> malformed\n", line.c_str()); + exit(1); + } + toklen = q - tok; + next = q + 1; + if (*next) { + if (*next != ' ') { + LOGE("vold line <%s> malformed\n", line.c_str()); + exit(0); + } + ++next; + } + } + else { + next = strchr(tok, ' '); + if (next) { + toklen = next - tok; + ++next; + } + else { + toklen = strlen(tok); + next = tok + toklen; + } + } + tokens.push_back(std::string(tok, toklen)); + tok = next; + } + + return tokens; +} + +void VoldClient::dispatch(const std::string& line) +{ + std::vector<std::string> tokens = split(line); + + switch (mResult) { + case ResponseCode::CommandOkay: + handleCommandOkay(); + break; + case ResponseCode::VolumeCreated: + handleVolumeCreated(tokens[1], tokens[2], tokens[3], tokens[4]); + break; + case ResponseCode::VolumeStateChanged: + handleVolumeStateChanged(tokens[1], tokens[2]); + break; + case ResponseCode::VolumeFsLabelChanged: + handleVolumeFsLabelChanged(tokens[1], tokens[2]); + break; + case ResponseCode::VolumePathChanged: + handleVolumePathChanged(tokens[1], tokens[2]); + break; + case ResponseCode::VolumeInternalPathChanged: + handleVolumeInternalPathChanged(tokens[1], tokens[2]); + break; + case ResponseCode::VolumeDestroyed: + handleVolumeDestroyed(tokens[1]); + break; + } +} + +void VoldClient::run(void) +{ + LOGI("VoldClient thread starting\n"); + while (mRunning) { + if (mSock == -1) { + LOGI("Connecting to Vold...\n"); + mSock = socket_local_client("vold", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + if (mSock == -1) { + sleep(1); + continue; + } + resetVolumeState(); + } + + int rc; + + struct timeval tv; + fd_set rfds; + + memset(&tv, 0, sizeof(tv)); + tv.tv_usec = 100 * 1000; + FD_ZERO(&rfds); + FD_SET(mSock, &rfds); + + rc = select(mSock + 1, &rfds, NULL, NULL, &tv); + if (rc <= 0) { + if (rc < 0 && errno != EINTR) { + LOGE("vdc: error in select (%s)\n", strerror(errno)); + close(mSock); + mSock = -1; + } + continue; + } + + char buf[4096]; + memset(buf, 0, sizeof(buf)); + rc = read(mSock, buf, sizeof(buf) - 1); + if (rc <= 0) { + LOGE("vdc: read failed: %s\n", (rc == 0 ? "EOF" : strerror(errno))); + close(mSock); + mSock = -1; + continue; + } + + // dispatch each line of the response + int nread = rc; + int off = 0; + while (off < nread) { + char* eol = (char*)memchr(buf + off, 0, nread - off); + if (!eol) { + break; + } + mResult = atoi(buf + off); + dispatch(std::string(buf + off)); + if (mResult >= 200 && mResult < 600) { + pthread_mutex_lock(&mSockMutex); + --mInFlight; + pthread_cond_signal(&mSockCond); + pthread_mutex_unlock(&mSockMutex); + } + off = (eol - buf) + 1; + } + } +} diff --git a/voldclient.h b/voldclient.h new file mode 100644 index 00000000..c5f568e9 --- /dev/null +++ b/voldclient.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013 The CyanogenMod 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 _VOLD_CLIENT_H +#define _VOLD_CLIENT_H + +#include <sys/types.h> +#include <pthread.h> + +#include <string> +#include <vector> + +class VoldWatcher { +public: + virtual ~VoldWatcher(void) {} + virtual void onVolumeChanged(void) = 0; +}; + +class VolumeInfo { +public: + std::string mId; + std::string mLabel; + std::string mPath; + std::string mInternalPath; + int mState; +}; + +class VoldClient +{ +public: + VoldClient(VoldWatcher* watcher = nullptr); + + void start(void); + void stop(void); + + std::vector<VolumeInfo> getVolumes(void) { return mVolumes; } + VolumeInfo getVolume(const std::string& id); + + bool isEmulatedStorage(void) { return mEmulatedStorage; } + + bool reset(void); + bool mountAll(void); + bool unmountAll(void); + + bool volumeMount(const std::string& id); + bool volumeUnmount(const std::string& id, bool detach = false); + bool volumeFormat(const std::string& id); + bool volumeAvailable(const std::string& id); + +private: + void resetVolumeState(void); + + VolumeInfo* getVolumeLocked(const std::string& id); + bool sendCommand(unsigned int len, const char** command, bool wait = true); + + void handleCommandOkay(void); + void handleVolumeCreated(const std::string& id, const std::string& type, + const std::string& disk, const std::string& guid); + void handleVolumeStateChanged(const std::string& id, const std::string& state); + void handleVolumeFsLabelChanged(const std::string& id, const std::string& label); + void handleVolumePathChanged(const std::string& id, const std::string& path); + void handleVolumeInternalPathChanged(const std::string& id, const std::string& path); + void handleVolumeDestroyed(const std::string& id); + + void dispatch(const std::string& line); + +public: + void run(void); // INTERNAL + +private: + bool mRunning; + int mSock; + pthread_t mThread; + pthread_mutex_t mSockMutex; + pthread_cond_t mSockCond; + unsigned int mInFlight; + unsigned int mResult; + VoldWatcher* mWatcher; + pthread_rwlock_t mVolumeLock; + bool mVolumeChanged; + bool mEmulatedStorage; + std::vector<VolumeInfo> mVolumes; +}; + +extern VoldClient* vdc; + +#endif + diff --git a/wear_ui.cpp b/wear_ui.cpp index b437fd0a..158276f5 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -430,7 +430,7 @@ void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * pthread_mutex_unlock(&updateMutex); } -int WearRecoveryUI::SelectMenu(int sel) { +int WearRecoveryUI::SelectMenu(int sel, bool abs /* = false */) { int old_sel; pthread_mutex_lock(&updateMutex); if (show_menu > 0) { @@ -22,6 +22,7 @@ class WearRecoveryUI : public ScreenRecoveryUI { public: WearRecoveryUI(); + virtual ~WearRecoveryUI() { } void Init(); // overall recovery state ("background image") @@ -48,7 +49,7 @@ class WearRecoveryUI : public ScreenRecoveryUI { // menu display void StartMenu(const char* const * headers, const char* const * items, int initial_selection); - int SelectMenu(int sel); + int SelectMenu(int sel, bool abs = false); void EndMenu(); void Redraw(); |