diff options
83 files changed, 7707 insertions, 1108 deletions
@@ -12,8 +12,9 @@ # 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) @@ -36,11 +37,23 @@ LOCAL_SRC_FILES := \ roots.cpp \ ui.cpp \ screen_ui.cpp \ + messagesocket.cpp \ asn1_decoder.cpp \ verifier.cpp \ adb_install.cpp \ fuse_sdcard_provider.c +# External tools +LOCAL_SRC_FILES += \ + ../../system/core/toolbox/dynarray.c \ + ../../system/core/toolbox/getprop.c \ + ../../system/core/toolbox/newfs_msdos.c \ + ../../system/core/toolbox/setprop.c \ + ../../system/core/toolbox/start.c \ + ../../system/core/toolbox/stop.c \ + ../../system/core/toolbox/wipe.c \ + ../../system/vold/vdc.c + LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true @@ -56,12 +69,20 @@ LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_STATIC_LIBRARIES := \ libext4_utils_static \ + libmake_ext4fs_static \ + libminizip_static \ libsparse_static \ + libfsck_msdos \ + libminipigz \ + libreboot_static \ + libvoldclient \ + libsdcard \ libminzip \ libz \ libmtdutils \ libmincrypt \ libminadbd \ + libbusybox \ libfusesideload \ libminui \ libpng \ @@ -72,12 +93,40 @@ LOCAL_STATIC_LIBRARIES := \ libstdc++ \ libutils \ libm \ - libc + libc \ + libext2_blkid \ + libext2_uuid -ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) +# 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 + +#ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) LOCAL_CFLAGS += -DUSE_EXT4 LOCAL_C_INCLUDES += system/extras/ext4_utils system/vold - LOCAL_STATIC_LIBRARIES += libext4_utils_static libz + LOCAL_STATIC_LIBRARIES += libext4_utils_static libz liblz4-static +#endif + +ifeq ($(BOARD_HAS_DOWNLOAD_MODE), true) + LOCAL_CFLAGS += -DDOWNLOAD_MODE +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 external/e2fsprogs/lib + +ifneq ($(BOARD_RECOVERY_BLDRMSG_OFFSET),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_BLDRMSG_OFFSET=$(BOARD_RECOVERY_BLDRMSG_OFFSET) +endif + +# 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 # This binary is in the recovery ramdisk, which is otherwise a copy of root. @@ -92,11 +141,143 @@ else LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) endif +LOCAL_LDFLAGS += -Wl,--no-fatal-warnings + LOCAL_C_INCLUDES += system/extras/ext4_utils LOCAL_C_INCLUDES += external/openssl/include +# Symlinks +RECOVERY_LINKS := busybox getprop make_ext4fs minizip reboot sdcard setup_adbd setprop start stop vdc + +RECOVERY_SYMLINKS := $(addprefix $(TARGET_RECOVERY_ROOT_OUT)/sbin/,$(RECOVERY_LINKS)) + +BUSYBOX_LINKS := $(shell cat external/busybox/busybox-minimal.links) +exclude := tune2fs mke2fs +RECOVERY_BUSYBOX_SYMLINKS := $(addprefix $(TARGET_RECOVERY_ROOT_OUT)/sbin/,$(filter-out $(exclude),$(notdir $(BUSYBOX_LINKS)))) + +ifeq ($(ONE_SHOT_MAKEFILE),) + +LOCAL_ADDITIONAL_DEPENDENCIES += \ + minivold \ + recovery_e2fsck \ + recovery_mke2fs \ + recovery_tune2fs + +ifeq ($(TARGET_USES_EXFAT),true) +LOCAL_ADDITIONAL_DEPENDENCIES += \ + mount.exfat_static +endif + +LOCAL_ADDITIONAL_DEPENDENCIES += \ + bu_recovery + +LOCAL_ADDITIONAL_DEPENDENCIES += $(RECOVERY_SYMLINKS) $(RECOVERY_BUSYBOX_SYMLINKS) + +ifneq ($(TARGET_RECOVERY_DEVICE_MODULES),) + LOCAL_ADDITIONAL_DEPENDENCIES += $(TARGET_RECOVERY_DEVICE_MODULES) +endif + +endif + +include $(BUILD_EXECUTABLE) + +$(RECOVERY_SYMLINKS): RECOVERY_BINARY := $(LOCAL_MODULE) +$(RECOVERY_SYMLINKS): + @echo "Symlink: $@ -> $(RECOVERY_BINARY)" + @mkdir -p $(dir $@) + @rm -rf $@ + $(hide) ln -sf $(RECOVERY_BINARY) $@ + +# Now let's do recovery symlinks +$(RECOVERY_BUSYBOX_SYMLINKS): BUSYBOX_BINARY := busybox +$(RECOVERY_BUSYBOX_SYMLINKS): + @echo "Symlink: $@ -> $(BUSYBOX_BINARY)" + @mkdir -p $(dir $@) + @rm -rf $@ + $(hide) ln -sf $(BUSYBOX_BINARY) $@ + +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 +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 liblz4-static +#endif +LOCAL_STATIC_LIBRARIES += \ + libsparse_static \ + libvoldclient \ + 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 +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) + +# 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_MODULE := libverifier @@ -115,14 +296,17 @@ LOCAL_SRC_FILES := \ verifier_test.cpp \ asn1_decoder.cpp \ verifier.cpp \ - ui.cpp + ui.cpp \ + messagesocket.cpp LOCAL_STATIC_LIBRARIES := \ + libvoldclient \ libmincrypt \ libminui \ libminzip \ libcutils \ libstdc++ \ libc +LOCAL_C_INCLUDES += system/core/fs_mgr/include include $(BUILD_EXECUTABLE) @@ -135,4 +319,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)/voldclient/Android.mk + +endif diff --git a/CleanSpec.mk b/CleanSpec.mk index e2d97d42..581174b1 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -49,3 +49,4 @@ # ************************************************ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libvoldclient_intermediates) diff --git a/adb_install.cpp b/adb_install.cpp index be3b9a06..ffa63b2a 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -36,6 +36,7 @@ extern "C" { } static RecoveryUI* ui = NULL; +static pthread_t sideload_thread; static void set_usb_driver(bool enabled) { @@ -70,19 +71,20 @@ maybe_restart_adbd() { } } +struct sideload_data { + int* 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_, int* wipe_cache, const char* install_file) { - 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) { @@ -90,47 +92,60 @@ apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) { _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", strerror(errno)); - result = INSTALL_ERROR; - kill(child, SIGKILL); - break; - } + if (sideload_data.cancel) { + break; + } + + status = stat(FUSE_SIDELOAD_HOST_PATHNAME, &st); + if (status == 0) { + break; + } + if (errno != ENOENT && errno != ENOTCONN) { + ui->Print("\nError %s waiting for package\n\n", strerror(errno)); + result = INSTALL_ERROR; + break; } - result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false); - 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"); @@ -139,8 +154,42 @@ apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) { } } - set_usb_driver(false); + LOGI("sideload thread finished\n"); + return NULL; +} + +void +start_sideload(RecoveryUI* ui_, int* wipe_cache, const char* install_file) { + 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 a18b712a..089886cf 100644 --- a/adb_install.h +++ b/adb_install.h @@ -19,6 +19,8 @@ class RecoveryUI; -int apply_from_adb(RecoveryUI* h, int* wipe_cache, const char* install_file); +void start_sideload(RecoveryUI* h, int* wipe_cache, const char* install_file); +void stop_sideload(); +int wait_sideload(); #endif diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 4984093d..3fd1625b 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -18,7 +18,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := applypatch.c bspatch.c freecache.c imgpatch.c utils.c LOCAL_MODULE := libapplypatch LOCAL_MODULE_TAGS := eng -LOCAL_C_INCLUDES += external/bzip2 external/zlib bootable/recovery +LOCAL_C_INCLUDES += external/bzip2 external/zlib $(LOCAL_PATH)/.. LOCAL_STATIC_LIBRARIES += libmtdutils libmincrypt libbz libz include $(BUILD_STATIC_LIBRARY) @@ -27,7 +27,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := main.c LOCAL_MODULE := applypatch -LOCAL_C_INCLUDES += bootable/recovery +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz LOCAL_SHARED_LIBRARIES += libz libcutils libstdc++ libc @@ -39,7 +39,7 @@ LOCAL_SRC_FILES := main.c LOCAL_MODULE := applypatch_static LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := eng -LOCAL_C_INCLUDES += bootable/recovery +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c index 2c86e098..53890177 100644 --- a/applypatch/applypatch.c +++ b/applypatch/applypatch.c @@ -61,7 +61,7 @@ int LoadFileContents(const char* filename, FileContents* file) { if (stat(filename, &file->st) != 0) { printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return -1; + return (errno == ENOENT ? -ENOENT : -1); } file->size = file->st.st_size; @@ -584,7 +584,12 @@ int applypatch_check(const char* filename, // LoadFileContents is successful. (Useful for reading // partitions, where the filename encodes the sha1s; no need to // check them twice.) - if (LoadFileContents(filename, &file) != 0 || + int filestate = LoadFileContents(filename, &file); + if (filestate == -ENOENT) { + return -ENOENT; + } + + if (filestate != 0 || (num_patches > 0 && FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0)) { printf("file \"%s\" doesn't have any of expected " diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c index 05c4f250..3bac8be9 100644 --- a/applypatch/imgdiff.c +++ b/applypatch/imgdiff.c @@ -408,6 +408,7 @@ unsigned char* ReadImage(const char* filename, p[2] == 0x08 && // deflate compression p[3] == 0x00) { // no header flags // 'pos' is the offset of the start of a gzip chunk. + size_t chunk_offset = pos; *num_chunks += 3; *chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk)); @@ -453,6 +454,14 @@ unsigned char* ReadImage(const char* filename, strm.avail_out = allocated - curr->len; 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; + } curr->len = allocated - strm.avail_out; if (strm.avail_out == 0) { allocated *= 2; diff --git a/backup.cpp b/backup.cpp new file mode 100644 index 00000000..09b00944 --- /dev/null +++ b/backup.cpp @@ -0,0 +1,298 @@ +#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" + +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 = lseek(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 = lseek(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") && is_data_media()) { + 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 = lseek(fd, 0, SEEK_END); + close(fd); + + th_set_from_stat(t, &st); + th_set_path(t, savename); + if (th_write(t) != 0) { + logmsg("tar_append_device_contents: th_write failed\n"); + return -1; + } + if (tar_append_regfile(t, devname) != 0) { + logmsg("tar_append_device_contents: tar_append_regfile %s failed\n", devname); + return -1; + } + return 0; +} + +int do_backup(int argc, char **argv) +{ + int rc = 1; + int n; + int i; + + int len; + int written; + + const char* opt_compress = "gzip"; + const char* opt_hash = "md5"; + + int optidx = 0; + while (optidx < argc && argv[optidx][0] == '-' && argv[optidx][1] == '-') { + char* optname = &argv[optidx][2]; + ++optidx; + char* optval = strchr(optname, '='); + if (optval) { + *optval = '\0'; + ++optval; + } + else { + if (optidx >= argc) { + logmsg("No argument to --%s\n", optname); + return -1; + } + optval = argv[optidx]; + ++optidx; + } + if (!strcmp(optname, "compress")) { + opt_compress = optval; + logmsg("do_backup: compress=%s\n", opt_compress); + } + else if (!strcmp(optname, "hash")) { + opt_hash = optval; + logmsg("do_backup: hash=%s\n", opt_hash); + } + else { + logmsg("do_backup: invalid option name \"%s\"\n", optname); + return -1; + } + } + for (n = optidx; n < argc; ++n) { + const char* partname = argv[n]; + if (*partname == '-') + ++partname; + if (part_add(partname) != 0) { + logmsg("Failed to add partition %s\n", partname); + return -1; + } + } + + rc = create_tar(adb_ofd, opt_compress, "w"); + if (rc != 0) { + logmsg("do_backup: cannot open tar stream\n"); + return rc; + } + + append_sod(opt_hash); + + hash_name = strdup(opt_hash); + + for (i = 0; i < MAX_PART; ++i) { + partspec* curpart = part_get(i); + if (!curpart) + break; + + part_set(curpart); + if (!volume_is_mountable(curpart->vol) || + volume_is_readonly(curpart->vol) || + volume_is_verity(curpart->vol)) { + rc = tar_append_device_contents(tar, curpart->vol->blk_device, curpart->name); + } + else { + if (ensure_path_mounted(curpart->path) != 0) { + rc = tar_append_device_contents(tar, curpart->vol->blk_device, curpart->name); + if (rc != 0) { + logmsg("do_backup: cannot backup %s\n", curpart->path); + continue; + } + } + String8 path(curpart->path); + rc = do_backup_tree(path); + ensure_path_unmounted(curpart->path); + } + } + + free(hash_name); + hash_name = NULL; + + append_eod(opt_hash); + + tar_append_eof(tar); + + if (opt_compress) + gzflush(gzf, Z_FINISH); + + logmsg("backup complete: rc=%d\n", rc); + + return rc; +} + diff --git a/bootloader.cpp b/bootloader.cpp index 600d238f..ce795498 100644 --- a/bootloader.cpp +++ b/bootloader.cpp @@ -26,16 +26,16 @@ #include <sys/stat.h> #include <unistd.h> -static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v); -static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v); -static int get_bootloader_message_block(struct bootloader_message *out, const Volume* v); -static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v); +static int get_bootloader_message_mtd(struct bootloader_message *out, const fstab_rec* v); +static int set_bootloader_message_mtd(const struct bootloader_message *in, const fstab_rec* v); +static int get_bootloader_message_block(struct bootloader_message *out, const fstab_rec* v); +static int set_bootloader_message_block(const struct bootloader_message *in, const fstab_rec* v); int get_bootloader_message(struct bootloader_message *out) { - Volume* v = volume_for_path("/misc"); + fstab_rec* 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); @@ -47,10 +47,10 @@ int get_bootloader_message(struct bootloader_message *out) { } int set_bootloader_message(const struct bootloader_message *in) { - Volume* v = volume_for_path("/misc"); + fstab_rec* 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); @@ -69,7 +69,7 @@ static const int MISC_PAGES = 3; // number of pages to save static const int MISC_COMMAND_PAGE = 1; // bootloader command is this page static int get_bootloader_message_mtd(struct bootloader_message *out, - const Volume* v) { + const fstab_rec* v) { size_t write_size; mtd_scan_partitions(); const MtdPartition *part = mtd_find_partition_by_name(v->blk_device); @@ -95,7 +95,7 @@ static int get_bootloader_message_mtd(struct bootloader_message *out, return 0; } static int set_bootloader_message_mtd(const struct bootloader_message *in, - const Volume* v) { + const fstab_rec* v) { size_t write_size; mtd_scan_partitions(); const MtdPartition *part = mtd_find_partition_by_name(v->blk_device); @@ -161,13 +161,16 @@ static void wait_for_device(const char* fn) { } static int get_bootloader_message_block(struct bootloader_message *out, - const Volume* v) { + const fstab_rec* v) { wait_for_device(v->blk_device); 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 struct bootloader_message temp; int count = fread(&temp, sizeof(temp), 1, f); if (count != 1) { @@ -183,13 +186,16 @@ static int get_bootloader_message_block(struct bootloader_message *out, } static int set_bootloader_message_block(const struct bootloader_message *in, - const Volume* v) { + const fstab_rec* 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,389 @@ +#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" + +#define PATHNAME_RC "/tmp/burc" + +#define PATHNAME_XCOMP_ENABLE "/sys/fs/xcomp/enable" + +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) +{ + fstab_rec* 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(); +// vold_client_start(&v_callbacks, 1); + + 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,61 @@ +#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 + +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif +#ifndef max +#define max(a,b) ((a)<(b)?(b):(a)) +#endif + +#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; + fstab_rec* 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); @@ -39,8 +39,6 @@ extern "C" { #define STRINGIFY(x) #x #define EXPAND(x) STRINGIFY(x) -typedef struct fstab_rec Volume; - // fopen a file, mounting volumes and making parent dirs as necessary. FILE* fopen_path(const char *path, const char *mode); diff --git a/default_device.cpp b/default_device.cpp index 97806ac5..7077d49d 100644 --- a/default_device.cpp +++ b/default_device.cpp @@ -20,42 +20,89 @@ #include "device.h" #include "screen_ui.h" -static const char* HEADERS[] = { "Volume up/down to move highlight;", - "enter button to select.", +#include "roots.h" + +static const char* HEADERS[] = { "Swipe up/down to change selections;", + "swipe right to select, or left to go back.", "", NULL }; -static const char* ITEMS[] = {"reboot system now", - "apply update from ADB", - "wipe data/factory reset", - "wipe cache partition", - "reboot to bootloader", - "power down", - "view recovery logs", +static const char* ITEMS[] = {"Reboot system now", + "Apply update", + "Wipe data/factory reset", + "Wipe cache partition", + "Wipe media", +#ifdef DOWNLOAD_MODE + "Reboot to download mode", +#else + "Reboot to bootloader", +#endif + "Power down", + "View recovery logs", NULL }; +static Device::BuiltinAction ACTIONS[] = { + Device::REBOOT, + Device::APPLY_UPDATE, + Device::WIPE_DATA, + Device::WIPE_CACHE, + Device::WIPE_MEDIA, + Device::REBOOT_BOOTLOADER, + Device::SHUTDOWN, + Device::READ_RECOVERY_LASTLOG, + Device::NO_ACTION +}; + +extern int ui_root_menu; + class DefaultDevice : public Device { public: DefaultDevice() : ui(new ScreenRecoveryUI) { + // Remove "wipe media" option for non-datamedia devices + if (!is_data_media()) { + int i; + for (i = 4; ITEMS[i+1] != NULL; ++i) { + ITEMS[i] = ITEMS[i+1]; + ACTIONS[i] = ACTIONS[i+1]; + } + ITEMS[i] = NULL; + ACTIONS[i] = NO_ACTION; + } } RecoveryUI* GetUI() { return ui; } int HandleMenuKey(int key, int visible) { if (visible) { + 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_HOME: + case KEY_HOMEPAGE: + case KEY_SEND: return kInvokeItem; + + case KEY_BACKSPACE: + case KEY_BACK: + if (!ui_root_menu) + return kGoBack; } } @@ -63,16 +110,12 @@ class DefaultDevice : public Device { } BuiltinAction InvokeMenuItem(int menu_position) { - switch (menu_position) { - case 0: return REBOOT; - case 1: return APPLY_ADB_SIDELOAD; - case 2: return WIPE_DATA; - case 3: return WIPE_CACHE; - case 4: return REBOOT_BOOTLOADER; - case 5: return SHUTDOWN; - case 6: return READ_RECOVERY_LASTLOG; - default: return NO_ACTION; + if (menu_position < 0 || + menu_position >= (int)(sizeof(ITEMS)/sizeof(ITEMS[0])) || + ITEMS[menu_position] == NULL) { + return NO_ACTION; } + return ACTIONS[menu_position]; } const char* const* GetMenuHeaders() { return HEADERS; } @@ -19,6 +19,8 @@ #include "ui.h" +#define KEY_FLAG_ABS 0x8000 + class Device { public: virtual ~Device() { } @@ -65,9 +67,8 @@ class Device { // - invoke a specific action (a menu position: any non-negative number) virtual int HandleMenuKey(int key, int visible) = 0; - enum BuiltinAction { NO_ACTION, REBOOT, APPLY_EXT, - APPLY_CACHE, // APPLY_CACHE is deprecated; has no effect - APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE, + enum BuiltinAction { NO_ACTION, REBOOT, APPLY_UPDATE, + WIPE_DATA, WIPE_CACHE, WIPE_MEDIA, REBOOT_BOOTLOADER, SHUTDOWN, READ_RECOVERY_LASTLOG }; // Perform a recovery action selected from the menu. @@ -85,6 +86,8 @@ class Device { static const int kHighlightUp = -2; static const int kHighlightDown = -3; static const int kInvokeItem = -4; + static const int kGoBack = -5; + static const int kRefresh = -6; // Called when we do a wipe data/factory reset operation (either via a // reboot from the main system with the --wipe_data flag, or when the @@ -94,6 +97,9 @@ class Device { // are erased AFTER this returns (whether it returns success or not). virtual int WipeData() { return 0; } + // Same as above for media (/data/media and primary storage) + virtual int WipeMedia() { return 0; } + // Return the headers (an array of strings, one per line, // NULL-terminated) for the main menu. Typically these tell users // what to push to move the selection and invoke the selected diff --git a/etc/init.rc b/etc/init.rc index c78a44a2..60deb8ea 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -29,6 +29,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 @@ -92,18 +112,37 @@ 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 + socket vold 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/fuse_sdcard_provider.c b/fuse_sdcard_provider.c index 19fb52df..c7645813 100644 --- a/fuse_sdcard_provider.c +++ b/fuse_sdcard_provider.c @@ -20,6 +20,7 @@ #include <pthread.h> #include <sys/mount.h> #include <sys/stat.h> +#include <sys/wait.h> #include <unistd.h> #include <fcntl.h> @@ -62,7 +63,7 @@ static void close_file(void* cookie) { } struct token { - pthread_t th; + pid_t pid; const char* path; int result; }; @@ -104,25 +105,31 @@ void* start_sdcard_fuse(const char* path) { struct token* t = malloc(sizeof(struct 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); } - // 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); + time_t start_time = time(NULL); + time_t now = start_time; + + 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; } @@ -131,11 +138,9 @@ void finish_sdcard_fuse(void* cookie) { if (cookie == NULL) return; struct token* t = (struct 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); } diff --git a/fuse_sideload.c b/fuse_sideload.c index ab91defb..16edc3aa 100644 --- a/fuse_sideload.c +++ b/fuse_sideload.c @@ -64,10 +64,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 @@ -83,7 +83,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 @@ -91,8 +91,81 @@ 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) +{ + struct block_entry* entry; + 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; @@ -105,12 +178,12 @@ static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, siz vec[0].iov_base = &hdr; vec[0].iov_len = sizeof(hdr); - vec[1].iov_base = data; + vec[1].iov_base = /* const_cast */(void*)(data); vec[1].iov_len = len; res = writev(fd->ffd, vec, 2); if (res < 0) { - printf("*** REPLY FAILED *** %d\n", errno); + printf("*** REPLY FAILED *** %s\n", strerror(errno)); } } @@ -154,14 +227,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, @@ -176,23 +247,17 @@ static int handle_lookup(void* data, struct fuse_data* fd, out.nodeid = PACKAGE_FILE_ID; out.generation = PACKAGE_FILE_ID; fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); - } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, 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; if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; struct fuse_open_out out; @@ -223,6 +288,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, @@ -262,6 +332,7 @@ static int fetch_block(struct fuse_data* fd, uint32_t block) { } memcpy(blockhash, hash, SHA256_DIGEST_SIZE); + block_cache_enter(fd, block); return 0; } @@ -338,6 +409,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) { @@ -395,6 +472,26 @@ 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; + uint64_t mem = free_memory(); + uint64_t avail = mem - (INSTALL_REQUIRED_MEMORY + fd.file_blocks * sizeof(uint8_t*)); + 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"); @@ -404,7 +501,7 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, char opts[256]; snprintf(opts, sizeof(opts), - ("fd=%d,user_id=%d,group_id=%d,max_read=%zu," + ("fd=%d,user_id=%d,group_id=%d,max_read=%u," "allow_other,rootmode=040000"), fd.ffd, fd.uid, fd.gid, block_size); @@ -415,7 +512,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 = read(fd.ffd, request_buffer, sizeof(request_buffer)); if (len < 0) { if (errno != EINTR) { @@ -472,11 +579,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); @@ -495,6 +597,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 c0b16efb..71a09a67 100644 --- a/fuse_sideload.h +++ b/fuse_sideload.h @@ -21,8 +21,6 @@ #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 diff --git a/install.cpp b/install.cpp index 9db5640a..a66eacc5 100644 --- a/install.cpp +++ b/install.cpp @@ -21,6 +21,7 @@ #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> +#include <setjmp.h> #include "common.h" #include "install.h" @@ -34,6 +35,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" @@ -45,6 +48,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, int* wipe_cache) { @@ -174,7 +183,12 @@ try_update_binary(const char *path, ZipArchive *zip, int* 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; } @@ -184,11 +198,34 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { static int really_install_package(const char *path, int* 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. @@ -216,16 +253,31 @@ really_install_package(const char *path, int* 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. @@ -235,20 +287,23 @@ really_install_package(const char *path, int* 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; +out: + set_perf_mode(false); + return ret; } int @@ -276,3 +331,8 @@ install_package(const char* path, int* 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, int* 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 00000000..355a667e --- /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 00000000..5a4c67d2 --- /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 04956d87..3fd52bb7 100644 --- a/minadbd/Android.mk +++ b/minadbd/Android.mk @@ -23,7 +23,7 @@ LOCAL_SRC_FILES := \ LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE -LOCAL_C_INCLUDES += bootable/recovery +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. LOCAL_MODULE := libminadbd diff --git a/minui/Android.mk b/minui/Android.mk index df4aac16..01c58e4f 100644 --- a/minui/Android.mk +++ b/minui/Android.mk @@ -1,32 +1,57 @@ LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_SRC_FILES := graphics.c graphics_adf.c graphics_fbdev.c events.c \ +common_cflags := + +common_src_files := graphics.c graphics_adf.c graphics_fbdev.c events.c \ resources.c -LOCAL_C_INCLUDES +=\ +common_c_includes := \ external/libpng\ external/zlib -LOCAL_WHOLE_STATIC_LIBRARIES += libadf +common_additional_dependencies := -LOCAL_MODULE := libminui +common_whole_static_libraries := libadf -# This used to compare against values in double-quotes (which are just -# ordinary characters in this context). Strip double-quotes from the -# value so that either will work. +common_cflags += -std=gnu11 +ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),ABGR_8888) + common_cflags += -DRECOVERY_ABGR +endif ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888) - LOCAL_CFLAGS += -DRECOVERY_RGBX + common_cflags += -DRECOVERY_RGBX endif + ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888) - LOCAL_CFLAGS += -DRECOVERY_BGRA + common_cflags += -DRECOVERY_BGRA endif ifneq ($(TARGET_RECOVERY_OVERSCAN_PERCENT),) - LOCAL_CFLAGS += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT) + common_cflags += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT) else - LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0 + common_cflags += -DOVERSCAN_PERCENT=0 +endif + +ifneq ($(BOARD_RECOVERY_NEEDS_FBIOPAN_DISPLAY),) + common_cflags += -DBOARD_RECOVERY_NEEDS_FBIOPAN_DISPLAY endif +include $(CLEAR_VARS) +LOCAL_MODULE := libminui +LOCAL_SRC_FILES := $(common_src_files) +LOCAL_ADDITIONAL_DEPENDENCIES := $(common_additional_dependencies) +LOCAL_C_INCLUDES += $(common_c_includes) +LOCAL_CFLAGS := $(common_cflags) +LOCAL_WHOLE_STATIC_LIBRARIES := $(common_whole_static_libraries) include $(BUILD_STATIC_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_MODULE := libminui +LOCAL_SRC_FILES := $(common_src_files) +LOCAL_ADDITIONAL_DEPENDENCIES := $(common_additional_dependencies) +LOCAL_C_INCLUDES += $(common_c_includes) +LOCAL_SHARED_LIBRARIES := libpng +LOCAL_CFLAGS += $(common_cflags) -DSHARED_MINUI +LOCAL_WHOLE_STATIC_LIBRARIES := $(common_whole_static_libraries) +include $(BUILD_SHARED_LIBRARY) diff --git a/minui/events.c b/minui/events.c index df7dad44..bde09ff1 100644 --- a/minui/events.c +++ b/minui/events.c @@ -23,6 +23,7 @@ #include <linux/input.h> #include "minui.h" +#include "cutils/log.h" #define MAX_DEVICES 16 #define MAX_MISC_FDS 16 @@ -79,7 +80,7 @@ int ev_init(ev_callback input_cb, void *data) /* TODO: add ability to specify event masks. For now, just assume * that only EV_KEY and EV_REL event types are ever needed. */ - if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits)) { + if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_ABS, ev_bits)) { close(fd); continue; } @@ -97,7 +98,7 @@ int ev_init(ev_callback input_cb, void *data) ev_fdinfo[ev_count].data = data; ev_count++; ev_dev_count++; - if(ev_dev_count == MAX_DEVICES) break; + if (ev_dev_count == (MAX_DEVICES + MAX_MISC_FDS)) break; } } @@ -137,6 +138,22 @@ int ev_get_epollfd(void) return epollfd; } +int ev_del_fd(int fd) +{ + unsigned n; + for (n = 0; n < ev_count; ++n) { + if (ev_fdinfo[n].fd == fd) { + if (n != ev_count-1) { + ev_fdinfo[n] = ev_fdinfo[ev_count-1]; + } + ev_count--; + ev_misc_count--; + return 1; + } + } + return 0; +} + void ev_exit(void) { while (ev_count > 0) { diff --git a/minui/font_10x18.h b/minui/font_10x18.h deleted file mode 100644 index 29d70534..00000000 --- a/minui/font_10x18.h +++ /dev/null @@ -1,214 +0,0 @@ -struct { - unsigned width; - unsigned height; - unsigned cwidth; - unsigned cheight; - unsigned char rundata[2973]; -} font = { - .width = 960, - .height = 18, - .cwidth = 10, - .cheight = 18, - .rundata = { -0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82, -0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06, -0x84,0x0a,0x81,0x03,0x88,0x04,0x84,0x04,0x88,0x04,0x84,0x06,0x84,0x1e,0x81, -0x0e,0x81,0x0a,0x84,0x06,0x84,0x07,0x82,0x05,0x85,0x07,0x84,0x04,0x86,0x04, -0x88,0x02,0x88,0x04,0x84,0x04,0x82,0x04,0x82,0x02,0x88,0x05,0x86,0x01,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04, -0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02, -0x88,0x03,0x86,0x0e,0x86,0x06,0x82,0x11,0x82,0x10,0x82,0x18,0x82,0x0f,0x84, -0x0d,0x82,0x1c,0x82,0x09,0x84,0x7f,0x16,0x84,0x05,0x82,0x05,0x84,0x07,0x83, -0x02,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x03,0x86,0x04, -0x83,0x02,0x82,0x03,0x82,0x01,0x82,0x07,0x82,0x09,0x82,0x06,0x82,0x3e,0x82, -0x04,0x84,0x06,0x83,0x06,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x03, -0x82,0x09,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82, -0x1c,0x82,0x0e,0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x05,0x84,0x04, -0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82, -0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x04, -0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82, -0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02, -0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82, -0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x0c, -0x82,0x05,0x84,0x11,0x82,0x0f,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82, -0x1c,0x82,0x0b,0x82,0x7f,0x15,0x82,0x08,0x82,0x08,0x82,0x05,0x82,0x01,0x82, -0x01,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x02,0x82,0x01, -0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x07,0x82, -0x08,0x82,0x08,0x82,0x3d,0x82,0x03,0x82,0x02,0x82,0x04,0x84,0x05,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x06,0x83,0x03,0x82,0x08,0x82,0x04,0x81,0x09,0x82, -0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x1a,0x82,0x10,0x82,0x06,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02, -0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83, -0x02,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02, -0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x08,0x82,0x0c,0x82,0x04,0x82,0x02,0x82, -0x11,0x82,0x0e,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,0x0b,0x82,0x0b, -0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x05,0x82, -0x02,0x83,0x1a,0x82,0x07,0x81,0x02,0x81,0x07,0x82,0x01,0x82,0x02,0x82,0x01, -0x82,0x05,0x82,0x01,0x84,0x04,0x82,0x01,0x82,0x07,0x82,0x08,0x82,0x08,0x82, -0x06,0x82,0x02,0x82,0x06,0x82,0x28,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01, -0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x84,0x03,0x82,0x08,0x82, -0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x19,0x82,0x12,0x82,0x05, -0x82,0x04,0x82,0x02,0x82,0x02,0x84,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82, -0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04, -0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,0x02,0x83, -0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,0x05,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08, -0x82,0x04,0x82,0x09,0x82,0x0b,0x82,0x03,0x82,0x04,0x82,0x20,0x82,0x18,0x82, -0x0e,0x82,0x10,0x82,0x0b,0x82,0x0b,0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45, -0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x88,0x01,0x82,0x01,0x82,0x06,0x83, -0x01,0x82,0x04,0x84,0x08,0x81,0x08,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06, -0x82,0x28,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x08,0x82,0x04,0x82, -0x01,0x82,0x03,0x82,0x08,0x82,0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x18,0x82,0x06,0x88,0x06,0x82,0x04,0x82,0x04,0x82,0x02,0x82,0x01,0x85, -0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02, -0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82, -0x02,0x82,0x04,0x82,0x08,0x88,0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02, -0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82, -0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x84,0x06, -0x84,0x08,0x82,0x05,0x82,0x09,0x82,0x0b,0x82,0x2b,0x82,0x18,0x82,0x0e,0x82, -0x10,0x82,0x1c,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x26, -0x82,0x11,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x09,0x82,0x06,0x82,0x12,0x82, -0x0a,0x82,0x06,0x84,0x07,0x82,0x27,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0b, -0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,0x83,0x04,0x82,0x01,0x83, -0x08,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x83,0x07,0x83,0x05, -0x82,0x16,0x82,0x08,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82, -0x02,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08, -0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82, -0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82, -0x0a,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01, -0x82,0x04,0x84,0x06,0x84,0x08,0x82,0x05,0x82,0x0a,0x82,0x0a,0x82,0x23,0x85, -0x03,0x82,0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x84,0x04,0x86,0x05, -0x85,0x01,0x81,0x02,0x82,0x01,0x83,0x05,0x84,0x09,0x84,0x02,0x82,0x03,0x82, -0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x04, -0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x02,0x82,0x01,0x84,0x04,0x86,0x03,0x86, -0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x03,0x87,0x05,0x82,0x08,0x82,0x08,0x82,0x26,0x82, -0x11,0x82,0x01,0x82,0x04,0x86,0x07,0x82,0x05,0x83,0x12,0x82,0x0a,0x82,0x04, -0x88,0x02,0x88,0x0c,0x88,0x10,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0a,0x82, -0x06,0x83,0x04,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x03,0x83,0x02,0x82,0x07, -0x82,0x06,0x84,0x05,0x82,0x02,0x83,0x05,0x83,0x07,0x83,0x04,0x82,0x18,0x82, -0x06,0x82,0x04,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x86,0x04, -0x82,0x08,0x82,0x04,0x82,0x02,0x86,0x04,0x86,0x04,0x82,0x02,0x84,0x02,0x88, -0x05,0x82,0x0a,0x82,0x03,0x85,0x05,0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02, -0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82, -0x04,0x82,0x02,0x82,0x03,0x82,0x05,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x03, -0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x08,0x82,0x08,0x82, -0x06,0x82,0x0a,0x82,0x0a,0x82,0x22,0x82,0x03,0x82,0x02,0x83,0x02,0x82,0x04, -0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x05,0x82,0x06,0x82, -0x03,0x83,0x02,0x83,0x02,0x82,0x06,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,0x07, -0x82,0x05,0x88,0x02,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82, -0x04,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06, -0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82, -0x03,0x82,0x04,0x82,0x08,0x82,0x02,0x84,0x09,0x82,0x09,0x84,0x23,0x82,0x11, -0x82,0x01,0x82,0x06,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x01,0x82,0x11,0x82, -0x0a,0x82,0x06,0x84,0x07,0x82,0x26,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x08, -0x83,0x09,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x02,0x82,0x04,0x82,0x05,0x82, -0x06,0x82,0x02,0x82,0x05,0x83,0x01,0x82,0x17,0x82,0x16,0x82,0x06,0x82,0x05, -0x82,0x01,0x82,0x01,0x82,0x02,0x88,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, -0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82, -0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x86,0x04,0x82,0x04,0x82,0x02, -0x86,0x09,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82, -0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,0x82,0x27, -0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82, -0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02, -0x82,0x01,0x82,0x08,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82, -0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x07, -0x82,0x0a,0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82, -0x04,0x84,0x04,0x82,0x04,0x82,0x07,0x82,0x06,0x82,0x08,0x82,0x08,0x82,0x26, -0x82,0x0f,0x88,0x05,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x01,0x82, -0x0d,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,0x82,0x26,0x82,0x05,0x82,0x04, -0x82,0x05,0x82,0x07,0x82,0x0c,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82, -0x05,0x82,0x05,0x82,0x04,0x82,0x08,0x82,0x18,0x82,0x14,0x82,0x07,0x82,0x05, -0x82,0x01,0x84,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, -0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82, -0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02, -0x82,0x02,0x82,0x0a,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82, -0x01,0x82,0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09, -0x82,0x22,0x87,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88, -0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02, -0x84,0x09,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x86,0x05, -0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82, -0x05,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x08,0x82,0x08,0x82,0x26, -0x82,0x10,0x82,0x01,0x82,0x07,0x82,0x01,0x82,0x04,0x82,0x01,0x83,0x02,0x82, -0x03,0x83,0x0f,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x25,0x82,0x07, -0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x09,0x82, -0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,0x82,0x08,0x82,0x19,0x82,0x05, -0x88,0x05,0x82,0x08,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,0x03,0x82,0x03,0x82, -0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02, -0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x05,0x82, -0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x06, -0x82,0x06,0x82,0x08,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,0x04,0x82,0x02,0x82, -0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03, -0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x85,0x08,0x82,0x05,0x82, -0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,0x06,0x82,0x04,0x82, -0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05, -0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x38,0x82,0x01,0x82,0x04,0x82,0x01,0x82, -0x01,0x82,0x04,0x84,0x01,0x82,0x01,0x82,0x03,0x82,0x10,0x82,0x08,0x82,0x30, -0x83,0x06,0x82,0x07,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x04,0x82, -0x07,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04, -0x82,0x03,0x81,0x04,0x82,0x1a,0x82,0x10,0x82,0x10,0x82,0x08,0x82,0x04,0x82, -0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08, -0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82, -0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02, -0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x02,0x84,0x02,0x82,0x03,0x82,0x03,0x82, -0x04,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x05,0x83,0x02,0x83,0x03, -0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0c,0x82,0x08,0x82,0x21,0x82, -0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a, -0x82,0x07,0x85,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x02,0x82, -0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82, -0x06,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x04,0x84,0x04, -0x82,0x04,0x82,0x04,0x82,0x09,0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x82, -0x01,0x82,0x05,0x86,0x04,0x82,0x01,0x82,0x01,0x82,0x01,0x83,0x01,0x84,0x10, -0x82,0x06,0x82,0x1d,0x83,0x11,0x83,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x82, -0x09,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, -0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x83,0x07,0x83,0x09,0x82, -0x0e,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03, -0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x09,0x82, -0x02,0x83,0x02,0x82,0x04,0x82,0x05,0x82,0x06,0x82,0x01,0x82,0x04,0x82,0x04, -0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82, -0x03,0x82,0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06, -0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82, -0x05,0x82,0x05,0x82,0x09,0x82,0x0d,0x82,0x07,0x82,0x21,0x82,0x04,0x82,0x02, -0x83,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x03,0x82, -0x04,0x82,0x06,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x03, -0x82,0x06,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82, -0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x07,0x82,0x04, -0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x02,0x83,0x05,0x82,0x05,0x88,0x03,0x82, -0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x0a,0x82,0x08,0x82,0x08,0x82,0x26, -0x82,0x1c,0x82,0x06,0x82,0x02,0x83,0x03,0x84,0x02,0x82,0x10,0x82,0x04,0x82, -0x1e,0x83,0x11,0x83,0x05,0x82,0x0a,0x82,0x05,0x88,0x02,0x88,0x04,0x84,0x09, -0x82,0x05,0x84,0x06,0x84,0x05,0x82,0x09,0x84,0x06,0x84,0x07,0x83,0x07,0x83, -0x0a,0x81,0x0e,0x81,0x0b,0x82,0x07,0x85,0x03,0x82,0x04,0x82,0x02,0x86,0x06, -0x84,0x04,0x86,0x04,0x88,0x02,0x82,0x0a,0x84,0x01,0x81,0x02,0x82,0x04,0x82, -0x02,0x88,0x04,0x83,0x05,0x82,0x04,0x82,0x02,0x88,0x02,0x82,0x04,0x82,0x02, -0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x0a,0x85,0x03,0x82,0x04,0x82,0x04,0x84, -0x07,0x82,0x07,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, -0x82,0x05,0x88,0x03,0x86,0x09,0x82,0x03,0x86,0x22,0x85,0x01,0x81,0x02,0x82, -0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x85,0x05,0x82,0x07,0x86,0x03, -0x82,0x04,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x88,0x02,0x82, -0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06, -0x83,0x01,0x82,0x03,0x82,0x08,0x86,0x06,0x84,0x05,0x83,0x01,0x82,0x05,0x82, -0x06,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x83,0x01,0x82,0x03,0x87,0x06, -0x84,0x05,0x82,0x05,0x84,0x7f,0x15,0x83,0x7f,0x14,0x83,0x7f,0x5e,0x82,0x7f, -0x05,0x89,0x47,0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x4e, -0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,0x82,0x04,0x82,0x17,0x82,0x03,0x82, -0x34,0x82,0x0e,0x82,0x48,0x82,0x04,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a, -0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x49,0x82,0x02,0x82, -0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0c,0x86,0x19,0x85,0x35,0x82,0x0e,0x82,0x4a, -0x84,0x3f, -0x00, - } -}; diff --git a/minui/graphics.c b/minui/graphics.c index 6049d85c..3109e0d2 100644 --- a/minui/graphics.c +++ b/minui/graphics.c @@ -30,15 +30,29 @@ #include <time.h> -#include "font_10x18.h" +#ifdef RECOVERY_FONT +#include RECOVERY_FONT +#else +#include "roboto_10x18.h" +#endif + #include "minui.h" #include "graphics.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#endif + typedef struct { - GRSurface* texture; - int cwidth; - int cheight; -} GRFont; + 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; @@ -72,15 +86,44 @@ void gr_font_size(int *x, int *y) *y = gr_font->cheight; } +static void icon_blend_alpha(unsigned char* src_p,int src_row_bytes, + unsigned char* dst_p, int dst_row_bytes, + int width, int height){ + int i,j; + unsigned char r,g,b,a; + + for (j = 0; j < height; ++j) { + unsigned char* sx = src_p; + unsigned char* px = dst_p; + for (i = 0; i < width; ++i) { + r = *sx++; + g = *sx++; + b = *sx++; + a = *sx++; + + *px = (*px * (255-a) + r * a )/ 255; + ++px; + + *px = (*px * (255-a) + g * a) / 255; + ++px; + + *px = (*px * (255-a) + b * a) / 255; + + ++px; + ++px; + } + src_p += src_row_bytes; + dst_p += dst_row_bytes; + } +} static void text_blend(unsigned char* src_p, int src_row_bytes, unsigned char* dst_p, int dst_row_bytes, int width, int height) { - int i, j; - for (j = 0; j < height; ++j) { + for (int j = 0; j < height; ++j) { unsigned char* sx = src_p; unsigned char* px = dst_p; - for (i = 0; i < width; ++i) { + for (int i = 0; i < width; ++i) { unsigned char a = *sx++; if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255; if (a == 255) { @@ -105,34 +148,78 @@ static void text_blend(unsigned char* src_p, int src_row_bytes, } } +/* Add text_blend one bitmap buffer */ +void gr_text_blend(int x,int y, GRFont* font) +{ + if (font == NULL ||font->texture==NULL || outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) + return; + + unsigned char* src_p = font->texture->data; + int offset = y * gr_draw->row_bytes + x * gr_draw->pixel_bytes; + unsigned char *dst_p = gr_draw->data+offset; + + text_blend(src_p,font->cwidth,dst_p,gr_draw->row_bytes,font->cwidth,font->cheight); +} + +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, int bold) { - GRFont *font = gr_font; - unsigned off; + GRFont* font = gr_font; - if (!font->texture) return; - if (gr_current_a == 0) return; + if (!font->texture || gr_current_a == 0) return; bold = bold && (font->texture->height != font->cheight); x += overscan_offset_x; y += overscan_offset_y; - while((off = *s++)) { - off -= 32; + 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 (off < 96) { - unsigned char* src_p = font->texture->data + (off * font->cwidth) + - (bold ? font->cheight * font->texture->row_bytes : 0); - unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes; + if (ch < ' ' || ch > '~') { + ch = '?'; + } - text_blend(src_p, font->texture->row_bytes, - dst_p, gr_draw->row_bytes, - font->cwidth, font->cheight); + unsigned char* src_p = font->texture->data + ((ch - ' ') * font->cwidth) + + (bold ? font->cheight * font->texture->row_bytes : 0); + unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes; + + text_blend(src_p, font->texture->row_bytes, + dst_p, gr_draw->row_bytes, + font->cwidth, font->cheight); - } x += font->cwidth; } } @@ -160,22 +247,27 @@ void gr_texticon(int x, int y, GRSurface* icon) { void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + gr_current_r = b; + gr_current_g = g; + gr_current_b = r; + gr_current_a = a; +#else gr_current_r = r; gr_current_g = g; gr_current_b = b; gr_current_a = a; +#endif } void gr_clear() { - if (gr_current_r == gr_current_g && - gr_current_r == gr_current_b) { + if (gr_current_r == gr_current_g && gr_current_r == gr_current_b) { memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes); } else { - int x, y; unsigned char* px = gr_draw->data; - for (y = 0; y < gr_draw->height; ++y) { - for (x = 0; x < gr_draw->width; ++x) { + for (int y = 0; y < gr_draw->height; ++y) { + for (int x = 0; x < gr_draw->width; ++x) { *px++ = gr_current_r; *px++ = gr_current_g; *px++ = gr_current_b; @@ -227,6 +319,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; @@ -243,14 +346,32 @@ void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes; unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes; - int i; - for (i = 0; i < h; ++i) { + for (int i = 0; i < h; ++i) { memcpy(dst_p, src_p, w * source->pixel_bytes); src_p += source->row_bytes; dst_p += gr_draw->row_bytes; } } +void gr_blend(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { + if (source == NULL) return; + + if (gr_draw->pixel_bytes != source->pixel_bytes) { + printf("gr_blit: source has wrong format\n"); + return; + } + + dx += overscan_offset_x; + dy += overscan_offset_y; + + if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return; + + unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes; + unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes; + + icon_blend_alpha(src_p, source->row_bytes, dst_p, gr_draw->row_bytes, w, h); +} + unsigned int gr_get_width(GRSurface* surface) { if (surface == NULL) { return 0; @@ -265,11 +386,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 = calloc(sizeof(*gr_font), 1); + char name[80]; + GRFont* gr_font = 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 @@ -301,6 +425,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_adf.c b/minui/graphics_adf.c index ac6d64e9..ab8f02c5 100644 --- a/minui/graphics_adf.c +++ b/minui/graphics_adf.c @@ -141,7 +141,9 @@ static gr_surface adf_init(minui_backend *backend) ssize_t n_dev_ids, i; gr_surface ret; -#if defined(RECOVERY_BGRA) +#if defined(RECOVERY_ABGR) + pdata->format = DRM_FORMAT_ABGR8888; +#elif defined(RECOVERY_BGRA) pdata->format = DRM_FORMAT_BGRA8888; #elif defined(RECOVERY_RGBX) pdata->format = DRM_FORMAT_RGBX8888; diff --git a/minui/graphics_fbdev.c b/minui/graphics_fbdev.c index 6df2726f..1f77de26 100644 --- a/minui/graphics_fbdev.c +++ b/minui/graphics_fbdev.c @@ -72,9 +72,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; } @@ -198,21 +204,8 @@ static gr_surface fbdev_flip(minui_backend* backend __unused) { set_displayed_framebuffer(1-displayed_buffer); } else { // Copy from the in-memory surface to the framebuffer. - -#if defined(RECOVERY_BGRA) - unsigned int idx; - unsigned char* ucfb_vaddr = (unsigned char*)gr_framebuffer[0].data; - unsigned char* ucbuffer_vaddr = (unsigned char*)gr_draw->data; - for (idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes); idx += 4) { - ucfb_vaddr[idx ] = ucbuffer_vaddr[idx + 2]; - ucfb_vaddr[idx + 1] = ucbuffer_vaddr[idx + 1]; - ucfb_vaddr[idx + 2] = ucbuffer_vaddr[idx ]; - ucfb_vaddr[idx + 3] = ucbuffer_vaddr[idx + 3]; - } -#else memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes); -#endif } return gr_draw; } diff --git a/minui/minui.h b/minui/minui.h index 733b675f..f7fa4b5b 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -33,6 +33,12 @@ typedef struct { unsigned char* data; } GRSurface; +typedef struct { + GRSurface* texture; + int cwidth; + int cheight; +} GRFont; + typedef GRSurface* gr_surface; int gr_init(void); @@ -47,12 +53,15 @@ 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, int bold); void gr_texticon(int x, int y, gr_surface icon); int gr_measure(const char *s); void gr_font_size(int *x, int *y); void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy); +void gr_blend(gr_surface source, int sx, int sy, int w, int h, int dx, int dy); + unsigned int gr_get_width(gr_surface surface); unsigned int gr_get_height(gr_surface surface); @@ -66,6 +75,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(void); int ev_add_fd(int fd, ev_callback cb, void *data); +int ev_del_fd(int fd); int ev_sync_key_state(ev_set_key_callback set_key_cb, void *data); /* timeout has the same semantics as for poll @@ -116,6 +126,10 @@ int res_create_localized_alpha_surface(const char* name, const char* locale, // Free a surface allocated by any of the res_create_*_surface() // functions. void res_free_surface(gr_surface surface); +void gr_text_blend(int x,int y, GRFont* pfont); + +void set_rainbow_mode(int enabled); +void move_rainbow(int x); #ifdef __cplusplus } diff --git a/minui/resources.c b/minui/resources.c index 2bae4ded..95b264f0 100644 --- a/minui/resources.c +++ b/minui/resources.c @@ -31,7 +31,11 @@ #include "minui.h" +#ifdef SHARED_MINUI +char *locale = NULL; +#else extern char* locale; +#endif #define SURFACE_DATA_ALIGNMENT 8 @@ -46,12 +50,16 @@ static gr_surface malloc_surface(size_t data_size) { static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, png_uint_32* width, png_uint_32* height, png_byte* channels) { - char resPath[256]; - unsigned char header[8]; + char resPath[256] = {0}; + unsigned char header[8] = {0}; int result = 0; - snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name); - resPath[sizeof(resPath)-1] = '\0'; + if(*name != '/') { + snprintf(resPath, sizeof(resPath), "/res/images/%s.png", name); + }else{ + strlcpy(resPath,name,sizeof(resPath)); + } + FILE* fp = fopen(resPath, "rb"); if (fp == NULL) { result = -1; @@ -107,7 +115,6 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, // channel, because minui doesn't support alpha channels in // general. png_set_palette_to_rgb(*png_ptr); - *channels = 3; } else { fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth, *channels, color_type); @@ -115,6 +122,17 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, goto exit; } + if (png_get_valid(*png_ptr, *info_ptr, PNG_INFO_tRNS)) { + fprintf(stdout,"Has PNG_INFO_tRNS!\n"); + png_set_tRNS_to_alpha(png_ptr); + } + + png_read_update_info(*png_ptr, *info_ptr); + png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth, + &color_type, NULL, NULL, NULL); + + *channels = png_get_channels(*png_ptr, *info_ptr); + return result; exit: @@ -190,6 +208,7 @@ static void transform_rgb_to_draw(unsigned char* input_row, break; case 4: + case 6: // copy RGBA to RGBX memcpy(output_row, input_row, width*4); break; @@ -215,6 +234,10 @@ int res_create_display_surface(const char* name, gr_surface* pSurface) { goto exit; } +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + png_set_bgr(png_ptr); +#endif + unsigned char* p_row = malloc(width * 4); unsigned int y; for (y = 0; y < height; ++y) { @@ -278,6 +301,10 @@ int res_create_multi_display_surface(const char* name, int* frames, gr_surface** } } +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + png_set_bgr(png_ptr); +#endif + unsigned char* p_row = malloc(width * 4); unsigned int y; for (y = 0; y < height; ++y) { @@ -333,6 +360,10 @@ int res_create_alpha_surface(const char* name, gr_surface* pSurface) { surface->row_bytes = width; surface->pixel_bytes = 1; +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + png_set_bgr(png_ptr); +#endif + unsigned char* p_row; unsigned int y; for (y = 0; y < height; ++y) { diff --git a/minui/roboto_10x18.h b/minui/roboto_10x18.h new file mode 100644 index 00000000..3119c81c --- /dev/null +++ b/minui/roboto_10x18.h @@ -0,0 +1,197 @@ +struct { + unsigned width; + unsigned height; + unsigned cwidth; + unsigned cheight; + unsigned char rundata[]; +} font = { + .width = 960, + .height = 18, + .cwidth = 10, + .cheight = 18, + .rundata = { +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x3b,0x81,0x29,0x81,0x06,0x81,0x3f,0x81,0x7f,0x7f,0x7f,0x37,0x83,0x05,0x81, +0x0a,0x83,0x7f,0x7f,0x2f,0x81,0x0e,0x81,0x29,0x82,0x07,0x81,0x01,0x82,0x07, +0x81,0x02,0x81,0x05,0x83,0x05,0x82,0x0a,0x82,0x09,0x82,0x09,0x81,0x08,0x81, +0x3e,0x81,0x05,0x84,0x06,0x84,0x06,0x83,0x06,0x84,0x09,0x82,0x05,0x85,0x06, +0x84,0x04,0x87,0x04,0x84,0x07,0x84,0x39,0x83,0x06,0x84,0x07,0x81,0x06,0x86, +0x06,0x84,0x04,0x86,0x04,0x87,0x04,0x87,0x04,0x84,0x04,0x82,0x04,0x82,0x03, +0x86,0x08,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x07,0x82,0x05,0x82,0x02,0x82, +0x05,0x81,0x04,0x84,0x04,0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02, +0x82,0x05,0x81,0x01,0x82,0x06,0x81,0x01,0x81,0x07,0x81,0x02,0x81,0x05,0x82, +0x02,0x82,0x04,0x82,0x02,0x87,0x06,0x81,0x07,0x82,0x0b,0x81,0x08,0x81,0x12, +0x82,0x10,0x82,0x18,0x82,0x10,0x83,0x0d,0x82,0x0b,0x82,0x08,0x82,0x06,0x82, +0x09,0x83,0x4c,0x82,0x48,0x81,0x07,0x82,0x07,0x81,0x28,0x82,0x07,0x81,0x01, +0x82,0x07,0x81,0x02,0x81,0x04,0x82,0x01,0x82,0x03,0x81,0x02,0x81,0x08,0x81, +0x02,0x81,0x08,0x82,0x08,0x82,0x08,0x82,0x3c,0x82,0x04,0x82,0x02,0x82,0x04, +0x85,0x05,0x82,0x01,0x82,0x04,0x82,0x02,0x82,0x07,0x83,0x05,0x85,0x05,0x82, +0x02,0x81,0x09,0x82,0x03,0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x37,0x82,0x01, +0x82,0x04,0x81,0x03,0x82,0x06,0x82,0x05,0x82,0x03,0x82,0x04,0x83,0x01,0x82, +0x03,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x08,0x82,0x02,0x82,0x03,0x82,0x04, +0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x07,0x82,0x05,0x82, +0x02,0x82,0x05,0x81,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02, +0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x05,0x81, +0x02,0x81,0x05,0x82,0x01,0x81,0x07,0x81,0x02,0x82,0x04,0x81,0x03,0x82,0x04, +0x82,0x07,0x82,0x06,0x81,0x08,0x81,0x0b,0x81,0x07,0x82,0x13,0x81,0x10,0x82, +0x18,0x82,0x0f,0x84,0x0d,0x82,0x1d,0x82,0x0a,0x82,0x4c,0x82,0x47,0x82,0x07, +0x82,0x07,0x82,0x27,0x82,0x07,0x81,0x01,0x82,0x06,0x81,0x02,0x81,0x04,0x82, +0x03,0x82,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x82,0x02,0x82,0x08,0x81,0x08, +0x81,0x0a,0x81,0x12,0x82,0x28,0x81,0x05,0x81,0x04,0x81,0x07,0x82,0x04,0x82, +0x03,0x82,0x03,0x81,0x04,0x81,0x07,0x83,0x05,0x81,0x08,0x82,0x0c,0x82,0x04, +0x81,0x04,0x82,0x04,0x81,0x04,0x81,0x36,0x82,0x03,0x81,0x03,0x81,0x05,0x82, +0x04,0x83,0x05,0x82,0x04,0x81,0x03,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02, +0x82,0x09,0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x0a,0x82, +0x03,0x82,0x02,0x82,0x04,0x82,0x07,0x83,0x03,0x83,0x02,0x83,0x04,0x81,0x02, +0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x81, +0x03,0x82,0x04,0x81,0x06,0x82,0x05,0x82,0x05,0x81,0x02,0x81,0x05,0x82,0x01, +0x82,0x02,0x81,0x02,0x82,0x03,0x82,0x02,0x82,0x04,0x81,0x03,0x82,0x08,0x81, +0x07,0x81,0x08,0x81,0x0b,0x81,0x07,0x83,0x13,0x81,0x0f,0x82,0x18,0x82,0x0e, +0x82,0x10,0x82,0x1d,0x82,0x0a,0x82,0x4c,0x82,0x47,0x81,0x08,0x82,0x08,0x81, +0x27,0x82,0x07,0x81,0x01,0x81,0x05,0x88,0x02,0x82,0x07,0x81,0x02,0x81,0x01, +0x82,0x05,0x81,0x02,0x81,0x08,0x81,0x08,0x82,0x0a,0x82,0x07,0x81,0x09,0x82, +0x27,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x0a,0x81,0x08,0x81,0x06,0x81,0x01, +0x82,0x05,0x81,0x08,0x82,0x0c,0x81,0x05,0x81,0x04,0x82,0x04,0x81,0x04,0x81, +0x05,0x82,0x08,0x82,0x2a,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x01, +0x82,0x04,0x82,0x04,0x81,0x03,0x82,0x04,0x82,0x02,0x82,0x05,0x81,0x02,0x82, +0x09,0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03, +0x82,0x02,0x81,0x05,0x82,0x07,0x83,0x03,0x83,0x02,0x84,0x03,0x81,0x02,0x82, +0x05,0x81,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02, +0x82,0x0b,0x82,0x05,0x82,0x05,0x81,0x02,0x82,0x04,0x81,0x03,0x81,0x02,0x82, +0x01,0x82,0x04,0x81,0x01,0x82,0x05,0x82,0x01,0x82,0x08,0x82,0x07,0x81,0x08, +0x82,0x0a,0x81,0x06,0x82,0x01,0x81,0x1a,0x85,0x04,0x82,0x01,0x83,0x06,0x84, +0x06,0x83,0x01,0x82,0x04,0x84,0x06,0x82,0x07,0x85,0x04,0x82,0x01,0x83,0x06, +0x83,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82, +0x03,0x82,0x01,0x84,0x05,0x84,0x04,0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x03, +0x82,0x01,0x84,0x04,0x84,0x04,0x86,0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x81, +0x02,0x82,0x06,0x81,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x03,0x86,0x07, +0x81,0x08,0x82,0x08,0x81,0x27,0x82,0x11,0x81,0x02,0x81,0x05,0x82,0x06,0x84, +0x01,0x81,0x06,0x83,0x12,0x82,0x0a,0x82,0x05,0x81,0x01,0x81,0x01,0x81,0x07, +0x82,0x27,0x81,0x05,0x82,0x04,0x82,0x06,0x82,0x09,0x82,0x07,0x82,0x05,0x82, +0x01,0x82,0x04,0x85,0x05,0x81,0x01,0x83,0x08,0x82,0x06,0x81,0x02,0x82,0x05, +0x81,0x04,0x81,0x05,0x82,0x08,0x82,0x0a,0x83,0x04,0x86,0x04,0x82,0x0c,0x82, +0x02,0x81,0x02,0x81,0x01,0x81,0x02,0x81,0x04,0x81,0x01,0x82,0x04,0x82,0x03, +0x82,0x03,0x82,0x08,0x82,0x05,0x81,0x02,0x82,0x09,0x82,0x07,0x81,0x09,0x82, +0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x01,0x82,0x05,0x82,0x07,0x84,0x02, +0x83,0x02,0x82,0x01,0x81,0x03,0x81,0x02,0x82,0x05,0x81,0x02,0x82,0x04,0x82, +0x02,0x81,0x06,0x81,0x02,0x82,0x03,0x82,0x04,0x82,0x0a,0x82,0x05,0x82,0x05, +0x81,0x03,0x81,0x03,0x82,0x03,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x05,0x83, +0x07,0x84,0x07,0x82,0x08,0x81,0x09,0x81,0x0a,0x81,0x06,0x81,0x02,0x81,0x19, +0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x83, +0x03,0x82,0x02,0x82,0x04,0x85,0x04,0x83,0x01,0x83,0x03,0x83,0x02,0x82,0x06, +0x82,0x08,0x82,0x06,0x82,0x02,0x81,0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82, +0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02, +0x83,0x03,0x83,0x03,0x81,0x03,0x82,0x02,0x82,0x04,0x82,0x07,0x82,0x04,0x82, +0x03,0x81,0x03,0x82,0x02,0x82,0x02,0x82,0x02,0x81,0x03,0x82,0x02,0x81,0x05, +0x81,0x04,0x82,0x07,0x81,0x08,0x81,0x08,0x82,0x08,0x81,0x27,0x82,0x11,0x81, +0x02,0x81,0x06,0x83,0x08,0x81,0x07,0x82,0x13,0x82,0x0a,0x82,0x05,0x85,0x04, +0x88,0x23,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x08,0x82,0x06,0x83,0x06,0x81, +0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x07,0x81,0x08,0x83,0x06, +0x82,0x02,0x82,0x19,0x83,0x11,0x83,0x09,0x82,0x03,0x81,0x01,0x81,0x02,0x81, +0x02,0x81,0x03,0x82,0x02,0x81,0x04,0x86,0x04,0x82,0x08,0x82,0x05,0x81,0x02, +0x86,0x05,0x86,0x03,0x81,0x09,0x88,0x05,0x82,0x0a,0x82,0x03,0x84,0x06,0x82, +0x07,0x82,0x01,0x81,0x01,0x81,0x01,0x82,0x02,0x82,0x01,0x82,0x02,0x81,0x02, +0x82,0x05,0x81,0x02,0x82,0x03,0x82,0x03,0x81,0x06,0x81,0x02,0x86,0x06,0x83, +0x08,0x82,0x05,0x82,0x05,0x81,0x03,0x82,0x02,0x81,0x04,0x81,0x01,0x81,0x01, +0x81,0x01,0x81,0x06,0x82,0x08,0x82,0x08,0x81,0x09,0x81,0x09,0x82,0x09,0x81, +0x06,0x81,0x02,0x82,0x1d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x81,0x03, +0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82, +0x04,0x82,0x05,0x82,0x08,0x82,0x06,0x82,0x01,0x81,0x08,0x82,0x05,0x82,0x01, +0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82, +0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x81,0x09,0x82,0x07,0x82,0x04,0x82,0x03, +0x82,0x02,0x81,0x04,0x81,0x02,0x82,0x02,0x81,0x04,0x81,0x01,0x82,0x05,0x82, +0x02,0x82,0x07,0x82,0x08,0x81,0x08,0x82,0x08,0x81,0x07,0x83,0x03,0x81,0x19, +0x82,0x11,0x81,0x02,0x81,0x08,0x82,0x07,0x81,0x01,0x82,0x03,0x82,0x01,0x81, +0x02,0x82,0x0e,0x81,0x0c,0x81,0x06,0x82,0x06,0x88,0x0d,0x86,0x10,0x81,0x06, +0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x09,0x82,0x04,0x82,0x02,0x82,0x09,0x82, +0x03,0x81,0x04,0x82,0x05,0x82,0x06,0x82,0x03,0x81,0x06,0x85,0x18,0x82,0x15, +0x83,0x06,0x82,0x04,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x03,0x82, +0x03,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x05,0x81,0x02,0x82,0x09,0x82,0x07, +0x81,0x03,0x84,0x02,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x01,0x82, +0x05,0x82,0x07,0x82,0x01,0x81,0x01,0x81,0x01,0x82,0x02,0x82,0x02,0x82,0x01, +0x81,0x02,0x82,0x05,0x81,0x02,0x86,0x04,0x81,0x06,0x81,0x02,0x82,0x03,0x82, +0x07,0x82,0x07,0x82,0x05,0x82,0x05,0x81,0x03,0x82,0x02,0x81,0x04,0x83,0x01, +0x83,0x05,0x83,0x08,0x82,0x07,0x82,0x09,0x81,0x0a,0x81,0x09,0x81,0x25,0x85, +0x03,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88,0x04,0x82,0x06, +0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x08,0x82,0x06,0x84,0x08,0x82, +0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02, +0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x83,0x07,0x82,0x07,0x82, +0x04,0x82,0x04,0x81,0x02,0x81,0x04,0x81,0x02,0x82,0x02,0x81,0x05,0x82,0x06, +0x82,0x02,0x82,0x06,0x82,0x07,0x82,0x09,0x82,0x09,0x82,0x04,0x82,0x01,0x82, +0x01,0x82,0x2a,0x87,0x08,0x82,0x05,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x02, +0x82,0x01,0x82,0x0e,0x81,0x0c,0x81,0x05,0x82,0x01,0x82,0x07,0x82,0x26,0x81, +0x06,0x82,0x04,0x82,0x06,0x82,0x06,0x82,0x0b,0x82,0x03,0x87,0x08,0x82,0x03, +0x81,0x04,0x82,0x05,0x82,0x06,0x81,0x04,0x82,0x09,0x81,0x18,0x82,0x08,0x86, +0x08,0x82,0x06,0x82,0x04,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x03,0x86,0x03, +0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x05,0x81,0x02,0x82,0x09,0x82,0x07,0x82, +0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04, +0x82,0x07,0x82,0x01,0x83,0x01,0x82,0x02,0x82,0x03,0x81,0x01,0x81,0x02,0x82, +0x05,0x81,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x81,0x08,0x82,0x06, +0x82,0x05,0x82,0x05,0x81,0x04,0x81,0x01,0x82,0x05,0x82,0x02,0x82,0x05,0x81, +0x01,0x82,0x07,0x82,0x06,0x82,0x0a,0x81,0x0a,0x81,0x09,0x81,0x24,0x82,0x02, +0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82, +0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x08,0x82,0x06,0x84,0x08, +0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82, +0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0b,0x83,0x04,0x82,0x07, +0x82,0x04,0x82,0x04,0x81,0x01,0x82,0x04,0x81,0x01,0x81,0x01,0x81,0x01,0x82, +0x05,0x82,0x07,0x81,0x02,0x82,0x06,0x81,0x0a,0x81,0x08,0x82,0x08,0x81,0x06, +0x81,0x03,0x83,0x2c,0x81,0x02,0x81,0x05,0x81,0x04,0x82,0x04,0x81,0x02,0x81, +0x02,0x81,0x01,0x82,0x03,0x83,0x0f,0x82,0x0a,0x82,0x11,0x82,0x25,0x82,0x07, +0x81,0x04,0x81,0x07,0x82,0x05,0x82,0x07,0x81,0x04,0x82,0x03,0x87,0x03,0x81, +0x04,0x82,0x03,0x81,0x04,0x82,0x05,0x81,0x07,0x81,0x04,0x82,0x08,0x82,0x19, +0x84,0x10,0x83,0x0e,0x81,0x01,0x81,0x02,0x81,0x01,0x82,0x02,0x82,0x04,0x81, +0x03,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x09, +0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x81,0x04,0x82, +0x03,0x82,0x03,0x81,0x04,0x82,0x07,0x82,0x02,0x81,0x02,0x82,0x02,0x82,0x03, +0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82, +0x02,0x81,0x05,0x81,0x06,0x82,0x05,0x83,0x03,0x82,0x04,0x83,0x06,0x82,0x02, +0x82,0x04,0x82,0x02,0x81,0x07,0x82,0x06,0x81,0x0b,0x81,0x0a,0x82,0x08,0x81, +0x23,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x81,0x03,0x82,0x04, +0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82, +0x08,0x82,0x06,0x82,0x01,0x82,0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02, +0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82, +0x03,0x82,0x0d,0x81,0x04,0x82,0x07,0x82,0x03,0x83,0x04,0x83,0x06,0x82,0x02, +0x82,0x05,0x81,0x01,0x82,0x06,0x84,0x06,0x81,0x0b,0x81,0x08,0x82,0x08,0x81, +0x27,0x82,0x10,0x81,0x02,0x81,0x05,0x82,0x02,0x83,0x04,0x81,0x02,0x81,0x02, +0x81,0x02,0x81,0x04,0x82,0x0f,0x82,0x0a,0x82,0x11,0x82,0x07,0x82,0x12,0x82, +0x08,0x81,0x08,0x82,0x02,0x82,0x07,0x82,0x05,0x81,0x08,0x82,0x03,0x81,0x08, +0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x81,0x07,0x82,0x02,0x82, +0x08,0x82,0x06,0x82,0x08,0x82,0x0b,0x82,0x0e,0x82,0x0a,0x82,0x04,0x81,0x01, +0x83,0x01,0x82,0x03,0x81,0x05,0x82,0x02,0x82,0x04,0x82,0x03,0x83,0x01,0x82, +0x03,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x08,0x82,0x03,0x82,0x02,0x82,0x04, +0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x07,0x82, +0x05,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x02, +0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x06,0x82,0x06,0x82,0x03,0x82, +0x05,0x82,0x06,0x82,0x02,0x81,0x04,0x82,0x03,0x82,0x06,0x82,0x05,0x82,0x0b, +0x81,0x0b,0x81,0x08,0x81,0x23,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x04,0x82, +0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x04,0x82,0x06,0x83,0x01, +0x83,0x03,0x82,0x04,0x82,0x05,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82, +0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03, +0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x08,0x82,0x02,0x82,0x04,0x82, +0x03,0x81,0x03,0x83,0x01,0x84,0x05,0x82,0x06,0x82,0x02,0x82,0x04,0x82,0x02, +0x81,0x07,0x83,0x05,0x82,0x0b,0x81,0x08,0x82,0x08,0x81,0x27,0x82,0x10,0x81, +0x02,0x81,0x06,0x85,0x09,0x82,0x04,0x84,0x01,0x82,0x0f,0x81,0x0a,0x81,0x1b, +0x82,0x12,0x82,0x08,0x81,0x09,0x84,0x08,0x82,0x04,0x87,0x04,0x84,0x09,0x82, +0x05,0x84,0x06,0x84,0x07,0x81,0x08,0x84,0x06,0x84,0x07,0x82,0x08,0x82,0x27, +0x82,0x04,0x82,0x09,0x81,0x06,0x81,0x02,0x87,0x05,0x84,0x04,0x86,0x04,0x87, +0x04,0x82,0x09,0x85,0x03,0x82,0x04,0x82,0x03,0x86,0x04,0x84,0x05,0x82,0x04, +0x82,0x02,0x87,0x02,0x82,0x05,0x82,0x02,0x82,0x05,0x81,0x04,0x84,0x04,0x82, +0x0a,0x86,0x02,0x82,0x04,0x82,0x03,0x85,0x07,0x82,0x07,0x85,0x06,0x82,0x06, +0x82,0x02,0x81,0x04,0x82,0x04,0x82,0x05,0x82,0x05,0x87,0x06,0x81,0x0b,0x82, +0x07,0x81,0x23,0x84,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x06,0x83,0x01, +0x82,0x04,0x85,0x05,0x82,0x07,0x83,0x01,0x82,0x03,0x82,0x04,0x82,0x03,0x86, +0x06,0x82,0x06,0x82,0x03,0x81,0x04,0x86,0x03,0x82,0x01,0x82,0x01,0x82,0x02, +0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x03,0x82, +0x09,0x84,0x06,0x84,0x05,0x84,0x01,0x82,0x05,0x82,0x06,0x81,0x03,0x82,0x03, +0x82,0x03,0x82,0x07,0x82,0x05,0x86,0x07,0x81,0x08,0x82,0x08,0x81,0x45,0x81, +0x27,0x81,0x0a,0x81,0x1c,0x81,0x1b,0x82,0x78,0x81,0x2e,0x82,0x7f,0x30,0x82, +0x5e,0x81,0x0c,0x81,0x07,0x81,0x0f,0x88,0x4d,0x82,0x1a,0x82,0x37,0x82,0x0e, +0x82,0x4b,0x82,0x13,0x82,0x07,0x82,0x07,0x82,0x45,0x81,0x28,0x81,0x08,0x81, +0x1c,0x81,0x1c,0x81,0x78,0x81,0x30,0x84,0x7f,0x7f,0x0e,0x83,0x0a,0x81,0x05, +0x83,0x64,0x82,0x1a,0x82,0x37,0x82,0x0e,0x82,0x4b,0x82,0x14,0x81,0x07,0x82, +0x07,0x81,0x70,0x81,0x06,0x81,0x7f,0x7f,0x7f,0x7f,0x6e,0x85,0x1a,0x82,0x38, +0x82,0x0e,0x82,0x4a,0x82,0x16,0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x62, +0x00, + } +}; diff --git a/minui/roboto_15x24.h b/minui/roboto_15x24.h new file mode 100644 index 00000000..7271d74b --- /dev/null +++ b/minui/roboto_15x24.h @@ -0,0 +1,281 @@ +struct { + unsigned width; + unsigned height; + unsigned cwidth; + unsigned cheight; + unsigned char rundata[]; +} font = { + .width = 1440, + .height = 24, + .cwidth = 15, + .cheight = 24, + .rundata = { +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x6e,0x81,0x3e,0x81, +0x07,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x76,0x84,0x17,0x84,0x7f,0x7f,0x7f,0x7f, +0x4c,0x81,0x3d,0x82,0x07,0x82,0x5f,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x15,0x84, +0x07,0x82,0x0e,0x84,0x45,0x82,0x24,0x82,0x1a,0x83,0x16,0x82,0x10,0x82,0x0e, +0x82,0x0a,0x82,0x0d,0x84,0x7f,0x62,0x82,0x0b,0x82,0x0b,0x82,0x3b,0x82,0x0c, +0x82,0x02,0x82,0x0a,0x82,0x03,0x81,0x0a,0x84,0x08,0x83,0x0e,0x84,0x0b,0x82, +0x10,0x82,0x09,0x82,0x5e,0x81,0x09,0x84,0x0b,0x84,0x0b,0x84,0x0b,0x84,0x0e, +0x83,0x08,0x88,0x09,0x84,0x07,0x8a,0x08,0x84,0x0b,0x84,0x56,0x84,0x1c,0x82, +0x08,0x87,0x0b,0x85,0x07,0x87,0x09,0x89,0x06,0x8a,0x07,0x85,0x06,0x82,0x08, +0x82,0x06,0x86,0x0f,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x0a,0x83,0x09,0x82, +0x03,0x82,0x08,0x82,0x06,0x85,0x07,0x87,0x0b,0x85,0x07,0x87,0x0c,0x84,0x06, +0x8d,0x03,0x82,0x07,0x82,0x02,0x83,0x08,0x83,0x01,0x82,0x0a,0x82,0x02,0x82, +0x08,0x82,0x03,0x82,0x08,0x82,0x04,0x8b,0x09,0x82,0x09,0x82,0x10,0x82,0x0d, +0x81,0x1d,0x82,0x18,0x82,0x24,0x82,0x19,0x84,0x16,0x82,0x10,0x82,0x0e,0x82, +0x0a,0x82,0x0d,0x84,0x7f,0x61,0x82,0x0c,0x82,0x0c,0x82,0x3a,0x82,0x0c,0x82, +0x02,0x82,0x0a,0x82,0x03,0x81,0x08,0x87,0x06,0x82,0x01,0x82,0x0c,0x86,0x0a, +0x82,0x0f,0x82,0x0b,0x82,0x5c,0x82,0x08,0x87,0x07,0x86,0x0a,0x87,0x08,0x87, +0x0c,0x83,0x08,0x88,0x07,0x87,0x06,0x8a,0x07,0x87,0x07,0x87,0x54,0x87,0x08, +0x86,0x0c,0x82,0x08,0x89,0x07,0x88,0x06,0x89,0x07,0x89,0x06,0x8a,0x05,0x88, +0x05,0x82,0x08,0x82,0x06,0x86,0x0f,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x0a, +0x83,0x08,0x83,0x03,0x83,0x07,0x82,0x04,0x88,0x06,0x89,0x07,0x88,0x06,0x89, +0x08,0x88,0x04,0x8d,0x03,0x82,0x07,0x82,0x03,0x82,0x08,0x82,0x02,0x82,0x04, +0x81,0x05,0x82,0x03,0x82,0x06,0x82,0x04,0x82,0x07,0x83,0x04,0x8b,0x09,0x82, +0x0a,0x82,0x0f,0x82,0x0c,0x83,0x1d,0x82,0x17,0x82,0x24,0x82,0x18,0x83,0x18, +0x82,0x2c,0x82,0x0f,0x82,0x7f,0x60,0x83,0x0c,0x82,0x0d,0x82,0x39,0x82,0x0c, +0x82,0x02,0x82,0x0a,0x82,0x02,0x82,0x07,0x83,0x03,0x83,0x04,0x82,0x03,0x81, +0x04,0x81,0x06,0x83,0x03,0x82,0x09,0x82,0x0f,0x82,0x0b,0x82,0x5c,0x82,0x07, +0x82,0x04,0x82,0x0a,0x83,0x09,0x82,0x04,0x83,0x06,0x82,0x04,0x83,0x0a,0x84, +0x08,0x82,0x0d,0x82,0x13,0x82,0x06,0x82,0x04,0x83,0x06,0x82,0x04,0x82,0x52, +0x82,0x04,0x82,0x07,0x82,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x83,0x06,0x82, +0x05,0x82,0x05,0x82,0x05,0x83,0x06,0x82,0x0d,0x82,0x0d,0x82,0x05,0x82,0x04, +0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06,0x82,0x05,0x82,0x06,0x82,0x0a,0x83, +0x08,0x83,0x03,0x83,0x07,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x06, +0x82,0x05,0x82,0x05,0x82,0x05,0x83,0x06,0x83,0x04,0x83,0x08,0x83,0x08,0x82, +0x07,0x82,0x03,0x82,0x08,0x82,0x02,0x82,0x03,0x83,0x04,0x82,0x03,0x83,0x04, +0x83,0x05,0x82,0x06,0x82,0x0d,0x82,0x0a,0x82,0x0a,0x82,0x0f,0x82,0x0c,0x83, +0x1e,0x81,0x17,0x82,0x24,0x82,0x18,0x82,0x19,0x82,0x2c,0x82,0x0f,0x82,0x74, +0x82,0x69,0x82,0x0d,0x82,0x0d,0x82,0x39,0x82,0x0c,0x82,0x02,0x82,0x0a,0x82, +0x02,0x82,0x07,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06,0x82,0x04, +0x82,0x09,0x82,0x0e,0x82,0x0d,0x82,0x1c,0x82,0x3c,0x82,0x08,0x82,0x05,0x82, +0x09,0x83,0x09,0x82,0x05,0x82,0x05,0x83,0x05,0x82,0x09,0x85,0x07,0x82,0x0d, +0x82,0x13,0x82,0x07,0x82,0x05,0x82,0x05,0x82,0x05,0x83,0x51,0x82,0x04,0x82, +0x06,0x82,0x07,0x82,0x08,0x84,0x07,0x82,0x06,0x82,0x05,0x82,0x07,0x82,0x04, +0x82,0x06,0x83,0x05,0x82,0x0d,0x82,0x0c,0x82,0x07,0x82,0x03,0x82,0x08,0x82, +0x08,0x82,0x11,0x82,0x06,0x82,0x04,0x83,0x06,0x82,0x0a,0x84,0x06,0x84,0x03, +0x84,0x06,0x82,0x03,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82, +0x04,0x82,0x06,0x82,0x06,0x82,0x06,0x82,0x08,0x83,0x08,0x82,0x07,0x82,0x03, +0x83,0x06,0x83,0x02,0x82,0x03,0x83,0x03,0x82,0x05,0x82,0x04,0x82,0x06,0x82, +0x05,0x83,0x0c,0x83,0x0a,0x82,0x0b,0x82,0x0e,0x82,0x0c,0x81,0x01,0x82,0x35, +0x82,0x24,0x82,0x18,0x82,0x19,0x82,0x2c,0x82,0x0f,0x82,0x74,0x82,0x69,0x82, +0x0d,0x82,0x0d,0x83,0x38,0x82,0x0c,0x82,0x02,0x81,0x0b,0x81,0x03,0x82,0x07, +0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x81,0x07,0x82,0x04,0x82,0x09,0x82, +0x0e,0x82,0x0d,0x82,0x0d,0x82,0x0d,0x82,0x3c,0x82,0x07,0x82,0x06,0x82,0x09, +0x83,0x08,0x83,0x05,0x82,0x0d,0x82,0x09,0x85,0x07,0x82,0x0d,0x82,0x12,0x83, +0x07,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x57,0x83,0x04,0x82,0x09,0x81,0x08, +0x84,0x07,0x82,0x06,0x82,0x05,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x05,0x82, +0x0d,0x82,0x0c,0x82,0x07,0x82,0x03,0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06, +0x82,0x03,0x83,0x07,0x82,0x0a,0x84,0x06,0x84,0x03,0x85,0x05,0x82,0x03,0x82, +0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x06,0x83,0x05, +0x82,0x06,0x82,0x08,0x83,0x08,0x82,0x07,0x82,0x04,0x82,0x06,0x82,0x03,0x82, +0x03,0x83,0x03,0x82,0x05,0x83,0x02,0x83,0x07,0x82,0x04,0x82,0x0d,0x82,0x0b, +0x82,0x0b,0x82,0x0e,0x82,0x0b,0x82,0x01,0x82,0x29,0x84,0x08,0x82,0x01,0x84, +0x0a,0x86,0x0a,0x83,0x02,0x82,0x08,0x84,0x0a,0x86,0x0a,0x83,0x02,0x82,0x06, +0x82,0x01,0x84,0x09,0x84,0x0e,0x82,0x0a,0x82,0x04,0x83,0x08,0x82,0x07,0x81, +0x02,0x83,0x03,0x83,0x05,0x81,0x02,0x84,0x0b,0x84,0x09,0x81,0x02,0x84,0x0a, +0x83,0x02,0x82,0x08,0x81,0x02,0x84,0x08,0x84,0x08,0x88,0x07,0x82,0x06,0x82, +0x05,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x04, +0x83,0x05,0x83,0x06,0x89,0x09,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x0c,0x82, +0x02,0x81,0x08,0x8b,0x05,0x82,0x0b,0x82,0x03,0x81,0x02,0x82,0x07,0x83,0x02, +0x82,0x0a,0x81,0x0f,0x82,0x0d,0x82,0x0d,0x82,0x0d,0x82,0x3c,0x82,0x07,0x82, +0x06,0x82,0x09,0x83,0x10,0x82,0x0d,0x82,0x08,0x82,0x01,0x83,0x07,0x82,0x0d, +0x82,0x12,0x82,0x08,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x08,0x82,0x0d,0x82, +0x13,0x81,0x06,0x89,0x06,0x81,0x14,0x82,0x05,0x81,0x04,0x84,0x02,0x82,0x06, +0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x04,0x82,0x0e,0x82,0x07,0x82,0x05,0x82, +0x0d,0x82,0x0b,0x82,0x0d,0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06,0x82,0x03, +0x82,0x08,0x82,0x0a,0x82,0x01,0x82,0x05,0x84,0x03,0x82,0x01,0x82,0x05,0x82, +0x02,0x82,0x09,0x82,0x03,0x82,0x07,0x82,0x03,0x82,0x09,0x82,0x03,0x82,0x06, +0x83,0x05,0x82,0x10,0x83,0x08,0x82,0x07,0x82,0x04,0x82,0x06,0x82,0x03,0x82, +0x03,0x81,0x01,0x82,0x02,0x82,0x06,0x82,0x02,0x82,0x08,0x82,0x03,0x83,0x0c, +0x82,0x0c,0x82,0x0b,0x82,0x0e,0x82,0x0b,0x82,0x02,0x81,0x28,0x87,0x06,0x88, +0x08,0x88,0x08,0x88,0x07,0x86,0x09,0x86,0x09,0x88,0x06,0x88,0x08,0x84,0x0e, +0x82,0x0a,0x82,0x03,0x83,0x09,0x82,0x07,0x87,0x01,0x85,0x04,0x88,0x09,0x87, +0x07,0x81,0x01,0x86,0x08,0x88,0x08,0x87,0x06,0x87,0x07,0x88,0x07,0x82,0x06, +0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x04,0x83,0x03,0x83, +0x05,0x82,0x05,0x82,0x07,0x89,0x09,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x1b, +0x82,0x03,0x81,0x08,0x83,0x0b,0x85,0x01,0x82,0x09,0x85,0x1a,0x83,0x0d,0x83, +0x09,0x81,0x02,0x82,0x01,0x81,0x0b,0x82,0x3b,0x82,0x08,0x82,0x06,0x82,0x09, +0x83,0x0f,0x82,0x0d,0x82,0x09,0x82,0x01,0x83,0x07,0x87,0x07,0x83,0x01,0x84, +0x0c,0x82,0x09,0x83,0x03,0x82,0x06,0x82,0x06,0x82,0x08,0x82,0x0d,0x82,0x11, +0x83,0x06,0x89,0x06,0x83,0x12,0x82,0x05,0x81,0x03,0x82,0x02,0x82,0x02,0x81, +0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x05,0x82,0x0e,0x82,0x07,0x83,0x04, +0x82,0x0d,0x82,0x0b,0x82,0x0d,0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06,0x82, +0x02,0x82,0x09,0x82,0x0a,0x82,0x01,0x82,0x04,0x82,0x01,0x82,0x03,0x82,0x02, +0x82,0x04,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x07,0x82,0x03,0x82,0x09,0x82, +0x03,0x82,0x06,0x82,0x06,0x83,0x0f,0x83,0x08,0x82,0x07,0x82,0x05,0x82,0x04, +0x82,0x05,0x81,0x02,0x82,0x01,0x82,0x02,0x82,0x07,0x84,0x0a,0x82,0x02,0x82, +0x0c,0x83,0x0c,0x82,0x0c,0x82,0x0d,0x82,0x0a,0x82,0x03,0x82,0x26,0x82,0x04, +0x82,0x06,0x83,0x03,0x83,0x07,0x82,0x05,0x82,0x06,0x82,0x04,0x83,0x06,0x82, +0x04,0x82,0x0a,0x82,0x0a,0x83,0x03,0x83,0x06,0x82,0x04,0x83,0x09,0x82,0x0e, +0x82,0x0a,0x82,0x03,0x82,0x0a,0x82,0x07,0x82,0x03,0x84,0x02,0x82,0x04,0x82, +0x04,0x83,0x07,0x82,0x04,0x83,0x06,0x82,0x04,0x83,0x06,0x82,0x04,0x83,0x08, +0x83,0x0a,0x82,0x04,0x82,0x08,0x82,0x0b,0x82,0x06,0x82,0x05,0x82,0x05,0x82, +0x04,0x82,0x03,0x83,0x03,0x82,0x06,0x82,0x03,0x82,0x06,0x82,0x05,0x82,0x0d, +0x82,0x0a,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x1b,0x82,0x02,0x82,0x09,0x85, +0x0e,0x81,0x0b,0x83,0x1b,0x82,0x0f,0x82,0x09,0x87,0x0b,0x82,0x3b,0x82,0x08, +0x82,0x06,0x82,0x09,0x83,0x0e,0x83,0x09,0x85,0x09,0x82,0x02,0x83,0x07,0x88, +0x06,0x89,0x0b,0x82,0x0a,0x86,0x07,0x82,0x06,0x82,0x27,0x85,0x17,0x84,0x0f, +0x82,0x05,0x82,0x03,0x81,0x03,0x82,0x02,0x81,0x06,0x82,0x02,0x82,0x06,0x88, +0x06,0x82,0x0e,0x82,0x07,0x83,0x04,0x88,0x07,0x88,0x05,0x82,0x0d,0x8c,0x08, +0x82,0x11,0x82,0x06,0x85,0x0a,0x82,0x0a,0x82,0x01,0x82,0x04,0x82,0x01,0x82, +0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x06,0x82,0x04, +0x82,0x09,0x82,0x03,0x82,0x04,0x83,0x08,0x85,0x0c,0x83,0x08,0x82,0x07,0x82, +0x05,0x82,0x04,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x81,0x08,0x84,0x0a, +0x86,0x0c,0x82,0x0d,0x82,0x0c,0x82,0x0d,0x82,0x0a,0x82,0x03,0x82,0x26,0x82, +0x04,0x83,0x05,0x82,0x05,0x82,0x06,0x83,0x05,0x82,0x05,0x83,0x05,0x82,0x05, +0x83,0x04,0x82,0x0a,0x82,0x0a,0x82,0x05,0x82,0x06,0x82,0x05,0x82,0x09,0x82, +0x0e,0x82,0x0a,0x82,0x02,0x82,0x0b,0x82,0x07,0x82,0x03,0x83,0x03,0x83,0x03, +0x82,0x05,0x82,0x06,0x83,0x05,0x82,0x06,0x82,0x05,0x82,0x05,0x83,0x05,0x82, +0x08,0x82,0x0b,0x82,0x04,0x82,0x08,0x82,0x0b,0x82,0x06,0x82,0x06,0x82,0x04, +0x82,0x04,0x82,0x02,0x84,0x03,0x82,0x07,0x82,0x01,0x82,0x07,0x83,0x03,0x83, +0x0c,0x83,0x0a,0x82,0x0d,0x82,0x0e,0x82,0x0a,0x82,0x2c,0x82,0x1b,0x82,0x02, +0x82,0x0b,0x85,0x0b,0x82,0x09,0x85,0x1b,0x82,0x0f,0x82,0x0b,0x83,0x09,0x8b, +0x36,0x81,0x09,0x82,0x06,0x82,0x09,0x83,0x0e,0x82,0x0a,0x86,0x07,0x82,0x03, +0x83,0x07,0x82,0x04,0x83,0x05,0x84,0x03,0x83,0x09,0x82,0x0b,0x87,0x07,0x82, +0x04,0x83,0x25,0x85,0x1b,0x85,0x0b,0x82,0x06,0x82,0x02,0x82,0x03,0x82,0x02, +0x81,0x05,0x82,0x04,0x82,0x05,0x8a,0x04,0x82,0x0e,0x82,0x07,0x83,0x04,0x88, +0x07,0x88,0x05,0x82,0x04,0x86,0x03,0x8c,0x08,0x82,0x11,0x82,0x06,0x86,0x09, +0x82,0x0a,0x82,0x02,0x82,0x02,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82, +0x02,0x82,0x09,0x82,0x03,0x8a,0x04,0x82,0x09,0x82,0x03,0x89,0x0a,0x85,0x0a, +0x83,0x08,0x82,0x07,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x01,0x82,0x02,0x81, +0x01,0x82,0x09,0x82,0x0c,0x84,0x0c,0x82,0x0e,0x82,0x0d,0x82,0x0c,0x82,0x3d, +0x83,0x05,0x82,0x05,0x82,0x06,0x82,0x0d,0x82,0x06,0x82,0x05,0x82,0x06,0x82, +0x09,0x82,0x09,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,0x0a, +0x82,0x01,0x83,0x0b,0x82,0x07,0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82, +0x06,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x08,0x82,0x0b, +0x82,0x0e,0x82,0x0b,0x82,0x06,0x82,0x06,0x82,0x03,0x82,0x06,0x82,0x01,0x82, +0x01,0x81,0x03,0x82,0x07,0x82,0x01,0x82,0x08,0x82,0x03,0x82,0x0c,0x83,0x09, +0x83,0x0e,0x82,0x0e,0x83,0x07,0x86,0x04,0x82,0x24,0x82,0x1b,0x81,0x03,0x82, +0x0d,0x84,0x0a,0x81,0x02,0x84,0x03,0x83,0x01,0x83,0x03,0x82,0x15,0x82,0x0f, +0x82,0x0b,0x84,0x08,0x8b,0x15,0x86,0x1a,0x82,0x09,0x82,0x06,0x82,0x09,0x83, +0x0d,0x82,0x0f,0x83,0x06,0x82,0x03,0x83,0x0e,0x82,0x05,0x83,0x05,0x82,0x09, +0x82,0x0a,0x82,0x04,0x83,0x06,0x89,0x24,0x83,0x21,0x84,0x08,0x82,0x07,0x81, +0x03,0x82,0x03,0x81,0x03,0x81,0x05,0x82,0x04,0x82,0x05,0x82,0x06,0x83,0x03, +0x82,0x0e,0x82,0x07,0x83,0x04,0x82,0x0d,0x82,0x0b,0x82,0x04,0x86,0x03,0x82, +0x08,0x82,0x08,0x82,0x11,0x82,0x06,0x82,0x02,0x82,0x09,0x82,0x0a,0x82,0x02, +0x82,0x02,0x82,0x02,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x02,0x82,0x09,0x82, +0x03,0x88,0x06,0x82,0x09,0x82,0x03,0x82,0x05,0x83,0x0c,0x84,0x08,0x83,0x08, +0x82,0x07,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x81,0x03,0x84,0x08,0x84, +0x0b,0x83,0x0c,0x83,0x0e,0x82,0x0d,0x82,0x0c,0x82,0x39,0x87,0x05,0x82,0x05, +0x82,0x06,0x82,0x0d,0x82,0x06,0x82,0x05,0x8a,0x09,0x82,0x09,0x82,0x06,0x82, +0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,0x0a,0x85,0x0c,0x82,0x07,0x82,0x04, +0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x06,0x82,0x06,0x82,0x06,0x82,0x05,0x82, +0x05,0x82,0x06,0x82,0x08,0x82,0x0b,0x85,0x0b,0x82,0x0b,0x82,0x06,0x82,0x06, +0x82,0x03,0x82,0x06,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x08,0x83,0x09,0x82, +0x03,0x82,0x0c,0x82,0x0a,0x82,0x0f,0x82,0x0f,0x83,0x06,0x82,0x02,0x83,0x02, +0x82,0x25,0x82,0x18,0x8b,0x0d,0x82,0x09,0x82,0x01,0x82,0x02,0x82,0x02,0x82, +0x03,0x83,0x02,0x82,0x15,0x82,0x0f,0x82,0x0a,0x82,0x01,0x82,0x0c,0x82,0x3a, +0x82,0x09,0x82,0x06,0x82,0x09,0x83,0x0c,0x82,0x11,0x82,0x05,0x82,0x04,0x83, +0x0e,0x82,0x05,0x83,0x05,0x82,0x09,0x82,0x09,0x82,0x06,0x82,0x08,0x83,0x02, +0x82,0x24,0x83,0x0c,0x89,0x0d,0x83,0x08,0x82,0x07,0x81,0x03,0x82,0x03,0x81, +0x03,0x81,0x04,0x89,0x05,0x82,0x07,0x82,0x03,0x82,0x0e,0x82,0x07,0x82,0x05, +0x82,0x0d,0x82,0x0b,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x08,0x82,0x11,0x82, +0x06,0x82,0x03,0x82,0x08,0x82,0x0a,0x82,0x03,0x81,0x02,0x81,0x03,0x82,0x03, +0x82,0x04,0x82,0x02,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x0c,0x82,0x09,0x82, +0x03,0x82,0x06,0x82,0x0e,0x82,0x08,0x83,0x08,0x82,0x07,0x82,0x06,0x82,0x02, +0x82,0x06,0x84,0x03,0x84,0x07,0x86,0x0b,0x82,0x0c,0x82,0x0f,0x82,0x0d,0x82, +0x0c,0x82,0x37,0x89,0x05,0x82,0x05,0x82,0x06,0x82,0x0d,0x82,0x06,0x82,0x05, +0x8a,0x09,0x82,0x09,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82, +0x0a,0x85,0x0c,0x82,0x07,0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x06, +0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x08,0x82,0x0d,0x85, +0x09,0x82,0x0b,0x82,0x06,0x82,0x07,0x82,0x02,0x81,0x07,0x82,0x01,0x81,0x02, +0x82,0x01,0x82,0x09,0x83,0x0a,0x82,0x01,0x83,0x0b,0x82,0x0c,0x82,0x0e,0x82, +0x0e,0x82,0x07,0x82,0x04,0x86,0x3f,0x8b,0x05,0x82,0x06,0x82,0x08,0x82,0x02, +0x81,0x04,0x81,0x02,0x82,0x04,0x85,0x16,0x82,0x0f,0x82,0x0e,0x81,0x0c,0x82, +0x39,0x82,0x0a,0x82,0x06,0x82,0x09,0x83,0x0b,0x83,0x11,0x82,0x05,0x8b,0x0c, +0x82,0x06,0x82,0x05,0x82,0x08,0x83,0x09,0x82,0x06,0x82,0x0d,0x82,0x25,0x85, +0x09,0x89,0x0a,0x85,0x09,0x82,0x07,0x81,0x03,0x82,0x03,0x81,0x02,0x82,0x04, +0x8a,0x04,0x82,0x07,0x82,0x03,0x83,0x07,0x82,0x04,0x82,0x07,0x82,0x05,0x82, +0x0d,0x82,0x0b,0x83,0x07,0x82,0x03,0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06, +0x82,0x03,0x83,0x07,0x82,0x0a,0x82,0x03,0x84,0x03,0x82,0x03,0x82,0x05,0x82, +0x01,0x82,0x02,0x83,0x07,0x83,0x03,0x82,0x0c,0x83,0x07,0x83,0x03,0x82,0x06, +0x83,0x04,0x82,0x07,0x82,0x08,0x83,0x08,0x82,0x07,0x82,0x06,0x82,0x01,0x83, +0x07,0x83,0x03,0x84,0x07,0x82,0x02,0x82,0x0b,0x82,0x0b,0x82,0x10,0x82,0x0e, +0x82,0x0b,0x82,0x36,0x83,0x04,0x83,0x05,0x82,0x05,0x82,0x06,0x82,0x0d,0x82, +0x06,0x82,0x05,0x82,0x11,0x82,0x09,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x09, +0x82,0x0e,0x82,0x0a,0x82,0x02,0x82,0x0b,0x82,0x07,0x82,0x04,0x82,0x04,0x82, +0x03,0x82,0x05,0x82,0x06,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x05,0x82,0x06, +0x82,0x08,0x82,0x10,0x83,0x08,0x82,0x0b,0x82,0x06,0x82,0x07,0x82,0x01,0x82, +0x08,0x83,0x03,0x81,0x01,0x82,0x08,0x85,0x09,0x82,0x01,0x82,0x0b,0x83,0x0d, +0x82,0x0d,0x82,0x0e,0x82,0x0f,0x82,0x43,0x82,0x02,0x82,0x08,0x83,0x05,0x82, +0x08,0x81,0x03,0x81,0x04,0x81,0x02,0x82,0x05,0x84,0x16,0x82,0x0f,0x82,0x1b, +0x82,0x39,0x82,0x0a,0x83,0x05,0x82,0x09,0x83,0x0a,0x83,0x0a,0x82,0x06,0x82, +0x0b,0x83,0x07,0x82,0x05,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x0a,0x82,0x06, +0x82,0x0c,0x83,0x27,0x85,0x18,0x84,0x15,0x82,0x02,0x82,0x02,0x82,0x02,0x81, +0x05,0x82,0x06,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x06, +0x83,0x05,0x82,0x0d,0x82,0x0c,0x82,0x07,0x82,0x03,0x82,0x08,0x82,0x08,0x82, +0x0a,0x82,0x05,0x82,0x06,0x82,0x04,0x83,0x06,0x82,0x0a,0x82,0x03,0x84,0x03, +0x82,0x03,0x82,0x05,0x85,0x03,0x82,0x07,0x82,0x04,0x82,0x0d,0x82,0x05,0x81, +0x01,0x82,0x04,0x82,0x06,0x83,0x04,0x83,0x06,0x82,0x08,0x83,0x08,0x82,0x07, +0x82,0x07,0x84,0x08,0x83,0x04,0x82,0x07,0x82,0x04,0x82,0x0a,0x82,0x0a,0x83, +0x10,0x82,0x0e,0x82,0x0b,0x82,0x36,0x82,0x05,0x83,0x05,0x82,0x05,0x82,0x06, +0x83,0x05,0x82,0x05,0x82,0x06,0x82,0x05,0x83,0x10,0x82,0x09,0x83,0x05,0x82, +0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,0x0a,0x82,0x02,0x83,0x0a,0x82,0x07, +0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x06,0x83,0x05,0x82,0x06,0x82, +0x05,0x82,0x05,0x82,0x06,0x82,0x08,0x82,0x0a,0x82,0x05,0x82,0x08,0x82,0x0b, +0x82,0x06,0x82,0x08,0x81,0x01,0x82,0x08,0x83,0x03,0x84,0x08,0x82,0x01,0x82, +0x09,0x82,0x01,0x82,0x0b,0x82,0x0e,0x82,0x0d,0x82,0x0d,0x83,0x54,0x82,0x02, +0x82,0x09,0x82,0x04,0x83,0x07,0x82,0x03,0x81,0x04,0x81,0x02,0x82,0x06,0x82, +0x18,0x82,0x0d,0x82,0x1c,0x82,0x39,0x81,0x0c,0x82,0x04,0x83,0x09,0x83,0x09, +0x83,0x0c,0x82,0x05,0x82,0x0b,0x83,0x07,0x82,0x04,0x83,0x06,0x83,0x04,0x82, +0x08,0x82,0x0a,0x83,0x05,0x82,0x0c,0x82,0x2b,0x83,0x15,0x84,0x18,0x81,0x03, +0x83,0x01,0x83,0x05,0x82,0x07,0x83,0x03,0x82,0x06,0x83,0x04,0x83,0x05,0x82, +0x05,0x82,0x06,0x82,0x06,0x82,0x0d,0x82,0x0c,0x83,0x06,0x82,0x03,0x82,0x08, +0x82,0x08,0x82,0x0a,0x82,0x05,0x82,0x06,0x82,0x05,0x83,0x05,0x82,0x0a,0x82, +0x04,0x82,0x04,0x82,0x03,0x82,0x06,0x84,0x03,0x83,0x05,0x83,0x04,0x82,0x0d, +0x83,0x05,0x83,0x04,0x82,0x06,0x83,0x05,0x82,0x06,0x82,0x08,0x83,0x08,0x83, +0x05,0x83,0x07,0x84,0x08,0x82,0x05,0x82,0x06,0x83,0x04,0x83,0x09,0x82,0x0a, +0x82,0x11,0x82,0x0e,0x82,0x0b,0x82,0x36,0x82,0x05,0x83,0x05,0x83,0x04,0x82, +0x07,0x82,0x05,0x82,0x06,0x82,0x04,0x83,0x06,0x82,0x10,0x82,0x0a,0x82,0x04, +0x83,0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,0x0a,0x82,0x03,0x82,0x0a,0x82, +0x07,0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x07,0x82,0x04,0x83,0x06, +0x82,0x05,0x82,0x06,0x82,0x04,0x83,0x08,0x82,0x0a,0x83,0x04,0x82,0x08,0x82, +0x03,0x82,0x06,0x83,0x04,0x83,0x08,0x83,0x09,0x82,0x04,0x83,0x08,0x82,0x03, +0x82,0x09,0x83,0x0b,0x82,0x0f,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x1a,0x81, +0x03,0x82,0x09,0x88,0x08,0x81,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x85,0x17, +0x82,0x0d,0x82,0x29,0x82,0x1d,0x82,0x0c,0x82,0x0c,0x83,0x02,0x83,0x0a,0x83, +0x09,0x89,0x06,0x84,0x01,0x83,0x0c,0x83,0x07,0x84,0x01,0x83,0x08,0x83,0x01, +0x83,0x09,0x82,0x0b,0x84,0x01,0x83,0x07,0x82,0x02,0x84,0x09,0x82,0x0d,0x82, +0x13,0x81,0x15,0x82,0x10,0x82,0x08,0x81,0x0f,0x82,0x08,0x82,0x03,0x8a,0x06, +0x83,0x02,0x84,0x05,0x89,0x07,0x89,0x06,0x82,0x0d,0x84,0x02,0x84,0x03,0x82, +0x08,0x82,0x06,0x86,0x09,0x83,0x01,0x83,0x07,0x82,0x06,0x83,0x04,0x8a,0x02, +0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x07,0x83,0x04,0x89,0x05,0x82,0x0e,0x8a, +0x04,0x82,0x07,0x82,0x05,0x84,0x02,0x83,0x09,0x83,0x09,0x84,0x01,0x84,0x08, +0x83,0x09,0x82,0x05,0x82,0x06,0x82,0x06,0x82,0x09,0x82,0x09,0x8b,0x09,0x82, +0x0f,0x82,0x0a,0x82,0x37,0x89,0x05,0x88,0x08,0x88,0x07,0x89,0x06,0x84,0x02, +0x82,0x0a,0x82,0x0a,0x89,0x06,0x82,0x05,0x82,0x06,0x88,0x0b,0x82,0x0a,0x82, +0x04,0x82,0x06,0x88,0x04,0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x07, +0x84,0x01,0x83,0x07,0x84,0x01,0x83,0x07,0x83,0x02,0x84,0x08,0x82,0x0b,0x83, +0x02,0x83,0x08,0x87,0x06,0x8a,0x08,0x83,0x09,0x82,0x05,0x82,0x07,0x83,0x03, +0x83,0x08,0x83,0x0a,0x89,0x09,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x1a,0x81, +0x03,0x82,0x0a,0x86,0x0f,0x84,0x05,0x86,0x01,0x83,0x16,0x82,0x0d,0x82,0x29, +0x82,0x1d,0x82,0x0c,0x82,0x0d,0x86,0x0b,0x83,0x09,0x89,0x07,0x86,0x0d,0x83, +0x08,0x86,0x0a,0x85,0x0a,0x82,0x0c,0x86,0x08,0x86,0x0b,0x82,0x0d,0x82,0x3b, +0x82,0x09,0x81,0x0e,0x82,0x08,0x82,0x03,0x89,0x08,0x86,0x07,0x88,0x08,0x89, +0x06,0x82,0x0e,0x87,0x05,0x82,0x08,0x82,0x06,0x86,0x0a,0x85,0x08,0x82,0x07, +0x82,0x04,0x8a,0x02,0x82,0x0a,0x82,0x03,0x82,0x07,0x83,0x05,0x86,0x07,0x82, +0x0f,0x86,0x01,0x83,0x03,0x82,0x07,0x82,0x07,0x86,0x0a,0x83,0x0a,0x87,0x0a, +0x82,0x09,0x82,0x06,0x81,0x05,0x83,0x06,0x83,0x08,0x82,0x09,0x8b,0x09,0x82, +0x0f,0x82,0x0a,0x82,0x37,0x85,0x02,0x82,0x05,0x81,0x01,0x85,0x0b,0x85,0x09, +0x85,0x01,0x82,0x08,0x86,0x0a,0x82,0x0b,0x85,0x01,0x82,0x06,0x82,0x05,0x82, +0x06,0x88,0x0b,0x82,0x0a,0x82,0x04,0x83,0x05,0x88,0x04,0x82,0x04,0x82,0x04, +0x82,0x03,0x82,0x05,0x82,0x09,0x85,0x08,0x87,0x09,0x85,0x01,0x82,0x08,0x82, +0x0c,0x86,0x0a,0x84,0x09,0x86,0x01,0x82,0x09,0x82,0x0a,0x81,0x05,0x82,0x07, +0x82,0x05,0x82,0x08,0x83,0x0a,0x89,0x09,0x82,0x0d,0x82,0x0d,0x82,0x68,0x81, +0x3b,0x82,0x0b,0x82,0x2a,0x82,0x2a,0x82,0x7f,0x35,0x82,0x46,0x83,0x7f,0x7f, +0x08,0x82,0x7f,0x10,0x82,0x10,0x82,0x09,0x82,0x18,0x8a,0x76,0x82,0x28,0x82, +0x54,0x82,0x14,0x82,0x71,0x82,0x1d,0x82,0x0d,0x82,0x0d,0x82,0x68,0x81,0x3b, +0x82,0x0b,0x82,0x2a,0x82,0x2a,0x82,0x7f,0x35,0x82,0x48,0x86,0x7f,0x7f,0x7f, +0x15,0x84,0x0e,0x82,0x07,0x84,0x18,0x8a,0x76,0x82,0x28,0x82,0x54,0x82,0x14, +0x82,0x71,0x82,0x1d,0x83,0x0c,0x82,0x0d,0x82,0x7f,0x26,0x82,0x09,0x82,0x2b, +0x81,0x7f,0x62,0x81,0x7f,0x7f,0x7f,0x64,0x84,0x17,0x84,0x7f,0x12,0x82,0x04, +0x82,0x28,0x83,0x54,0x82,0x14,0x82,0x70,0x83,0x1e,0x83,0x0b,0x82,0x0c,0x82, +0x7f,0x28,0x82,0x07,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x27,0x88,0x26, +0x84,0x55,0x82,0x14,0x82,0x6e,0x84,0x21,0x81,0x0b,0x82,0x0c,0x81,0x7f,0x2a, +0x81,0x07,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x2a,0x84,0x28,0x83,0x7f, +0x5d,0x83,0x64, +0x00, + } +}; diff --git a/minui/roboto_23x41.h b/minui/roboto_23x41.h new file mode 100644 index 00000000..6e7566ef --- /dev/null +++ b/minui/roboto_23x41.h @@ -0,0 +1,461 @@ +struct { + unsigned width; + unsigned height; + unsigned cwidth; + unsigned cheight; + unsigned char rundata[]; +} font = { + .width = 2208, + .height = 41, + .cwidth = 23, + .cheight = 41, + .rundata = { +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x17,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x2e,0x83,0x60,0x82,0x0a,0x81,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0b,0x87,0x23,0x86,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x05,0x83,0x5f,0x83,0x09,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x0a,0x87,0x23,0x86,0x7f,0x7f,0x7f,0x7f,0x7f,0x39,0x82,0x26, +0x82,0x7f,0x21,0x83,0x5e,0x83,0x0b,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x09,0x87,0x23,0x86,0x40,0x84,0x27,0x83,0x37,0x83,0x27,0x86,0x20, +0x83,0x1a,0x83,0x17,0x83,0x0c,0x83,0x16,0x86,0x7f,0x7f,0x5a,0x85,0x10,0x83, +0x11,0x85,0x7f,0x1d,0x87,0x5c,0x83,0x0b,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x09,0x83,0x2a,0x83,0x41,0x83,0x27,0x83,0x37,0x83,0x26,0x86, +0x21,0x83,0x1a,0x83,0x17,0x83,0x0c,0x83,0x16,0x86,0x7f,0x7f,0x59,0x84,0x12, +0x83,0x13,0x84,0x58,0x83,0x11,0x83,0x03,0x83,0x10,0x83,0x04,0x83,0x0a,0x8a, +0x0c,0x84,0x16,0x85,0x14,0x83,0x18,0x83,0x0d,0x83,0x7f,0x11,0x83,0x0d,0x86, +0x13,0x84,0x11,0x86,0x11,0x86,0x16,0x83,0x0d,0x8c,0x0e,0x86,0x0b,0x91,0x0c, +0x87,0x0f,0x86,0x7f,0x05,0x86,0x2a,0x83,0x0d,0x8c,0x10,0x87,0x0a,0x8c,0x0c, +0x90,0x07,0x90,0x0d,0x86,0x0a,0x83,0x0d,0x83,0x09,0x89,0x16,0x83,0x08,0x83, +0x0b,0x84,0x06,0x83,0x10,0x84,0x0e,0x84,0x03,0x83,0x0d,0x83,0x0a,0x86,0x0c, +0x8d,0x0f,0x86,0x0c,0x8c,0x10,0x87,0x09,0x94,0x04,0x83,0x0d,0x83,0x03,0x83, +0x0e,0x84,0x01,0x83,0x10,0x83,0x03,0x84,0x0b,0x84,0x03,0x84,0x0d,0x84,0x04, +0x92,0x0c,0x83,0x0e,0x83,0x19,0x83,0x14,0x83,0x2b,0x83,0x26,0x83,0x37,0x83, +0x25,0x87,0x21,0x83,0x1a,0x83,0x17,0x83,0x0c,0x83,0x19,0x83,0x7f,0x7f,0x59, +0x83,0x13,0x83,0x14,0x83,0x58,0x83,0x11,0x83,0x03,0x83,0x10,0x83,0x04,0x83, +0x09,0x8c,0x09,0x88,0x12,0x89,0x12,0x83,0x17,0x83,0x0f,0x83,0x7f,0x0f,0x83, +0x0c,0x8a,0x0e,0x87,0x0f,0x8a,0x0d,0x8a,0x13,0x84,0x0d,0x8c,0x0c,0x8a,0x09, +0x91,0x0a,0x8b,0x0b,0x8a,0x7f,0x01,0x8a,0x28,0x83,0x0d,0x8e,0x0c,0x8b,0x08, +0x8e,0x0a,0x90,0x07,0x90,0x0a,0x8c,0x07,0x83,0x0d,0x83,0x09,0x89,0x16,0x83, +0x08,0x83,0x0a,0x84,0x07,0x83,0x10,0x85,0x0d,0x84,0x03,0x84,0x0c,0x83,0x08, +0x8a,0x0a,0x8f,0x0b,0x8a,0x0a,0x8e,0x0c,0x8b,0x07,0x94,0x04,0x83,0x0d,0x83, +0x03,0x84,0x0d,0x84,0x01,0x83,0x08,0x81,0x07,0x83,0x03,0x84,0x0b,0x84,0x04, +0x84,0x0b,0x84,0x05,0x92,0x0c,0x83,0x0e,0x84,0x18,0x83,0x14,0x83,0x2b,0x84, +0x25,0x83,0x37,0x83,0x24,0x84,0x25,0x83,0x43,0x83,0x19,0x83,0x7f,0x33,0x83, +0x7f,0x22,0x84,0x13,0x83,0x14,0x83,0x58,0x83,0x11,0x83,0x03,0x83,0x10,0x83, +0x04,0x83,0x09,0x84,0x05,0x84,0x08,0x83,0x02,0x83,0x11,0x8b,0x11,0x83,0x17, +0x83,0x0f,0x83,0x7f,0x0f,0x83,0x0b,0x8c,0x0b,0x89,0x0e,0x8c,0x0b,0x8c,0x11, +0x85,0x0c,0x8d,0x0b,0x8b,0x09,0x91,0x09,0x8d,0x09,0x8c,0x7e,0x8c,0x0e,0x87, +0x11,0x85,0x0c,0x8f,0x0a,0x8e,0x06,0x8f,0x09,0x90,0x07,0x90,0x09,0x8e,0x06, +0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x09,0x84,0x08,0x83,0x10,0x85, +0x0c,0x85,0x03,0x85,0x0b,0x83,0x06,0x8e,0x08,0x90,0x08,0x8e,0x08,0x8f,0x0a, +0x8d,0x06,0x94,0x04,0x83,0x0d,0x83,0x03,0x84,0x0d,0x83,0x02,0x83,0x08,0x81, +0x07,0x83,0x04,0x84,0x09,0x84,0x05,0x84,0x0b,0x84,0x05,0x92,0x0c,0x83,0x0f, +0x83,0x18,0x83,0x13,0x85,0x2b,0x83,0x25,0x83,0x37,0x83,0x24,0x83,0x26,0x83, +0x43,0x83,0x19,0x83,0x7f,0x33,0x83,0x7f,0x22,0x83,0x14,0x83,0x15,0x83,0x57, +0x83,0x11,0x83,0x03,0x83,0x10,0x82,0x05,0x83,0x08,0x84,0x07,0x83,0x07,0x83, +0x04,0x83,0x05,0x82,0x08,0x85,0x04,0x84,0x10,0x83,0x16,0x83,0x11,0x83,0x7f, +0x0e,0x83,0x0a,0x85,0x04,0x85,0x09,0x86,0x01,0x83,0x0d,0x84,0x05,0x84,0x0a, +0x84,0x05,0x84,0x11,0x85,0x0c,0x84,0x13,0x84,0x06,0x82,0x16,0x84,0x09,0x84, +0x05,0x84,0x08,0x85,0x04,0x85,0x7d,0x84,0x04,0x84,0x0c,0x8b,0x0f,0x85,0x0c, +0x83,0x08,0x85,0x08,0x85,0x06,0x84,0x06,0x83,0x08,0x85,0x08,0x83,0x14,0x83, +0x15,0x85,0x06,0x85,0x05,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x08, +0x84,0x09,0x83,0x10,0x86,0x0b,0x85,0x03,0x85,0x0b,0x83,0x05,0x85,0x06,0x85, +0x07,0x83,0x09,0x85,0x06,0x85,0x06,0x85,0x07,0x83,0x08,0x85,0x08,0x85,0x05, +0x85,0x0e,0x83,0x0c,0x83,0x0d,0x83,0x04,0x83,0x0c,0x84,0x02,0x83,0x07,0x82, +0x07,0x83,0x05,0x84,0x07,0x84,0x07,0x84,0x09,0x84,0x14,0x83,0x0d,0x83,0x0f, +0x83,0x18,0x83,0x13,0x85,0x53,0x83,0x37,0x83,0x24,0x83,0x26,0x83,0x43,0x83, +0x19,0x83,0x7f,0x33,0x83,0x7f,0x22,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x11, +0x83,0x03,0x83,0x10,0x82,0x05,0x82,0x09,0x83,0x08,0x84,0x06,0x83,0x04,0x83, +0x05,0x82,0x08,0x83,0x07,0x83,0x10,0x83,0x16,0x83,0x11,0x83,0x2c,0x83,0x5d, +0x83,0x0b,0x83,0x08,0x83,0x09,0x83,0x04,0x83,0x0d,0x83,0x07,0x84,0x08,0x84, +0x07,0x84,0x0f,0x86,0x0c,0x83,0x13,0x84,0x1e,0x84,0x09,0x84,0x07,0x84,0x07, +0x83,0x08,0x83,0x7c,0x84,0x06,0x84,0x0a,0x83,0x06,0x84,0x0e,0x86,0x0b,0x83, +0x0a,0x84,0x07,0x84,0x08,0x84,0x05,0x83,0x0a,0x84,0x07,0x83,0x14,0x83,0x15, +0x84,0x09,0x83,0x05,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x08,0x83, +0x0a,0x83,0x10,0x86,0x0b,0x85,0x03,0x86,0x0a,0x83,0x05,0x84,0x08,0x84,0x07, +0x83,0x0b,0x83,0x06,0x84,0x08,0x84,0x07,0x83,0x0a,0x84,0x06,0x84,0x09,0x83, +0x0e,0x83,0x0c,0x83,0x0d,0x83,0x04,0x84,0x0b,0x84,0x03,0x82,0x07,0x83,0x06, +0x82,0x06,0x84,0x07,0x84,0x08,0x83,0x09,0x84,0x13,0x84,0x0d,0x83,0x0f,0x84, +0x17,0x83,0x13,0x82,0x01,0x83,0x52,0x83,0x37,0x83,0x24,0x83,0x26,0x83,0x43, +0x83,0x19,0x83,0x7f,0x33,0x83,0x7f,0x22,0x83,0x14,0x83,0x15,0x83,0x57,0x83, +0x11,0x83,0x03,0x83,0x0f,0x83,0x04,0x83,0x09,0x83,0x09,0x83,0x06,0x83,0x04, +0x83,0x04,0x82,0x09,0x83,0x07,0x83,0x10,0x83,0x15,0x83,0x13,0x83,0x2b,0x83, +0x5d,0x83,0x0b,0x83,0x08,0x83,0x10,0x83,0x0c,0x83,0x09,0x83,0x08,0x83,0x09, +0x83,0x0f,0x86,0x0c,0x83,0x13,0x83,0x1e,0x84,0x0a,0x83,0x09,0x83,0x06,0x84, +0x08,0x83,0x7c,0x83,0x08,0x83,0x09,0x83,0x09,0x83,0x0c,0x87,0x0b,0x83,0x0b, +0x83,0x06,0x84,0x0a,0x84,0x04,0x83,0x0b,0x84,0x06,0x83,0x14,0x83,0x14,0x84, +0x0a,0x84,0x04,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x07,0x84,0x0a, +0x83,0x10,0x86,0x0a,0x86,0x03,0x87,0x09,0x83,0x04,0x84,0x0a,0x84,0x06,0x83, +0x0b,0x84,0x04,0x84,0x0a,0x84,0x06,0x83,0x0b,0x83,0x06,0x83,0x0a,0x84,0x0d, +0x83,0x0c,0x83,0x0d,0x83,0x04,0x84,0x0b,0x83,0x04,0x83,0x06,0x83,0x06,0x82, +0x07,0x84,0x05,0x84,0x09,0x84,0x07,0x84,0x13,0x84,0x0e,0x83,0x10,0x83,0x17, +0x83,0x12,0x83,0x01,0x83,0x52,0x83,0x37,0x83,0x24,0x83,0x26,0x83,0x43,0x83, +0x19,0x83,0x7f,0x33,0x83,0x7f,0x22,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x11, +0x82,0x04,0x82,0x10,0x83,0x04,0x83,0x09,0x83,0x09,0x83,0x06,0x83,0x04,0x83, +0x04,0x82,0x09,0x83,0x07,0x83,0x10,0x82,0x16,0x83,0x13,0x83,0x14,0x83,0x14, +0x83,0x5d,0x82,0x0b,0x83,0x0a,0x83,0x0f,0x83,0x0c,0x83,0x09,0x83,0x08,0x83, +0x09,0x83,0x0e,0x83,0x01,0x83,0x0c,0x83,0x13,0x83,0x1e,0x83,0x0b,0x83,0x09, +0x83,0x06,0x83,0x0a,0x83,0x0d,0x83,0x14,0x83,0x5f,0x83,0x08,0x83,0x0b,0x82, +0x0c,0x83,0x01,0x83,0x0b,0x83,0x0b,0x83,0x06,0x83,0x0c,0x83,0x04,0x83,0x0c, +0x83,0x06,0x83,0x14,0x83,0x14,0x83,0x0c,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83, +0x19,0x83,0x08,0x83,0x06,0x84,0x0b,0x83,0x10,0x87,0x09,0x86,0x03,0x87,0x09, +0x83,0x04,0x83,0x0c,0x83,0x06,0x83,0x0c,0x83,0x04,0x83,0x0c,0x83,0x06,0x83, +0x0b,0x83,0x06,0x83,0x0b,0x83,0x0d,0x83,0x0c,0x83,0x0d,0x83,0x05,0x83,0x0a, +0x84,0x04,0x83,0x06,0x83,0x05,0x83,0x07,0x84,0x05,0x84,0x0a,0x83,0x07,0x84, +0x13,0x83,0x0f,0x83,0x10,0x83,0x17,0x83,0x12,0x83,0x01,0x83,0x40,0x86,0x0c, +0x83,0x03,0x85,0x11,0x86,0x10,0x85,0x03,0x83,0x0d,0x85,0x0f,0x8a,0x0f,0x85, +0x04,0x82,0x08,0x83,0x03,0x85,0x0f,0x86,0x17,0x83,0x0c,0x83,0x07,0x84,0x0e, +0x83,0x0a,0x83,0x02,0x83,0x07,0x83,0x09,0x83,0x03,0x85,0x10,0x86,0x0d,0x82, +0x04,0x85,0x10,0x85,0x04,0x82,0x09,0x83,0x02,0x85,0x11,0x86,0x0c,0x8d,0x0a, +0x83,0x09,0x83,0x08,0x84,0x09,0x83,0x03,0x84,0x0f,0x83,0x05,0x84,0x08,0x84, +0x07,0x83,0x0a,0x83,0x07,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x11, +0x82,0x04,0x82,0x0b,0x92,0x06,0x83,0x12,0x83,0x04,0x83,0x03,0x82,0x0a,0x83, +0x06,0x84,0x10,0x82,0x16,0x83,0x13,0x83,0x14,0x83,0x14,0x83,0x5c,0x83,0x0b, +0x83,0x0a,0x83,0x0f,0x83,0x0c,0x83,0x09,0x83,0x14,0x83,0x0d,0x84,0x01,0x83, +0x0c,0x83,0x12,0x83,0x1e,0x83,0x0c,0x83,0x09,0x83,0x06,0x83,0x0a,0x83,0x0d, +0x83,0x14,0x83,0x5f,0x83,0x07,0x83,0x0d,0x82,0x0b,0x83,0x01,0x84,0x0a,0x83, +0x0b,0x83,0x05,0x84,0x0c,0x83,0x04,0x83,0x0c,0x83,0x06,0x83,0x14,0x83,0x13, +0x83,0x0d,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x05,0x84, +0x0c,0x83,0x10,0x83,0x01,0x83,0x08,0x83,0x01,0x83,0x03,0x83,0x01,0x84,0x08, +0x83,0x03,0x84,0x0c,0x84,0x05,0x83,0x0c,0x83,0x03,0x84,0x0c,0x84,0x05,0x83, +0x0b,0x83,0x06,0x83,0x0b,0x83,0x0d,0x83,0x0c,0x83,0x0d,0x83,0x05,0x84,0x09, +0x84,0x04,0x83,0x06,0x83,0x05,0x83,0x08,0x84,0x03,0x84,0x0b,0x84,0x05,0x84, +0x13,0x84,0x0f,0x83,0x10,0x84,0x16,0x83,0x11,0x83,0x03,0x83,0x3d,0x8a,0x0a, +0x83,0x01,0x89,0x0d,0x89,0x0d,0x89,0x01,0x83,0x0b,0x89,0x0d,0x8a,0x0d,0x89, +0x01,0x83,0x08,0x83,0x01,0x89,0x0d,0x86,0x17,0x83,0x0c,0x83,0x06,0x84,0x0f, +0x83,0x0a,0x83,0x01,0x86,0x03,0x87,0x07,0x83,0x01,0x89,0x0d,0x89,0x0b,0x83, +0x01,0x89,0x0c,0x89,0x01,0x83,0x09,0x83,0x01,0x88,0x0d,0x8a,0x0a,0x8d,0x0a, +0x83,0x09,0x83,0x08,0x84,0x09,0x83,0x03,0x84,0x06,0x82,0x06,0x84,0x06,0x84, +0x06,0x84,0x08,0x84,0x08,0x84,0x07,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,0x57, +0x83,0x11,0x82,0x04,0x82,0x0b,0x92,0x06,0x84,0x12,0x83,0x02,0x83,0x03,0x83, +0x0a,0x84,0x04,0x84,0x11,0x82,0x16,0x83,0x13,0x83,0x14,0x83,0x14,0x83,0x5c, +0x83,0x0b,0x83,0x0a,0x83,0x0f,0x83,0x18,0x83,0x13,0x84,0x0d,0x83,0x02,0x83, +0x0c,0x83,0x12,0x83,0x1d,0x84,0x0c,0x84,0x07,0x84,0x06,0x83,0x0a,0x83,0x0d, +0x83,0x14,0x83,0x1a,0x82,0x0a,0x8f,0x08,0x81,0x21,0x83,0x07,0x82,0x06,0x84, +0x04,0x83,0x09,0x83,0x03,0x83,0x0a,0x83,0x0b,0x83,0x05,0x83,0x0d,0x83,0x04, +0x83,0x0d,0x83,0x05,0x83,0x14,0x83,0x13,0x83,0x14,0x83,0x0d,0x83,0x0c,0x83, +0x19,0x83,0x08,0x83,0x04,0x84,0x0d,0x83,0x10,0x83,0x01,0x84,0x07,0x83,0x01, +0x83,0x03,0x83,0x01,0x85,0x07,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x0c,0x83, +0x03,0x83,0x0e,0x83,0x05,0x83,0x0b,0x83,0x06,0x83,0x1b,0x83,0x0c,0x83,0x0d, +0x83,0x05,0x84,0x09,0x83,0x06,0x82,0x06,0x83,0x05,0x82,0x0a,0x83,0x02,0x84, +0x0d,0x83,0x05,0x83,0x13,0x84,0x10,0x83,0x11,0x83,0x16,0x83,0x11,0x83,0x03, +0x83,0x3c,0x8c,0x09,0x8e,0x0b,0x8b,0x0b,0x8e,0x0a,0x8b,0x0c,0x8a,0x0c,0x8e, +0x08,0x8e,0x0f,0x83,0x17,0x83,0x0c,0x83,0x06,0x84,0x0f,0x83,0x0a,0x8b,0x01, +0x89,0x06,0x8e,0x0a,0x8c,0x0a,0x8e,0x0a,0x8e,0x09,0x8d,0x0b,0x8c,0x09,0x8d, +0x0a,0x83,0x09,0x83,0x08,0x84,0x08,0x84,0x03,0x84,0x05,0x83,0x06,0x84,0x06, +0x84,0x06,0x83,0x09,0x84,0x08,0x84,0x07,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83, +0x57,0x83,0x24,0x92,0x07,0x83,0x12,0x88,0x03,0x82,0x0c,0x83,0x03,0x84,0x29, +0x83,0x15,0x83,0x0f,0x81,0x03,0x83,0x03,0x81,0x10,0x83,0x5b,0x83,0x0c,0x83, +0x0a,0x83,0x0f,0x83,0x17,0x83,0x13,0x84,0x0d,0x83,0x03,0x83,0x0c,0x83,0x01, +0x85,0x0c,0x83,0x03,0x85,0x15,0x83,0x0e,0x84,0x05,0x84,0x07,0x83,0x0a,0x83, +0x3f,0x84,0x0a,0x8f,0x08,0x83,0x1e,0x83,0x07,0x82,0x06,0x86,0x04,0x82,0x09, +0x83,0x03,0x83,0x0a,0x83,0x0a,0x83,0x06,0x83,0x14,0x83,0x0d,0x83,0x05,0x83, +0x14,0x83,0x13,0x83,0x14,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x03, +0x84,0x0e,0x83,0x10,0x83,0x02,0x83,0x06,0x84,0x01,0x83,0x03,0x83,0x02,0x84, +0x07,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x0c,0x83,0x03,0x83,0x0e,0x83,0x05, +0x83,0x0b,0x83,0x07,0x83,0x1a,0x83,0x0c,0x83,0x0d,0x83,0x06,0x83,0x08,0x84, +0x06,0x83,0x04,0x85,0x04,0x82,0x0a,0x84,0x01,0x84,0x0d,0x84,0x03,0x84,0x12, +0x84,0x11,0x83,0x11,0x84,0x15,0x83,0x11,0x83,0x03,0x84,0x3b,0x84,0x04,0x84, +0x09,0x85,0x05,0x84,0x0b,0x84,0x04,0x84,0x0a,0x84,0x05,0x85,0x09,0x84,0x05, +0x84,0x0e,0x83,0x10,0x84,0x05,0x85,0x08,0x85,0x05,0x84,0x0f,0x83,0x17,0x83, +0x0c,0x83,0x05,0x84,0x10,0x83,0x0a,0x85,0x02,0x84,0x01,0x83,0x02,0x84,0x06, +0x85,0x05,0x84,0x0a,0x84,0x05,0x84,0x09,0x85,0x05,0x84,0x0a,0x84,0x05,0x85, +0x09,0x85,0x05,0x84,0x09,0x84,0x05,0x84,0x0c,0x83,0x11,0x83,0x09,0x83,0x09, +0x83,0x08,0x83,0x05,0x83,0x05,0x83,0x06,0x83,0x08,0x84,0x04,0x84,0x0a,0x83, +0x08,0x83,0x12,0x84,0x0f,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x28,0x83,0x05, +0x82,0x0b,0x85,0x12,0x84,0x04,0x82,0x0d,0x89,0x2a,0x83,0x15,0x83,0x0e,0x88, +0x01,0x83,0x10,0x83,0x5b,0x83,0x0c,0x83,0x0a,0x83,0x0f,0x83,0x17,0x83,0x0e, +0x88,0x0e,0x83,0x03,0x83,0x0c,0x8b,0x0a,0x83,0x01,0x89,0x12,0x83,0x10,0x8b, +0x08,0x83,0x0a,0x83,0x3c,0x87,0x0a,0x8f,0x08,0x86,0x1a,0x84,0x07,0x82,0x05, +0x83,0x02,0x82,0x04,0x82,0x08,0x84,0x03,0x84,0x09,0x83,0x09,0x84,0x06,0x83, +0x14,0x83,0x0d,0x83,0x05,0x83,0x14,0x83,0x13,0x83,0x14,0x83,0x0d,0x83,0x0c, +0x83,0x19,0x83,0x08,0x83,0x02,0x84,0x0f,0x83,0x10,0x83,0x02,0x83,0x06,0x83, +0x02,0x83,0x03,0x83,0x03,0x84,0x06,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x0b, +0x84,0x03,0x83,0x0e,0x83,0x05,0x83,0x0a,0x83,0x08,0x84,0x19,0x83,0x0c,0x83, +0x0d,0x83,0x06,0x84,0x07,0x84,0x06,0x83,0x04,0x85,0x03,0x83,0x0b,0x87,0x0f, +0x83,0x03,0x83,0x13,0x84,0x11,0x83,0x12,0x83,0x15,0x83,0x10,0x83,0x05,0x83, +0x3a,0x83,0x07,0x84,0x08,0x84,0x07,0x84,0x09,0x84,0x06,0x84,0x08,0x84,0x07, +0x84,0x08,0x84,0x07,0x83,0x0e,0x83,0x0f,0x84,0x07,0x84,0x08,0x84,0x07,0x84, +0x0e,0x83,0x17,0x83,0x0c,0x83,0x04,0x84,0x11,0x83,0x0a,0x84,0x04,0x85,0x05, +0x84,0x05,0x84,0x07,0x84,0x08,0x84,0x07,0x84,0x08,0x84,0x07,0x84,0x08,0x84, +0x07,0x84,0x09,0x84,0x08,0x82,0x09,0x83,0x07,0x84,0x0b,0x83,0x11,0x83,0x09, +0x83,0x09,0x84,0x07,0x83,0x05,0x83,0x05,0x83,0x06,0x83,0x09,0x83,0x04,0x83, +0x0b,0x84,0x06,0x84,0x12,0x84,0x0f,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x28, +0x83,0x04,0x83,0x0c,0x86,0x17,0x83,0x0e,0x86,0x2c,0x83,0x15,0x83,0x0e,0x8d, +0x0f,0x83,0x5b,0x82,0x0d,0x83,0x0a,0x83,0x0f,0x83,0x16,0x83,0x0f,0x87,0x0e, +0x83,0x04,0x83,0x0c,0x8c,0x09,0x8e,0x11,0x83,0x12,0x87,0x0a,0x84,0x09,0x83, +0x3a,0x88,0x22,0x88,0x18,0x83,0x08,0x82,0x04,0x83,0x03,0x82,0x04,0x83,0x07, +0x83,0x05,0x83,0x09,0x8f,0x07,0x83,0x14,0x83,0x0d,0x83,0x05,0x8e,0x09,0x8f, +0x07,0x83,0x14,0x93,0x0c,0x83,0x19,0x83,0x08,0x88,0x10,0x83,0x10,0x83,0x02, +0x84,0x05,0x83,0x02,0x83,0x03,0x83,0x03,0x85,0x05,0x83,0x03,0x83,0x0e,0x83, +0x05,0x83,0x0b,0x83,0x04,0x83,0x0e,0x83,0x05,0x83,0x09,0x84,0x09,0x86,0x16, +0x83,0x0c,0x83,0x0d,0x83,0x07,0x83,0x07,0x83,0x07,0x83,0x04,0x82,0x01,0x82, +0x03,0x83,0x0c,0x85,0x10,0x84,0x01,0x84,0x12,0x84,0x12,0x83,0x12,0x83,0x15, +0x83,0x10,0x83,0x05,0x83,0x3a,0x83,0x08,0x83,0x08,0x83,0x09,0x83,0x09,0x83, +0x08,0x83,0x08,0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x0d,0x83,0x0f,0x83,0x09, +0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,0x04,0x84,0x11,0x83, +0x0a,0x83,0x06,0x84,0x06,0x83,0x05,0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x08, +0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x09,0x83,0x14,0x83,0x08,0x83,0x0b,0x83, +0x11,0x83,0x09,0x83,0x0a,0x83,0x06,0x84,0x05,0x84,0x04,0x84,0x05,0x83,0x09, +0x84,0x02,0x84,0x0b,0x84,0x06,0x84,0x11,0x84,0x0f,0x83,0x15,0x83,0x16,0x83, +0x56,0x83,0x28,0x83,0x04,0x83,0x0d,0x88,0x14,0x82,0x0f,0x85,0x2d,0x83,0x15, +0x83,0x11,0x88,0x0a,0x92,0x52,0x83,0x0d,0x83,0x0a,0x83,0x0f,0x83,0x15,0x84, +0x0f,0x89,0x0b,0x83,0x05,0x83,0x0b,0x85,0x04,0x85,0x08,0x85,0x05,0x85,0x0f, +0x83,0x11,0x8b,0x09,0x83,0x08,0x84,0x38,0x88,0x26,0x88,0x15,0x83,0x08,0x82, +0x05,0x83,0x03,0x82,0x05,0x82,0x07,0x83,0x05,0x83,0x09,0x8f,0x07,0x83,0x14, +0x83,0x0d,0x83,0x05,0x8e,0x09,0x8f,0x07,0x83,0x07,0x89,0x04,0x93,0x0c,0x83, +0x19,0x83,0x08,0x88,0x10,0x83,0x10,0x83,0x03,0x83,0x04,0x83,0x03,0x83,0x03, +0x83,0x04,0x84,0x05,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x09,0x85,0x04,0x83, +0x0e,0x83,0x05,0x8f,0x0c,0x87,0x13,0x83,0x0c,0x83,0x0d,0x83,0x07,0x83,0x06, +0x84,0x08,0x82,0x04,0x82,0x01,0x83,0x02,0x82,0x0d,0x85,0x11,0x83,0x01,0x83, +0x12,0x84,0x13,0x83,0x12,0x84,0x14,0x83,0x0f,0x84,0x06,0x83,0x44,0x83,0x08, +0x83,0x09,0x84,0x07,0x83,0x09,0x83,0x07,0x84,0x09,0x83,0x07,0x83,0x0a,0x83, +0x0d,0x83,0x0e,0x84,0x09,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c, +0x83,0x03,0x84,0x12,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83, +0x07,0x84,0x09,0x84,0x07,0x83,0x09,0x84,0x06,0x84,0x09,0x83,0x09,0x83,0x14, +0x83,0x16,0x83,0x11,0x83,0x09,0x83,0x0a,0x83,0x06,0x83,0x06,0x84,0x03,0x85, +0x04,0x83,0x0b,0x88,0x0d,0x83,0x06,0x83,0x11,0x84,0x0f,0x84,0x15,0x83,0x16, +0x84,0x0c,0x85,0x44,0x83,0x28,0x82,0x05,0x83,0x0e,0x88,0x12,0x82,0x0f,0x87, +0x2c,0x83,0x15,0x83,0x12,0x85,0x0c,0x92,0x1f,0x8d,0x26,0x83,0x0d,0x83,0x0a, +0x83,0x0f,0x83,0x15,0x83,0x16,0x84,0x0a,0x83,0x05,0x83,0x0b,0x84,0x07,0x83, +0x08,0x84,0x08,0x83,0x0f,0x83,0x10,0x84,0x04,0x85,0x08,0x85,0x04,0x86,0x36, +0x87,0x2c,0x87,0x12,0x83,0x09,0x82,0x04,0x83,0x04,0x82,0x05,0x82,0x06,0x83, +0x06,0x84,0x08,0x90,0x06,0x83,0x14,0x83,0x0d,0x83,0x05,0x8e,0x09,0x8f,0x07, +0x83,0x07,0x89,0x04,0x93,0x0c,0x83,0x19,0x83,0x08,0x89,0x0f,0x83,0x10,0x83, +0x03,0x84,0x03,0x83,0x03,0x83,0x03,0x83,0x05,0x84,0x04,0x83,0x03,0x83,0x0e, +0x83,0x05,0x90,0x05,0x83,0x0e,0x83,0x05,0x8e,0x0f,0x87,0x11,0x83,0x0c,0x83, +0x0d,0x83,0x07,0x84,0x05,0x83,0x09,0x83,0x02,0x83,0x02,0x82,0x02,0x82,0x0d, +0x85,0x11,0x87,0x12,0x84,0x13,0x83,0x13,0x83,0x14,0x83,0x60,0x83,0x08,0x83, +0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,0x83,0x0a,0x83,0x0d,0x83,0x0e, +0x83,0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,0x02,0x84, +0x13,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x07,0x83,0x0b, +0x83,0x07,0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,0x14,0x84,0x15,0x83, +0x11,0x83,0x09,0x83,0x0a,0x84,0x05,0x83,0x07,0x83,0x03,0x85,0x04,0x83,0x0c, +0x86,0x0e,0x84,0x05,0x83,0x10,0x84,0x0f,0x84,0x16,0x83,0x17,0x85,0x09,0x88, +0x07,0x83,0x38,0x83,0x28,0x82,0x05,0x82,0x12,0x86,0x11,0x82,0x03,0x84,0x07, +0x84,0x01,0x84,0x06,0x83,0x22,0x83,0x15,0x83,0x11,0x87,0x0b,0x92,0x1f,0x8d, +0x26,0x82,0x0e,0x83,0x0a,0x83,0x0f,0x83,0x14,0x83,0x18,0x83,0x09,0x83,0x06, +0x83,0x16,0x84,0x07,0x83,0x09,0x84,0x0d,0x83,0x10,0x83,0x08,0x83,0x09,0x8e, +0x34,0x87,0x30,0x87,0x0f,0x83,0x0a,0x82,0x04,0x83,0x04,0x82,0x05,0x82,0x06, +0x83,0x07,0x83,0x08,0x83,0x09,0x85,0x05,0x83,0x14,0x83,0x0d,0x83,0x05,0x83, +0x14,0x83,0x13,0x83,0x07,0x89,0x04,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08, +0x83,0x03,0x84,0x0e,0x83,0x10,0x83,0x04,0x83,0x02,0x84,0x03,0x83,0x03,0x83, +0x05,0x85,0x03,0x83,0x03,0x83,0x0e,0x83,0x05,0x8f,0x06,0x83,0x0e,0x83,0x05, +0x8f,0x11,0x86,0x0f,0x83,0x0c,0x83,0x0d,0x83,0x08,0x83,0x05,0x83,0x09,0x83, +0x02,0x82,0x03,0x82,0x01,0x83,0x0d,0x86,0x11,0x85,0x12,0x84,0x14,0x83,0x13, +0x83,0x14,0x83,0x59,0x8a,0x08,0x83,0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83, +0x07,0x90,0x0d,0x83,0x0e,0x83,0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17, +0x83,0x0c,0x88,0x14,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83, +0x07,0x83,0x0b,0x83,0x07,0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,0x15, +0x85,0x13,0x83,0x11,0x83,0x09,0x83,0x0b,0x83,0x04,0x84,0x07,0x83,0x03,0x86, +0x03,0x83,0x0c,0x86,0x0f,0x83,0x04,0x84,0x10,0x84,0x0f,0x83,0x17,0x83,0x18, +0x84,0x08,0x8a,0x06,0x83,0x38,0x83,0x27,0x83,0x05,0x82,0x14,0x85,0x0f,0x82, +0x03,0x86,0x05,0x84,0x03,0x84,0x05,0x83,0x22,0x83,0x15,0x83,0x10,0x84,0x01, +0x83,0x12,0x83,0x27,0x8d,0x25,0x83,0x0e,0x83,0x0a,0x83,0x0f,0x83,0x13,0x83, +0x1a,0x83,0x07,0x84,0x06,0x83,0x17,0x83,0x07,0x83,0x0a,0x83,0x0d,0x83,0x0f, +0x84,0x08,0x84,0x09,0x89,0x01,0x83,0x34,0x85,0x35,0x85,0x0d,0x84,0x0a,0x82, +0x04,0x83,0x04,0x82,0x05,0x82,0x06,0x83,0x07,0x83,0x08,0x83,0x0b,0x83,0x05, +0x83,0x14,0x83,0x0d,0x83,0x05,0x83,0x14,0x83,0x13,0x83,0x0d,0x83,0x04,0x83, +0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x03,0x85,0x0d,0x83,0x10,0x83,0x04, +0x84,0x01,0x83,0x04,0x83,0x03,0x83,0x06,0x84,0x03,0x83,0x03,0x83,0x0e,0x83, +0x05,0x8d,0x08,0x83,0x0e,0x83,0x05,0x83,0x09,0x84,0x13,0x84,0x0e,0x83,0x0c, +0x83,0x0d,0x83,0x08,0x83,0x04,0x84,0x09,0x83,0x02,0x82,0x03,0x82,0x01,0x83, +0x0c,0x87,0x11,0x85,0x11,0x84,0x15,0x83,0x13,0x84,0x13,0x83,0x56,0x8d,0x08, +0x83,0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,0x90,0x0d,0x83,0x0e,0x83, +0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x88,0x14,0x83,0x0a, +0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x07,0x83,0x0b,0x83,0x07,0x83, +0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,0x16,0x88,0x0f,0x83,0x11,0x83,0x09, +0x83,0x0b,0x83,0x04,0x83,0x08,0x84,0x01,0x83,0x01,0x83,0x03,0x83,0x0d,0x84, +0x10,0x83,0x04,0x83,0x10,0x84,0x10,0x84,0x16,0x83,0x17,0x85,0x07,0x84,0x04, +0x84,0x04,0x84,0x38,0x83,0x23,0x92,0x12,0x84,0x0d,0x83,0x02,0x83,0x02,0x83, +0x03,0x84,0x05,0x84,0x04,0x83,0x22,0x83,0x15,0x83,0x10,0x83,0x03,0x83,0x11, +0x83,0x59,0x82,0x0f,0x83,0x0a,0x83,0x0f,0x83,0x12,0x84,0x1a,0x83,0x07,0x83, +0x07,0x83,0x17,0x83,0x07,0x83,0x0a,0x83,0x0d,0x83,0x0f,0x83,0x0a,0x83,0x0b, +0x85,0x03,0x83,0x34,0x85,0x14,0x8f,0x13,0x84,0x0d,0x83,0x0b,0x82,0x04,0x83, +0x04,0x82,0x05,0x82,0x05,0x8f,0x07,0x83,0x0c,0x83,0x04,0x83,0x14,0x83,0x0d, +0x83,0x05,0x83,0x14,0x83,0x13,0x83,0x0d,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83, +0x19,0x83,0x08,0x83,0x04,0x85,0x0c,0x83,0x10,0x83,0x04,0x84,0x01,0x83,0x04, +0x83,0x03,0x83,0x07,0x84,0x02,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x12,0x83, +0x0e,0x83,0x05,0x83,0x0a,0x84,0x13,0x83,0x0e,0x83,0x0c,0x83,0x0d,0x83,0x08, +0x84,0x03,0x83,0x0a,0x83,0x01,0x83,0x03,0x82,0x01,0x83,0x0b,0x84,0x01,0x84, +0x11,0x83,0x12,0x83,0x16,0x83,0x14,0x83,0x13,0x83,0x55,0x8e,0x08,0x83,0x0a, +0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,0x90,0x0d,0x83,0x0e,0x83,0x0a,0x83, +0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x88,0x14,0x83,0x0a,0x83,0x06, +0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x07,0x83,0x0b,0x83,0x07,0x83,0x0a,0x83, +0x06,0x83,0x0a,0x83,0x09,0x83,0x19,0x87,0x0d,0x83,0x11,0x83,0x09,0x83,0x0b, +0x84,0x03,0x83,0x09,0x83,0x01,0x83,0x01,0x83,0x02,0x83,0x0e,0x84,0x10,0x84, +0x03,0x83,0x0f,0x84,0x12,0x84,0x15,0x83,0x16,0x84,0x09,0x83,0x06,0x8a,0x39, +0x83,0x23,0x92,0x12,0x84,0x0d,0x82,0x02,0x83,0x04,0x83,0x02,0x83,0x07,0x84, +0x02,0x84,0x22,0x83,0x15,0x83,0x11,0x81,0x04,0x82,0x12,0x83,0x58,0x83,0x0f, +0x83,0x0a,0x83,0x0f,0x83,0x11,0x84,0x1b,0x83,0x06,0x92,0x13,0x83,0x07,0x83, +0x0a,0x83,0x0d,0x83,0x0f,0x83,0x0a,0x83,0x13,0x83,0x35,0x87,0x11,0x8f,0x10, +0x87,0x0d,0x83,0x0b,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x05,0x8f,0x07,0x83, +0x0c,0x83,0x04,0x83,0x0d,0x83,0x04,0x83,0x0d,0x83,0x05,0x83,0x14,0x83,0x13, +0x83,0x0d,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x05,0x84, +0x0c,0x83,0x10,0x83,0x05,0x86,0x05,0x83,0x03,0x83,0x07,0x85,0x01,0x83,0x03, +0x83,0x0e,0x83,0x05,0x83,0x12,0x83,0x0e,0x83,0x05,0x83,0x0b,0x83,0x14,0x83, +0x0d,0x83,0x0c,0x83,0x0d,0x83,0x09,0x83,0x03,0x83,0x0b,0x82,0x01,0x82,0x04, +0x86,0x0b,0x83,0x02,0x84,0x11,0x83,0x11,0x84,0x16,0x83,0x14,0x84,0x12,0x83, +0x55,0x84,0x07,0x83,0x08,0x83,0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07, +0x83,0x1a,0x83,0x0e,0x83,0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83, +0x0c,0x83,0x02,0x84,0x13,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09, +0x83,0x07,0x83,0x0b,0x83,0x07,0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83, +0x1c,0x85,0x0c,0x83,0x11,0x83,0x09,0x83,0x0c,0x83,0x02,0x84,0x09,0x83,0x01, +0x83,0x01,0x83,0x02,0x83,0x0d,0x86,0x10,0x83,0x02,0x84,0x0e,0x84,0x14,0x83, +0x15,0x83,0x16,0x83,0x0a,0x83,0x07,0x88,0x60,0x92,0x13,0x83,0x0c,0x82,0x03, +0x83,0x04,0x83,0x02,0x83,0x07,0x85,0x01,0x83,0x23,0x83,0x15,0x83,0x2a,0x83, +0x58,0x83,0x0f,0x83,0x0a,0x83,0x0f,0x83,0x10,0x84,0x0f,0x83,0x0a,0x83,0x06, +0x92,0x13,0x83,0x07,0x83,0x0a,0x83,0x0c,0x83,0x10,0x83,0x0a,0x83,0x13,0x83, +0x37,0x87,0x0f,0x8f,0x0e,0x87,0x0f,0x83,0x0b,0x82,0x04,0x82,0x05,0x82,0x05, +0x82,0x05,0x8f,0x07,0x83,0x0c,0x83,0x04,0x84,0x0c,0x83,0x04,0x83,0x0c,0x84, +0x05,0x83,0x14,0x83,0x13,0x84,0x0c,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x0d, +0x83,0x09,0x83,0x08,0x83,0x06,0x84,0x0b,0x83,0x10,0x83,0x05,0x86,0x05,0x83, +0x03,0x83,0x08,0x84,0x01,0x83,0x03,0x84,0x0c,0x84,0x05,0x83,0x12,0x84,0x0c, +0x84,0x05,0x83,0x0b,0x83,0x05,0x83,0x0c,0x83,0x0d,0x83,0x0c,0x83,0x0d,0x83, +0x09,0x84,0x01,0x84,0x0b,0x82,0x01,0x82,0x05,0x84,0x0b,0x84,0x03,0x84,0x10, +0x83,0x10,0x84,0x17,0x83,0x15,0x83,0x12,0x83,0x54,0x84,0x08,0x83,0x08,0x83, +0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,0x83,0x1a,0x83,0x0e,0x83,0x0a, +0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,0x03,0x84,0x12,0x83, +0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x07,0x83,0x0b,0x83,0x07, +0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,0x1e,0x84,0x0b,0x83,0x11,0x83, +0x09,0x83,0x0c,0x83,0x02,0x83,0x0a,0x83,0x01,0x82,0x03,0x83,0x01,0x83,0x0d, +0x87,0x0f,0x83,0x02,0x83,0x0f,0x83,0x16,0x83,0x14,0x83,0x15,0x83,0x17,0x85, +0x65,0x82,0x05,0x83,0x0a,0x83,0x0a,0x83,0x0c,0x82,0x03,0x83,0x04,0x83,0x02, +0x83,0x08,0x88,0x23,0x83,0x15,0x83,0x2a,0x83,0x58,0x82,0x10,0x83,0x0a,0x83, +0x0f,0x83,0x0f,0x84,0x10,0x83,0x0a,0x83,0x06,0x92,0x07,0x83,0x09,0x83,0x07, +0x84,0x09,0x83,0x0c,0x83,0x10,0x83,0x0a,0x83,0x12,0x84,0x39,0x87,0x27,0x88, +0x1f,0x82,0x04,0x82,0x05,0x82,0x04,0x82,0x05,0x83,0x0a,0x84,0x06,0x83,0x0c, +0x83,0x05,0x83,0x0c,0x83,0x04,0x83,0x0c,0x83,0x06,0x83,0x14,0x83,0x14,0x83, +0x0c,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x0d,0x83,0x09,0x83,0x08,0x83,0x07, +0x84,0x0a,0x83,0x10,0x83,0x06,0x85,0x05,0x83,0x03,0x83,0x09,0x87,0x04,0x83, +0x0c,0x83,0x06,0x83,0x13,0x83,0x0c,0x83,0x06,0x83,0x0b,0x83,0x05,0x83,0x0c, +0x83,0x0d,0x83,0x0c,0x83,0x0c,0x84,0x0a,0x83,0x01,0x83,0x0c,0x85,0x05,0x84, +0x0a,0x84,0x05,0x84,0x0f,0x83,0x10,0x83,0x18,0x83,0x15,0x83,0x12,0x83,0x54, +0x83,0x09,0x83,0x08,0x83,0x0a,0x83,0x07,0x83,0x09,0x83,0x07,0x83,0x0a,0x83, +0x07,0x83,0x1a,0x83,0x0e,0x83,0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17, +0x83,0x0c,0x83,0x04,0x84,0x11,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83, +0x09,0x83,0x07,0x84,0x09,0x84,0x07,0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09, +0x83,0x1f,0x83,0x0b,0x83,0x11,0x83,0x09,0x83,0x0d,0x83,0x01,0x83,0x0a,0x86, +0x03,0x83,0x01,0x82,0x0d,0x83,0x02,0x83,0x0f,0x84,0x01,0x83,0x0e,0x84,0x16, +0x83,0x14,0x83,0x15,0x83,0x7f,0x02,0x82,0x05,0x82,0x0b,0x83,0x0a,0x83,0x0b, +0x82,0x04,0x83,0x04,0x83,0x02,0x83,0x09,0x86,0x24,0x83,0x15,0x83,0x2a,0x83, +0x57,0x83,0x11,0x83,0x08,0x83,0x10,0x83,0x0f,0x83,0x11,0x84,0x08,0x84,0x11, +0x83,0x0b,0x83,0x08,0x84,0x08,0x83,0x08,0x84,0x0c,0x83,0x10,0x84,0x08,0x84, +0x12,0x83,0x3c,0x88,0x22,0x88,0x21,0x82,0x04,0x83,0x03,0x83,0x04,0x82,0x05, +0x83,0x0b,0x83,0x06,0x83,0x0c,0x83,0x05,0x84,0x0a,0x83,0x05,0x83,0x0b,0x84, +0x06,0x83,0x14,0x83,0x14,0x84,0x0b,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x0d, +0x83,0x08,0x84,0x08,0x83,0x07,0x85,0x09,0x83,0x10,0x83,0x07,0x83,0x06,0x83, +0x03,0x83,0x09,0x87,0x04,0x84,0x0a,0x84,0x06,0x83,0x13,0x84,0x0a,0x84,0x06, +0x83,0x0b,0x83,0x05,0x84,0x0b,0x83,0x0d,0x83,0x0d,0x83,0x0b,0x83,0x0b,0x83, +0x01,0x83,0x0c,0x85,0x05,0x84,0x0a,0x84,0x05,0x84,0x0f,0x83,0x0f,0x84,0x18, +0x83,0x15,0x84,0x11,0x83,0x54,0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x09,0x83, +0x08,0x83,0x07,0x84,0x09,0x83,0x08,0x83,0x19,0x83,0x0e,0x84,0x09,0x83,0x08, +0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,0x04,0x84,0x11,0x83,0x0a,0x83, +0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x08,0x83,0x09, +0x83,0x07,0x84,0x09,0x83,0x09,0x83,0x13,0x83,0x09,0x83,0x0b,0x83,0x11,0x83, +0x09,0x83,0x0d,0x86,0x0c,0x85,0x03,0x83,0x01,0x82,0x0c,0x84,0x02,0x84,0x0f, +0x87,0x0d,0x84,0x17,0x83,0x14,0x83,0x15,0x83,0x7f,0x01,0x83,0x05,0x82,0x0b, +0x83,0x0a,0x83,0x0a,0x83,0x04,0x83,0x04,0x83,0x02,0x84,0x09,0x84,0x26,0x83, +0x13,0x83,0x2b,0x83,0x57,0x83,0x11,0x83,0x08,0x83,0x10,0x83,0x0e,0x83,0x13, +0x83,0x08,0x83,0x12,0x83,0x0b,0x84,0x07,0x83,0x09,0x84,0x07,0x83,0x0d,0x83, +0x11,0x83,0x08,0x83,0x12,0x84,0x25,0x83,0x16,0x86,0x21,0x86,0x25,0x82,0x03, +0x83,0x02,0x85,0x02,0x82,0x05,0x84,0x0b,0x83,0x06,0x83,0x0b,0x84,0x06,0x83, +0x09,0x84,0x05,0x83,0x0a,0x84,0x07,0x83,0x14,0x83,0x15,0x84,0x09,0x84,0x04, +0x83,0x0d,0x83,0x0c,0x83,0x0d,0x84,0x07,0x83,0x09,0x83,0x08,0x85,0x08,0x83, +0x10,0x83,0x07,0x83,0x06,0x83,0x03,0x83,0x0a,0x86,0x05,0x83,0x09,0x84,0x07, +0x83,0x14,0x83,0x09,0x84,0x07,0x83,0x0b,0x83,0x06,0x84,0x09,0x84,0x0d,0x83, +0x0d,0x84,0x09,0x84,0x0b,0x87,0x0c,0x84,0x07,0x83,0x09,0x84,0x07,0x84,0x0e, +0x83,0x0e,0x84,0x19,0x83,0x16,0x83,0x11,0x83,0x54,0x83,0x08,0x84,0x08,0x84, +0x08,0x83,0x09,0x83,0x07,0x84,0x08,0x83,0x08,0x84,0x08,0x83,0x0a,0x81,0x0e, +0x83,0x0f,0x83,0x08,0x84,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83, +0x05,0x84,0x10,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x08, +0x84,0x07,0x84,0x08,0x84,0x07,0x84,0x08,0x84,0x07,0x84,0x09,0x83,0x13,0x84, +0x08,0x83,0x0b,0x83,0x11,0x84,0x07,0x84,0x0d,0x86,0x0c,0x84,0x05,0x85,0x0c, +0x83,0x04,0x83,0x0f,0x86,0x0d,0x84,0x18,0x83,0x14,0x83,0x15,0x83,0x7f,0x01, +0x83,0x04,0x83,0x0c,0x83,0x08,0x84,0x0a,0x82,0x05,0x83,0x04,0x83,0x03,0x84, +0x06,0x87,0x25,0x83,0x13,0x83,0x2b,0x83,0x57,0x82,0x12,0x85,0x04,0x85,0x10, +0x83,0x0d,0x83,0x14,0x85,0x04,0x85,0x12,0x83,0x0c,0x84,0x05,0x84,0x0a,0x84, +0x04,0x85,0x0d,0x83,0x11,0x85,0x04,0x85,0x09,0x81,0x07,0x84,0x26,0x83,0x18, +0x84,0x21,0x84,0x18,0x83,0x0c,0x82,0x04,0x85,0x01,0x86,0x06,0x83,0x0c,0x84, +0x05,0x83,0x0a,0x84,0x07,0x85,0x06,0x84,0x06,0x83,0x08,0x85,0x08,0x83,0x14, +0x83,0x15,0x85,0x07,0x85,0x04,0x83,0x0d,0x83,0x0c,0x83,0x0e,0x84,0x05,0x84, +0x09,0x83,0x09,0x84,0x08,0x83,0x10,0x83,0x10,0x83,0x03,0x83,0x0b,0x85,0x05, +0x85,0x06,0x85,0x07,0x83,0x14,0x85,0x06,0x85,0x07,0x83,0x0b,0x83,0x06,0x85, +0x06,0x85,0x0e,0x83,0x0e,0x85,0x05,0x85,0x0d,0x85,0x0d,0x84,0x07,0x83,0x08, +0x84,0x09,0x84,0x0d,0x83,0x0d,0x84,0x1a,0x83,0x16,0x83,0x11,0x83,0x54,0x84, +0x05,0x86,0x08,0x85,0x05,0x85,0x0a,0x84,0x04,0x84,0x09,0x85,0x05,0x85,0x09, +0x84,0x06,0x83,0x0e,0x83,0x0f,0x85,0x05,0x85,0x08,0x83,0x09,0x83,0x0e,0x83, +0x17,0x83,0x0c,0x83,0x06,0x84,0x0f,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05, +0x83,0x09,0x83,0x09,0x84,0x05,0x84,0x09,0x85,0x05,0x85,0x08,0x85,0x05,0x85, +0x09,0x83,0x14,0x84,0x05,0x85,0x0b,0x84,0x05,0x83,0x09,0x84,0x05,0x85,0x0e, +0x85,0x0c,0x84,0x05,0x85,0x0b,0x84,0x04,0x84,0x0e,0x86,0x0d,0x83,0x19,0x83, +0x14,0x83,0x15,0x83,0x57,0x83,0x26,0x83,0x04,0x83,0x0c,0x85,0x05,0x84,0x13, +0x83,0x02,0x83,0x05,0x91,0x24,0x83,0x13,0x83,0x40,0x83,0x2c,0x83,0x12,0x83, +0x13,0x8c,0x11,0x83,0x0c,0x90,0x09,0x8c,0x13,0x83,0x0c,0x8c,0x0b,0x8c,0x0e, +0x83,0x12,0x8c,0x0a,0x8c,0x0f,0x83,0x14,0x83,0x1a,0x82,0x21,0x82,0x1a,0x83, +0x0c,0x83,0x04,0x83,0x03,0x84,0x07,0x83,0x0d,0x83,0x05,0x90,0x09,0x8e,0x06, +0x8f,0x09,0x90,0x07,0x83,0x16,0x8f,0x05,0x83,0x0d,0x83,0x0c,0x83,0x0e,0x8c, +0x0a,0x83,0x0a,0x84,0x07,0x90,0x03,0x83,0x10,0x83,0x03,0x83,0x0b,0x85,0x06, +0x8e,0x08,0x83,0x15,0x90,0x06,0x83,0x0b,0x83,0x07,0x8e,0x0f,0x83,0x0e,0x8e, +0x0e,0x85,0x0e,0x83,0x07,0x83,0x08,0x84,0x09,0x84,0x0d,0x83,0x0d,0x92,0x0c, +0x83,0x16,0x84,0x10,0x83,0x55,0x8a,0x01,0x83,0x08,0x8e,0x0b,0x8b,0x0b,0x8e, +0x09,0x8e,0x0d,0x83,0x10,0x8e,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c, +0x83,0x06,0x84,0x0f,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83, +0x09,0x8d,0x09,0x8e,0x0a,0x8e,0x09,0x83,0x14,0x8d,0x0c,0x8c,0x09,0x8e,0x0e, +0x84,0x0e,0x83,0x05,0x84,0x0b,0x84,0x06,0x84,0x0e,0x85,0x0c,0x8f,0x0e,0x83, +0x14,0x83,0x15,0x83,0x57,0x83,0x26,0x82,0x05,0x83,0x0d,0x8c,0x15,0x86,0x07, +0x8b,0x02,0x84,0x23,0x83,0x13,0x83,0x40,0x83,0x2c,0x83,0x12,0x82,0x15,0x8a, +0x12,0x83,0x0c,0x90,0x0a,0x8a,0x14,0x83,0x0d,0x8a,0x0e,0x89,0x0f,0x83,0x13, +0x8a,0x0b,0x8a,0x11,0x83,0x14,0x83,0x59,0x83,0x0d,0x82,0x14,0x84,0x0d,0x83, +0x05,0x8f,0x0b,0x8b,0x08,0x8e,0x0a,0x90,0x07,0x83,0x18,0x8b,0x07,0x83,0x0d, +0x83,0x09,0x89,0x0c,0x8a,0x0b,0x83,0x0b,0x84,0x06,0x90,0x03,0x83,0x10,0x83, +0x03,0x83,0x0c,0x84,0x07,0x8b,0x0a,0x83,0x17,0x8a,0x01,0x84,0x05,0x83,0x0b, +0x83,0x09,0x8b,0x10,0x83,0x10,0x8b,0x0f,0x84,0x0f,0x82,0x09,0x82,0x07,0x84, +0x0b,0x84,0x0c,0x83,0x0d,0x92,0x0c,0x83,0x17,0x83,0x10,0x83,0x56,0x88,0x02, +0x83,0x08,0x83,0x01,0x89,0x0d,0x89,0x0d,0x89,0x01,0x83,0x0b,0x8a,0x0f,0x83, +0x11,0x89,0x01,0x83,0x08,0x83,0x09,0x83,0x0a,0x8b,0x13,0x83,0x0c,0x83,0x07, +0x84,0x0a,0x8b,0x06,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x0b,0x89, +0x0b,0x83,0x01,0x89,0x0c,0x89,0x01,0x83,0x09,0x83,0x16,0x8a,0x0e,0x8a,0x0b, +0x89,0x01,0x83,0x0e,0x84,0x0e,0x83,0x06,0x83,0x0b,0x84,0x06,0x84,0x0e,0x84, +0x0d,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x25,0x83,0x05,0x82,0x0f, +0x8a,0x17,0x84,0x0a,0x87,0x04,0x84,0x24,0x83,0x11,0x83,0x41,0x83,0x2c,0x83, +0x11,0x83,0x17,0x86,0x14,0x83,0x0c,0x90,0x0c,0x86,0x16,0x83,0x0f,0x86,0x11, +0x86,0x11,0x83,0x15,0x86,0x0e,0x87,0x13,0x83,0x14,0x82,0x5a,0x83,0x0d,0x83, +0x13,0x83,0x0e,0x84,0x04,0x8d,0x0f,0x87,0x0a,0x8c,0x0c,0x90,0x07,0x83,0x1a, +0x87,0x09,0x83,0x0d,0x83,0x09,0x89,0x0e,0x86,0x0d,0x83,0x0b,0x85,0x05,0x90, +0x03,0x83,0x10,0x83,0x03,0x83,0x0d,0x83,0x0a,0x86,0x0c,0x83,0x19,0x86,0x04, +0x84,0x04,0x83,0x0c,0x83,0x0a,0x87,0x12,0x83,0x12,0x87,0x12,0x83,0x10,0x81, +0x09,0x81,0x07,0x85,0x0b,0x85,0x0b,0x83,0x0d,0x92,0x0c,0x83,0x17,0x84,0x0f, +0x83,0x57,0x86,0x03,0x83,0x08,0x82,0x04,0x85,0x11,0x86,0x10,0x85,0x04,0x82, +0x0d,0x86,0x11,0x83,0x13,0x85,0x03,0x83,0x08,0x83,0x09,0x83,0x0a,0x8b,0x13, +0x83,0x0c,0x83,0x08,0x84,0x09,0x8b,0x06,0x83,0x06,0x83,0x07,0x83,0x05,0x83, +0x09,0x83,0x0c,0x87,0x0c,0x83,0x03,0x85,0x10,0x85,0x03,0x83,0x09,0x83,0x17, +0x87,0x11,0x87,0x0f,0x85,0x03,0x83,0x0f,0x83,0x0e,0x82,0x07,0x83,0x0a,0x84, +0x08,0x84,0x0d,0x84,0x0d,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,0x7f,0x1c,0x85, +0x5a,0x83,0x11,0x83,0x41,0x83,0x40,0x83,0x7f,0x7f,0x16,0x83,0x6b,0x83,0x7f, +0x7f,0x7f,0x17,0x83,0x7f,0x5b,0x83,0x18,0x83,0x0f,0x83,0x25,0x90,0x7f,0x36, +0x83,0x3f,0x83,0x7e,0x83,0x20,0x83,0x7f,0x30,0x83,0x2b,0x83,0x14,0x83,0x15, +0x83,0x7f,0x1d,0x83,0x5c,0x83,0x0f,0x83,0x42,0x82,0x41,0x82,0x7f,0x7f,0x17, +0x82,0x6d,0x84,0x06,0x81,0x7f,0x7f,0x7f,0x0f,0x81,0x7f,0x5c,0x83,0x18,0x83, +0x0f,0x83,0x25,0x90,0x7f,0x36,0x83,0x3f,0x83,0x7e,0x83,0x20,0x83,0x7f,0x30, +0x83,0x2b,0x84,0x13,0x83,0x14,0x83,0x7f,0x1e,0x83,0x5c,0x83,0x0f,0x83,0x41, +0x83,0x7f,0x7f,0x5a,0x82,0x6e,0x8b,0x7f,0x7f,0x7f,0x7f,0x6b,0x83,0x2a,0x83, +0x25,0x90,0x7f,0x36,0x83,0x3f,0x83,0x7e,0x83,0x20,0x83,0x7f,0x2f,0x84,0x2c, +0x83,0x13,0x83,0x14,0x83,0x7f,0x1e,0x83,0x5d,0x83,0x0d,0x83,0x42,0x82,0x7f, +0x7f,0x7f,0x4e,0x86,0x7f,0x7f,0x7f,0x7f,0x6e,0x87,0x23,0x86,0x7f,0x6a,0x83, +0x3f,0x84,0x7e,0x83,0x20,0x83,0x7f,0x2f,0x83,0x2d,0x84,0x12,0x83,0x13,0x84, +0x7f,0x7f,0x82,0x0d,0x82,0x43,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x43,0x87,0x23,0x86,0x7f,0x60,0x82,0x07,0x84,0x3e,0x84,0x7f,0x83,0x20,0x83, +0x7f,0x2e,0x84,0x2e,0x84,0x11,0x83,0x11,0x85,0x7f,0x7f,0x01,0x83,0x0b,0x83, +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x09,0x87,0x23,0x86,0x7f,0x60, +0x8c,0x3b,0x88,0x7f,0x83,0x20,0x83,0x7f,0x2b,0x86,0x31,0x82,0x11,0x83,0x12, +0x82,0x7f,0x7f,0x04,0x83,0x09,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x1b,0x8b,0x3c,0x87,0x7f,0x01,0x83,0x20,0x83,0x7f,0x2b,0x86, +0x7f,0x7f,0x60,0x81,0x0b,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x1e,0x87,0x3e,0x85,0x7f,0x03,0x83,0x20,0x83,0x7f,0x2b,0x84,0x7f, +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x4a, +0x00, + } +}; diff --git a/minzip/Android.mk b/minzip/Android.mk index 045f3557..f1026115 100644 --- a/minzip/Android.mk +++ b/minzip/Android.mk @@ -10,9 +10,11 @@ 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 diff --git a/minzip/Zip.c b/minzip/Zip.c index 70aff00c..c4808288 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> @@ -505,6 +506,69 @@ 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; + size_t total = 0; + + 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) @@ -625,6 +689,26 @@ bool mzProcessZipEntryContents(const ZipArchive *pArchive, return ret; } +/* + * 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; +} + static bool crcProcessFunction(const unsigned char *data, int dataLen, void *crc) { diff --git a/minzip/Zip.h b/minzip/Zip.h index 2054b38a..a5052cc4 100644 --- a/minzip/Zip.h +++ b/minzip/Zip.h @@ -158,6 +158,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/mounts.c b/mtdutils/mounts.c index c90fc8ac..149f4773 100644 --- a/mtdutils/mounts.c +++ b/mtdutils/mounts.c @@ -113,10 +113,10 @@ scan_mounted_volumes() */ bufp = buf; while (nbytes > 0) { - char device[64]; - char mount_point[64]; + char device[PATH_MAX]; + char mount_point[PATH_MAX]; char filesystem[64]; - char flags[128]; + char flags[256]; int matches; /* %as is a gnu extension that malloc()s a string for each field. @@ -214,6 +214,21 @@ unmount_mounted_volume(const MountedVolume *volume) } int +unmount_mounted_volume_detach(const MountedVolume *volume) +{ + /* Intentionally pass NULL to umount if the caller tries + * to unmount a volume they already unmounted using this + * function. + */ + int ret = umount2(volume->mount_point, MNT_DETACH); + if (ret == 0) { + free_volume_internals(volume, 1); + return 0; + } + return ret; +} + +int remount_read_only(const MountedVolume* volume) { return mount(volume->device, volume->mount_point, volume->filesystem, diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h index d721355b..c8318c0f 100644 --- a/mtdutils/mounts.h +++ b/mtdutils/mounts.h @@ -31,6 +31,7 @@ const MountedVolume * find_mounted_volume_by_mount_point(const char *mount_point); int unmount_mounted_volume(const MountedVolume *volume); +int unmount_mounted_volume_detach(const MountedVolume *volume); int remount_read_only(const MountedVolume* volume); diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c index d04b26ef..98e7aaf3 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; + } } } } @@ -313,8 +319,8 @@ static int read_block(const MtdPartition *partition, int fd, char *data) memcpy(&before, &after, sizeof(struct mtd_ecc_stats)); } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) { fprintf(stderr, - "mtd: MEMGETBADBLOCK returned %d at 0x%08llx (errno=%d)\n", - mgbb, pos, errno); + "mtd: MEMGETBADBLOCK returned %d at 0x%08llx: %s\n", + mgbb, pos, strerror(errno)); } else { return 0; // Success! } @@ -419,8 +425,8 @@ static int write_block(MtdWriteContext *ctx, const char *data) if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) { add_bad_block_offset(ctx, pos); fprintf(stderr, - "mtd: not writing bad block at 0x%08lx (ret %d errno %d)\n", - pos, ret, errno); + "mtd: not writing bad block at 0x%08lx (ret %d): %s\n", + pos, ret, strerror(errno)); pos += partition->erase_size; continue; // Don't try to erase known factory-bad blocks. } diff --git a/recovery.cpp b/recovery.cpp index 575e287a..b75cb2f4 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -42,26 +42,66 @@ #include "ui.h" #include "screen_ui.h" #include "device.h" + +#include "voldclient/voldclient.h" + #include "adb_install.h" extern "C" { #include "minadbd/adb.h" #include "fuse_sideload.h" #include "fuse_sdcard_provider.h" +#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, 's' }, { "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' }, { "just_exit", no_argument, NULL, 'x' }, { "locale", required_argument, NULL, 'l' }, { "stages", required_argument, NULL, 'g' }, { "shutdown_after", no_argument, NULL, 'p' }, { "reason", required_argument, NULL, 'r' }, + { "sideload", no_argument, NULL, 'a' }, { NULL, 0, NULL, 0 }, }; @@ -74,7 +114,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"; @@ -91,6 +130,8 @@ char recovery_version[PROPERTY_VALUE_MAX+1]; char* stage = NULL; char* reason = NULL; +#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 @@ -104,6 +145,7 @@ char* reason = NULL; * --wipe_cache - wipe cache (but not user data), then reboot * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs * --just_exit - do nothing; exit and reboot + * --sideload - enter sideload mode * * After completing, we remove /cache/recovery/command and reboot. * Arguments may also be supplied in the bootloader control block (BCB). @@ -211,6 +253,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"); @@ -414,15 +464,15 @@ typedef struct _saved_log_file { } saved_log_file; static int -erase_volume(const char *volume) { +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 // "/cache/recovery/last*" files into memory, so we can restore // them after the reformat. @@ -468,10 +518,12 @@ 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) { @@ -507,7 +559,7 @@ prepend_title(const char* const* headers) { const char** new_headers = (const char**)malloc((count+1) * sizeof(char*)); const char** h = new_headers; - *(h++) = "Android system recovery <" EXPAND(RECOVERY_API_VERSION) "e>"; + *(h++) = "CyanogenMod Simple recovery <" EXPAND(RECOVERY_API_VERSION) "e>"; *(h++) = recovery_version; *(h++) = ""; for (p = headers; *p; ++p, ++h) *h = *p; @@ -516,18 +568,23 @@ prepend_title(const char* const* headers) { return new_headers; } -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 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::kRefresh) { int key = ui->WaitKey(); int visible = ui->IsTextVisible(); @@ -539,10 +596,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) { + if ((action & ~KEY_FLAG_ABS) >= 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 = action & ~KEY_FLAG_ABS; + action = Device::kInvokeItem; + selected = ui->SelectMenu(selected, true); + usleep(50*1000); + } + } + if (action < 0) { switch (action) { case Device::kHighlightUp: @@ -558,6 +634,12 @@ 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::kRefresh: + chosen_item = Device::kRefresh; + break; } } else if (!menu_only) { chosen_item = action; @@ -646,15 +728,16 @@ browse_directory(const char* path, Device* device) { 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 "../" + // item 0 is always "../" + 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); @@ -695,19 +778,12 @@ wipe_data(int confirm, Device* device) { const char* items[] = { " No", " No", - " No", - " No", - " No", - " No", - " No", " Yes -- delete all user data", // [7] " No", - " No", - " No", NULL }; int chosen_item = get_menu_selection(title_headers, items, 1, 0, device); - if (chosen_item != 7) { + if (chosen_item != 2) { return; } } @@ -728,6 +804,7 @@ static void file_to_ui(const char* fn) { char line[1024]; int ct = 0; int key = 0; + ui->SetBackground(RecoveryUI::VIEWING_LOG); redirect_stdio("/dev/null"); while(fgets(line, sizeof(line), fp) != NULL) { ui->Print("%s", line); @@ -736,7 +813,7 @@ static void file_to_ui(const char* fn) { // give the user time to glance at the entries key = ui->WaitKey(); - if (key == KEY_POWER) { + if (key == KEY_POWER || key == KEY_BACK) { break; } @@ -762,11 +839,13 @@ static void file_to_ui(const char* fn) { // If the user didn't abort, then give the user time to glance at // the end of the log, sorry, no rewind here - if (key != KEY_POWER) { + if (key != KEY_POWER && key != KEY_BACK) { ui->Print("\n--END-- (press any key)\n"); ui->WaitKey(); } + ui->SetBackground(RecoveryUI::NONE); + redirect_stdio(TEMPORARY_LOG_FILE); fclose(fp); } @@ -810,7 +889,7 @@ static void choose_recovery_file(Device* device) { while(1) { int chosen_item = get_menu_selection(title_headers, entries, 1, 0, device); - if (chosen_item == 0) break; + if (chosen_item == 0 || chosen_item == Device::kGoBack) break; file_to_ui(entries[chosen_item]); } @@ -819,6 +898,152 @@ static void choose_recovery_file(Device* device) { } } +static void +wipe_media(int confirm, Device* device) { + if (confirm) { + static const char** title_headers = NULL; + + if (title_headers == NULL) { + const char* headers[] = { "Confirm wipe of all user media?", + " THIS CAN NOT BE UNDONE.", + "", + NULL }; + title_headers = prepend_title((const char**)headers); + } + + const char* items[] = { " No", + " No", + " Yes -- delete all user media", // [7] + " No", + NULL }; + + int chosen_item = get_menu_selection(title_headers, items, 1, 0, device); + if (chosen_item != 2) { + return; + } + } + + ui->Print("\n-- Wiping media...\n"); + device->WipeMedia(); + erase_volume("media"); + ui->Print("Media wipe complete.\n"); +} + +static int enter_sideload_mode(int* wipe_cache, Device* device) { + + ensure_path_mounted(CACHE_ROOT); + start_sideload(ui, wipe_cache, TEMPORARY_INSTALL_FILE); + + static const char* headers[] = { "ADB Sideload", + "", + NULL + }; + + static const char* list[] = { "Cancel sideload", NULL }; + + int status = INSTALL_NONE; + int item = get_menu_selection(headers, list, 0, 0, device); + if (item != Device::kNoAction) { + stop_sideload(); + } + status = wait_sideload(); + + if (status >= 0 && status != INSTALL_NONE) { + if (status != INSTALL_SUCCESS) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + } else if (!ui->IsTextVisible()) { + return status; // reboot if logs aren't visible + } else { + ui->Print("\nInstall from ADB complete.\n"); + } + } + 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]; + storage_item* items = get_storage_items(); + + int item_sideload = 0; + menu_items[item_sideload] = strdup("Apply from ADB"); + + int n; + for (n = 0; items[n].label; ++n) { + menu_items[n+1] = (char*)malloc(256); + sprintf(menu_items[n+1], "Choose from %s", items[n].label); + } + menu_items[n+1] = NULL; + + int wipe_cache; + int status = INSTALL_ERROR; + + int chosen = get_menu_selection(headers, menu_items, 0, 0, device); + if (chosen == Device::kGoBack) { + status = INSTALL_NONE; + goto out; + } + if (chosen == item_sideload) { + status = enter_sideload_mode(&wipe_cache, device); + } + else { + storage_item* item = &items[chosen-1]; + status = ensure_volume_mounted(item->vol); + if (status == 0) { + char* path = browse_directory(item->path, device); + if (path != NULL) { + ui->Print("\n-- Install %s ...\n", path); + ui->ClearLog(); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + set_sdcard_update_bootloader_message(); + void* token = start_sdcard_fuse(path); + status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache, + TEMPORARY_INSTALL_FILE, false); + finish_sdcard_fuse(token); + if (status != INSTALL_SUCCESS) { + ui->DialogShowErrorLog("Install failed"); + } + } + else { + ui->Print("\n-- No package file selected.\n", path); + status = INSTALL_NONE; + } + } + else { + status = INSTALL_ERROR; + } + ensure_volume_unmounted(item->vol); + } + if (status == INSTALL_SUCCESS && wipe_cache) { + ui->Print("\n-- Wiping cache (at package request)...\n"); + ui->DialogShowInfo("Wiping cache ..."); + if (erase_volume("/cache")) { + ui->Print("Cache wipe failed.\n"); + } else { + ui->Print("Cache wipe complete.\n"); + } + ui->DialogDismiss(); + } + if (status >= 0 && status != INSTALL_NONE) { + if (status != INSTALL_SUCCESS) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + ui->DialogShowErrorLog("Install failed"); + } else if (ui->IsTextVisible()) { + ui->Print("\nInstallation complete.\n"); + } + } + +out: + free_storage_items(items); + + return status; +} + +int ui_root_menu = 0; + // Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION // means to take the default, which is to reboot or shutdown depending // on if the --shutdown_after flag was passed to recovery. @@ -828,12 +1053,12 @@ prompt_and_wait(Device* device, int status) { for (;;) { finish_recovery(NULL); + ui_root_menu = 1; 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); @@ -842,6 +1067,7 @@ prompt_and_wait(Device* device, int status) { ui->SetProgressType(RecoveryUI::EMPTY); int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device); + ui_root_menu = 0; // device-specific code may take some action here. It may // return one of the core actions handled in the switch @@ -849,89 +1075,50 @@ prompt_and_wait(Device* device, int status) { Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item); int wipe_cache = 0; - switch (chosen_action) { - case Device::NO_ACTION: - break; - case Device::REBOOT: - case Device::SHUTDOWN: - 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_CACHE: - ui->Print("\n-- Wiping cache...\n"); - erase_volume("/cache"); - ui->Print("Cache wipe complete.\n"); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; - - case Device::APPLY_EXT: { - ensure_path_mounted(SDCARD_ROOT); - char* path = browse_directory(SDCARD_ROOT, device); - if (path == NULL) { - ui->Print("\n-- No package file selected.\n", path); + for (;;) { + switch (chosen_action) { + case Device::NO_ACTION: break; - } - ui->Print("\n-- Install %s ...\n", path); - set_sdcard_update_bootloader_message(); - void* token = start_sdcard_fuse(path); + case Device::REBOOT: + case Device::SHUTDOWN: + case Device::REBOOT_BOOTLOADER: + return chosen_action; - int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache, - TEMPORARY_INSTALL_FILE, false); + case Device::WIPE_DATA: + wipe_data(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - finish_sdcard_fuse(token); - ensure_path_unmounted(SDCARD_ROOT); + case Device::WIPE_CACHE: + ui->Print("\n-- Wiping cache...\n"); + erase_volume("/cache"); + ui->Print("Cache wipe complete.\n"); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - if (status == INSTALL_SUCCESS && wipe_cache) { - ui->Print("\n-- Wiping cache (at package request)...\n"); - if (erase_volume("/cache")) { - ui->Print("Cache wipe failed.\n"); - } else { - ui->Print("Cache wipe complete.\n"); - } - } + case Device::WIPE_MEDIA: + wipe_media(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - if (status >= 0) { - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - } else if (!ui->IsTextVisible()) { + case Device::APPLY_UPDATE: + status = show_apply_update_menu(device); + if (status == INSTALL_SUCCESS && !ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from sdcard complete.\n"); } - } - break; - } - - case Device::APPLY_CACHE: - ui->Print("\nAPPLY_CACHE is deprecated.\n"); - break; - - case Device::READ_RECOVERY_LASTLOG: - choose_recovery_file(device); - break; + break; - case Device::APPLY_ADB_SIDELOAD: - status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE); - if (status >= 0) { - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - copy_logs(); - } else if (!ui->IsTextVisible()) { - return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from ADB complete.\n"); - } - } - break; + case Device::READ_RECOVERY_LASTLOG: + choose_recovery_file(device); + break; + } + if (status == Device::kRefresh) { + status = 0; + continue; + } + break; } } } @@ -960,6 +1147,39 @@ load_locale_from_cache() { } } +static void +setup_adbd() { + struct stat f; + const char *key_src = "/data/misc/adb/adb_keys"; + const char *key_dest = "/adb_keys"; + + // 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 @@ -978,12 +1198,49 @@ ui_print(const char* format, ...) { } } +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; + } +} + +static int handle_volume_hotswap(char* label, char* path) { + ui->NotifyVolumesChanged(); + return 0; +} + +static int handle_volume_state_changed(char* label, char* path, int state) { + LOGV("%s: %s\n", path, volume_state_to_string(state)); + + return 0; +} + +static struct vold_callbacks v_callbacks = { + .state_changed = handle_volume_state_changed, + .disk_added = handle_volume_hotswap, + .disk_removed = handle_volume_hotswap, +}; + int main(int argc, char **argv) { time_t start = time(NULL); - redirect_stdio(TEMPORARY_LOG_FILE); - // 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 @@ -996,16 +1253,52 @@ 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 busybox_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); + + redirect_stdio(TEMPORARY_LOG_FILE); + printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table(); + vold_client_start(&v_callbacks, 0); + vold_set_automount(1); ensure_path_mounted(LAST_LOG_FILE); rotate_last_logs(KEEP_LOG_COUNT); get_args(&argc, &argv); const char *send_intent = NULL; const char *update_package = NULL; - int wipe_data = 0, wipe_cache = 0, show_text = 0; + int wipe_data = 0, wipe_cache = 0, wipe_media = 0, show_text = 0, sideload = 0; + bool headless = false; bool just_exit = false; bool shutdown_after = false; @@ -1014,7 +1307,9 @@ main(int argc, char **argv) { switch (arg) { case 's': send_intent = optarg; break; case 'u': update_package = optarg; break; + case 'h': headless = true; break; case 'w': wipe_data = wipe_cache = 1; break; + case 'm': wipe_media = 1; break; case 'c': wipe_cache = 1; break; case 't': show_text = 1; break; case 'x': just_exit = true; break; @@ -1029,6 +1324,7 @@ main(int argc, char **argv) { } case 'p': shutdown_after = true; break; case 'r': reason = optarg; break; + case 'a': sideload = 1; break; case '?': LOGE("Invalid command argument\n"); continue; @@ -1057,6 +1353,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" } }; @@ -1097,6 +1396,17 @@ main(int argc, char **argv) { int status = INSTALL_SUCCESS; +#ifdef HAVE_OEMLOCK + if (oem_lock == OEM_LOCK_UNLOCK) { + if (device->WipeData()) status = INSTALL_ERROR; + if (erase_volume("/data", true)) status = INSTALL_ERROR; + if (wipe_cache && erase_volume("/cache", true)) status = INSTALL_ERROR; + 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, &wipe_cache, TEMPORARY_INSTALL_FILE, true); if (status == INSTALL_SUCCESS && wipe_cache) { @@ -1119,15 +1429,19 @@ main(int argc, char **argv) { } } else if (wipe_data) { if (device->WipeData()) status = INSTALL_ERROR; - if (erase_volume("/data")) status = INSTALL_ERROR; + if (erase_volume("/data", wipe_media)) status = INSTALL_ERROR; if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n"); } else if (wipe_cache) { if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n"); + } else if (wipe_media) { + if (erase_volume("media")) status = INSTALL_ERROR; + if (status != INSTALL_SUCCESS) ui->Print("Media wipe failed.\n"); + } else if (sideload) { + status = enter_sideload_mode(&wipe_cache, device); } else if (!just_exit) { status = INSTALL_NONE; // No command specified - ui->SetBackground(RecoveryUI::NO_COMMAND); } if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) { @@ -1135,7 +1449,16 @@ main(int argc, char **argv) { ui->SetBackground(RecoveryUI::ERROR); } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; - if (status != INSTALL_SUCCESS || ui->IsTextVisible()) { + if (headless) { + ui->ShowText(true); + ui->SetHeadlessMode(); + finish_recovery(NULL); + for (;;) { + pause(); + } + } + else if (status != INSTALL_SUCCESS || ui->IsTextVisible()) { + ui->ShowText(true); Device::BuiltinAction temp = prompt_and_wait(device, status); if (temp != Device::NO_ACTION) after = temp; } @@ -1143,6 +1466,13 @@ main(int argc, char **argv) { // Save logs and clean up before rebooting or shutting down. finish_recovery(send_intent); + vold_unmount_all(); + + sync(); + + write_file("/sys/class/leds/lcd-backlight/brightness", "0"); + gr_fb_blank(true); + switch (after) { case Device::SHUTDOWN: ui->Print("Shutting down...\n"); @@ -1150,8 +1480,13 @@ main(int argc, char **argv) { 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: diff --git a/recovery_cmds.h b/recovery_cmds.h new file mode 100644 index 00000000..c82e5712 --- /dev/null +++ b/recovery_cmds.h @@ -0,0 +1,81 @@ +/* + * 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 minizip_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 setprop_main(int argc, char **argv); +int getprop_main(int argc, char **argv); +int fsck_msdos_main(int argc, char **argv); +int newfs_msdos_main(int argc, char **argv); +int vdc_main(int argc, char **argv); +int pigz_main(int argc, char **argv); +int sdcard_main(int argc, char **argv); +int start_main(int argc, char **argv); +int stop_main(int argc, char **argv); +#ifdef USE_F2FS +int make_f2fs_main(int argc, char **argv); +int fsck_f2fs_main(int argc, char **argv); +int fibmap_main(int argc, char **argv); +#endif + +int busybox_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[] = { + { "minizip", minizip_main }, + { "make_ext4fs", make_ext4fs_main }, + { "reboot", reboot_main }, + { "poweroff", reboot_main }, + { "setprop", setprop_main }, + { "getprop", getprop_main }, + { "fsck_msdos", fsck_msdos_main }, + { "newfs_msdos", newfs_msdos_main }, + { "vdc", vdc_main }, + { "pigz", pigz_main }, + { "sdcard", sdcard_main }, + { "start", start_main }, + { "stop", stop_main }, +#ifdef USE_F2FS + { "mkfs.f2fs", make_f2fs_main }, + { "fsck.f2fs", fsck_f2fs_main }, + { "fibmap.f2fs", fibmap_main }, +#endif + { NULL, NULL }, +}; + +struct recovery_cmd get_command(char* command) { + int i; + + for (i = 0; recovery_cmds[i].name; i++) { + if (strcmp(command, recovery_cmds[i].name) == 0) + break; + } + + return recovery_cmds[i]; +} +#endif diff --git a/res-hdpi/images/font_log.png b/res-hdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..59b990ae --- /dev/null +++ b/res-hdpi/images/font_log.png diff --git a/res-hdpi/images/font_menu.png b/res-hdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..d1aad6da --- /dev/null +++ b/res-hdpi/images/font_menu.png diff --git a/res-hdpi/images/icon_header.png b/res-hdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..634489f7 --- /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 00000000..780836f3 --- /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 00000000..39939418 --- /dev/null +++ b/res-hdpi/images/icon_info.png diff --git a/res-mdpi/images/font_log.png b/res-mdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..bcbe242d --- /dev/null +++ b/res-mdpi/images/font_log.png diff --git a/res-mdpi/images/font_menu.png b/res-mdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..59b990ae --- /dev/null +++ b/res-mdpi/images/font_menu.png diff --git a/res-mdpi/images/icon_header.png b/res-mdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..4ddb812b --- /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 00000000..5d134cd7 --- /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 00000000..39939418 --- /dev/null +++ b/res-mdpi/images/icon_info.png diff --git a/res-xhdpi/images/font_log.png b/res-xhdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..d1aad6da --- /dev/null +++ b/res-xhdpi/images/font_log.png diff --git a/res-xhdpi/images/font_menu.png b/res-xhdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..6aead738 --- /dev/null +++ b/res-xhdpi/images/font_menu.png diff --git a/res-xhdpi/images/icon_header.png b/res-xhdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..3254528d --- /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 00000000..af283e23 --- /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 00000000..39939418 --- /dev/null +++ b/res-xhdpi/images/icon_info.png diff --git a/res-xxhdpi/images/font_log.png b/res-xxhdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..6aead738 --- /dev/null +++ b/res-xxhdpi/images/font_log.png diff --git a/res-xxhdpi/images/font_menu.png b/res-xxhdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..f3b54b38 --- /dev/null +++ b/res-xxhdpi/images/font_menu.png diff --git a/res-xxhdpi/images/icon_header.png b/res-xxhdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..4b68cbcc --- /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 00000000..09a82340 --- /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 00000000..39939418 --- /dev/null +++ b/res-xxhdpi/images/icon_info.png diff --git a/res-xxxhdpi/images/font_log.png b/res-xxxhdpi/images/font_log.png Binary files differnew file mode 100644 index 00000000..ec8d899b --- /dev/null +++ b/res-xxxhdpi/images/font_log.png diff --git a/res-xxxhdpi/images/font_menu.png b/res-xxxhdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..d7c326ad --- /dev/null +++ b/res-xxxhdpi/images/font_menu.png diff --git a/res-xxxhdpi/images/icon_header.png b/res-xxxhdpi/images/icon_header.png Binary files differnew file mode 100644 index 00000000..b4aebd7a --- /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 00000000..a715e3ff --- /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 00000000..39939418 --- /dev/null +++ b/res-xxxhdpi/images/icon_info.png diff --git a/restore.cpp b/restore.cpp new file mode 100644 index 00000000..b12b6f53 --- /dev/null +++ b/restore.cpp @@ -0,0 +1,305 @@ +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/vfs.h> + +#include <cutils/properties.h> + +#include <lib/libtar.h> +#include <zlib.h> + +#include "roots.h" + +#include "bu.h" + +using namespace android; + +static int verify_sod() +{ + const char* key; + char value[PROPERTY_VALUE_MAX]; + char sodbuf[PROP_LINE_LEN*10]; + size_t len; + + len = sizeof(sodbuf); + if (tar_extract_file_contents(tar, sodbuf, &len) != 0) { + logmsg("tar_verify_sod: failed to extract file\n"); + return -1; + } + + int partidx = 0; + + char val_hashname[PROPERTY_VALUE_MAX]; + memset(val_hashname, 0, sizeof(val_hashname)); + char val_product[PROPERTY_VALUE_MAX]; + memset(val_product, 0, sizeof(val_product)); + char* p = sodbuf; + char* q; + while ((q = strchr(p, '\n')) != NULL) { + char* key = p; + *q = '\0'; + logmsg("verify_sod: line=%s\n", p); + p = q+1; + char* val = strchr(key, '='); + if (val) { + *val = '\0'; + ++val; + if (strcmp(key, "hash.name") == 0) { + strncpy(val_hashname, val, sizeof(val_hashname)); + } + if (strcmp(key, "ro.product.device") == 0) { + strncpy(val_product, val, sizeof(val_product)); + } + if (strncmp(key, "fs.", 3) == 0) { + char* name = key+3; + char* attr = strchr(name, '.'); + if (attr) { + *attr = '\0'; + ++attr; + part_add(name); + struct partspec* part = part_find(name); + if (!strcmp(attr, "size")) { + part->size = strtoul(val, NULL, 0); + } + if (!strcmp(attr, "used")) { + part->used = strtoul(val, NULL, 0); + } + } + } + } + } + + if (!val_hashname[0]) { + logmsg("verify_sod: did not find hash.name\n"); + return -1; + } + hash_name = strdup(val_hashname); + + if (!val_product[0]) { + logmsg("verify_sod: did not find ro.product.device\n"); + return -1; + } + key = "ro.product.device"; + property_get(key, value, ""); + if (strcmp(val_product, value) != 0) { + logmsg("verify_sod: product does not match\n"); + return -1; + } + + return 0; +} + +static int verify_eod(size_t actual_hash_datalen, + SHA_CTX* actual_sha_ctx, MD5_CTX* actual_md5_ctx) +{ + int rc = -1; + char eodbuf[PROP_LINE_LEN*10]; + size_t len; + + len = sizeof(eodbuf); + if (tar_extract_file_contents(tar, eodbuf, &len) != 0) { + logmsg("verify_eod: failed to extract file\n"); + return -1; + } + + size_t reported_datalen = 0; + char reported_hash[HASH_MAX_STRING_LENGTH]; + memset(reported_hash, 0, sizeof(reported_hash)); + char* p = eodbuf; + char* q; + while ((q = strchr(p, '\n')) != NULL) { + char* key = p; + *q = '\0'; + logmsg("verify_eod: line=%s\n", p); + p = q+1; + char* val = strchr(key, '='); + if (val) { + *val = '\0'; + ++val; + if (strcmp(key, "hash.datalen") == 0) { + reported_datalen = strtoul(val, NULL, 0); + } + if (strcmp(key, "hash.value") == 0) { + memset(reported_hash, 0, sizeof(reported_hash)); + strncpy(reported_hash, val, sizeof(reported_hash)); + } + } + } + + unsigned char digest[HASH_MAX_LENGTH]; + char hexdigest[HASH_MAX_STRING_LENGTH]; + + int n; + if (hash_name != NULL && !strcasecmp(hash_name, "sha1")) { + SHA1_Final(digest, actual_sha_ctx); + for (n = 0; n < SHA_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + } + else { // default to md5 + MD5_Final(digest, actual_md5_ctx); + for (n = 0; n < MD5_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + } + + logmsg("verify_eod: expected=%d,%s\n", actual_hash_datalen, hexdigest); + + logmsg("verify_eod: reported=%d,%s\n", reported_datalen, reported_hash); + + if ((reported_datalen == actual_hash_datalen) && + (memcmp(hexdigest, reported_hash, strlen(hexdigest)) == 0)) { + rc = 0; + } + + return rc; +} + +static int do_restore_tree() +{ + int rc = 0; + ssize_t len; + const char* compress = "none"; + char buf[512]; + char rootpath[] = "/"; + + logmsg("do_restore_tree: enter\n"); + + len = recv(adb_ifd, buf, sizeof(buf), MSG_PEEK); + if (len < 0) { + logmsg("do_restore_tree: peek failed (%d:%s)\n", rc, strerror(errno)); + return -1; + } + if (len < 2) { + logmsg("do_restore_tree: peek returned %d\n", len); + return -1; + } + if (buf[0] == 0x1f && buf[1] == 0x8b) { + logmsg("do_restore_tree: is gzip\n"); + compress = "gzip"; + } + + create_tar(adb_ifd, compress, "r"); + + size_t save_hash_datalen; + SHA_CTX save_sha_ctx; + MD5_CTX save_md5_ctx; + + char cur_mount[PATH_MAX]; + cur_mount[0] = '\0'; + while (1) { + save_hash_datalen = hash_datalen; + memcpy(&save_sha_ctx, &sha_ctx, sizeof(SHA_CTX)); + memcpy(&save_md5_ctx, &md5_ctx, sizeof(MD5_CTX)); + rc = th_read(tar); + if (rc != 0) { + if (rc == 1) { // EOF + rc = 0; + } + break; + } + char* pathname = th_get_pathname(tar); + logmsg("do_restore_tree: extract %s\n", pathname); + if (pathname[0] == '/') { + const char* mntend = strchr(&pathname[1], '/'); + if (!mntend) { + mntend = pathname + strlen(pathname); + } + if (memcmp(cur_mount, pathname, mntend-pathname) != 0) { + // New mount + if (cur_mount[0]) { + logmsg("do_restore_tree: unmounting %s\n", cur_mount); + ensure_path_unmounted(cur_mount); + } + memcpy(cur_mount, pathname, mntend-pathname); + cur_mount[mntend-pathname] = '\0'; + + // XXX: Assume paths are not interspersed + logmsg("do_restore_tree: switching to %s\n", cur_mount); + rc = ensure_path_unmounted(cur_mount); + if (rc != 0) { + logmsg("do_restore_tree: cannot unmount %s\n", cur_mount); + break; + } + logmsg("do_restore_tree: formatting %s\n", cur_mount); + rc = format_volume(cur_mount); + if (rc != 0) { + logmsg("do_restore_tree: cannot format %s\n", cur_mount); + break; + } + rc = ensure_path_mounted(cur_mount, true); + if (rc != 0) { + logmsg("do_restore_tree: cannot mount %s\n", cur_mount); + break; + } + partspec* curpart = part_find(&cur_mount[1]); + part_set(curpart); + } + rc = tar_extract_file(tar, pathname); + if (rc != 0) { + logmsg("do_restore_tree: failed to restore %s", pathname); + } + } + else if (!strcmp(pathname, "SOD")) { + rc = verify_sod(); + logmsg("do_restore_tree: tar_verify_sod returned %d\n", rc); + } + else if (!strcmp(pathname, "EOD")) { + rc = verify_eod(save_hash_datalen, &save_sha_ctx, &save_md5_ctx); + logmsg("do_restore_tree: tar_verify_eod returned %d\n", rc); + } + else { + char mnt[PATH_MAX]; + snprintf(mnt, sizeof(mnt), "/%s", pathname); + fstab_rec* vol = volume_for_path(mnt); + if (vol != NULL && vol->fs_type != NULL) { + partspec* curpart = part_find(pathname); + part_set(curpart); + rc = tar_extract_file(tar, vol->blk_device); + } + else { + logmsg("do_restore_tree: cannot find volume for %s\n", mnt); + } + } + free(pathname); + if (rc != 0) { + logmsg("extract failed, rc=%d\n", rc); + break; + } + } + + if (cur_mount[0]) { + logmsg("do_restore_tree: unmounting %s\n", cur_mount); + ensure_path_unmounted(cur_mount); + } + + tar_close(tar); + + return rc; +} + +int do_restore(int argc, char **argv) +{ + int rc = 1; + int n; + + char buf[256]; + int len; + int written; + + rc = do_restore_tree(); + logmsg("do_restore: rc=%d\n", rc); + + free(hash_name); + hash_name = NULL; + + return rc; +} + @@ -23,6 +23,7 @@ #include <unistd.h> #include <ctype.h> #include <fcntl.h> +#include <dirent.h> #include <fs_mgr.h> #include "mtdutils/mtdutils.h" @@ -35,10 +36,88 @@ extern "C" { #include "cryptfs.h" } +#include "voldclient/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(fstab_rec *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 defaults\n", v->fs_type); + } +} + +int get_num_volumes() { + return fstab->num_entries; +} + +fstab_rec* get_device_volumes() { + return fstab->recs; +} + +static int is_datamedia; +int is_data_media() +{ + return is_datamedia; +} + +static int is_volume_primary_storage(fstab_rec* v) +{ + // Static mount point /sdcard is primary storage, except when it's + // declared as datamedia + if (strcmp(v->mount_point, "/sdcard") == 0) { + if (strcmp(v->fs_type, "datamedia") == 0) { + return 0; + } + return 1; + } + + // Detect static and dynamic mount points named sdcard* + const char* endp = NULL; + if (strncmp(v->mount_point, "/mnt/media_rw/sdcard", 20) == 0) { + endp = &v->mount_point[20]; + } + else if (fs_mgr_is_voldmanaged(v) && strncmp(v->label, "sdcard", 6) == 0) { + endp = &v->label[6]; + } + if (endp) { + // If the name ends with a non-zero value, it's not primary + if (isdigit(*endp) && atoi(endp) != 0) { + return 0; + } + return 1; + } + + return 0; +} + void load_volume_table() { int i; @@ -58,26 +137,175 @@ 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; + } + + is_datamedia = 1; + printf("recovery filesystem table\n"); printf("=========================\n"); for (i = 0; i < fstab->num_entries; ++i) { - Volume* v = &fstab->recs[i]; + fstab_rec* 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); + + if (is_volume_primary_storage(v)) { + is_datamedia = 0; + } } + + fclose(file); + printf("\n"); } -Volume* volume_for_path(const char* path) { - return fs_mgr_get_entry_for_mount_point(fstab, path); +storage_item* get_storage_items() { + storage_item* items = (storage_item*)calloc(MAX_NUM_MANAGED_VOLUMES+1, sizeof(storage_item)); + int i; + storage_item* item = items; + + if (is_data_media()) { + item->vol = volume_for_path("/data"); + item->label = strdup("internal storage"); + item->path = strdup("/data/media"); + ++item; + } + for (i = 0; i < get_num_volumes(); i++) { + fstab_rec* v = get_device_volumes() + i; + + // Internal storage was allocated above + if (strcmp(v->fs_type, "datamedia") == 0) + continue; + + if (strcmp(v->mount_point, "/external_sd") == 0 || + strncmp(v->mount_point, "/sdcard", 7) == 0) { + item->vol = v; + item->label = strdup(&v->mount_point[1]); + item->path = strdup(v->mount_point); + ++item; + } + else if (strncmp(v->mount_point, "/mnt/media_rw/sdcard", 20) == 0) { + item->vol = v; + item->label = strdup(&v->mount_point[14]); + item->path = strdup(v->mount_point); + ++item; + } + else if (fs_mgr_is_voldmanaged(v) && strncmp(v->label, "sdcard", 6) == 0) { + char* path = (char*)malloc(9+strlen(v->label)+1); + sprintf(path, "/storage/%s", v->label); + if (vold_is_volume_available(path)) { + item->vol = v; + item->label = strdup(v->label); + item->path = strdup(path); + ++item; + } + free(path); + } + } + + return items; +} + +void free_storage_items(storage_item* items) { + storage_item* item = items; + while (item->vol) { + free(item->label); + free(item->path); + ++item; + } + free(items); +} + +bool volume_is_mountable(fstab_rec *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(fstab_rec *v) +{ + return (v->flags & MS_RDONLY); +} + +bool volume_is_verity(fstab_rec *v) +{ + return fs_mgr_is_verified(v); +} + +fstab_rec* volume_for_path(const char* path) { + fstab_rec *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; + + fstab_rec *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; } -int ensure_path_mounted(const char* path) { - Volume* v = volume_for_path(path); +fstab_rec* volume_for_label(const char* label) { + int i; + for (i = 0; i < get_num_volumes(); i++) { + fstab_rec* v = get_device_volumes() + i; + if (v->label && !strcmp(v->label, label)) { + return v; + } + } + return NULL; +} + +int ensure_path_mounted(const char* path, bool force_rw) { + fstab_rec* 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_mounted(v, force_rw); +} + +int ensure_volume_mounted(fstab_rec* v, bool force_rw) { + if (v == NULL) { + LOGE("cannot mount unknown volume\n"); + return -1; + } if (strcmp(v->fs_type, "ramdisk") == 0) { // the ramdisk is always mounted. return 0; @@ -90,16 +318,24 @@ 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 (!fs_mgr_is_voldmanaged(v)) { + const MountedVolume* mv = + find_mounted_volume_by_mount_point(v->mount_point); + if (mv) { + // volume is already mounted + return 0; + } } - mkdir(v->mount_point, 0755); // in case it doesn't already exist + mkdir_p(v->mount_point, 0755); // in case it doesn't already exist - if (strcmp(v->fs_type, "yaffs2") == 0) { + if (fs_mgr_is_voldmanaged(v)) { + if (!strcmp(v->mount_point, "auto")) { + return vold_mount_auto_volume(v->label, 1); + } + return vold_mount_volume(v->mount_point, 1); + + } else if (strcmp(v->fs_type, "yaffs2") == 0) { // mount an MTD partition as a YAFFS2 filesystem. mtd_scan_partitions(); const MtdPartition* partition; @@ -111,9 +347,15 @@ int ensure_path_mounted(const char* path) { } return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0); } else if (strcmp(v->fs_type, "ext4") == 0 || + strcmp(v->fs_type, "f2fs") == 0 || strcmp(v->fs_type, "vfat") == 0) { - result = mount(v->blk_device, v->mount_point, v->fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); + unsigned long mntflags = MS_NOATIME | MS_NODEV | MS_NODIRATIME; + if (!force_rw) { + if ((v->flags & MS_RDONLY) || fs_mgr_is_verified(v)) { + mntflags |= MS_RDONLY; + } + } + result = mount(v->blk_device, v->mount_point, v->fs_type, mntflags, ""); if (result == 0) return 0; LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno)); @@ -124,12 +366,36 @@ int ensure_path_mounted(const char* path) { return -1; } -int ensure_path_unmounted(const char* path) { - Volume* v = volume_for_path(path); +int ensure_path_unmounted(const char* path, bool detach) { + fstab_rec* 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(fstab_rec* v, bool detach) { + 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; @@ -142,6 +408,13 @@ int ensure_path_unmounted(const char* path) { return -1; } + if (fs_mgr_is_voldmanaged(v)) { + if (!strcmp(v->mount_point, "auto")) { + return vold_unmount_auto_volume(v->label, 0, 1, detach); + } + return vold_unmount_volume(v->mount_point, 0, 1, detach); + } + const MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point); if (mv == NULL) { @@ -149,7 +422,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[]) { @@ -166,8 +446,61 @@ static int exec_cmd(const char* path, char* const argv[]) { return WEXITSTATUS(status); } -int format_volume(const char* volume) { - Volume* v = volume_for_path(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 (!is_data_media()) { + return 0; + } + if (ensure_path_mounted("/data") != 0) { + LOGE("format_volume failed to mount /data\n"); + return -1; + } + int rc = 0; + rc = rmtree_except("/data/media", NULL); + ensure_path_unmounted("/data"); + return rc; + } + + fstab_rec* v = volume_for_path(volume); if (v == NULL) { LOGE("unknown volume \"%s\"\n", volume); return -1; @@ -182,11 +515,52 @@ int format_volume(const char* volume) { return -1; } + if (strcmp(volume, "/data") == 0 && is_data_media() && !force) { + if (ensure_path_mounted("/data") == 0) { + // 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; + } + LOGE("format_volume failed to mount /data, formatting instead\n"); + } + if (ensure_path_unmounted(volume) != 0) { LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point); return -1; } + // Only use vold format for exact matches otherwise /sdcard will be + // formatted instead of /storage/sdcard0/.android_secure + if (fs_mgr_is_voldmanaged(v) && strcmp(volume, v->mount_point) == 0) { + if (ensure_path_unmounted(volume) != 0) { + LOGE("format_volume failed to unmount %s", v->mount_point); + } + return vold_format_volume(v->mount_point, 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); @@ -234,24 +608,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)); @@ -270,7 +644,7 @@ int setup_install_mounts() { return -1; } for (int i = 0; i < fstab->num_entries; ++i) { - Volume* v = fstab->recs + i; + fstab_rec* v = fstab->recs + i; if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) { @@ -280,7 +654,16 @@ 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 (is_data_media() && strcmp(v->mount_point, "/data") == 0) { + detach = true; + } + if (fs_mgr_is_voldmanaged(v)) { + detach = true; + } + if (ensure_volume_unmounted(v, detach) != 0) { LOGE("failed to unmount %s\n", v->mount_point); return -1; } @@ -18,6 +18,7 @@ #define RECOVERY_ROOTS_H_ #include "common.h" +#include <fs_mgr.h> #ifdef __cplusplus extern "C" { @@ -26,28 +27,49 @@ extern "C" { // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); -// Return the Volume* record for this path (or NULL). -Volume* volume_for_path(const char* path); +// Return the fstab_rec* record for this path (or NULL). +fstab_rec* 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(fstab_rec* v, bool force_rw=false); +int ensure_path_mounted(const char* path, bool force_rw=false); // Make sure that the volume 'path' is on is mounted. Returns 0 on // success (volume is unmounted); -int ensure_path_unmounted(const char* path); +int ensure_volume_unmounted(fstab_rec *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(); +char* get_android_secure_path(); +int get_num_volumes(); + +struct storage_item { + fstab_rec* vol; + char* label; + char* path; +}; + +int is_data_media(); +storage_item* get_storage_items(); +void free_storage_items(storage_item* items); + +bool volume_is_mountable(fstab_rec *v); +bool volume_is_readonly(fstab_rec *v); +bool volume_is_verity(fstab_rec *v); + #ifdef __cplusplus } #endif +#define MAX_NUM_MANAGED_VOLUMES 10 + #endif // RECOVERY_ROOTS_H_ diff --git a/screen_ui.cpp b/screen_ui.cpp index 03ef049a..e639e3a6 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -33,9 +33,7 @@ #include "minui/minui.h" #include "screen_ui.h" #include "ui.h" - -static int char_width; -static int char_height; +#include "cutils/properties.h" // There's only (at most) one of these objects, and global callbacks // (for pthread_create, and the input event system) need to find it, @@ -59,6 +57,8 @@ ScreenRecoveryUI::ScreenRecoveryUI() : progressScopeSize(0), progress(0), pagesIdentical(false), + log_text_cols(0), + log_text_rows(0), text_cols(0), text_rows(0), text_col(0), @@ -66,21 +66,30 @@ ScreenRecoveryUI::ScreenRecoveryUI() : text_top(0), show_text(false), show_text_ever(false), + dialog_icon(NONE), + dialog_text(NULL), + dialog_show_log(false), show_menu(false), - menu_top(0), menu_items(0), menu_sel(0), + menu_show_start(0), + max_menu_rows(0), animation_fps(20), installing_frames(-1), stage(-1), - max_stage(-1) { + max_stage(-1), + rainbow(false), + wrap_count(0) { - for (int i = 0; i < 5; i++) + headerIcon = NULL; + for (int i = 0; i < NR_ICONS; i++) backgroundIcon[i] = NULL; memset(text, 0, sizeof(text)); pthread_mutex_init(&updateMutex, NULL); + pthread_cond_init(&progressCondition, NULL); + self = this; } @@ -89,7 +98,7 @@ ScreenRecoveryUI::ScreenRecoveryUI() : void ScreenRecoveryUI::draw_background_locked(Icon icon) { pagesIdentical = false; - gr_color(0, 0, 0, 255); + SetColor(TEXT_FILL); gr_clear(); if (icon) { @@ -104,14 +113,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) { @@ -125,7 +136,9 @@ void ScreenRecoveryUI::draw_background_locked(Icon icon) } } - gr_color(255, 255, 255, 255); + LOGV("textX=%d textY=%d iconX=%d iconY=%d", textX, textY, iconX, iconY); + + SetColor(MENU); gr_texticon(textX, textY, text_surface); } } @@ -146,11 +159,14 @@ 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); + SetColor(TEXT_FILL); gr_fill(dx, dy, width, height); if (progressBarType == DETERMINATE) { @@ -160,18 +176,18 @@ void ScreenRecoveryUI::draw_progress_locked() if (rtl_locale) { // Fill the progress bar from right to left. if (pos > 0) { - gr_blit(progressBarFill, width-pos, 0, pos, height, dx+width-pos, dy); + gr_blend(progressBarFill, width-pos, 0, pos, height, dx+width-pos, dy); } if (pos < width-1) { - gr_blit(progressBarEmpty, 0, 0, width-pos, height, dx, dy); + gr_blend(progressBarEmpty, 0, 0, width-pos, height, dx, dy); } } else { // Fill the progress bar from left to right. if (pos > 0) { - gr_blit(progressBarFill, 0, 0, pos, height, dx, dy); + gr_blend(progressBarFill, 0, 0, pos, height, dx, dy); } if (pos < width-1) { - gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); + gr_blend(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); } } } @@ -181,20 +197,26 @@ void ScreenRecoveryUI::draw_progress_locked() void ScreenRecoveryUI::SetColor(UIElement e) { switch (e) { case HEADER: - gr_color(247, 0, 6, 255); + gr_color(111,111,111,255); break; - case MENU: - case MENU_SEL_BG: - gr_color(0, 106, 157, 255); + case TOP: + gr_color(208, 208, 208, 255); break; + case MENU: case MENU_SEL_FG: - gr_color(255, 255, 255, 255); + gr_color(0, 177, 229, 255); + break; + case MENU_SEL_BG: + gr_color(106, 103, 102, 255); break; case LOG: - gr_color(249, 194, 0, 255); + gr_color(76, 76, 76, 255); break; case TEXT_FILL: - gr_color(0, 0, 0, 160); + gr_color(0, 0, 0, 255); + break; + case ERROR_TEXT: + gr_color(255, 0, 0, 255); break; default: gr_color(255, 255, 255, 255); @@ -202,58 +224,140 @@ void ScreenRecoveryUI::SetColor(UIElement e) { } } +int ScreenRecoveryUI::draw_header_icon() +{ + gr_surface 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(20, (textrow+1)*char_height-1, text, 0); + SetColor(MENU); + } + else { + SetColor(MENU); + gr_text(20, (textrow+1)*char_height-1, text, 0); + } +} + +void ScreenRecoveryUI::draw_dialog() +{ + int x, y, w, h; + + if (dialog_icon == HEADLESS) { + return; + } + draw_header_icon(); + + 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); + + int row; + for (row = 0; row < log_text_rows; ++row) { + gr_text(0, y, text[row], 0); + y += cy+2; + } + gr_set_font("menu"); + } + + if (dialog_icon == 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; - int i = 0; - if (show_menu) { - SetColor(HEADER); - - for (; i < menu_top + menu_items; ++i) { - if (i == menu_top) SetColor(MENU); - - if (i == menu_top + menu_sel) { - // draw the highlight bar - SetColor(MENU_SEL_BG); - gr_fill(0, y-2, gr_fb_width(), y+char_height+2); - // white text of selected item - SetColor(MENU_SEL_FG); - if (menu[i][0]) gr_text(4, y, menu[i], 1); - SetColor(MENU); - } else { - if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top); - } - y += char_height+4; + if (DialogShowing()) { + draw_dialog(); + return; + } + + SetColor(MENU); + + if (show_text) { + + if (currentIcon != ERASING && currentIcon != INSTALLING_UPDATE) + draw_header_icon(); + + if (currentIcon == ERASING || currentIcon == INSTALLING_UPDATE || currentIcon == VIEWING_LOG) { + int 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. + int row = (text_first_row+log_text_rows-1) % log_text_rows; + for (int ty = gr_fb_height() - cy, count = 0; + ty > y+2 && count < log_text_rows; + ty -= (cy+2), ++count) { + gr_text(0, ty, text[row], 0); + --row; + if (row < 0) row = log_text_rows-1; } - SetColor(MENU); - y += 4; - gr_fill(0, y, gr_fb_width(), y+2); - y += 4; - ++i; + return; } - 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; - for (int ty = gr_fb_height() - char_height, count = 0; - ty > y+2 && count < text_rows; - ty -= char_height, ++count) { - gr_text(4, ty, text[row], 0); - --row; - if (row < 0) row = text_rows-1; + if (show_menu) { + gr_set_font("menu"); + + int nr_items = menu_items - menu_show_start; + if (nr_items > max_menu_rows) + nr_items = max_menu_rows; + for (int i = 0; i < nr_items; ++i) { + draw_menu_item(text_first_row + 3*i, menu[menu_show_start+i], + ((menu_show_start+i) == menu_sel)); + } } } } @@ -262,21 +366,9 @@ void ScreenRecoveryUI::draw_screen_locked() // 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. @@ -285,20 +377,31 @@ void* ScreenRecoveryUI::progress_thread(void *cookie) { return NULL; } +void ScreenRecoveryUI::ToggleRainbowMode() +{ + rainbow = rainbow ? false : true; + set_rainbow_mode(rainbow); + property_set("sys.rainbow.recovery", rainbow ? "1" : "0"); +} + void ScreenRecoveryUI::progress_loop() { double interval = 1.0 / animation_fps; for (;;) { - double start = now(); pthread_mutex_lock(&updateMutex); + if (progressBarType == EMPTY && !update_waiting) + pthread_cond_wait(&progressCondition, &updateMutex); + + bool redraw = false; + double start = now(); - int redraw = 0; + 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 @@ -309,13 +412,26 @@ void ScreenRecoveryUI::progress_loop() { 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); @@ -349,22 +465,40 @@ void ScreenRecoveryUI::Init() { gr_init(); + gr_set_font("log"); + gr_font_size(&log_char_width, &log_char_height); + gr_set_font("menu"); gr_font_size(&char_width, &char_height); + log_text_rows = gr_fb_height() / log_char_height; + log_text_cols = gr_fb_width() / log_char_width; + text_col = text_row = 0; text_rows = gr_fb_height() / char_height; + if (max_menu_rows > kMaxMenuRows) + max_menu_rows = kMaxMenuRows; if (text_rows > kMaxRows) text_rows = kMaxRows; text_top = 1; text_cols = gr_fb_width() / char_width; if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; + LoadBitmap("icon_header", &headerIcon); + header_height = gr_get_height(headerIcon); + header_width = gr_get_width(headerIcon); + + 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] = NULL; LoadBitmapArray("icon_installing", &installing_frames, &installation); backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL; backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; + LoadBitmap("icon_info", &backgroundIcon[INFO]); LoadBitmap("icon_error", &backgroundIcon[ERROR]); - backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + backgroundIcon[NO_COMMAND] = NULL; + LoadBitmap("icon_headless", &backgroundIcon[HEADLESS]); LoadBitmap("progress_empty", &progressBarEmpty); LoadBitmap("progress_fill", &progressBarFill); @@ -426,7 +560,8 @@ void ScreenRecoveryUI::SetProgressType(ProgressType type) progressScopeStart = 0; progressScopeSize = 0; progress = 0; - update_progress_locked(); + + update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -439,7 +574,8 @@ void ScreenRecoveryUI::ShowProgress(float portion, float seconds) progressScopeTime = now(); progressScopeDuration = seconds; progress = 0; - update_progress_locked(); + + update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -454,7 +590,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); @@ -479,14 +615,13 @@ void ScreenRecoveryUI::Print(const char *fmt, ...) // 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) { + if (log_text_rows > 0 && log_text_cols > 0) { + for (char* ptr = buf; *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; } @@ -496,39 +631,129 @@ void ScreenRecoveryUI::Print(const char *fmt, ...) pthread_mutex_unlock(&updateMutex); } +void ScreenRecoveryUI::ClearLog() +{ + memset(text, 0, sizeof(text)); + text_col = text_row = 0; +} + +void ScreenRecoveryUI::DialogShowInfo(const char* text) +{ + pthread_mutex_lock(&updateMutex); + free(dialog_text); + dialog_text = strdup(text); + dialog_show_log = false; + dialog_icon = 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 = 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 = 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::StartMenu(const char* const * headers, const char* const * items, int initial_selection) { - int i; + int i = 0; pthread_mutex_lock(&updateMutex); if (text_rows > 0 && text_cols > 0) { - for (i = 0; i < text_rows; ++i) { - if (headers[i] == NULL) break; - strncpy(menu[i], headers[i], text_cols-1); + for (; i < kMaxMenuRows; ++i) { + if (items[i] == NULL) break; + strncpy(menu[i], items[i], text_cols-1); menu[i][text_cols-1] = '\0'; } - menu_top = i; - for (; i < text_rows; ++i) { - if (items[i-menu_top] == NULL) break; - strncpy(menu[i], items[i-menu_top], text_cols-1); - menu[i][text_cols-1] = '\0'; - } - menu_items = i - menu_top; + menu_items = i; show_menu = 1; 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 old_sel; +int ScreenRecoveryUI::SelectMenu(int sel, bool abs) { + int wrapped = 0; pthread_mutex_lock(&updateMutex); + if (abs) { + sel += menu_show_start; + } if (show_menu > 0) { - old_sel = menu_sel; + int 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 (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) { + menu_show_start = menu_sel - max_menu_rows + 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; + ToggleRainbowMode(); + } + } if (menu_sel != old_sel) update_screen_locked(); } pthread_mutex_unlock(&updateMutex); @@ -536,11 +761,9 @@ int ScreenRecoveryUI::SelectMenu(int sel) { } void ScreenRecoveryUI::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); } diff --git a/screen_ui.h b/screen_ui.h index 01a33bfe..122fe1d3 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -48,16 +48,26 @@ class ScreenRecoveryUI : public RecoveryUI { // printing messages void Print(const char* fmt, ...); // __attribute__((format(printf, 1, 2))); + void ClearLog(); + 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 == ERROR); } + void DialogDismiss(); + void SetHeadlessMode(); // 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 Redraw(); - enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL }; + enum UIElement { HEADER, MENU, TOP, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL, ERROR_TEXT }; virtual void SetColor(UIElement e); private: @@ -67,8 +77,11 @@ class ScreenRecoveryUI : public RecoveryUI { bool rtl_locale; pthread_mutex_t updateMutex; - gr_surface backgroundIcon[5]; - gr_surface backgroundText[5]; + pthread_cond_t progressCondition; + + gr_surface headerIcon; + gr_surface backgroundIcon[NR_ICONS]; + gr_surface backgroundText[NR_ICONS]; gr_surface *installation; gr_surface progressBarEmpty; gr_surface progressBarFill; @@ -87,16 +100,28 @@ class ScreenRecoveryUI : public RecoveryUI { static const int kMaxCols = 96; static const int kMaxRows = 96; + static const int kMaxMenuCols = 96; + static const int kMaxMenuRows = 250; + // Log text overlay, displayed when a magic key is pressed char text[kMaxRows][kMaxCols]; + int log_text_cols, log_text_rows; int text_cols, text_rows; int text_col, text_row, text_top; bool show_text; bool show_text_ever; // has show_text ever been true? - char menu[kMaxRows][kMaxCols]; + Icon dialog_icon; + char *dialog_text; + bool dialog_show_log; + + char menu[kMaxMenuRows][kMaxMenuCols]; bool show_menu; - int menu_top, menu_items, menu_sel; + int menu_items, menu_sel; + int menu_show_start; + int max_menu_rows; + + int menu_item_start; pthread_t progress_t; @@ -109,17 +134,33 @@ class ScreenRecoveryUI : public RecoveryUI { int stage, max_stage; + int log_char_height, log_char_width; + int char_height, char_width; + + int header_height; + int header_width; + int text_first_row; + + bool update_waiting; + + bool rainbow; + int wrap_count; + 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_dialog(); void draw_screen_locked(); void update_screen_locked(); - void update_progress_locked(); static void* progress_thread(void* cookie); void progress_loop(); void LoadBitmap(const char* filename, gr_surface* surface); void LoadBitmapArray(const char* filename, int* frames, gr_surface** surface); void LoadLocalizedBitmap(const char* filename, gr_surface* surface); + + void ToggleRainbowMode(); }; #endif // RECOVERY_UI_H @@ -27,8 +27,10 @@ #include <sys/types.h> #include <time.h> #include <unistd.h> +#include <sys/epoll.h> #include <cutils/android_reboot.h> +#include <cutils/properties.h> #include "common.h" #include "roots.h" @@ -37,13 +39,195 @@ #include "screen_ui.h" #include "ui.h" +#include "voldclient/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 void show_event(int fd, struct input_event *ev) +{ +#ifdef DEBUG_EVENTS + char typebuf[40]; + char codebuf[40]; + const char *evtypestr = NULL; + const char *evcodestr = NULL; + + sprintf(typebuf, "0x%04x", ev->type); + evtypestr = typebuf; + + sprintf(codebuf, "0x%04x", ev->code); + evcodestr = codebuf; + + switch (ev->type) { + case EV_SYN: + evtypestr = "EV_SYN"; + switch (ev->code) { + case SYN_REPORT: + evcodestr = "SYN_REPORT"; + break; + case SYN_MT_REPORT: + evcodestr = "SYN_MT_REPORT"; + break; + } + break; + case EV_KEY: + evtypestr = "EV_KEY"; + switch (ev->code) { + case KEY_HOME: /* 102 */ + evcodestr = "KEY_HOME"; + break; + case KEY_POWER: /* 116 */ + evcodestr = "KEY_POWER"; + break; + case KEY_MENU: /* 139 */ + evcodestr = "KEY_MENU"; + break; + case KEY_BACK: /* 158 */ + evcodestr = "KEY_BACK"; + break; + case KEY_HOMEPAGE: /* 172 */ + evcodestr = "KEY_HOMEPAGE"; + break; + case KEY_SEARCH: /* 217 */ + evcodestr = "KEY_SEARCH"; + break; + case BTN_TOOL_FINGER: /* 0x145 */ + evcodestr = "BTN_TOOL_FINGER"; + break; + case BTN_TOUCH: /* 0x14a */ + evcodestr = "BTN_TOUCH"; + break; + } + break; + case EV_REL: + evtypestr = "EV_REL"; + switch (ev->code) { + case REL_X: + evcodestr = "REL_X"; + break; + case REL_Y: + evcodestr = "REL_Y"; + break; + case REL_Z: + evcodestr = "REL_Z"; + break; + } + break; + case EV_ABS: + evtypestr = "EV_ABS"; + switch (ev->code) { + case ABS_MT_TOUCH_MAJOR: + evcodestr = "ABS_MT_TOUCH_MAJOR"; + break; + case ABS_MT_TOUCH_MINOR: + evcodestr = "ABS_MT_TOUCH_MINOR"; + break; + case ABS_MT_WIDTH_MAJOR: + evcodestr = "ABS_MT_WIDTH_MAJOR"; + break; + case ABS_MT_WIDTH_MINOR: + evcodestr = "ABS_MT_WIDTH_MINOR"; + break; + case ABS_MT_ORIENTATION: + evcodestr = "ABS_MT_ORIENTATION"; + break; + case ABS_MT_POSITION_X: + evcodestr = "ABS_MT_POSITION_X"; + break; + case ABS_MT_POSITION_Y: + evcodestr = "ABS_MT_POSITION_Y"; + break; + case ABS_MT_TRACKING_ID: + evcodestr = "ABS_MT_TRACKING_ID"; + break; + case ABS_MT_PRESSURE: + evcodestr = "ABS_MT_PRESSURE"; + break; + case ABS_MT_ANGLE: + evcodestr = "ABS_MT_ANGLE"; + break; + } + break; + } + LOGI("show_event: fd=%d, type=%s, code=%s, val=%d\n", fd, evtypestr, evcodestr, ev->value); +#endif +} + // 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 RecoveryUI* self = NULL; +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'; + fields[n++] = s; + s = p+1; + } + fields[n] = s; + return n+1; +} + +static int message_socket_client_event(int fd, uint32_t epevents, void *data) +{ + MessageSocket* client = (MessageSocket*)data; + + if (!(epevents & EPOLLIN)) { + return 0; + } + + char buf[256]; + ssize_t nread; + nread = client->Read(buf, sizeof(buf)); + if (nread <= 0) { + ev_del_fd(fd); + self->DialogDismiss(); + client->Close(); + delete client; + return 0; + } + + // Parse the message. Right now we support: + // dialog show <string> + // dialog dismiss + char* fields[3]; + int nfields; + nfields = string_split(buf, fields, 3); + if (nfields < 2) + return 0; + if (strcmp(fields[0], "dialog") == 0) { + if (strcmp(fields[1], "show") == 0 && nfields > 2) { + self->DialogShowInfo(fields[2]); + } + if (strcmp(fields[1], "dismiss") == 0) { + self->DialogDismiss(); + } + } + + return 0; +} + +static int message_socket_listen_event(int fd, uint32_t epevents, void *data) +{ + MessageSocket* ms = (MessageSocket*)data; + MessageSocket* client = ms->Accept(); + if (client) { + ev_add_fd(client->fd(), message_socket_client_event, client); + } + return 0; +} + RecoveryUI::RecoveryUI() : key_queue_len(0), key_last_down(-1), @@ -55,12 +239,16 @@ RecoveryUI::RecoveryUI() : last_key(-1) { pthread_mutex_init(&key_queue_mutex, NULL); pthread_cond_init(&key_queue_cond, NULL); + self = this; memset(key_pressed, 0, sizeof(key_pressed)); } void RecoveryUI::Init() { + calibrate_swipe(); ev_init(input_callback, NULL); + message_socket.ServerInit(); + ev_add_fd(message_socket.fd(), message_socket_listen_event, &message_socket); pthread_create(&input_t, NULL, input_thread, NULL); } @@ -74,31 +262,48 @@ int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data) if (ret) 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. - self->rel_sum += ev.value; - if (self->rel_sum > 3) { - self->process_key(KEY_DOWN, 1); // press down key - self->process_key(KEY_DOWN, 0); // and release it - self->rel_sum = 0; - } else if (self->rel_sum < -3) { - self->process_key(KEY_UP, 1); // press up key - self->process_key(KEY_UP, 0); // and release it - self->rel_sum = 0; - } + show_event(fd, &ev); + + input_device* dev = NULL; + int n; + for (n = 0; n < MAX_NR_INPUT_DEVICES; ++n) { + if (self->input_devices[n].fd == fd) { + dev = &self->input_devices[n]; + break; + } + if (self->input_devices[n].fd == -1) { + dev = &self->input_devices[n]; + memset(dev, 0, sizeof(input_device)); + dev->fd = fd; + dev->tracking_id = -1; + self->calibrate_touch(dev); + self->setup_vkeys(dev); + break; } - } else { - self->rel_sum = 0; + } + if (!dev) { + LOGE("input_callback: no more available input devices\n"); + return -1; } - if (ev.type == EV_KEY && ev.code <= KEY_MAX) - self->process_key(ev.code, ev.value); + if (ev.type != EV_REL) { + dev->rel_sum = 0; + } + + switch (ev.type) { + case EV_SYN: + self->process_syn(dev, ev.code, ev.value); + break; + case EV_ABS: + self->process_abs(dev, ev.code, ev.value); + break; + case EV_REL: + self->process_rel(dev, ev.code, ev.value); + break; + case EV_KEY: + self->process_key(dev, ev.code, ev.value); + break; + } return 0; } @@ -115,11 +320,14 @@ int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data) // a key is registered. // // updown == 1 for key down events; 0 for key up events -void RecoveryUI::process_key(int key_code, int updown) { +void RecoveryUI::process_key(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) { @@ -154,6 +362,7 @@ void RecoveryUI::process_key(int key_code, int updown) { break; case RecoveryUI::REBOOT: + vold_unmount_all(); if (reboot_enabled) { android_reboot(ANDROID_RB_RESTART, 0, 0); } @@ -173,6 +382,146 @@ void RecoveryUI::process_key(int key_code, int updown) { } } +void RecoveryUI::process_syn(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::process_abs(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::process_rel(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); @@ -191,7 +540,193 @@ 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; +} + +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; + } + } + } + + 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); + process_key(dev, key, 1); + process_key(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; + process_key(dev, key, 1); + process_key(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) { @@ -212,9 +747,19 @@ void* RecoveryUI::input_thread(void *cookie) return NULL; } +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); +} + 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,14 +769,19 @@ int RecoveryUI::WaitKey() gettimeofday(&now, NULL); 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; + } } - } while (usb_connected() && key_queue_len == 0); + timeouts--; + } while ((timeouts || usb_connected()) && key_queue_len == 0); int key = -1; if (key_queue_len > 0) { @@ -282,7 +832,7 @@ void RecoveryUI::FlushKeys() { // - Press power seven times in a row to reboot. // - Alternate vol-up and vol-down seven times to mount /system. RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) { - if ((IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) || key == KEY_HOME) { + if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) { return TOGGLE; } @@ -329,3 +879,14 @@ void RecoveryUI::SetEnableReboot(bool enabled) { enable_reboot = enabled; pthread_mutex_unlock(&key_queue_mutex); } + +void RecoveryUI::NotifyVolumesChanged() { + v_changed = 1; +} + +bool RecoveryUI::VolumesChanged() { + int ret = v_changed; + if (v_changed > 0) + v_changed = 0; + return ret == 1; +} @@ -21,6 +21,74 @@ #include <pthread.h> #include <time.h> +#include "messagesocket.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 +105,7 @@ class RecoveryUI { virtual void SetLocale(const char* locale) { } // 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, INFO, ERROR, HEADLESS, NR_ICONS }; virtual void SetBackground(Icon icon) = 0; // --- progress indicator --- @@ -64,12 +132,23 @@ class RecoveryUI { // Write a message to the on-screen log (shown if the user has // toggled on the text display). virtual void Print(const char* fmt, ...) = 0; // __attribute__((format(printf, 1, 2))) = 0; + virtual void ClearLog() = 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 keypress and return it. May return -1 after timeout. virtual int WaitKey(); + // Cancel a WaitKey() + virtual void CancelWaitKey(); + virtual bool IsKeyPressed(int key); // Erase any queued-up keys. @@ -102,6 +181,9 @@ class RecoveryUI { // --- menu display --- + virtual int MenuItemStart() const = 0; + virtual int MenuItemHeight() const = 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). @@ -110,12 +192,15 @@ class RecoveryUI { // Set the menu highlight to the given index, and return it (capped to // the range [0..numitems). - 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 + virtual void NotifyVolumesChanged(); + protected: void EnqueueKey(int key_code); @@ -130,11 +215,19 @@ 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 consecutive_alternate_keys; int last_key; + input_device input_devices[MAX_NR_INPUT_DEVICES]; + + point fb_dimensions; + point min_swipe_px; + + MessageSocket message_socket; + typedef struct { RecoveryUI* ui; int key_code; @@ -145,11 +238,23 @@ private: static void* input_thread(void* cookie); static int input_callback(int fd, uint32_t epevents, void* data); - void process_key(int key_code, int updown); + void process_key(input_device* dev, int key_code, int updown); + void process_syn(input_device* dev, int code, int value); + void process_abs(input_device* dev, int code, int value); + void process_rel(input_device* dev, int code, int value); bool usb_connected(); + bool VolumesChanged(); static void* time_key_helper(void* cookie); void time_key(int key_code, int count); + + void process_touch(int fd, struct input_event *ev); + void calibrate_touch(input_device* dev); + void setup_vkeys(input_device* dev); + void calibrate_swipe(); + void handle_press(input_device* dev); + void handle_release(input_device* dev); + void handle_gestures(input_device* dev); }; #endif // RECOVERY_UI_H diff --git a/updater/Android.mk b/updater/Android.mk index 11e7bb80..333de5ab 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -19,19 +19,31 @@ LOCAL_MODULE_TAGS := eng LOCAL_SRC_FILES := $(updater_src_files) -ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) +#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 + libz \ + liblz4-static +#endif + +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 += libmincrypt libbz libxz LOCAL_STATIC_LIBRARIES += libcutils liblog libstdc++ libc LOCAL_STATIC_LIBRARIES += libselinux tune2fs_static_libraries := \ @@ -57,8 +69,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. # Note that we have to remove any existing depency files before creating new one, @@ -78,14 +93,18 @@ $(inc) : $(inc_dep_file) $(hide) echo "void RegisterDeviceExtensions() {" >> $@ $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;) $(hide) echo "}" >> $@ - +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 6060ac28..d2d186bf 100644 --- a/updater/blockimg.c +++ b/updater/blockimg.c @@ -16,6 +16,7 @@ #include <ctype.h> #include <errno.h> +#include <dirent.h> #include <fcntl.h> #include <inttypes.h> #include <pthread.h> @@ -23,6 +24,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/ioctl.h> @@ -32,7 +34,7 @@ #include "applypatch/applypatch.h" #include "edify/expr.h" #include "mincrypt/sha.h" -#include "minzip/DirUtil.h" +#include "minzip/Hash.h" #include "updater.h" #define BLOCKSIZE 4096 @@ -46,6 +48,10 @@ #define BLKDISCARD _IO(0x12,119) #endif +#define STASH_DIRECTORY_BASE "/cache/recovery" +#define STASH_DIRECTORY_MODE 0700 +#define STASH_FILE_MODE 0600 + char* PrintSha1(const uint8_t* digest); typedef struct { @@ -80,44 +86,72 @@ static RangeSet* parse_range(char* text) { return out; } -static void readblock(int fd, uint8_t* data, size_t size) { +static int range_overlaps(RangeSet* r1, RangeSet* r2) { + int i, j, r1_0, r1_1, r2_0, r2_1; + + if (!r1 || !r2) { + return 0; + } + + for (i = 0; i < r1->count; ++i) { + r1_0 = r1->pos[i * 2]; + r1_1 = r1->pos[i * 2 + 1]; + + for (j = 0; j < r2->count; ++j) { + r2_0 = r2->pos[j * 2]; + r2_1 = r2->pos[j * 2 + 1]; + + if (!(r2_0 > r1_1 || r1_0 > r2_1)) { + return 1; + } + } + } + + return 0; +} + +static int read_all(int fd, uint8_t* data, size_t size) { size_t so_far = 0; while (so_far < size) { ssize_t r = read(fd, data+so_far, size-so_far); if (r < 0 && errno != EINTR) { fprintf(stderr, "read failed: %s\n", strerror(errno)); - return; + return -1; } else { so_far += r; } } + return 0; } -static void writeblock(int fd, const uint8_t* data, size_t size) { +static int write_all(int fd, const uint8_t* data, size_t size) { size_t written = 0; while (written < size) { ssize_t w = write(fd, data+written, size-written); if (w < 0 && errno != EINTR) { fprintf(stderr, "write failed: %s\n", strerror(errno)); - return; + return -1; } else { written += w; } } + + return 0; } -static void check_lseek(int fd, off64_t offset, int whence) { +static int check_lseek(int fd, off64_t offset, int whence) { while (true) { off64_t ret = lseek64(fd, offset, whence); if (ret < 0) { if (errno != EINTR) { fprintf(stderr, "lseek64 failed: %s\n", strerror(errno)); - exit(1); + return -1; } } else { break; } } + return 0; } static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) { @@ -146,14 +180,21 @@ static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { if (rss->p_remain <= 0) { fprintf(stderr, "range sink write overrun"); - exit(1); + return 0; } ssize_t written = 0; while (size > 0) { size_t write_now = size; - if (rss->p_remain < write_now) write_now = rss->p_remain; - writeblock(rss->fd, data, write_now); + + if (rss->p_remain < write_now) { + write_now = rss->p_remain; + } + + if (write_all(rss->fd, data, write_now) == -1) { + break; + } + data += write_now; size -= write_now; @@ -164,12 +205,17 @@ static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { // move to the next block ++rss->p_block; if (rss->p_block < rss->tgt->count) { - rss->p_remain = (rss->tgt->pos[rss->p_block*2+1] - rss->tgt->pos[rss->p_block*2]) * BLOCKSIZE; - check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, SEEK_SET); + rss->p_remain = (rss->tgt->pos[rss->p_block * 2 + 1] - + rss->tgt->pos[rss->p_block * 2]) * BLOCKSIZE; + + if (check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, + SEEK_SET) == -1) { + break; + } } else { // we can't write any more; return how many bytes have // been written so far. - return written; + break; } } } @@ -241,10 +287,67 @@ 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; } +static int ReadBlocks(RangeSet* src, uint8_t* buffer, int fd) { + int i; + size_t p = 0; + size_t size; + + if (!src || !buffer) { + return -1; + } + + for (i = 0; i < src->count; ++i) { + if (check_lseek(fd, (off64_t) src->pos[i * 2] * BLOCKSIZE, SEEK_SET) == -1) { + return -1; + } + + size = (src->pos[i * 2 + 1] - src->pos[i * 2]) * BLOCKSIZE; + + if (read_all(fd, buffer + p, size) == -1) { + return -1; + } + + p += size; + } + + return 0; +} + +static int WriteBlocks(RangeSet* tgt, uint8_t* buffer, int fd) { + int i; + size_t p = 0; + size_t size; + + if (!tgt || !buffer) { + return -1; + } + + for (i = 0; i < tgt->count; ++i) { + if (check_lseek(fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET) == -1) { + return -1; + } + + size = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * BLOCKSIZE; + + if (write_all(fd, buffer + p, size) == -1) { + return -1; + } + + p += size; + } + + return 0; +} + // Do a source/target load for move/bsdiff/imgdiff in version 1. // 'wordsave' is the save_ptr of a strtok_r()-in-progress. We expect // to parse the remainder of the string as: @@ -255,30 +358,506 @@ static void* unzip_new_data(void* cookie) { // it to make it larger if necessary. The target ranges are returned // in *tgt, if tgt is non-NULL. -static void LoadSrcTgtVersion1(char* wordsave, RangeSet** tgt, int* src_blocks, +static int LoadSrcTgtVersion1(char** wordsave, RangeSet** tgt, int* src_blocks, uint8_t** buffer, size_t* buffer_alloc, int fd) { char* word; + int rc; - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); RangeSet* src = parse_range(word); if (tgt != NULL) { - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); *tgt = parse_range(word); } allocate(src->size * BLOCKSIZE, buffer, buffer_alloc); - size_t p = 0; - int i; - for (i = 0; i < src->count; ++i) { - check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET); - size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE; - readblock(fd, *buffer+p, sz); - p += sz; - } - + rc = ReadBlocks(src, *buffer, fd); *src_blocks = src->size; + free(src); + return rc; +} + +static int VerifyBlocks(const char *expected, const uint8_t *buffer, + size_t blocks, int printerror) { + char* hexdigest = NULL; + int rc = -1; + uint8_t digest[SHA_DIGEST_SIZE]; + + if (!expected || !buffer) { + return rc; + } + + SHA_hash(buffer, blocks * BLOCKSIZE, digest); + hexdigest = PrintSha1(digest); + + if (hexdigest != NULL) { + rc = strcmp(expected, hexdigest); + + if (rc != 0 && printerror) { + fprintf(stderr, "failed to verify blocks (expected %s, read %s)\n", + expected, hexdigest); + } + + free(hexdigest); + } + + return rc; +} + +static char* GetStashFileName(const char* base, const char* id, const char* postfix) { + char* fn; + int len; + int res; + + if (base == NULL) { + return NULL; + } + + if (id == NULL) { + id = ""; + } + + if (postfix == NULL) { + postfix = ""; + } + + len = strlen(STASH_DIRECTORY_BASE) + 1 + strlen(base) + 1 + strlen(id) + strlen(postfix) + 1; + fn = malloc(len); + + if (fn == NULL) { + fprintf(stderr, "failed to malloc %d bytes for fn\n", len); + return NULL; + } + + res = snprintf(fn, len, STASH_DIRECTORY_BASE "/%s/%s%s", base, id, postfix); + + if (res < 0 || res >= len) { + fprintf(stderr, "failed to format file name (return value %d)\n", res); + free(fn); + return NULL; + } + + return fn; +} + +typedef void (*StashCallback)(const char*, void*); + +// Does a best effort enumeration of stash files. Ignores possible non-file +// items in the stash directory and continues despite of errors. Calls the +// 'callback' function for each file and passes 'data' to the function as a +// parameter. + +static void EnumerateStash(const char* dirname, StashCallback callback, void* data) { + char* fn; + DIR* directory; + int len; + int res; + struct dirent* item; + + if (dirname == NULL || callback == NULL) { + return; + } + + directory = opendir(dirname); + + if (directory == NULL) { + if (errno != ENOENT) { + fprintf(stderr, "opendir \"%s\" failed: %s\n", dirname, strerror(errno)); + } + return; + } + + while ((item = readdir(directory)) != NULL) { + if (item->d_type != DT_REG) { + continue; + } + + len = strlen(dirname) + 1 + strlen(item->d_name) + 1; + fn = malloc(len); + + if (fn == NULL) { + fprintf(stderr, "failed to malloc %d bytes for fn\n", len); + continue; + } + + res = snprintf(fn, len, "%s/%s", dirname, item->d_name); + + if (res < 0 || res >= len) { + fprintf(stderr, "failed to format file name (return value %d)\n", res); + free(fn); + continue; + } + + callback(fn, data); + free(fn); + } + + if (closedir(directory) == -1) { + fprintf(stderr, "closedir \"%s\" failed: %s\n", dirname, strerror(errno)); + } +} + +static void UpdateFileSize(const char* fn, void* data) { + int* size = (int*) data; + struct stat st; + + if (!fn || !data) { + return; + } + + if (stat(fn, &st) == -1) { + fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno)); + return; + } + + *size += st.st_size; +} + +// Deletes the stash directory and all files in it. Assumes that it only +// contains files. There is nothing we can do about unlikely, but possible +// errors, so they are merely logged. + +static void DeleteFile(const char* fn, void* data) { + if (fn) { + fprintf(stderr, "deleting %s\n", fn); + + if (unlink(fn) == -1 && errno != ENOENT) { + fprintf(stderr, "unlink \"%s\" failed: %s\n", fn, strerror(errno)); + } + } +} + +static void DeletePartial(const char* fn, void* data) { + if (fn && strstr(fn, ".partial") != NULL) { + DeleteFile(fn, data); + } +} + +static void DeleteStash(const char* base) { + char* dirname; + + if (base == NULL) { + return; + } + + dirname = GetStashFileName(base, NULL, NULL); + + if (dirname == NULL) { + return; + } + + fprintf(stderr, "deleting stash %s\n", base); + EnumerateStash(dirname, DeleteFile, NULL); + + if (rmdir(dirname) == -1) { + if (errno != ENOENT && errno != ENOTDIR) { + fprintf(stderr, "rmdir \"%s\" failed: %s\n", dirname, strerror(errno)); + } + } + + free(dirname); +} + +static int LoadStash(const char* base, const char* id, int verify, int* blocks, uint8_t** buffer, + size_t* buffer_alloc, int printnoent) { + char *fn = NULL; + int blockcount = 0; + int fd = -1; + int rc = -1; + int res; + struct stat st; + + if (!base || !id || !buffer || !buffer_alloc) { + goto lsout; + } + + if (!blocks) { + blocks = &blockcount; + } + + fn = GetStashFileName(base, id, NULL); + + if (fn == NULL) { + goto lsout; + } + + res = stat(fn, &st); + + if (res == -1) { + if (errno != ENOENT || printnoent) { + fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno)); + } + goto lsout; + } + + fprintf(stderr, " loading %s\n", fn); + + if ((st.st_size % BLOCKSIZE) != 0) { + fprintf(stderr, "%s size %zd not multiple of block size %d", fn, st.st_size, BLOCKSIZE); + goto lsout; + } + + fd = TEMP_FAILURE_RETRY(open(fn, O_RDONLY)); + + if (fd == -1) { + fprintf(stderr, "open \"%s\" failed: %s\n", fn, strerror(errno)); + goto lsout; + } + + allocate(st.st_size, buffer, buffer_alloc); + + if (read_all(fd, *buffer, st.st_size) == -1) { + goto lsout; + } + + *blocks = st.st_size / BLOCKSIZE; + + if (verify && VerifyBlocks(id, *buffer, *blocks, 1) != 0) { + fprintf(stderr, "unexpected contents in %s\n", fn); + DeleteFile(fn, NULL); + goto lsout; + } + + rc = 0; + +lsout: + if (fd != -1) { + TEMP_FAILURE_RETRY(close(fd)); + } + + if (fn) { + free(fn); + } + + return rc; +} + +static int WriteStash(const char* base, const char* id, int blocks, uint8_t* buffer, + int checkspace, int *exists) { + char *fn = NULL; + char *cn = NULL; + int fd = -1; + int rc = -1; + int res; + struct stat st; + + if (base == NULL || buffer == NULL) { + goto wsout; + } + + if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) { + fprintf(stderr, "not enough space to write stash\n"); + goto wsout; + } + + fn = GetStashFileName(base, id, ".partial"); + cn = GetStashFileName(base, id, NULL); + + if (fn == NULL || cn == NULL) { + goto wsout; + } + + if (exists) { + res = stat(cn, &st); + + if (res == 0) { + // The file already exists and since the name is the hash of the contents, + // it's safe to assume the contents are identical (accidental hash collisions + // are unlikely) + fprintf(stderr, " skipping %d existing blocks in %s\n", blocks, cn); + *exists = 1; + rc = 0; + goto wsout; + } + + *exists = 0; + } + + fprintf(stderr, " writing %d blocks to %s\n", blocks, cn); + + 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)); + goto wsout; + } + + if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) { + goto wsout; + } + + if (fsync(fd) == -1) { + fprintf(stderr, "fsync \"%s\" failed: %s\n", fn, strerror(errno)); + goto wsout; + } + + if (rename(fn, cn) == -1) { + fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", fn, cn, strerror(errno)); + goto wsout; + } + + rc = 0; + +wsout: + if (fd != -1) { + TEMP_FAILURE_RETRY(close(fd)); + } + + if (fn) { + free(fn); + } + + if (cn) { + free(cn); + } + + return rc; +} + +// Creates a directory for storing stash files and checks if the /cache partition +// hash enough space for the expected amount of blocks we need to store. Returns +// >0 if we created the directory, zero if it existed already, and <0 of failure. + +static int CreateStash(State* state, int maxblocks, const char* blockdev, char** base) { + char* dirname = NULL; + const uint8_t* digest; + int rc = -1; + int res; + int size = 0; + SHA_CTX ctx; + struct stat st; + + if (blockdev == NULL || base == NULL) { + goto csout; + } + + // Stash directory should be different for each partition to avoid conflicts + // when updating multiple partitions at the same time, so we use the hash of + // the block device name as the base directory + SHA_init(&ctx); + SHA_update(&ctx, blockdev, strlen(blockdev)); + digest = SHA_final(&ctx); + *base = PrintSha1(digest); + + if (*base == NULL) { + goto csout; + } + + dirname = GetStashFileName(*base, NULL, NULL); + + if (dirname == NULL) { + goto csout; + } + + res = stat(dirname, &st); + + if (res == -1 && errno != ENOENT) { + ErrorAbort(state, "stat \"%s\" failed: %s\n", dirname, strerror(errno)); + goto csout; + } else if (res != 0) { + fprintf(stderr, "creating stash %s\n", dirname); + res = mkdir(dirname, STASH_DIRECTORY_MODE); + + if (res != 0) { + ErrorAbort(state, "mkdir \"%s\" failed: %s\n", dirname, strerror(errno)); + goto csout; + } + + if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) { + ErrorAbort(state, "not enough space for stash\n"); + goto csout; + } + + rc = 1; // Created directory + goto csout; + } + + fprintf(stderr, "using existing stash %s\n", dirname); + + // If the directory already exists, calculate the space already allocated to + // stash files and check if there's enough for all required blocks. Delete any + // partially completed stash files first. + + EnumerateStash(dirname, DeletePartial, NULL); + EnumerateStash(dirname, UpdateFileSize, &size); + + size = (maxblocks * BLOCKSIZE) - size; + + if (size > 0 && CacheSizeCheck(size) != 0) { + ErrorAbort(state, "not enough space for stash (%d more needed)\n", size); + goto csout; + } + + rc = 0; // Using existing directory + +csout: + if (dirname) { + free(dirname); + } + + return rc; +} + +static int SaveStash(const char* base, char** wordsave, uint8_t** buffer, size_t* buffer_alloc, + int fd, int usehash, int* isunresumable) { + char *id = NULL; + int res = -1; + int blocks = 0; + + if (!wordsave || !buffer || !buffer_alloc || !isunresumable) { + return -1; + } + + id = strtok_r(NULL, " ", wordsave); + + if (id == NULL) { + fprintf(stderr, "missing id field in stash command\n"); + return -1; + } + + if (usehash && LoadStash(base, id, 1, &blocks, buffer, buffer_alloc, 0) == 0) { + // Stash file already exists and has expected contents. Do not + // read from source again, as the source may have been already + // overwritten during a previous attempt. + return 0; + } + + if (LoadSrcTgtVersion1(wordsave, NULL, &blocks, buffer, buffer_alloc, fd) == -1) { + return -1; + } + + if (usehash && VerifyBlocks(id, *buffer, blocks, 1) != 0) { + // Source blocks have unexpected contents. If we actually need this + // data later, this is an unrecoverable error. However, the command + // that uses the data may have already completed previously, so the + // possible failure will occur during source block verification. + fprintf(stderr, "failed to load source blocks for stash %s\n", id); + return 0; + } + + fprintf(stderr, "stashing %d blocks to %s\n", blocks, id); + return WriteStash(base, id, blocks, *buffer, 0, NULL); +} + +static int FreeStash(const char* base, const char* id) { + char *fn = NULL; + + if (base == NULL || id == NULL) { + return -1; + } + + fn = GetStashFileName(base, id, NULL); + + if (fn == NULL) { + return -1; + } + + DeleteFile(fn, NULL); + free(fn); + + return 0; } static void MoveRange(uint8_t* dest, RangeSet* locs, const uint8_t* source) { @@ -312,64 +891,616 @@ static void MoveRange(uint8_t* dest, RangeSet* locs, const uint8_t* source) { // On return, buffer is filled with the loaded source data (rearranged // and combined with stashed data as necessary). buffer may be // reallocated if needed to accommodate the source data. *tgt is the -// target RangeSet. Any stashes required are taken from stash_table -// and free()'d after being used. +// target RangeSet. Any stashes required are loaded using LoadStash. -static void LoadSrcTgtVersion2(char* wordsave, RangeSet** tgt, int* src_blocks, +static int LoadSrcTgtVersion2(char** wordsave, RangeSet** tgt, int* src_blocks, uint8_t** buffer, size_t* buffer_alloc, int fd, - uint8_t** stash_table) { + const char* stashbase, int* overlap) { char* word; + char* colonsave; + char* colon; + int id; + int res; + RangeSet* locs; + size_t stashalloc = 0; + uint8_t* stash = NULL; if (tgt != NULL) { - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); *tgt = parse_range(word); } - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); *src_blocks = strtol(word, NULL, 0); allocate(*src_blocks * BLOCKSIZE, buffer, buffer_alloc); - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); if (word[0] == '-' && word[1] == '\0') { // no source ranges, only stashes } else { RangeSet* src = parse_range(word); + res = ReadBlocks(src, *buffer, fd); - size_t p = 0; - int i; - for (i = 0; i < src->count; ++i) { - check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET); - size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE; - readblock(fd, *buffer+p, sz); - p += sz; + if (overlap && tgt) { + *overlap = range_overlaps(src, *tgt); } + free(src); - word = strtok_r(NULL, " ", &wordsave); + if (res == -1) { + return -1; + } + + word = strtok_r(NULL, " ", wordsave); if (word == NULL) { // no stashes, only source range - return; + return 0; } - RangeSet* locs = parse_range(word); + locs = parse_range(word); MoveRange(*buffer, locs, *buffer); + free(locs); } - while ((word = strtok_r(NULL, " ", &wordsave)) != NULL) { + while ((word = strtok_r(NULL, " ", wordsave)) != NULL) { // Each word is a an index into the stash table, a colon, and // then a rangeset describing where in the source block that // stashed data should go. - char* colonsave = NULL; - char* colon = strtok_r(word, ":", &colonsave); - int stash_id = strtol(colon, NULL, 0); + colonsave = NULL; + colon = strtok_r(word, ":", &colonsave); + + res = LoadStash(stashbase, colon, 0, NULL, &stash, &stashalloc, 1); + + if (res == -1) { + // These source blocks will fail verification if used later, but we + // will let the caller decide if this is a fatal failure + fprintf(stderr, "failed to load stash %s\n", colon); + continue; + } + colon = strtok_r(NULL, ":", &colonsave); - RangeSet* locs = parse_range(colon); - MoveRange(*buffer, locs, stash_table[stash_id]); - free(stash_table[stash_id]); - stash_table[stash_id] = NULL; + locs = parse_range(colon); + + MoveRange(*buffer, locs, stash); free(locs); } + + if (stash) { + free(stash); + } + + return 0; +} + +// Parameters for transfer list command functions +typedef struct { + char* cmdname; + char* cpos; + char* freestash; + char* stashbase; + int canwrite; + int createdstash; + int fd; + int foundwrites; + int isunresumable; + int version; + int written; + NewThreadInfo nti; + pthread_t thread; + size_t bufsize; + uint8_t* buffer; + uint8_t* patch_start; +} CommandParameters; + +// Do a source/target load for move/bsdiff/imgdiff in version 3. +// +// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which +// tells the function whether to expect separate source and targe block hashes, or +// if they are both the same and only one hash should be expected, and +// 'isunresumable', which receives a non-zero value if block verification fails in +// a way that the update cannot be resumed anymore. +// +// If the function is unable to load the necessary blocks or their contents don't +// match the hashes, the return value is -1 and the command should be aborted. +// +// If the return value is 1, the command has already been completed according to +// the contents of the target blocks, and should not be performed again. +// +// If the return value is 0, source blocks have expected content and the command +// can be performed. + +static int LoadSrcTgtVersion3(CommandParameters* params, RangeSet** tgt, int* src_blocks, + int onehash, int* overlap) { + char* srchash = NULL; + char* tgthash = NULL; + int stash_exists = 0; + int overlap_blocks = 0; + int rc = -1; + uint8_t* tgtbuffer = NULL; + + if (!params|| !tgt || !src_blocks || !overlap) { + goto v3out; + } + + srchash = strtok_r(NULL, " ", ¶ms->cpos); + + if (srchash == NULL) { + fprintf(stderr, "missing source hash\n"); + goto v3out; + } + + if (onehash) { + tgthash = srchash; + } else { + tgthash = strtok_r(NULL, " ", ¶ms->cpos); + + if (tgthash == NULL) { + fprintf(stderr, "missing target hash\n"); + goto v3out; + } + } + + if (LoadSrcTgtVersion2(¶ms->cpos, tgt, src_blocks, ¶ms->buffer, ¶ms->bufsize, + params->fd, params->stashbase, overlap) == -1) { + goto v3out; + } + + tgtbuffer = (uint8_t*) malloc((*tgt)->size * BLOCKSIZE); + + if (tgtbuffer == NULL) { + fprintf(stderr, "failed to allocate %d bytes\n", (*tgt)->size * BLOCKSIZE); + goto v3out; + } + + if (ReadBlocks(*tgt, tgtbuffer, params->fd) == -1) { + goto v3out; + } + + if (VerifyBlocks(tgthash, tgtbuffer, (*tgt)->size, 0) == 0) { + // Target blocks already have expected content, command should be skipped + rc = 1; + goto v3out; + } + + if (VerifyBlocks(srchash, params->buffer, *src_blocks, 1) == 0) { + // If source and target blocks overlap, stash the source blocks so we can + // resume from possible write errors + if (*overlap) { + fprintf(stderr, "stashing %d overlapping blocks to %s\n", *src_blocks, + srchash); + + if (WriteStash(params->stashbase, srchash, *src_blocks, params->buffer, 1, + &stash_exists) != 0) { + fprintf(stderr, "failed to stash overlapping source blocks\n"); + goto v3out; + } + + // Can be deleted when the write has completed + if (!stash_exists) { + params->freestash = srchash; + } + } + + // Source blocks have expected content, command can proceed + rc = 0; + goto v3out; + } + + if (*overlap && LoadStash(params->stashbase, srchash, 1, NULL, ¶ms->buffer, + ¶ms->bufsize, 1) == 0) { + // Overlapping source blocks were previously stashed, command can proceed. + // We are recovering from an interrupted command, so we don't know if the + // stash can safely be deleted after this command. + rc = 0; + goto v3out; + } + + // Valid source data not available, update cannot be resumed + fprintf(stderr, "partition has unexpected contents\n"); + params->isunresumable = 1; + +v3out: + if (tgtbuffer) { + free(tgtbuffer); + } + + return rc; +} + +static int PerformCommandMove(CommandParameters* params) { + int blocks = 0; + int overlap = 0; + int rc = -1; + int status = 0; + RangeSet* tgt = NULL; + + if (!params) { + goto pcmout; + } + + if (params->version == 1) { + status = LoadSrcTgtVersion1(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd); + } else if (params->version == 2) { + status = LoadSrcTgtVersion2(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd, params->stashbase, NULL); + } else if (params->version >= 3) { + status = LoadSrcTgtVersion3(params, &tgt, &blocks, 1, &overlap); + } + + if (status == -1) { + fprintf(stderr, "failed to read blocks for move\n"); + goto pcmout; + } + + if (status == 0) { + params->foundwrites = 1; + } else if (params->foundwrites) { + fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname); + } + + if (params->canwrite) { + if (status == 0) { + fprintf(stderr, " moving %d blocks\n", blocks); + + if (WriteBlocks(tgt, params->buffer, params->fd) == -1) { + goto pcmout; + } + } else { + fprintf(stderr, "skipping %d already moved blocks\n", blocks); + } + + } + + if (params->freestash) { + FreeStash(params->stashbase, params->freestash); + params->freestash = NULL; + } + + params->written += tgt->size; + rc = 0; + +pcmout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandStash(CommandParameters* params) { + if (!params) { + return -1; + } + + return SaveStash(params->stashbase, ¶ms->cpos, ¶ms->buffer, ¶ms->bufsize, + params->fd, (params->version >= 3), ¶ms->isunresumable); +} + +static int PerformCommandFree(CommandParameters* params) { + if (!params) { + return -1; + } + + if (params->createdstash || params->canwrite) { + return FreeStash(params->stashbase, params->cpos); + } + + return 0; +} + +static int PerformCommandZero(CommandParameters* params) { + char* range = NULL; + int i; + int j; + int rc = -1; + RangeSet* tgt = NULL; + + if (!params) { + goto pczout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + fprintf(stderr, "missing target blocks for zero\n"); + goto pczout; + } + + tgt = parse_range(range); + + fprintf(stderr, " zeroing %d blocks\n", tgt->size); + + allocate(BLOCKSIZE, ¶ms->buffer, ¶ms->bufsize); + memset(params->buffer, 0, BLOCKSIZE); + + if (params->canwrite) { + for (i = 0; i < tgt->count; ++i) { + if (check_lseek(params->fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET) == -1) { + goto pczout; + } + + for (j = tgt->pos[i * 2]; j < tgt->pos[i * 2 + 1]; ++j) { + if (write_all(params->fd, params->buffer, BLOCKSIZE) == -1) { + goto pczout; + } + } + } + } + + if (params->cmdname[0] == 'z') { + // Update only for the zero command, as the erase command will call + // this if DEBUG_ERASE is defined. + params->written += tgt->size; + } + + rc = 0; + +pczout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandNew(CommandParameters* params) { + char* range = NULL; + int rc = -1; + RangeSet* tgt = NULL; + RangeSinkState rss; + + if (!params) { + goto pcnout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + goto pcnout; + } + + tgt = parse_range(range); + + if (params->canwrite) { + fprintf(stderr, " writing %d blocks of new data\n", tgt->size); + + rss.fd = params->fd; + rss.tgt = tgt; + rss.p_block = 0; + rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; + + if (check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET) == -1) { + goto pcnout; + } + + pthread_mutex_lock(¶ms->nti.mu); + params->nti.rss = &rss; + pthread_cond_broadcast(¶ms->nti.cv); + + while (params->nti.rss) { + pthread_cond_wait(¶ms->nti.cv, ¶ms->nti.mu); + } + + pthread_mutex_unlock(¶ms->nti.mu); + } + + params->written += tgt->size; + rc = 0; + +pcnout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandDiff(CommandParameters* params) { + char* logparams = NULL; + char* value = NULL; + int blocks = 0; + int overlap = 0; + int rc = -1; + int status = 0; + RangeSet* tgt = NULL; + RangeSinkState rss; + size_t len = 0; + size_t offset = 0; + Value patch_value; + + if (!params) { + goto pcdout; + } + + logparams = strdup(params->cpos); + value = strtok_r(NULL, " ", ¶ms->cpos); + + if (value == NULL) { + fprintf(stderr, "missing patch offset for %s\n", params->cmdname); + goto pcdout; + } + + offset = strtoul(value, NULL, 0); + + value = strtok_r(NULL, " ", ¶ms->cpos); + + if (value == NULL) { + fprintf(stderr, "missing patch length for %s\n", params->cmdname); + goto pcdout; + } + + len = strtoul(value, NULL, 0); + + if (params->version == 1) { + status = LoadSrcTgtVersion1(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd); + } else if (params->version == 2) { + status = LoadSrcTgtVersion2(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd, params->stashbase, NULL); + } else if (params->version >= 3) { + status = LoadSrcTgtVersion3(params, &tgt, &blocks, 0, &overlap); + } + + if (status == -1) { + fprintf(stderr, "failed to read blocks for diff\n"); + goto pcdout; + } + + if (status == 0) { + params->foundwrites = 1; + } else if (params->foundwrites) { + fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname); + } + + if (params->canwrite) { + if (status == 0) { + fprintf(stderr, "patching %d blocks to %d\n", blocks, tgt->size); + + patch_value.type = VAL_BLOB; + patch_value.size = len; + patch_value.data = (char*) (params->patch_start + offset); + + rss.fd = params->fd; + rss.tgt = tgt; + rss.p_block = 0; + rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; + + if (check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET) == -1) { + goto pcdout; + } + + if (params->cmdname[0] == 'i') { // imgdiff + ApplyImagePatch(params->buffer, blocks * BLOCKSIZE, &patch_value, + &RangeSinkWrite, &rss, NULL, NULL); + } else { + ApplyBSDiffPatch(params->buffer, blocks * BLOCKSIZE, &patch_value, + 0, &RangeSinkWrite, &rss, NULL); + } + + // We expect the output of the patcher to fill the tgt ranges exactly. + if (rss.p_block != tgt->count || rss.p_remain != 0) { + fprintf(stderr, "range sink underrun?\n"); + } + } else { + fprintf(stderr, "skipping %d blocks already patched to %d [%s]\n", + blocks, tgt->size, logparams); + } + } + + if (params->freestash) { + FreeStash(params->stashbase, params->freestash); + params->freestash = NULL; + } + + params->written += tgt->size; + rc = 0; + +pcdout: + if (logparams) { + free(logparams); + } + + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandErase(CommandParameters* params) { + char* range = NULL; + int i; + int rc = -1; + RangeSet* tgt = NULL; + struct stat st; + uint64_t blocks[2]; + + if (DEBUG_ERASE) { + return PerformCommandZero(params); + } + + if (!params) { + goto pceout; + } + + if (fstat(params->fd, &st) == -1) { + fprintf(stderr, "failed to fstat device to erase: %s\n", strerror(errno)); + goto pceout; + } + + if (!S_ISBLK(st.st_mode)) { + fprintf(stderr, "not a block device; skipping erase\n"); + rc = 0; + goto pceout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + fprintf(stderr, "missing target blocks for zero\n"); + goto pceout; + } + + tgt = parse_range(range); + + if (params->canwrite) { + fprintf(stderr, " erasing %d blocks\n", tgt->size); + + for (i = 0; i < tgt->count; ++i) { + // offset in bytes + blocks[0] = tgt->pos[i * 2] * (uint64_t) BLOCKSIZE; + // length in bytes + blocks[1] = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * (uint64_t) BLOCKSIZE; + +#ifndef SUPPRESS_EMMC_WIPE + if (ioctl(params->fd, BLKDISCARD, &blocks) == -1) { + fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno)); + // Continue anyway, nothing we can do + } +#endif + } + } + + rc = 0; + +pceout: + if (tgt) { + free(tgt); + } + + return rc; +} + +// Definitions for transfer list command functions +typedef int (*CommandFunction)(CommandParameters*); + +typedef struct { + const char* name; + CommandFunction f; +} Command; + +// CompareCommands and CompareCommandNames are for the hash table + +static int CompareCommands(const void* c1, const void* c2) { + return strcmp(((const Command*) c1)->name, ((const Command*) c2)->name); +} + +static int CompareCommandNames(const void* c1, const void* c2) { + return strcmp(((const Command*) c1)->name, (const char*) c2); +} + +// HashString is used to hash command names for the hash table + +static unsigned int HashString(const char *s) { + unsigned int hash = 0; + if (s) { + while (*s) { + hash = hash * 33 + *s++; + } + } + return hash; } // args: @@ -378,393 +1509,376 @@ static void LoadSrcTgtVersion2(char* wordsave, RangeSet** tgt, int* src_blocks, // - new data stream (filename within package.zip) // - patch stream (filename within package.zip, must be uncompressed) -Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { - Value* blockdev_filename; - Value* transfer_list_value; +static Value* PerformBlockImageUpdate(const char* name, State* state, int argc, Expr* argv[], + const Command* commands, int cmdcount, int dryrun) { + + char* line = NULL; + char* linesave = NULL; + char* logcmd = NULL; char* transfer_list = NULL; - Value* new_data_fn; - Value* patch_data_fn; - bool success = false; + CommandParameters params; + const Command* cmd = NULL; + const ZipEntry* new_entry = NULL; + const ZipEntry* patch_entry = NULL; + FILE* cmd_pipe = NULL; + HashTable* cmdht = NULL; + int i; + int res; + int rc = -1; + int stash_max_blocks = 0; + int total_blocks = 0; + pthread_attr_t attr; + unsigned int cmdhash; + UpdaterInfo* ui = NULL; + Value* blockdev_filename = NULL; + Value* new_data_fn = NULL; + Value* patch_data_fn = NULL; + Value* transfer_list_value = NULL; + ZipArchive* za = NULL; + + memset(¶ms, 0, sizeof(params)); + params.canwrite = !dryrun; + + fprintf(stderr, "performing %s\n", dryrun ? "verification" : "update"); if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value, - &new_data_fn, &patch_data_fn) < 0) { - return NULL; + &new_data_fn, &patch_data_fn) < 0) { + goto pbiudone; } if (blockdev_filename->type != VAL_STRING) { ErrorAbort(state, "blockdev_filename argument to %s must be string", name); - goto done; + goto pbiudone; } if (transfer_list_value->type != VAL_BLOB) { ErrorAbort(state, "transfer_list argument to %s must be blob", name); - goto done; + goto pbiudone; } if (new_data_fn->type != VAL_STRING) { ErrorAbort(state, "new_data_fn argument to %s must be string", name); - goto done; + goto pbiudone; } if (patch_data_fn->type != VAL_STRING) { ErrorAbort(state, "patch_data_fn argument to %s must be string", name); - goto done; + goto pbiudone; } - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - FILE* cmd_pipe = ui->cmd_pipe; + ui = (UpdaterInfo*) state->cookie; - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + if (ui == NULL) { + goto pbiudone; + } + + cmd_pipe = ui->cmd_pipe; + za = ui->package_zip; + + if (cmd_pipe == NULL || za == NULL) { + goto pbiudone; + } + + patch_entry = mzFindZipEntry(za, patch_data_fn->data); - const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data); if (patch_entry == NULL) { - ErrorAbort(state, "%s(): no file \"%s\" in package", name, patch_data_fn->data); - goto done; + fprintf(stderr, "%s(): no file \"%s\" in package", name, patch_data_fn->data); + goto pbiudone; } - uint8_t* patch_start = ((UpdaterInfo*)(state->cookie))->package_zip_addr + - mzGetZipEntryOffset(patch_entry); + params.patch_start = ui->package_zip_addr + mzGetZipEntryOffset(patch_entry); + new_entry = mzFindZipEntry(za, new_data_fn->data); - const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data); if (new_entry == NULL) { - ErrorAbort(state, "%s(): no file \"%s\" in package", name, new_data_fn->data); - goto done; + fprintf(stderr, "%s(): no file \"%s\" in package", name, new_data_fn->data); + goto pbiudone; } - // The transfer list is a text file containing commands to - // transfer data from one place to another on the target - // partition. We parse it and execute the commands in order: - // - // zero [rangeset] - // - fill the indicated blocks with zeros - // - // new [rangeset] - // - fill the blocks with data read from the new_data file - // - // erase [rangeset] - // - mark the given blocks as empty - // - // move <...> - // bsdiff <patchstart> <patchlen> <...> - // imgdiff <patchstart> <patchlen> <...> - // - read the source blocks, apply a patch (or not in the - // case of move), write result to target blocks. bsdiff or - // imgdiff specifies the type of patch; move means no patch - // at all. - // - // The format of <...> differs between versions 1 and 2; - // see the LoadSrcTgtVersion{1,2}() functions for a - // description of what's expected. - // - // stash <stash_id> <src_range> - // - (version 2 only) load the given source range and stash - // the data in the given slot of the stash table. - // - // The creator of the transfer list will guarantee that no block - // is read (ie, used as the source for a patch or move) after it - // has been written. - // - // In version 2, the creator will guarantee that a given stash is - // loaded (with a stash command) before it's used in a - // move/bsdiff/imgdiff command. - // - // Within one command the source and target ranges may overlap so - // in general we need to read the entire source into memory before - // writing anything to the target blocks. - // - // All the patch data is concatenated into one patch_data file in - // the update package. It must be stored uncompressed because we - // memory-map it in directly from the archive. (Since patches are - // already compressed, we lose very little by not compressing - // their concatenation.) - - pthread_t new_data_thread; - NewThreadInfo nti; - nti.za = za; - nti.entry = new_entry; - nti.rss = NULL; - pthread_mutex_init(&nti.mu, NULL); - pthread_cond_init(&nti.cv, NULL); + params.fd = TEMP_FAILURE_RETRY(open(blockdev_filename->data, O_RDWR)); - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - pthread_create(&new_data_thread, &attr, unzip_new_data, &nti); + if (params.fd == -1) { + fprintf(stderr, "open \"%s\" failed: %s\n", blockdev_filename->data, strerror(errno)); + goto pbiudone; + } - int i, j; + if (params.canwrite) { + params.nti.za = za; + params.nti.entry = new_entry; - char* linesave; - char* wordsave; + pthread_mutex_init(¶ms.nti.mu, NULL); + pthread_cond_init(¶ms.nti.cv, NULL); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - int fd = open(blockdev_filename->data, O_RDWR); - if (fd < 0) { - ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno)); - goto done; + int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); + if (error != 0) { + fprintf(stderr, "pthread_create failed: %s\n", strerror(error)); + goto pbiudone; + } } - char* line; - char* word; + // The data in transfer_list_value is not necessarily null-terminated, so we need + // to copy it to a new buffer and add the null that strtok_r will need. + transfer_list = malloc(transfer_list_value->size + 1); - // The data in transfer_list_value is not necessarily - // null-terminated, so we need to copy it to a new buffer and add - // the null that strtok_r will need. - transfer_list = malloc(transfer_list_value->size+1); if (transfer_list == NULL) { fprintf(stderr, "failed to allocate %zd bytes for transfer list\n", - transfer_list_value->size+1); - exit(1); + transfer_list_value->size + 1); + goto pbiudone; } + memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size); transfer_list[transfer_list_value->size] = '\0'; + // First line in transfer list is the version number line = strtok_r(transfer_list, "\n", &linesave); + params.version = strtol(line, NULL, 0); - int version; - // first line in transfer list is the version number; currently - // there's only version 1. - if (strcmp(line, "1") == 0) { - version = 1; - } else if (strcmp(line, "2") == 0) { - version = 2; - } else { - ErrorAbort(state, "unexpected transfer list version [%s]\n", line); - goto done; + if (params.version < 1 || params.version > 3) { + fprintf(stderr, "unexpected transfer list version [%s]\n", line); + goto pbiudone; } - printf("blockimg version is %d\n", version); - // second line in transfer list is the total number of blocks we - // expect to write. + fprintf(stderr, "blockimg version is %d\n", params.version); + + // Second line in transfer list is the total number of blocks we expect to write line = strtok_r(NULL, "\n", &linesave); - int total_blocks = strtol(line, NULL, 0); - // shouldn't happen, but avoid divide by zero. - if (total_blocks == 0) ++total_blocks; - int blocks_so_far = 0; - - uint8_t** stash_table = NULL; - if (version >= 2) { - // Next line is how many stash entries are needed simultaneously. + total_blocks = strtol(line, NULL, 0); + + if (total_blocks < 0) { + ErrorAbort(state, "unexpected block count [%s]\n", line); + goto pbiudone; + } else if (total_blocks == 0) { + rc = 0; + goto pbiudone; + } + + if (params.version >= 2) { + // Third line is how many stash entries are needed simultaneously line = strtok_r(NULL, "\n", &linesave); - int stash_entries = strtol(line, NULL, 0); + fprintf(stderr, "maximum stash entries %s\n", line); - stash_table = (uint8_t**) calloc(stash_entries, sizeof(uint8_t*)); - if (stash_table == NULL) { - fprintf(stderr, "failed to allocate %d-entry stash table\n", stash_entries); - exit(1); + // Fourth line is the maximum number of blocks that will be stashed simultaneously + line = strtok_r(NULL, "\n", &linesave); + stash_max_blocks = strtol(line, NULL, 0); + + if (stash_max_blocks < 0) { + ErrorAbort(state, "unexpected maximum stash blocks [%s]\n", line); + goto pbiudone; } - // Next line is the maximum number of blocks that will be - // stashed simultaneously. This could be used to verify that - // enough memory or scratch disk space is available. - line = strtok_r(NULL, "\n", &linesave); - int stash_max_blocks = strtol(line, NULL, 0); + if (stash_max_blocks >= 0) { + res = CreateStash(state, stash_max_blocks, blockdev_filename->data, + ¶ms.stashbase); + + if (res == -1) { + goto pbiudone; + } + + params.createdstash = res; + } } - uint8_t* buffer = NULL; - size_t buffer_alloc = 0; + // Build a hash table of the available commands + cmdht = mzHashTableCreate(cmdcount, NULL); + + for (i = 0; i < cmdcount; ++i) { + cmdhash = HashString(commands[i].name); + mzHashTableLookup(cmdht, cmdhash, (void*) &commands[i], CompareCommands, true); + } - // third and subsequent lines are all individual transfer commands. + // Subsequent lines are all individual transfer commands for (line = strtok_r(NULL, "\n", &linesave); line; line = strtok_r(NULL, "\n", &linesave)) { - char* style; - style = strtok_r(line, " ", &wordsave); - - if (strcmp("move", style) == 0) { - RangeSet* tgt; - int src_blocks; - if (version == 1) { - LoadSrcTgtVersion1(wordsave, &tgt, &src_blocks, - &buffer, &buffer_alloc, fd); - } else if (version == 2) { - LoadSrcTgtVersion2(wordsave, &tgt, &src_blocks, - &buffer, &buffer_alloc, fd, stash_table); - } + logcmd = strdup(line); + params.cmdname = strtok_r(line, " ", ¶ms.cpos); - printf(" moving %d blocks\n", src_blocks); + if (params.cmdname == NULL) { + fprintf(stderr, "missing command [%s]\n", line); + goto pbiudone; + } - size_t p = 0; - for (i = 0; i < tgt->count; ++i) { - check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET); - size_t sz = (tgt->pos[i*2+1] - tgt->pos[i*2]) * BLOCKSIZE; - writeblock(fd, buffer+p, sz); - p += sz; - } + cmdhash = HashString(params.cmdname); + cmd = (const Command*) mzHashTableLookup(cmdht, cmdhash, params.cmdname, + CompareCommandNames, false); - blocks_so_far += tgt->size; - fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); - fflush(cmd_pipe); + if (cmd == NULL) { + fprintf(stderr, "unexpected command [%s]\n", params.cmdname); + goto pbiudone; + } - free(tgt); - - } else if (strcmp("stash", style) == 0) { - word = strtok_r(NULL, " ", &wordsave); - int stash_id = strtol(word, NULL, 0); - int src_blocks; - size_t stash_alloc = 0; - - // Even though the "stash" style only appears in version - // 2, the version 1 source loader happens to do exactly - // what we want to read data into the stash_table. - LoadSrcTgtVersion1(wordsave, NULL, &src_blocks, - stash_table + stash_id, &stash_alloc, fd); - - } else if (strcmp("zero", style) == 0 || - (DEBUG_ERASE && strcmp("erase", style) == 0)) { - word = strtok_r(NULL, " ", &wordsave); - RangeSet* tgt = parse_range(word); - - printf(" zeroing %d blocks\n", tgt->size); - - allocate(BLOCKSIZE, &buffer, &buffer_alloc); - memset(buffer, 0, BLOCKSIZE); - for (i = 0; i < tgt->count; ++i) { - check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET); - for (j = tgt->pos[i*2]; j < tgt->pos[i*2+1]; ++j) { - writeblock(fd, buffer, BLOCKSIZE); - } - } + if (cmd->f != NULL && cmd->f(¶ms) == -1) { + fprintf(stderr, "failed to execute command [%s]\n", + logcmd ? logcmd : params.cmdname); + goto pbiudone; + } - if (style[0] == 'z') { // "zero" but not "erase" - blocks_so_far += tgt->size; - fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); - fflush(cmd_pipe); - } + if (logcmd) { + free(logcmd); + logcmd = NULL; + } - free(tgt); - } else if (strcmp("new", style) == 0) { + 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); + } + } - word = strtok_r(NULL, " ", &wordsave); - RangeSet* tgt = parse_range(word); + if (params.canwrite) { + pthread_join(params.thread, NULL); - printf(" writing %d blocks of new data\n", tgt->size); + fprintf(stderr, "wrote %d blocks; expected %d\n", params.written, total_blocks); + fprintf(stderr, "max alloc needed was %zu\n", params.bufsize); - RangeSinkState rss; - rss.fd = fd; - rss.tgt = tgt; - rss.p_block = 0; - rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; - check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET); + // Delete stash only after successfully completing the update, as it + // may contain blocks needed to complete the update later. + DeleteStash(params.stashbase); + } else { + fprintf(stderr, "verified partition contents; update may be resumed\n"); + } - pthread_mutex_lock(&nti.mu); - nti.rss = &rss; - pthread_cond_broadcast(&nti.cv); - while (nti.rss) { - pthread_cond_wait(&nti.cv, &nti.mu); - } - pthread_mutex_unlock(&nti.mu); + rc = 0; - blocks_so_far += tgt->size; - fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); - fflush(cmd_pipe); +pbiudone: + if (params.fd != -1) { + if (fsync(params.fd) == -1) { + fprintf(stderr, "fsync failed: %s\n", strerror(errno)); + } + TEMP_FAILURE_RETRY(close(params.fd)); + } - free(tgt); - - } else if (strcmp("bsdiff", style) == 0 || - strcmp("imgdiff", style) == 0) { - word = strtok_r(NULL, " ", &wordsave); - size_t patch_offset = strtoul(word, NULL, 0); - word = strtok_r(NULL, " ", &wordsave); - size_t patch_len = strtoul(word, NULL, 0); - - RangeSet* tgt; - int src_blocks; - if (version == 1) { - LoadSrcTgtVersion1(wordsave, &tgt, &src_blocks, - &buffer, &buffer_alloc, fd); - } else if (version == 2) { - LoadSrcTgtVersion2(wordsave, &tgt, &src_blocks, - &buffer, &buffer_alloc, fd, stash_table); - } + if (logcmd) { + free(logcmd); + } - printf(" patching %d blocks to %d\n", src_blocks, tgt->size); + if (cmdht) { + mzHashTableFree(cmdht); + } - Value patch_value; - patch_value.type = VAL_BLOB; - patch_value.size = patch_len; - patch_value.data = (char*)(patch_start + patch_offset); + if (params.buffer) { + free(params.buffer); + } - RangeSinkState rss; - rss.fd = fd; - rss.tgt = tgt; - rss.p_block = 0; - rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; - check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET); + if (transfer_list) { + free(transfer_list); + } - int ret; - if (style[0] == 'i') { // imgdiff - ret = ApplyImagePatch(buffer, src_blocks * BLOCKSIZE, - &patch_value, - &RangeSinkWrite, &rss, NULL, NULL); - } else { - ret = ApplyBSDiffPatch(buffer, src_blocks * BLOCKSIZE, - &patch_value, 0, - &RangeSinkWrite, &rss, NULL); - } + if (blockdev_filename) { + FreeValue(blockdev_filename); + } - if (ret != 0) { - ErrorAbort(state, "patch failed\n"); - goto done; - } + if (transfer_list_value) { + FreeValue(transfer_list_value); + } - // We expect the output of the patcher to fill the tgt ranges exactly. - if (rss.p_block != tgt->count || rss.p_remain != 0) { - ErrorAbort(state, "range sink underrun?\n"); - goto done; - } + if (new_data_fn) { + FreeValue(new_data_fn); + } - blocks_so_far += tgt->size; - fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); - fflush(cmd_pipe); + if (patch_data_fn) { + FreeValue(patch_data_fn); + } - free(tgt); - } else if (!DEBUG_ERASE && strcmp("erase", style) == 0) { - struct stat st; - if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) { - word = strtok_r(NULL, " ", &wordsave); - RangeSet* tgt = parse_range(word); - - printf(" erasing %d blocks\n", tgt->size); - - for (i = 0; i < tgt->count; ++i) { - uint64_t range[2]; - // offset in bytes - range[0] = tgt->pos[i*2] * (uint64_t)BLOCKSIZE; - // len in bytes - range[1] = (tgt->pos[i*2+1] - tgt->pos[i*2]) * (uint64_t)BLOCKSIZE; - - if (ioctl(fd, BLKDISCARD, &range) < 0) { - ErrorAbort(state, " blkdiscard failed: %s\n", strerror(errno)); - goto done; - } - } + // Only delete the stash if the update cannot be resumed, or it's + // a verification run and we created the stash. + if (params.isunresumable || (!params.canwrite && params.createdstash)) { + DeleteStash(params.stashbase); + } - free(tgt); - } else { - printf(" ignoring erase (not block device)\n"); - } - } else { - ErrorAbort(state, "unknown transfer style \"%s\"\n", style); - goto done; - } + if (params.stashbase) { + free(params.stashbase); } - pthread_join(new_data_thread, NULL); - success = true; + return StringValue(rc == 0 ? strdup("t") : strdup("")); +} - free(buffer); - printf("wrote %d blocks; expected %d\n", blocks_so_far, total_blocks); - printf("max alloc needed was %zu\n", buffer_alloc); +// The transfer list is a text file containing commands to +// transfer data from one place to another on the target +// partition. We parse it and execute the commands in order: +// +// zero [rangeset] +// - fill the indicated blocks with zeros +// +// new [rangeset] +// - fill the blocks with data read from the new_data file +// +// erase [rangeset] +// - mark the given blocks as empty +// +// move <...> +// bsdiff <patchstart> <patchlen> <...> +// imgdiff <patchstart> <patchlen> <...> +// - read the source blocks, apply a patch (or not in the +// case of move), write result to target blocks. bsdiff or +// imgdiff specifies the type of patch; move means no patch +// at all. +// +// The format of <...> differs between versions 1 and 2; +// see the LoadSrcTgtVersion{1,2}() functions for a +// description of what's expected. +// +// stash <stash_id> <src_range> +// - (version 2+ only) load the given source range and stash +// the data in the given slot of the stash table. +// +// The creator of the transfer list will guarantee that no block +// is read (ie, used as the source for a patch or move) after it +// has been written. +// +// In version 2, the creator will guarantee that a given stash is +// loaded (with a stash command) before it's used in a +// move/bsdiff/imgdiff command. +// +// Within one command the source and target ranges may overlap so +// in general we need to read the entire source into memory before +// writing anything to the target blocks. +// +// All the patch data is concatenated into one patch_data file in +// the update package. It must be stored uncompressed because we +// memory-map it in directly from the archive. (Since patches are +// already compressed, we lose very little by not compressing +// their concatenation.) +// +// In version 3, commands that read data from the partition (i.e. +// move/bsdiff/imgdiff/stash) have one or more additional hashes +// before the range parameters, which are used to check if the +// command has already been completed and verify the integrity of +// the source data. + +Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) { + // Commands which are not tested are set to NULL to skip them completely + const Command commands[] = { + { "bsdiff", PerformCommandDiff }, + { "erase", NULL }, + { "free", PerformCommandFree }, + { "imgdiff", PerformCommandDiff }, + { "move", PerformCommandMove }, + { "new", NULL }, + { "stash", PerformCommandStash }, + { "zero", NULL } + }; + + // Perform a dry run without writing to test if an update can proceed + return PerformBlockImageUpdate(name, state, argc, argv, commands, + sizeof(commands) / sizeof(commands[0]), 1); +} -done: - free(transfer_list); - FreeValue(blockdev_filename); - FreeValue(transfer_list_value); - FreeValue(new_data_fn); - FreeValue(patch_data_fn); - if (success) { - return StringValue(strdup("t")); - } else { - // NULL will be passed to its caller at Evaluate() and abort the OTA - // process. - return NULL; - } +Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { + const Command commands[] = { + { "bsdiff", PerformCommandDiff }, + { "erase", PerformCommandErase }, + { "free", PerformCommandFree }, + { "imgdiff", PerformCommandDiff }, + { "move", PerformCommandMove }, + { "new", PerformCommandNew }, + { "stash", PerformCommandStash }, + { "zero", PerformCommandZero } + }; + + return PerformBlockImageUpdate(name, state, argc, argv, commands, + sizeof(commands) / sizeof(commands[0]), 0); } Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { @@ -786,7 +1900,7 @@ Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { int fd = open(blockdev_filename->data, O_RDWR); if (fd < 0) { - ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno)); + ErrorAbort(state, "open \"%s\" failed: %s", blockdev_filename->data, strerror(errno)); goto done; } @@ -798,9 +1912,19 @@ Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { int i, j; for (i = 0; i < rs->count; ++i) { - check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET); + if (check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET) == -1) { + ErrorAbort(state, "failed to seek %s: %s", blockdev_filename->data, + strerror(errno)); + goto done; + } + for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) { - readblock(fd, buffer, BLOCKSIZE); + if (read_all(fd, buffer, BLOCKSIZE) == -1) { + ErrorAbort(state, "failed to read %s: %s", blockdev_filename->data, + strerror(errno)); + goto done; + } + SHA_update(&ctx, buffer, BLOCKSIZE); } } @@ -818,6 +1942,7 @@ done: } void RegisterBlockImageFunctions() { + RegisterFunction("block_image_verify", BlockImageVerifyFn); RegisterFunction("block_image_update", BlockImageUpdateFn); RegisterFunction("range_sha1", RangeSha1Fn); } diff --git a/updater/install.c b/updater/install.c index 2b2ffb0c..9a25abe7 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" @@ -48,6 +49,11 @@ #include "install.h" #include "tune2fs.h" +#include <dirent.h> + +static char bakfiles[PATH_MAX][512]; +static int totalbaks = 0; + #ifdef USE_EXT4 #include "make_ext4fs.h" #include "wipe.h" @@ -165,6 +171,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) { @@ -270,7 +286,7 @@ static int exec_cmd(const char* path, char* const argv[]) { // fs_type="f2fs" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> // if fs_size == 0, then make fs uses the entire partition. // if fs_size > 0, that is the size to use -// if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs") +// if fs_size < 0, then reserve that many bytes at the end of the partition Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; if (argc != 5) { @@ -344,13 +360,18 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { result = location; } else if (strcmp(fs_type, "f2fs") == 0) { char *num_sectors; + char bytes_reserved[10] = {0}; if (asprintf(&num_sectors, "%lld", atoll(fs_size) / 512) <= 0) { printf("format_volume: failed to create %s command for %s\n", fs_type, location); result = strdup(""); goto done; } + if (atoll(num_sectors) <=0) { + snprintf(bytes_reserved, sizeof(bytes_reserved), "%lld", -atoll(fs_size)); + } const char *f2fs_path = "/sbin/mkfs.f2fs"; - const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", location, num_sectors, NULL}; + const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", "-r", bytes_reserved, + location, NULL}; int status = exec_cmd(f2fs_path, (char* const*)f2fs_argv); free(num_sectors); if (status != 0) { @@ -415,6 +436,7 @@ done: Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { char** paths = malloc(argc * sizeof(char*)); int i; + for (i = 0; i < argc; ++i) { paths[i] = Evaluate(state, argv[i]); if (paths[i] == NULL) { @@ -1166,6 +1188,20 @@ Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { return NULL; } + int i; + /* Skip files listed in the backup table */ + for (i=0; i<totalbaks; i++) { + if (!strncmp(source_filename, bakfiles[i],PATH_MAX)) { + fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, + "ui_print Skipping update of modified file %s\n", source_filename); + /* the command pipe tokenizes on \n, so issue an empty ui_print + to do the real line break */ + fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, + "ui_print\n"); + return StringValue(strdup("t")); + } + } + char* endptr; size_t target_size = strtol(target_size_str, &endptr, 10); if (target_size == 0 && endptr == target_size_str) { @@ -1181,7 +1217,6 @@ Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { int patchcount = (argc-4) / 2; Value** patches = ReadValueVarArgs(state, argc-4, argv+4); - int i; for (i = 0; i < patchcount; ++i) { if (patches[i*2]->type != VAL_STRING) { ErrorAbort(state, "%s(): sha-1 #%d is not string", name, i); @@ -1234,12 +1269,30 @@ Value* ApplyPatchCheckFn(const char* name, State* state, return NULL; } + int i=0; + /* Skip files listed in the backup table */ + for (i=0; i<totalbaks; i++) { + if (!strncmp(filename, bakfiles[i],PATH_MAX)) { + /*fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, + "ui_print Skipping update of modified file %s\n", filename);*/ + return StringValue(strdup("t")); + } + } + int patchcount = argc-1; char** sha1s = ReadVarArgs(state, argc-1, argv+1); int result = applypatch_check(filename, patchcount, sha1s); - int i; + if (result == -ENOENT && totalbaks) { + /* File is gone, and we're dealing with a system containing + modified files supported by the CM backup tool. Push it + to the "skippable" list so we don't try to apply it when + the time comes, and return OK to any enclosing asserts */ + sprintf (bakfiles[totalbaks++], "%s", filename); + result = 0; + } + for (i = 0; i < patchcount; ++i) { free(sha1s[i]); } @@ -1280,6 +1333,62 @@ Value* WipeCacheFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(strdup("t")); } +static int collect_backup_data(char *bakpath, char *bakroot) { + DIR *d; + + d = opendir(bakpath); + if (!d) { + /* No backups, go away */ + return 0; + } + while (1) { + struct dirent *entry; + const char *d_name; + entry = readdir(d); + if (!entry) { + break; + } + d_name = entry->d_name; + if (entry->d_type & DT_DIR) { + if (strcmp (d_name, "..") != 0 && + strcmp (d_name, ".") != 0) { + int path_length; + char path[PATH_MAX]; + + path_length = snprintf (path, PATH_MAX, + "%s/%s", bakpath, d_name); + //printf ("%s\n", path); + if (path_length >= PATH_MAX) { + return 1; + } + collect_backup_data(path, bakroot); + } + } else { + char *fspath = strdup(bakpath); + sprintf (bakfiles[totalbaks++], "%s/%s", bakpath+strlen(bakroot), d_name); + } + + } + closedir(d); + + return 0; +} + +Value* CollectBackupDataFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s(): expected at least 1 arg, got %d", + name, argc); + } + + char* bakpath; + if (ReadArgs(state, argv, 1, &bakpath) < 0) { + return NULL; + } + + int ret = collect_backup_data(bakpath, bakpath); + return StringValue(strdup(ret == 0 ? "t" : "")); +} + Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc < 1) { return ErrorAbort(state, "%s() expects at least 1 arg", name); @@ -1416,10 +1525,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 +1545,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 +1560,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 +1595,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 +1624,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 +1746,6 @@ void RegisterInstallFunctions() { RegisterFunction("enable_reboot", EnableRebootFn); RegisterFunction("tune2fs", Tune2FsFn); + + RegisterFunction("collect_backup_data", CollectBackupDataFn); } diff --git a/verifier_test.cpp b/verifier_test.cpp index 10a5ddaa..942564a9 100644 --- a/verifier_test.cpp +++ b/verifier_test.cpp @@ -137,11 +137,22 @@ class FakeUI : public RecoveryUI { vfprintf(stderr, fmt, ap); va_end(ap); } + void ClearLog() {} + 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; } }; void diff --git a/voldclient/Android.mk b/voldclient/Android.mk new file mode 100644 index 00000000..1aee8385 --- /dev/null +++ b/voldclient/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := libvoldclient +LOCAL_SRC_FILES := commands.cpp dispatcher.cpp event_loop.cpp +LOCAL_CFLAGS := -DMINIVOLD -Werror -Wno-unused-parameter +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. \ + system/core/fs_mgr/include \ + system/core/include \ + system/core/libcutils \ + system/vold +LOCAL_MODULE_TAGS := optional +include $(BUILD_STATIC_LIBRARY) diff --git a/voldclient/commands.cpp b/voldclient/commands.cpp new file mode 100644 index 00000000..f37d5594 --- /dev/null +++ b/voldclient/commands.cpp @@ -0,0 +1,198 @@ +/* + * 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 "voldclient.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include "Volume.h" + +#include "common.h" + +int vold_update_volumes() { + + const char *cmd[2] = {"volume", "list"}; + return vold_command(2, cmd, 1); +} + +int vold_mount_volume(const char* path, bool wait) { + + const char *cmd[3] = { "volume", "mount", path }; + int state = vold_get_volume_state(path); + + if (state == Volume::State_Mounted) { + LOGI("Volume %s already mounted\n", path); + return 0; + } + + if (state != Volume::State_Idle) { + LOGI("Volume %s is not idle, current state is %d\n", path, state); + return -1; + } + + if (access(path, R_OK) != 0) { + mkdir(path, 0000); + chown(path, 1000, 1000); + } + return vold_command(3, cmd, wait); +} + +int vold_mount_auto_volume(const char* label, bool wait) { + char path[80]; + sprintf(path, "/storage/%s", label); + const char *cmd[3] = { "volume", "mount", label }; + int state = vold_get_volume_state(path); + + if (state == Volume::State_Mounted) { + LOGI("Volume %s already mounted\n", path); + return 0; + } + + if (state != Volume::State_Idle) { + LOGI("Volume %s is not idle, current state is %d\n", path, state); + return -1; + } + + if (access(path, R_OK) != 0) { + mkdir(path, 0000); + chown(path, 1000, 1000); + } + return vold_command(3, cmd, wait); +} + +int vold_unmount_volume(const char* path, bool force, bool wait, bool detach) { + + const char *cmd[4] = { "volume", "unmount", path, NULL }; + int cmdlen = 3; + int state = vold_get_volume_state(path); + + if (state <= Volume::State_Idle) { + LOGI("Volume %s is not mounted\n", path); + return 0; + } + + if (state != Volume::State_Mounted) { + LOGI("Volume %s cannot be unmounted in state %d\n", path, state); + return -1; + } + + if (force) { + cmd[3] = "force"; + cmdlen = 4; + } + else if (detach) { + cmd[3] = "detach"; + cmdlen = 4; + } + + return vold_command(cmdlen, cmd, wait); +} + +int vold_unmount_auto_volume(const char* label, bool force, bool wait, bool detach) { + + char path[80]; + sprintf(path, "/storage/%s", label); + const char *cmd[4] = { "volume", "unmount", label, NULL }; + int cmdlen = 3; + int state = vold_get_volume_state(path); + + if (state <= Volume::State_Idle) { + LOGI("Volume %s is not mounted\n", path); + return 0; + } + + if (state != Volume::State_Mounted) { + LOGI("Volume %s cannot be unmounted in state %d\n", path, state); + return -1; + } + + if (force) { + cmd[3] = "force"; + cmdlen = 4; + } + else if (detach) { + cmd[3] = "detach"; + cmdlen = 4; + } + + return vold_command(cmdlen, cmd, wait); +} + +int vold_share_volume(const char* path) { + + const char *cmd[4] = { "volume", "share", path, "ums" }; + int state = vold_get_volume_state(path); + + if (state == Volume::State_Mounted) + vold_unmount_volume(path, 0, 1); + + return vold_command(4, cmd, 1); +} + +int vold_unshare_volume(const char* path, bool mount) { + + const char *cmd[4] = { "volume", "unshare", path, "ums" }; + int state = vold_get_volume_state(path); + int ret = 0; + + if (state != Volume::State_Shared) { + LOGE("Volume %s is not shared - state=%d\n", path, state); + return 0; + } + + ret = vold_command(4, cmd, 1); + + if (mount) + vold_mount_volume(path, 1); + + return ret; +} + +int vold_format_volume(const char* path, bool wait) { + + const char* cmd[3] = { "volume", "format", path }; + return vold_command(3, cmd, wait); +} + +const char* volume_state_to_string(int state) { + if (state == Volume::State_Init) + return "Initializing"; + else if (state == Volume::State_NoMedia) + return "No-Media"; + else if (state == Volume::State_Idle) + return "Idle-Unmounted"; + else if (state == Volume::State_Pending) + return "Pending"; + else if (state == Volume::State_Mounted) + return "Mounted"; + else if (state == Volume::State_Unmounting) + return "Unmounting"; + else if (state == Volume::State_Checking) + return "Checking"; + else if (state == Volume::State_Formatting) + return "Formatting"; + else if (state == Volume::State_Shared) + return "Shared-Unmounted"; + else if (state == Volume::State_SharedMnt) + return "Shared-Mounted"; + else + return "Unknown-Error"; +} diff --git a/voldclient/dispatcher.cpp b/voldclient/dispatcher.cpp new file mode 100644 index 00000000..7520c4f8 --- /dev/null +++ b/voldclient/dispatcher.cpp @@ -0,0 +1,232 @@ +/* + * 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 "voldclient.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> + +#include "ResponseCode.h" +#include "Volume.h" + +#include "common.h" + +static struct vold_callbacks* callbacks = NULL; +static int should_automount = 0; + +struct volume_node { + const char *label; + const char *path; + int state; + struct volume_node *next; +}; + +static struct volume_node *volume_head = NULL; +static struct volume_node *volume_tail = NULL; + +static int num_volumes = 0; + +static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + +void vold_set_callbacks(struct vold_callbacks* ev_callbacks) { + callbacks = ev_callbacks; +} + +void vold_set_automount(int automount) { + should_automount = automount; +} + +void vold_mount_all() { + + struct volume_node *node; + + pthread_rwlock_rdlock(&rwlock); + for (node = volume_head; node; node = node->next) { + if (node->state == Volume::State_Idle) { + vold_mount_volume(node->path, false); + } + } + pthread_rwlock_unlock(&rwlock); +} + +void vold_unmount_all() { + + struct volume_node *node; + + pthread_rwlock_rdlock(&rwlock); + for (node = volume_head; node; node = node->next) { + if (node->state >= Volume::State_Shared) { + vold_unshare_volume(node->path, false); + } + if (node->state == Volume::State_Mounted) { + vold_unmount_volume(node->path, true, true); + } + } + pthread_rwlock_unlock(&rwlock); +} + +int vold_get_volume_state(const char *path) { + + int ret = 0; + struct volume_node *node; + + pthread_rwlock_rdlock(&rwlock); + for (node = volume_head; node; node = node->next) { + if (strcmp(path, node->path) == 0) { + ret = node->state; + break; + } + } + pthread_rwlock_unlock(&rwlock); + return ret; +} + +int vold_get_num_volumes() { + return num_volumes; +} + +int vold_is_volume_available(const char *path) { + return vold_get_volume_state(path) > 0; +} + +static void free_volume_list_locked() { + + struct volume_node *node; + + node = volume_head; + while (node) { + struct volume_node *next = node->next; + free((void *)node->path); + free((void *)node->label); + free(node); + node = next; + } + volume_head = volume_tail = NULL; +} + +static int is_listing_volumes = 0; + +static void vold_handle_volume_list(const char* label, const char* path, int state) { + + struct volume_node *node; + + pthread_rwlock_wrlock(&rwlock); + if (is_listing_volumes == 0) { + free_volume_list_locked(); + num_volumes = 0; + is_listing_volumes = 1; + } + + node = (struct volume_node *)malloc(sizeof(struct volume_node)); + node->label = strdup(label); + node->path = strdup(path); + node->state = state; + node->next = NULL; + + if (volume_head == NULL) + volume_head = volume_tail = node; + else { + volume_tail->next = node; + volume_tail = node; + } + + num_volumes++; + pthread_rwlock_unlock(&rwlock); +} + +static void vold_handle_volume_list_done() { + + pthread_rwlock_wrlock(&rwlock); + is_listing_volumes = 0; + pthread_rwlock_unlock(&rwlock); +} + +static void set_volume_state(char* path, int state) { + + struct volume_node *node; + + pthread_rwlock_rdlock(&rwlock); + for (node = volume_head; node; node = node->next) { + if (strcmp(node->path, path) == 0) { + node->state = state; + break; + } + } + pthread_rwlock_unlock(&rwlock); +} + +static void vold_handle_volume_state_change(char* label, char* path, int state) { + + set_volume_state(path, state); + + if (callbacks != NULL && callbacks->state_changed != NULL) + callbacks->state_changed(label, path, state); +} + +static void vold_handle_volume_inserted(char* label, char* path) { + + set_volume_state(path, Volume::State_Idle); + + if (callbacks != NULL && callbacks->disk_added != NULL) + callbacks->disk_added(label, path); + + if (should_automount) + vold_mount_volume(path, false); +} + +static void vold_handle_volume_removed(char* label, char* path) { + + set_volume_state(path, Volume::State_NoMedia); + + if (callbacks != NULL && callbacks->disk_removed != NULL) + callbacks->disk_removed(label, path); +} + +int vold_dispatch(int code, char** tokens, int len) { + + int i = 0; + int ret = 0; + + if (code == ResponseCode::VolumeListResult) { + // <code> <seq> <label> <path> <state> + vold_handle_volume_list(tokens[2], tokens[3], atoi(tokens[4])); + + } else if (code == ResponseCode::VolumeStateChange) { + // <code> "Volume <label> <path> state changed from <old_#> (<old_str>) to <new_#> (<new_str>)" + vold_handle_volume_state_change(tokens[2], tokens[3], atoi(tokens[10])); + + } else if (code == ResponseCode::VolumeDiskInserted) { + // <code> Volume <label> <path> disk inserted (<blk_id>)" + vold_handle_volume_inserted(tokens[2], tokens[3]); + + } else if (code == ResponseCode::VolumeDiskRemoved || code == ResponseCode::VolumeBadRemoval) { + // <code> Volume <label> <path> disk removed (<blk_id>)" + vold_handle_volume_removed(tokens[2], tokens[3]); + + } else if (code == ResponseCode::CommandOkay && is_listing_volumes) { + vold_handle_volume_list_done(); + + } else { + ret = 0; + } + + return ret; +} + diff --git a/voldclient/event_loop.cpp b/voldclient/event_loop.cpp new file mode 100644 index 00000000..fdd827ca --- /dev/null +++ b/voldclient/event_loop.cpp @@ -0,0 +1,282 @@ +/* + * 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 "voldclient.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <pthread.h> + +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/un.h> + +#include <cutils/sockets.h> +#include <private/android_filesystem_config.h> + +#include "ResponseCode.h" + +#include "common.h" + +// locking +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t completion = PTHREAD_COND_INITIALIZER; + +// command result status, read with mutex held +static int cmd_result = 0; + +// commands currently in flight +static int cmd_inflight = 0; + +// socket fd +static int sock = -1; + + +static int vold_connect() { + + int retries = 5; + int ret = -1; + if (sock > 0) { + return 1; + } + + // socket connection to vold + while (retries > 0) { + if ((sock = socket_local_client("vold", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM)) >= 0) { + LOGI("Connected to Vold..\n"); + return 1; + } + sleep(1); + retries--; + } + + LOGE("Error connecting to Vold! (%s)\n", strerror(errno)); + return ret; +} + +static int split(char *str, char **splitstr) { + + char *p; + int i = 0; + + p = strtok(str, " "); + + while(p != NULL) { + splitstr[i] = (char *)malloc(strlen(p) + 1); + if (splitstr[i]) + strcpy(splitstr[i], p); + i++; + p = strtok (NULL, " "); + } + + return i; +} + +extern int vold_dispatch(int code, char** tokens, int len); + +static int handle_response(char* response) { + + int code = 0, len = 0, i = 0; + char *tokens[32] = { NULL }; + + len = split(response, tokens); + code = atoi(tokens[0]); + + if (len) { + vold_dispatch(code, tokens, len); + + for (i = 0; i < len; i++) + free(tokens[i]); + } + + return code; +} + +static int monitor_started = 0; + +// wait for events and signal waiters when appropriate +static int monitor() { + + char *buffer = (char *)malloc(4096); + int code = 0; + + while(1) { + fd_set read_fds; + struct timeval to; + int rc = 0; + + to.tv_sec = 10; + to.tv_usec = 0; + + FD_ZERO(&read_fds); + FD_SET(sock, &read_fds); + + if (!monitor_started) { + pthread_mutex_lock(&mutex); + monitor_started = 1; + pthread_cond_signal(&completion); + pthread_mutex_unlock(&mutex); + } + + if ((rc = select(sock +1, &read_fds, NULL, NULL, &to)) < 0) { + LOGE("Error in select (%s)\n", strerror(errno)); + goto out; + + } else if (!rc) { + continue; + + } else if (FD_ISSET(sock, &read_fds)) { + memset(buffer, 0, 4096); + if ((rc = read(sock, buffer, 4096)) <= 0) { + if (rc == 0) + LOGE("Lost connection to Vold - did it crash?\n"); + else + LOGE("Error reading data (%s)\n", strerror(errno)); + if (rc == 0) + return ECONNRESET; + goto out; + } + + int offset = 0; + int i = 0; + + // dispatch each line of the response + for (i = 0; i < rc; i++) { + if (buffer[i] == '\0') { + + LOGI("%s\n", buffer + offset); + code = handle_response(strdup(buffer + offset)); + + if (code >= 200 && code < 600) { + pthread_mutex_lock(&mutex); + cmd_result = code; + cmd_inflight--; + pthread_cond_signal(&completion); + pthread_mutex_unlock(&mutex); + } + offset = i + 1; + } + } + } + } +out: + free(buffer); + pthread_mutex_unlock(&mutex); + return code; +} + +static void *event_thread_func(void* v) { + + // if monitor() returns, it means we lost the connection to vold + while (1) { + + if (vold_connect()) { + monitor(); + + if (sock) + close(sock); + } + sleep(3); + } + return NULL; +} + +extern void vold_set_callbacks(struct vold_callbacks* callbacks); +extern void vold_set_automount(int automount); + +// start the client thread +void vold_client_start(struct vold_callbacks* callbacks, int automount) { + + if (sock > 0) { + return; + } + + pthread_mutex_lock(&mutex); + + vold_set_callbacks(callbacks); + + pthread_t vold_event_thread; + pthread_create(&vold_event_thread, NULL, &event_thread_func, NULL); + pthread_cond_wait(&completion, &mutex); + pthread_mutex_unlock(&mutex); + + vold_update_volumes(); + + if (automount) { + vold_mount_all(); + } + vold_set_automount(automount); +} + +// send a command to vold. waits for completion and returns result +// code if wait is 1, otherwise returns zero immediately. +int vold_command(int len, const char** command, int wait) { + + char final_cmd[255] = "0 "; /* 0 is a (now required) sequence number */ + int i; + size_t sz; + int ret = 0; + + if (!vold_connect()) { + return -1; + } + + for (i = 0; i < len; i++) { + char *cmp; + + if (!strchr(command[i], ' ')) + asprintf(&cmp, "%s%s", command[i], (i == (len -1)) ? "" : " "); + else + asprintf(&cmp, "\"%s\"%s", command[i], (i == (len -1)) ? "" : " "); + + sz = strlcat(final_cmd, cmp, sizeof(final_cmd)); + + if (sz >= sizeof(final_cmd)) { + LOGE("command syntax error sz=%d size=%d", sz, sizeof(final_cmd)); + free(cmp); + return -1; + } + free(cmp); + } + + // only one writer at a time + pthread_mutex_lock(&mutex); + if (write(sock, final_cmd, strlen(final_cmd) + 1) < 0) { + LOGE("Unable to send command to vold!\n"); + ret = -1; + } + cmd_inflight++; + + if (wait) { + while (cmd_inflight) { + // wait for completion + pthread_cond_wait(&completion, &mutex); + ret = cmd_result; + } + } + pthread_mutex_unlock(&mutex); + + return ret == ResponseCode::CommandOkay ? 0 : -1; +} diff --git a/voldclient/voldclient.h b/voldclient/voldclient.h new file mode 100644 index 00000000..6e730114 --- /dev/null +++ b/voldclient/voldclient.h @@ -0,0 +1,53 @@ +/* + * 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 <linux/kdev_t.h> + +int vold_mount_volume(const char* path, bool wait); +int vold_mount_auto_volume(const char* label, bool wait); +int vold_unmount_volume(const char* path, bool force, bool wait, bool detach=false); +int vold_unmount_auto_volume(const char* label, bool force, bool wait, bool detach=false); + +int vold_share_volume(const char* path); +int vold_unshare_volume(const char* path, bool remount); + +int vold_format_volume(const char* path, bool wait); + +int vold_is_volume_available(const char* path); +int vold_get_volume_state(const char* path); + +int vold_update_volumes(); +int vold_get_num_volumes(); +void vold_mount_all(); +void vold_unmount_all(); + +struct vold_callbacks { + int (*state_changed)(char* label, char* path, int state); + int (*disk_added)(char* label, char* path); + int (*disk_removed)(char* label, char* path); +}; + +void vold_client_start(struct vold_callbacks* callbacks, int automount); +void vold_set_automount(int enabled); +int vold_command(int len, const char** command, int wait); + +const char* volume_state_to_string(int state); + +#endif + |