aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk199
-rw-r--r--CleanSpec.mk1
-rw-r--r--adb_install.cpp125
-rw-r--r--adb_install.h4
-rw-r--r--applypatch/Android.mk6
-rw-r--r--applypatch/applypatch.c9
-rw-r--r--applypatch/imgdiff.c9
-rw-r--r--backup.cpp298
-rw-r--r--bootloader.cpp36
-rw-r--r--bu.cpp389
-rw-r--r--bu.h61
-rw-r--r--common.h2
-rw-r--r--default_device.cpp79
-rw-r--r--device.h12
-rw-r--r--etc/init.rc47
-rw-r--r--fuse_sdcard_provider.c51
-rw-r--r--fuse_sideload.c153
-rw-r--r--fuse_sideload.h2
-rw-r--r--install.cpp72
-rw-r--r--install.h2
-rw-r--r--messagesocket.cpp116
-rw-r--r--messagesocket.h40
-rw-r--r--minadbd/Android.mk2
-rw-r--r--minui/Android.mk49
-rw-r--r--minui/events.c21
-rw-r--r--minui/font_10x18.h214
-rw-r--r--minui/graphics.c197
-rw-r--r--minui/graphics_adf.c4
-rw-r--r--minui/graphics_fbdev.c19
-rw-r--r--minui/minui.h14
-rw-r--r--minui/resources.c41
-rw-r--r--minui/roboto_10x18.h197
-rw-r--r--minui/roboto_15x24.h281
-rw-r--r--minui/roboto_23x41.h461
-rw-r--r--minzip/Android.mk4
-rw-r--r--minzip/Zip.c84
-rw-r--r--minzip/Zip.h12
-rw-r--r--mtdutils/mounts.c21
-rw-r--r--mtdutils/mounts.h1
-rw-r--r--mtdutils/mtdutils.c16
-rw-r--r--recovery.cpp549
-rw-r--r--recovery_cmds.h81
-rw-r--r--res-hdpi/images/font_log.pngbin0 -> 13408 bytes
-rw-r--r--res-hdpi/images/font_menu.pngbin0 -> 22354 bytes
-rw-r--r--res-hdpi/images/icon_header.pngbin0 -> 3288 bytes
-rw-r--r--res-hdpi/images/icon_headless.pngbin0 -> 1155 bytes
-rw-r--r--res-hdpi/images/icon_info.pngbin0 -> 3745 bytes
-rw-r--r--res-mdpi/images/font_log.pngbin0 -> 7287 bytes
-rw-r--r--res-mdpi/images/font_menu.pngbin0 -> 13408 bytes
-rw-r--r--res-mdpi/images/icon_header.pngbin0 -> 2191 bytes
-rw-r--r--res-mdpi/images/icon_headless.pngbin0 -> 734 bytes
-rw-r--r--res-mdpi/images/icon_info.pngbin0 -> 3745 bytes
-rw-r--r--res-xhdpi/images/font_log.pngbin0 -> 22354 bytes
-rw-r--r--res-xhdpi/images/font_menu.pngbin0 -> 17287 bytes
-rw-r--r--res-xhdpi/images/icon_header.pngbin0 -> 5579 bytes
-rw-r--r--res-xhdpi/images/icon_headless.pngbin0 -> 1536 bytes
-rw-r--r--res-xhdpi/images/icon_info.pngbin0 -> 3745 bytes
-rw-r--r--res-xxhdpi/images/font_log.pngbin0 -> 17287 bytes
-rw-r--r--res-xxhdpi/images/font_menu.pngbin0 -> 65852 bytes
-rw-r--r--res-xxhdpi/images/icon_header.pngbin0 -> 8357 bytes
-rw-r--r--res-xxhdpi/images/icon_headless.pngbin0 -> 2395 bytes
-rw-r--r--res-xxhdpi/images/icon_info.pngbin0 -> 3745 bytes
-rw-r--r--res-xxxhdpi/images/font_log.pngbin0 -> 60087 bytes
-rw-r--r--res-xxxhdpi/images/font_menu.pngbin0 -> 101284 bytes
-rw-r--r--res-xxxhdpi/images/icon_header.pngbin0 -> 11760 bytes
-rw-r--r--res-xxxhdpi/images/icon_headless.pngbin0 -> 3300 bytes
-rw-r--r--res-xxxhdpi/images/icon_info.pngbin0 -> 3745 bytes
-rw-r--r--restore.cpp305
-rw-r--r--roots.cpp451
-rw-r--r--roots.h32
-rw-r--r--screen_ui.cpp457
-rw-r--r--screen_ui.h55
-rw-r--r--ui.cpp613
-rw-r--r--ui.h111
-rw-r--r--updater/Android.mk29
-rw-r--r--updater/blockimg.c1847
-rw-r--r--updater/install.c142
-rw-r--r--verifier_test.cpp13
-rw-r--r--voldclient/Android.mk14
-rw-r--r--voldclient/commands.cpp198
-rw-r--r--voldclient/dispatcher.cpp232
-rw-r--r--voldclient/event_loop.cpp282
-rw-r--r--voldclient/voldclient.h53
83 files changed, 7707 insertions, 1108 deletions
diff --git a/Android.mk b/Android.mk
index 5a1f479f..48ea33af 100644
--- a/Android.mk
+++ b/Android.mk
@@ -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));
diff --git a/bu.cpp b/bu.cpp
new file mode 100644
index 00000000..83b89020
--- /dev/null
+++ b/bu.cpp
@@ -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;
+}
diff --git a/bu.h b/bu.h
new file mode 100644
index 00000000..a751af0b
--- /dev/null
+++ b/bu.h
@@ -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);
diff --git a/common.h b/common.h
index 768f499f..4a80c0ab 100644
--- a/common.h
+++ b/common.h
@@ -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; }
diff --git a/device.h b/device.h
index 8ff4ec03..d1c23e1f 100644
--- a/device.h
+++ b/device.h
@@ -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");
+}
diff --git a/install.h b/install.h
index 53c0d312..5478a115 100644
--- a/install.h
+++ b/install.h
@@ -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
new file mode 100644
index 00000000..59b990ae
--- /dev/null
+++ b/res-hdpi/images/font_log.png
Binary files differ
diff --git a/res-hdpi/images/font_menu.png b/res-hdpi/images/font_menu.png
new file mode 100644
index 00000000..d1aad6da
--- /dev/null
+++ b/res-hdpi/images/font_menu.png
Binary files differ
diff --git a/res-hdpi/images/icon_header.png b/res-hdpi/images/icon_header.png
new file mode 100644
index 00000000..634489f7
--- /dev/null
+++ b/res-hdpi/images/icon_header.png
Binary files differ
diff --git a/res-hdpi/images/icon_headless.png b/res-hdpi/images/icon_headless.png
new file mode 100644
index 00000000..780836f3
--- /dev/null
+++ b/res-hdpi/images/icon_headless.png
Binary files differ
diff --git a/res-hdpi/images/icon_info.png b/res-hdpi/images/icon_info.png
new file mode 100644
index 00000000..39939418
--- /dev/null
+++ b/res-hdpi/images/icon_info.png
Binary files differ
diff --git a/res-mdpi/images/font_log.png b/res-mdpi/images/font_log.png
new file mode 100644
index 00000000..bcbe242d
--- /dev/null
+++ b/res-mdpi/images/font_log.png
Binary files differ
diff --git a/res-mdpi/images/font_menu.png b/res-mdpi/images/font_menu.png
new file mode 100644
index 00000000..59b990ae
--- /dev/null
+++ b/res-mdpi/images/font_menu.png
Binary files differ
diff --git a/res-mdpi/images/icon_header.png b/res-mdpi/images/icon_header.png
new file mode 100644
index 00000000..4ddb812b
--- /dev/null
+++ b/res-mdpi/images/icon_header.png
Binary files differ
diff --git a/res-mdpi/images/icon_headless.png b/res-mdpi/images/icon_headless.png
new file mode 100644
index 00000000..5d134cd7
--- /dev/null
+++ b/res-mdpi/images/icon_headless.png
Binary files differ
diff --git a/res-mdpi/images/icon_info.png b/res-mdpi/images/icon_info.png
new file mode 100644
index 00000000..39939418
--- /dev/null
+++ b/res-mdpi/images/icon_info.png
Binary files differ
diff --git a/res-xhdpi/images/font_log.png b/res-xhdpi/images/font_log.png
new file mode 100644
index 00000000..d1aad6da
--- /dev/null
+++ b/res-xhdpi/images/font_log.png
Binary files differ
diff --git a/res-xhdpi/images/font_menu.png b/res-xhdpi/images/font_menu.png
new file mode 100644
index 00000000..6aead738
--- /dev/null
+++ b/res-xhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xhdpi/images/icon_header.png b/res-xhdpi/images/icon_header.png
new file mode 100644
index 00000000..3254528d
--- /dev/null
+++ b/res-xhdpi/images/icon_header.png
Binary files differ
diff --git a/res-xhdpi/images/icon_headless.png b/res-xhdpi/images/icon_headless.png
new file mode 100644
index 00000000..af283e23
--- /dev/null
+++ b/res-xhdpi/images/icon_headless.png
Binary files differ
diff --git a/res-xhdpi/images/icon_info.png b/res-xhdpi/images/icon_info.png
new file mode 100644
index 00000000..39939418
--- /dev/null
+++ b/res-xhdpi/images/icon_info.png
Binary files differ
diff --git a/res-xxhdpi/images/font_log.png b/res-xxhdpi/images/font_log.png
new file mode 100644
index 00000000..6aead738
--- /dev/null
+++ b/res-xxhdpi/images/font_log.png
Binary files differ
diff --git a/res-xxhdpi/images/font_menu.png b/res-xxhdpi/images/font_menu.png
new file mode 100644
index 00000000..f3b54b38
--- /dev/null
+++ b/res-xxhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xxhdpi/images/icon_header.png b/res-xxhdpi/images/icon_header.png
new file mode 100644
index 00000000..4b68cbcc
--- /dev/null
+++ b/res-xxhdpi/images/icon_header.png
Binary files differ
diff --git a/res-xxhdpi/images/icon_headless.png b/res-xxhdpi/images/icon_headless.png
new file mode 100644
index 00000000..09a82340
--- /dev/null
+++ b/res-xxhdpi/images/icon_headless.png
Binary files differ
diff --git a/res-xxhdpi/images/icon_info.png b/res-xxhdpi/images/icon_info.png
new file mode 100644
index 00000000..39939418
--- /dev/null
+++ b/res-xxhdpi/images/icon_info.png
Binary files differ
diff --git a/res-xxxhdpi/images/font_log.png b/res-xxxhdpi/images/font_log.png
new file mode 100644
index 00000000..ec8d899b
--- /dev/null
+++ b/res-xxxhdpi/images/font_log.png
Binary files differ
diff --git a/res-xxxhdpi/images/font_menu.png b/res-xxxhdpi/images/font_menu.png
new file mode 100644
index 00000000..d7c326ad
--- /dev/null
+++ b/res-xxxhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xxxhdpi/images/icon_header.png b/res-xxxhdpi/images/icon_header.png
new file mode 100644
index 00000000..b4aebd7a
--- /dev/null
+++ b/res-xxxhdpi/images/icon_header.png
Binary files differ
diff --git a/res-xxxhdpi/images/icon_headless.png b/res-xxxhdpi/images/icon_headless.png
new file mode 100644
index 00000000..a715e3ff
--- /dev/null
+++ b/res-xxxhdpi/images/icon_headless.png
Binary files differ
diff --git a/res-xxxhdpi/images/icon_info.png b/res-xxxhdpi/images/icon_info.png
new file mode 100644
index 00000000..39939418
--- /dev/null
+++ b/res-xxxhdpi/images/icon_info.png
Binary files differ
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;
+}
+
diff --git a/roots.cpp b/roots.cpp
index ee140160..07b46697 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -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;
}
diff --git a/roots.h b/roots.h
index 230d9ded..ed9f9369 100644
--- a/roots.h
+++ b/roots.h
@@ -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
diff --git a/ui.cpp b/ui.cpp
index c8f08cdd..58354db2 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -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;
+}
diff --git a/ui.h b/ui.h
index 31a8a7fb..e50c8589 100644
--- a/ui.h
+++ b/ui.h
@@ -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, " ", &params->cpos);
+
+ if (srchash == NULL) {
+ fprintf(stderr, "missing source hash\n");
+ goto v3out;
+ }
+
+ if (onehash) {
+ tgthash = srchash;
+ } else {
+ tgthash = strtok_r(NULL, " ", &params->cpos);
+
+ if (tgthash == NULL) {
+ fprintf(stderr, "missing target hash\n");
+ goto v3out;
+ }
+ }
+
+ if (LoadSrcTgtVersion2(&params->cpos, tgt, src_blocks, &params->buffer, &params->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, &params->buffer,
+ &params->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(&params->cpos, &tgt, &blocks, &params->buffer,
+ &params->bufsize, params->fd);
+ } else if (params->version == 2) {
+ status = LoadSrcTgtVersion2(&params->cpos, &tgt, &blocks, &params->buffer,
+ &params->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, &params->cpos, &params->buffer, &params->bufsize,
+ params->fd, (params->version >= 3), &params->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, " ", &params->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, &params->buffer, &params->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, " ", &params->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(&params->nti.mu);
+ params->nti.rss = &rss;
+ pthread_cond_broadcast(&params->nti.cv);
+
+ while (params->nti.rss) {
+ pthread_cond_wait(&params->nti.cv, &params->nti.mu);
+ }
+
+ pthread_mutex_unlock(&params->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, " ", &params->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, " ", &params->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(&params->cpos, &tgt, &blocks, &params->buffer,
+ &params->bufsize, params->fd);
+ } else if (params->version == 2) {
+ status = LoadSrcTgtVersion2(&params->cpos, &tgt, &blocks, &params->buffer,
+ &params->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, " ", &params->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(&params, 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(&params.nti.mu, NULL);
+ pthread_cond_init(&params.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(&params.thread, &attr, unzip_new_data, &params.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,
+ &params.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, " ", &params.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(&params) == -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
+