diff options
107 files changed, 6409 insertions, 670 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,13 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -LOCAL_PATH := $(call my-dir) +ifeq ($(call my-dir),$(call project-path-for,recovery)) +LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := fuse_sideload.c - +LOCAL_SRC_FILES := fuse_sideload.cpp +LOCAL_CLANG := true LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE @@ -34,70 +36,269 @@ LOCAL_SRC_FILES := \ asn1_decoder.cpp \ bootloader.cpp \ device.cpp \ - fuse_sdcard_provider.c \ + fuse_sdcard_provider.cpp \ install.cpp \ recovery.cpp \ roots.cpp \ screen_ui.cpp \ + messagesocket.cpp \ ui.cpp \ verifier.cpp \ + wear_ui.cpp \ + voldclient.cpp + +# External tools +LOCAL_SRC_FILES += \ + ../../system/core/toolbox/newfs_msdos.c \ + ../../system/core/toolbox/start.c \ + ../../system/core/toolbox/stop.c \ + ../../system/vold/vdc.c LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -ifeq ($(HOST_OS),linux) -LOCAL_REQUIRED_MODULES := mkfs.f2fs -endif - RECOVERY_API_VERSION := 3 RECOVERY_FSTAB_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CLANG := true LOCAL_C_INCLUDES += \ system/vold \ system/extras/ext4_utils \ system/core/adb \ + external/e2fsprogs/lib LOCAL_STATIC_LIBRARIES := \ + 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 \ libmincrypt \ libminadbd \ + libtoybox_driver \ + libmksh_static \ libfusesideload \ libminui \ libpng \ + libf2fs_sparseblock \ + libdiskconfig \ + libsysutils \ libfs_mgr \ + libsquashfs_utils \ libbase \ libcutils \ + libutils \ liblog \ + liblogwrap \ libselinux \ - libstdc++ \ + libcrypto_static \ + libscrypt_static \ + libnl \ + libc++_static \ libm \ - libc + libc \ + libext2_blkid \ + libext2_uuid -ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) - LOCAL_CFLAGS += -DUSE_EXT4 - LOCAL_C_INCLUDES += system/extras/ext4_utils - LOCAL_STATIC_LIBRARIES += libext4_utils_static libz +# 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 + +ifneq ($(BOARD_RECOVERY_BLDRMSG_OFFSET),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_BLDRMSG_OFFSET=$(BOARD_RECOVERY_BLDRMSG_OFFSET) +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 LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) 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: TOY_LIST=$(shell $(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} $(TOY_LIST) + @mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/sbin + $(hide) $(foreach t,$(TOY_LIST),ln -sf toybox $(TARGET_RECOVERY_ROOT_OUT)/sbin/$(t);) + +# 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 \ + messagesocket.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_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/extras/ext4_utils/canned_fs_config.c +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 +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 +LOCAL_C_INCLUDES := external/zlib +LOCAL_SRC_FILES := \ + ../../external/zlib/src/contrib/minizip/ioapi.c \ + ../../external/zlib/src/contrib/minizip/miniunz.c \ + ../../external/zlib/src/contrib/minizip/unzip.c +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) + + # All the APIs for testing include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_MODULE := libverifier LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ @@ -105,22 +306,27 @@ LOCAL_SRC_FILES := \ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_MODULE := verifier_test LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := tests LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CFLAGS += -DVERIFIER_TEST LOCAL_SRC_FILES := \ verifier_test.cpp \ asn1_decoder.cpp \ verifier.cpp \ - ui.cpp + ui.cpp \ + messagesocket.cpp LOCAL_STATIC_LIBRARIES := \ libmincrypt \ libminui \ libminzip \ libcutils \ - libstdc++ \ libc +LOCAL_C_INCLUDES += \ + system/core/fs_mgr/include \ + system/vold include $(BUILD_EXECUTABLE) @@ -133,4 +339,7 @@ include $(LOCAL_PATH)/minui/Android.mk \ $(LOCAL_PATH)/edify/Android.mk \ $(LOCAL_PATH)/uncrypt/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ - $(LOCAL_PATH)/applypatch/Android.mk + $(LOCAL_PATH)/applypatch/Android.mk \ + $(LOCAL_PATH)/fstools/Android.mk + +endif diff --git a/adb_install.cpp b/adb_install.cpp index e3b94ea..23bfb51 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; - 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); - 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); + + 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 efad436..7c9d7bc 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/applypatch/Android.mk b/applypatch/Android.mk index 4984093..eb3e458 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -13,8 +13,10 @@ # limitations under the License. LOCAL_PATH := $(call my-dir) + include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := applypatch.c bspatch.c freecache.c imgpatch.c utils.c LOCAL_MODULE := libapplypatch LOCAL_MODULE_TAGS := eng @@ -25,28 +27,31 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := main.c LOCAL_MODULE := applypatch LOCAL_C_INCLUDES += bootable/recovery LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz -LOCAL_SHARED_LIBRARIES += libz libcutils libstdc++ libc +LOCAL_SHARED_LIBRARIES += libz libcutils libc include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := main.c LOCAL_MODULE := applypatch_static LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := eng LOCAL_C_INCLUDES += bootable/recovery LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz -LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc +LOCAL_STATIC_LIBRARIES += libz libcutils libc include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := imgdiff.c utils.c bsdiff.c LOCAL_MODULE := imgdiff LOCAL_FORCE_STATIC_EXECUTABLE := true diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c index 3bac8be..3a2eb91 100644 --- a/applypatch/imgdiff.c +++ b/applypatch/imgdiff.c @@ -122,6 +122,7 @@ */ #include <errno.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -192,8 +193,9 @@ unsigned char* ReadZip(const char* filename, } unsigned char* img = malloc(st.st_size); + size_t sz = (size_t) st.st_size; FILE* f = fopen(filename, "rb"); - if (fread(img, 1, st.st_size, f) != st.st_size) { + if (fread(img, 1, sz, f) != sz) { printf("failed to read \"%s\" %s\n", filename, strerror(errno)); fclose(f); return NULL; @@ -383,7 +385,8 @@ unsigned char* ReadImage(const char* filename, unsigned char* img = malloc(st.st_size + 4); FILE* f = fopen(filename, "rb"); - if (fread(img, 1, st.st_size, f) != st.st_size) { + size_t sz = (size_t) st.st_size; + if (fread(img, 1, sz, f) != sz) { printf("failed to read \"%s\" %s\n", filename, strerror(errno)); fclose(f); return NULL; @@ -400,10 +403,11 @@ unsigned char* ReadImage(const char* filename, *num_chunks = 0; *chunks = NULL; - while (pos < st.st_size) { + while (pos < sz) { unsigned char* p = img+pos; - if (st.st_size - pos >= 4 && + bool processed_deflate = false; + if (sz - pos >= 4 && p[0] == 0x1f && p[1] == 0x8b && p[2] == 0x08 && // deflate compression p[3] == 0x00) { // no header flags @@ -455,18 +459,24 @@ unsigned char* ReadImage(const char* filename, strm.next_out = curr->data + curr->len; ret = inflate(&strm, Z_NO_FLUSH); if (ret < 0) { - printf("Error: inflate failed [%s] at file offset [%zu]\n" - "imgdiff only supports gzip kernel compression," - " did you try CONFIG_KERNEL_LZO?\n", - strm.msg, chunk_offset); - free(img); - return NULL; + if (!processed_deflate) { + // This is the first chunk, assume that it's just a spurious + // gzip header instead of a real one. + break; + } + printf("Error: inflate failed [%s] at file offset [%zu]\n" + "imgdiff only supports gzip kernel compression," + " did you try CONFIG_KERNEL_LZO?\n", + strm.msg, chunk_offset); + free(img); + return NULL; } curr->len = allocated - strm.avail_out; if (strm.avail_out == 0) { allocated *= 2; curr->data = realloc(curr->data, allocated); } + processed_deflate = true; } while (ret != Z_STREAM_END); curr->deflate_len = st.st_size - strm.avail_in - pos; @@ -493,7 +503,7 @@ unsigned char* ReadImage(const char* filename, // the decompression. size_t footer_size = Read4(p-4); if (footer_size != curr[-2].len) { - printf("Error: footer size %d != decompressed size %d\n", + printf("Error: footer size %zu != decompressed size %zu\n", footer_size, curr[-2].len); free(img); return NULL; @@ -623,7 +633,15 @@ unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) { } char ptemp[] = "/tmp/imgdiff-patch-XXXXXX"; - mkstemp(ptemp); + int fd = mkstemp(ptemp); + + if (fd == -1) { + printf("MakePatch failed to create a temporary file: %s\n", + strerror(errno)); + return NULL; + } + close(fd); // temporary file is created and we don't need its file + // descriptor int r = bsdiff(src->data, src->len, &(src->I), tgt->data, tgt->len, ptemp); if (r != 0) { @@ -639,8 +657,8 @@ unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) { } unsigned char* data = malloc(st.st_size); - - if (tgt->type == CHUNK_NORMAL && tgt->len <= st.st_size) { + size_t sz = (size_t) st.st_size; + if (tgt->type == CHUNK_NORMAL && tgt->len <= sz) { unlink(ptemp); tgt->type = CHUNK_RAW; @@ -648,14 +666,14 @@ unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) { return tgt->data; } - *size = st.st_size; + *size = sz; FILE* f = fopen(ptemp, "rb"); if (f == NULL) { printf("failed to open patch %s: %s\n", ptemp, strerror(errno)); return NULL; } - if (fread(data, 1, st.st_size, f) != st.st_size) { + if (fread(data, 1, sz, f) != sz) { printf("failed to read patch %s: %s\n", ptemp, strerror(errno)); return NULL; } @@ -783,7 +801,7 @@ ImageChunk* FindChunkByName(const char* name, void DumpChunks(ImageChunk* chunks, int num_chunks) { int i; for (i = 0; i < num_chunks; ++i) { - printf("chunk %d: type %d start %d len %d\n", + printf("chunk %d: type %d start %zu len %zu\n", i, chunks[i].type, chunks[i].start, chunks[i].len); } } @@ -967,7 +985,7 @@ int main(int argc, char** argv) { } } else { if (i == 1 && bonus_data) { - printf(" using %d bytes of bonus data for chunk %d\n", bonus_size, i); + printf(" using %zu bytes of bonus data for chunk %d\n", bonus_size, i); src_chunks[i].data = realloc(src_chunks[i].data, src_chunks[i].len + bonus_size); memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size); src_chunks[i].len += bonus_size; @@ -975,7 +993,7 @@ int main(int argc, char** argv) { patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i); } - printf("patch %3d is %d bytes (of %d)\n", + printf("patch %3d is %zu bytes (of %zu)\n", i, patch_size[i], tgt_chunks[i].source_len); } @@ -1012,7 +1030,7 @@ int main(int argc, char** argv) { switch (tgt_chunks[i].type) { case CHUNK_NORMAL: - printf("chunk %3d: normal (%10d, %10d) %10d\n", i, + printf("chunk %3d: normal (%10zu, %10zu) %10zu\n", i, tgt_chunks[i].start, tgt_chunks[i].len, patch_size[i]); Write8(tgt_chunks[i].source_start, f); Write8(tgt_chunks[i].source_len, f); @@ -1021,7 +1039,7 @@ int main(int argc, char** argv) { break; case CHUNK_DEFLATE: - printf("chunk %3d: deflate (%10d, %10d) %10d %s\n", i, + printf("chunk %3d: deflate (%10zu, %10zu) %10zu %s\n", i, tgt_chunks[i].start, tgt_chunks[i].deflate_len, patch_size[i], tgt_chunks[i].filename); Write8(tgt_chunks[i].source_start, f); @@ -1038,7 +1056,7 @@ int main(int argc, char** argv) { break; case CHUNK_RAW: - printf("chunk %3d: raw (%10d, %10d)\n", i, + printf("chunk %3d: raw (%10zu, %10zu)\n", i, tgt_chunks[i].start, tgt_chunks[i].len); Write4(patch_size[i], f); fwrite(patch_data[i], 1, patch_size[i], f); diff --git a/backup.cpp b/backup.cpp new file mode 100644 index 0000000..7792add --- /dev/null +++ b/backup.cpp @@ -0,0 +1,308 @@ +#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" + +#include "messagesocket.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; + } + } + + MessageSocket ms; + ms.ClientInit(); + ms.Show("Backup in progress..."); + + 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); + + ms.Dismiss(); + + logmsg("backup complete: rc=%d\n", rc); + + return rc; +} + diff --git a/bootloader.cpp b/bootloader.cpp index 600d238..9e91b27 100644 --- a/bootloader.cpp +++ b/bootloader.cpp @@ -34,8 +34,8 @@ static int set_bootloader_message_block(const struct bootloader_message *in, con int get_bootloader_message(struct bootloader_message *out) { Volume* v = volume_for_path("/misc"); if (v == NULL) { - LOGE("Cannot load volume /misc!\n"); - return -1; + LOGI("Cannot load volume /misc.\n"); + return -1; } if (strcmp(v->fs_type, "mtd") == 0) { return get_bootloader_message_mtd(out, v); @@ -49,8 +49,8 @@ int get_bootloader_message(struct bootloader_message *out) { int set_bootloader_message(const struct bootloader_message *in) { Volume* v = volume_for_path("/misc"); if (v == NULL) { - LOGE("Cannot load volume /misc!\n"); - return -1; + LOGI("Cannot load volume /misc.\n"); + return -1; } if (strcmp(v->fs_type, "mtd") == 0) { return set_bootloader_message_mtd(in, v); @@ -168,6 +168,9 @@ static int get_bootloader_message_block(struct bootloader_message *out, LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } +#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET + fseek(f, BOARD_RECOVERY_BLDRMSG_OFFSET, SEEK_SET); +#endif struct bootloader_message temp; int count = fread(&temp, sizeof(temp), 1, f); if (count != 1) { @@ -185,11 +188,14 @@ static int get_bootloader_message_block(struct bootloader_message *out, static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v) { wait_for_device(v->blk_device); - FILE* f = fopen(v->blk_device, "wb"); + FILE* f = fopen(v->blk_device, "rb+"); if (f == NULL) { LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } +#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET + fseek(f, BOARD_RECOVERY_BLDRMSG_OFFSET, SEEK_SET); +#endif int count = fwrite(in, sizeof(*in), 1, f); if (count != 1) { LOGE("Failed writing %s\n(%s)\n", v->blk_device, strerror(errno)); @@ -0,0 +1,394 @@ +#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 "messagesocket.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; + +static MessageSocket ms; + +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); + ms.Show(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(); + + ms.ClientInit(); + + if (!strcmp(opname, "backup")) { + ms.Show("Backup in progress..."); + rc = do_backup(argc-optidx, &argv[optidx]); + } + else if (!strcmp(opname, "restore")) { + ms.Show("Restore in progress..."); + rc = do_restore(argc-optidx, &argv[optidx]); + } + else { + logmsg("Unknown operation %s\n", opname); + rc = 1; + } + + ms.Dismiss(); + + 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,36 +16,135 @@ #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 + "System reset (keep media)", +#endif + "Full factory reset", "Wipe cache partition", + nullptr +}; +static const menu_entry WIPE_MENU_ENTRIES[] = { +#ifndef RELEASE_BUILD + { ACTION_INVOKE, { .action = Device::WIPE_DATA } }, +#endif + { ACTION_INVOKE, { .action = Device::WIPE_FULL } }, + { ACTION_INVOKE, { .action = Device::WIPE_CACHE } }, + { 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", "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::SHUTDOWN } }, + { ACTION_NONE, { .action = Device::NO_ACTION } } }; +static const menu ADVANCED_MENU = { + ADVANCED_MENU_NAMES, + ADVANCED_MENU_ENTRIES +}; + +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 +}; + +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) { - switch (menu_position) { - case 0: return REBOOT; - case 1: return REBOOT_BOOTLOADER; - case 2: return APPLY_ADB_SIDELOAD; - case 3: return APPLY_SDCARD; - case 4: return WIPE_DATA; - case 5: return WIPE_CACHE; - case 6: return MOUNT_SYSTEM; - case 7: return VIEW_RECOVERY_LOGS; - case 8: return SHUTDOWN; - default: return NO_ACTION; + 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) { @@ -53,19 +152,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,17 +63,18 @@ 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, + NO_ACTION, + REBOOT, + APPLY_UPDATE, + WIPE_DATA, + WIPE_FULL, + WIPE_CACHE, + REBOOT_RECOVERY, + REBOOT_BOOTLOADER, + SHUTDOWN, + VIEW_RECOVERY_LOGS, + MOUNT_SYSTEM, + WIPE_SYSTEM, }; // Return the list of menu items (an array of strings, @@ -86,10 +93,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, @@ -102,8 +114,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/edify/Android.mk b/edify/Android.mk index 03c04e4..c366450 100644 --- a/edify/Android.mk +++ b/edify/Android.mk @@ -25,6 +25,7 @@ LOCAL_CFLAGS := $(edify_cflags) -g -O0 LOCAL_MODULE := edify LOCAL_YACCFLAGS := -v LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CLANG := true include $(BUILD_HOST_EXECUTABLE) @@ -38,5 +39,6 @@ LOCAL_SRC_FILES := $(edify_src_files) LOCAL_CFLAGS := $(edify_cflags) LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_MODULE := libedify +LOCAL_CLANG := true include $(BUILD_STATIC_LIBRARY) diff --git a/etc/init.rc b/etc/init.rc index 4277277..e070438 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -5,7 +5,6 @@ on early-init start healthd on init - export PATH /sbin:/system/bin export ANDROID_ROOT /system export ANDROID_DATA /data export EXTERNAL_STORAGE /sdcard @@ -22,6 +21,26 @@ on init 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 @@ -50,6 +69,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 @@ -65,6 +87,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 @@ -85,18 +110,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 0000000..b2923c6 --- /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 0000000..73dc0f0 --- /dev/null +++ b/fstools/Android.mk @@ -0,0 +1,69 @@ +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 + +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 0000000..b1cc7ad --- /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 0000000..d99b382 --- /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.c b/fuse_sdcard_provider.cpp index 4565c7b..8681425 100644 --- a/fuse_sdcard_provider.c +++ b/fuse_sdcard_provider.cpp @@ -21,6 +21,7 @@ #include <pthread.h> #include <sys/mount.h> #include <sys/stat.h> +#include <sys/wait.h> #include <unistd.h> #include <fcntl.h> @@ -34,7 +35,7 @@ struct file_data { }; static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) { - struct file_data* fd = (struct file_data*)cookie; + file_data* fd = reinterpret_cast<file_data*>(cookie); off64_t offset = ((off64_t) block) * fd->block_size; if (TEMP_FAILURE_RETRY(lseek64(fd->fd, offset, SEEK_SET)) == -1) { @@ -56,18 +57,18 @@ static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32 } static void close_file(void* cookie) { - struct file_data* fd = (struct file_data*)cookie; + file_data* fd = reinterpret_cast<file_data*>(cookie); close(fd->fd); } struct token { - pthread_t th; + pid_t pid; const char* path; int result; }; static void* run_sdcard_fuse(void* cookie) { - struct token* t = (struct token*)cookie; + token* t = reinterpret_cast<token*>(cookie); struct stat sb; if (stat(t->path, &sb) < 0) { @@ -100,41 +101,45 @@ static void* run_sdcard_fuse(void* cookie) { #define SDCARD_INSTALL_TIMEOUT 10 void* start_sdcard_fuse(const char* path) { - struct token* t = malloc(sizeof(struct token)); + token* t = new token; t->path = path; - pthread_create(&(t->th), NULL, run_sdcard_fuse, t); - - struct stat st; - int i; - for (i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { - if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { - if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT-1) { - sleep(1); - continue; - } else { - return NULL; - } - } + if ((t->pid = fork()) < 0) { + free(t); + return NULL; } + if (t->pid == 0) { + run_sdcard_fuse(t); + _exit(0); + } + + time_t start_time = time(NULL); + time_t now = start_time; - // 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); + while (now - start_time < SDCARD_INSTALL_TIMEOUT) { + struct stat st; + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) == 0) { + break; + } + if (errno != ENOENT && errno != ENOTCONN) { + free(t); + t = NULL; + break; + } + sleep(1); + now = time(NULL); + } return t; } void finish_sdcard_fuse(void* cookie) { if (cookie == NULL) return; - struct token* t = (struct token*)cookie; + token* t = reinterpret_cast<token*>(cookie); - // Calling stat() on this magic filename signals the fuse - // filesystem to shut down. - struct stat st; - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + kill(t->pid, SIGTERM); + int status; + waitpid(t->pid, &status, 0); - pthread_join(t->th, NULL); - free(t); + delete t; } diff --git a/fuse_sdcard_provider.h b/fuse_sdcard_provider.h index dbfbcd5..dc2982c 100644 --- a/fuse_sdcard_provider.h +++ b/fuse_sdcard_provider.h @@ -17,13 +17,7 @@ #ifndef __FUSE_SDCARD_PROVIDER_H #define __FUSE_SDCARD_PROVIDER_H -#include <sys/cdefs.h> - -__BEGIN_DECLS - void* start_sdcard_fuse(const char* path); void finish_sdcard_fuse(void* token); -__END_DECLS - #endif diff --git a/fuse_sideload.c b/fuse_sideload.cpp index 48e6cc5..a53a756 100644 --- a/fuse_sideload.c +++ b/fuse_sideload.cpp @@ -65,10 +65,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 @@ -84,7 +84,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 @@ -92,8 +92,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; @@ -116,7 +188,7 @@ static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, siz } static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { - const struct fuse_init_in* req = data; + const struct fuse_init_in* req = reinterpret_cast<const struct fuse_init_in*>(data); struct fuse_init_out out; size_t fuse_struct_size; @@ -170,8 +242,7 @@ static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd, attr->mode = mode; } -static int handle_getattr(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { - const struct fuse_getattr_in* req = data; +static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { struct fuse_attr_out out; memset(&out, 0, sizeof(out)); out.attr_valid = 10; @@ -180,14 +251,12 @@ static int handle_getattr(void* data, struct fuse_data* fd, const struct fuse_in 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, @@ -197,28 +266,20 @@ static int handle_lookup(void* data, struct fuse_data* fd, out.entry_valid = 10; out.attr_valid = 10; - if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, data, + if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, reinterpret_cast<const char*>(data), sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) { 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, 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) { - const struct fuse_open_in* req = data; - - if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; +static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; struct fuse_open_out out; @@ -249,6 +310,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, @@ -288,11 +354,12 @@ static int fetch_block(struct fuse_data* fd, uint32_t block) { } memcpy(blockhash, hash, SHA256_DIGEST_SIZE); + block_cache_enter(fd, block); return 0; } static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { - const struct fuse_read_in* req = data; + const struct fuse_read_in* req = reinterpret_cast<const struct fuse_read_in*>(data); struct fuse_out_header outhdr; struct iovec vec[3]; int vec_used; @@ -364,6 +431,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) { @@ -390,6 +463,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; @@ -421,6 +497,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"); @@ -441,7 +535,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"); @@ -496,11 +600,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); @@ -519,6 +618,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/fuse_sideload.h b/fuse_sideload.h index f9e3bf0..71a09a6 100644 --- a/fuse_sideload.h +++ b/fuse_sideload.h @@ -17,16 +17,10 @@ #ifndef __FUSE_SIDELOAD_H #define __FUSE_SIDELOAD_H -#include <sys/cdefs.h> - -__BEGIN_DECLS - // define the filenames created by the sideload FUSE filesystem #define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload" #define FUSE_SIDELOAD_HOST_FILENAME "package.zip" #define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME) -#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit" -#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG) struct provider_vtab { // read a block @@ -39,6 +33,4 @@ struct provider_vtab { int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size, uint32_t block_size); -__END_DECLS - #endif diff --git a/install.cpp b/install.cpp index c7d382f..c557cd0 100644 --- a/install.cpp +++ b/install.cpp @@ -22,6 +22,8 @@ #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> +#include <setjmp.h> +#include <sys/mount.h> #include "common.h" #include "install.h" @@ -35,6 +37,8 @@ #include "verifier.h" #include "ui.h" +#include "cutils/properties.h" + extern RecoveryUI* ui; #define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" @@ -46,6 +50,12 @@ static const float VERIFICATION_PROGRESS_FRACTION = 0.25; static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; +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) { @@ -164,9 +174,9 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) { } else if (strcmp(command, "ui_print") == 0) { char* str = strtok(NULL, "\n"); if (str) { - ui->Print("%s", str); + ui->PrintOnScreenOnly("%s", str); } else { - ui->Print("\n"); + ui->PrintOnScreenOnly("\n"); } fflush(stdout); } else if (strcmp(command, "wipe_cache") == 0) { @@ -187,21 +197,101 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) { int status; waitpid(pid, &status, 0); 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. */ + mkdir("/system", 0755); + 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 = 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"); + + free(args); + + return (status > 0) ? 1 : 0; +} +#endif /* USE_MDTP */ + static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount) { + 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. @@ -212,6 +302,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); } } @@ -229,16 +320,31 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount) } LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); + set_perf_mode(true); + ui->Print("Verifying update package...\n"); int err; - err = verify_file(map.addr, map.length, loadedKeys, numKeys); + + // 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(map.addr, map.length, loadedKeys, numKeys); + } + else { + err = VERIFY_FAILURE; + } + signal(SIGBUS, SIG_DFL); + free(loadedKeys); LOGI("verify_file returned %d\n", err); if (err != VERIFY_SUCCESS) { LOGE("signature verification failed\n"); sysReleaseMap(&map); - return INSTALL_CORRUPT; + ret = INSTALL_CORRUPT; + goto out; } /* Try to open the package. @@ -248,20 +354,34 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount) if (err != 0) { LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad"); sysReleaseMap(&map); - return INSTALL_CORRUPT; + ret = INSTALL_CORRUPT; + goto out; } /* Verify and install the contents of the package. */ ui->Print("Installing update...\n"); ui->SetEnableReboot(false); - int result = try_update_binary(path, &zip, wipe_cache); + ret = try_update_binary(path, &zip, wipe_cache); 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 */ + +out: + set_perf_mode(false); + return ret; } int @@ -291,3 +411,8 @@ install_package(const char* path, bool* wipe_cache, const char* install_file, } return result; } + +void +set_perf_mode(bool enable) { + property_set("recovery.perf.mode", enable ? "1" : "0"); +} @@ -30,6 +30,8 @@ enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE }; int install_package(const char* root_path, bool* wipe_cache, const char* install_file, bool needs_mount); +void set_perf_mode(bool enable); + #ifdef __cplusplus } #endif diff --git a/messagesocket.cpp b/messagesocket.cpp new file mode 100644 index 0000000..355a667 --- /dev/null +++ b/messagesocket.cpp @@ -0,0 +1,116 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> + +#include "messagesocket.h" + +static const char * const SOCKET_PATH = "/tmp/.dialog_sock"; + +bool MessageSocket::ServerInit() +{ + int fd, rc; + unlink(SOCKET_PATH); + fd = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + return false; + } + struct sockaddr_un sa; + socklen_t salen; + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, SOCKET_PATH); + rc = ::bind(fd, (struct sockaddr *)&sa, sizeof(sa)); + if (rc != 0) { + ::close(fd); + return false; + } + rc = ::listen(fd, 5); + if (rc != 0) { + ::close(fd); + return false; + } + _sock = fd; + return true; +} + +MessageSocket* MessageSocket::Accept() +{ + int clientfd; + struct sockaddr_un sa; + socklen_t salen; + memset(&sa, 0, sizeof(sa)); + salen = sizeof(sa); + clientfd = ::accept(_sock, (struct sockaddr*)&sa, &salen); + if (clientfd < 0) { + return NULL; + } + return new MessageSocket(clientfd); +} + +ssize_t MessageSocket::Read(void* buf, size_t len) +{ + //XXX: use fdopen/getline + ssize_t nread = ::read(_sock, buf, len); + if (nread > 0) { + char* p = (char*)memchr(buf, '\n', nread); + if (p) { + *p = '\0'; + } + } + return nread; +} + +bool MessageSocket::ClientInit() +{ + int fd, rc; + fd = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + return false; + } + struct sockaddr_un sa; + socklen_t salen; + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, SOCKET_PATH); + rc = ::connect(fd, (struct sockaddr*)&sa, sizeof(sa)); + if (rc != 0) { + ::close(fd); + return false; + } + _sock = fd; + return true; +} + +bool MessageSocket::Show(const char* message) +{ + char buf[256]; + sprintf(buf, "show %s", message); + return send_command(buf); +} + +bool MessageSocket::Dismiss() +{ + return send_command("dismiss"); +} + +void MessageSocket::Close() +{ + if (_sock != -1) { + ::close(_sock); + _sock = -1; + } +} + +bool MessageSocket::send_command(const char* command) +{ + char buf[256]; + int len; + ssize_t written; + len = sprintf(buf, "dialog %s\n", command); + written = ::write(_sock, buf, len); + return (written == len); +} diff --git a/messagesocket.h b/messagesocket.h new file mode 100644 index 0000000..5a4c67d --- /dev/null +++ b/messagesocket.h @@ -0,0 +1,40 @@ +#ifndef MESSAGESOCKET_H +#define MESSAGESOCKET_H + +class MessageSocket +{ +private: + // Unimplemented + MessageSocket(const MessageSocket&); + MessageSocket& operator=(const MessageSocket&); + +public: + MessageSocket() : _sock(-1) {} + ~MessageSocket() { Close(); } + int fd() const { return _sock; } + + bool ServerInit(); + MessageSocket* Accept(); + ssize_t Read(void* buf, size_t len); + + bool ClientInit(); + bool Show(const char* message); + bool Dismiss(); + + void Close(); + +private: + explicit MessageSocket(int fd) : _sock(fd) {} + + bool send_command(const char* command); + + int _sock; +}; + +extern int dialog_server_init(); +extern int dialog_client_init(); +extern int dialog_accept(int fd); +extern int dialog_show(int fd); +extern int dialog_dismiss(int fd); + +#endif diff --git a/minadbd/Android.mk b/minadbd/Android.mk index a7a3e08..3db3b41 100644 --- a/minadbd/Android.mk +++ b/minadbd/Android.mk @@ -15,6 +15,7 @@ LOCAL_SRC_FILES := \ fuse_adb_provider.cpp \ services.cpp \ +LOCAL_CLANG := true LOCAL_MODULE := libminadbd LOCAL_CFLAGS := $(minadbd_cflags) LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration diff --git a/minadbd/services.cpp b/minadbd/services.cpp index dd1fd7c..0eeeb20 100644 --- a/minadbd/services.cpp +++ b/minadbd/services.cpp @@ -44,13 +44,14 @@ void* service_bootstrap_func(void* x) { } static void sideload_host_service(int sfd, void* data) { - const char* args = reinterpret_cast<const char*>(data); + char* args = reinterpret_cast<char*>(data); int file_size; int block_size; if (sscanf(args, "%d:%d", &file_size, &block_size) != 2) { printf("bad sideload-host arguments: %s\n", args); exit(1); } + free(args); printf("sideload-host file size %d block size %d\n", file_size, block_size); @@ -97,7 +98,8 @@ int service_to_fd(const char* name) { // sideload-host). exit(3); } else if (!strncmp(name, "sideload-host:", 14)) { - ret = create_service_thread(sideload_host_service, (void*)(name + 14)); + char* arg = strdup(name + 14); + ret = create_service_thread(sideload_host_service, arg); } if (ret >= 0) { close_on_exec(ret); diff --git a/minui/Android.mk b/minui/Android.mk index 97724fb..e74c7be 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,10 +40,15 @@ else LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0 endif +ifneq ($(BOARD_RECOVERY_NEEDS_FBIOPAN_DISPLAY),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_NEEDS_FBIOPAN_DISPLAY +endif + include $(BUILD_STATIC_LIBRARY) # Used by OEMs for factory test images. include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_MODULE := libminui LOCAL_WHOLE_STATIC_LIBRARIES += libminui LOCAL_SHARED_LIBRARIES := libpng diff --git a/minui/events.cpp b/minui/events.cpp index 3b2262a..120baed 100644 --- a/minui/events.cpp +++ b/minui/events.cpp @@ -78,8 +78,8 @@ 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_REL, EV_SW, and EV_ABS event types are ever needed. + 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)) { close(fd); continue; } @@ -137,6 +137,23 @@ int ev_add_fd(int fd, ev_callback cb, void* data) { return ret; } +int ev_del_fd(int fd) +{ + unsigned n; + for (n = 0; n < ev_count; ++n) { + if (ev_fdinfo[n].fd == fd) { + epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, fd, NULL); + if (n != ev_count-1) { + ev_fdinfo[n] = ev_fdinfo[ev_count-1]; + } + ev_count--; + ev_misc_count--; + return 0; + } + } + return -1; +} + void ev_exit(void) { while (ev_count > 0) { close(ev_fdinfo[--ev_count].fd); diff --git a/minui/graphics.cpp b/minui/graphics.cpp index c0eea9e..1f25531 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 997e9ca..b017ff2 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; } @@ -176,18 +182,6 @@ static GRSurface* fbdev_init(minui_backend* backend) { static GRSurface* fbdev_flip(minui_backend* backend __unused) { if (double_buffered) { -#if defined(RECOVERY_BGRA) - // In case of BGRA, do some byte swapping - unsigned int idx; - unsigned char tmp; - unsigned char* ucfb_vaddr = (unsigned char*)gr_draw->data; - for (idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes); - idx += 4) { - tmp = ucfb_vaddr[idx]; - ucfb_vaddr[idx ] = ucfb_vaddr[idx + 2]; - ucfb_vaddr[idx + 2] = tmp; - } -#endif // Change gr_draw to point to the buffer currently displayed, // then flip the driver so we're displaying the other buffer // instead. diff --git a/minui/minui.h b/minui/minui.h index bdde083..063fc86 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); @@ -67,6 +68,7 @@ typedef int (*ev_set_key_callback)(int code, int value, void* data); int ev_init(ev_callback input_cb, void* data); void ev_exit(); int ev_add_fd(int fd, ev_callback cb, void* data); +int ev_del_fd(int fd); void ev_iterate_available_keys(std::function<void(int)> f); int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data); @@ -120,4 +122,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/minzip/Android.mk b/minzip/Android.mk index 045f355..1605a53 100644 --- a/minzip/Android.mk +++ b/minzip/Android.mk @@ -10,12 +10,16 @@ LOCAL_SRC_FILES := \ LOCAL_C_INCLUDES := \ external/zlib \ - external/safe-iop/include + external/safe-iop/include \ + external/lzma/xz-embedded LOCAL_STATIC_LIBRARIES := libselinux +LOCAL_STATIC_LIBRARIES += libxz LOCAL_MODULE := libminzip -LOCAL_CFLAGS += -Wall +LOCAL_CLANG := true + +LOCAL_CFLAGS += -Werror -Wall include $(BUILD_STATIC_LIBRARY) diff --git a/minzip/Hash.c b/minzip/Hash.c index 8f8ed68..49bcb31 100644 --- a/minzip/Hash.c +++ b/minzip/Hash.c @@ -361,7 +361,7 @@ void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc, { const void* data = (const void*)mzHashIterData(&iter); int count; - + count = countProbes(pHashTable, (*calcFunc)(data), data, cmpFunc); numEntries++; @@ -373,7 +373,7 @@ void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc, totalProbe += count; } - LOGI("Probe: min=%d max=%d, total=%d in %d (%d), avg=%.3f\n", + LOGV("Probe: min=%d max=%d, total=%d in %d (%d), avg=%.3f\n", minProbe, maxProbe, totalProbe, numEntries, pHashTable->tableSize, (float) totalProbe / (float) numEntries); } diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c index b1fb455..32bc4c0 100644 --- a/minzip/SysUtil.c +++ b/minzip/SysUtil.c @@ -3,93 +3,52 @@ * * System utilities. */ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> #include <stdbool.h> #include <stdint.h> -#include <stdlib.h> #include <stdio.h> -#include <unistd.h> +#include <stdlib.h> #include <string.h> #include <sys/mman.h> -#include <sys/types.h> #include <sys/stat.h> -#include <fcntl.h> -#include <limits.h> -#include <errno.h> -#include <assert.h> +#include <sys/types.h> +#include <unistd.h> #define LOG_TAG "sysutil" #include "Log.h" #include "SysUtil.h" -static int getFileStartAndLength(int fd, off_t *start_, size_t *length_) -{ - off_t start, end; - size_t length; - - assert(start_ != NULL); - assert(length_ != NULL); - - // TODO: isn't start always 0 for the single call site? just use fstat instead? - - start = TEMP_FAILURE_RETRY(lseek(fd, 0L, SEEK_CUR)); - end = TEMP_FAILURE_RETRY(lseek(fd, 0L, SEEK_END)); - - if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1 || - start == (off_t) -1 || end == (off_t) -1) { - LOGE("could not determine length of file\n"); - return -1; - } - - length = end - start; - if (length == 0) { - LOGE("file is empty\n"); - return -1; - } - - *start_ = start; - *length_ = length; - - return 0; -} - -/* - * Map a file (from fd's current offset) into a private, read-only memory - * segment. The file offset must be a multiple of the page size. - * - * On success, returns 0 and fills out "pMap". On failure, returns a nonzero - * value and does not disturb "pMap". - */ -static int sysMapFD(int fd, MemMapping* pMap) -{ - off_t start; - size_t length; - void* memPtr; - +static bool sysMapFD(int fd, MemMapping* pMap) { assert(pMap != NULL); - if (getFileStartAndLength(fd, &start, &length) < 0) - return -1; + struct stat sb; + if (fstat(fd, &sb) == -1) { + LOGE("fstat(%d) failed: %s\n", fd, strerror(errno)); + return false; + } - memPtr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, start); + void* memPtr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (memPtr == MAP_FAILED) { - LOGW("mmap(%d, R, PRIVATE, %d, %d) failed: %s\n", (int) length, - fd, (int) start, strerror(errno)); - return -1; + LOGE("mmap(%d, R, PRIVATE, %d, 0) failed: %s\n", (int) sb.st_size, fd, strerror(errno)); + return false; } pMap->addr = memPtr; - pMap->length = length; + pMap->length = sb.st_size; pMap->range_count = 1; pMap->ranges = malloc(sizeof(MappedRange)); if (pMap->ranges == NULL) { LOGE("malloc failed: %s\n", strerror(errno)); - munmap(memPtr, length); - return -1; + munmap(memPtr, pMap->length); + return false; } pMap->ranges[0].addr = memPtr; - pMap->ranges[0].length = length; + pMap->ranges[0].length = sb.st_size; - return 0; + return true; } static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) @@ -102,7 +61,7 @@ static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) unsigned int i; if (fgets(block_dev, sizeof(block_dev), mapf) == NULL) { - LOGW("failed to read block device from header\n"); + LOGE("failed to read block device from header\n"); return -1; } for (i = 0; i < sizeof(block_dev); ++i) { @@ -113,9 +72,10 @@ static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) } if (fscanf(mapf, "%zu %u\n%u\n", &size, &blksize, &range_count) != 3) { - LOGW("failed to parse block map header\n"); + LOGE("failed to parse block map header\n"); return -1; } + if (blksize != 0) { blocks = ((size-1) / blksize) + 1; } @@ -136,14 +96,14 @@ static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) unsigned char* reserve; reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); if (reserve == MAP_FAILED) { - LOGW("failed to reserve address space: %s\n", strerror(errno)); + LOGE("failed to reserve address space: %s\n", strerror(errno)); free(pMap->ranges); return -1; } int fd = open(block_dev, O_RDONLY); if (fd < 0) { - LOGW("failed to open block device %s: %s\n", block_dev, strerror(errno)); + LOGE("failed to open block device %s: %s\n", block_dev, strerror(errno)); munmap(reserve, blocks * blksize); free(pMap->ranges); return -1; @@ -155,7 +115,7 @@ static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) for (i = 0; i < range_count; ++i) { size_t start, end; if (fscanf(mapf, "%zu %zu\n", &start, &end) != 2) { - LOGW("failed to parse range %d in block map\n", i); + LOGE("failed to parse range %d in block map\n", i); success = false; break; } @@ -168,7 +128,7 @@ static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize); if (addr == MAP_FAILED) { - LOGW("failed to map block %d: %s\n", i, strerror(errno)); + LOGE("failed to map block %d: %s\n", i, strerror(errno)); success = false; break; } @@ -206,12 +166,12 @@ int sysMapFile(const char* fn, MemMapping* pMap) // A map of blocks FILE* mapf = fopen(fn+1, "r"); if (mapf == NULL) { - LOGV("Unable to open '%s': %s\n", fn+1, strerror(errno)); + LOGE("Unable to open '%s': %s\n", fn+1, strerror(errno)); return -1; } if (sysMapBlockFile(mapf, pMap) != 0) { - LOGW("Map of '%s' failed\n", fn); + LOGE("Map of '%s' failed\n", fn); fclose(mapf); return -1; } @@ -219,13 +179,13 @@ int sysMapFile(const char* fn, MemMapping* pMap) fclose(mapf); } else { // This is a regular file. - int fd = open(fn, O_RDONLY, 0); - if (fd < 0) { + int fd = open(fn, O_RDONLY); + if (fd == -1) { LOGE("Unable to open '%s': %s\n", fn, strerror(errno)); return -1; } - if (sysMapFD(fd, pMap) != 0) { + if (!sysMapFD(fd, pMap)) { LOGE("Map of '%s' failed\n", fn); close(fd); return -1; @@ -244,7 +204,7 @@ void sysReleaseMap(MemMapping* pMap) int i; for (i = 0; i < pMap->range_count; ++i) { if (munmap(pMap->ranges[i].addr, pMap->ranges[i].length) < 0) { - LOGW("munmap(%p, %d) failed: %s\n", + LOGE("munmap(%p, %d) failed: %s\n", pMap->ranges[i].addr, (int)pMap->ranges[i].length, strerror(errno)); } } diff --git a/minzip/Zip.c b/minzip/Zip.c index 40712e0..7823c38 100644 --- a/minzip/Zip.c +++ b/minzip/Zip.c @@ -5,6 +5,7 @@ */ #include "safe_iop.h" #include "zlib.h" +#include "xz_config.h" #include <errno.h> #include <fcntl.h> @@ -198,10 +199,10 @@ static bool parseZipArchive(ZipArchive* pArchive) */ val = get4LE(pArchive->addr); if (val == ENDSIG) { - LOGI("Found Zip archive, but it looks empty\n"); + LOGW("Found Zip archive, but it looks empty\n"); goto bail; } else if (val != LOCSIG) { - LOGV("Not a Zip archive (found 0x%08x)\n", val); + LOGW("Not a Zip archive (found 0x%08x)\n", val); goto bail; } @@ -217,7 +218,7 @@ static bool parseZipArchive(ZipArchive* pArchive) ptr--; } if (ptr < (const unsigned char*) pArchive->addr) { - LOGI("Could not find end-of-central-directory in Zip\n"); + LOGW("Could not find end-of-central-directory in Zip\n"); goto bail; } @@ -429,7 +430,7 @@ int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive) if (length < ENDHDR) { err = -1; - LOGV("File '%s' too small to be zip (%zd)\n", fileName, map.length); + LOGW("Archive %p is too small to be zip (%zd)\n", pArchive, length); goto bail; } @@ -438,7 +439,7 @@ int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive) if (!parseZipArchive(pArchive)) { err = -1; - LOGV("Parsing '%s' failed\n", fileName); + LOGW("Parsing archive %p failed\n", pArchive); goto bail; } @@ -501,12 +502,73 @@ static bool processStoredEntry(const ZipArchive *pArchive, return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie); } +static bool processXZEntry(const ZipArchive *pArchive, + const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, + void *cookie) +{ + unsigned char out[32*1024]; + struct xz_buf b; + struct xz_dec *s; + enum xz_ret ret; + + printf("ok!\n"); + xz_crc32_init(); + xz_crc64_init(); + s = xz_dec_init(XZ_DYNALLOC, 1 << 26); + if (s == NULL) { + LOGE("XZ decompression alloc failed\n"); + goto bail; + } + + b.in = pArchive->addr + pEntry->offset; + b.in_pos = 0; + b.in_size = pEntry->compLen; + b.out = out; + b.out_pos = 0; + b.out_size = sizeof(out); + + do { + ret = xz_dec_run(s, &b); + + LOGVV("+++ b.in_pos = %zu b.out_pos = %zu ret=%d\n", b.in_pos, b.out_pos, ret); + if (b.out_pos == sizeof(out)) { + LOGVV("+++ processing %d bytes\n", b.out_pos); + bool err = processFunction(out, b.out_pos, cookie); + if (!err) { + LOGW("Process function elected to fail (in xz_dec)\n"); + goto xz_bail; + } + b.out_pos = 0; + } + + } while (ret == XZ_OK); + + assert(ret == XZ_STREAM_END); + + bool err = processFunction(out, b.out_pos, cookie); + if (!err) { + LOGW("Process function elected to fail (in xz_dec)\n"); + goto xz_bail; + } + + +xz_bail: + xz_dec_end(s); + +bail: + if (b.in_pos != (unsigned long)pEntry->compLen) { + LOGW("Size mismatch on file after xz_dec (%ld vs %zu)\n", + pEntry->compLen, b.in_pos); + //return false; + } + return true; +} + static bool processDeflatedEntry(const ZipArchive *pArchive, const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, void *cookie) { long result = -1; - unsigned char readBuf[32 * 1024]; unsigned char procBuf[32 * 1024]; z_stream zstream; int zerr; @@ -549,7 +611,7 @@ static bool processDeflatedEntry(const ZipArchive *pArchive, /* uncompress the data */ zerr = inflate(&zstream, Z_NO_FLUSH); if (zerr != Z_OK && zerr != Z_STREAM_END) { - LOGD("zlib inflate call failed (zerr=%d)\n", zerr); + LOGW("zlib inflate call failed (zerr=%d)\n", zerr); goto z_bail; } @@ -603,7 +665,6 @@ bool mzProcessZipEntryContents(const ZipArchive *pArchive, void *cookie) { bool ret = false; - off_t oldOff; switch (pEntry->compression) { case STORED: @@ -621,13 +682,6 @@ bool mzProcessZipEntryContents(const ZipArchive *pArchive, return ret; } -static bool crcProcessFunction(const unsigned char *data, int dataLen, - void *crc) -{ - *(unsigned long *)crc = crc32(*(unsigned long *)crc, data, dataLen); - return true; -} - typedef struct { char *buf; int bufLen; @@ -647,6 +701,26 @@ static bool copyProcessFunction(const unsigned char *data, int dataLen, } /* + * Similar to mzProcessZipEntryContents, but explicitly process the stream + * using XZ/LZMA before calling processFunction. + * + * This is a separate function for use by the updater. LZMA provides huge + * size reductions vs deflate, but isn't actually supported by the ZIP format. + * We need to process it using as little memory as possible. + */ +bool mzProcessZipEntryContentsXZ(const ZipArchive *pArchive, + const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, + void *cookie) +{ + if (pEntry->compression == STORED) { + return processXZEntry(pArchive, pEntry, processFunction, cookie); + } + LOGE("Explicit XZ decoding of entry '%s' unsupported for type %d", + pEntry->fileName, pEntry->compression); + return false; +} + +/* * Read an entry into a buffer allocated by the caller. */ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, @@ -1016,7 +1090,7 @@ bool mzExtractRecursive(const ZipArchive *pArchive, if (callback != NULL) callback(targetFile, cookie); } - LOGD("Extracted %d file(s)\n", extractCount); + LOGV("Extracted %d file(s)\n", extractCount); free(helper.buf); free(zpath); diff --git a/minzip/Zip.h b/minzip/Zip.h index 86d8db5..92c20a0 100644 --- a/minzip/Zip.h +++ b/minzip/Zip.h @@ -114,6 +114,18 @@ bool mzProcessZipEntryContents(const ZipArchive *pArchive, void *cookie); /* + * Similar to mzProcessZipEntryContents, but explicitly process the stream + * using XZ/LZMA before calling processFunction. + * + * This is a separate function for use by the updater. LZMA provides huge + * size reductions vs deflate, but isn't actually supported by the ZIP format. + * We need to process it using as little memory as possible. + */ +bool mzProcessZipEntryContentsXZ(const ZipArchive *pArchive, + const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, + void *cookie); + +/* * Read an entry into a buffer allocated by the caller. */ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk index f04355b..b7d35c2 100644 --- a/mtdutils/Android.mk +++ b/mtdutils/Android.mk @@ -6,10 +6,12 @@ LOCAL_SRC_FILES := \ mounts.c LOCAL_MODULE := libmtdutils +LOCAL_CLANG := true include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := flash_image.c LOCAL_MODULE := flash_image LOCAL_MODULE_TAGS := eng diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c index 6a9b03d..41efa37 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 d721355..c8318c0 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 cc30334..ed74d0f 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; + } } } } @@ -300,20 +306,20 @@ static int read_block(const MtdPartition *partition, int fd, char *data) if (TEMP_FAILURE_RETRY(lseek64(fd, pos, SEEK_SET)) != pos || TEMP_FAILURE_RETRY(read(fd, data, size)) != size) { printf("mtd: read error at 0x%08llx (%s)\n", - pos, strerror(errno)); + (long long)pos, strerror(errno)); } else if (ioctl(fd, ECCGETSTATS, &after)) { printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno)); return -1; } else if (after.failed != before.failed) { printf("mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n", - after.corrected - before.corrected, - after.failed - before.failed, pos); + after.corrected - before.corrected, + after.failed - before.failed, (long long)pos); // copy the comparison baseline for the next read. memcpy(&before, &after, sizeof(struct mtd_ecc_stats)); } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) { fprintf(stderr, "mtd: MEMGETBADBLOCK returned %d at 0x%08llx: %s\n", - mgbb, pos, strerror(errno)); + mgbb, (long long)pos, strerror(errno)); } else { return 0; // Success! } diff --git a/recovery.cpp b/recovery.cpp index b7a5458..03425dc 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -31,6 +31,8 @@ #include <time.h> #include <unistd.h> +#include <chrono> + #include <base/file.h> #include <base/stringprintf.h> @@ -45,18 +47,60 @@ #include "ui.h" #include "screen_ui.h" #include "device.h" + +#include "voldclient.h" + #include "adb_install.h" #include "adb.h" #include "fuse_sideload.h" #include "fuse_sdcard_provider.h" +extern "C" { +#include "recovery_cmds.h" +} + 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' }, + { "headless", no_argument, NULL, 'h' }, { "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' }, @@ -75,7 +119,6 @@ static const char *LOG_FILE = "/cache/recovery/log"; static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; static const char *LOCALE_FILE = "/cache/recovery/last_locale"; static const char *CACHE_ROOT = "/cache"; -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"; @@ -88,6 +131,8 @@ char* stage = NULL; char* reason = NULL; bool modified_flash = false; +#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 @@ -151,8 +196,7 @@ static const int MAX_ARG_LENGTH = 4096; static const int MAX_ARGS = 100; // open a given path, mounting partitions as necessary -FILE* -fopen_path(const char *path, const char *mode) { +FILE* fopen_path(const char *path, const char *mode) { if (ensure_path_mounted(path) != 0) { LOGE("Can't mount %s\n", path); return NULL; @@ -166,23 +210,102 @@ fopen_path(const char *path, const char *mode) { return fp; } +// close a file, log an error if the error indicator is set +static void check_and_fclose(FILE *fp, const char *name) { + fflush(fp); + if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno)); + fclose(fp); +} + bool is_ro_debuggable() { char value[PROPERTY_VALUE_MAX+1]; return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); } static void redirect_stdio(const char* filename) { - // If these fail, there's not really anywhere to complain... - freopen(filename, "a", stdout); setbuf(stdout, NULL); - freopen(filename, "a", stderr); setbuf(stderr, NULL); -} + int pipefd[2]; + if (pipe(pipefd) == -1) { + LOGE("pipe failed: %s\n", strerror(errno)); -// close a file, log an error if the error indicator is set -static void -check_and_fclose(FILE *fp, const char *name) { - fflush(fp); - if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno)); - fclose(fp); + // Fall back to traditional logging mode without timestamps. + // If these fail, there's not really anywhere to complain... + freopen(filename, "a", stdout); setbuf(stdout, NULL); + freopen(filename, "a", stderr); setbuf(stderr, NULL); + + return; + } + + pid_t pid = fork(); + if (pid == -1) { + LOGE("fork failed: %s\n", strerror(errno)); + + // Fall back to traditional logging mode without timestamps. + // If these fail, there's not really anywhere to complain... + freopen(filename, "a", stdout); setbuf(stdout, NULL); + freopen(filename, "a", stderr); setbuf(stderr, NULL); + + return; + } + + if (pid == 0) { + /// Close the unused write end. + close(pipefd[1]); + + auto start = std::chrono::steady_clock::now(); + + // Child logger to actually write to the log file. + FILE* log_fp = fopen(filename, "a"); + if (log_fp == nullptr) { + LOGE("fopen \"%s\" failed: %s\n", filename, strerror(errno)); + close(pipefd[0]); + _exit(1); + } + + FILE* pipe_fp = fdopen(pipefd[0], "r"); + if (pipe_fp == nullptr) { + LOGE("fdopen failed: %s\n", strerror(errno)); + check_and_fclose(log_fp, filename); + close(pipefd[0]); + _exit(1); + } + + char* line = nullptr; + size_t len = 0; + while (getline(&line, &len, pipe_fp) != -1) { + auto now = std::chrono::steady_clock::now(); + double duration = std::chrono::duration_cast<std::chrono::duration<double>>( + now - start).count(); + if (line[0] == '\n') { + fprintf(log_fp, "[%12.6lf]\n", duration); + } else { + fprintf(log_fp, "[%12.6lf] %s", duration, line); + } + fflush(log_fp); + } + + LOGE("getline failed: %s\n", strerror(errno)); + + free(line); + check_and_fclose(log_fp, filename); + close(pipefd[0]); + _exit(1); + } else { + // Redirect stdout/stderr to the logger process. + // Close the unused read end. + close(pipefd[0]); + + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + if (dup2(pipefd[1], STDOUT_FILENO) == -1) { + LOGE("dup2 stdout failed: %s\n", strerror(errno)); + } + if (dup2(pipefd[1], STDERR_FILENO) == -1) { + LOGE("dup2 stderr failed: %s\n", strerror(errno)); + } + + close(pipefd[1]); + } } // command line args come from, in decreasing precedence: @@ -213,6 +336,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"); @@ -326,14 +457,18 @@ static void rotate_logs(int max) { ensure_path_mounted(LAST_KMSG_FILE); for (int i = max-1; i >= 0; --i) { - std::string old_log = android::base::StringPrintf((i == 0) ? "%s" : "%s.%d", - LAST_LOG_FILE, i); + std::string old_log = android::base::StringPrintf("%s", LAST_LOG_FILE); + if (i > 0) { + old_log += "." + std::to_string(i); + } std::string new_log = android::base::StringPrintf("%s.%d", LAST_LOG_FILE, i+1); // Ignore errors if old_log doesn't exist. rename(old_log.c_str(), new_log.c_str()); - std::string old_kmsg = android::base::StringPrintf((i == 0) ? "%s" : "%s.%d", - LAST_KMSG_FILE, i); + std::string old_kmsg = android::base::StringPrintf("%s", LAST_KMSG_FILE); + if (i > 0) { + old_kmsg += "." + std::to_string(i); + } std::string new_kmsg = android::base::StringPrintf("%s.%d", LAST_KMSG_FILE, i+1); rename(old_kmsg.c_str(), new_kmsg.c_str()); } @@ -417,15 +552,15 @@ 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); + 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 @@ -472,10 +607,12 @@ static bool erase_volume(const char* volume) { ui->Print("Formatting %s...\n", volume); - ensure_path_unmounted(volume); - int result = format_volume(volume); + if (volume[0] == '/') { + ensure_path_unmounted(volume); + } + int result = format_volume(volume, force); - if (is_cache) { + if (!force && is_cache) { while (head) { FILE* f = fopen_path(head->name, "wb"); if (f) { @@ -498,21 +635,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(); @@ -524,10 +677,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: @@ -541,6 +713,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; @@ -548,6 +729,9 @@ get_menu_selection(const char* const * headers, const char* const * items, } ui->EndMenu(); + if (chosen_item == Device::kGoHome) { + device->GoHome(); + } return chosen_item; } @@ -557,8 +741,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)); @@ -617,21 +799,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); @@ -664,7 +851,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; } @@ -672,15 +859,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) && erase_volume("/cache") && 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 (should_confirm && !yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!")) { @@ -695,6 +906,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; +} + static void choose_recovery_file(Device* device) { // "Back" + KEEP_LOG_COUNT * 2 + terminating nullptr entry char* entries[1 + KEEP_LOG_COUNT * 2 + 1]; @@ -706,7 +931,10 @@ static void choose_recovery_file(Device* device) { // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x for (int i = 0; i < KEEP_LOG_COUNT; i++) { char* log_file; - if (asprintf(&log_file, (i == 0) ? "%s" : "%s.%d", LAST_LOG_FILE, i) == -1) { + int ret; + ret = (i == 0) ? asprintf(&log_file, "%s", LAST_LOG_FILE) : + asprintf(&log_file, "%s.%d", LAST_LOG_FILE, i); + if (ret == -1) { // memory allocation failure - return early. Should never happen. return; } @@ -717,7 +945,9 @@ static void choose_recovery_file(Device* device) { } char* kmsg_file; - if (asprintf(&kmsg_file, (i == 0) ? "%s" : "%s.%d", LAST_KMSG_FILE, i) == -1) { + ret = (i == 0) ? asprintf(&kmsg_file, "%s", LAST_KMSG_FILE) : + asprintf(&kmsg_file, "%s.%d", LAST_KMSG_FILE, i); + if (ret == -1) { // memory allocation failure - return early. Should never happen. return; } @@ -734,12 +964,11 @@ 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; - // TODO: do we need to redirect? ShowFile could just avoid writing to stdio. - redirect_stdio("/dev/null"); ui->ShowFile(entries[chosen_item]); - redirect_stdio(TEMPORARY_LOG_FILE); } for (size_t i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) { @@ -747,29 +976,101 @@ static void choose_recovery_file(Device* device) { } } -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); + int status; + + 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"); - 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(); void* token = start_sdcard_fuse(path); - int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, + vdc->volumeUnmount(vi.mId, true); + + status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, TEMPORARY_INSTALL_FILE, false); finish_sdcard_fuse(token); - ensure_path_unmounted(SDCARD_ROOT); + free(path); + return status; +} + +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->DialogShowErrorLog("Install failed"); + } + return status; } @@ -783,12 +1084,12 @@ 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: case INSTALL_CORRUPT: - ui->SetBackground(RecoveryUI::ERROR); + ui->SetBackground(RecoveryUI::D_ERROR); break; } ui->SetProgressType(RecoveryUI::EMPTY); @@ -801,62 +1102,91 @@ 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; + + 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 && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } + } + + if (status >= 0 && status != INSTALL_NONE) { + if (status != INSTALL_SUCCESS) { + ui->SetBackground(RecoveryUI::D_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; - 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 + 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"); + } } else { - ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); + if (ensure_path_mounted("/system") != -1) { + ui->Print("Mounted /system.\n"); + } } - } - break; - case Device::VIEW_RECOVERY_LOGS: - choose_recovery_file(device); - break; + break; - case Device::MOUNT_SYSTEM: - if (ensure_path_mounted("/system") != -1) { - ui->Print("Mounted /system.\n"); - } - break; + case Device::WIPE_SYSTEM: + wipe_system(device); + break; + } + if (status == Device::kRefresh) { + status = 0; + continue; + } + break; } } } @@ -885,6 +1215,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 @@ -903,12 +1267,32 @@ ui_print(const char* format, ...) { } } -int -main(int argc, char **argv) { - time_t start = time(NULL); +extern "C" int toybox_driver(int argc, char **argv); - redirect_stdio(TEMPORARY_LOG_FILE); +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) { // If this binary is started with the single argument "--adbd", // instead of being the normal recovery binary, it turns into kind // of a stripped-down version of adbd that only supports the @@ -921,6 +1305,43 @@ 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 + // we may have two logger instances with different timestamps. + redirect_stdio(TEMPORARY_LOG_FILE); + printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table(); @@ -930,9 +1351,11 @@ main(int argc, char **argv) { const char *update_package = NULL; bool should_wipe_data = false; bool should_wipe_cache = false; + bool should_wipe_media = false; bool show_text = false; bool sideload = false; bool sideload_auto_reboot = false; + bool headless = false; bool just_exit = false; bool shutdown_after = false; @@ -941,8 +1364,10 @@ main(int argc, char **argv) { switch (arg) { case 'i': send_intent = optarg; break; case 'u': update_package = optarg; break; + case 'h': headless = true; 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; @@ -975,6 +1400,9 @@ main(int argc, char **argv) { ui = device->GetUI(); gCurrentUI = ui; + vdc = new VoldClient(device); + vdc->start(); + ui->SetLocale(locale); ui->Init(); @@ -986,6 +1414,9 @@ 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" } }; @@ -1011,11 +1442,15 @@ main(int argc, char **argv) { if (strncmp(update_package, "CACHE:", 6) == 0) { int len = strlen(update_package) + 10; char* modified_path = (char*)malloc(len); - strlcpy(modified_path, "/cache/", len); - strlcat(modified_path, update_package+6, len); - printf("(replacing path \"%s\" with \"%s\")\n", - update_package, modified_path); - update_package = modified_path; + if (modified_path) { + strlcpy(modified_path, "/cache/", len); + strlcat(modified_path, update_package+6, len); + printf("(replacing path \"%s\" with \"%s\")\n", + update_package, modified_path); + update_package = modified_path; + } + else + printf("modified_path allocation failed\n"); } } printf("\n"); @@ -1023,10 +1458,20 @@ main(int argc, char **argv) { 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) { status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true); if (status == INSTALL_SUCCESS && should_wipe_cache) { @@ -1043,13 +1488,17 @@ 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) { if (!wipe_cache(false, device)) { 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 @@ -1060,7 +1509,8 @@ 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; @@ -1074,21 +1524,25 @@ 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); } if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) { copy_logs(); - ui->SetBackground(RecoveryUI::ERROR); + ui->SetBackground(RecoveryUI::D_ERROR); } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; - if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) { + if (headless) { + ui->ShowText(true); + ui->SetHeadlessMode(); + finish_recovery(NULL); + for (;;) { + pause(); + } + } + else if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) { Device::BuiltinAction temp = prompt_and_wait(device, status); if (temp != Device::NO_ACTION) { after = temp; @@ -1098,20 +1552,40 @@ 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; } sleep(5); // should reboot before this finishes diff --git a/recovery_cmds.h b/recovery_cmds.h new file mode 100644 index 0000000..8fc4d3e --- /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-560dpi b/res-560dpi new file mode 120000 index 0000000..8576a9b --- /dev/null +++ b/res-560dpi @@ -0,0 +1 @@ +res-xxhdpi
\ No newline at end of file diff --git a/res-hdpi/images/font_log.png b/res-hdpi/images/font_log.png Binary files differnew file mode 100644 index 0000000..59b990a --- /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 0000000..d1aad6d --- /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 0000000..c6c35ad --- /dev/null +++ b/res-hdpi/images/icon_header.png diff --git a/res-hdpi/images/icon_headless.png b/res-hdpi/images/icon_headless.png Binary files differnew file mode 100644 index 0000000..780836f --- /dev/null +++ b/res-hdpi/images/icon_headless.png diff --git a/res-hdpi/images/icon_info.png b/res-hdpi/images/icon_info.png Binary files differnew file mode 100644 index 0000000..3993941 --- /dev/null +++ b/res-hdpi/images/icon_info.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 0000000..5aa8e62 --- /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 0000000..5836504 --- /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 0000000..b37422d --- /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 0000000..ed0ccfa --- /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 0000000..bcbe242 --- /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 0000000..59b990a --- /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 0000000..0fc5723 --- /dev/null +++ b/res-mdpi/images/icon_header.png diff --git a/res-mdpi/images/icon_headless.png b/res-mdpi/images/icon_headless.png Binary files differnew file mode 100644 index 0000000..5d134cd --- /dev/null +++ b/res-mdpi/images/icon_headless.png diff --git a/res-mdpi/images/icon_info.png b/res-mdpi/images/icon_info.png Binary files differnew file mode 100644 index 0000000..3993941 --- /dev/null +++ b/res-mdpi/images/icon_info.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 0000000..81e4637 --- /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 0000000..d173042 --- /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 0000000..d9e3a43 --- /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 0000000..ef65d61 --- /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 0000000..d1aad6d --- /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 0000000..6aead73 --- /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 0000000..c04c7bb --- /dev/null +++ b/res-xhdpi/images/icon_header.png diff --git a/res-xhdpi/images/icon_headless.png b/res-xhdpi/images/icon_headless.png Binary files differnew file mode 100644 index 0000000..af283e2 --- /dev/null +++ b/res-xhdpi/images/icon_headless.png diff --git a/res-xhdpi/images/icon_info.png b/res-xhdpi/images/icon_info.png Binary files differnew file mode 100644 index 0000000..3993941 --- /dev/null +++ b/res-xhdpi/images/icon_info.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 0000000..415715e --- /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 0000000..237af6a --- /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 0000000..425c0dc --- /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 0000000..f7b6ce3 --- /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 0000000..6aead73 --- /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 0000000..f3b54b3 --- /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 0000000..e690d8d --- /dev/null +++ b/res-xxhdpi/images/icon_header.png diff --git a/res-xxhdpi/images/icon_headless.png b/res-xxhdpi/images/icon_headless.png Binary files differnew file mode 100644 index 0000000..09a8234 --- /dev/null +++ b/res-xxhdpi/images/icon_headless.png diff --git a/res-xxhdpi/images/icon_info.png b/res-xxhdpi/images/icon_info.png Binary files differnew file mode 100644 index 0000000..3993941 --- /dev/null +++ b/res-xxhdpi/images/icon_info.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 0000000..0b2e866 --- /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 0000000..03cb8a1 --- /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 0000000..7348841 --- /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 0000000..a981495 --- /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 0000000..ec8d899 --- /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 0000000..d7c326a --- /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 0000000..a35f300 --- /dev/null +++ b/res-xxxhdpi/images/icon_header.png diff --git a/res-xxxhdpi/images/icon_headless.png b/res-xxxhdpi/images/icon_headless.png Binary files differnew file mode 100644 index 0000000..a715e3f --- /dev/null +++ b/res-xxxhdpi/images/icon_headless.png diff --git a/res-xxxhdpi/images/icon_info.png b/res-xxxhdpi/images/icon_info.png Binary files differnew file mode 100644 index 0000000..3993941 --- /dev/null +++ b/res-xxxhdpi/images/icon_info.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 0000000..f630553 --- /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 0000000..9211093 --- /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 0000000..9ee96ce --- /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 0000000..d63ecb0 --- /dev/null +++ b/res-xxxhdpi/images/icon_sysbar_home_highlight.png diff --git a/restore.cpp b/restore.cpp new file mode 100644 index 0000000..edfeff8 --- /dev/null +++ b/restore.cpp @@ -0,0 +1,313 @@ +#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" + +#include "messagesocket.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; + + MessageSocket ms; + ms.ClientInit(); + ms.Show("Restore in progress..."); + + rc = do_restore_tree(); + logmsg("do_restore: rc=%d\n", rc); + + ms.Dismiss(); + + 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" @@ -35,10 +36,53 @@ extern "C" { #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; @@ -58,21 +102,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; } -int ensure_path_mounted(const char* path) { +// Mount the volume specified by path at the given 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); @@ -90,14 +197,20 @@ int ensure_path_mounted(const char* path) { return -1; } - const MountedVolume* mv = - find_mounted_volume_by_mount_point(v->mount_point); - if (mv) { - // volume is already mounted - return 0; + if (!mount_point) { + mount_point = v->mount_point; } - mkdir(v->mount_point, 0755); // in case it doesn't already exist + 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_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. @@ -106,31 +219,102 @@ int ensure_path_mounted(const char* path) { partition = mtd_find_partition_by_name(v->blk_device); if (partition == NULL) { LOGE("failed to find \"%s\" partition to mount at \"%s\"\n", - v->blk_device, v->mount_point); + v->blk_device, mount_point); return -1; } - return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0); + 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) { - result = mount(v->blk_device, v->mount_point, v->fs_type, - v->flags, v->fs_options); + 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, + mntflags, v->fs_options); if (result == 0) return 0; - LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno)); + LOGE("failed to mount %s (%s)\n", mount_point, strerror(errno)); return -1; } - LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point); + LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, mount_point); return -1; } -int ensure_path_unmounted(const char* path) { - Volume* v = volume_for_path(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, force_rw); +} + +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; @@ -150,7 +334,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[]) { @@ -167,7 +358,61 @@ static int exec_cmd(const char* path, char* const argv[]) { return WEXITSTATUS(status); } -int format_volume(const char* volume) { +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, 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); @@ -183,11 +428,52 @@ int format_volume(const char* volume) { 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); @@ -235,24 +521,24 @@ int format_volume(const char* volume) { if (strcmp(v->fs_type, "ext4") == 0) { result = make_ext4fs(v->blk_device, length, volume, sehandle); } 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)); @@ -281,7 +567,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,10 +18,7 @@ #define RECOVERY_ROOTS_H_ #include "common.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include <fs_mgr.h> // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); @@ -31,23 +28,34 @@ 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); -// Make sure that the volume 'path' is on is mounted. Returns 0 on +// 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, 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); // 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(); -#ifdef __cplusplus -} -#endif +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 ff95915..46f4add 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -30,16 +30,16 @@ #include <vector> -#include "base/strings.h" -#include "cutils/properties.h" +#include <base/strings.h> +#include <base/stringprintf.h> +#include <cutils/properties.h> + #include "common.h" #include "device.h" #include "minui/minui.h" #include "screen_ui.h" #include "ui.h" - -static int char_width; -static int char_height; +#include "cutils/properties.h" // Return the current time as a double (including fractions of a second). static double now() { @@ -58,6 +58,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), @@ -66,20 +68,34 @@ ScreenRecoveryUI::ScreenRecoveryUI() : text_top_(0), show_text(false), show_text_ever(false), + dialog_icon(NONE), + dialog_text(nullptr), + dialog_show_log(false), menu_(nullptr), + menu_headers_(nullptr), + header_items(0), show_menu(false), menu_items(0), menu_sel(0), + sysbar_state(0), file_viewer_text_(nullptr), animation_fps(20), installing_frames(-1), stage(-1), - max_stage(-1) { - - for (int i = 0; i < 5; i++) { + max_stage(-1), + rainbow(false), + wrap_count(0) { + + headerIcon = nullptr; + sysbarBackIcon = nullptr; + sysbarBackHighlightIcon = nullptr; + sysbarHomeIcon = nullptr; + sysbarHomeHighlightIcon = nullptr; + for (int i = 0; i < NR_ICONS; i++) { backgroundIcon[i] = nullptr; } pthread_mutex_init(&updateMutex, nullptr); + pthread_cond_init(&progressCondition, NULL); } // Clear the screen and draw the currently selected background icon (if any). @@ -101,14 +117,16 @@ void ScreenRecoveryUI::draw_background_locked(Icon icon) { int textWidth = gr_get_width(text_surface); int textHeight = gr_get_height(text_surface); int stageHeight = gr_get_height(stageMarkerEmpty); + int availableHeight = icon == INSTALLING_UPDATE && !DialogShowing() && show_text + ? 3 * gr_fb_height() / 4 : gr_fb_height(); int sh = (max_stage >= 0) ? stageHeight : 0; iconX = (gr_fb_width() - iconWidth) / 2; - iconY = (gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2; + iconY = (availableHeight - (iconHeight+textHeight+40+sh)) / 2; int textX = (gr_fb_width() - textWidth) / 2; - int textY = ((gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2) + iconHeight + 40; + int textY = ((availableHeight - (iconHeight+textHeight+40+sh)) / 2) + iconHeight + 40; gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); if (stageHeight > 0) { @@ -122,6 +140,8 @@ void ScreenRecoveryUI::draw_background_locked(Icon icon) { } } + LOGV("textX=%d textY=%d iconX=%d iconY=%d", textX, textY, iconX, iconY); + gr_color(255, 255, 255, 255); gr_texticon(textX, textY, text_surface); } @@ -130,7 +150,7 @@ void ScreenRecoveryUI::draw_background_locked(Icon icon) { // Draw the progress bar (if any) on the screen. Does not flip pages. // Should only be called with updateMutex locked. void ScreenRecoveryUI::draw_progress_locked() { - if (currentIcon == ERROR) return; + if (currentIcon == D_ERROR) return; if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { GRSurface* icon = installation[installingFrame]; @@ -142,8 +162,11 @@ void ScreenRecoveryUI::draw_progress_locked() { int width = gr_get_width(progressBarEmpty); int height = gr_get_height(progressBarEmpty); - int dx = (gr_fb_width() - width)/2; - int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; + int bottomOfUsableHeight = show_text ? 3 * gr_fb_height() / 4 : gr_fb_height(); + int bottomOfIcon = bottomOfUsableHeight / 2 + iconHeight / 2; + + int dx = (gr_fb_width() - width) / 2; + int dy = bottomOfIcon + (bottomOfUsableHeight - bottomOfIcon) / 2 - height / 2; // Erase behind the progress bar (in case this was a progress-only update) gr_color(0, 0, 0, 255); @@ -184,13 +207,13 @@ void ScreenRecoveryUI::SetColor(UIElement e) { break; case MENU: case MENU_SEL_BG: - gr_color(0, 106, 157, 255); + gr_color(106, 103, 102, 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(0, 177, 229, 255); break; case LOG: gr_color(196, 196, 196, 255); @@ -198,6 +221,9 @@ void ScreenRecoveryUI::SetColor(UIElement e) { case TEXT_FILL: gr_color(0, 0, 0, 160); break; + case ERROR_TEXT: + gr_color(255, 0, 0, 255); + break; default: gr_color(255, 255, 255, 255); break; @@ -213,7 +239,7 @@ void ScreenRecoveryUI::DrawHorizontalRule(int* y) { void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) { gr_text(4, *y, line, bold); - *y += char_height + 4; + *y += char_height_ + 4; } void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) { @@ -233,63 +259,193 @@ 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); + } +} + +void ScreenRecoveryUI::draw_dialog() +{ + int x, y, w, h; + + if (dialog_icon == HEADLESS) { + return; + } + draw_header_icon(); + draw_sysbar(); + + int iconHeight = gr_get_height(backgroundIcon[dialog_icon]); + + x = (gr_fb_width()/2 - (char_width_ * strlen(dialog_text))/2); + if (dialog_show_log) { + y = gr_get_height(headerIcon) + char_height_; + } + else { + y = (gr_fb_height()/2 + iconHeight/2); + } + + SetColor(ERROR_TEXT); + gr_text(x, y, dialog_text, 0); + y += char_height_ + 2; + + if (dialog_show_log) { + int cx, cy; + gr_set_font("log"); + gr_font_size(&cx, &cy); + + size_t row; + for (row = 0; row < log_text_rows_; ++row) { + gr_text(2, y, text_[row], 0); + y += cy + 2; + } + gr_set_font("menu"); + } + + if (dialog_icon == D_ERROR) { + /* + * This could be improved... + * + * Draw rect around text "Okay". + * Text is centered horizontally. + * Bottom of text is 4 lines from bottom of screen. + * Rect width 4px + * Rect padding 8px + */ + w = char_width_ * 4; + h = char_height_; + x = gr_fb_width()/2 - w/2; + y = gr_fb_height() - h - 4 * char_height_; + SetColor(HEADER); + gr_fill(x-(4+8), y-(4+8), x+w+(4+8), y+h+(4+8)); + SetColor(MENU_SEL_BG); + gr_fill(x-8, y-8, x+w+8, y+h+8); + SetColor(MENU_SEL_FG); + gr_text(x, y, "Okay", 0); + } +} + // 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(currentIcon); - draw_progress_locked(); - } else { - gr_color(0, 0, 0, 255); - gr_clear(); + draw_background_locked(currentIcon); - int y = 0; - if (show_menu) { - char recovery_fingerprint[PROPERTY_VALUE_MAX]; - property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); + if (DialogShowing()) { + draw_dialog(); + return; + } - SetColor(INFO); - DrawTextLine(&y, "Android Recovery", true); - for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(&y, chunk.c_str(), false); - } - DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); - - SetColor(HEADER); - DrawTextLines(&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); - } - y += char_height + 4; + if (show_text) { + if (currentIcon == ERASING || currentIcon == INSTALLING_UPDATE || currentIcon == VIEWING_LOG) { + size_t y = currentIcon == INSTALLING_UPDATE ? 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_first_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; } - DrawHorizontalRule(&y); + gr_set_font("menu"); + return; } - // display from the bottom up, until we hit the top of the - // screen, the bottom of the menu, or we've displayed the - // entire text buffer. - SetColor(LOG); - int row = (text_top_ + text_rows_ - 1) % text_rows_; - size_t count = 0; - for (int ty = gr_fb_height() - char_height; - ty >= y && count < text_rows_; - ty -= char_height, ++count) { - gr_text(0, ty, text_[row], false); - --row; - if (row < 0) row = text_rows_ - 1; + if (show_menu) { + 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 = (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)); + } } } } @@ -297,20 +453,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_progress_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. @@ -319,20 +464,31 @@ 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); - int redraw = 0; + bool redraw = false; + double start = now(); + + LOGV("loop %f show_text=%d progressBarType=%d waiting=%d\n", start, show_text, progressBarType, update_waiting ); // update the installation animation, if active // skip this if we have a text overlay (too expensive to update) if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && - installing_frames > 0 && !show_text) { + installing_frames > 0) { installingFrame = (installingFrame + 1) % installing_frames; - redraw = 1; + redraw = true; } // move the progress bar forward on timed intervals, if configured @@ -343,13 +499,26 @@ void ScreenRecoveryUI::ProgressThreadLoop() { if (p > 1.0) p = 1.0; if (p > progress) { progress = p; - redraw = 1; + redraw = true; } } - 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_progress_locked\n"); + draw_progress_locked(); + } + gr_flip(); + + update_waiting = false; pthread_mutex_unlock(&updateMutex); + double end = now(); // minimum of 20ms delay between frames double delay = interval - (end-start); @@ -391,23 +560,47 @@ static char** Alloc2d(size_t rows, size_t cols) { void ScreenRecoveryUI::Init() { gr_init(); - gr_font_size(&char_width, &char_height); - text_rows_ = gr_fb_height() / char_height; - text_cols_ = gr_fb_width() / char_width; + 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_col_ = text_row_ = 0; + text_top_ = 1; + + 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_ = Alloc2d(text_rows_, text_cols_ + 1); + 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_col_ = text_row_ = 0; - text_top_ = 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; backgroundIcon[NONE] = nullptr; LoadBitmapArray("icon_installing", &installing_frames, &installation); backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr; backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; - LoadBitmap("icon_error", &backgroundIcon[ERROR]); - backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + LoadBitmap("icon_info", &backgroundIcon[D_INFO]); + LoadBitmap("icon_error", &backgroundIcon[D_ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[D_ERROR]; + LoadBitmap("icon_headless", &backgroundIcon[HEADLESS]); LoadBitmap("progress_empty", &progressBarEmpty); LoadBitmap("progress_fill", &progressBarFill); @@ -417,7 +610,7 @@ void ScreenRecoveryUI::Init() { LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]); LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]); LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); - LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); + LoadLocalizedBitmap("error_text", &backgroundText[D_ERROR]); pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); @@ -467,7 +660,8 @@ void ScreenRecoveryUI::SetProgressType(ProgressType type) { progressScopeStart = 0; progressScopeSize = 0; progress = 0; - update_progress_locked(); + + update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -479,7 +673,8 @@ void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { progressScopeTime = now(); progressScopeDuration = seconds; progress = 0; - update_progress_locked(); + + update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -493,7 +688,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); @@ -506,23 +701,22 @@ void ScreenRecoveryUI::SetStage(int current, int max) { pthread_mutex_unlock(&updateMutex); } -void ScreenRecoveryUI::Print(const char *fmt, ...) { - char buf[256]; - va_list ap; - va_start(ap, fmt); - vsnprintf(buf, 256, fmt, ap); - va_end(ap); +void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { + std::string str; + android::base::StringAppendV(&str, fmt, ap); - fputs(buf, stdout); + if (copy_to_stdout) { + fputs(str.c_str(), stdout); + } pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - for (const char* ptr = buf; *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col_ >= text_cols_) { + if (log_text_rows_ > 0 && log_text_cols_ > 0) { + for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { + 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; } @@ -532,6 +726,20 @@ void ScreenRecoveryUI::Print(const char *fmt, ...) { pthread_mutex_unlock(&updateMutex); } +void ScreenRecoveryUI::Print(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, true, ap); + va_end(ap); +} + +void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); +} + void ScreenRecoveryUI::PutChar(char ch) { pthread_mutex_lock(&updateMutex); if (ch != '\n') text_[text_row_][text_col_++] = ch; @@ -549,8 +757,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); } @@ -560,13 +768,15 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { offsets.push_back(ftell(fp)); ClearText(); + SetBackground(RecoveryUI::VIEWING_LOG); + struct stat sb; fstat(fileno(fp), &sb); bool show_prompt = false; while (true) { if (show_prompt) { - Print("--(%d%% of %d bytes)--", + PrintOnScreenOnly("--(%d%% of %d bytes)--", static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))), static_cast<int>(sb.st_size)); Redraw(); @@ -630,11 +840,82 @@ void ScreenRecoveryUI::ShowFile(const char* filename) { text_top_ = old_text_top; } +void ScreenRecoveryUI::DialogShowInfo(const char* text) +{ + pthread_mutex_lock(&updateMutex); + free(dialog_text); + dialog_text = strdup(text); + dialog_show_log = false; + dialog_icon = D_INFO; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::DialogShowError(const char* text) +{ + pthread_mutex_lock(&updateMutex); + free(dialog_text); + dialog_text = strdup(text); + dialog_show_log = false; + dialog_icon = D_ERROR; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::DialogShowErrorLog(const char* text) +{ + pthread_mutex_lock(&updateMutex); + free(dialog_text); + dialog_text = strdup(text); + dialog_show_log = true; + dialog_icon = D_ERROR; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::DialogDismiss() +{ + pthread_mutex_lock(&updateMutex); + free(dialog_text); + dialog_text = NULL; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::SetHeadlessMode() +{ + pthread_mutex_lock(&updateMutex); + free(dialog_text); + dialog_text = strdup(""); + dialog_show_log = false; + dialog_icon = HEADLESS; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +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); @@ -643,22 +924,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); @@ -669,7 +987,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 ea05bf1..78fcb9b 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -23,6 +23,9 @@ #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 { @@ -49,12 +52,28 @@ class ScreenRecoveryUI : public RecoveryUI { // printing messages void Print(const char* fmt, ...) __printflike(2, 3); + void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3); void ShowFile(const char* filename); + void DialogShowInfo(const char* text); + void DialogShowError(const char* text); + void DialogShowErrorLog(const char* text); + int DialogShowing() const { return (dialog_text != NULL); } + bool DialogDismissable() const { return (dialog_icon == D_ERROR); } + void DialogDismiss(); + void SetHeadlessMode(); + + // 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); @@ -62,7 +81,7 @@ class ScreenRecoveryUI : public RecoveryUI { void Redraw(); enum UIElement { - HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO + HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO, ERROR_TEXT }; void SetColor(UIElement e); @@ -73,8 +92,15 @@ class ScreenRecoveryUI : public RecoveryUI { bool rtl_locale; pthread_mutex_t updateMutex; - GRSurface* backgroundIcon[5]; - GRSurface* backgroundText[5]; + pthread_cond_t progressCondition; + + GRSurface* headerIcon; + GRSurface* sysbarBackIcon; + GRSurface* sysbarBackHighlightIcon; + GRSurface* sysbarHomeIcon; + GRSurface* sysbarHomeHighlightIcon; + GRSurface* backgroundIcon[NR_ICONS]; + GRSurface* backgroundText[NR_ICONS]; GRSurface** installation; GRSurface* progressBarEmpty; GRSurface* progressBarFill; @@ -89,6 +115,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. @@ -98,14 +125,26 @@ class ScreenRecoveryUI : public RecoveryUI { bool show_text; bool show_text_ever; // has show_text ever been true? + Icon dialog_icon; + char *dialog_text; + bool dialog_show_log; + 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_; int animation_fps; @@ -115,16 +154,32 @@ class ScreenRecoveryUI : public RecoveryUI { int stage, max_stage; + 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; + void draw_background_locked(Icon icon); void draw_progress_locked(); + int draw_header_icon(); + void draw_menu_item(int textrow, const char *text, int selected); + void draw_sysbar(); + void draw_dialog(); void draw_screen_locked(); void update_screen_locked(); - void update_progress_locked(); static void* ProgressThreadStartRoutine(void* data); void ProgressThreadLoop(); void ShowFile(FILE*); + void PrintV(const char*, bool, va_list); void PutChar(char); void ClearText(); @@ -135,6 +190,8 @@ class ScreenRecoveryUI : public RecoveryUI { void LoadBitmap(const char* filename, GRSurface** surface); void LoadBitmapArray(const char* filename, int* frames, GRSurface*** surface); void LoadLocalizedBitmap(const char* filename, GRSurface** surface); + + void OMGRainbows(); }; #endif // RECOVERY_UI_H diff --git a/tests/Android.mk b/tests/Android.mk index 02a272a..4ce00b4 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -17,6 +17,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_STATIC_LIBRARIES := libverifier LOCAL_SRC_FILES := asn1_decoder_test.cpp @@ -27,8 +27,11 @@ #include <sys/types.h> #include <time.h> #include <unistd.h> +#include <sys/epoll.h> +#include <cutils/properties.h> #include <cutils/android_reboot.h> +#include <cutils/properties.h> #include "common.h" #include "roots.h" @@ -37,19 +40,113 @@ #include "screen_ui.h" #include "ui.h" +#include "voldclient.h" + +#include "messagesocket.h" + #define UI_WAIT_KEY_TIMEOUT_SEC 120 +/* Some extra input defines */ +#ifndef ABS_MT_ANGLE +#define ABS_MT_ANGLE 0x38 +#endif + +static int string_split(char* s, char** fields, int maxfields) +{ + int n = 0; + while (n+1 < maxfields) { + char* p = strchr(s, ' '); + if (!p) + break; + *p = '\0'; + printf("string_split: field[%d]=%s\n", n, s); + fields[n++] = s; + s = p+1; + } + fields[n] = s; + printf("string_split: last field[%d]=%s\n", n, s); + return n+1; +} + +struct ms_info { + RecoveryUI* ui; + MessageSocket* sock; +}; + +static int message_socket_client_event(int fd, uint32_t epevents, void *data) +{ + ms_info* info = (ms_info*)data; + + printf("message_socket client event\n"); + if (!(epevents & EPOLLIN)) { + return 0; + } + + char buf[256]; + ssize_t nread; + nread = info->sock->Read(buf, sizeof(buf)); + if (nread <= 0) { + ev_del_fd(fd); + info->ui->DialogDismiss(); + info->sock->Close(); + delete info->sock; + delete info; + return 0; + } + + printf("message_socket client message <%s>\n", buf); + + // Parse the message. Right now we support: + // dialog show <string> + // dialog dismiss + char* fields[3]; + int nfields; + nfields = string_split(buf, fields, 3); + printf("fields=%d\n", nfields); + if (nfields < 2) + return 0; + printf("field[0]=%s, field[1]=%s\n", fields[0], fields[1]); + if (strcmp(fields[0], "dialog") == 0) { + if (strcmp(fields[1], "show") == 0 && nfields > 2) { + info->ui->DialogShowInfo(fields[2]); + } + if (strcmp(fields[1], "dismiss") == 0) { + info->ui->DialogDismiss(); + } + } + + return 0; +} + +static int message_socket_listen_event(int fd, uint32_t epevents, void *data) +{ + ms_info* info = (ms_info*)data; + MessageSocket* sock = info->sock->Accept(); + printf("message_socket_listen_event: event on %d\n", fd); + if (sock) { + printf("message_socket client connected\n"); + ms_info* clientinfo = new ms_info; + clientinfo->ui = info->ui; + clientinfo->sock = sock; + ev_add_fd(sock->fd(), message_socket_client_event, clientinfo); + } + return 0; +} + 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)); @@ -62,6 +159,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"); } } @@ -80,8 +183,15 @@ static void* InputThreadLoop(void*) { } void RecoveryUI::Init() { + calibrate_swipe(); ev_init(InputCallback, this); + message_socket.ServerInit(); + ms_info* info = new ms_info; + info->ui = this; + info->sock = &message_socket; + ev_add_fd(message_socket.fd(), message_socket_listen_event, info); + ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr); @@ -93,31 +203,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_KEY && ev.code <= KEY_MAX) { - ProcessKey(ev.code, ev.value); + if (ev.type != EV_REL) { + dev->rel_sum = 0; + } + + 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; @@ -135,11 +259,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) { @@ -173,8 +300,12 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { break; case RecoveryUI::REBOOT: +#ifndef VERIFIER_TEST + vdc->unmountAll(); +#endif if (reboot_enabled) { - android_reboot(ANDROID_RB_RESTART, 0, 0); + property_set(ANDROID_RB_PROPERTY, "reboot,"); + while(1) { pause(); } } break; @@ -185,6 +316,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) { + process_key(dev, KEY_DOWN, 1); // press down key + process_key(dev, KEY_DOWN, 0); // and release it + dev->rel_sum = 0; + } else if (dev->rel_sum < -3) { + process_key(dev, KEY_UP, 1); // press up key + process_key(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); @@ -203,7 +474,212 @@ 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 (DialogShowing()) { + if (DialogDismissable() && !dev->in_swipe) { + DialogDismiss(); + } + 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; + if (!DialogShowing()) { + 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) { + if (DialogShowing()) { + if (DialogDismissable()) { + DialogDismiss(); + } + return; + } pthread_mutex_lock(&key_queue_mutex); const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); if (key_queue_len < queue_max) { @@ -215,6 +691,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. @@ -224,13 +701,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) { @@ -241,6 +723,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) { @@ -335,3 +826,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,75 @@ #include <pthread.h> #include <time.h> +#include "messagesocket.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: @@ -37,7 +106,7 @@ class RecoveryUI { virtual void SetLocale(const char* locale) = 0; // Set the overall recovery state ("background image"). - enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; + enum Icon { NONE, INSTALLING_UPDATE, VIEWING_LOG, ERASING, NO_COMMAND, D_INFO, D_ERROR, HEADLESS, NR_ICONS }; virtual void SetBackground(Icon icon) = 0; // --- progress indicator --- @@ -62,16 +131,30 @@ class RecoveryUI { virtual bool WasTextEverVisible() = 0; // Write a message to the on-screen log (shown if the user has - // toggled on the text display). + // toggled on the text display). Print() will also dump the message + // to stdout / log file, while PrintOnScreenOnly() not. virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; + virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; virtual void ShowFile(const char* filename) = 0; + virtual void ClearText() = 0; + + virtual void DialogShowInfo(const char* text) = 0; + virtual void DialogShowError(const char* text) = 0; + virtual void DialogShowErrorLog(const char* text) = 0; + virtual int DialogShowing() const = 0; + virtual bool DialogDismissable() const = 0; + virtual void DialogDismiss() = 0; + virtual void SetHeadlessMode() = 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(); @@ -79,6 +162,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(); @@ -105,6 +191,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). @@ -113,12 +206,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); @@ -133,6 +229,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; @@ -140,6 +237,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; @@ -149,16 +253,31 @@ private: pthread_t input_thread_; + MessageSocket message_socket; + void OnKeyDetected(int key_code); 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/uncrypt/Android.mk b/uncrypt/Android.mk index c7d4d37..e73c8f1 100644 --- a/uncrypt/Android.mk +++ b/uncrypt/Android.mk @@ -16,6 +16,8 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +LOCAL_CLANG := true + LOCAL_SRC_FILES := uncrypt.cpp LOCAL_MODULE := uncrypt diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp index 46da86d..2cf09a5 100644 --- a/uncrypt/uncrypt.cpp +++ b/uncrypt/uncrypt.cpp @@ -226,7 +226,7 @@ static int produce_block_map(const char* path, const char* map_file, const char* int wfd = -1; if (encrypted) { - wfd = open(blk_dev, O_WRONLY | O_SYNC); + wfd = open(blk_dev, O_WRONLY); if (wfd < 0) { ALOGE("failed to open fd for writing: %s\n", strerror(errno)); return -1; diff --git a/updater/Android.mk b/updater/Android.mk index ff02a33..470eb5d 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -17,22 +17,32 @@ include $(CLEAR_VARS) # needed only for OTA packages.) LOCAL_MODULE_TAGS := eng +LOCAL_CLANG := true + LOCAL_SRC_FILES := $(updater_src_files) -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 += \ libext4_utils_static \ libsparse_static \ libz + +LOCAL_C_INCLUDES += external/e2fsprogs/lib +LOCAL_STATIC_LIBRARIES += libext2_blkid libext2_uuid + +ifneq ($(BOARD_RECOVERY_BLDRMSG_OFFSET),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_BLDRMSG_OFFSET=$(BOARD_RECOVERY_BLDRMSG_OFFSET) +endif + +ifeq ($(BOARD_SUPPRESS_EMMC_WIPE),true) + LOCAL_CFLAGS += -DSUPPRESS_EMMC_WIPE endif LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz -LOCAL_STATIC_LIBRARIES += libmincrypt libbz -LOCAL_STATIC_LIBRARIES += libcutils liblog libstdc++ libc +LOCAL_STATIC_LIBRARIES += libmincrypt libbz libxz +LOCAL_STATIC_LIBRARIES += libcutils liblog libc LOCAL_STATIC_LIBRARIES += libselinux tune2fs_static_libraries := \ libext2_com_err \ @@ -57,7 +67,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. @@ -79,13 +93,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.c b/updater/blockimg.c index b006d10..be56fbd 100644 --- a/updater/blockimg.c +++ b/updater/blockimg.c @@ -61,30 +61,91 @@ typedef struct { int pos[0]; } RangeSet; +#define RANGESET_MAX_POINTS \ + ((int)((INT_MAX / sizeof(int)) - sizeof(RangeSet))) + static RangeSet* parse_range(char* text) { char* save; - int num; - num = strtol(strtok_r(text, ",", &save), NULL, 0); + char* token; + int i, num; + long int val; + RangeSet* out = NULL; + size_t bufsize; - RangeSet* out = malloc(sizeof(RangeSet) + num * sizeof(int)); - if (out == NULL) { - fprintf(stderr, "failed to allocate range of %zu bytes\n", - sizeof(RangeSet) + num * sizeof(int)); - exit(1); + if (!text) { + goto err; + } + + token = strtok_r(text, ",", &save); + + if (!token) { + goto err; } + + val = strtol(token, NULL, 0); + + if (val < 2 || val > RANGESET_MAX_POINTS) { + goto err; + } else if (val % 2) { + goto err; // must be even + } + + num = (int) val; + bufsize = sizeof(RangeSet) + num * sizeof(int); + + out = malloc(bufsize); + + if (!out) { + fprintf(stderr, "failed to allocate range of %zu bytes\n", bufsize); + goto err; + } + out->count = num / 2; out->size = 0; - int i; + for (i = 0; i < num; ++i) { - out->pos[i] = strtol(strtok_r(NULL, ",", &save), NULL, 0); - if (i%2) { + token = strtok_r(NULL, ",", &save); + + if (!token) { + goto err; + } + + val = strtol(token, NULL, 0); + + if (val < 0 || val > INT_MAX) { + goto err; + } + + out->pos[i] = (int) val; + + if (i % 2) { + if (out->pos[i - 1] >= out->pos[i]) { + goto err; // empty or negative range + } + + if (out->size > INT_MAX - out->pos[i]) { + goto err; // overflow + } + out->size += out->pos[i]; } else { + if (out->size < 0) { + goto err; + } + out->size -= out->pos[i]; } } + if (out->size <= 0) { + goto err; + } + return out; + +err: + fprintf(stderr, "failed to parse range '%s'\n", text ? text : "NULL"); + exit(1); } static int range_overlaps(RangeSet* r1, RangeSet* r2) { @@ -135,11 +196,6 @@ static int write_all(int fd, const uint8_t* data, size_t size) { written += w; } - if (fsync(fd) == -1) { - fprintf(stderr, "fsync failed: %s\n", strerror(errno)); - return -1; - } - return 0; } @@ -285,7 +341,12 @@ static bool receive_new_data(const unsigned char* data, int size, void* cookie) static void* unzip_new_data(void* cookie) { NewThreadInfo* nti = (NewThreadInfo*) cookie; - mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti); + if (strncmp(".xz", nti->entry->fileName + (nti->entry->fileNameLen - 3), 3) == 0) { + mzProcessZipEntryContentsXZ(nti->za, nti->entry, receive_new_data, nti); + } else { + mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti); + } + return NULL; } @@ -671,7 +732,7 @@ static int WriteStash(const char* base, const char* id, int blocks, uint8_t* buf fprintf(stderr, " writing %d blocks to %s\n", blocks, cn); - fd = TEMP_FAILURE_RETRY(open(fn, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, STASH_FILE_MODE)); + fd = TEMP_FAILURE_RETRY(open(fn, O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE)); if (fd == -1) { fprintf(stderr, "failed to create \"%s\": %s\n", fn, strerror(errno)); @@ -1465,10 +1526,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)); goto pceout; } +#endif } } @@ -1726,6 +1789,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, int argc, } if (params.canwrite) { + if (fsync(params.fd) == -1) { + fprintf(stderr, "fsync failed: %s\n", strerror(errno)); + goto pbiudone; + } fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks); fflush(cmd_pipe); } diff --git a/updater/install.c b/updater/install.c index 01a5dd2..19c22fb 100644 --- a/updater/install.c +++ b/updater/install.c @@ -33,6 +33,7 @@ #include <sys/xattr.h> #include <linux/xattr.h> #include <inttypes.h> +#include <blkid/blkid.h> #include "bootloader.h" #include "applypatch/applypatch.h" @@ -47,11 +48,8 @@ #include "updater.h" #include "install.h" #include "tune2fs.h" - -#ifdef USE_EXT4 #include "make_ext4fs.h" #include "wipe.h" -#endif void uiPrint(State* state, char* buffer) { char* line = strtok(buffer, "\n"); @@ -61,6 +59,11 @@ void uiPrint(State* state, char* buffer) { line = strtok(NULL, "\n"); } fprintf(ui->cmd_pipe, "ui_print\n"); + + // The recovery will only print the contents to screen for pipe command + // ui_print. We need to dump the contents to stderr (which has been + // redirected to the log file) directly. + fprintf(stderr, "%s", buffer); } __attribute__((__format__(printf, 2, 3))) __nonnull((2)) @@ -165,6 +168,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) { @@ -332,7 +345,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) { @@ -360,7 +372,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); @@ -931,6 +942,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) // @@ -974,7 +1053,7 @@ Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { goto done; } - if (fread(buffer, 1, st.st_size, f) != st.st_size) { + if (fread(buffer, 1, st.st_size, f) != (size_t)st.st_size) { ErrorAbort(state, "%s: failed to read %lld bytes from %s", name, (long long)st.st_size+1, filename); fclose(f); @@ -1416,10 +1495,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, "%s() expects 2 args, got %d", name, argc); @@ -1435,6 +1515,9 @@ 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); +#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET + fseek(f, BOARD_RECOVERY_BLDRMSG_OFFSET, SEEK_CUR); +#endif fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f); fclose(f); free(filename); @@ -1447,6 +1530,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, "%s() failed to reboot", name); return NULL; @@ -1477,6 +1565,9 @@ 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); +#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET + fseek(f, BOARD_RECOVERY_BLDRMSG_OFFSET, SEEK_CUR); +#endif int to_write = strlen(stagestr)+1; int max_size = sizeof(((struct bootloader_message*)0)->stage); if (to_write > max_size) { @@ -1503,6 +1594,9 @@ 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); +#ifdef BOARD_RECOVERY_BLDRMSG_OFFSET + fseek(f, BOARD_RECOVERY_BLDRMSG_OFFSET, SEEK_CUR); +#endif fread(buffer, sizeof(buffer), 1, f); fclose(f); buffer[sizeof(buffer)-1] = '\0'; @@ -1622,4 +1716,5 @@ void RegisterInstallFunctions() { RegisterFunction("enable_reboot", EnableRebootFn); RegisterFunction("tune2fs", Tune2FsFn); + RegisterFunction("get_device_compatible", ConfirmDevVariant); } diff --git a/verifier_test.cpp b/verifier_test.cpp index 82546ed..a9d42c0 100644 --- a/verifier_test.cpp +++ b/verifier_test.cpp @@ -141,12 +141,34 @@ class FakeUI : public RecoveryUI { vfprintf(stderr, fmt, ap); va_end(ap); } + void PrintOnScreenOnly(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } void ShowFile(const char*) { } + void ClearText() {} + + virtual void DialogShowInfo(const char* text) {} + virtual void DialogShowError(const char* text) {} + virtual void DialogShowErrorLog(const char* text) {} + virtual int DialogShowing() const { return 0; } + bool DialogDismissable() const { return false; } + virtual void DialogDismiss() {} + virtual void SetHeadlessMode() {} void StartMenu(const char* const * headers, const char* const * items, int initial_selection) { } - int SelectMenu(int sel) { return 0; } + int SelectMenu(int sel, bool abs = false) { return 0; } void EndMenu() { } + + virtual int MenuItemStart() const { return 0; } + virtual int MenuItemHeight() const { return 0; } + + virtual int GetSysbarHeight() { return 0; } + virtual int GetSysbarState() { return 0; } + virtual void SetSysbarState(int state) {} }; void diff --git a/voldclient.cpp b/voldclient.cpp new file mode 100644 index 0000000..0c8462a --- /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 0000000..c5f568e --- /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 new file mode 100644 index 0000000..9d4ec09 --- /dev/null +++ b/wear_ui.cpp @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include <vector> + +#include "common.h" +#include "device.h" +#include "minui/minui.h" +#include "wear_ui.h" +#include "ui.h" +#include "cutils/properties.h" +#include "base/strings.h" + +static int char_width; +static int char_height; + +// There's only (at most) one of these objects, and global callbacks +// (for pthread_create, and the input event system) need to find it, +// so use a global variable. +static WearRecoveryUI* self = NULL; + +// Return the current time as a double (including fractions of a second). +static double now() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +WearRecoveryUI::WearRecoveryUI() : + progress_bar_height(3), + progress_bar_width(200), + progress_bar_y(259), + outer_height(0), + outer_width(0), + menu_unusable_rows(0), + intro_frames(22), + loop_frames(60), + currentIcon(NONE), + intro_done(false), + current_frame(0), + animation_fps(30), + rtl_locale(false), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + text_cols(0), + text_rows(0), + text_col(0), + text_row(0), + text_top(0), + show_text(false), + show_text_ever(false), + show_menu(false), + menu_items(0), + menu_sel(0) { + + for (size_t i = 0; i < NR_ICONS; i++) + backgroundIcon[i] = NULL; + + pthread_mutex_init(&updateMutex, NULL); + self = this; +} + +// Draw background frame on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void WearRecoveryUI::draw_background_locked(Icon icon) +{ + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (icon) { + GRSurface* surface; + if (icon == INSTALLING_UPDATE || icon == ERASING) { + if (!intro_done) { + surface = introFrames[current_frame]; + } else { + surface = loopFrames[current_frame]; + } + } + else { + surface = backgroundIcon[icon]; + } + + int width = gr_get_width(surface); + int height = gr_get_height(surface); + + int x = (gr_fb_width() - width) / 2; + int y = (gr_fb_height() - height) / 2; + + gr_blit(surface, 0, 0, width, height, x, y); + } +} + +// Draw the progress bar (if any) on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void WearRecoveryUI::draw_progress_locked() +{ + if (currentIcon == D_ERROR) return; + if (progressBarType != DETERMINATE) return; + + int width = progress_bar_width; + int height = progress_bar_height; + int dx = (gr_fb_width() - width)/2; + int dy = progress_bar_y; + + float p = progressScopeStart + progress * progressScopeSize; + int pos = (int) (p * width); + + gr_color(0x43, 0x43, 0x43, 0xff); + gr_fill(dx, dy, dx + width, dy + height); + + if (pos > 0) { + gr_color(0x02, 0xa8, 0xf3, 255); + if (rtl_locale) { + // Fill the progress bar from right to left. + gr_fill(dx + width - pos, dy, dx + width, dy + height); + } else { + // Fill the progress bar from left to right. + gr_fill(dx, dy, dx + pos, dy + height); + } + } +} + +void WearRecoveryUI::SetColor(UIElement e) { + switch (e) { + case HEADER: + gr_color(247, 0, 6, 255); + break; + case MENU: + case MENU_SEL_BG: + gr_color(0, 106, 157, 255); + break; + case MENU_SEL_FG: + gr_color(255, 255, 255, 255); + break; + case LOG: + gr_color(249, 194, 0, 255); + break; + case TEXT_FILL: + gr_color(0, 0, 0, 160); + break; + default: + gr_color(255, 255, 255, 255); + break; + } +} + +void WearRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) { + gr_text(x, *y, line, bold); + *y += char_height + 4; +} + +void WearRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) { + for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { + DrawTextLine(x, y, lines[i], false); + } +} + +static const char* HEADERS[] = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + NULL +}; + +void WearRecoveryUI::draw_screen_locked() +{ + draw_background_locked(currentIcon); + draw_progress_locked(); + char cur_selection_str[50]; + + if (show_text) { + SetColor(TEXT_FILL); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + int y = outer_height; + int x = outer_width; + if (show_menu) { + char recovery_fingerprint[PROPERTY_VALUE_MAX]; + property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); + SetColor(HEADER); + DrawTextLine(x + 4, &y, "Android Recovery", true); + for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) { + DrawTextLine(x +4, &y, chunk.c_str(), false); + } + + // This is actually the help strings. + DrawTextLines(x + 4, &y, HEADERS); + SetColor(HEADER); + DrawTextLines(x + 4, &y, menu_headers_); + + // Show the current menu item number in relation to total number if + // items don't fit on the screen. + if (menu_items > menu_end - menu_start) { + sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); + gr_text(x+4, y, cur_selection_str, 1); + y += char_height+4; + } + + // Menu begins here + SetColor(MENU); + + for (int i = menu_start; i < menu_end; ++i) { + + if (i == menu_sel) { + // draw the highlight bar + SetColor(MENU_SEL_BG); + gr_fill(x, y-2, gr_fb_width()-x, y+char_height+2); + // white text of selected item + SetColor(MENU_SEL_FG); + if (menu[i][0]) gr_text(x+4, y, menu[i], 1); + SetColor(MENU); + } else { + if (menu[i][0]) gr_text(x+4, y, menu[i], 0); + } + y += char_height+4; + } + SetColor(MENU); + y += 4; + gr_fill(0, y, gr_fb_width(), y+2); + y += 4; + } + + SetColor(LOG); + + // display from the bottom up, until we hit the top of the + // screen, the bottom of the menu, or we've displayed the + // entire text buffer. + int ty; + int row = (text_top+text_rows-1) % text_rows; + size_t count = 0; + for (int ty = gr_fb_height() - char_height - outer_height; + ty > y+2 && count < text_rows; + ty -= char_height, ++count) { + gr_text(x+4, ty, text[row], 0); + --row; + if (row < 0) row = text_rows-1; + } + } +} + +void WearRecoveryUI::update_screen_locked() +{ + draw_screen_locked(); + gr_flip(); +} + +// Keeps the progress bar updated, even when the process is otherwise busy. +void* WearRecoveryUI::progress_thread(void *cookie) { + self->progress_loop(); + return NULL; +} + +void WearRecoveryUI::progress_loop() { + double interval = 1.0 / animation_fps; + for (;;) { + double start = now(); + pthread_mutex_lock(&updateMutex); + int redraw = 0; + + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) + && !show_text) { + if (!intro_done) { + if (current_frame == intro_frames - 1) { + intro_done = true; + current_frame = 0; + } else { + current_frame++; + } + } else { + current_frame = (current_frame + 1) % loop_frames; + } + redraw = 1; + } + + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float p = 1.0 * elapsed / duration; + if (p > 1.0) p = 1.0; + if (p > progress) { + progress = p; + redraw = 1; + } + } + + if (redraw) + update_screen_locked(); + + pthread_mutex_unlock(&updateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end-start); + if (delay < 0.02) delay = 0.02; + usleep((long)(delay * 1000000)); + } +} + +void WearRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { + int result = res_create_display_surface(filename, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + +void WearRecoveryUI::Init() +{ + gr_init(); + + gr_font_size(&char_width, &char_height); + + text_col = text_row = 0; + text_rows = (gr_fb_height()) / char_height; + visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height; + if (text_rows > kMaxRows) text_rows = kMaxRows; + text_top = 1; + + text_cols = (gr_fb_width() - (outer_width * 2)) / char_width; + if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; + + LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); + backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; + LoadBitmap("icon_error", &backgroundIcon[D_ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[D_ERROR]; + + introFrames = (GRSurface**)malloc(intro_frames * sizeof(GRSurface*)); + for (int i = 0; i < intro_frames; ++i) { + char filename[40]; + sprintf(filename, "intro%02d", i); + LoadBitmap(filename, introFrames + i); + } + + loopFrames = (GRSurface**)malloc(loop_frames * sizeof(GRSurface*)); + for (int i = 0; i < loop_frames; ++i) { + char filename[40]; + sprintf(filename, "loop%02d", i); + LoadBitmap(filename, loopFrames + i); + } + + pthread_create(&progress_t, NULL, progress_thread, NULL); + RecoveryUI::Init(); +} + +void WearRecoveryUI::SetLocale(const char* locale) { + if (locale) { + char* lang = strdup(locale); + for (char* p = lang; *p; ++p) { + if (*p == '_') { + *p = '\0'; + break; + } + } + + // A bit cheesy: keep an explicit list of supported languages + // that are RTL. + if (strcmp(lang, "ar") == 0 || // Arabic + strcmp(lang, "fa") == 0 || // Persian (Farsi) + strcmp(lang, "he") == 0 || // Hebrew (new language code) + strcmp(lang, "iw") == 0 || // Hebrew (old language code) + strcmp(lang, "ur") == 0) { // Urdu + rtl_locale = true; + } + free(lang); + } +} + +void WearRecoveryUI::SetBackground(Icon icon) +{ + pthread_mutex_lock(&updateMutex); + currentIcon = icon; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgressType(ProgressType type) +{ + pthread_mutex_lock(&updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowProgress(float portion, float seconds) +{ + pthread_mutex_lock(&updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgress(float fraction) +{ + pthread_mutex_lock(&updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = progress_bar_width; + float scale = width * progressScopeSize; + if ((int) (progress * scale) != (int) (fraction * scale)) { + progress = fraction; + update_screen_locked(); + } + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetStage(int current, int max) +{ +} + +void WearRecoveryUI::Print(const char *fmt, ...) +{ + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + fputs(buf, stdout); + + // This can get called before ui_init(), so be careful. + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + char *ptr; + for (ptr = buf; *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col >= text_cols) { + text[text_row][text_col] = '\0'; + text_col = 0; + text_row = (text_row + 1) % text_rows; + if (text_row == text_top) text_top = (text_top + 1) % text_rows; + } + if (*ptr != '\n') text[text_row][text_col++] = *ptr; + } + text[text_row][text_col] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items, + int initial_selection) { + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + menu_headers_ = headers; + size_t i = 0; + // "i < text_rows" is removed from the loop termination condition, + // which is different from the one in ScreenRecoveryUI::StartMenu(). + // Because WearRecoveryUI supports scrollable menu, it's fine to have + // more entries than text_rows. The menu may be truncated otherwise. + // Bug: 23752519 + for (; items[i] != nullptr; i++) { + strncpy(menu[i], items[i], text_cols - 1); + menu[i][text_cols - 1] = '\0'; + } + menu_items = i; + show_menu = 1; + menu_sel = initial_selection; + menu_start = 0; + menu_end = visible_text_rows - 1 - menu_unusable_rows; + if (menu_items <= menu_end) + menu_end = menu_items; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +int WearRecoveryUI::SelectMenu(int sel, bool abs /* = false */) { + int old_sel; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items-1; + if (menu_sel < menu_start) { + menu_start--; + menu_end--; + } else if (menu_sel >= menu_end && menu_sel < menu_items) { + menu_end++; + menu_start++; + } + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; +} + +void WearRecoveryUI::EndMenu() { + int i; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0 && text_rows > 0 && text_cols > 0) { + show_menu = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +bool WearRecoveryUI::IsTextVisible() +{ + pthread_mutex_lock(&updateMutex); + int visible = show_text; + pthread_mutex_unlock(&updateMutex); + return visible; +} + +bool WearRecoveryUI::WasTextEverVisible() +{ + pthread_mutex_lock(&updateMutex); + int ever_visible = show_text_ever; + pthread_mutex_unlock(&updateMutex); + return ever_visible; +} + +void WearRecoveryUI::ShowText(bool visible) +{ + pthread_mutex_lock(&updateMutex); + // Don't show text during ota install or factory reset + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + pthread_mutex_unlock(&updateMutex); + return; + } + show_text = visible; + if (show_text) show_text_ever = 1; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::Redraw() +{ + pthread_mutex_lock(&updateMutex); + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowFile(FILE* fp) { + std::vector<long> offsets; + offsets.push_back(ftell(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + Print("--(%d%% of %d bytes)--", + static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))), + static_cast<int>(sb.st_size)); + Redraw(); + while (show_prompt) { + show_prompt = false; + int key = WaitKey(); + if (key == KEY_POWER || key == KEY_ENTER) { + return; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + if (offsets.size() <= 1) { + show_prompt = true; + } else { + offsets.pop_back(); + fseek(fp, offsets.back(), SEEK_SET); + } + } else { + if (feof(fp)) { + return; + } + offsets.push_back(ftell(fp)); + } + } + ClearText(); + } + + int ch = getc(fp); + if (ch == EOF) { + text_row = text_top = text_rows - 2; + show_prompt = true; + } else { + PutChar(ch); + if (text_col == 0 && text_row >= text_rows - 2) { + text_top = text_row; + show_prompt = true; + } + } + } +} + +void WearRecoveryUI::PutChar(char ch) { + pthread_mutex_lock(&updateMutex); + if (ch != '\n') text[text_row][text_col++] = ch; + if (ch == '\n' || text_col >= text_cols) { + text_col = 0; + ++text_row; + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowFile(const char* filename) { + FILE* fp = fopen_path(filename, "re"); + if (fp == nullptr) { + Print(" Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + ShowFile(fp); + fclose(fp); +} + +void WearRecoveryUI::ClearText() { + pthread_mutex_lock(&updateMutex); + text_col = 0; + text_row = 0; + text_top = 1; + for (size_t i = 0; i < text_rows; ++i) { + memset(text[i], 0, text_cols + 1); + } + pthread_mutex_unlock(&updateMutex); +} diff --git a/wear_ui.h b/wear_ui.h new file mode 100644 index 0000000..2947d9c --- /dev/null +++ b/wear_ui.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_WEAR_UI_H +#define RECOVERY_WEAR_UI_H + +#include <pthread.h> +#include <stdio.h> + +#include "ui.h" +#include "minui/minui.h" + +class WearRecoveryUI : public RecoveryUI { + public: + WearRecoveryUI(); + + void Init(); + void SetLocale(const char* locale); + + // overall recovery state ("background image") + void SetBackground(Icon icon); + + // progress indicator + void SetProgressType(ProgressType type); + void ShowProgress(float portion, float seconds); + void SetProgress(float fraction); + + void SetStage(int current, int max); + + // text log + void ShowText(bool visible); + bool IsTextVisible(); + bool WasTextEverVisible(); + + // printing messages + void Print(const char* fmt, ...); + void ShowFile(const char* filename); + void ShowFile(FILE* fp); + + // menu display + void StartMenu(const char* const * headers, const char* const * items, + int initial_selection); + int SelectMenu(int sel, bool abs = false); + void EndMenu(); + + void Redraw(); + + enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL }; + virtual void SetColor(UIElement e); + + protected: + int progress_bar_height, progress_bar_width; + + // progress bar vertical position, it's centered horizontally + int progress_bar_y; + + // outer of window + int outer_height, outer_width; + + // Unusable rows when displaying the recovery menu, including the lines + // for headers (Android Recovery, build id and etc) and the bottom lines + // that may otherwise go out of the screen. + int menu_unusable_rows; + + // number of intro frames (default: 22) and loop frames (default: 60) + int intro_frames; + int loop_frames; + + private: + Icon currentIcon; + + bool intro_done; + + int current_frame; + + int animation_fps; + + bool rtl_locale; + + pthread_mutex_t updateMutex; + GRSurface* backgroundIcon[NR_ICONS]; + GRSurface* *introFrames; + GRSurface* *loopFrames; + + ProgressType progressBarType; + + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; + + static const int kMaxCols = 96; + static const int kMaxRows = 96; + + // Log text overlay, displayed when a magic key is pressed + char text[kMaxRows][kMaxCols]; + size_t text_cols, text_rows; + // Number of text rows seen on screen + int visible_text_rows; + size_t text_col, text_row, text_top; + bool show_text; + bool show_text_ever; // has show_text ever been true? + + char menu[kMaxRows][kMaxCols]; + bool show_menu; + const char* const* menu_headers_; + int menu_items, menu_sel; + int menu_start, menu_end; + + pthread_t progress_t; + + private: + void draw_background_locked(Icon icon); + void draw_progress_locked(); + void draw_screen_locked(); + void update_screen_locked(); + static void* progress_thread(void* cookie); + void progress_loop(); + void LoadBitmap(const char* filename, GRSurface** surface); + void PutChar(char); + void ClearText(); + void DrawTextLine(int x, int* y, const char* line, bool bold); + void DrawTextLines(int x, int* y, const char* const* lines); +}; + +#endif // RECOVERY_WEAR_UI_H |