diff --git a/Android.mk b/Android.mk index 282862fc9..b1b513361 100644 --- a/Android.mk +++ b/Android.mk @@ -1,7 +1,11 @@ +ifeq ($(call my-dir),$(call project-path-for,recovery)) + LOCAL_PATH := $(call my-dir) + include $(CLEAR_VARS) commands_recovery_local_path := $(LOCAL_PATH) +# LOCAL_CPP_EXTENSION := .c LOCAL_SRC_FILES := \ recovery.c \ @@ -9,21 +13,94 @@ LOCAL_SRC_FILES := \ install.c \ roots.c \ ui.c \ - verifier.c + mounts.c \ + extendedcommands.c \ + nandroid.c \ + nandroid_md5.c \ + reboot.c \ + ../../system/core/toolbox/dynarray.c \ + ../../system/core/toolbox/newfs_msdos.c \ + firmware.c \ + edifyscripting.c \ + prop.c \ + adb_install.c \ + verifier.c \ + ../../system/vold/vdc.c \ + propsrvc/legacy_property_service.c + +ADDITIONAL_RECOVERY_FILES := $(shell echo $$ADDITIONAL_RECOVERY_FILES) +LOCAL_SRC_FILES += $(ADDITIONAL_RECOVERY_FILES) + +ifndef BOARD_TOUCH_RECOVERY +ifdef BOARD_RECOVERY_SWIPE +LOCAL_SRC_FILES += input/touch.c +endif +endif LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -RECOVERY_API_VERSION := 3 +RECOVERY_FSTAB_VERSION := 2 + +ifdef I_AM_KOUSH +RECOVERY_NAME := ClockworkMod Recovery +LOCAL_CFLAGS += -DI_AM_KOUSH +else +ifndef RECOVERY_NAME +RECOVERY_NAME := CWM-based Recovery +endif +endif + +RECOVERY_VERSION := $(RECOVERY_NAME) v6.0.5.1 + +LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" +RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) +ifdef BOARD_TOUCH_RECOVERY +ifeq ($(BOARD_USE_CUSTOM_RECOVERY_FONT),) + BOARD_USE_CUSTOM_RECOVERY_FONT := \"roboto_15x24.h\" +endif +endif + +ifeq ($(BOARD_USE_CUSTOM_RECOVERY_FONT),) + BOARD_USE_CUSTOM_RECOVERY_FONT := \"font_10x18.h\" +endif + +BOARD_RECOVERY_CHAR_WIDTH := $(shell echo $(BOARD_USE_CUSTOM_RECOVERY_FONT) | cut -d _ -f 2 | cut -d . -f 1 | cut -d x -f 1) +BOARD_RECOVERY_CHAR_HEIGHT := $(shell echo $(BOARD_USE_CUSTOM_RECOVERY_FONT) | cut -d _ -f 2 | cut -d . -f 1 | cut -d x -f 2) + +LOCAL_CFLAGS += -DBOARD_RECOVERY_CHAR_WIDTH=$(BOARD_RECOVERY_CHAR_WIDTH) -DBOARD_RECOVERY_CHAR_HEIGHT=$(BOARD_RECOVERY_CHAR_HEIGHT) + +BOARD_RECOVERY_DEFINES := BOARD_HAS_NO_SELECT_BUTTON BOARD_UMS_LUNFILE BOARD_RECOVERY_ALWAYS_WIPES BOARD_RECOVERY_HANDLES_MOUNT BOARD_TOUCH_RECOVERY RECOVERY_EXTEND_NANDROID_MENU TARGET_USE_CUSTOM_LUN_FILE_PATH TARGET_DEVICE TARGET_RECOVERY_FSTAB BOARD_NATIVE_DUALBOOT BOARD_NATIVE_DUALBOOT_SINGLEDATA + +ifndef BOARD_TOUCH_RECOVERY +BOARD_RECOVERY_DEFINES += BOARD_RECOVERY_SWIPE BOARD_RECOVERY_SWIPE_SWAPXY +endif + +$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \ + $(if $($(board_define)), \ + $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \ + ) \ + ) + +ifneq ($(BOARD_RECOVERY_BLDRMSG_OFFSET),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_BLDRMSG_OFFSET=$(BOARD_RECOVERY_BLDRMSG_OFFSET) +endif + LOCAL_STATIC_LIBRARIES := -ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) -LOCAL_CFLAGS += -DUSE_EXT4 -LOCAL_C_INCLUDES += system/extras/ext4_utils -LOCAL_STATIC_LIBRARIES += libext4_utils libz +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/openssl/include + +LOCAL_STATIC_LIBRARIES += libext4_utils_static libz libsparse_static + +ifeq ($(ENABLE_LOKI_RECOVERY),true) + LOCAL_CFLAGS += -DENABLE_LOKI + LOCAL_STATIC_LIBRARIES += libloki_static + LOCAL_SRC_FILES += loki/loki_recovery.c endif # This binary is in the recovery ramdisk, which is otherwise a copy of root. @@ -33,25 +110,108 @@ endif LOCAL_MODULE_TAGS := eng -ifeq ($(TARGET_RECOVERY_UI_LIB),) +ifeq ($(BOARD_CUSTOM_RECOVERY_KEYMAPPING),) + LOCAL_SRC_FILES += default_recovery_keys.c +else + LOCAL_SRC_FILES += $(BOARD_CUSTOM_RECOVERY_KEYMAPPING) +endif + +ifeq ($(BOARD_CUSTOM_RECOVERY_UI),) LOCAL_SRC_FILES += default_recovery_ui.c else - LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) + LOCAL_SRC_FILES += $(BOARD_CUSTOM_RECOVERY_UI) +endif + +LOCAL_STATIC_LIBRARIES += libvoldclient libsdcard libminipigz libfsck_msdos +LOCAL_STATIC_LIBRARIES += libmake_ext4fs libext4_utils_static libz libsparse_static + +ifeq ($(TARGET_USERIMAGES_USE_F2FS), true) +LOCAL_CFLAGS += -DUSE_F2FS +LOCAL_STATIC_LIBRARIES += libmake_f2fs libfsck_f2fs libfibmap_f2fs endif -LOCAL_STATIC_LIBRARIES += libext4_utils libz -LOCAL_STATIC_LIBRARIES += libminzip libunz libmtdutils libmincrypt -LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils + +LOCAL_STATIC_LIBRARIES += libminzip libunz libmincrypt + +LOCAL_STATIC_LIBRARIES += libminizip libminadbd libedify libbusybox libmkyaffs2image libunyaffs liberase_image libdump_image libflash_image +LOCAL_LDFLAGS += -Wl,--no-fatal-warnings + +LOCAL_STATIC_LIBRARIES += libfs_mgr libdedupe libcrypto_static libcrecovery libflashutils libmtdutils libmmcutils libbmlutils + +ifeq ($(BOARD_USES_BML_OVER_MTD),true) +LOCAL_STATIC_LIBRARIES += libbml_over_mtd +endif + +LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils liblog libutils LOCAL_STATIC_LIBRARIES += libstdc++ libc -LOCAL_C_INCLUDES += system/extras/ext4_utils +LOCAL_STATIC_LIBRARIES += libselinux +LOCAL_STATIC_LIBRARIES += libcrypto_static + +RECOVERY_LINKS := bu make_ext4fs edify busybox flash_image dump_image mkyaffs2image unyaffs erase_image nandroid reboot volume setprop getprop start stop dedupe minizip setup_adbd fsck_msdos newfs_msdos vdc sdcard pigz + +ifeq ($(TARGET_USERIMAGES_USE_F2FS), true) +RECOVERY_LINKS += mkfs.f2fs fsck.f2fs fibmap.f2fs +endif + +# nc is provided by external/netcat +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)))) + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + killrecovery.sh \ + parted \ + sdparted \ + su.recovery \ + install-su.sh \ + run-su-daemon.sh + +LOCAL_ADDITIONAL_DEPENDENCIES += \ + minivold \ + recovery_e2fsck \ + recovery_mke2fs \ + recovery_tune2fs \ + mount.exfat_static + +LOCAL_ADDITIONAL_DEPENDENCIES += $(RECOVERY_SYMLINKS) $(RECOVERY_BUSYBOX_SYMLINKS) + +ifneq ($(TARGET_RECOVERY_DEVICE_MODULES),) + LOCAL_ADDITIONAL_DEPENDENCIES += $(TARGET_RECOVERY_DEVICE_MODULES) +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 := killrecovery.sh +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := killrecovery.sh +include $(BUILD_PREBUILT) include $(CLEAR_VARS) LOCAL_SRC_FILES := verifier_test.c verifier.c +LOCAL_C_INCLUDES += system/extras/ext4_utils system/core/fs_mgr/include + LOCAL_MODULE := verifier_test LOCAL_FORCE_STATIC_EXECUTABLE := true @@ -62,13 +222,24 @@ LOCAL_STATIC_LIBRARIES := libmincrypt libcutils libstdc++ libc include $(BUILD_EXECUTABLE) - +include $(commands_recovery_local_path)/bmlutils/Android.mk +include $(commands_recovery_local_path)/dedupe/Android.mk +include $(commands_recovery_local_path)/flashutils/Android.mk +include $(commands_recovery_local_path)/libcrecovery/Android.mk include $(commands_recovery_local_path)/minui/Android.mk include $(commands_recovery_local_path)/minelf/Android.mk include $(commands_recovery_local_path)/minzip/Android.mk +include $(commands_recovery_local_path)/minadbd/Android.mk include $(commands_recovery_local_path)/mtdutils/Android.mk +include $(commands_recovery_local_path)/mmcutils/Android.mk include $(commands_recovery_local_path)/tools/Android.mk include $(commands_recovery_local_path)/edify/Android.mk include $(commands_recovery_local_path)/updater/Android.mk include $(commands_recovery_local_path)/applypatch/Android.mk +include $(commands_recovery_local_path)/utilities/Android.mk +include $(commands_recovery_local_path)/su/Android.mk +include $(commands_recovery_local_path)/voldclient/Android.mk +include $(commands_recovery_local_path)/loki/Android.mk commands_recovery_local_path := + +endif diff --git a/CleanSpec.mk b/CleanSpec.mk index b84e1b65e..eefcc0402 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -47,3 +47,33 @@ # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/dump_image_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/flash_image_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/erase_image_intermediates) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libflashutils_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libmmcutils_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libmtdutils_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libbmlutils_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libcrecovery_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libvoldclient_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libdedupe_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libedify_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libdump_image_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/liberase_image_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libflash_image_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminizip_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminadbd_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libmkyaffs2image_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libunyaffs_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminelf_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libbml_over_mtd_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libmake_ext4fs_intermediates) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libminui_intermediates) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/RECOVERY_EXECUTABLES) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/updater_intermediates) diff --git a/adb_install.c b/adb_install.c new file mode 100644 index 000000000..e17cda824 --- /dev/null +++ b/adb_install.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "minui/minui.h" +#include "cutils/properties.h" +#include "install.h" +#include "common.h" +#include "recovery_ui.h" +#include "adb_install.h" +#include "minadbd/adb.h" + +static void +set_usb_driver(int enabled) { + int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY); + if (fd < 0) { + ui_print("failed to open driver control: %s\n", strerror(errno)); + return; + } + + int status; + if (enabled > 0) { + status = write(fd, "1", 1); + } else { + status = write(fd, "0", 1); + } + + if (status < 0) { + ui_print("failed to set driver control: %s\n", strerror(errno)); + } + + if (close(fd) < 0) { + ui_print("failed to close driver control: %s\n", strerror(errno)); + } +} + +static void +stop_adbd() { + property_set("ctl.stop", "adbd"); + set_usb_driver(0); +} + + +static void +maybe_restart_adbd() { + char value[PROPERTY_VALUE_MAX+1]; + int len = property_get("ro.debuggable", value, NULL); + if (len == 1 && value[0] == '1') { + ui_print("Restarting adbd...\n"); + set_usb_driver(1); + property_set("ctl.start", "adbd"); + } +} + +struct sideload_waiter_data { + pid_t child; +}; + +void *adb_sideload_thread(void* v) { + struct sideload_waiter_data* data = (struct sideload_waiter_data*)v; + + int status; + waitpid(data->child, &status, 0); + LOGI("sideload process finished\n"); + + ui_cancel_wait_key(); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + ui_print("status %d\n", WEXITSTATUS(status)); + } + + LOGI("sideload thread finished\n"); + return NULL; +} + +int +apply_from_adb() { + stop_adbd(); + set_usb_driver(1); + + ui_print("\n\nSideload started ...\nNow send the package you want to apply\n" + "to the device with \"adb sideload \"...\n\n"); + + struct sideload_waiter_data data; + if ((data.child = fork()) == 0) { + execl("/sbin/recovery", "recovery", "adbd", NULL); + _exit(-1); + } + + pthread_t sideload_thread; + pthread_create(&sideload_thread, NULL, &adb_sideload_thread, &data); + + static const char* headers[] = { "ADB Sideload", + "", + NULL + }; + + static char* list[] = { "Cancel sideload", NULL }; + + get_menu_selection(headers, list, 0, 0); + + set_usb_driver(0); + maybe_restart_adbd(); + + // kill the child + kill(data.child, SIGTERM); + pthread_join(sideload_thread, NULL); + ui_clear_key_queue(); + + struct stat st; + if (stat(ADB_SIDELOAD_FILENAME, &st) != 0) { + if (errno == ENOENT) { + ui_print("No package received.\n"); + ui_set_background(BACKGROUND_ICON_ERROR); + } else { + ui_print("Error reading package:\n %s\n", strerror(errno)); + ui_set_background(BACKGROUND_ICON_ERROR); + } + return INSTALL_ERROR; + } + + int install_status = install_package(ADB_SIDELOAD_FILENAME); + ui_reset_progress(); + + if (install_status != INSTALL_SUCCESS) { + ui_set_background(BACKGROUND_ICON_ERROR); + ui_print("Installation aborted.\n"); + } + +#ifdef ENABLE_LOKI + else if (loki_support_enabled) { + ui_print("Checking if loki-fying is needed"); + install_status = loki_check(); + if (install_status != INSTALL_SUCCESS) + ui_set_background(BACKGROUND_ICON_ERROR); + } +#endif + + if (install_status == INSTALL_SUCCESS) + ui_set_background(BACKGROUND_ICON_NONE); + + remove(ADB_SIDELOAD_FILENAME); + return install_status; +} diff --git a/adb_install.h b/adb_install.h new file mode 100644 index 000000000..14a7b204c --- /dev/null +++ b/adb_install.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ADB_INSTALL_H +#define _ADB_INSTALL_H + +int apply_from_adb(); + +#endif \ No newline at end of file diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 0e529d4cb..0a4951c2c 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 libminelf 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 libminelf LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc @@ -50,7 +50,6 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := imgdiff.c utils.c bsdiff.c LOCAL_MODULE := imgdiff LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_MODULE_TAGS := eng LOCAL_C_INCLUDES += external/zlib external/bzip2 LOCAL_STATIC_LIBRARIES += libz libbz diff --git a/applypatch/NOTICE b/applypatch/NOTICE new file mode 100644 index 000000000..6156a0ca3 --- /dev/null +++ b/applypatch/NOTICE @@ -0,0 +1,41 @@ +Copyright (C) 2009 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +bsdiff.c +bspatch.c + +Copyright 2003-2005 Colin Percival +All rights reserved + +Redistribution and use in source and binary forms, with or without +modification, are permitted providing that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c index 106091386..6cfe76042 100644 --- a/applypatch/applypatch.c +++ b/applypatch/applypatch.c @@ -30,10 +30,17 @@ #include "mtdutils/mtdutils.h" #include "edify/expr.h" -int SaveFileContents(const char* filename, FileContents file); static int LoadPartitionContents(const char* filename, FileContents* file); -int ParseSha1(const char* str, uint8_t* digest); static ssize_t FileSink(unsigned char* data, ssize_t len, void* token); +static int GenerateTarget(FileContents* source_file, + const Value* source_patch_value, + FileContents* copy_file, + const Value* copy_patch_value, + const char* source_filename, + const char* target_filename, + const uint8_t target_sha1[SHA_DIGEST_SIZE], + size_t target_size, + const Value* bonus_data); static int mtd_partitions_scanned = 0; @@ -56,7 +63,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; @@ -94,7 +101,7 @@ int LoadFileContents(const char* filename, FileContents* file, } } - SHA(file->data, file->size, file->sha1); + SHA_hash(file->data, file->size, file->sha1); return 0; } @@ -113,11 +120,6 @@ static int compare_size_indices(const void* a, const void* b) { } } -void FreeFileContents(FileContents* file) { - if (file) free(file->data); - free(file); -} - // Load the contents of an MTD or EMMC partition into the provided // FileContents. filename should be a string of the form // "MTD::::::..." (or @@ -322,18 +324,18 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { // Save the contents of the given FileContents object under the given // filename. Return 0 on success. -int SaveFileContents(const char* filename, FileContents file) { - int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC); +int SaveFileContents(const char* filename, const FileContents* file) { + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd < 0) { printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); return -1; } - ssize_t bytes_written = FileSink(file.data, file.size, &fd); - if (bytes_written != file.size) { + ssize_t bytes_written = FileSink(file->data, file->size, &fd); + if (bytes_written != file->size) { printf("short write of \"%s\" (%ld bytes of %ld) (%s)\n", - filename, (long)bytes_written, (long)file.size, + filename, (long)bytes_written, (long)file->size, strerror(errno)); close(fd); return -1; @@ -341,11 +343,11 @@ int SaveFileContents(const char* filename, FileContents file) { fsync(fd); close(fd); - if (chmod(filename, file.st.st_mode) != 0) { + if (chmod(filename, file->st.st_mode) != 0) { printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); return -1; } - if (chown(filename, file.st.st_uid, file.st.st_gid) != 0) { + if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) { printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); return -1; } @@ -419,18 +421,99 @@ int WriteToPartition(unsigned char* data, size_t len, break; case EMMC: - ; - FILE* f = fopen(partition, "wb"); - if (fwrite(data, 1, len, f) != len) { - printf("short write writing to %s (%s)\n", - partition, strerror(errno)); + { + size_t start = 0; + int success = 0; + int fd = open(partition, O_RDWR); + if (fd < 0) { + printf("failed to open %s: %s\n", partition, strerror(errno)); + return -1; + } + int attempt; + + for (attempt = 0; attempt < 2; ++attempt) { + lseek(fd, start, SEEK_SET); + while (start < len) { + size_t to_write = len - start; + if (to_write > 1<<20) to_write = 1<<20; + + ssize_t written = write(fd, data+start, to_write); + if (written < 0) { + if (errno == EINTR) { + written = 0; + } else { + printf("failed write writing to %s (%s)\n", + partition, strerror(errno)); + return -1; + } + } + start += written; + } + fsync(fd); + + // drop caches so our subsequent verification read + // won't just be reading the cache. + sync(); + int dc = open("/proc/sys/vm/drop_caches", O_WRONLY); + write(dc, "3\n", 2); + close(dc); + sleep(1); + printf(" caches dropped\n"); + + // verify + lseek(fd, 0, SEEK_SET); + unsigned char buffer[4096]; + start = len; + size_t p; + for (p = 0; p < len; p += sizeof(buffer)) { + size_t to_read = len - p; + if (to_read > sizeof(buffer)) to_read = sizeof(buffer); + + size_t so_far = 0; + while (so_far < to_read) { + ssize_t read_count = read(fd, buffer+so_far, to_read-so_far); + if (read_count < 0) { + if (errno == EINTR) { + read_count = 0; + } else { + printf("verify read error %s at %d: %s\n", + partition, p, strerror(errno)); + return -1; + } + } + if ((size_t)read_count < to_read) { + printf("short verify read %s at %d: %d %d %s\n", + partition, p, read_count, to_read, strerror(errno)); + } + so_far += read_count; + } + + if (memcmp(buffer, data+p, to_read)) { + printf("verification failed starting at %d\n", p); + start = p; + break; + } + } + + if (start == len) { + printf("verification read succeeded (attempt %d)\n", attempt+1); + success = true; + break; + } + } + + if (!success) { + printf("failed to verify after all attempts\n"); return -1; } - if (fclose(f) != 0) { + + if (close(fd) != 0) { printf("error closing %s (%s)\n", partition, strerror(errno)); return -1; } + sync(); break; + } } free(copy); @@ -471,7 +554,7 @@ int ParseSha1(const char* str, uint8_t* digest) { // Search an array of sha1 strings for one matching the given sha1. // Return the index of the match on success, or -1 if no match is // found. -int FindMatchingPatch(uint8_t* sha1, char** const patch_sha1_str, +int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str, int num_patches) { int i; uint8_t patch_sha1[SHA_DIGEST_SIZE]; @@ -496,13 +579,19 @@ 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, RETOUCH_DO_MASK) != 0 || + int filestate = LoadFileContents(filename, &file, RETOUCH_DO_MASK); + 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 " "sha1 sums; checking cache\n", filename); free(file.data); + file.data = NULL; // If the source file is missing or corrupted, it might be because // we were killed in the middle of patching it. A copy of it @@ -615,7 +704,8 @@ int applypatch(const char* source_filename, size_t target_size, int num_patches, char** const patch_sha1_str, - Value** patch_data) { + Value** patch_data, + Value* bonus_data) { printf("\napplying patch to %s\n", source_filename); if (target_filename[0] == '-' && @@ -631,9 +721,10 @@ int applypatch(const char* source_filename, FileContents copy_file; FileContents source_file; + copy_file.data = NULL; + source_file.data = NULL; const Value* source_patch_value = NULL; const Value* copy_patch_value = NULL; - int made_copy = 0; // We try to load the target file into the source_file object. if (LoadFileContents(target_filename, &source_file, @@ -643,6 +734,7 @@ int applypatch(const char* source_filename, // has the desired hash, nothing for us to do. printf("\"%s\" is already target; no patch needed\n", target_filename); + free(source_file.data); return 0; } } @@ -653,6 +745,7 @@ int applypatch(const char* source_filename, // Need to load the source file: either we failed to load the // target file, or we did but it's different from the source file. free(source_file.data); + source_file.data = NULL; LoadFileContents(source_filename, &source_file, RETOUCH_DO_MASK); } @@ -667,6 +760,7 @@ int applypatch(const char* source_filename, if (source_patch_value == NULL) { free(source_file.data); + source_file.data = NULL; printf("source file is bad; trying copy\n"); if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file, @@ -685,16 +779,37 @@ int applypatch(const char* source_filename, if (copy_patch_value == NULL) { // fail. printf("copy file doesn't match source SHA-1s either\n"); + free(copy_file.data); return 1; } } + int result = GenerateTarget(&source_file, source_patch_value, + ©_file, copy_patch_value, + source_filename, target_filename, + target_sha1, target_size, bonus_data); + free(source_file.data); + free(copy_file.data); + + return result; +} + +static int GenerateTarget(FileContents* source_file, + const Value* source_patch_value, + FileContents* copy_file, + const Value* copy_patch_value, + const char* source_filename, + const char* target_filename, + const uint8_t target_sha1[SHA_DIGEST_SIZE], + size_t target_size, + const Value* bonus_data) { int retry = 1; SHA_CTX ctx; int output; MemorySinkInfo msi; FileContents* source_to_use; char* outname; + int made_copy = 0; // assume that target_filename (eg "/system/app/Foo.apk") is located // on the same filesystem as its top-level directory ("/system"). @@ -723,7 +838,7 @@ int applypatch(const char* source_filename, // We still write the original source to cache, in case // the partition write is interrupted. - if (MakeFreeSpaceOnCache(source_file.size) < 0) { + if (MakeFreeSpaceOnCache(source_file->size) < 0) { printf("not enough free space on /cache\n"); return 1; } @@ -763,7 +878,7 @@ int applypatch(const char* source_filename, return 1; } - if (MakeFreeSpaceOnCache(source_file.size) < 0) { + if (MakeFreeSpaceOnCache(source_file->size) < 0) { printf("not enough free space on /cache\n"); return 1; } @@ -782,10 +897,10 @@ int applypatch(const char* source_filename, const Value* patch; if (source_patch_value != NULL) { - source_to_use = &source_file; + source_to_use = source_file; patch = source_patch_value; } else { - source_to_use = ©_file; + source_to_use = copy_file; patch = copy_patch_value; } @@ -817,7 +932,7 @@ int applypatch(const char* source_filename, strcpy(outname, target_filename); strcat(outname, ".patch"); - output = open(outname, O_WRONLY | O_CREAT | O_TRUNC); + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (output < 0) { printf("failed to open output file %s: %s\n", outname, strerror(errno)); @@ -841,7 +956,7 @@ int applypatch(const char* source_filename, } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { result = ApplyImagePatch(source_to_use->data, source_to_use->size, - patch, sink, token, &ctx); + patch, sink, token, &ctx, bonus_data); } else { printf("Unknown patch file format\n"); return 1; diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h index a78c89bfe..f1f13a100 100644 --- a/applypatch/applypatch.h +++ b/applypatch/applypatch.h @@ -55,16 +55,17 @@ int applypatch(const char* source_filename, size_t target_size, int num_patches, char** const patch_sha1_str, - Value** patch_data); + Value** patch_data, + Value* bonus_data); int applypatch_check(const char* filename, int num_patches, char** const patch_sha1_str); int LoadFileContents(const char* filename, FileContents* file, int retouch_flag); -int SaveFileContents(const char* filename, FileContents file); +int SaveFileContents(const char* filename, const FileContents* file); void FreeFileContents(FileContents* file); -int FindMatchingPatch(uint8_t* sha1, char** const patch_sha1_str, +int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str, int num_patches); // bsdiff.c @@ -79,7 +80,8 @@ int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, // imgpatch.c int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, - SinkFn sink, void* token, SHA_CTX* ctx); + SinkFn sink, void* token, SHA_CTX* ctx, + const Value* bonus_data); // freecache.c int MakeFreeSpaceOnCache(size_t bytes_needed); diff --git a/applypatch/bspatch.c b/applypatch/bspatch.c index 2e80f81d0..1dc7ab10b 100644 --- a/applypatch/bspatch.c +++ b/applypatch/bspatch.c @@ -205,6 +205,11 @@ int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, ctrl[1] = offtin(buf+8); ctrl[2] = offtin(buf+16); + if (ctrl[0] < 0 || ctrl[1] < 0) { + printf("corrupt patch (negative byte counts)\n"); + return 1; + } + // Sanity check if (newpos + ctrl[0] > *new_size) { printf("corrupt patch (new file overrun)\n"); diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c index 6b9ebee5c..05c4f250f 100644 --- a/applypatch/imgdiff.c +++ b/applypatch/imgdiff.c @@ -111,6 +111,14 @@ * * After the header there are 'chunk count' bsdiff patches; the offset * of each from the beginning of the file is specified in the header. + * + * This tool can take an optional file of "bonus data". This is an + * extra file of data that is appended to chunk #1 after it is + * compressed (it must be a CHUNK_DEFLATE chunk). The same file must + * be available (and passed to applypatch with -b) when applying the + * patch. This is used to reduce the size of recovery-from-boot + * patches by combining the boot image with recovery ramdisk + * information that is stored on the system partition. */ #include @@ -772,21 +780,45 @@ void DumpChunks(ImageChunk* chunks, int num_chunks) { } int main(int argc, char** argv) { - if (argc != 4 && argc != 5) { - usage: - printf("usage: %s [-z] \n", - argv[0]); - return 2; - } - int zip_mode = 0; - if (strcmp(argv[1], "-z") == 0) { + if (argc >= 2 && strcmp(argv[1], "-z") == 0) { zip_mode = 1; --argc; ++argv; } + size_t bonus_size = 0; + unsigned char* bonus_data = NULL; + if (argc >= 3 && strcmp(argv[1], "-b") == 0) { + struct stat st; + if (stat(argv[2], &st) != 0) { + printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + bonus_size = st.st_size; + bonus_data = malloc(bonus_size); + FILE* f = fopen(argv[2], "rb"); + if (f == NULL) { + printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + if (fread(bonus_data, 1, bonus_size, f) != bonus_size) { + printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + fclose(f); + + argc -= 2; + argv += 2; + } + + if (argc != 4) { + usage: + printf("usage: %s [-z] [-b ] \n", + argv[0]); + return 2; + } int num_src_chunks; ImageChunk* src_chunks; @@ -909,6 +941,8 @@ int main(int argc, char** argv) { // Compute bsdiff patches for each chunk's data (the uncompressed // data, in the case of deflate chunks). + DumpChunks(src_chunks, num_src_chunks); + printf("Construct patches for %d chunks...\n", num_tgt_chunks); unsigned char** patch_data = malloc(num_tgt_chunks * sizeof(unsigned char*)); size_t* patch_size = malloc(num_tgt_chunks * sizeof(size_t)); @@ -923,6 +957,13 @@ int main(int argc, char** argv) { patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i); } } else { + if (i == 1 && bonus_data) { + printf(" using %d bytes of bonus data for chunk %d\n", bonus_size, i); + src_chunks[i].data = realloc(src_chunks[i].data, src_chunks[i].len + bonus_size); + memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size); + src_chunks[i].len += bonus_size; + } + patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i); } printf("patch %3d is %d bytes (of %d)\n", diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c index e3ee80ac0..3a1df3872 100644 --- a/applypatch/imgpatch.c +++ b/applypatch/imgpatch.c @@ -37,7 +37,8 @@ */ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, - SinkFn sink, void* token, SHA_CTX* ctx) { + SinkFn sink, void* token, SHA_CTX* ctx, + const Value* bonus_data) { ssize_t pos = 12; char* header = patch->data; if (patch->size < 12) { @@ -123,6 +124,12 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, // Decompress the source data; the chunk header tells us exactly // how big we expect it to be when decompressed. + // Note: expanded_len will include the bonus data size if + // the patch was constructed with bonus data. The + // deflation will come up 'bonus_size' bytes short; these + // must be appended from the bonus_data value. + size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->size : 0; + unsigned char* expanded_source = malloc(expanded_len); if (expanded_source == NULL) { printf("failed to allocate %d bytes for expanded_source\n", @@ -153,13 +160,19 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, printf("source inflation returned %d\n", ret); return -1; } - // We should have filled the output buffer exactly. - if (strm.avail_out != 0) { - printf("source inflation short by %d bytes\n", strm.avail_out); + // We should have filled the output buffer exactly, except + // for the bonus_size. + if (strm.avail_out != bonus_size) { + printf("source inflation short by %d bytes\n", strm.avail_out-bonus_size); return -1; } inflateEnd(&strm); + if (bonus_size) { + memcpy(expanded_source + (expanded_len - bonus_size), + bonus_data->data, bonus_size); + } + // Next, apply the bsdiff patch (in memory) to the uncompressed // data. unsigned char* uncompressed_target_data; diff --git a/applypatch/main.c b/applypatch/main.c index 7025a2e2e..f61db5d9e 100644 --- a/applypatch/main.c +++ b/applypatch/main.c @@ -100,6 +100,21 @@ static int ParsePatchArgs(int argc, char** argv, } int PatchMode(int argc, char** argv) { + Value* bonus = NULL; + if (argc >= 3 && strcmp(argv[1], "-b") == 0) { + FileContents fc; + if (LoadFileContents(argv[2], &fc, RETOUCH_DONT_MASK) != 0) { + printf("failed to load bonus file %s\n", argv[2]); + return 1; + } + bonus = malloc(sizeof(Value)); + bonus->type = VAL_BLOB; + bonus->size = fc.size; + bonus->data = (char*)fc.data; + argc -= 2; + argv += 2; + } + if (argc < 6) { return 2; } @@ -120,7 +135,7 @@ int PatchMode(int argc, char** argv) { } int result = applypatch(argv[1], argv[2], argv[3], target_size, - num_patches, sha1s, patches); + num_patches, sha1s, patches, bonus); int i; for (i = 0; i < num_patches; ++i) { @@ -130,6 +145,10 @@ int PatchMode(int argc, char** argv) { free(p); } } + if (bonus) { + free(bonus->data); + free(bonus); + } free(sha1s); free(patches); @@ -163,7 +182,7 @@ int main(int argc, char** argv) { if (argc < 2) { usage: printf( - "usage: %s " + "usage: %s [-b ] " "[: ...]\n" " or %s -c [ ...]\n" " or %s -s \n" diff --git a/bmlutils/Android.mk b/bmlutils/Android.mk new file mode 100644 index 000000000..7d8bf8501 --- /dev/null +++ b/bmlutils/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY + +$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \ + $(if $($(board_define)), \ + $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \ + ) \ + ) + +LOCAL_STATIC_LIBRARIES := libcrecovery +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../libcrecovery + +LOCAL_SRC_FILES := bmlutils.c +LOCAL_MODULE := libbmlutils +LOCAL_MODULE_TAGS := eng +include $(BUILD_STATIC_LIBRARY) diff --git a/bmlutils/bmlutils.c b/bmlutils/bmlutils.c new file mode 100644 index 000000000..fdf9ad0a3 --- /dev/null +++ b/bmlutils/bmlutils.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#define BML_UNLOCK_ALL 0x8A29 ///< unlock all partition RO -> RW + +#ifndef BOARD_BML_BOOT +#define BOARD_BML_BOOT "/dev/block/bml7" +#endif + +#ifndef BOARD_BML_RECOVERY +#define BOARD_BML_RECOVERY "/dev/block/bml8" +#endif + +static int restore_internal(const char* bml, const char* filename) +{ + char buf[4096]; + int dstfd, srcfd, bytes_read, bytes_written, total_read = 0; + if (filename == NULL) + srcfd = 0; + else { + srcfd = open(filename, O_RDONLY | O_LARGEFILE); + if (srcfd < 0) + return 2; + } + dstfd = open(bml, O_RDWR | O_LARGEFILE); + if (dstfd < 0) + return 3; + if (ioctl(dstfd, BML_UNLOCK_ALL, 0)) + return 4; + do { + total_read += bytes_read = read(srcfd, buf, 4096); + if (!bytes_read) + break; + if (bytes_read < 4096) + memset(&buf[bytes_read], 0, 4096 - bytes_read); + if (write(dstfd, buf, 4096) < 4096) + return 5; + } while(bytes_read == 4096); + + close(dstfd); + close(srcfd); + + return 0; +} + +int cmd_bml_restore_raw_partition(const char *partition, const char *filename) +{ + if (strcmp(partition, "boot") != 0 && strcmp(partition, "recovery") != 0 && strcmp(partition, "recoveryonly") != 0 && partition[0] != '/') + return 6; + + int ret = -1; + if (strcmp(partition, "recoveryonly") != 0) { + // always restore boot, regardless of whether recovery or boot is flashed. + // this is because boot and recovery are the same on some samsung phones. + // unless of course, recoveryonly is explictly chosen (bml8) + ret = restore_internal(BOARD_BML_BOOT, filename); + if (ret != 0) + return ret; + } + + if (strcmp(partition, "recovery") == 0 || strcmp(partition, "recoveryonly") == 0) + ret = restore_internal(BOARD_BML_RECOVERY, filename); + + // support explicitly provided device paths + if (partition[0] == '/') + ret = restore_internal(partition, filename); + return ret; +} + +int cmd_bml_backup_raw_partition(const char *partition, const char *out_file) +{ + const char* bml; + if (strcmp("boot", partition) == 0) + bml = BOARD_BML_BOOT; + else if (strcmp("recovery", partition) == 0) + bml = BOARD_BML_RECOVERY; + else if (partition[0] == '/') { + // support explicitly provided device paths + bml = partition; + } + else { + printf("Invalid partition.\n"); + return -1; + } + + int ch; + FILE *in; + FILE *out; + int val = 0; + char buf[512]; + unsigned sz = 0; + unsigned i; + int ret = -1; + + in = fopen ( bml, "r" ); + if (in == NULL) + goto ERROR3; + + out = fopen ( out_file, "w" ); + if (out == NULL) + goto ERROR2; + + fseek(in, 0L, SEEK_END); + sz = ftell(in); + fseek(in, 0L, SEEK_SET); + + if (sz % 512) + { + while ( ( ch = fgetc ( in ) ) != EOF ) + fputc ( ch, out ); + } + else + { + for (i=0; i< (sz/512); i++) + { + if ((fread(buf, 512, 1, in)) != 1) + goto ERROR1; + if ((fwrite(buf, 512, 1, out)) != 1) + goto ERROR1; + } + } + + fsync(fileno(out)); + ret = 0; +ERROR1: + fclose ( out ); +ERROR2: + fclose ( in ); +ERROR3: + return ret; +} + +int cmd_bml_erase_raw_partition(const char *partition) +{ + // TODO: implement raw wipe + return 0; +} + +int cmd_bml_erase_partition(const char *partition, const char *filesystem) +{ + return -1; +} + +int cmd_bml_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only) +{ + return -1; +} + +int cmd_bml_get_partition_device(const char *partition, char *device) +{ + return -1; +} + +int format_rfs_device (const char *device, const char *path) { + const char *fatsize = "32"; + const char *sectorsize = "1"; + + if (strcmp(path, "/datadata") == 0 || strcmp(path, "/cache") == 0) { + fatsize = "16"; + } + + // Just in case /data sector size needs to be altered + else if (strcmp(path, "/data") == 0 ) { + sectorsize = "1"; + } + + // dump 10KB of zeros to partition before format due to fat.format bug + char cmd[PATH_MAX]; + + sprintf(cmd, "/sbin/dd if=/dev/zero of=%s bs=4096 count=10", device); + if(__system(cmd)) { + printf("failure while zeroing rfs partition.\n"); + return -1; + } + + // Run fat.format + sprintf(cmd, "/sbin/fat.format -F %s -S 4096 -s %s %s", fatsize, sectorsize, device); + if(__system(cmd)) { + printf("failure while running fat.format\n"); + return -1; + } + + return 0; +} diff --git a/bmlutils/bmlutils.h b/bmlutils/bmlutils.h new file mode 100644 index 000000000..e6ffeee57 --- /dev/null +++ b/bmlutils/bmlutils.h @@ -0,0 +1,6 @@ +#ifndef BMLUTILS_H_ +#define BMLUTILS_H_ + +int format_rfs_device (const char *device, const char *path); + +#endif // BMLUTILS_H_ diff --git a/bootloader.c b/bootloader.c index baaddc55f..95f5fef9f 100644 --- a/bootloader.c +++ b/bootloader.c @@ -33,8 +33,8 @@ static int set_bootloader_message_block(const struct bootloader_message *in, con int get_bootloader_message(struct bootloader_message *out) { Volume* v = volume_for_path("/misc"); if (v == NULL) { - LOGE("Cannot load volume /misc!\n"); - return -1; + LOGI("Cannot load volume /misc.\n"); + return -1; } if (strcmp(v->fs_type, "mtd") == 0) { return get_bootloader_message_mtd(out, v); @@ -48,8 +48,8 @@ int get_bootloader_message(struct bootloader_message *out) { int set_bootloader_message(const struct bootloader_message *in) { Volume* v = volume_for_path("/misc"); if (v == NULL) { - LOGE("Cannot load volume /misc!\n"); - return -1; + LOGI("Cannot load volume /misc.\n"); + return -1; } if (strcmp(v->fs_type, "mtd") == 0) { return set_bootloader_message_mtd(in, v); @@ -71,22 +71,22 @@ static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v) { size_t write_size; mtd_scan_partitions(); - const MtdPartition *part = mtd_find_partition_by_name(v->device); + const MtdPartition *part = mtd_find_partition_by_name(v->blk_device); if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) { - LOGE("Can't find %s\n", v->device); + LOGE("Can't find %s\n", v->blk_device); return -1; } MtdReadContext *read = mtd_read_partition(part); if (read == NULL) { - LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); + LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } const ssize_t size = write_size * MISC_PAGES; char data[size]; ssize_t r = mtd_read_data(read, data, size); - if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno)); + if (r != size) LOGE("Can't read %s\n(%s)\n", v->blk_device, strerror(errno)); mtd_read_close(read); if (r != size) return -1; @@ -97,22 +97,22 @@ static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v) { size_t write_size; mtd_scan_partitions(); - const MtdPartition *part = mtd_find_partition_by_name(v->device); + const MtdPartition *part = mtd_find_partition_by_name(v->blk_device); if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) { - LOGE("Can't find %s\n", v->device); + LOGE("Can't find %s\n", v->blk_device); return -1; } MtdReadContext *read = mtd_read_partition(part); if (read == NULL) { - LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); + LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } ssize_t size = write_size * MISC_PAGES; char data[size]; ssize_t r = mtd_read_data(read, data, size); - if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno)); + if (r != size) LOGE("Can't read %s\n(%s)\n", v->blk_device, strerror(errno)); mtd_read_close(read); if (r != size) return -1; @@ -120,16 +120,16 @@ static int set_bootloader_message_mtd(const struct bootloader_message *in, MtdWriteContext *write = mtd_write_partition(part); if (write == NULL) { - LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); + LOGE("Can't open %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } if (mtd_write_data(write, data, size) != size) { - LOGE("Can't write %s\n(%s)\n", v->device, strerror(errno)); + LOGE("Can't write %s\n(%s)\n", v->blk_device, strerror(errno)); mtd_write_close(write); return -1; } if (mtd_write_close(write)) { - LOGE("Can't finish %s\n(%s)\n", v->device, strerror(errno)); + LOGE("Can't finish %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } @@ -161,20 +161,23 @@ static void wait_for_device(const char* fn) { static int get_bootloader_message_block(struct bootloader_message *out, const Volume* v) { - wait_for_device(v->device); - FILE* f = fopen(v->device, "rb"); + 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->device, strerror(errno)); + 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) { - LOGE("Failed reading %s\n(%s)\n", v->device, strerror(errno)); + LOGE("Failed reading %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } if (fclose(f) != 0) { - LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno)); + LOGE("Failed closing %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } memcpy(out, &temp, sizeof(temp)); @@ -183,20 +186,173 @@ static int get_bootloader_message_block(struct bootloader_message *out, static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v) { - wait_for_device(v->device); - FILE* f = fopen(v->device, "wb"); + 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->device, strerror(errno)); + 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->device, strerror(errno)); + LOGE("Failed writing %s\n(%s)\n", v->blk_device, strerror(errno)); return -1; } if (fclose(f) != 0) { - LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno)); + LOGE("Failed closing %s\n(%s)\n", v->blk_device, strerror(errno)); + return -1; + } + return 0; +} + +/* Update Image + * + * - will be stored in the "cache" partition + * - bad blocks will be ignored, like boot.img and recovery.img + * - the first block will be the image header (described below) + * - the size is in BYTES, inclusive of the header + * - offsets are in BYTES from the start of the update header + * - two raw bitmaps will be included, the "busy" and "fail" bitmaps + * - for dream, the bitmaps will be 320x480x16bpp RGB565 + */ + +#define UPDATE_MAGIC "MSM-RADIO-UPDATE" +#define UPDATE_MAGIC_SIZE 16 +#define UPDATE_VERSION 0x00010000 + +struct update_header { + unsigned char MAGIC[UPDATE_MAGIC_SIZE]; + + unsigned version; + unsigned size; + + unsigned image_offset; + unsigned image_length; + + unsigned bitmap_width; + unsigned bitmap_height; + unsigned bitmap_bpp; + + unsigned busy_bitmap_offset; + unsigned busy_bitmap_length; + + unsigned fail_bitmap_offset; + unsigned fail_bitmap_length; +}; + +int write_update_for_bootloader( + const char *update, int update_length, + int bitmap_width, int bitmap_height, int bitmap_bpp, + const char *busy_bitmap, const char *fail_bitmap) { + if (ensure_path_unmounted("/cache")) { + LOGE("Can't unmount /cache\n"); + return -1; + } + + const MtdPartition *part = mtd_find_partition_by_name("cache"); + if (part == NULL) { + LOGE("Can't find cache\n"); + return -1; + } + + MtdWriteContext *write = mtd_write_partition(part); + if (write == NULL) { + LOGE("Can't open cache\n(%s)\n", strerror(errno)); + return -1; + } + + /* Write an invalid (zero) header first, to disable any previous + * update and any other structured contents (like a filesystem), + * and as a placeholder for the amount of space required. + */ + + struct update_header header; + memset(&header, 0, sizeof(header)); + const ssize_t header_size = sizeof(header); + if (mtd_write_data(write, (char*) &header, header_size) != header_size) { + LOGE("Can't write header to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + + /* Write each section individually block-aligned, so we can write + * each block independently without complicated buffering. + */ + + memcpy(&header.MAGIC, UPDATE_MAGIC, UPDATE_MAGIC_SIZE); + header.version = UPDATE_VERSION; + header.size = header_size; + + off_t image_start_pos = mtd_erase_blocks(write, 0); + header.image_length = update_length; + if ((int) header.image_offset == -1 || + mtd_write_data(write, update, update_length) != update_length) { + LOGE("Can't write update to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + off_t busy_start_pos = mtd_erase_blocks(write, 0); + header.image_offset = mtd_find_write_start(write, image_start_pos); + + header.bitmap_width = bitmap_width; + header.bitmap_height = bitmap_height; + header.bitmap_bpp = bitmap_bpp; + + int bitmap_length = (bitmap_bpp + 7) / 8 * bitmap_width * bitmap_height; + + header.busy_bitmap_length = busy_bitmap != NULL ? bitmap_length : 0; + if ((int) header.busy_bitmap_offset == -1 || + mtd_write_data(write, busy_bitmap, bitmap_length) != bitmap_length) { + LOGE("Can't write bitmap to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + off_t fail_start_pos = mtd_erase_blocks(write, 0); + header.busy_bitmap_offset = mtd_find_write_start(write, busy_start_pos); + + header.fail_bitmap_length = fail_bitmap != NULL ? bitmap_length : 0; + if ((int) header.fail_bitmap_offset == -1 || + mtd_write_data(write, fail_bitmap, bitmap_length) != bitmap_length) { + LOGE("Can't write bitmap to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + mtd_erase_blocks(write, 0); + header.fail_bitmap_offset = mtd_find_write_start(write, fail_start_pos); + + /* Write the header last, after all the blocks it refers to, so that + * when the magic number is installed everything is valid. + */ + + if (mtd_write_close(write)) { + LOGE("Can't finish writing cache\n(%s)\n", strerror(errno)); + return -1; + } + + write = mtd_write_partition(part); + if (write == NULL) { + LOGE("Can't reopen cache\n(%s)\n", strerror(errno)); + return -1; + } + + if (mtd_write_data(write, (char*) &header, header_size) != header_size) { + LOGE("Can't rewrite header to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + + if (mtd_erase_blocks(write, 0) != image_start_pos) { + LOGE("Misalignment rewriting cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + + if (mtd_write_close(write)) { + LOGE("Can't finish header of cache\n(%s)\n", strerror(errno)); return -1; } + return 0; } diff --git a/bootloader.h b/bootloader.h index 2e749aa12..3d4302f1e 100644 --- a/bootloader.h +++ b/bootloader.h @@ -47,4 +47,13 @@ struct bootloader_message { int get_bootloader_message(struct bootloader_message *out); int set_bootloader_message(const struct bootloader_message *in); +/* Write an update to the cache partition for update-radio or update-hboot. + * Note, this destroys any filesystem on the cache partition! + * The expected bitmap format is 240x320, 16bpp (2Bpp), RGB 5:6:5. + */ +int write_update_for_bootloader( + const char *update, int update_len, + int bitmap_width, int bitmap_height, int bitmap_bpp, + const char *busy_bitmap, const char *error_bitmap); + #endif diff --git a/common.h b/common.h index ef2fe9f62..0dfc30ebe 100644 --- a/common.h +++ b/common.h @@ -18,11 +18,17 @@ #define RECOVERY_COMMON_H #include +#include + +#define MENU_TEXT_COLOR 0, 191, 255, 255 +#define NORMAL_TEXT_COLOR 200, 200, 200, 255 +#define HEADER_TEXT_COLOR NORMAL_TEXT_COLOR // Initialize the graphics system. void ui_init(); // Use KEY_* codes from or KEY_DREAM_* from "minui/minui.h". +void ui_cancel_wait_key(); int ui_wait_key(); // waits for a key/button press, returns the code int ui_key_pressed(int key); // returns >0 if the code is currently pressed int ui_text_visible(); // returns >0 if text log is currently visible @@ -34,27 +40,61 @@ void ui_clear_key_queue(); // The screen is small, and users may need to report these messages to support, // so keep the output short and not too cryptic. void ui_print(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +void ui_printlogtail(int nb_lines); + +void ui_delete_line(); +void ui_set_show_text(int value); +int ui_get_text_cols(); +void ui_setMenuTextColor(int r, int g, int b, int a); + +#ifdef ENABLE_LOKI +extern int loki_support_enabled; +int loki_check(); +#endif // 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). -void ui_start_menu(char** headers, char** items, int initial_selection); +int ui_start_menu(const char** headers, char** items, int initial_selection); + // Set the menu highlight to the given index, and return it (capped to // the range [0..numitems). int ui_menu_select(int sel); + // End menu mode, resetting the text overlay so that ui_print() // statements will be displayed. void ui_end_menu(); +int ui_get_selected_item(); + +int is_ui_initialized(); +void ui_set_showing_back_button(int showBackButton); +int ui_is_showing_back_button(); + +void ui_set_log_stdout(int enabled); +int ui_should_log_stdout(); + +int ui_get_rainbow_mode(); +void ui_set_rainbow_mode(int rainbowMode); + // Set the icon (normally the only thing visible besides the progress bar). enum { BACKGROUND_ICON_NONE, BACKGROUND_ICON_INSTALLING, BACKGROUND_ICON_ERROR, + BACKGROUND_ICON_CLOCKWORK, + BACKGROUND_ICON_CID, + BACKGROUND_ICON_FIRMWARE_INSTALLING, + BACKGROUND_ICON_FIRMWARE_ERROR, NUM_BACKGROUND_ICONS }; void ui_set_background(int icon); +// Get a malloc'd copy of the screen image showing (only) the specified icon. +// Also returns the width, height, and bits per pixel of the returned image. +// TODO: Use some sort of "struct Bitmap" here instead of all these variables? +char *ui_copy_image(int icon, int *width, int *height, int *bpp); + // Show a progress bar and define the scope of the next operation: // portion - fraction of the progress bar the next operation will use // seconds - expected time interval (progress bar moves at this minimum rate) @@ -88,24 +128,7 @@ void ui_reset_progress(); #define STRINGIFY(x) #x #define EXPAND(x) STRINGIFY(x) -typedef struct { - const char* mount_point; // eg. "/cache". must live in the root directory. - - const char* fs_type; // "yaffs2" or "ext4" or "vfat" - - const char* device; // MTD partition name if fs_type == "yaffs" - // block device if fs_type == "ext4" or "vfat" - - const char* device2; // alternative device to try if fs_type - // == "ext4" or "vfat" and mounting - // 'device' fails - - long long length; // (ext4 partition only) when - // formatting, size to use for the - // partition. 0 or negative number - // means to format all but the last - // (that much). -} Volume; +typedef struct fstab_rec Volume; typedef struct { // number of frames in indeterminate progress bar animation @@ -129,4 +152,19 @@ typedef struct { // fopen a file, mounting volumes and making parent dirs as necessary. FILE* fopen_path(const char *path, const char *mode); +/* + * Set performance mode on/off before/after tar compress and extract. + * Device must have properly configured init.rc or + * init.recovery.{ro.hardware}.rc that enables and disables cores, or + * sets other cpu performance settings, when recovery.perf.mode + * changes + */ +void set_perf_mode(int on); + +/* + * Initialize mini vold so that recovery can mount, unmount, and format + * vold managed storage. + */ +void vold_init(); + #endif // RECOVERY_COMMON_H diff --git a/dedupe/Android.mk b/dedupe/Android.mk new file mode 100644 index 000000000..36dddc156 --- /dev/null +++ b/dedupe/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := dedupe.c driver.c \ + ../../../external/libselinux/src/lsetfilecon.c \ + ../../../external/libselinux/src/lgetfilecon.c + +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE := dedupe +LOCAL_STATIC_LIBRARIES := libcrypto_static libselinux +LOCAL_C_INCLUDES += external/openssl/include external/libselinux/include +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := dedupe.c +LOCAL_STATIC_LIBRARIES := libcrypto_static libcutils libc libselinux +LOCAL_MODULE := libdedupe +LOCAL_MODULE_TAGS := eng +LOCAL_C_INCLUDES := external/openssl/include external/libselinux/include +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := driver.c +LOCAL_STATIC_LIBRARIES := libdedupe libcrypto_static libcutils libc libselinux +LOCAL_MODULE := utility_dedupe +LOCAL_MODULE_TAGS := eng +LOCAL_MODULE_STEM := dedupe +LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES +LOCAL_C_INCLUDES := external/openssl/include external/libselinux/include +LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities +LOCAL_FORCE_STATIC_EXECUTABLE := true +include $(BUILD_EXECUTABLE) diff --git a/dedupe/dedupe.c b/dedupe/dedupe.c new file mode 100644 index 000000000..b8352379d --- /dev/null +++ b/dedupe/dedupe.c @@ -0,0 +1,636 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define DEDUPE_VERSION 2 +#define ARRAY_CAPACITY 1000 + +static int copy_file(const char *src, const char *dst) { + char buf[4096]; + int dstfd, srcfd, bytes_read, bytes_written, total_read = 0; + if (src == NULL) + return 1; + if (dst == NULL) + return 2; + + srcfd = open(src, O_RDONLY); + if (srcfd < 0) + return 3; + + dstfd = open(dst, O_RDWR | O_CREAT | O_TRUNC, 0666); + if (dstfd < 0) { + close(srcfd); + return 4; + } + + while (bytes_read = read(srcfd, buf, 4096)) { + total_read += bytes_read; + if (write(dstfd, buf, bytes_read) != bytes_read) + return 5; + } + + close(dstfd); + close(srcfd); + + return 0; +} + +typedef struct DEDUPE_STORE_CONTEXT { + char blob_dir[PATH_MAX]; + FILE *output_manifest; + const char** excludes; + int exclude_count; +}; + +static void usage(char** argv) { + fprintf(stderr, "usage: %s c input_directory blob_dir output_manifest [exclude...]\n", argv[0]); + fprintf(stderr, "usage: %s x input_manifest blob_dir output_directory\n", argv[0]); + fprintf(stderr, "usage: %s gc blob_dir input_manifests...\n", argv[0]); +} + +static void do_sha256sum(FILE *mfile, unsigned char *rptr) { + char rdata[BUFSIZ]; + int rsize; + SHA256_CTX c; + + SHA256_Init(&c); + while(!feof(mfile)) { + rsize = fread(rdata, sizeof(char), BUFSIZ, mfile); + if(rsize > 0) { + SHA256_Update(&c, rdata, rsize); + } + } + + SHA256_Final(rptr, &c); +} + +static int do_sha256sum_file(const char* filename, unsigned char *rptr) { + FILE *f = fopen(filename, "rb"); + if (f == NULL) { + fprintf(stderr, "Unable to open file: %s\n", filename); + return 1; + } + do_sha256sum(f, rptr); + fclose(f); + return 0; +} + +static int store_st(struct DEDUPE_STORE_CONTEXT *context, struct stat st, const char* s); + +void print_stat(struct DEDUPE_STORE_CONTEXT *context, char type, struct stat st, char *selabel, const char *f) { + fprintf(context->output_manifest, "%c\t%o\t%lu\t%lu\t%s\t%lu\t%lu\t%lu\t%s\t", type, st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID), st.st_uid, st.st_gid, selabel, st.st_atime, st.st_mtime, st.st_ctime, f); +} + +static int store_file(struct DEDUPE_STORE_CONTEXT *context, struct stat st, const char* f) { + printf("%s\n", f); + unsigned char sumdata[SHA256_DIGEST_LENGTH]; + int ret; + if (ret = do_sha256sum_file(f, sumdata)) { + fprintf(stderr, "Error calculating sha256sum of %s\n", f); + return ret; + } + char psum[128]; + int j; + for (j = 0; j < SHA256_DIGEST_LENGTH; j++) + sprintf(&psum[(j*2)], "%02x", (int)sumdata[j]); + psum[(SHA256_DIGEST_LENGTH * 2)] = '\0'; + + // if a hash is abcdefg, + // the output blob name is abc/defg + // this is to get around vfat having a 64k directory size limit (usually around 20k files) + char out_blob[PATH_MAX]; + char tmp_out_blob[PATH_MAX]; + char key[SHA256_DIGEST_LENGTH * 2 + 2]; + // int i = 0; + // int keyIndex = 0; + // while (psum[i]) { + // key[keyIndex] = psum[i]; + // i++; + // keyIndex++; + // if (i % 2 == 0 && psum[i]) { + // key[keyIndex] = '/'; + // keyIndex++; + // } + // } + strcpy(key, psum); + key[3] = '/'; + key[4] = NULL; + strcat(key, psum + 3); + sprintf(out_blob, "%s/%s", context->blob_dir, key); + sprintf(tmp_out_blob, "%s.tmp", out_blob); + //when BUILD_HOST_EXECUTABLE, dirname(out_blob) will change out_blob + char out_blob_dir[PATH_MAX]; + strcpy(out_blob_dir, out_blob); + mkdir(dirname(out_blob_dir), S_IRWXU | S_IRWXG | S_IRWXO); + + // don't copy the file if it exists? not quite sure how I feel about this. + int size = (int)st.st_size; + struct stat file_info; + // verify the file exists and is of the same size + int file_ok = stat(out_blob, &file_info) == 0; + if (file_ok) { + int existing_size = file_info.st_size; + if (existing_size != size) + file_ok = 0; + } + if (!file_ok) { + // copy to the tmp file + if ((ret = copy_file(f, tmp_out_blob)) || (ret = rename(tmp_out_blob, out_blob))) { + fprintf(stderr, "Error copying blob %s\n", f); + return ret; + } + } + + fprintf(context->output_manifest, "%s\t%d\t\n", key, size); + return 0; +} + +static int store_dir(struct DEDUPE_STORE_CONTEXT *context, struct stat st, const char* d) { + char full_path[PATH_MAX]; + printf("%s\n", d); + DIR *dp = opendir(d); + if (dp == NULL) { + fprintf(stderr, "Error opening directory: %s\n", d); + return 1; + } + struct dirent *ep; + while (ep = readdir(dp)) { + if (strcmp(ep->d_name, ".") == 0) + continue; + if (strcmp(ep->d_name, "..") == 0) + continue; + struct stat cst; + int ret; + sprintf(full_path, "%s/%s", d, ep->d_name); + int i; + for (i = 0; i < context->exclude_count; i++) { + if (!strcmp(context->excludes[i], full_path)) + break; + } + if (i != context->exclude_count) + continue; + if (0 != (ret = lstat(full_path, &cst))) { + fprintf(stderr, "Error opening: %s\n", full_path); + closedir(dp); + return ret; + } + + if (ret = store_st(context, cst, full_path)) { + fprintf(stderr, "Error storing: %s\n", full_path); + closedir(dp); + return ret; + } + } + closedir(dp); + return 0; +} + +static int store_link(struct DEDUPE_STORE_CONTEXT *context, struct stat st, const char* l) { + printf("%s\n", l); + char link[PATH_MAX]; + int ret = readlink(l, link, PATH_MAX); + if (ret < 0) { + fprintf(stderr, "Error reading symlink\n"); + return errno; + } + link[ret] = '\0'; + fprintf(context->output_manifest, "%s\t\n", link); + return 0; +} + +static int store_st(struct DEDUPE_STORE_CONTEXT *context, struct stat st, const char* s) { + char* selabel = NULL; + if (lgetfilecon(s, &selabel) < 0) { + fprintf(stderr, "Can't get %s context\n", s); + selabel = strdup("unlabel"); + } + if (S_ISREG(st.st_mode)) { + print_stat(context, 'f', st, selabel, s); + freecon(selabel); + return store_file(context, st, s); + } + else if (S_ISDIR(st.st_mode)) { + print_stat(context, 'd', st, selabel, s); + fprintf(context->output_manifest, "\n"); + freecon(selabel); + return store_dir(context, st, s); + } + else if (S_ISLNK(st.st_mode)) { + print_stat(context, 'l', st, selabel, s); + freecon(selabel); + return store_link(context, st, s); + } + else { + fprintf(stderr, "Skipping special: %s\n", s); + freecon(selabel); + return 0; + } +} + +static char* tokenize(char *out, const char* line, const char sep) { + while (*line != sep) { + if (*line == '\0') { + return NULL; + } + + *out = *line; + out++; + line++; + } + + *out = '\0'; + // resume at the next char + return ++line; +} + +static int dec_to_oct(int dec) { + int ret = 0; + int mult = 1; + while (dec != 0) { + int rem = dec % 10; + ret += (rem * mult); + dec /= 10; + mult *= 8; + } + + return ret; +} + +struct array { + void** data; + int size; + int capacity; +}; + +static void array_init(struct array* arr, int capacity) { + arr->data = malloc(sizeof(void*) * capacity); + assert(arr->data != NULL); + arr->size = 0; + arr->capacity = capacity; +} + +static void array_free(struct array* arr, int free_members) { + if (free_members) { + int i; + for (i = 0; i < arr->size; i++) { + free(arr->data[i]); + } + } + + if (arr->data != NULL) { + free(arr->data); + arr->data = NULL; + } + arr->size = 0; + arr->capacity = 0; +} + +static void array_add(struct array* arr, void* val) { + if (arr->size == arr->capacity) { + // Expand array + arr->capacity *= 2; + arr->data = realloc(arr->data, sizeof(void*) * arr->capacity); + assert(arr->data != NULL); + } + arr->data[arr->size++] = val; +} + +static int string_compare(const void* a, const void* b) { + return strcmp(*(char**) a, *(char **) b); +} + +static void recursive_list_dir(char* d, struct array *arr) { + DIR *dp = opendir(d); + if (dp == NULL) { + fprintf(stderr, "Error opening directory: %s\n", d); + return; + } + struct dirent *ep; + while ((ep = readdir(dp))) { + if (strcmp(ep->d_name, ".") == 0) + continue; + if (strcmp(ep->d_name, "..") == 0) + continue; + struct stat cst; + int ret; + char blob[PATH_MAX]; + sprintf(blob, "%s/%s", d, ep->d_name); + if ((ret = lstat(blob, &cst))) { + fprintf(stderr, "Error opening: %s\n", ep->d_name); + continue; + } + + if (S_ISDIR(cst.st_mode)) { + recursive_list_dir(blob, arr); + continue; + } + + array_add(arr, strdup(blob)); + } + closedir(dp); +} + +static int check_file(const char* f) { + struct stat cst; + return lstat(f, &cst); +} + +int dedupe_main(int argc, char** argv) { + if (argc < 3) { + usage(argv); + return 1; + } + + if (strcmp(argv[1], "c") == 0) { + if (argc < 5) { + usage(argv); + return 1; + } + + struct stat st; + int ret; + if (0 != (ret = lstat(argv[2], &st))) { + fprintf(stderr, "Error opening input_file/input_directory.\n"); + return ret; + } + + if (!S_ISDIR(st.st_mode)) { + fprintf(stderr, "%s must be a directory.\n", argv[2]); + return 1; + } + + struct DEDUPE_STORE_CONTEXT context; + context.output_manifest = fopen(argv[4], "wb"); + fprintf(context.output_manifest, "dedupe\t%d\n", DEDUPE_VERSION); + if (context.output_manifest == NULL) { + fprintf(stderr, "Unable to open output file %s\n", argv[4]); + return 1; + } + mkdir(argv[3], S_IRWXU | S_IRWXG | S_IRWXO); + realpath(argv[3], context.blob_dir); + chdir(argv[2]); + context.excludes = argv + 5; + context.exclude_count = argc - 5; + + return store_dir(&context, st, "."); + } + else if (strcmp(argv[1], "x") == 0) { + if (argc != 5) { + usage(argv); + return 1; + } + + FILE *input_manifest = fopen(argv[2], "rb"); + if (input_manifest == NULL) { + fprintf(stderr, "Unable to open input manifest %s\n", argv[2]); + return 1; + } + + char blob_dir[PATH_MAX]; + char *output_dir = argv[4]; + realpath(argv[3], blob_dir); + + printf("%s\n" , output_dir); + mkdir(output_dir, S_IRWXU | S_IRWXG | S_IRWXO); + if (chdir(output_dir)) { + fprintf(stderr, "Unable to open output directory %s\n", output_dir); + return 1; + } + + char line[PATH_MAX]; + fgets(line, PATH_MAX, input_manifest); + int version = 1; + if (sscanf(line, "dedupe\t%d", &version) != 1) { + fseek(input_manifest, 0, SEEK_SET); + } + if (version > DEDUPE_VERSION) { + fprintf(stderr, "Attempting to restore newer dedupe file: %s\n", argv[2]); + return 1; + } + while (fgets(line, PATH_MAX, input_manifest)) { + //printf("%s", line); + + char type[4]; + char mode[8]; + char uid[32]; + char gid[32]; + char selabel[PATH_MAX]; + char at[32]; + char mt[32]; + char ct[32]; + char filename[PATH_MAX]; + + char *token = line; + token = tokenize(type, token, '\t'); + token = tokenize(mode, token, '\t'); + token = tokenize(uid, token, '\t'); + token = tokenize(gid, token, '\t'); + token = tokenize(selabel, token, '\t'); + if (version >= 2) { + token = tokenize(at, token, '\t'); + token = tokenize(mt, token, '\t'); + token = tokenize(ct, token, '\t'); + } + token = tokenize(filename, token, '\t'); + + int mode_oct = dec_to_oct(atoi(mode)); + int uid_int = atoi(uid); + int gid_int = atoi(gid); + int ret; + //printf("%s\t%s\t%s\t%s\t%s\t%s\t", type, mode, uid, gid, selabel, filename); + printf("%s\n", filename); + if (strcmp(type, "f") == 0) { + char sha256[128]; + token = tokenize(sha256, token, '\t'); + char sizeStr[32]; + token = tokenize(sizeStr, token, '\t'); + int size = atoi(sizeStr); + // printf("%s\t%d\n", sha256, size); + + char blob_file[PATH_MAX]; + sprintf(blob_file, "%s/%s", blob_dir, sha256); + if (ret = copy_file(blob_file, filename)) { + fprintf(stderr, "Unable to copy file %s\n", filename); + fclose(input_manifest); + return ret; + } + + chown(filename, uid_int, gid_int); + chmod(filename, mode_oct); + } + else if (strcmp(type, "l") == 0) { + char link[41]; + token = tokenize(link, token, '\t'); + // printf("%s\n", link); + + symlink(link, filename); + + // Android has no lchmod, and chmod follows symlinks + //chmod(filename, mode_oct); + lchown(filename, uid_int, gid_int); + } + else if (strcmp(type, "d") == 0) { + // printf("\n"); + + mkdir(filename, mode_oct); + + chown(filename, uid_int, gid_int); + chmod(filename, mode_oct); + } + else { + fprintf(stderr, "Unknown type %s\n", type); + fclose(input_manifest); + return 1; + } + if (lsetfilecon(filename, selabel) < 0) { + fprintf(stderr, "Can't setfilecon %s\n", filename); + } + if (version >= 2) { + struct timeval times[2]; + times[0].tv_sec = atol(at); + times[1].tv_sec = atol(mt); + utimes(filename, times); + } + } + + fclose(input_manifest); + return 0; + } + else if (strcmp(argv[1], "gc") == 0) { + if (argc < 3) { + usage(argv); + return 1; + } + + char blob_dir[PATH_MAX]; + realpath(argv[2], blob_dir); + if (check_file(blob_dir)) { + fprintf(stderr, "Unable to open blobs dir: %s\n", blob_dir); + return 1; + } + + struct array used_files; + struct array all_files; + array_init(&used_files, ARRAY_CAPACITY); + array_init(&all_files, ARRAY_CAPACITY); + + char blob[PATH_MAX]; + int i; + int failure = 0; + for (i = 3; i < argc; i++) { + FILE *input_manifest = fopen(argv[i], "rb"); + if (input_manifest == NULL) { + fprintf(stderr, "Unable to open input manifest %s\n", argv[i]); + failure = 1; + goto out; + } + + char line[PATH_MAX]; + fgets(line, PATH_MAX, input_manifest); + int version = 1; + if (sscanf(line, "dedupe\t%d", &version) != 1) { + fseek(input_manifest, 0, SEEK_SET); + } + if (version > DEDUPE_VERSION) { + fprintf(stderr, "Attempting to gc newer dedupe file: %s\n", argv[2]); + failure = 1; + fclose(input_manifest); + break; + } + while (fgets(line, PATH_MAX, input_manifest)) { + char type[4]; + char mode[8]; + char uid[32]; + char gid[32]; + char selabel[PATH_MAX]; + char at[32]; + char mt[32]; + char ct[32]; + char filename[PATH_MAX]; + + char *token = line; + token = tokenize(type, token, '\t'); + token = tokenize(mode, token, '\t'); + token = tokenize(uid, token, '\t'); + token = tokenize(gid, token, '\t'); + token = tokenize(selabel, token, '\t'); + if (version >= 2) { + token = tokenize(at, token, '\t'); + token = tokenize(mt, token, '\t'); + token = tokenize(ct, token, '\t'); + } + token = tokenize(filename, token, '\t'); + + int mode_oct = dec_to_oct(atoi(mode)); + int uid_int = atoi(uid); + int gid_int = atoi(gid); + int ret; + // printf("%s\n", filename); + if (strcmp(type, "f") == 0) { + char key[128]; + token = tokenize(key, token, '\t'); + char sizeStr[32]; + token = tokenize(sizeStr, token, '\t'); + int size = atoi(sizeStr); + + sprintf(blob, "%s/%s", blob_dir, key); + array_add(&used_files, strdup(blob)); + } + } + fclose(input_manifest); + } + + recursive_list_dir(blob_dir, &all_files); + + qsort(used_files.data, used_files.size, sizeof(void*), string_compare); + qsort(all_files.data, all_files.size, sizeof(void*), string_compare); + + // Search for unused files + int j = 0; + for (i = 0; i < all_files.size; i++) { + int cmp; + while (j < used_files.size && + (cmp = strcmp(used_files.data[j], all_files.data[i])) < 0) { + j++; + } + + if (cmp > 0 || j >= used_files.size) { + if (remove(all_files.data[i])) { + fprintf(stderr, "Error removing: %s\n", all_files.data[i]); + } + printf("Delete: %s\n", all_files.data[i]); + } + } + + out: + array_free(&used_files, 1); + array_free(&all_files, 1); + + return failure; + } + else { + usage(argv); + return 1; + } +} diff --git a/dedupe/dedupe.h b/dedupe/dedupe.h new file mode 100644 index 000000000..ae75d3cd4 --- /dev/null +++ b/dedupe/dedupe.h @@ -0,0 +1,6 @@ +#ifndef DEDUPE_H +#define DEDUPE_H + +int dedupe_main(int argc, char** argv); + +#endif \ No newline at end of file diff --git a/dedupe/driver.c b/dedupe/driver.c new file mode 100644 index 000000000..45249c4bd --- /dev/null +++ b/dedupe/driver.c @@ -0,0 +1,5 @@ +#include "dedupe.h" + +int main(int argc, char** argv) { + return dedupe_main(argc, argv); +} \ No newline at end of file diff --git a/default_recovery_keys.c b/default_recovery_keys.c new file mode 100644 index 000000000..6c1763e7a --- /dev/null +++ b/default_recovery_keys.c @@ -0,0 +1,43 @@ +#include + +#include "common.h" +#include "extendedcommands.h" +#include "recovery_ui.h" + +int device_handle_key(int key_code, int visible) { + if (visible) { + switch (key_code) { + case KEY_CAPSLOCK: + case KEY_DOWN: + case KEY_VOLUMEDOWN: + case KEY_MENU: + return HIGHLIGHT_DOWN; + + case KEY_LEFTSHIFT: + case KEY_UP: + case KEY_VOLUMEUP: + case KEY_HOME: + return HIGHLIGHT_UP; + + case KEY_POWER: + case KEY_LEFTBRACE: + case KEY_ENTER: + case BTN_MOUSE: + case KEY_CAMERA: + case KEY_F21: + case KEY_SEND: + case KEY_HOMEPAGE: + case KEY_SEARCH: + return SELECT_ITEM; + + case KEY_END: + case KEY_BACKSPACE: + case KEY_BACK: + if (!ui_root_menu) { + return GO_BACK; + } + } + } + + return NO_ACTION; +} diff --git a/default_recovery_ui.c b/default_recovery_ui.c index d56164e7e..004c95cec 100644 --- a/default_recovery_ui.c +++ b/default_recovery_ui.c @@ -16,18 +16,19 @@ #include -#include "recovery_ui.h" #include "common.h" +#include "extendedcommands.h" +#include "recovery_ui.h" -char* MENU_HEADERS[] = { "Android system recovery utility", - "", - NULL }; +char* MENU_HEADERS[] = { NULL }; char* MENU_ITEMS[] = { "reboot system now", - "apply update from external storage", + "install zip", "wipe data/factory reset", "wipe cache partition", - "apply update from cache", + "backup and restore", + "mounts and storage", + "advanced", NULL }; void device_ui_init(UIParameters* ui_parameters) { @@ -37,33 +38,11 @@ int device_recovery_start() { return 0; } -int device_toggle_display(volatile char* key_pressed, int key_code) { - return key_code == KEY_HOME; -} - +// add here any key combo check to reboot device int device_reboot_now(volatile char* key_pressed, int key_code) { return 0; } -int device_handle_key(int key_code, int visible) { - if (visible) { - switch (key_code) { - case KEY_DOWN: - case KEY_VOLUMEDOWN: - return HIGHLIGHT_DOWN; - - case KEY_UP: - case KEY_VOLUMEUP: - return HIGHLIGHT_UP; - - case KEY_ENTER: - return SELECT_ITEM; - } - } - - return NO_ACTION; -} - int device_perform_action(int which) { return which; } diff --git a/edify/expr.c b/edify/expr.c index 360007586..07a8ceb6a 100644 --- a/edify/expr.c +++ b/edify/expr.c @@ -495,7 +495,7 @@ Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]) { // Use printf-style arguments to compose an error message to put into // *state. Returns NULL. -Value* ErrorAbort(State* state, char* format, ...) { +Value* ErrorAbort(State* state, const char* format, ...) { char* buffer = malloc(4096); va_list v; va_start(v, format); diff --git a/edify/expr.h b/edify/expr.h index 8e1c63872..0d8ed8f57 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -21,6 +21,10 @@ #include "yydefs.h" +#ifdef __cplusplus +extern "C" { +#endif + #define MAX_STRING_LEN 1024 typedef struct Expr Expr; @@ -152,7 +156,7 @@ Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]); // Use printf-style arguments to compose an error message to put into // *state. Returns NULL. -Value* ErrorAbort(State* state, char* format, ...); +Value* ErrorAbort(State* state, const char* format, ...) __attribute__((format(printf, 2, 3))); // Wrap a string into a Value, taking ownership of the string. Value* StringValue(char* str); @@ -160,4 +164,8 @@ Value* StringValue(char* str); // Free a Value object. void FreeValue(Value* v); +#ifdef __cplusplus +} // extern "C" +#endif + #endif // _EXPRESSION_H diff --git a/edifyscripting.c b/edifyscripting.c new file mode 100644 index 000000000..5387c3ab5 --- /dev/null +++ b/edifyscripting.c @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bootloader.h" +#include "common.h" +#include "cutils/properties.h" +#include "edify/expr.h" +#include "edifyscripting.h" +#include "extendedcommands.h" +#include "firmware.h" +#include "flashutils/flashutils.h" +#include "install.h" +#include "minui/minui.h" +#include "minzip/DirUtil.h" +#include "mounts.h" +#include "mmcutils/mmcutils.h" +#include "mtdutils/mtdutils.h" +#include "nandroid.h" +#include "recovery_ui.h" +#include "roots.h" + +#define EXTENDEDCOMMAND_SCRIPT "/cache/recovery/extendedcommand" + +extern int yyparse(); +extern int yy_scan_bytes(); + +Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + int size = 0; + int i; + for (i = 0; i < argc; ++i) { + size += strlen(args[i]); + } + char* buffer = malloc(size+1); + size = 0; + for (i = 0; i < argc; ++i) { + strcpy(buffer+size, args[i]); + size += strlen(args[i]); + free(args[i]); + } + free(args); + buffer[size] = '\0'; + + char* line = strtok(buffer, "\n"); + while (line) { + ui_print("%s\n", line); + line = strtok(NULL, "\n"); + } + + return StringValue(buffer); +} + +Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s() expects at least 1 arg", name); + } + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + char** args2 = malloc(sizeof(char*) * (argc+1)); + memcpy(args2, args, sizeof(char*) * argc); + args2[argc] = NULL; + + fprintf(stderr, "about to run program [%s] with %d args\n", args2[0], argc); + + pid_t child = fork(); + if (child == 0) { + execv(args2[0], args2); + fprintf(stderr, "run_program: execv failed: %s\n", strerror(errno)); + _exit(1); + } + int status; + waitpid(child, &status, 0); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + fprintf(stderr, "run_program: child exited with status %d\n", + WEXITSTATUS(status)); + } + } else if (WIFSIGNALED(status)) { + fprintf(stderr, "run_program: child terminated by signal %d\n", + WTERMSIG(status)); + } + + int i; + for (i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + free(args2); + + char buffer[20]; + sprintf(buffer, "%d", status); + + return StringValue(strdup(buffer)); +} + +Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + + char *path; + if (ReadArgs(state, argv, 1, &path) < 0) { + return NULL; + } + + ui_print("Formatting %s...\n", path); + if (0 != format_volume(path)) { + free(path); + return StringValue(strdup("")); + } + + if (strcmp(path, "/data") == 0 && has_datadata()) { + ui_print("Formatting /datadata...\n"); + if (0 != format_volume("/datadata")) { + free(path); + return StringValue(strdup("")); + } + if (0 != format_volume(get_android_secure_path())) { + free(path); + return StringValue(strdup("")); + } + } + +done: + return StringValue(strdup(path)); +} + +Value* BackupFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 args, got %d", name, argc); + } + char* path; + if (ReadArgs(state, argv, 1, &path) < 0) { + return NULL; + } + + if (0 != nandroid_backup(path)) + return StringValue(strdup("")); + + return StringValue(strdup(path)); +} + +Value* RestoreFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s() expects at least 1 arg", name); + } + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + char** args2 = malloc(sizeof(char*) * (argc+1)); + memcpy(args2, args, sizeof(char*) * argc); + args2[argc] = NULL; + + char* path = strdup(args2[0]); + unsigned char flags = NANDROID_BOOT | NANDROID_SYSTEM | NANDROID_DATA + | NANDROID_CACHE | NANDROID_SDEXT; + int i; + for (i = 1; i < argc; i++) + { + if (args2[i] == NULL) + continue; + if (strcmp(args2[i], "noboot") == 0) + flags ^= NANDROID_BOOT; + else if (strcmp(args2[i], "nosystem") == 0) + flags ^= NANDROID_SYSTEM; + else if (strcmp(args2[i], "nodata") == 0) + flags ^= NANDROID_DATA; + else if (strcmp(args2[i], "nocache") == 0) + flags ^= NANDROID_CACHE; + else if (strcmp(args2[i], "nosd-ext") == 0) + flags ^= NANDROID_SDEXT; + } + + for (i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + free(args2); + + if (0 != nandroid_restore(path, flags)) { + free(path); + return StringValue(strdup("")); + } + + return StringValue(path); +} + +Value* InstallZipFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 args, got %d", name, argc); + } + char* path; + if (ReadArgs(state, argv, 1, &path) < 0) { + return NULL; + } + + if (0 != install_zip(path)) + return StringValue(strdup("")); + + return StringValue(strdup(path)); +} + +Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 args, got %d", name, argc); + } + char* path; + if (ReadArgs(state, argv, 1, &path) < 0) { + return NULL; + } + + if (0 != ensure_path_mounted(path)) + return StringValue(strdup("")); + + return StringValue(strdup(path)); +} + +static void RegisterRecoveryHooks() { + RegisterFunction("mount", MountFn); + RegisterFunction("format", FormatFn); + RegisterFunction("ui_print", UIPrintFn); + RegisterFunction("run_program", RunProgramFn); + RegisterFunction("backup_rom", BackupFn); + RegisterFunction("restore_rom", RestoreFn); + RegisterFunction("install_zip", InstallZipFn); +} + +static int hasInitializedEdify = 0; +static int run_script_from_buffer(char* script_data, int script_len, char* filename) { + if (!hasInitializedEdify) { + RegisterBuiltins(); + RegisterRecoveryHooks(); + FinishRegistration(); + hasInitializedEdify = 1; + } + + Expr* root; + int error_count = 0; + yy_scan_bytes(script_data, script_len); + int error = yyparse(&root, &error_count); + printf("parse returned %d; %d errors encountered\n", error, error_count); + if (error == 0 || error_count > 0) { + //ExprDump(0, root, buffer); + + State state; + state.cookie = NULL; + state.script = script_data; + state.errmsg = NULL; + + char* result = Evaluate(&state, root); + if (result == NULL) { + printf("result was NULL, message is: %s\n", + (state.errmsg == NULL ? "(NULL)" : state.errmsg)); + free(state.errmsg); + return -1; + } else { + printf("result is [%s]\n", result); + } + } + return 0; +} + +int extendedcommand_file_exists() { + struct stat file_info; + return 0 == stat(EXTENDEDCOMMAND_SCRIPT, &file_info); +} + +int edify_main(int argc, char** argv) { + load_volume_table(); + process_volumes(); + RegisterBuiltins(); + RegisterRecoveryHooks(); + FinishRegistration(); + + if (argc != 2) { + printf("edify \n"); + return 1; + } + + FILE* f = fopen(argv[1], "r"); + if (f == NULL) { + printf("%s: %s: No such file or directory\n", argv[0], argv[1]); + return 1; + } + char buffer[8192]; + int size = fread(buffer, 1, 8191, f); + fclose(f); + buffer[size] = '\0'; + + Expr* root; + int error_count = 0; + yy_scan_bytes(buffer, size); + int error = yyparse(&root, &error_count); + printf("parse returned %d; %d errors encountered\n", error, error_count); + if (error == 0 || error_count > 0) { + + //ExprDump(0, root, buffer); + + State state; + state.cookie = NULL; + state.script = buffer; + state.errmsg = NULL; + + char* result = Evaluate(&state, root); + if (result == NULL) { + printf("result was NULL, message is: %s\n", + (state.errmsg == NULL ? "(NULL)" : state.errmsg)); + free(state.errmsg); + } else { + printf("result is [%s]\n", result); + } + } + return 0; +} + +static int run_script(char* filename) { + struct stat file_info; + if (0 != stat(filename, &file_info)) { + printf("Error executing stat on file: %s\n", filename); + return 1; + } + + int script_len = file_info.st_size; + char* script_data = (char*)malloc(script_len + 1); + FILE *file = fopen(filename, "rb"); + fread(script_data, script_len, 1, file); + // supposedly not necessary, but let's be safe. + script_data[script_len] = '\0'; + fclose(file); + LOGI("Running script:\n"); + LOGI("\n%s\n", script_data); + + int ret = run_script_from_buffer(script_data, script_len, filename); + free(script_data); + return ret; +} + +int run_and_remove_extendedcommand() { + char* primary_path = get_primary_storage_path(); + char tmp[PATH_MAX]; + sprintf(tmp, "cp %s /tmp/%s", EXTENDEDCOMMAND_SCRIPT, basename(EXTENDEDCOMMAND_SCRIPT)); + __system(tmp); + remove(EXTENDEDCOMMAND_SCRIPT); + int i = 0; + for (i = 20; i > 0; i--) { + ui_print("Waiting for SD Card to mount (%ds)\n", i); + if (ensure_path_mounted(primary_path) == 0) { + ui_print("SD Card mounted...\n"); + break; + } + sleep(1); + } + remove("/sdcard/clockworkmod/.recoverycheckpoint"); + if (i == 0) { + ui_print("Timed out waiting for SD card... continuing anyways."); + } + + ui_print("Verifying SD Card marker...\n"); + struct stat st; + if (stat("/sdcard/clockworkmod/.salted_hash", &st) != 0) { + ui_print("SD Card marker not found...\n"); + if (volume_for_path("/emmc") != NULL) { + ui_print("Checking Internal SD Card marker...\n"); + ensure_path_unmounted(primary_path); + if (ensure_path_mounted_at_mount_point("/emmc", primary_path) != 0) { + ui_print("Internal SD Card marker not found... continuing anyways.\n"); + // unmount everything, and remount as normal + ensure_path_unmounted("/emmc"); + ensure_path_unmounted(primary_path); + + ensure_path_mounted(primary_path); + } + } + } + + sprintf(tmp, "/tmp/%s", basename(EXTENDEDCOMMAND_SCRIPT)); + int ret; +#ifdef I_AM_KOUSH + if (0 != (ret = before_run_script(tmp))) { + ui_print("Error processing ROM Manager script. Please verify that you are performing the backup, restore, or ROM installation from ROM Manager v4.4.0.0 or higher.\n"); + return ret; + } +#endif + return run_script(tmp); +} diff --git a/edifyscripting.h b/edifyscripting.h new file mode 100644 index 000000000..acf9ceb52 --- /dev/null +++ b/edifyscripting.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __EDIFYSCRIPTING_H +#define __EDIFYSCRIPTING_H + +int edify_main(int argc, char** argv); +int extendedcommand_file_exists(); +int run_and_remove_extendedcommand(); + +#endif // __EDIFYSCRIPTING_H \ No newline at end of file diff --git a/etc/init.rc b/etc/init.rc index 554d4e634..83a6f5887 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -1,19 +1,74 @@ +import /init.recovery.${ro.hardware}.rc + on early-init + # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls. + write /sys/fs/selinux/checkreqprot 0 + + # Set the security context for the init process. + # This should occur before anything else (e.g. ueventd) is started. + setcon u:r:init:s0 + start ueventd + start healthd on init - export PATH /sbin + export PATH /sbin:/system/bin export ANDROID_ROOT /system export ANDROID_DATA /data export EXTERNAL_STORAGE /sdcard symlink /system/etc /etc + mkdir /boot + mkdir /recovery mkdir /sdcard + mkdir /internal_sd + mkdir /external_sd + mkdir /sd-ext + mkdir /datadata + mkdir /emmc mkdir /system mkdir /data mkdir /cache - mount /tmp /tmp tmpfs + mount tmpfs tmpfs /tmp + + chown root shell /tmp + chmod 0775 /tmp + + mkdir /mnt 0775 root system + mkdir /storage 0050 root sdcard_r + mount tmpfs tmpfs /storage mode=0050,uid=0,gid=1028 + + # See storage config details at http://source.android.com/tech/storage/ + mkdir /mnt/shell 0700 shell shell + + # Directory for putting things only root should see. + mkdir /mnt/secure 0700 root root + + # Create private mountpoint so we can MS_MOVE from staging + mount tmpfs tmpfs /mnt/secure mode=0700,uid=0,gid=0 + + # Directory for staging bindmounts + mkdir /mnt/secure/staging 0700 root root + + # Fuse public mount points. + mkdir /mnt/fuse 0700 root system + mount tmpfs tmpfs /mnt/fuse mode=0775,gid=1000 + +on fs + mkdir /dev/usb-ffs 0770 shell shell + mkdir /dev/usb-ffs/adb 0770 shell shell + mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000 + + write /sys/class/android_usb/android0/enable 0 + write /sys/class/android_usb/android0/idVendor 18D1 + write /sys/class/android_usb/android0/idProduct D001 + write /sys/class/android_usb/android0/f_ffs/aliases adb + write /sys/class/android_usb/android0/functions adb + write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer} + write /sys/class/android_usb/android0/iProduct ${ro.product.model} + write /sys/class/android_usb/android0/iSerial ${ro.serialno} + on boot @@ -23,28 +78,47 @@ on boot class_start default +on property:sys.powerctl=* + powerctl ${sys.powerctl} + service ueventd /sbin/ueventd critical + seclabel u:r:ueventd:s0 + +service healthd /sbin/healthd -n + critical + seclabel u:r:healthd:s0 service recovery /sbin/recovery + seclabel u:r:recovery:s0 + +service setup_adbd /sbin/setup_adbd + oneshot -service adbd /sbin/adbd recovery +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 -on property:ro.debuggable=1 - write /sys/class/android_usb/android0/enable 0 - write /sys/class/android_usb/android0/idVendor 18D1 - write /sys/class/android_usb/android0/idProduct D001 - write /sys/class/android_usb/android0/functions adb - write /sys/class/android_usb/android0/enable 1 - write /sys/class/android_usb/android0/iManufacturer $ro.product.manufacturer - write /sys/class/android_usb/android0/iProduct $ro.product.model - write /sys/class/android_usb/android0/iSerial $ro.serialno - start adbd +service vold /sbin/minivold + socket vold stream 0660 root mount + ioprio be 2 + seclabel u:r:recovery:s0 -# Restart adbd so it can run as root +# setup_adbd will start adb once it has checked the keys 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/idProduct D003 + write /sys/class/android_usb/android0/functions mass_storage,adb + 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/idProduct D001 + write /sys/class/android_usb/android0/functions adb + write /sys/class/android_usb/android0/enable ${service.adb.root} diff --git a/extendedcommands.c b/extendedcommands.c new file mode 100644 index 000000000..5c1440dc1 --- /dev/null +++ b/extendedcommands.c @@ -0,0 +1,1850 @@ +/* + * Copyright (C) 2014 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adb_install.h" +#include "bmlutils/bmlutils.h" +#include "bootloader.h" +#include "common.h" +#include "cutils/android_reboot.h" +#include "cutils/properties.h" +#include "edify/expr.h" +#include "extendedcommands.h" +#include "firmware.h" +#include "flashutils/flashutils.h" +#include "install.h" +#include "make_ext4fs.h" +#include "minui/minui.h" +#include "minzip/DirUtil.h" +#include "mmcutils/mmcutils.h" +#include "mounts.h" +#include "mtdutils/mtdutils.h" +#include "nandroid.h" +#include "recovery_settings.h" +#include "recovery_ui.h" +#include "roots.h" +#include "voldclient/voldclient.h" + +// top fixed menu items, those before extra storage volumes +#define FIXED_TOP_INSTALL_ZIP_MENUS 1 +// bottom fixed menu items, those after extra storage volumes +#define FIXED_BOTTOM_INSTALL_ZIP_MENUS 3 +#define FIXED_INSTALL_ZIP_MENUS (FIXED_TOP_INSTALL_ZIP_MENUS + FIXED_BOTTOM_INSTALL_ZIP_MENUS) + +// number of actions added for each volume by add_nandroid_options_for_volume() +// these go on top of menu list +#define NANDROID_ACTIONS_NUM 4 +// number of fixed bottom entries after volume actions +#define NANDROID_FIXED_ENTRIES 2 + +#if defined(ENABLE_LOKI) && defined(BOARD_NATIVE_DUALBOOT_SINGLEDATA) +#define FIXED_ADVANCED_ENTRIES 10 +#elif !defined(ENABLE_LOKI) && defined(BOARD_NATIVE_DUALBOOT_SINGLEDATA) +#define FIXED_ADVANCED_ENTRIES 9 +#elif defined(ENABLE_LOKI) && !defined(BOARD_NATIVE_DUALBOOT_SINGLEDATA) +#define FIXED_ADVANCED_ENTRIES 8 +#else +#define FIXED_ADVANCED_ENTRIES 7 +#endif + +extern struct selabel_handle *sehandle; +int signature_check_enabled = 1; + +typedef struct { + char mount[255]; + char unmount[255]; + char path[PATH_MAX]; +} MountMenuEntry; + +typedef struct { + char txt[255]; + char path[PATH_MAX]; + char type[255]; +} FormatMenuEntry; + +typedef struct { + char *name; + int can_mount; + int can_format; +} MFMatrix; + +// Prototypes of private functions that are used before defined +static void show_choose_zip_menu(const char *mount_point); +static void format_sdcard(const char* volume); +static int can_partition(const char* volume); +static int is_path_mounted(const char* path); + +static int get_filtered_menu_selection(const char** headers, char** items, int menu_only, int initial_selection, int items_count) { + int index; + int offset = 0; + int* translate_table = (int*)malloc(sizeof(int) * items_count); + char* items_new[items_count]; + + for (index = 0; index < items_count; index++) { + items_new[index] = items[index]; + } + + for (index = 0; index < items_count; index++) { + if (items_new[index] == NULL) + continue; + char *item = items_new[index]; + items_new[index] = NULL; + items_new[offset] = item; + translate_table[offset] = index; + offset++; + } + items_new[offset] = NULL; + + initial_selection = translate_table[initial_selection]; + int ret = get_menu_selection(headers, items_new, menu_only, initial_selection); + if (ret < 0 || ret >= offset) { + free(translate_table); + return ret; + } + + ret = translate_table[ret]; + free(translate_table); + return ret; +} + +static void write_string_to_file(const char* filename, const char* string) { + ensure_path_mounted(filename); + char tmp[PATH_MAX]; + sprintf(tmp, "mkdir -p $(dirname %s)", filename); + __system(tmp); + FILE *file = fopen(filename, "w"); + if (file != NULL) { + fprintf(file, "%s", string); + fclose(file); + } +} + +void write_recovery_version() { + char path[PATH_MAX]; + sprintf(path, "%s%s%s", get_primary_storage_path(), (is_data_media() ? "/0/" : "/"), RECOVERY_VERSION_FILE); + write_string_to_file(path, EXPAND(RECOVERY_VERSION) "\n" EXPAND(TARGET_DEVICE)); + // force unmount /data for /data/media devices as we call this on recovery exit + preserve_data_media(0); + ensure_path_unmounted(path); + preserve_data_media(1); +} + +static void write_last_install_path(const char* install_path) { + char path[PATH_MAX]; + sprintf(path, "%s%s%s", get_primary_storage_path(), (is_data_media() ? "/0/" : "/"), RECOVERY_LAST_INSTALL_FILE); + write_string_to_file(path, install_path); +} + +static char* read_last_install_path() { + static char path[PATH_MAX]; + sprintf(path, "%s%s%s", get_primary_storage_path(), (is_data_media() ? "/0/" : "/"), RECOVERY_LAST_INSTALL_FILE); + + ensure_path_mounted(path); + FILE *f = fopen(path, "r"); + if (f != NULL) { + fgets(path, PATH_MAX, f); + fclose(f); + + return path; + } + return NULL; +} + +static void toggle_signature_check() { + signature_check_enabled = !signature_check_enabled; + ui_print("Signature Check: %s\n", signature_check_enabled ? "Enabled" : "Disabled"); +} + +#ifdef ENABLE_LOKI +int loki_support_enabled = 1; +void toggle_loki_support() { + loki_support_enabled = !loki_support_enabled; + ui_print("Loki Support: %s\n", loki_support_enabled ? "Enabled" : "Disabled"); +} +#endif + +int install_zip(const char* packagefilepath) { + ui_print("\n-- Installing: %s\n", packagefilepath); + if (device_flash_type() == MTD) { + set_sdcard_update_bootloader_message(); + } + + int status = install_package(packagefilepath); + ui_reset_progress(); + if (status != INSTALL_SUCCESS) { + ui_set_background(BACKGROUND_ICON_ERROR); + ui_print("Installation aborted.\n"); + return 1; + } +#ifdef ENABLE_LOKI + if (loki_support_enabled) { + ui_print("Checking if loki-fying is needed\n"); + status = loki_check(); + if (status != INSTALL_SUCCESS) { + ui_set_background(BACKGROUND_ICON_ERROR); + return 1; + } + } +#endif + + ui_set_background(BACKGROUND_ICON_CLOCKWORK); + ui_print("\nInstall from sdcard complete.\n"); + return 0; +} + +int show_install_update_menu() { + char buf[100]; + int i = 0, chosen_item = 0; + static char* install_menu_items[MAX_NUM_MANAGED_VOLUMES + FIXED_INSTALL_ZIP_MENUS + 1]; + + char* primary_path = get_primary_storage_path(); + char** extra_paths = get_extra_storage_paths(); + int num_extra_volumes = get_num_extra_volumes(); + + memset(install_menu_items, 0, MAX_NUM_MANAGED_VOLUMES + FIXED_INSTALL_ZIP_MENUS + 1); + + static const char* headers[] = { "Install update from zip file", "", NULL }; + + // FIXED_TOP_INSTALL_ZIP_MENUS + sprintf(buf, "choose zip from %s", primary_path); + install_menu_items[0] = strdup(buf); + + // extra storage volumes (vold managed) + for (i = 0; i < num_extra_volumes; i++) { + sprintf(buf, "choose zip from %s", extra_paths[i]); + install_menu_items[FIXED_TOP_INSTALL_ZIP_MENUS + i] = strdup(buf); + } + + // FIXED_BOTTOM_INSTALL_ZIP_MENUS + install_menu_items[FIXED_TOP_INSTALL_ZIP_MENUS + num_extra_volumes] = "choose zip from last install folder"; + install_menu_items[FIXED_TOP_INSTALL_ZIP_MENUS + num_extra_volumes + 1] = "install zip from sideload"; + install_menu_items[FIXED_TOP_INSTALL_ZIP_MENUS + num_extra_volumes + 2] = "toggle signature verification"; + + // extra NULL for GO_BACK + install_menu_items[FIXED_TOP_INSTALL_ZIP_MENUS + num_extra_volumes + 3] = NULL; + + for (;;) { + chosen_item = get_menu_selection(headers, install_menu_items, 0, 0); + if (chosen_item == 0) { + show_choose_zip_menu(primary_path); + } else if (chosen_item >= FIXED_TOP_INSTALL_ZIP_MENUS && chosen_item < FIXED_TOP_INSTALL_ZIP_MENUS + num_extra_volumes) { + show_choose_zip_menu(extra_paths[chosen_item - FIXED_TOP_INSTALL_ZIP_MENUS]); + } else if (chosen_item == FIXED_TOP_INSTALL_ZIP_MENUS + num_extra_volumes) { + char *last_path_used = read_last_install_path(); + if (last_path_used == NULL) + show_choose_zip_menu(primary_path); + else + show_choose_zip_menu(last_path_used); + } else if (chosen_item == FIXED_TOP_INSTALL_ZIP_MENUS + num_extra_volumes + 1) { + apply_from_adb(); + } else if (chosen_item == FIXED_TOP_INSTALL_ZIP_MENUS + num_extra_volumes + 2) { + toggle_signature_check(); + } else { + // GO_BACK or REFRESH (chosen_item < 0) + goto out; + } + } +out: + free(install_menu_items[0]); + if (extra_paths != NULL) { + for (i = 0; i < num_extra_volumes; i++) + free(install_menu_items[FIXED_TOP_INSTALL_ZIP_MENUS + i]); + } + return chosen_item; +} + + +static void free_string_array(char** array) { + if (array == NULL) + return; + char* cursor = array[0]; + int i = 0; + while (cursor != NULL) { + free(cursor); + cursor = array[++i]; + } + free(array); +} + +static char** gather_files(const char* directory, const char* fileExtensionOrDirectory, int* numFiles) { + char path[PATH_MAX] = ""; + DIR *dir; + struct dirent *de; + int total = 0; + int i; + char** files = NULL; + int pass; + *numFiles = 0; + int dirLen = strlen(directory); + + dir = opendir(directory); + if (dir == NULL) { + ui_print("Couldn't open directory.\n"); + return NULL; + } + + unsigned int extension_length = 0; + if (fileExtensionOrDirectory != NULL) + extension_length = strlen(fileExtensionOrDirectory); + + int isCounting = 1; + i = 0; + for (pass = 0; pass < 2; pass++) { + while ((de = readdir(dir)) != NULL) { + // skip hidden files + if (de->d_name[0] == '.') + continue; + + // NULL means that we are gathering directories, so skip this + if (fileExtensionOrDirectory != NULL) { + // make sure that we can have the desired extension (prevent seg fault) + if (strlen(de->d_name) < extension_length) + continue; + // compare the extension + if (strcmp(de->d_name + strlen(de->d_name) - extension_length, fileExtensionOrDirectory) != 0) + continue; + } else { + struct stat info; + char fullFileName[PATH_MAX]; + strcpy(fullFileName, directory); + strcat(fullFileName, de->d_name); + lstat(fullFileName, &info); + // make sure it is a directory + if (!(S_ISDIR(info.st_mode))) + continue; + } + + if (pass == 0) { + total++; + continue; + } + + files[i] = (char*)malloc(dirLen + strlen(de->d_name) + 2); + strcpy(files[i], directory); + strcat(files[i], de->d_name); + if (fileExtensionOrDirectory == NULL) + strcat(files[i], "/"); + i++; + } + if (pass == 1) + break; + if (total == 0) + break; + rewinddir(dir); + *numFiles = total; + files = (char**)malloc((total + 1) * sizeof(char*)); + files[total] = NULL; + } + + if (closedir(dir) < 0) { + LOGE("Failed to close directory.\n"); + } + + if (total == 0) { + return NULL; + } + // sort the result + if (files != NULL) { + for (i = 0; i < total; i++) { + int curMax = -1; + int j; + for (j = 0; j < total - i; j++) { + if (curMax == -1 || strcmp(files[curMax], files[j]) < 0) + curMax = j; + } + char* temp = files[curMax]; + files[curMax] = files[total - i - 1]; + files[total - i - 1] = temp; + } + } + + return files; +} + +// pass in NULL for fileExtensionOrDirectory and you will get a directory chooser +static char* choose_file_menu(const char* basedir, const char* fileExtensionOrDirectory, const char* headers[]) { + const char* fixed_headers[20]; + int numFiles = 0; + int numDirs = 0; + int i; + char* return_value = NULL; + char directory[PATH_MAX]; + int dir_len = strlen(basedir); + + strcpy(directory, basedir); + + // Append a trailing slash if necessary + if (directory[dir_len - 1] != '/') { + strcat(directory, "/"); + dir_len++; + } + + i = 0; + while (headers[i]) { + fixed_headers[i] = headers[i]; + i++; + } + fixed_headers[i] = directory; + fixed_headers[i + 1] = ""; + fixed_headers[i + 2] = NULL; + + char** files = gather_files(directory, fileExtensionOrDirectory, &numFiles); + char** dirs = NULL; + if (fileExtensionOrDirectory != NULL) + dirs = gather_files(directory, NULL, &numDirs); + int total = numDirs + numFiles; + if (total == 0) { + ui_print("No files found.\n"); + } else { + char** list = (char**)malloc((total + 1) * sizeof(char*)); + list[total] = NULL; + + + for (i = 0; i < numDirs; i++) { + list[i] = strdup(dirs[i] + dir_len); + } + + for (i = 0; i < numFiles; i++) { + list[numDirs + i] = strdup(files[i] + dir_len); + } + + for (;;) { + int chosen_item = get_menu_selection(fixed_headers, list, 0, 0); + if (chosen_item == GO_BACK || chosen_item == REFRESH) + break; + if (chosen_item < numDirs) { + char* subret = choose_file_menu(dirs[chosen_item], fileExtensionOrDirectory, headers); + if (subret != NULL) { + return_value = strdup(subret); + free(subret); + break; + } + continue; + } + return_value = strdup(files[chosen_item - numDirs]); + break; + } + free_string_array(list); + } + + free_string_array(files); + free_string_array(dirs); + return return_value; +} + +static void show_choose_zip_menu(const char *mount_point) { + if (ensure_path_mounted(mount_point) != 0) { + LOGE("Can't mount %s\n", mount_point); + return; + } + + static const char* headers[] = { "Choose a zip to apply", "", NULL }; + + char* file = choose_file_menu(mount_point, ".zip", headers); + if (file == NULL) + return; + char confirm[PATH_MAX]; + sprintf(confirm, "Yes - Install %s", basename(file)); + + if (confirm_selection("Confirm install?", confirm)) { + install_zip(file); + write_last_install_path(dirname(file)); + } + + free(file); +} + +static void show_nandroid_restore_menu(const char* path) { + if (ensure_path_mounted(path) != 0) { + LOGE("Can't mount %s\n", path); + return; + } + + static const char* headers[] = { "Choose an image to restore", "", NULL }; + + char tmp[PATH_MAX]; + sprintf(tmp, "%s/clockworkmod/backup/", path); + char* file = choose_file_menu(tmp, NULL, headers); + if (file == NULL) + return; + + if (confirm_selection("Confirm restore?", "Yes - Restore")) { + unsigned char flags = NANDROID_BOOT | NANDROID_SYSTEM | NANDROID_DATA + | NANDROID_CACHE | NANDROID_SDEXT; + nandroid_restore(file, flags); + } + + free(file); +} + +static void show_nandroid_delete_menu(const char* path) { + if (ensure_path_mounted(path) != 0) { + LOGE("Can't mount %s\n", path); + return; + } + + static const char* headers[] = { "Choose an image to delete", "", NULL }; + + char tmp[PATH_MAX]; + sprintf(tmp, "%s/clockworkmod/backup/", path); + char* file = choose_file_menu(tmp, NULL, headers); + if (file == NULL) + return; + + if (confirm_selection("Confirm delete?", "Yes - Delete")) { + sprintf(tmp, "rm -rf %s", file); + __system(tmp); + } + + free(file); +} + +static int control_usb_storage(bool on) { + int i = 0; + int num = 0; + + for (i = 0; i < get_num_volumes(); i++) { + Volume *v = get_device_volumes() + i; + if (fs_mgr_is_voldmanaged(v) && vold_is_volume_available(v->mount_point)) { + if (on) { + vold_share_volume(v->mount_point); + } else { + vold_unshare_volume(v->mount_point, 1); + } + property_set("sys.storage.ums_enabled", on ? "1" : "0"); + num++; + } + } + return num; +} + +static void show_mount_usb_storage_menu() { + // Enable USB storage using vold + if (!control_usb_storage(true)) + return; + + static const char* headers[] = { "USB Mass Storage device", + "Leaving this menu unmounts", + "your SD card from your PC.", + "", + NULL + }; + + static char* list[] = { "Unmount", NULL }; + + for (;;) { + int chosen_item = get_menu_selection(headers, list, 0, 0); + if (chosen_item == GO_BACK || chosen_item == 0) + break; + } + + // Disable USB storage + control_usb_storage(false); +} + +int confirm_selection(const char* title, const char* confirm) { + struct stat info; + int ret = 0; + + char path[PATH_MAX]; + sprintf(path, "%s%s%s", get_primary_storage_path(), (is_data_media() ? "/0/" : "/"), RECOVERY_NO_CONFIRM_FILE); + ensure_path_mounted(path); + if (0 == stat(path, &info)) + return 1; + +#ifdef BOARD_NATIVE_DUALBOOT + char buf[PATH_MAX]; + device_build_selection_title(buf, title); + title = (char*)&buf; +#endif + + int many_confirm; + char* confirm_str = strdup(confirm); + const char* confirm_headers[] = { title, " THIS CAN NOT BE UNDONE.", "", NULL }; + int old_val = ui_is_showing_back_button(); + ui_set_showing_back_button(0); + + sprintf(path, "%s%s%s", get_primary_storage_path(), (is_data_media() ? "/0/" : "/"), RECOVERY_MANY_CONFIRM_FILE); + ensure_path_mounted(path); + many_confirm = 0 == stat(path, &info); + + if (many_confirm) { + char* items[] = { "No", + "No", + "No", + "No", + "No", + "No", + "No", + confirm_str, // Yes, [7] + "No", + "No", + "No", + NULL }; + int chosen_item = get_menu_selection(confirm_headers, items, 0, 0); + ret = (chosen_item == 7); + } else { + char* items[] = { "No", + confirm_str, // Yes, [1] + NULL }; + int chosen_item = get_menu_selection(confirm_headers, items, 0, 0); + ret = (chosen_item == 1); + } + + free(confirm_str); + ui_set_showing_back_button(old_val); + return ret; +} + +int format_device(const char *device, const char *path, const char *fs_type) { +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + if(device_truedualboot_format_device(device, path, fs_type) <= 0) + return 0; +#endif + if (is_data_media_volume_path(path)) { + return format_unknown_device(NULL, path, NULL); + } + if (strstr(path, "/data") == path && is_data_media()) { + return format_unknown_device(NULL, path, NULL); + } + + Volume* v = volume_for_path(path); + if (v == NULL) { + // silent failure for sd-ext + if (strcmp(path, "/sd-ext") != 0) + LOGE("unknown volume '%s'\n", path); + return -1; + } + + if (strcmp(fs_type, "ramdisk") == 0) { + // you can't format the ramdisk. + LOGE("can't format_volume \"%s\"", path); + return -1; + } + + if (strcmp(fs_type, "rfs") == 0) { + if (ensure_path_unmounted(path) != 0) { + LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point); + return -1; + } + if (0 != format_rfs_device(device, path)) { + LOGE("format_volume: format_rfs_device failed on %s\n", device); + return -1; + } + return 0; + } + + if (strcmp(v->mount_point, path) != 0) { + return format_unknown_device(v->blk_device, path, NULL); + } + + if (ensure_path_unmounted(path) != 0) { + LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point); + return -1; + } + + if (strcmp(fs_type, "yaffs2") == 0 || strcmp(fs_type, "mtd") == 0) { + mtd_scan_partitions(); + const MtdPartition* partition = mtd_find_partition_by_name(device); + if (partition == NULL) { + LOGE("format_volume: no MTD partition \"%s\"\n", device); + return -1; + } + + MtdWriteContext *write = mtd_write_partition(partition); + if (write == NULL) { + LOGW("format_volume: can't open MTD \"%s\"\n", device); + return -1; + } else if (mtd_erase_blocks(write, -1) == (off_t) - 1) { + LOGW("format_volume: can't erase MTD \"%s\"\n", device); + mtd_write_close(write); + return -1; + } else if (mtd_write_close(write)) { + LOGW("format_volume: can't close MTD \"%s\"\n", device); + return -1; + } + return 0; + } + + if (strcmp(fs_type, "ext4") == 0) { + int length = 0; + if (strcmp(v->fs_type, "ext4") == 0) { + // Our desired filesystem matches the one in fstab, respect v->length + length = v->length; + } + + int result = make_ext4fs(device, length, v->mount_point, sehandle); + if (result != 0) { + LOGE("format_volume: make_ext4fs failed on %s\n", device); + return -1; + } + return 0; + } +#ifdef USE_F2FS + if (strcmp(fs_type, "f2fs") == 0) { + char* args[] = { "mkfs.f2fs", v->blk_device }; + if (make_f2fs_main(2, args) != 0) { + LOGE("format_volume: mkfs.f2fs failed on %s\n", v->blk_device); + return -1; + } + return 0; + } +#endif + return format_unknown_device(device, path, fs_type); +} + +int format_unknown_device(const char *device, const char* path, const char *fs_type) { + LOGI("Formatting unknown device.\n"); + + if (fs_type != NULL && get_flash_type(fs_type) != UNSUPPORTED) + return erase_raw_partition(fs_type, device); + + // if this is SDEXT:, don't worry about it if it does not exist. + if (0 == strcmp(path, "/sd-ext")) { + struct stat st; + Volume *vol = volume_for_path("/sd-ext"); + if (vol == NULL || 0 != stat(vol->blk_device, &st)) { + LOGI("No app2sd partition found. Skipping format of /sd-ext.\n"); + return 0; + } + } + + if (NULL != fs_type) { + if (strcmp("ext3", fs_type) == 0) { + LOGI("Formatting ext3 device.\n"); + if (0 != ensure_path_unmounted(path)) { + LOGE("Error while unmounting %s.\n", path); + return -12; + } + return format_ext3_device(device); + } + + if (strcmp("ext2", fs_type) == 0) { + LOGI("Formatting ext2 device.\n"); + if (0 != ensure_path_unmounted(path)) { + LOGE("Error while unmounting %s.\n", path); + return -12; + } + return format_ext2_device(device); + } + } + + if (0 != ensure_path_mounted(path)) { + ui_print("Error mounting %s!\n", path); + ui_print("Skipping format...\n"); + return 0; + } + + char tmp[PATH_MAX]; + if (strcmp(path, "/data") == 0) { + sprintf(tmp, "cd /data ; for f in $(ls -a | grep -v ^media$); do rm -rf $f; done"); + __system(tmp); + // if the /data/media sdcard has already been migrated for android 4.2, + // prevent the migration from happening again by writing the .layout_version + struct stat st; + if (0 == lstat("/data/media/0", &st)) { + char* layout_version = "2"; + FILE* f = fopen("/data/.layout_version", "wb"); + if (NULL != f) { + fwrite(layout_version, 1, 2, f); + fclose(f); + } else { + LOGI("error opening /data/.layout_version for write.\n"); + } + } else { + LOGI("/data/media/0 not found. migration may occur.\n"); + } + } else { + sprintf(tmp, "rm -rf %s/*", path); + __system(tmp); + sprintf(tmp, "rm -rf %s/.*", path); + __system(tmp); + } + + ensure_path_unmounted(path); + return 0; +} + +static MFMatrix get_mnt_fmt_capabilities(char *fs_type, char *mount_point) { + MFMatrix mfm = { mount_point, 1, 1 }; + + const int NUM_FS_TYPES = 6; + MFMatrix *fs_matrix = malloc(NUM_FS_TYPES * sizeof(MFMatrix)); + // Defined capabilities: fs_type mnt fmt + fs_matrix[0] = (MFMatrix){ "bml", 0, 1 }; + fs_matrix[1] = (MFMatrix){ "datamedia", 0, 1 }; + fs_matrix[2] = (MFMatrix){ "emmc", 0, 1 }; + fs_matrix[3] = (MFMatrix){ "mtd", 0, 0 }; + fs_matrix[4] = (MFMatrix){ "ramdisk", 0, 0 }; + fs_matrix[5] = (MFMatrix){ "swap", 0, 0 }; + + const int NUM_MNT_PNTS = 6; + MFMatrix *mp_matrix = malloc(NUM_MNT_PNTS * sizeof(MFMatrix)); + // Defined capabilities: mount_point mnt fmt + mp_matrix[0] = (MFMatrix){ "/misc", 0, 0 }; + mp_matrix[1] = (MFMatrix){ "/radio", 0, 0 }; + mp_matrix[2] = (MFMatrix){ "/bootloader", 0, 0 }; + mp_matrix[3] = (MFMatrix){ "/recovery", 0, 0 }; + mp_matrix[4] = (MFMatrix){ "/efs", 0, 0 }; + mp_matrix[5] = (MFMatrix){ "/wimax", 0, 0 }; + + int i; + for (i = 0; i < NUM_FS_TYPES; i++) { + if (strcmp(fs_type, fs_matrix[i].name) == 0) { + mfm.can_mount = fs_matrix[i].can_mount; + mfm.can_format = fs_matrix[i].can_format; + } + } + for (i = 0; i < NUM_MNT_PNTS; i++) { + if (strcmp(mount_point, mp_matrix[i].name) == 0) { + mfm.can_mount = mp_matrix[i].can_mount; + mfm.can_format = mp_matrix[i].can_format; + } + } + + free(fs_matrix); + free(mp_matrix); + + // User-defined capabilities + char *custom_mp; + char custom_forbidden_mount[PROPERTY_VALUE_MAX]; + char custom_forbidden_format[PROPERTY_VALUE_MAX]; + property_get("ro.cwm.forbid_mount", custom_forbidden_mount, ""); + property_get("ro.cwm.forbid_format", custom_forbidden_format, ""); + + custom_mp = strtok(custom_forbidden_mount, ","); + while (custom_mp != NULL) { + if (strcmp(mount_point, custom_mp) == 0) { + mfm.can_mount = 0; + } + custom_mp = strtok(NULL, ","); + } + + custom_mp = strtok(custom_forbidden_format, ","); + while (custom_mp != NULL) { + if (strcmp(mount_point, custom_mp) == 0) { + mfm.can_format = 0; + } + custom_mp = strtok(NULL, ","); + } + + return mfm; +} + +static int is_ums_capable() { + // control_usb_storage() only supports vold managed storage + int i; + + // If USB volume is available, UMS not possible (assumes one USB/device) + for (i = 0; i < get_num_volumes(); i++) { + Volume *v = get_device_volumes() + i; + if (fs_mgr_is_voldmanaged(v) && vold_is_volume_available(v->mount_point) + && (strcasestr(v->label, "usb") || strcasestr(v->label, "otg"))) + return 0; + } + + // No USB storage found, look for any other vold managed storage + for (i = 0; i < get_num_volumes(); i++) { + Volume *v = get_device_volumes() + i; + if (fs_mgr_is_voldmanaged(v) && vold_is_volume_available(v->mount_point)) + return 1; + } + + return 0; +} + +int show_partition_menu() { + static const char* headers[] = { "Mounts and Storage Menu", "", NULL }; + + static char* confirm_format = "Confirm format?"; + static char* confirm = "Yes - Format"; + char confirm_string[255]; + + static MountMenuEntry* mount_menu = NULL; + static FormatMenuEntry* format_menu = NULL; + static char* list[256]; + + int i, mountable_volumes, formatable_volumes; + int num_volumes; + int chosen_item = 0; + int menu_entries = 0; + + struct menu_extras { + int dm; // boolean: enable wipe data media + int ums; // boolean: enable mount usb mass storage + int idm; // index of wipe dm entry in list[] + int iums; // index of ums entry in list[] + }; + struct menu_extras me; + + num_volumes = get_num_volumes(); + + if (!num_volumes) + return 0; + + mountable_volumes = 0; + formatable_volumes = 0; + + mount_menu = malloc(num_volumes * sizeof(MountMenuEntry)); + format_menu = malloc(num_volumes * sizeof(FormatMenuEntry)); + + for (i = 0; i < num_volumes; i++) { + Volume* v = get_device_volumes() + i; + + if (fs_mgr_is_voldmanaged(v) && !vold_is_volume_available(v->mount_point)) { + continue; + } + + MFMatrix mfm = get_mnt_fmt_capabilities(v->fs_type, v->mount_point); + + if (mfm.can_mount) { + sprintf(mount_menu[mountable_volumes].mount, "mount %s", v->mount_point); + sprintf(mount_menu[mountable_volumes].unmount, "unmount %s", v->mount_point); + sprintf(mount_menu[mountable_volumes].path, "%s", v->mount_point); + ++mountable_volumes; + } + if (mfm.can_format) { + sprintf(format_menu[formatable_volumes].txt, "format %s", v->mount_point); + sprintf(format_menu[formatable_volumes].path, "%s", v->mount_point); + sprintf(format_menu[formatable_volumes].type, "%s", v->fs_type); + ++formatable_volumes; + } + } + + for (;;) { + for (i = 0; i < mountable_volumes; i++) { + MountMenuEntry* e = &mount_menu[i]; + if (is_path_mounted(e->path)) + list[i] = e->unmount; + else + list[i] = e->mount; + } + + for (i = 0; i < formatable_volumes; i++) { + FormatMenuEntry* e = &format_menu[i]; + list[mountable_volumes + i] = e->txt; + } + + menu_entries = mountable_volumes + formatable_volumes; + me = (struct menu_extras){ 0, 0, 0, 0 }; + + if (me.dm = is_data_media()) { + me.idm = menu_entries; + list[me.idm] = "format /data and /data/media (/sdcard)"; + menu_entries++; + } + if (me.ums = is_ums_capable()) { + me.iums = menu_entries; + list[me.iums] = "mount USB storage"; + menu_entries++; + } + list[menu_entries] = '\0'; + + chosen_item = get_menu_selection(headers, list, 0, 0); + if (chosen_item >= menu_entries || chosen_item < 0) + break; + + if (chosen_item < mountable_volumes) { + MountMenuEntry* e = &mount_menu[chosen_item]; + + if (is_path_mounted(e->path)) { + preserve_data_media(0); + if (0 != ensure_path_unmounted(e->path)) + ui_print("Error unmounting %s!\n", e->path); + preserve_data_media(1); + } else { + if (0 != ensure_path_mounted(e->path)) + ui_print("Error mounting %s!\n", e->path); + } + } else if (chosen_item < (mountable_volumes + formatable_volumes)) { + chosen_item = chosen_item - mountable_volumes; + FormatMenuEntry* e = &format_menu[chosen_item]; + + sprintf(confirm_string, "%s - %s", e->path, confirm_format); + + // support user choice fstype when formatting external storage + // ensure fstype==auto because most devices with internal vfat + // storage cannot be formatted to other types + if (strcmp(e->type, "auto") == 0) { + format_sdcard(e->path); + continue; + } + + if (!confirm_selection(confirm_string, confirm)) + continue; + ui_print("Formatting %s...\n", e->path); + if (0 != format_volume(e->path)) + ui_print("Error formatting %s!\n", e->path); + else + ui_print("Done.\n"); + } else if (me.dm && chosen_item == me.idm) { + if (!confirm_selection("format /data and /data/media (/sdcard)", confirm)) + continue; + preserve_data_media(0); + ui_print("Formatting /data...\n"); + if (0 != format_volume("/data")) + ui_print("Error formatting /data!\n"); + else + ui_print("Done.\n"); + preserve_data_media(1); + + // recreate /data/media with proper permissions + ensure_path_mounted("/data"); + setup_data_media(); + } else if (me.ums && chosen_item == me.iums) { + show_mount_usb_storage_menu(); + } + } + + free(mount_menu); + free(format_menu); + return chosen_item; +} + +static void nandroid_adv_update_selections(char *str[], int listnum, unsigned char *flags) { + int len = strlen(str[listnum]); + if (str[listnum][len-2] == ' ') { + str[listnum][len-1] = ')'; + str[listnum][len-2] = '+'; + str[listnum][len-3] = '('; + } else { + str[listnum][len-1] = ' '; + str[listnum][len-2] = ' '; + str[listnum][len-3] = ' '; + } + switch(listnum) { + case 0: + *flags ^= NANDROID_BOOT; + break; + case 1: + *flags ^= NANDROID_SYSTEM; + break; + case 2: + *flags ^= NANDROID_DATA; + break; + case 3: + *flags ^= NANDROID_CACHE; + break; + case 4: + *flags ^= NANDROID_SDEXT; + break; + case 5: + *flags ^= NANDROID_WIMAX; + break; + } +} + +int empty_nandroid_bitmask(unsigned char flags) { + int ret = !(((flags & NANDROID_BOOT) == NANDROID_BOOT) || + ((flags & NANDROID_SYSTEM) == NANDROID_SYSTEM) || + ((flags & NANDROID_DATA) == NANDROID_DATA) || + ((flags & NANDROID_CACHE) == NANDROID_CACHE) || + ((flags & NANDROID_SDEXT) == NANDROID_SDEXT) || + ((flags & NANDROID_WIMAX) == NANDROID_WIMAX)); + + return ret; +} + +static void show_nandroid_advanced_restore_menu(const char* path) { + if (ensure_path_mounted(path) != 0) { + LOGE("Can't mount sdcard\n"); + return; + } + + static const char* advancedheaders[] = { "Choose an image to restore", + "", + "Choose an image to restore", + "first. The next menu will", + "show you more options.", + "", + NULL }; + + char tmp[PATH_MAX]; + sprintf(tmp, "%s/clockworkmod/backup/", path); + char* file = choose_file_menu(tmp, NULL, advancedheaders); + if (file == NULL) + return; + + static const char* headers[] = { "Advanced Restore", + "", + "Select image(s) to restore:", + NULL }; + + int disable_wimax = 0; + if (0 != get_partition_device("wimax", tmp)) + disable_wimax = 1; + + char *list[9 - disable_wimax]; + // Dynamically allocated entries will have (+) added/removed to end + // Leave space at end of string so terminator doesn't need to move + list[0] = malloc(sizeof("Restore boot ")); + list[1] = malloc(sizeof("Restore system ")); + list[2] = malloc(sizeof("Restore data ")); + list[3] = malloc(sizeof("Restore cache ")); + list[4] = malloc(sizeof("Restore sd-ext ")); + if (!disable_wimax) + list[5] = malloc(sizeof("Restore wimax ")); + list[6 - disable_wimax] = "Start restore"; + list[7 - disable_wimax] = NULL; + + sprintf(list[0], "Restore boot "); + sprintf(list[1], "Restore system "); + sprintf(list[2], "Restore data "); + sprintf(list[3], "Restore cache "); + sprintf(list[4], "Restore sd-ext "); + if (!disable_wimax) + sprintf(list[5], "Restore wimax "); + + unsigned char flags = NANDROID_NONE; + int reload_menu; + int start_restore = 6-disable_wimax; + int chosen_item; + + do { + reload_menu = 0; + chosen_item = get_menu_selection(headers, list, 0, 0); + if (chosen_item < 0 || chosen_item > start_restore) + break; + + if (chosen_item < start_restore) { + nandroid_adv_update_selections(list, chosen_item, &flags); + } else if ((chosen_item == start_restore) && empty_nandroid_bitmask(flags)) { + ui_print("No image(s) selected!\n"); + reload_menu = 1; + } + } while ((chosen_item >=0 && chosen_item < start_restore) || reload_menu); + + if (chosen_item == start_restore) + nandroid_restore(file, flags); + + free(file); + int i; + for (i = 0; i < (5-disable_wimax); i++) { + free(list[i]); + } +} + +static void run_dedupe_gc() { + char path[PATH_MAX]; + char* fmt = "%s/clockworkmod/blobs"; + char* primary_path = get_primary_storage_path(); + char** extra_paths = get_extra_storage_paths(); + int i = 0; + + sprintf(path, fmt, primary_path); + ensure_path_mounted(primary_path); + nandroid_dedupe_gc(path); + + if (extra_paths != NULL) { + for (i = 0; i < get_num_extra_volumes(); i++) { + ensure_path_mounted(extra_paths[i]); + sprintf(path, fmt, extra_paths[i]); + nandroid_dedupe_gc(path); + } + } +} + +static void choose_default_backup_format() { + static const char* headers[] = { "Default Backup Format", "", NULL }; + + int fmt = nandroid_get_default_backup_format(); + + char **list; + char* list_tar_default[] = { "tar (default)", + "dup", + "tar + gzip", + NULL }; + char* list_dup_default[] = { "tar", + "dup (default)", + "tar + gzip", + NULL }; + char* list_tgz_default[] = { "tar", + "dup", + "tar + gzip (default)", + NULL }; + + if (fmt == NANDROID_BACKUP_FORMAT_DUP) { + list = list_dup_default; + } else if (fmt == NANDROID_BACKUP_FORMAT_TGZ) { + list = list_tgz_default; + } else { + list = list_tar_default; + } + + char path[PATH_MAX]; + sprintf(path, "%s%s%s", get_primary_storage_path(), (is_data_media() ? "/0/" : "/"), NANDROID_BACKUP_FORMAT_FILE); + int chosen_item = get_menu_selection(headers, list, 0, 0); + switch (chosen_item) { + case 0: { + write_string_to_file(path, "tar"); + ui_print("Default backup format set to tar.\n"); + break; + } + case 1: { + write_string_to_file(path, "dup"); + ui_print("Default backup format set to dedupe.\n"); + break; + } + case 2: { + write_string_to_file(path, "tgz"); + ui_print("Default backup format set to tar + gzip.\n"); + break; + } + } +} + +static void add_nandroid_options_for_volume(char** menu, char* path, int offset) { + char buf[100]; + + sprintf(buf, "backup to %s", path); + menu[offset] = strdup(buf); + + sprintf(buf, "restore from %s", path); + menu[offset + 1] = strdup(buf); + + sprintf(buf, "delete from %s", path); + menu[offset + 2] = strdup(buf); + + sprintf(buf, "advanced restore from %s", path); + menu[offset + 3] = strdup(buf); +} + +int show_nandroid_menu() { + char* primary_path = get_primary_storage_path(); + char** extra_paths = get_extra_storage_paths(); + int num_extra_volumes = get_num_extra_volumes(); + int i = 0, offset = 0, chosen_item = 0; + char* chosen_path = NULL; + int action_entries_num = (num_extra_volumes + 1) * NANDROID_ACTIONS_NUM; + + static const char* headers[] = { "Backup and Restore", "", NULL }; + + // (MAX_NUM_MANAGED_VOLUMES + 1) for primary_path (/sdcard) + // + 1 for extra NULL entry + static char* list[((MAX_NUM_MANAGED_VOLUMES + 1) * NANDROID_ACTIONS_NUM) + NANDROID_FIXED_ENTRIES + 1]; + + // actions for primary_path + add_nandroid_options_for_volume(list, primary_path, offset); + offset += NANDROID_ACTIONS_NUM; + + // actions for voldmanaged volumes + if (extra_paths != NULL) { + for (i = 0; i < num_extra_volumes; i++) { + add_nandroid_options_for_volume(list, extra_paths[i], offset); + offset += NANDROID_ACTIONS_NUM; + } + } + // fixed bottom entries + list[offset] = "free unused backup data"; + list[offset + 1] = "choose default backup format"; + offset += NANDROID_FIXED_ENTRIES; + +#ifdef RECOVERY_EXTEND_NANDROID_MENU + extend_nandroid_menu(list, offset, sizeof(list) / sizeof(char*)); + offset++; +#endif + + // extra NULL for GO_BACK + list[offset] = NULL; + offset++; + + for (;;) { + chosen_item = get_filtered_menu_selection(headers, list, 0, 0, offset); + if (chosen_item == GO_BACK || chosen_item == REFRESH) + break; + + // fixed bottom entries + if (chosen_item == action_entries_num) { + run_dedupe_gc(); + } else if (chosen_item == (action_entries_num + 1)) { + choose_default_backup_format(); + } else if (chosen_item < action_entries_num) { + // get nandroid volume actions path + if (chosen_item < NANDROID_ACTIONS_NUM) { + chosen_path = primary_path; + } else if (extra_paths != NULL) { + chosen_path = extra_paths[(chosen_item / NANDROID_ACTIONS_NUM) - 1]; + } + // process selected nandroid action + int chosen_subitem = chosen_item % NANDROID_ACTIONS_NUM; + switch (chosen_subitem) { + case 0: { + char backup_path[PATH_MAX]; + time_t t = time(NULL); + struct tm *tmp = localtime(&t); + if (tmp == NULL) { + struct timeval tp; + gettimeofday(&tp, NULL); + sprintf(backup_path, "%s/clockworkmod/backup/%ld", chosen_path, tp.tv_sec); + } else { + char path_fmt[PATH_MAX]; + strftime(path_fmt, sizeof(path_fmt), "clockworkmod/backup/%F.%H.%M.%S", tmp); + // this sprintf results in: + // clockworkmod/backup/%F.%H.%M.%S (time values are populated too) + sprintf(backup_path, "%s/%s", chosen_path, path_fmt); + } + nandroid_backup(backup_path); + break; + } + case 1: + show_nandroid_restore_menu(chosen_path); + break; + case 2: + show_nandroid_delete_menu(chosen_path); + break; + case 3: + show_nandroid_advanced_restore_menu(chosen_path); + break; + default: + break; + } + } else { +#ifdef RECOVERY_EXTEND_NANDROID_MENU + handle_nandroid_menu(action_entries_num + NANDROID_FIXED_ENTRIES, chosen_item); +#endif + goto out; + } + } +out: + for (i = 0; i < action_entries_num; i++) + free(list[i]); + return chosen_item; +} + +static void format_sdcard(const char* volume) { + if (is_data_media_volume_path(volume)) + return; + + Volume *v = volume_for_path(volume); + if (v == NULL || strcmp(v->fs_type, "auto") != 0) + return; + if (!fs_mgr_is_voldmanaged(v) && !can_partition(volume)) + return; + + const char* headers[] = { "Format device:", volume, "", NULL }; + + static char* list[] = { "default", + "vfat", + "exfat", + "ntfs", + "ext4", + "ext3", + "ext2", + NULL }; + + int ret = -1; + char cmd[PATH_MAX]; + int chosen_item = get_menu_selection(headers, list, 0, 0); + if (chosen_item < 0) // REFRESH or GO_BACK + return; + if (!confirm_selection("Confirm formatting?", "Yes - Format device")) + return; + + if (ensure_path_unmounted(v->mount_point) != 0) + return; + + switch (chosen_item) { + case 0: + ret = format_volume(v->mount_point); + break; + case 1: + case 2: + case 3: + case 4: { + if (fs_mgr_is_voldmanaged(v)) { + ret = vold_custom_format_volume(v->mount_point, list[chosen_item], 1) == CommandOkay ? 0 : -1; + } else if (strcmp(list[chosen_item], "vfat") == 0) { + sprintf(cmd, "/sbin/newfs_msdos -F 32 -O android -c 8 %s", v->blk_device); + ret = __system(cmd); + } else if (strcmp(list[chosen_item], "exfat") == 0) { + sprintf(cmd, "/sbin/mkfs.exfat %s", v->blk_device); + ret = __system(cmd); + } else if (strcmp(list[chosen_item], "ntfs") == 0) { + sprintf(cmd, "/sbin/mkntfs -f %s", v->blk_device); + ret = __system(cmd); + } else if (strcmp(list[chosen_item], "ext4") == 0) { + char *secontext = NULL; + if (selabel_lookup(sehandle, &secontext, v->mount_point, S_IFDIR) < 0) { + LOGE("cannot lookup security context for %s\n", v->mount_point); + ret = make_ext4fs(v->blk_device, v->length, volume, NULL); + } else { + ret = make_ext4fs(v->blk_device, v->length, volume, sehandle); + freecon(secontext); + } + } + break; + } + case 5: + case 6: { + // workaround for new vold managed volumes that cannot be recognized by prebuilt ext2/ext3 bins + const char *device = v->blk_device2; + if (device == NULL) + device = v->blk_device; + ret = format_unknown_device(device, v->mount_point, list[chosen_item]); + break; + } + } + + if (ret) + ui_print("Could not format %s (%s)\n", volume, list[chosen_item]); + else + ui_print("Done formatting %s (%s)\n", volume, list[chosen_item]); +} + +static void partition_sdcard(const char* volume) { + if (!can_partition(volume)) { + ui_print("Can't partition device: %s\n", volume); + return; + } + + static char* ext_sizes[] = { "128M", + "256M", + "512M", + "1024M", + "2048M", + "4096M", + NULL }; + + static char* swap_sizes[] = { "0M", + "32M", + "64M", + "128M", + "256M", + NULL }; + + static char* partition_types[] = { "ext3", + "ext4", + NULL }; + + static const char* ext_headers[] = { "Ext Size", "", NULL }; + static const char* swap_headers[] = { "Swap Size", "", NULL }; + static const char* fstype_headers[] = { "Partition Type", "", NULL }; + + int ext_size = get_menu_selection(ext_headers, ext_sizes, 0, 0); + if (ext_size < 0) + return; + + int swap_size = get_menu_selection(swap_headers, swap_sizes, 0, 0); + if (swap_size < 0) + return; + + int partition_type = get_menu_selection(fstype_headers, partition_types, 0, 0); + if (partition_type < 0) + return; + + char sddevice[256]; + Volume *vol = volume_for_path(volume); + + // can_partition() ensured either blk_device or blk_device2 has /dev/block/mmcblk format + if (strstr(vol->blk_device, "/dev/block/mmcblk") != NULL) + strcpy(sddevice, vol->blk_device); + else + strcpy(sddevice, vol->blk_device2); + + // we only want the mmcblk, not the partition + sddevice[strlen("/dev/block/mmcblkX")] = '\0'; + char cmd[PATH_MAX]; + setenv("SDPATH", sddevice, 1); + sprintf(cmd, "sdparted -es %s -ss %s -efs %s -s", ext_sizes[ext_size], swap_sizes[swap_size], partition_types[partition_type]); + ui_print("Partitioning SD Card... please wait...\n"); + if (0 == __system(cmd)) + ui_print("Done!\n"); + else + ui_print("An error occured while partitioning your SD Card. Please see /tmp/recovery.log for more details.\n"); +} + +static int can_partition(const char* volume) { + if (is_data_media_volume_path(volume)) + return 0; + + Volume *vol = volume_for_path(volume); + if (vol == NULL) { + LOGI("Can't format unknown volume: %s\n", volume); + return 0; + } + if (strcmp(vol->fs_type, "auto") != 0) { + LOGI("Can't partition non-vfat: %s (%s)\n", volume, vol->fs_type); + return 0; + } + // do not allow partitioning of a device that isn't mmcblkX or mmcblkXp1 + // needed with new vold managed volumes and virtual device path links + int vol_len; + char *device = NULL; + if (strstr(vol->blk_device, "/dev/block/mmcblk") != NULL) { + device = vol->blk_device; + } else if (vol->blk_device2 != NULL && strstr(vol->blk_device2, "/dev/block/mmcblk") != NULL) { + device = vol->blk_device2; + } else { + LOGI("Can't partition non mmcblk device: %s\n", vol->blk_device); + return 0; + } + + vol_len = strlen(device); + if (device[vol_len - 2] == 'p' && device[vol_len - 1] != '1') { + LOGI("Can't partition unsafe device: %s\n", device); + return 0; + } + + return 1; +} + +int show_advanced_menu() { + char buf[80]; + int i = 0, j = 0, chosen_item = 0, list_index = 0; + /* Default number of entries if no compile-time extras are added */ + static char* list[MAX_NUM_MANAGED_VOLUMES + FIXED_ADVANCED_ENTRIES + 1]; + + char* primary_path = get_primary_storage_path(); + char** extra_paths = get_extra_storage_paths(); + int num_extra_volumes = get_num_extra_volumes(); + + static const char* headers[] = { "Advanced Menu", "", NULL }; + + memset(list, 0, MAX_NUM_MANAGED_VOLUMES + FIXED_ADVANCED_ENTRIES + 1); + + list[list_index++] = "reboot recovery"; + + char bootloader_mode[PROPERTY_VALUE_MAX]; + property_get("ro.bootloader.mode", bootloader_mode, ""); + if (!strcmp(bootloader_mode, "download")) { + list[list_index++] = "reboot to download mode"; + } else { + list[list_index++] = "reboot to bootloader"; + } + + list[list_index++] = "power off"; + list[list_index++] = "wipe dalvik cache"; + list[list_index++] = "report error"; + list[list_index++] = "key test"; + list[list_index++] = "show log"; +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + int index_tdb = list_index++; + int index_bootmode = list_index++; +#endif +#ifdef ENABLE_LOKI + int index_loki = list_index++; + list[index_loki] = "toggle loki support"; +#endif + + char list_prefix[] = "partition "; + if (can_partition(primary_path)) { + sprintf(buf, "%s%s", list_prefix, primary_path); + list[FIXED_ADVANCED_ENTRIES] = strdup(buf); + j++; + } + + if (extra_paths != NULL) { + for (i = 0; i < num_extra_volumes; i++) { + if (can_partition(extra_paths[i])) { + sprintf(buf, "%s%s", list_prefix, extra_paths[i]); + list[FIXED_ADVANCED_ENTRIES + j] = strdup(buf); + j++; + } + } + } + list[FIXED_ADVANCED_ENTRIES + j] = NULL; + + for (;;) { +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + char tdb_name[PATH_MAX]; + device_get_truedualboot_entry(tdb_name); + list[index_tdb] = &tdb_name; + + char bootmode_name[PATH_MAX]; + device_get_bootmode(bootmode_name); + list[index_bootmode] = &bootmode_name; +#endif + chosen_item = get_filtered_menu_selection(headers, list, 0, 0, sizeof(list) / sizeof(char*)); + if (chosen_item == GO_BACK || chosen_item == REFRESH) + break; + switch (chosen_item) { + case 0: { + ui_print("Rebooting recovery...\n"); + reboot_main_system(ANDROID_RB_RESTART2, 0, "recovery"); + break; + } + case 1: { + if (!strcmp(bootloader_mode, "download")) { + ui_print("Rebooting to download mode...\n"); + reboot_main_system(ANDROID_RB_RESTART2, 0, "download"); + } else { + ui_print("Rebooting to bootloader...\n"); + reboot_main_system(ANDROID_RB_RESTART2, 0, "bootloader"); + } + break; + } + case 2: { + ui_print("Shutting down...\n"); + reboot_main_system(ANDROID_RB_POWEROFF, 0, 0); + break; + } + case 3: { + if (0 != ensure_path_mounted("/data")) + break; + if (volume_for_path("/sd-ext") != NULL) + ensure_path_mounted("/sd-ext"); + ensure_path_mounted("/cache"); + if (confirm_selection("Confirm wipe?", "Yes - Wipe Dalvik Cache")) { + __system("rm -r /data/dalvik-cache"); + __system("rm -r /cache/dalvik-cache"); + __system("rm -r /sd-ext/dalvik-cache"); + ui_print("Dalvik Cache wiped.\n"); + } + ensure_path_unmounted("/data"); + break; + } + case 4: + handle_failure(1); + break; + case 5: { + ui_print("Outputting key codes.\n"); + ui_print("Go back to end debugging.\n"); + int key; + int action; + do { + key = ui_wait_key(); + action = device_handle_key(key, 1); + ui_print("Key: %d\n", key); + } while (action != GO_BACK); + break; + } + case 6: + ui_printlogtail(24); + ui_wait_key(); + ui_clear_key_queue(); + break; + default: +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + if(chosen_item==index_tdb) { + device_toggle_truedualboot(); + break; + } + if(chosen_item==index_bootmode) { + device_choose_bootmode(); + break; + } +#endif +#ifdef ENABLE_LOKI + if(chosen_item==index_loki) { + toggle_loki_support(); + break; + } +#endif + partition_sdcard(list[chosen_item] + strlen(list_prefix)); + break; + } + } + + for (; j > 0; --j) { + free(list[FIXED_ADVANCED_ENTRIES + j - 1]); + } + return chosen_item; +} + +static void write_fstab_root(char *path, FILE *file) { + Volume *vol = volume_for_path(path); + if (vol == NULL) { + LOGW("Unable to get recovery.fstab info for %s during fstab generation!\n", path); + return; + } + + char device[200]; + if (vol->blk_device[0] != '/') + get_partition_device(vol->blk_device, device); + else + strcpy(device, vol->blk_device); + + fprintf(file, "%s ", device); + fprintf(file, "%s ", path); + // special case rfs cause auto will mount it as vfat on samsung. + fprintf(file, "%s rw\n", vol->fs_type2 != NULL && strcmp(vol->fs_type, "rfs") != 0 ? "auto" : vol->fs_type); +} + +static void create_fstab() { + struct stat info; + __system("touch /etc/mtab"); + FILE *file = fopen("/etc/fstab", "w"); + if (file == NULL) { + LOGW("Unable to create /etc/fstab!\n"); + return; + } + Volume *vol = volume_for_path("/boot"); + if (NULL != vol && strcmp(vol->fs_type, "mtd") != 0 && strcmp(vol->fs_type, "emmc") != 0 && strcmp(vol->fs_type, "bml") != 0) + write_fstab_root("/boot", file); + write_fstab_root("/cache", file); + write_fstab_root("/data", file); + write_fstab_root("/datadata", file); + write_fstab_root("/emmc", file); + write_fstab_root("/system", file); + write_fstab_root("/sdcard", file); + write_fstab_root("/sd-ext", file); + write_fstab_root("/external_sd", file); + fclose(file); + LOGI("Completed outputting fstab.\n"); +} + +static int bml_check_volume(const char *path) { + ui_print("Checking %s...\n", path); + ensure_path_unmounted(path); + if (0 == ensure_path_mounted(path)) { + ensure_path_unmounted(path); + return 0; + } + + Volume *vol = volume_for_path(path); + if (vol == NULL) { + LOGE("Unable process volume! Skipping...\n"); + return 0; + } + + ui_print("%s may be rfs. Checking...\n", path); + char tmp[PATH_MAX]; + sprintf(tmp, "mount -t rfs %s %s", vol->blk_device, path); + int ret = __system(tmp); + printf("%d\n", ret); + return ret == 0 ? 1 : 0; +} + +void process_volumes() { + create_fstab(); + + if (is_data_media()) { + setup_data_media(); + } + + return; +} + +void handle_failure(int ret) { + if (ret == 0) + return; + if (0 != ensure_path_mounted(get_primary_storage_path())) + return; + mkdir("/sdcard/clockworkmod", S_IRWXU | S_IRWXG | S_IRWXO); + __system("cp /tmp/recovery.log /sdcard/clockworkmod/recovery.log"); + ui_print("/tmp/recovery.log was copied to /sdcard/clockworkmod/recovery.log. Please open ROM Manager to report the issue.\n"); +} + +static int is_path_mounted(const char* path) { + Volume* v = volume_for_path(path); + if (v == NULL) { + return 0; + } + if (strcmp(v->fs_type, "ramdisk") == 0) { + // the ramdisk is always mounted. + return 1; + } + + if (scan_mounted_volumes() < 0) + return 0; + + const MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point); + if (mv) { + // volume is already mounted + return 1; + } + return 0; +} + +int has_datadata() { + Volume *vol = volume_for_path("/datadata"); + return vol != NULL; +} + +int volume_main(int argc, char **argv) { + load_volume_table(); + return 0; +} + +int verify_root_and_recovery() { + if (ensure_path_mounted("/system") != 0) + return 0; + + int ret = 0; + struct stat st; + // check to see if install-recovery.sh is going to clobber recovery + // install-recovery.sh is also used to run the su daemon on stock rom for 4.3+ + // so verify that doesn't exist... + if (0 != lstat("/system/etc/.installed_su_daemon", &st)) { + // check install-recovery.sh exists and is executable + if (0 == lstat("/system/etc/install-recovery.sh", &st)) { + if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { + ui_show_text(1); + ret = 1; + if (confirm_selection("ROM may flash stock recovery on boot. Fix?", "Yes - Disable recovery flash")) { + __system("chmod -x /system/etc/install-recovery.sh"); + } + } + } + } + + int exists = 0; + if (0 == lstat("/system/bin/su", &st)) { + exists = 1; + if (S_ISREG(st.st_mode)) { + if ((st.st_mode & (S_ISUID | S_ISGID)) != (S_ISUID | S_ISGID)) { + ui_show_text(1); + ret = 1; + if (confirm_selection("Root access possibly lost. Fix?", "Yes - Fix root (/system/bin/su)")) { + __system("chmod 6755 /system/bin/su"); + } + } + } + } + + if (0 == lstat("/system/xbin/su", &st)) { + exists = 1; + if (S_ISREG(st.st_mode)) { + if ((st.st_mode & (S_ISUID | S_ISGID)) != (S_ISUID | S_ISGID)) { + ui_show_text(1); + ret = 1; + if (confirm_selection("Root access possibly lost. Fix?", "Yes - Fix root (/system/xbin/su)")) { + __system("chmod 6755 /system/xbin/su"); + } + } + } + } + + if (!exists) { + ui_show_text(1); + ret = 1; + if (confirm_selection("Root access is missing. Root device?", "Yes - Root device (/system/xbin/su)")) { + __system("/sbin/install-su.sh"); + } + } + + ensure_path_unmounted("/system"); + return ret; +} diff --git a/extendedcommands.h b/extendedcommands.h new file mode 100644 index 000000000..24cfe39af --- /dev/null +++ b/extendedcommands.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 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 __EXTENDEDCOMMANDS_H +#define __EXTENDEDCOMMANDS_H + +extern int signature_check_enabled; + +int __system(const char *command); + +int show_nandroid_menu(); +int show_advanced_menu(); +int show_partition_menu(); +int show_install_update_menu(); +int confirm_selection(const char* title, const char* confirm); +int install_zip(const char* packagefilepath); + +int empty_nandroid_bitmask(unsigned char flags); +int has_datadata(); +void process_volumes(); +int volume_main(int argc, char **argv); +int format_device(const char *device, const char *path, const char *fs_type); +int format_unknown_device(const char *device, const char* path, const char *fs_type); + +void handle_failure(int ret); +void write_recovery_version(); +int verify_root_and_recovery(); + +#ifdef USE_F2FS +extern int make_f2fs_main(int argc, char **argv); +extern int fsck_f2fs_main(int argc, char **argv); +extern int fibmap_main(int argc, char **argv); +#endif + +#ifdef RECOVERY_EXTEND_NANDROID_MENU +void extend_nandroid_menu(char** items, int item_count, int max_items); +void handle_nandroid_menu(int item_count, int selected); +#endif + +#endif // __EXTENDEDCOMMANDS_H \ No newline at end of file diff --git a/firmware.c b/firmware.c new file mode 100644 index 000000000..4e5c24f09 --- /dev/null +++ b/firmware.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootloader.h" +#include "common.h" +#include "firmware.h" +#include "roots.h" + +#include +#include +#include + +static const char *update_type = NULL; +static const char *update_data = NULL; +static int update_length = 0; + +int remember_firmware_update(const char *type, const char *data, int length) { + if (update_type != NULL || update_data != NULL) { + LOGE("Multiple firmware images\n"); + return -1; + } + + update_type = type; + update_data = data; + update_length = length; + return 0; +} + +// Return true if there is a firmware update pending. +int firmware_update_pending() { + return update_data != NULL && update_length > 0; +} + +/* Bootloader / Recovery Flow + * + * On every boot, the bootloader will read the bootloader_message + * from flash and check the command field. The bootloader should + * deal with the command field not having a 0 terminator correctly + * (so as to not crash if the block is invalid or corrupt). + * + * The bootloader will have to publish the partition that contains + * the bootloader_message to the linux kernel so it can update it. + * + * if command == "boot-recovery" -> boot recovery.img + * else if command == "update-radio" -> update radio image (below) + * else if command == "update-hboot" -> update hboot image (below) + * else -> boot boot.img (normal boot) + * + * Radio/Hboot Update Flow + * 1. the bootloader will attempt to load and validate the header + * 2. if the header is invalid, status="invalid-update", goto #8 + * 3. display the busy image on-screen + * 4. if the update image is invalid, status="invalid-radio-image", goto #8 + * 5. attempt to update the firmware (depending on the command) + * 6. if successful, status="okay", goto #8 + * 7. if failed, and the old image can still boot, status="failed-update" + * 8. write the bootloader_message, leaving the recovery field + * unchanged, updating status, and setting command to + * "boot-recovery" + * 9. reboot + * + * The bootloader will not modify or erase the cache partition. + * It is recovery's responsibility to clean up the mess afterwards. + */ + +int maybe_install_firmware_update(const char *send_intent) { + if (update_data == NULL || update_length == 0) return 0; + + /* We destroy the cache partition to pass the update image to the + * bootloader, so all we can really do afterwards is wipe cache and reboot. + * Set up this instruction now, in case we're interrupted while writing. + */ + + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n--wipe_cache\n", sizeof(boot.command)); + if (send_intent != NULL) { + strlcat(boot.recovery, "--send_intent=", sizeof(boot.recovery)); + strlcat(boot.recovery, send_intent, sizeof(boot.recovery)); + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + if (set_bootloader_message(&boot)) return -1; + + int width = 0, height = 0, bpp = 0; + char *busy_image = ui_copy_image( + BACKGROUND_ICON_FIRMWARE_INSTALLING, &width, &height, &bpp); + char *fail_image = ui_copy_image( + BACKGROUND_ICON_FIRMWARE_ERROR, &width, &height, &bpp); + + ui_print("Writing %s image...\n", update_type); + if (write_update_for_bootloader( + update_data, update_length, + width, height, bpp, busy_image, fail_image)) { + LOGE("Can't write %s image\n(%s)\n", update_type, strerror(errno)); + format_volume("/cache"); // Attempt to clean cache up, at least. + return -1; + } + + free(busy_image); + free(fail_image); + + /* The update image is fully written, so now we can instruct the bootloader + * to install it. (After doing so, it will come back here, and we will + * wipe the cache and reboot into the system.) + */ + snprintf(boot.command, sizeof(boot.command), "update-%s", update_type); + if (set_bootloader_message(&boot)) { + format_volume("/cache"); + return -1; + } + + reboot(RB_AUTOBOOT); + + // Can't reboot? WTF? + LOGE("Can't reboot\n"); + return -1; +} diff --git a/firmware.h b/firmware.h new file mode 100644 index 000000000..aeb8f97aa --- /dev/null +++ b/firmware.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _RECOVERY_FIRMWARE_H +#define _RECOVERY_FIRMWARE_H + +/* Save a radio or bootloader update image for later installation. + * The type should be one of "hboot" or "radio". + * Takes ownership of type and data. Returns nonzero on error. + */ +int remember_firmware_update(const char *type, const char *data, int length); + +/* Returns true if a firmware update has been saved. */ +int firmware_update_pending(); + +/* If an update was saved, reboot into the bootloader now to install it. + * Returns 0 if no radio image was defined, nonzero on error, + * doesn't return at all on success... + */ +int maybe_install_firmware_update(const char *send_intent); + +#endif diff --git a/flashutils/Android.mk b/flashutils/Android.mk new file mode 100644 index 000000000..c512f500f --- /dev/null +++ b/flashutils/Android.mk @@ -0,0 +1,105 @@ +LOCAL_PATH := $(call my-dir) + +ifneq ($(TARGET_SIMULATOR),true) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := flashutils.c +LOCAL_MODULE := libflashutils +LOCAL_MODULE_TAGS := optional +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. +LOCAL_STATIC_LIBRARIES := libmmcutils libmtdutils libbmlutils libcrecovery + +BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY + +$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \ + $(if $($(board_define)), \ + $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \ + ) \ + ) + +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := flash_image.c +LOCAL_MODULE := flash_image +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcrecovery +LOCAL_SHARED_LIBRARIES := libcutils libc +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := dump_image.c +LOCAL_MODULE := dump_image +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcrecovery +LOCAL_SHARED_LIBRARIES := libcutils libc +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := erase_image.c +LOCAL_MODULE := erase_image +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcrecovery +LOCAL_SHARED_LIBRARIES := libcutils libc +include $(BUILD_EXECUTABLE) + +ALL_DEFAULT_INSTALLED_MODULES += $(addprefix $(TARGET_OUT)/bin/, flash_image dump_image erase_image) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := flash_image.c +LOCAL_MODULE := libflash_image +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS += -Dmain=flash_image_main +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := dump_image.c +LOCAL_MODULE := libdump_image +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS += -Dmain=dump_image_main +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := erase_image.c +LOCAL_MODULE := liberase_image +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS += -Dmain=erase_image_main +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := dump_image.c +LOCAL_MODULE := utility_dump_image +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities +LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities +LOCAL_MODULE_STEM := dump_image +LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc +LOCAL_FORCE_STATIC_EXECUTABLE := true +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := flash_image.c +LOCAL_MODULE := utility_flash_image +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities +LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities +LOCAL_MODULE_STEM := flash_image +LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc +LOCAL_FORCE_STATIC_EXECUTABLE := true +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := erase_image.c +LOCAL_MODULE := utility_erase_image +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities +LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities +LOCAL_MODULE_STEM := erase_image +LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc +LOCAL_FORCE_STATIC_EXECUTABLE := true +include $(BUILD_EXECUTABLE) + +endif # !TARGET_SIMULATOR diff --git a/flashutils/dump_image.c b/flashutils/dump_image.c new file mode 100644 index 000000000..64c4e1c42 --- /dev/null +++ b/flashutils/dump_image.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cutils/log.h" +#include "flashutils.h" + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#if 0 + +#define LOG_TAG "dump_image" + +#define BLOCK_SIZE 2048 +#define SPARE_SIZE (BLOCK_SIZE >> 5) + +static int die(const char *msg, ...) { + int err = errno; + va_list args; + va_start(args, msg); + char buf[1024]; + vsnprintf(buf, sizeof(buf), msg, args); + va_end(args); + + if (err != 0) { + strlcat(buf, ": ", sizeof(buf)); + strlcat(buf, strerror(err), sizeof(buf)); + } + + fprintf(stderr, "%s\n", buf); + return 1; +} + +/* Read a flash partition and write it to an image file. */ + +int dump_image(char* partition_name, char* filename, dump_image_callback callback) { + MtdReadContext *in; + const MtdPartition *partition; + char buf[BLOCK_SIZE + SPARE_SIZE]; + size_t partition_size; + size_t read_size; + size_t total; + int fd; + int wrote; + int len; + + if (mtd_scan_partitions() <= 0) + return die("error scanning partitions"); + + partition = mtd_find_partition_by_name(partition_name); + if (partition == NULL) + return die("can't find %s partition", partition_name); + + if (mtd_partition_info(partition, &partition_size, NULL, NULL)) { + return die("can't get info of partition %s", partition_name); + } + + if (!strcmp(filename, "-")) { + fd = fileno(stdout); + } + else { + fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); + } + + if (fd < 0) + return die("error opening %s", filename); + + in = mtd_read_partition(partition); + if (in == NULL) { + close(fd); + unlink(filename); + return die("error opening %s: %s\n", partition_name, strerror(errno)); + } + + total = 0; + while ((len = mtd_read_data(in, buf, BLOCK_SIZE)) > 0) { + wrote = write(fd, buf, len); + if (wrote != len) { + close(fd); + unlink(filename); + return die("error writing %s", filename); + } + total += BLOCK_SIZE; + if (callback != NULL) + callback(total, partition_size); + } + + mtd_read_close(in); + + if (close(fd)) { + unlink(filename); + return die("error closing %s", filename); + } + return 0; +} + +int main(int argc, char **argv) +{ + ssize_t (*read_func) (MtdReadContext *, char *, size_t); + MtdReadContext *in; + const MtdPartition *partition; + char buf[BLOCK_SIZE + SPARE_SIZE]; + size_t partition_size; + size_t read_size; + size_t total; + int fd; + int wrote; + int len; + + if (argc != 3) { + fprintf(stderr, "usage: %s partition file.img\n", argv[0]); + return 2; + } + + return dump_image(argv[1], argv[2], NULL); +} + +#endif + +int main(int argc, char **argv) +{ + if (argc != 3) { + fprintf(stderr, "usage: %s partition file.img\n", argv[0]); + return 2; + } + + return backup_raw_partition(NULL, argv[1], argv[2]); +} diff --git a/flashutils/erase_image.c b/flashutils/erase_image.c new file mode 100644 index 000000000..b09a42480 --- /dev/null +++ b/flashutils/erase_image.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * Portions Copyright (C) 2010 Magnus Eriksson + * + * 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 +#include +#include +#include +#include +#include + +#include + +#include "cutils/log.h" +#include "flashutils.h" + +#if 0 + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + + +#define LOG_TAG "erase_image" + +static int die(const char *msg, ...) { + int err = errno; + va_list args; + va_start(args, msg); + char buf[1024]; + vsnprintf(buf, sizeof(buf), msg, args); + va_end(args); + + if (err != 0) { + strlcat(buf, ": ", sizeof(buf)); + strlcat(buf, strerror(err), sizeof(buf)); + } + + fprintf(stderr, "%s\n", buf); + LOGE("%s\n", buf); + return 3; +} + + +int erase_image(char* partition_name) { + MtdWriteContext *out; + size_t erased; + size_t total_size; + size_t erase_size; + + if (mtd_scan_partitions() <= 0) die("error scanning partitions"); + const MtdPartition *partition = mtd_find_partition_by_name(partition_name); + if (partition == NULL) return die("can't find %s partition", partition_name); + + out = mtd_write_partition(partition); + if (out == NULL) return die("could not estabilish write context for %s", partition_name); + + // do the actual erase, -1 = full partition erase + erased = mtd_erase_blocks(out, -1); + + // erased = bytes erased, if zero, something borked + if (!erased) return die("error erasing %s", partition_name); + + return 0; +} + + +/* Erase a mtd partition */ + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + + return erase_image(argv[1]); +} + +#endif + + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "usage: %s partition\n", argv[0]); + return 2; + } + + return erase_raw_partition(NULL, argv[1]); +} diff --git a/mtdutils/flash_image.c b/flashutils/flash_image.c similarity index 92% rename from mtdutils/flash_image.c rename to flashutils/flash_image.c index c77687602..c9f3683c3 100644 --- a/mtdutils/flash_image.c +++ b/flashutils/flash_image.c @@ -22,8 +22,9 @@ #include #include "cutils/log.h" -#include "mtdutils.h" +#include "flashutils.h" +#if 0 #define LOG_TAG "flash_image" #define HEADER_SIZE 2048 // size of header to compare for equality @@ -138,3 +139,17 @@ int main(int argc, char **argv) { if (mtd_write_close(out)) die("error closing %s", argv[1]); return 0; } +#endif + +int main(int argc, char **argv) +{ + if (argc != 3) { + fprintf(stderr, "usage: %s partition file.img\n", argv[0]); + return 2; + } + + int ret = restore_raw_partition(NULL, argv[1], argv[2]); + if (ret != 0) + fprintf(stderr, "failed with error: %d\n", ret); + return ret; +} diff --git a/flashutils/flashutils.c b/flashutils/flashutils.c new file mode 100644 index 000000000..5c092ebf7 --- /dev/null +++ b/flashutils/flashutils.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include + +#include "flashutils/flashutils.h" + +#ifndef BOARD_BML_BOOT +#define BOARD_BML_BOOT "/dev/block/bml7" +#endif + +#ifndef BOARD_BML_RECOVERY +#define BOARD_BML_RECOVERY "/dev/block/bml8" +#endif + +int the_flash_type = UNKNOWN; + +int device_flash_type() +{ + if (the_flash_type == UNKNOWN) { + if (access(BOARD_BML_BOOT, F_OK) == 0) { + the_flash_type = BML; + } else if (access("/proc/emmc", F_OK) == 0) { + the_flash_type = MMC; + } else if (access("/proc/mtd", F_OK) == 0) { + the_flash_type = MTD; + } else { + the_flash_type = UNSUPPORTED; + } + } + return the_flash_type; +} + +char* get_default_filesystem() +{ + return device_flash_type() == MMC ? "ext3" : "yaffs2"; +} + +int get_flash_type(const char* partitionType) { + int type = UNSUPPORTED; + if (strcmp(partitionType, "mtd") == 0) + type = MTD; + else if (strcmp(partitionType, "emmc") == 0) + type = MMC; + else if (strcmp(partitionType, "bml") == 0) + type = BML; + return type; +} + +static int detect_partition(const char *partitionType, const char *partition) +{ + int type = device_flash_type(); + if (strstr(partition, "/dev/block/mtd") != NULL) + type = MTD; + else if (strstr(partition, "/dev/block/mmc") != NULL) + type = MMC; + else if (strstr(partition, "/dev/block/bml") != NULL) + type = BML; + + if (partitionType != NULL) { + type = get_flash_type(partitionType); + } + + return type; +} +int restore_raw_partition(const char* partitionType, const char *partition, const char *filename) +{ + int type = detect_partition(partitionType, partition); + switch (type) { + case MTD: + return cmd_mtd_restore_raw_partition(partition, filename); + case MMC: + return cmd_mmc_restore_raw_partition(partition, filename); + case BML: + return cmd_bml_restore_raw_partition(partition, filename); + default: + return -1; + } +} + +int backup_raw_partition(const char* partitionType, const char *partition, const char *filename) +{ + int type = detect_partition(partitionType, partition); + switch (type) { + case MTD: + return cmd_mtd_backup_raw_partition(partition, filename); + case MMC: + return cmd_mmc_backup_raw_partition(partition, filename); + case BML: + return cmd_bml_backup_raw_partition(partition, filename); + default: + fprintf(stderr, "unable to detect device type\n"); + return -1; + } +} + +int erase_raw_partition(const char* partitionType, const char *partition) +{ + int type = detect_partition(partitionType, partition); + switch (type) { + case MTD: + return cmd_mtd_erase_raw_partition(partition); + case MMC: + return cmd_mmc_erase_raw_partition(partition); + case BML: + return cmd_bml_erase_raw_partition(partition); + default: + return -1; + } +} + +int erase_partition(const char *partition, const char *filesystem) +{ + int type = detect_partition(NULL, partition); + switch (type) { + case MTD: + return cmd_mtd_erase_partition(partition, filesystem); + case MMC: + return cmd_mmc_erase_partition(partition, filesystem); + case BML: + return cmd_bml_erase_partition(partition, filesystem); + default: + return -1; + } +} + +int mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only) +{ + int type = detect_partition(NULL, partition); + switch (type) { + case MTD: + return cmd_mtd_mount_partition(partition, mount_point, filesystem, read_only); + case MMC: + return cmd_mmc_mount_partition(partition, mount_point, filesystem, read_only); + case BML: + return cmd_bml_mount_partition(partition, mount_point, filesystem, read_only); + default: + return -1; + } +} + +int get_partition_device(const char *partition, char *device) +{ + int type = device_flash_type(); + switch (type) { + case MTD: + return cmd_mtd_get_partition_device(partition, device); + case MMC: + return cmd_mmc_get_partition_device(partition, device); + case BML: + return cmd_bml_get_partition_device(partition, device); + default: + return -1; + } +} diff --git a/flashutils/flashutils.h b/flashutils/flashutils.h new file mode 100644 index 000000000..d112a3180 --- /dev/null +++ b/flashutils/flashutils.h @@ -0,0 +1,50 @@ +#ifndef FLASHUTILS_H +#define FLASHUTILS_H + +int restore_raw_partition(const char* partitionType, const char *partition, const char *filename); +int backup_raw_partition(const char* partitionType, const char *partition, const char *filename); +int erase_raw_partition(const char* partitionType, const char *partition); +int erase_partition(const char *partition, const char *filesystem); +int mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only); +int get_partition_device(const char *partition, char *device); + +#define FLASH_MTD 0 +#define FLASH_MMC 1 +#define FLASH_BML 2 + +int is_mtd_device(); +char* get_default_filesystem(); + +extern int cmd_mtd_restore_raw_partition(const char *partition, const char *filename); +extern int cmd_mtd_backup_raw_partition(const char *partition, const char *filename); +extern int cmd_mtd_erase_raw_partition(const char *partition); +extern int cmd_mtd_erase_partition(const char *partition, const char *filesystem); +extern int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only); +extern int cmd_mtd_get_partition_device(const char *partition, char *device); + +extern int cmd_mmc_restore_raw_partition(const char *partition, const char *filename); +extern int cmd_mmc_backup_raw_partition(const char *partition, const char *filename); +extern int cmd_mmc_erase_raw_partition(const char *partition); +extern int cmd_mmc_erase_partition(const char *partition, const char *filesystem); +extern int cmd_mmc_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only); +extern int cmd_mmc_get_partition_device(const char *partition, char *device); + +extern int cmd_bml_restore_raw_partition(const char *partition, const char *filename); +extern int cmd_bml_backup_raw_partition(const char *partition, const char *filename); +extern int cmd_bml_erase_raw_partition(const char *partition); +extern int cmd_bml_erase_partition(const char *partition, const char *filesystem); +extern int cmd_bml_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only); +extern int cmd_bml_get_partition_device(const char *partition, char *device); + +extern int device_flash_type(); +extern int get_flash_type(const char* fs_type); + +enum flash_type { + UNSUPPORTED = -1, + UNKNOWN = 0, + MTD = 1, + MMC = 2, + BML = 3 +}; + +#endif \ No newline at end of file diff --git a/input/touch.c b/input/touch.c new file mode 100644 index 000000000..884fb2377 --- /dev/null +++ b/input/touch.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2014 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 +#include +#include +#include +#include + +#include "../minui/minui.h" +#include "../common.h" +#include "touch.h" + +#ifndef ABS_MT_ANGLE +#define ABS_MT_ANGLE 0x38 +#endif + +/* Handle axis swap */ +#ifdef BOARD_RECOVERY_SWIPE_SWAPXY +#define EV_CODE_POS_X ABS_MT_POSITION_Y +#define EV_CODE_POS_Y ABS_MT_POSITION_X +#else +#define EV_CODE_POS_X ABS_MT_POSITION_X +#define EV_CODE_POS_Y ABS_MT_POSITION_Y +#endif + +#define KEY_SWIPE_UP KEY_VOLUMEUP +#define KEY_SWIPE_DOWN KEY_VOLUMEDOWN + +static input_device input_devices[MAX_INPUT_DEVS]; + +// Defaults, will be changed based on dpi in calibrate_swipe() +static int min_swipe_dx = 100; +static int min_swipe_dy = 80; + +static void show_event(int fd, struct input_event *ev) { +#if DEBUG_TOUCH_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("[Touch] event: fd=%d, type=%s, code=%s, val=%d\n", fd, evtypestr, evcodestr, ev->value); +#endif +} + +static int setup_virtual_keys(input_device *dev) { + char name[256] = "unknown"; + char vkpath[PATH_MAX] = "/sys/board_properties/virtualkeys."; + + if (ioctl(dev->fd, EVIOCGNAME(sizeof(name)), name) < 0) { + LOGI("[Touch] Could not find virtual keys device name\n"); + return -1; + } + snprintf(vkpath, PATH_MAX, "%s%s", vkpath, name); + LOGI("[Touch] Loading virtual keys file: %s\n", vkpath); + + int bl = 0; + FILE *f; + int maxline = 40*MAX_VIRTUAL_KEYS; + char buf[MAX_VIRTUAL_KEYS][maxline]; + char tmp[maxline]; + + f = fopen(vkpath, "r"); + if (f != NULL) { + while (fgets(buf[bl], maxline, f) && bl < MAX_VIRTUAL_KEYS) { + if (buf[bl][0] == '#') + continue; + if (buf[bl][strlen(buf[bl])-1] == '\n') + buf[bl][strlen(buf[bl])-1] = '\0'; + bl++; + } + fclose(f); + } else { + LOGI("[Touch] Could not open virtual keys file\n"); + return -1; + } + + // Set the virtual key parameters + int i, j; + int count = 0; + char *token, *endp; + const char *sep = ":"; + for (j = 0; j < bl; j++) { + snprintf(tmp, maxline, "%s", buf[j]); + token = strtok(tmp, sep); + while (token && (count/6) < MAX_VIRTUAL_KEYS) { + if (count % 6 == 0 && strcmp("0x01", token) != 0) { + for (i = 0; i < 6; i++) + token = strtok(NULL, sep); + continue; + } + if (count % 6 == 1) + dev->virtual_keys[count/6].keycode = (int)strtol(token, &endp, 10); + if (count % 6 == 2) + dev->virtual_keys[count/6].center_x = (int)strtol(token, &endp, 10); + if (count % 6 == 3) + dev->virtual_keys[count/6].center_y = (int)strtol(token, &endp, 10); + if (count % 6 == 4) + dev->virtual_keys[count/6].width = (int)strtol(token, &endp, 10); + if (count % 6 == 5) + dev->virtual_keys[count/6].height = (int)strtol(token, &endp, 10); + + token = strtok(NULL, sep); + count++; + } + } + dev->virtual_key_count = count/6; + +#if DEBUG_TOUCH_EVENTS + for (i = 0; i < dev->virtual_key_count; i++) { + LOGI("[Touch] Virtual key: code=%d, x=%d, y=%d, width=%d, height=%d\n", + dev->virtual_keys[i].keycode, dev->virtual_keys[i].center_x, + dev->virtual_keys[i].center_y, dev->virtual_keys[i].width, + dev->virtual_keys[i].height); + } +#endif + + return 0; +} + +static void calibrate_swipe() { + char value[PROPERTY_VALUE_MAX]; + property_get("ro.sf.lcd_density", value, "0"); + int screen_density = atoi(value); + if(screen_density > 160) { + min_swipe_dx = (int)(0.5 * screen_density); // Roughly 0.5in + min_swipe_dy = (int)(0.3 * screen_density); // Roughly 0.3in + } else { + min_swipe_dx = gr_fb_width()/4; + min_swipe_dy = 3*BOARD_RECOVERY_CHAR_HEIGHT; + } +} + +static int calibrate_touch(input_device *dev) { + struct input_absinfo info; + + memset(&info, 0, sizeof(info)); + if (ioctl(dev->fd, EVIOCGABS(EV_CODE_POS_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(EV_CODE_POS_Y), &info) == 0) { + dev->touch_min.y = info.minimum; + dev->touch_max.y = info.maximum; + dev->touch_pos.y = info.value; + } + + if (dev->touch_min.x == dev->touch_max.x + || dev->touch_min.y == dev->touch_max.y) + return 0; // Probably not a touch device + + return 1; // Success +} + +static void handle_press(input_device *dev, struct input_event *ev) { + dev->touch_start = dev->touch_pos; + dev->touch_track = dev->touch_pos; + dev->in_touch = 1; + dev->in_swipe = 0; +} + +static void handle_release(input_device *dev, struct input_event *ev) { + int i; + int dx, dy; + + if (dev->in_swipe) { + dx = dev->touch_pos.x - dev->touch_start.x; + dy = dev->touch_pos.y - dev->touch_start.y; + if (abs(dx) > abs(dy) && abs(dx) > min_swipe_dx) { + ev->type = EV_KEY; + ev->code = (dx > 0 ? KEY_ENTER : KEY_BACK); + ev->value = 2; + } else { + /* Vertical swipe, handled real-time */ + } + } else { + // Check if virtual key pressed + for (i = 0; i < dev->virtual_key_count; i++) { + dx = dev->virtual_keys[i].center_x - dev->touch_pos.x; + dy = dev->virtual_keys[i].center_y - dev->touch_pos.y; + if (abs(dx) < dev->virtual_keys[i].width/2 + && abs(dy) < dev->virtual_keys[i].height/2) { +#if DEBUG_TOUCH_EVENTS + LOGI("[Touch] Virtual key event: code=%d, touch-x=%d, touch-y=%d\n", + dev->virtual_keys[i].keycode, + dev->touch_pos.x, dev->touch_pos.y); +#endif + ev->type = EV_KEY; + ev->code = dev->virtual_keys[i].keycode; + ev->value = 2; + } + } + } + + dev->in_touch = 0; + dev->in_swipe = 0; + ui_clear_key_queue(); +} + +static void handle_gestures(input_device *dev, struct input_event *ev) { + int dx = dev->touch_pos.x - dev->touch_start.x; + int dy = dev->touch_pos.y - dev->touch_start.y; + if (abs(dx) > abs(dy)) { + if (abs(dx) > min_swipe_dx) { + /* Horizontal swipe, handle it on release */ + dev->in_swipe = 1; + } + } else { + dy = dev->touch_pos.y - dev->touch_track.y; + if (abs(dy) > min_swipe_dy) { + dev->in_swipe = 1; + dev->touch_track = dev->touch_pos; + ev->type = EV_KEY; + ev->code = (dy < 0) ? KEY_SWIPE_UP : KEY_SWIPE_DOWN; + ev->value = 2; + } + } +} + +static void process_syn(input_device *dev, struct input_event *ev) { + /* + * 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 (ev->code == SYN_MT_REPORT) { + if (!dev->in_touch && (dev->saw_pos_x && dev->saw_pos_y)) { +#if DEBUG_TOUCH_EVENTS + LOGI("[Touch] handle_press(fd=%d): type A\n", dev->fd); +#endif + handle_press(dev, ev); + } + dev->saw_mt_report = 1; + return; + } + + if (ev->code == SYN_REPORT) { + if (dev->in_touch) { + handle_gestures(dev, ev); + } else { + if (dev->saw_tracking_id) { +#if DEBUG_TOUCH_EVENTS + LOGI("[Touch] handle_press(fd=%d): type B\n", dev->fd); +#endif + handle_press(dev, ev); + } + } + + /* Detect release */ + if (dev->saw_mt_report) { + if (!dev->saw_pos_x && !dev->saw_pos_y) { +#if DEBUG_TOUCH_EVENTS + LOGI("[Touch] handle_release(fd=%d): type A\n", dev->fd); +#endif + handle_release(dev, ev); + dev->slot_first = 0; + } + } else { + if (dev->saw_tracking_id && dev->slot_current == dev->slot_first + && dev->tracking_id == -1) { +#if DEBUG_TOUCH_EVENTS + LOGI("[Touch] handle_release(fd=%d): type B\n", dev->fd); +#endif + handle_release(dev, ev); + dev->slot_first = 0; + } + } + + dev->saw_pos_x = 0; + dev->saw_pos_y = 0; + dev->saw_mt_report = 0; + dev->saw_tracking_id = 0; + } +} + +static void process_abs(input_device *dev, struct input_event *ev) { + if (ev->code == ABS_MT_SLOT) { + dev->slot_current = ev->value; + if (dev->slot_first == -1) + dev->slot_first = ev->value; + + return; + } + if (ev->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 = 1; + dev->slot_first = 0; + dev->slot_current = 0; + + if (ev->value != dev->tracking_id) { + dev->tracking_id = ev->value; + if (dev->tracking_id < 0) + dev->slot_count_active--; + else + dev->slot_count_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_count_active == 0) { + /* type A */ + if (dev->saw_pos_x && dev->saw_pos_y) { + return; + } + } else { + /* type B */ + if (dev->slot_current != dev->slot_first) + return; + } + if (ev->code == EV_CODE_POS_X) { + dev->saw_pos_x = 1; + dev->touch_pos.x = ev->value * gr_fb_width() + / (dev->touch_max.x - dev->touch_min.x); + } else if (ev->code == EV_CODE_POS_Y) { + dev->saw_pos_y = 1; + dev->touch_pos.y = ev->value * gr_fb_height() + / (dev->touch_max.y - dev->touch_min.y); + } +} + +static int one_time_initd = 0; +static void one_time_init() { + if (one_time_initd) + return; + one_time_initd = 1; + + int i; + for (i = 0; i < MAX_INPUT_DEVS; i++) + input_devices[i].fd = -1; + + calibrate_swipe(); +} + +void touch_handle_input(int fd, struct input_event *ev) { + show_event(fd, ev); + one_time_init(); + + int i; + input_device *dev = NULL; + for (i = 0; i < MAX_INPUT_DEVS; i++) { + if (fd == input_devices[i].fd) { + dev = &input_devices[i]; + break; + } + if (input_devices[i].fd == -1) { + dev = &input_devices[i]; + dev->fd = fd; + dev->tracking_id = -1; + dev->touch_min.x = 0; + dev->touch_max.x = 0; + dev->touch_min.y = 0; + dev->touch_max.y = 0; + dev->touch_calibrated = calibrate_touch(dev); + if(dev->touch_calibrated) + setup_virtual_keys(dev); + break; + } + } + if (!dev) { + LOGE("[Touch] Maximum # of input devices reached\n"); + return; + } + + if(!dev->touch_calibrated) + return; + + switch (ev->type) { + case EV_SYN: + process_syn(dev, ev); + break; + case EV_ABS: + process_abs(dev, ev); + break; + } +} \ No newline at end of file diff --git a/input/touch.h b/input/touch.h new file mode 100644 index 000000000..161b02eaf --- /dev/null +++ b/input/touch.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2014 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 TOUCH_H_ +#define TOUCH_H_ + +#define DEBUG_TOUCH_EVENTS 0 +#define MAX_INPUT_DEVS 8 +#define MAX_VIRTUAL_KEYS 8 + +typedef struct { + int x; + int y; +} point; + +typedef struct { + int keycode; + int center_x; + int center_y; + int width; + int height; +} virtual_key; + +typedef struct { + int fd; // Initialize to -1 + int touch_calibrated; + int slot_first; + int slot_current; + int slot_count_active; + int tracking_id; + int rel_sum; // Accumulated relative movement + + int saw_pos_x; // Did this sequence have an ABS_MT_POSITION_X? + int saw_pos_y; // Did this sequence have an ABS_MT_POSITION_Y? + int saw_mt_report; // Did this sequence have an SYN_MT_REPORT? + int saw_tracking_id;// Did this sequence have a SYN_TRACKING_ID? + int in_touch; // Are we in a touch event? + int in_swipe; // Are we in a swipe event? + + point touch_min; + point touch_max; + point touch_pos; // Current touch coordinates + point touch_start; // Coordinates of touch start + point touch_track; // Last tracked coordinates + + virtual_key virtual_keys[MAX_VIRTUAL_KEYS]; + int virtual_key_count; +} input_device; + +void touch_handle_input(int fd, struct input_event *ev); + +#endif // TOUCH_H_ \ No newline at end of file diff --git a/install.c b/install.c index 707cda438..0cd644167 100644 --- a/install.c +++ b/install.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -28,24 +29,146 @@ #include "minui/minui.h" #include "minzip/SysUtil.h" #include "minzip/Zip.h" -#include "mtdutils/mounts.h" +#include "mounts.h" #include "mtdutils/mtdutils.h" #include "roots.h" #include "verifier.h" +#include "recovery_ui.h" + +#include "firmware.h" + +#include "extendedcommands.h" +#include "propsrvc/legacy_property_service.h" #define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" +#define ASSUMED_UPDATE_SCRIPT_NAME "META-INF/com/google/android/update-script" #define PUBLIC_KEYS_FILE "/res/keys" +// The update binary ask us to install a firmware file on reboot. Set +// that up. Takes ownership of type and filename. +static int +handle_firmware_update(char* type, char* filename, ZipArchive* zip) { + unsigned int data_size; + const ZipEntry* entry = NULL; + + if (strncmp(filename, "PACKAGE:", 8) == 0) { + entry = mzFindZipEntry(zip, filename+8); + if (entry == NULL) { + LOGE("Failed to find \"%s\" in package", filename+8); + return INSTALL_ERROR; + } + data_size = entry->uncompLen; + } else { + struct stat st_data; + if (stat(filename, &st_data) < 0) { + LOGE("Error stat'ing %s: %s\n", filename, strerror(errno)); + return INSTALL_ERROR; + } + data_size = st_data.st_size; + } + + LOGI("type is %s; size is %d; file is %s\n", + type, data_size, filename); + + char* data = malloc(data_size); + if (data == NULL) { + LOGI("Can't allocate %d bytes for firmware data\n", data_size); + return INSTALL_ERROR; + } + + if (entry) { + if (mzReadZipEntry(zip, entry, data, data_size) == false) { + LOGE("Failed to read \"%s\" from package", filename+8); + return INSTALL_ERROR; + } + } else { + FILE* f = fopen(filename, "rb"); + if (f == NULL) { + LOGE("Failed to open %s: %s\n", filename, strerror(errno)); + return INSTALL_ERROR; + } + if (fread(data, 1, data_size, f) != data_size) { + LOGE("Failed to read firmware data: %s\n", strerror(errno)); + return INSTALL_ERROR; + } + fclose(f); + } + + if (remember_firmware_update(type, data, data_size)) { + LOGE("Can't store %s image\n", type); + free(data); + return INSTALL_ERROR; + } + + free(filename); + + return INSTALL_SUCCESS; +} + static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; +static const char *DEV_PROP_PATH = "/dev/__properties__"; +static const char *DEV_PROP_BACKUP_PATH = "/dev/__properties_backup__"; +static bool legacy_props_env_initd = false; +static bool legacy_props_path_modified = false; + +static int set_legacy_props() { + if (!legacy_props_env_initd) { + if (legacy_properties_init() != 0) + return -1; + + char tmp[32]; + int propfd, propsz; + legacy_get_property_workspace(&propfd, &propsz); + sprintf(tmp, "%d,%d", dup(propfd), propsz); + setenv("ANDROID_PROPERTY_WORKSPACE", tmp, 1); + legacy_props_env_initd = true; + } + + if (rename(DEV_PROP_PATH, DEV_PROP_BACKUP_PATH) != 0) { + LOGE("Could not rename properties path: %s\n", DEV_PROP_PATH); + return -1; + } else { + legacy_props_path_modified = true; + } + + return 0; +} + +static int unset_legacy_props() { + if (rename(DEV_PROP_BACKUP_PATH, DEV_PROP_PATH) != 0) { + LOGE("Could not rename properties path: %s\n", DEV_PROP_BACKUP_PATH); + return -1; + } else { + legacy_props_path_modified = false; + } + + return 0; +} // If the package contains an update binary, extract it and run it. static int try_update_binary(const char *path, ZipArchive *zip) { +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + int rc; + if((rc=device_truedualboot_before_update(path, zip))!=0) + return rc; +#endif + const ZipEntry* binary_entry = mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); if (binary_entry == NULL) { + const ZipEntry* update_script_entry = + mzFindZipEntry(zip, ASSUMED_UPDATE_SCRIPT_NAME); + if (update_script_entry != NULL) { + ui_print("Amend scripting (update-script) is no longer supported.\n"); + ui_print("Amend scripting was deprecated by Google in Android 1.5.\n"); + ui_print("It was necessary to remove it when upgrading to the ClockworkMod 3.0 Gingerbread based recovery.\n"); + ui_print("Please switch to Edify scripting (updater-script and update-binary) to create working update zip packages.\n"); + return INSTALL_UPDATE_BINARY_MISSING; + } + mzCloseZipArchive(zip); - return INSTALL_CORRUPT; + return INSTALL_UPDATE_BINARY_MISSING; } char* binary = "/tmp/update_binary"; @@ -58,13 +181,69 @@ try_update_binary(const char *path, ZipArchive *zip) { } bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); close(fd); - mzCloseZipArchive(zip); if (!ok) { LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME); + mzCloseZipArchive(zip); return 1; } + /* Make sure the update binary is compatible with this recovery + * + * We're building this against 4.4's (or above) bionic, which + * has a different property namespace structure. If "set_perm_" + * is found, it's probably a regular updater instead of a custom + * one. If "set_metadata_" isn't there, it's pre-4.4, which + * makes it incompatible. + * + * Also, I hate matching strings in binary blobs */ + + FILE *updaterfile = fopen(binary, "rb"); + char tmpbuf; + char setpermmatch[9] = { 's','e','t','_','p','e','r','m','_' }; + char setmetamatch[13] = { 's','e','t','_','m','e','t','a','d','a','t','a','_' }; + size_t pos = 0; + bool foundsetperm = false; + bool foundsetmeta = false; + + if (updaterfile == NULL) { + LOGE("Can't find %s for validation\n", ASSUMED_UPDATE_BINARY_NAME); + return 1; + } + fseek(updaterfile, 0, SEEK_SET); + while (!feof(updaterfile)) { + fread(&tmpbuf, 1, 1, updaterfile); + if (!foundsetperm && pos < sizeof(setpermmatch) && tmpbuf == setpermmatch[pos]) { + pos++; + if (pos == sizeof(setpermmatch)) { + foundsetperm = true; + pos = 0; + } + continue; + } + if (!foundsetmeta && tmpbuf == setmetamatch[pos]) { + pos++; + if (pos == sizeof(setmetamatch)) { + foundsetmeta = true; + pos = 0; + } + continue; + } + /* None of the match loops got a continuation, reset the counter */ + pos = 0; + } + fclose(updaterfile); + + /* Set legacy properties */ + if (foundsetperm && !foundsetmeta) { + LOGI("Using legacy property environment for update-binary...\n"); + if (set_legacy_props() != 0) { + LOGE("Legacy property environment did not init successfully. Properties may not be detected.\n"); + } else { + LOGI("Legacy property environment initialized.\n"); + } + } + int pipefd[2]; pipe(pipefd); @@ -112,13 +291,17 @@ try_update_binary(const char *path, ZipArchive *zip) { pid_t pid = fork(); if (pid == 0) { + setenv("UPDATE_PACKAGE", path, 1); close(pipefd[0]); - execv(binary, args); + execve(binary, args, environ); fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); _exit(-1); } close(pipefd[1]); + char* firmware_type = NULL; + char* firmware_filename = NULL; + char buffer[1024]; FILE* from_child = fdopen(pipefd[0], "r"); while (fgets(buffer, sizeof(buffer), from_child) != NULL) { @@ -138,6 +321,18 @@ try_update_binary(const char *path, ZipArchive *zip) { char* fraction_s = strtok(NULL, " \n"); float fraction = strtof(fraction_s, NULL); ui_set_progress(fraction); + } else if (strcmp(command, "firmware") == 0) { + char* type = strtok(NULL, " \n"); + char* filename = strtok(NULL, " \n"); + + if (type != NULL && filename != NULL) { + if (firmware_type != NULL) { + LOGE("ignoring attempt to do multiple firmware updates"); + } else { + firmware_type = strdup(type); + firmware_filename = strdup(filename); + } + } } else if (strcmp(command, "ui_print") == 0) { char* str = strtok(NULL, "\n"); if (str) { @@ -153,86 +348,29 @@ try_update_binary(const char *path, ZipArchive *zip) { int status; waitpid(pid, &status, 0); + + /* Unset legacy properties */ + if (legacy_props_path_modified) { + if (unset_legacy_props() != 0) { + LOGE("Legacy property environment did not disable successfully. Legacy properties may still be in use.\n"); + } else { + LOGI("Legacy property environment disabled.\n"); + } + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); + mzCloseZipArchive(zip); return INSTALL_ERROR; } - return INSTALL_SUCCESS; -} - -// Reads a file containing one or more public keys as produced by -// DumpPublicKey: this is an RSAPublicKey struct as it would appear -// as a C source literal, eg: -// -// "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" -// -// (Note that the braces and commas in this example are actual -// characters the parser expects to find in the file; the ellipses -// indicate more numbers omitted from this example.) -// -// The file may contain multiple keys in this format, separated by -// commas. The last key must not be followed by a comma. -// -// Returns NULL if the file failed to parse, or if it contain zero keys. -static RSAPublicKey* -load_keys(const char* filename, int* numKeys) { - RSAPublicKey* out = NULL; - *numKeys = 0; - - FILE* f = fopen(filename, "r"); - if (f == NULL) { - LOGE("opening %s: %s\n", filename, strerror(errno)); - goto exit; - } - - int i; - bool done = false; - while (!done) { - ++*numKeys; - out = realloc(out, *numKeys * sizeof(RSAPublicKey)); - RSAPublicKey* key = out + (*numKeys - 1); - if (fscanf(f, " { %i , 0x%x , { %u", - &(key->len), &(key->n0inv), &(key->n[0])) != 3) { - goto exit; - } - if (key->len != RSANUMWORDS) { - LOGE("key length (%d) does not match expected size\n", key->len); - goto exit; - } - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; - } - if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; - } - fscanf(f, " } } "); - - // if the line ends in a comma, this file has more keys. - switch (fgetc(f)) { - case ',': - // more keys to come. - break; - - case EOF: - done = true; - break; - - default: - LOGE("unexpected character between keys\n"); - goto exit; - } + if (firmware_type != NULL) { + int ret = handle_firmware_update(firmware_type, firmware_filename, zip); + mzCloseZipArchive(zip); + return ret; } - - fclose(f); - return out; - -exit: - if (f) fclose(f); - free(out); - *numKeys = 0; - return NULL; + mzCloseZipArchive(zip); + return INSTALL_SUCCESS; } static int @@ -241,6 +379,27 @@ really_install_package(const char *path) ui_set_background(BACKGROUND_ICON_INSTALLING); ui_print("Finding update package...\n"); ui_show_indeterminate_progress(); + + // 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 = 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); if (ensure_path_mounted(path) != 0) { @@ -250,27 +409,32 @@ really_install_package(const char *path) ui_print("Opening update package...\n"); - int numKeys; - RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); - if (loadedKeys == NULL) { - LOGE("Failed to load keys\n"); - return INSTALL_CORRUPT; - } - LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); - - // Give verification half the progress bar... - ui_print("Verifying update package...\n"); - ui_show_progress( - VERIFICATION_PROGRESS_FRACTION, - VERIFICATION_PROGRESS_TIME); - int err; - err = verify_file(path, loadedKeys, numKeys); - free(loadedKeys); - LOGI("verify_file returned %d\n", err); - if (err != VERIFY_SUCCESS) { - LOGE("signature verification failed\n"); - return INSTALL_CORRUPT; + + if (signature_check_enabled) { + int numKeys; + Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); + if (loadedKeys == NULL) { + LOGE("Failed to load keys\n"); + return INSTALL_CORRUPT; + } + LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); + + // Give verification half the progress bar... + ui_print("Verifying update package...\n"); + ui_show_progress( + VERIFICATION_PROGRESS_FRACTION, + VERIFICATION_PROGRESS_TIME); + + err = verify_file(path, loadedKeys, numKeys); + free(loadedKeys); + LOGI("verify_file returned %d\n", err); + if (err != VERIFY_SUCCESS) { + LOGE("signature verification failed\n"); + ui_show_text(1); + if (!confirm_selection("Install Untrusted Package?", "Yes - Install untrusted zip")) + return INSTALL_CORRUPT; + } } /* Try to open the package. diff --git a/install.h b/install.h index a7ebc090d..ec97d39a6 100644 --- a/install.h +++ b/install.h @@ -19,7 +19,7 @@ #include "common.h" -enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT }; +enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_UPDATE_SCRIPT_MISSING, INSTALL_UPDATE_BINARY_MISSING }; int install_package(const char *root_path); #endif // RECOVERY_INSTALL_H_ diff --git a/killrecovery.sh b/killrecovery.sh new file mode 100755 index 000000000..352cb4b06 --- /dev/null +++ b/killrecovery.sh @@ -0,0 +1,22 @@ +#!/sbin/sh +mkdir -p /sd-ext +rm /cache/recovery/command +rm /cache/update.zip +touch /tmp/.ignorebootmessage +kill $(ps | grep /sbin/adbd) +kill $(ps | grep /sbin/recovery) + +# On the Galaxy S, the recovery comes test signed, but the +# recovery is not automatically restarted. +if [ -f /init.smdkc110.rc ] +then + /sbin/recovery & +fi + +# Droid X +if [ -f /init.mapphone_cdma.rc ] +then + /sbin/recovery & +fi + +exit 1 diff --git a/libcrecovery/Android.mk b/libcrecovery/Android.mk new file mode 100644 index 000000000..d948dd100 --- /dev/null +++ b/libcrecovery/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH := $(call my-dir) + +ifneq ($(TARGET_SIMULATOR),true) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := system.c popen.c +LOCAL_MODULE := libcrecovery +LOCAL_MODULE_TAGS := eng +include $(BUILD_STATIC_LIBRARY) + +endif diff --git a/libcrecovery/common.h b/libcrecovery/common.h new file mode 100644 index 000000000..59af22e53 --- /dev/null +++ b/libcrecovery/common.h @@ -0,0 +1,10 @@ +#ifndef LIBCRECOVERY_COMMON_H +#define LIBCRECOVERY_COMMON_H + +#include + +int __system(const char *command); +FILE * __popen(const char *program, const char *type); +int __pclose(FILE *iop); + +#endif \ No newline at end of file diff --git a/libcrecovery/defines.h b/libcrecovery/defines.h new file mode 100644 index 000000000..d94ad2d65 --- /dev/null +++ b/libcrecovery/defines.h @@ -0,0 +1,2 @@ +#undef _PATH_BSHELL +#define _PATH_BSHELL "/sbin/sh" diff --git a/libcrecovery/popen.c b/libcrecovery/popen.c new file mode 100644 index 000000000..59a7bd47b --- /dev/null +++ b/libcrecovery/popen.c @@ -0,0 +1,166 @@ +/* $OpenBSD: popen.c,v 1.17 2005/08/08 08:05:34 espie Exp $ */ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "defines.h" + +static struct pid { + struct pid *next; + FILE *fp; + pid_t pid; +} *pidlist; + +extern char **environ; + +FILE * +__popen(const char *program, const char *type) +{ + struct pid * volatile cur; + FILE *iop; + int pdes[2]; + pid_t pid; + char *argp[] = {"sh", "-c", NULL, NULL}; + + if ((*type != 'r' && *type != 'w') || type[1] != '\0') { + errno = EINVAL; + return (NULL); + } + + if ((cur = malloc(sizeof(struct pid))) == NULL) + return (NULL); + + if (pipe(pdes) < 0) { + free(cur); + return (NULL); + } + + switch (pid = fork()) { + case -1: /* Error. */ + (void)close(pdes[0]); + (void)close(pdes[1]); + free(cur); + return (NULL); + /* NOTREACHED */ + case 0: /* Child. */ + { + struct pid *pcur; + /* + * We fork()'d, we got our own copy of the list, no + * contention. + */ + for (pcur = pidlist; pcur; pcur = pcur->next) + close(fileno(pcur->fp)); + + if (*type == 'r') { + (void) close(pdes[0]); + if (pdes[1] != STDOUT_FILENO) { + (void)dup2(pdes[1], STDOUT_FILENO); + (void)close(pdes[1]); + } + } else { + (void)close(pdes[1]); + if (pdes[0] != STDIN_FILENO) { + (void)dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + } + argp[2] = (char *)program; + execve(_PATH_BSHELL, argp, environ); + _exit(127); + /* NOTREACHED */ + } + } + + /* Parent; assume fdopen can't fail. */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + + /* Link into list of file descriptors. */ + cur->fp = iop; + cur->pid = pid; + cur->next = pidlist; + pidlist = cur; + + return (iop); +} + +/* + * pclose -- + * Pclose returns -1 if stream is not associated with a `popened' command, + * if already `pclosed', or waitpid returns an error. + */ +int +__pclose(FILE *iop) +{ + struct pid *cur, *last; + int pstat; + pid_t pid; + + /* Find the appropriate file pointer. */ + for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next) + if (cur->fp == iop) + break; + + if (cur == NULL) + return (-1); + + (void)fclose(iop); + + do { + pid = waitpid(cur->pid, &pstat, 0); + } while (pid == -1 && errno == EINTR); + + /* Remove the entry from the linked list. */ + if (last == NULL) + pidlist = cur->next; + else + last->next = cur->next; + free(cur); + + return (pid == -1 ? -1 : pstat); +} diff --git a/libcrecovery/system.c b/libcrecovery/system.c new file mode 100644 index 000000000..6d78ae946 --- /dev/null +++ b/libcrecovery/system.c @@ -0,0 +1,76 @@ +/* $OpenBSD: system.c,v 1.8 2005/08/08 08:05:37 espie Exp $ */ +/* + * Copyright (c) 1988 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "defines.h" + +extern char **environ; + +int +__system(const char *command) +{ + pid_t pid; + sig_t intsave, quitsave; + sigset_t mask, omask; + int pstat; + char *argp[] = {"sh", "-c", NULL, NULL}; + + if (!command) /* just checking... */ + return(1); + + argp[2] = (char *)command; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &omask); + switch (pid = vfork()) { + case -1: /* error */ + sigprocmask(SIG_SETMASK, &omask, NULL); + return(-1); + case 0: /* child */ + sigprocmask(SIG_SETMASK, &omask, NULL); + execve(_PATH_BSHELL, argp, environ); + _exit(127); + } + + intsave = (sig_t) bsd_signal(SIGINT, SIG_IGN); + quitsave = (sig_t) bsd_signal(SIGQUIT, SIG_IGN); + pid = waitpid(pid, (int *)&pstat, 0); + sigprocmask(SIG_SETMASK, &omask, NULL); + (void)bsd_signal(SIGINT, intsave); + (void)bsd_signal(SIGQUIT, quitsave); + return (pid == -1 ? -1 : pstat); +} diff --git a/loki/Android.mk b/loki/Android.mk new file mode 100644 index 000000000..5a1656f46 --- /dev/null +++ b/loki/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH := $(call my-dir) + +# libloki +include $(CLEAR_VARS) +LOCAL_SRC_FILES := loki_flash.c loki_patch.c +LOCAL_MODULE := libloki_static +LOCAL_MODULE_TAGS := eng +include $(BUILD_STATIC_LIBRARY) + +# build static binary +include $(CLEAR_VARS) +LOCAL_SRC_FILES := loki_flash.c loki_patch.c loki_find.c main.c +LOCAL_MODULE := loki_tool_static +LOCAL_MODULE_STEM := loki_tool +LOCAL_MODULE_TAGS := eng +# LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_STATIC_LIBRARIES := libc +LOCAL_FORCE_STATIC_EXECUTABLE := true +include $(BUILD_EXECUTABLE) diff --git a/loki/LICENSE.txt b/loki/LICENSE.txt new file mode 100644 index 000000000..bc2a91404 --- /dev/null +++ b/loki/LICENSE.txt @@ -0,0 +1,25 @@ +Copyright (c) 2013 Dan Rosenberg. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INFRAE OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/loki/Makefile b/loki/Makefile new file mode 100644 index 000000000..6b00354e2 --- /dev/null +++ b/loki/Makefile @@ -0,0 +1,24 @@ +SRC_LOKI := loki_flash.c loki_patch.c loki_find.c main.c +OBJ_LOKI = $(SRC_LOKI:.c=.o) +MODULE_LOKI := loki_tool + +CC := arm-linux-androideabi-gcc +CC_STRIP := arm-linux-androideabi-strip + +CFLAGS += -g -static -Wall +#$(LDFLAGS) += + + +%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) + +$(MODULE_LOKI): $(OBJ_LOKI) + $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) + +strip: + $(CC_STRIP) --strip-unneeded $(MODULE_LOKI) + $(CC_STRIP) --strip-debug $(MODULE_LOKI) + +clean: + rm -f *.o + rm -f loki_tool diff --git a/loki/README.txt b/loki/README.txt new file mode 100644 index 000000000..aadfb10c4 --- /dev/null +++ b/loki/README.txt @@ -0,0 +1,72 @@ +============================= +Loki +by Dan Rosenberg (@djrbliss) +============================= + +Loki is a set of tools for creating and flashing custom kernels and recoveries +on the AT&T and Verizon branded Samsung Galaxy S4, the Samsung Galaxy Stellar, +and various locked LG devices. For an explanation of how the exploit works, +please see the technical blog post at: + +http://blog.azimuthsecurity.com/2013/05/exploiting-samsung-galaxy-s4-secure-boot.html + +Devices must be rooted in order to flash custom kernels and recoveries. + +"loki_patch" is a tool primarily intended for developers to create custom +kernels and recoveries. It's designed to take a specific aboot image and an +unmodified boot or recovery image, and it generates an output image in a new +file format, ".lok". The resulting .lok image is specifically tailored for the +device build it was created with, and can be flashed directly to the recovery +or boot partition on the target device. + +"loki_flash" is a sample utility that can be used to flash a .lok image to an +actual device. It will verify that the provided .lok image is safe to flash for +a given target, and then perform the flashing if validation is successful. It +is also possible to simply use "dd" to flash a .lok image directly to the boot +or recovery partition, but using loki_flash is recommended in order to validate +that the .lok matches the target device. + + +============= +Sample usage +============= + +First, a developer must pull the aboot image from a target device: + + +dan@pc:~$ adb shell +shell@android:/ $ su +shell@android:/ # dd if=/dev/block/platform/msm_sdcc.1/by-name/aboot of=/data/local/tmp/aboot.img +shell@android:/ # chmod 644 /data/local/tmp/aboot.img +shell@android:/ # exit +shell@android:/ $ exit +dan@pc:~$ adb pull /data/local/tmp/aboot.img +3293 KB/s (2097152 bytes in 0.621s) + + +Next, a .lok image can be prepared using loki_patch: + + +dan@pc:~$ loki_patch +Usage: ./loki_patch [boot|recovery] [aboot.img] [in.img] [out.lok] +dan@pc:~$ loki_patch recovery aboot.img cwm.img cwm.lok +[+] Detected target AT&T build JDQ39.I337UCUAMDB or JDQ39.I337UCUAMDL +[+] Output file written to cwm.lok + + +Finally, the .lok image can be flashed using loki_flash: + + +dan@pc:~$ adb push cwm.lok /data/local/tmp +dan@pc:~$ adb push loki_flash /data/local/tmp +dan@pc:~$ adb shell +shell@android:/ $ su +shell@android:/ # chmod 755 /data/local/tmp/loki_flash +shell@android:/ # /data/local/tmp/loki_flash +Usage: /data/local/tmp/loki_flash [boot|recovery] [in.lok] +shell@android:/ # /data/local/tmp/loki_flash recovery /data/local/tmp/cwm.lok +[+] Loki validation passed, flashing image. +2253+1 records in +2253+1 records out +9230848 bytes transferred in 0.656 secs (14071414 bytes/sec) +[+] Loki flashing complete! diff --git a/loki/loki.h b/loki/loki.h new file mode 100644 index 000000000..bb8ecb3a8 --- /dev/null +++ b/loki/loki.h @@ -0,0 +1,56 @@ +#ifndef __LOKI_H_ +#define __LOKI_H_ + +#define VERSION "2.1" + +#define BOOT_MAGIC_SIZE 8 +#define BOOT_NAME_SIZE 16 +#define BOOT_ARGS_SIZE 512 + +#define BOOT_PARTITION "/dev/block/platform/msm_sdcc.1/by-name/boot" +#define RECOVERY_PARTITION "/dev/block/platform/msm_sdcc.1/by-name/recovery" +#define ABOOT_PARTITION "/dev/block/platform/msm_sdcc.1/by-name/aboot" + +#define PATTERN1 "\xf0\xb5\x8f\xb0\x06\x46\xf0\xf7" +#define PATTERN2 "\xf0\xb5\x8f\xb0\x07\x46\xf0\xf7" +#define PATTERN3 "\x2d\xe9\xf0\x41\x86\xb0\xf1\xf7" +#define PATTERN4 "\x2d\xe9\xf0\x4f\xad\xf5\xc6\x6d" +#define PATTERN5 "\x2d\xe9\xf0\x4f\xad\xf5\x21\x7d" +#define PATTERN6 "\x2d\xe9\xf0\x4f\xf3\xb0\x05\x46" + +#define ABOOT_BASE_SAMSUNG 0x88dfffd8 +#define ABOOT_BASE_LG 0x88efffd8 +#define ABOOT_BASE_G2 0xf7fffd8 + +struct boot_img_hdr { + unsigned char magic[BOOT_MAGIC_SIZE]; + unsigned kernel_size; /* size in bytes */ + unsigned kernel_addr; /* physical load addr */ + unsigned ramdisk_size; /* size in bytes */ + unsigned ramdisk_addr; /* physical load addr */ + unsigned second_size; /* size in bytes */ + unsigned second_addr; /* physical load addr */ + unsigned tags_addr; /* physical addr for kernel tags */ + unsigned page_size; /* flash page size we assume */ + unsigned dt_size; /* device_tree in bytes */ + unsigned unused; /* future expansion: should be 0 */ + unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */ + unsigned char cmdline[BOOT_ARGS_SIZE]; + unsigned id[8]; /* timestamp / checksum / sha1 / etc */ +}; + +struct loki_hdr { + unsigned char magic[4]; /* 0x494b4f4c */ + unsigned int recovery; /* 0 = boot.img, 1 = recovery.img */ + char build[128]; /* Build number */ + + unsigned int orig_kernel_size; + unsigned int orig_ramdisk_size; + unsigned int ramdisk_addr; +}; + +int loki_patch(const char* partition_label, const char* aboot_image, const char* in_image, const char* out_image); +int loki_flash(const char* partition_label, const char* loki_image); +int loki_find(const char* aboot_image); + +#endif //__LOKI_H_ diff --git a/loki/loki_find.c b/loki/loki_find.c new file mode 100644 index 000000000..f3e5e3df5 --- /dev/null +++ b/loki/loki_find.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +#include "loki.h" + +#define BOOT_PATTERN1 "\x4f\xf4\x70\x40\xb3\x49\x2d\xe9" /* Samsung GS4 */ +#define BOOT_PATTERN2 "\x2d\xe9\xf0\x4f\xad\xf5\x82\x5d" /* LG */ +#define BOOT_PATTERN3 "\x2d\xe9\xf0\x4f\x4f\xf4\x70\x40" /* LG */ +#define BOOT_PATTERN4 "\x2d\xe9\xf0\x4f\xad\xf5\x80\x5d" /* LG G2 */ + +int loki_find(const char* aboot_image) +{ + int aboot_fd; + struct stat st; + void *aboot, *ptr; + unsigned long aboot_base, check_sigs, boot_mmc; + + aboot_fd = open(aboot_image, O_RDONLY); + if (aboot_fd < 0) { + printf("[-] Failed to open %s for reading.\n", aboot_image); + return 1; + } + + if (fstat(aboot_fd, &st)) { + printf("[-] fstat() failed.\n"); + return 1; + } + + aboot = mmap(0, (st.st_size + 0xfff) & ~0xfff, PROT_READ, MAP_PRIVATE, aboot_fd, 0); + if (aboot == MAP_FAILED) { + printf("[-] Failed to mmap aboot.\n"); + return 1; + } + + check_sigs = 0; + + /* Do a pass to find signature checking function */ + for (ptr = aboot; ptr < aboot + st.st_size - 0x1000; ptr++) { + if (!memcmp(ptr, PATTERN1, 8) || + !memcmp(ptr, PATTERN2, 8) || + !memcmp(ptr, PATTERN3, 8)) { + + aboot_base = ABOOT_BASE_SAMSUNG; + check_sigs = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + break; + } + + if (!memcmp(ptr, PATTERN4, 8)) { + + aboot_base = ABOOT_BASE_LG; + check_sigs = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + break; + } + + if (!memcmp(ptr, PATTERN5, 8)) { + + aboot_base = ABOOT_BASE_G2; + check_sigs = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + break; + } + + if (!memcmp(ptr, PATTERN6, 8)) { + + aboot_base = ABOOT_BASE_LG; + check_sigs = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + + /* Don't break, because the other LG patterns override this one */ + continue; + } + } + + if (!check_sigs) { + printf("[-] Could not find signature checking function.\n"); + return 1; + } + + printf("[+] Signature check function: %.08lx\n", check_sigs); + + boot_mmc = 0; + + /* Do a second pass for the boot_linux_from_emmc function */ + for (ptr = aboot; ptr < aboot + st.st_size - 0x1000; ptr++) { + if (!memcmp(ptr, BOOT_PATTERN1, 8) || + !memcmp(ptr, BOOT_PATTERN2, 8) || + !memcmp(ptr, BOOT_PATTERN3, 8) || + !memcmp(ptr, BOOT_PATTERN4, 8)) { + + boot_mmc = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + break; + } + } + + if (!boot_mmc) { + printf("[-] Could not find boot_linux_from_mmc.\n"); + return 1; + } + + printf("[+] boot_linux_from_mmc: %.08lx\n", boot_mmc); + + return 0; +} diff --git a/loki/loki_flash.c b/loki/loki_flash.c new file mode 100644 index 000000000..b3d62d11c --- /dev/null +++ b/loki/loki_flash.c @@ -0,0 +1,142 @@ +/* + * loki_flash + * + * A sample utility to validate and flash .lok files + * + * by Dan Rosenberg (@djrbliss) + * modified for use in recovery by Seth Shelnutt, adapted by PhilZ + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "loki.h" + +int loki_flash(const char* partition_label, const char* loki_image) +{ + int ifd, aboot_fd, ofd, recovery, offs, match; + void *orig, *aboot, *patch; + struct stat st; + struct boot_img_hdr *hdr; + struct loki_hdr *loki_hdr; + char outfile[1024]; + + if (!strcmp(partition_label, "boot")) { + recovery = 0; + } else if (!strcmp(partition_label, "recovery")) { + recovery = 1; + } else { + printf("[+] First argument must be \"boot\" or \"recovery\".\n"); + return 1; + } + + /* Verify input file */ + aboot_fd = open(ABOOT_PARTITION, O_RDONLY); + if (aboot_fd < 0) { + printf("[-] Failed to open aboot for reading.\n"); + return 1; + } + + ifd = open(loki_image, O_RDONLY); + if (ifd < 0) { + printf("[-] Failed to open %s for reading.\n", loki_image); + return 1; + } + + /* Map the image to be flashed */ + if (fstat(ifd, &st)) { + printf("[-] fstat() failed.\n"); + return 1; + } + + orig = mmap(0, (st.st_size + 0x2000 + 0xfff) & ~0xfff, PROT_READ, MAP_PRIVATE, ifd, 0); + if (orig == MAP_FAILED) { + printf("[-] Failed to mmap Loki image.\n"); + return 1; + } + + hdr = orig; + loki_hdr = orig + 0x400; + + /* Verify this is a Loki image */ + if (memcmp(loki_hdr->magic, "LOKI", 4)) { + printf("[-] Input file is not a Loki image.\n"); + return 1; + } + + /* Verify this is the right type of image */ + if (loki_hdr->recovery != recovery) { + printf("[-] Loki image is not a %s image.\n", recovery ? "recovery" : "boot"); + return 1; + } + + /* Verify the to-be-patched address matches the known code pattern */ + aboot = mmap(0, 0x40000, PROT_READ, MAP_PRIVATE, aboot_fd, 0); + if (aboot == MAP_FAILED) { + printf("[-] Failed to mmap aboot.\n"); + return 1; + } + + match = 0; + + for (offs = 0; offs < 0x10; offs += 0x4) { + + if (hdr->ramdisk_addr < ABOOT_BASE_SAMSUNG) + patch = hdr->ramdisk_addr - ABOOT_BASE_G2 + aboot + offs; + else if (hdr->ramdisk_addr < ABOOT_BASE_LG) + patch = hdr->ramdisk_addr - ABOOT_BASE_SAMSUNG + aboot + offs; + else + patch = hdr->ramdisk_addr - ABOOT_BASE_LG + aboot + offs; + + if (patch < aboot || patch > aboot + 0x40000 - 8) { + printf("[-] Invalid .lok file.\n"); + return 1; + } + + if (!memcmp(patch, PATTERN1, 8) || + !memcmp(patch, PATTERN2, 8) || + !memcmp(patch, PATTERN3, 8) || + !memcmp(patch, PATTERN4, 8) || + !memcmp(patch, PATTERN5, 8) || + !memcmp(patch, PATTERN6, 8)) { + + match = 1; + break; + } + } + + if (!match) { + printf("[-] Loki aboot version does not match device.\n"); + return 1; + } + + printf("[+] Loki validation passed, flashing image.\n"); + + snprintf(outfile, sizeof(outfile), + "%s", + recovery ? RECOVERY_PARTITION : BOOT_PARTITION); + + ofd = open(outfile, O_WRONLY); + if (ofd < 0) { + printf("[-] Failed to open output block device.\n"); + return 1; + } + + if (write(ofd, orig, st.st_size) != st.st_size) { + printf("[-] Failed to write to block device.\n"); + return 1; + } + + printf("[+] Loki flashing complete!\n"); + + close(ifd); + close(aboot_fd); + close(ofd); + + return 0; +} diff --git a/loki/loki_patch.c b/loki/loki_patch.c new file mode 100644 index 000000000..ba12f0d0c --- /dev/null +++ b/loki/loki_patch.c @@ -0,0 +1,673 @@ +/* + * loki_patch + * + * A utility to patch unsigned boot and recovery images to make + * them suitable for booting on the AT&T/Verizon Samsung + * Galaxy S4, Galaxy Stellar, and various locked LG devices + * + * by Dan Rosenberg (@djrbliss) + * modified for use in recovery by Seth Shelnutt, adapted by PhilZ + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "loki.h" + +struct target { + char *vendor; + char *device; + char *build; + unsigned long check_sigs; + unsigned long hdr; + int lg; +}; + +struct target targets[] = { + { + .vendor = "AT&T", + .device = "Samsung Galaxy S4", + .build = "JDQ39.I337UCUAMDB or JDQ39.I337UCUAMDL", + .check_sigs = 0x88e0ff98, + .hdr = 0x88f3bafc, + .lg = 0, + }, + { + .vendor = "Verizon", + .device = "Samsung Galaxy S4", + .build = "JDQ39.I545VRUAMDK", + .check_sigs = 0x88e0fe98, + .hdr = 0x88f372fc, + .lg = 0, + }, + { + .vendor = "DoCoMo", + .device = "Samsung Galaxy S4", + .build = "JDQ39.SC04EOMUAMDI", + .check_sigs = 0x88e0fcd8, + .hdr = 0x88f0b2fc, + .lg = 0, + }, + { + .vendor = "Verizon", + .device = "Samsung Galaxy Stellar", + .build = "IMM76D.I200VRALH2", + .check_sigs = 0x88e0f5c0, + .hdr = 0x88ed32e0, + .lg = 0, + }, + { + .vendor = "Verizon", + .device = "Samsung Galaxy Stellar", + .build = "JZO54K.I200VRBMA1", + .check_sigs = 0x88e101ac, + .hdr = 0x88ed72e0, + .lg = 0, + }, + { + .vendor = "DoCoMo", + .device = "LG Optimus G", + .build = "L01E20b", + .check_sigs = 0x88F10E48, + .hdr = 0x88F54418, + .lg = 1, + }, + { + .vendor = "AT&T or HK", + .device = "LG Optimus G Pro", + .build = "E98010g or E98810b", + .check_sigs = 0x88f11084, + .hdr = 0x88f54418, + .lg = 1, + }, + { + .vendor = "KT, LGU, or SKT", + .device = "LG Optimus G Pro", + .build = "F240K10o, F240L10v, or F240S10w", + .check_sigs = 0x88f110b8, + .hdr = 0x88f54418, + .lg = 1, + }, + { + .vendor = "KT, LGU, or SKT", + .device = "LG Optimus LTE 2", + .build = "F160K20g, F160L20f, F160LV20d, or F160S20f", + .check_sigs = 0x88f10864, + .hdr = 0x88f802b8, + .lg = 1, + }, + { + .vendor = "MetroPCS", + .device = "LG Spirit", + .build = "MS87010a_05", + .check_sigs = 0x88f0e634, + .hdr = 0x88f68194, + .lg = 1, + }, + { + .vendor = "MetroPCS", + .device = "LG Motion", + .build = "MS77010f_01", + .check_sigs = 0x88f1015c, + .hdr = 0x88f58194, + .lg = 1, + }, + { + .vendor = "Verizon", + .device = "LG Lucid 2", + .build = "VS87010B_12", + .check_sigs = 0x88f10adc, + .hdr = 0x88f702bc, + .lg = 1, + }, + { + .vendor = "Verizon", + .device = "LG Spectrum 2", + .build = "VS93021B_05", + .check_sigs = 0x88f10c10, + .hdr = 0x88f84514, + .lg = 1, + }, + { + .vendor = "Boost Mobile", + .device = "LG Optimus F7", + .build = "LG870ZV4_06", + .check_sigs = 0x88f11714, + .hdr = 0x88f842ac, + .lg = 1, + }, + { + .vendor = "US Cellular", + .device = "LG Optimus F7", + .build = "US78011a", + .check_sigs = 0x88f112c8, + .hdr = 0x88f84518, + .lg = 1, + }, + { + .vendor = "Sprint", + .device = "LG Optimus F7", + .build = "LG870ZV5_02", + .check_sigs = 0x88f11710, + .hdr = 0x88f842a8, + .lg = 1, + }, + { + .vendor = "Virgin Mobile", + .device = "LG Optimus F3", + .build = "LS720ZV5", + .check_sigs = 0x88f108f0, + .hdr = 0x88f854f4, + .lg = 1, + }, + { + .vendor = "T-Mobile and MetroPCS", + .device = "LG Optimus F3", + .build = "LS720ZV5", + .check_sigs = 0x88f10264, + .hdr = 0x88f64508, + .lg = 1, + }, + { + .vendor = "AT&T", + .device = "LG G2", + .build = "D80010d", + .check_sigs = 0xf8132ac, + .hdr = 0xf906440, + .lg = 1, + }, + { + .vendor = "Verizon", + .device = "LG G2", + .build = "VS98010b", + .check_sigs = 0xf8131f0, + .hdr = 0xf906440, + .lg = 1, + }, + { + .vendor = "AT&T", + .device = "LG G2", + .build = "D80010o", + .check_sigs = 0xf813428, + .hdr = 0xf904400, + .lg = 1, + }, + { + .vendor = "Verizon", + .device = "LG G2", + .build = "VS98012b", + .check_sigs = 0xf813210, + .hdr = 0xf906440, + .lg = 1, + }, + { + .vendor = "T-Mobile or Canada", + .device = "LG G2", + .build = "D80110c or D803", + .check_sigs = 0xf813294, + .hdr = 0xf906440, + .lg = 1, + }, + { + .vendor = "International", + .device = "LG G2", + .build = "D802b", + .check_sigs = 0xf813a70, + .hdr = 0xf9041c0, + .lg = 1, + }, + { + .vendor = "Sprint", + .device = "LG G2", + .build = "LS980ZV7", + .check_sigs = 0xf813460, + .hdr = 0xf9041c0, + .lg = 1, + }, + { + .vendor = "KT or LGU", + .device = "LG G2", + .build = "F320K, F320L", + .check_sigs = 0xf81346c, + .hdr = 0xf8de440, + .lg = 1, + }, + { + .vendor = "SKT", + .device = "LG G2", + .build = "F320S", + .check_sigs = 0xf8132e4, + .hdr = 0xf8ee440, + .lg = 1, + }, + { + .vendor = "SKT", + .device = "LG G2", + .build = "F320S11c", + .check_sigs = 0xf813470, + .hdr = 0xf8de440, + .lg = 1, + }, + { + .vendor = "DoCoMo", + .device = "LG G2", + .build = "L-01F", + .check_sigs = 0xf813538, + .hdr = 0xf8d41c0, + .lg = 1, + }, + { + .vendor = "KT", + .device = "LG G Flex", + .build = "F340K", + .check_sigs = 0xf8124a4, + .hdr = 0xf8b6440, + .lg = 1, + }, + { + .vendor = "KDDI", + .device = "LG G Flex", + .build = "LGL2310d", + .check_sigs = 0xf81261c, + .hdr = 0xf8b41c0, + .lg = 1, + }, + { + .vendor = "International", + .device = "LG Optimus F5", + .build = "P87510e", + .check_sigs = 0x88f10a9c, + .hdr = 0x88f702b8, + .lg = 1, + }, + { + .vendor = "SKT", + .device = "LG Optimus LTE 3", + .build = "F260S10l", + .check_sigs = 0x88f11398, + .hdr = 0x88f8451c, + .lg = 1, + }, + { + .vendor = "International", + .device = "LG G Pad 8.3", + .build = "V50010a", + .check_sigs = 0x88f10814, + .hdr = 0x88f801b8, + .lg = 1, + }, + { + .vendor = "International", + .device = "LG G Pad 8.3", + .build = "V50010c or V50010e", + .check_sigs = 0x88f108bc, + .hdr = 0x88f801b8, + .lg = 1, + }, + { + .vendor = "International", + .device = "LG Optimus L9 II", + .build = "D60510a", + .check_sigs = 0x88f10d98, + .hdr = 0x88f84aa4, + .lg = 1, + }, + { + .vendor = "MetroPCS", + .device = "LG Optimus F6", + .build = "MS50010e", + .check_sigs = 0x88f10260, + .hdr = 0x88f70508, + .lg = 1, + }, + { + .vendor = "Open EU", + .device = "LG Optimus F6", + .build = "D50510a", + .check_sigs = 0x88f10284, + .hdr = 0x88f70aa4, + .lg = 1, + }, + { + .vendor = "KDDI", + .device = "LG Isai", + .build = "LGL22", + .check_sigs = 0xf813458, + .hdr = 0xf8d41c0, + .lg = 1, + }, + { + .vendor = "KT", + .device = "LG Optimus GK", + .build = "F220K", + .check_sigs = 0x88f11034, + .hdr = 0x88f54418, + .lg = 1, + }, + { + .vendor = "International", + .device = "LG Vu 3", + .build = "F300L", + .check_sigs = 0xf813170, + .hdr = 0xf8d2440, + .lg = 1, + }, +}; + +unsigned char patch[] = +"\xfe\xb5" +"\x0d\x4d" +"\xd5\xf8" +"\x88\x04" +"\xab\x68" +"\x98\x42" +"\x12\xd0" +"\xd5\xf8" +"\x90\x64" +"\x0a\x4c" +"\xd5\xf8" +"\x8c\x74" +"\x07\xf5\x80\x57" +"\x0f\xce" +"\x0f\xc4" +"\x10\x3f" +"\xfb\xdc" +"\xd5\xf8" +"\x88\x04" +"\x04\x49" +"\xd5\xf8" +"\x8c\x24" +"\xa8\x60" +"\x69\x61" +"\x2a\x61" +"\x00\x20" +"\xfe\xbd" +"\xff\xff\xff\xff" +"\xee\xee\xee\xee"; + +int patch_shellcode(unsigned int header, unsigned int ramdisk) +{ + + unsigned int i; + int found_header, found_ramdisk; + unsigned int *ptr; + + found_header = 0; + found_ramdisk = 0; + + for (i = 0; i < sizeof(patch); i++) { + ptr = (unsigned int *)&patch[i]; + if (*ptr == 0xffffffff) { + *ptr = header; + found_header = 1; + } + + if (*ptr == 0xeeeeeeee) { + *ptr = ramdisk; + found_ramdisk = 1; + } + } + + if (found_header && found_ramdisk) + return 0; + + return -1; +} + +int loki_patch(const char* partition_label, const char* aboot_image, const char* in_image, const char* out_image) +{ + int ifd, ofd, aboot_fd, pos, i, recovery, offset, fake_size; + unsigned int orig_ramdisk_size, orig_kernel_size, page_kernel_size, page_ramdisk_size, page_size, page_mask; + unsigned long target, aboot_base; + void *orig, *aboot, *ptr; + struct target *tgt; + struct stat st; + struct boot_img_hdr *hdr; + struct loki_hdr *loki_hdr; + char *buf; + + if (!strcmp(partition_label, "boot")) { + recovery = 0; + } else if (!strcmp(partition_label, "recovery")) { + recovery = 1; + } else { + printf("[+] First argument must be \"boot\" or \"recovery\".\n"); + return 1; + } + + /* Open input files */ + aboot_fd = open(aboot_image, O_RDONLY); + if (aboot_fd < 0) { + printf("[-] Failed to open %s for reading.\n", aboot_image); + return 1; + } + + ifd = open(in_image, O_RDONLY); + if (ifd < 0) { + printf("[-] Failed to open %s for reading.\n", in_image); + return 1; + } + + ofd = open(out_image, O_WRONLY|O_CREAT|O_TRUNC, 0644); + if (ofd < 0) { + printf("[-] Failed to open %s for writing.\n", out_image); + return 1; + } + + /* Find the signature checking function via pattern matching */ + if (fstat(aboot_fd, &st)) { + printf("[-] fstat() failed.\n"); + return 1; + } + + aboot = mmap(0, (st.st_size + 0xfff) & ~0xfff, PROT_READ, MAP_PRIVATE, aboot_fd, 0); + if (aboot == MAP_FAILED) { + printf("[-] Failed to mmap aboot.\n"); + return 1; + } + + target = 0; + + for (ptr = aboot; ptr < aboot + st.st_size - 0x1000; ptr++) { + if (!memcmp(ptr, PATTERN1, 8) || + !memcmp(ptr, PATTERN2, 8) || + !memcmp(ptr, PATTERN3, 8)) { + + aboot_base = ABOOT_BASE_SAMSUNG; + target = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + break; + } + + if (!memcmp(ptr, PATTERN4, 8)) { + + aboot_base = ABOOT_BASE_LG; + target = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + break; + } + + if (!memcmp(ptr, PATTERN5, 8)) { + + aboot_base = ABOOT_BASE_G2; + target = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + break; + } + } + + /* Do a second pass for the second LG pattern. This is necessary because + * apparently some LG models have both LG patterns, which throws off the + * fingerprinting. */ + + if (!target) { + for (ptr = aboot; ptr < aboot + st.st_size - 0x1000; ptr++) { + if (!memcmp(ptr, PATTERN6, 8)) { + + aboot_base = ABOOT_BASE_LG; + target = (unsigned long)ptr - (unsigned long)aboot + aboot_base; + break; + } + } + } + + if (!target) { + printf("[-] Failed to find function to patch.\n"); + return 1; + } + + tgt = NULL; + + for (i = 0; i < (sizeof(targets)/sizeof(targets[0])); i++) { + if (targets[i].check_sigs == target) { + tgt = &targets[i]; + break; + } + } + + if (!tgt) { + printf("[-] Unsupported aboot image.\n"); + return 1; + } + + printf("[+] Detected target %s %s build %s\n", tgt->vendor, tgt->device, tgt->build); + + /* Map the original boot/recovery image */ + if (fstat(ifd, &st)) { + printf("[-] fstat() failed.\n"); + return 1; + } + + orig = mmap(0, (st.st_size + 0x2000 + 0xfff) & ~0xfff, PROT_READ|PROT_WRITE, MAP_PRIVATE, ifd, 0); + if (orig == MAP_FAILED) { + printf("[-] Failed to mmap input file.\n"); + return 1; + } + + hdr = orig; + loki_hdr = orig + 0x400; + + if (!memcmp(loki_hdr->magic, "LOKI", 4)) { + printf("[-] Input file is already a Loki image.\n"); + + /* Copy the entire file to the output transparently */ + if (write(ofd, orig, st.st_size) != st.st_size) { + printf("[-] Failed to copy Loki image.\n"); + return 1; + } + + printf("[+] Copied Loki image to %s.\n", out_image); + + return 0; + } + + /* Set the Loki header */ + memcpy(loki_hdr->magic, "LOKI", 4); + loki_hdr->recovery = recovery; + strncpy(loki_hdr->build, tgt->build, sizeof(loki_hdr->build) - 1); + + page_size = hdr->page_size; + page_mask = hdr->page_size - 1; + + orig_kernel_size = hdr->kernel_size; + orig_ramdisk_size = hdr->ramdisk_size; + + printf("[+] Original kernel address: %.08x\n", hdr->kernel_addr); + printf("[+] Original ramdisk address: %.08x\n", hdr->ramdisk_addr); + + /* Store the original values in unused fields of the header */ + loki_hdr->orig_kernel_size = orig_kernel_size; + loki_hdr->orig_ramdisk_size = orig_ramdisk_size; + loki_hdr->ramdisk_addr = hdr->kernel_addr + ((hdr->kernel_size + page_mask) & ~page_mask); + + if (patch_shellcode(tgt->hdr, hdr->ramdisk_addr) < 0) { + printf("[-] Failed to patch shellcode.\n"); + return 1; + } + + /* Ramdisk must be aligned to a page boundary */ + hdr->kernel_size = ((hdr->kernel_size + page_mask) & ~page_mask) + hdr->ramdisk_size; + + /* Guarantee 16-byte alignment */ + offset = tgt->check_sigs & 0xf; + + hdr->ramdisk_addr = tgt->check_sigs - offset; + + if (tgt->lg) { + fake_size = page_size; + hdr->ramdisk_size = page_size; + } + else { + fake_size = 0x200; + hdr->ramdisk_size = 0; + } + + /* Write the image header */ + if (write(ofd, orig, page_size) != page_size) { + printf("[-] Failed to write header to output file.\n"); + return 1; + } + + page_kernel_size = (orig_kernel_size + page_mask) & ~page_mask; + + /* Write the kernel */ + if (write(ofd, orig + page_size, page_kernel_size) != page_kernel_size) { + printf("[-] Failed to write kernel to output file.\n"); + return 1; + } + + page_ramdisk_size = (orig_ramdisk_size + page_mask) & ~page_mask; + + /* Write the ramdisk */ + if (write(ofd, orig + page_size + page_kernel_size, page_ramdisk_size) != page_ramdisk_size) { + printf("[-] Failed to write ramdisk to output file.\n"); + return 1; + } + + /* Write fake_size bytes of original code to the output */ + buf = malloc(fake_size); + if (!buf) { + printf("[-] Out of memory.\n"); + return 1; + } + + lseek(aboot_fd, tgt->check_sigs - aboot_base - offset, SEEK_SET); + read(aboot_fd, buf, fake_size); + + if (write(ofd, buf, fake_size) != fake_size) { + printf("[-] Failed to write original aboot code to output file.\n"); + return 1; + } + + /* Save this position for later */ + pos = lseek(ofd, 0, SEEK_CUR); + + /* Write the device tree if needed */ + if (hdr->dt_size) { + + printf("[+] Writing device tree.\n"); + + if (write(ofd, orig + page_size + page_kernel_size + page_ramdisk_size, hdr->dt_size) != hdr->dt_size) { + printf("[-] Failed to write device tree to output file.\n"); + return 1; + } + } + + lseek(ofd, pos - (fake_size - offset), SEEK_SET); + + /* Write the patch */ + if (write(ofd, patch, sizeof(patch)) != sizeof(patch)) { + printf("[-] Failed to write patch to output file.\n"); + return 1; + } + + close(ifd); + close(ofd); + close(aboot_fd); + + printf("[+] Output file written to %s\n", out_image); + + return 0; +} diff --git a/loki/loki_recovery.c b/loki/loki_recovery.c new file mode 100644 index 000000000..f9dff6713 --- /dev/null +++ b/loki/loki_recovery.c @@ -0,0 +1,114 @@ +/* + * loki_patch + * + * A utility to patch unsigned boot and recovery images to make + * them suitable for booting on the AT&T/Verizon Samsung + * Galaxy S4, Galaxy Stellar, and various locked LG devices + * + * by Dan Rosenberg (@djrbliss) + * modified for use in recovery by Seth Shelnutt, adapted by PhilZ + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "loki.h" +#include "../common.h" +#include "../libcrecovery/common.h" // __system + + +#define ABOOT_DUMP_IMAGE "/tmp/loki_aboot-dump.img" +#define BOOT_DUMP_IMAGE "/tmp/loki_boot-dump.img" +#define RECOVERY_DUMP_IMAGE "/tmp/loki_recovery-dump.img" +#define LOKI_IMAGE "/tmp/loki_lokied-image" + +static int loki_dump_partitions() { + char cmd[PATH_MAX]; + int ifd; + + const char *target_partitions[] = { ABOOT_PARTITION, BOOT_PARTITION, RECOVERY_PARTITION, NULL }; + const char *dump_images[] = { ABOOT_DUMP_IMAGE, BOOT_DUMP_IMAGE, RECOVERY_DUMP_IMAGE, NULL }; + int i = 0; + + ui_print("[+] Dumping partitions...\n"); + while (target_partitions[i] != NULL) { + sprintf(cmd, "dd if=%s of=%s", target_partitions[i], dump_images[i]); + if (__system(cmd) != 0) { + LOGE("[-] Failed to dump %s\n", target_partitions[i]); + return 1; + } + ++i; + } + + return 0; +} + +static int needs_loki_patch(const char *partition_image) { + int ifd; + void *orig; + struct stat st; + struct boot_img_hdr *hdr; + struct loki_hdr *loki_hdr; + char *buf; + + ifd = open(partition_image, O_RDONLY); + if (ifd < 0) { + LOGE("[-] Failed to open %s for reading.\n", partition_image); + return 0; + } + + /* Map the image to be flashed */ + if (fstat(ifd, &st)) { + LOGE("[-] fstat() failed.\n"); + return 0; + } + + orig = mmap(0, (st.st_size + 0x2000 + 0xfff) & ~0xfff, PROT_READ, MAP_PRIVATE, ifd, 0); + if (orig == MAP_FAILED) { + LOGE("[-] Failed to mmap Loki image.\n"); + return 0; + } + + hdr = orig; + loki_hdr = orig + 0x400; + + /* Verify this is a Loki image */ + if (memcmp(loki_hdr->magic, "LOKI", 4)) { + ui_print("%s needs lokifying.\n", partition_image); + return 1; + } + + return 0; +} + +int loki_check() { + const char *target_partitions[] = { "boot", "recovery", NULL }; + const char *dump_images[] = { BOOT_DUMP_IMAGE, RECOVERY_DUMP_IMAGE, NULL }; + int i = 0; + int ret = 0; + + ret = loki_dump_partitions(); + while (!ret && target_partitions[i] != NULL) { + if (needs_loki_patch(dump_images[i])) { + if ((ret = loki_patch(target_partitions[i], ABOOT_DUMP_IMAGE, dump_images[i], LOKI_IMAGE)) != 0) + LOGE("Error loki-ifying the %s image.\n", target_partitions[i]); + else if ((ret = loki_flash(target_partitions[i], LOKI_IMAGE)) != 0) + LOGE("Error loki-flashing the %s image.\n", target_partitions[i]); + } + ++ i; + } + + // free the ramdisk + remove(ABOOT_DUMP_IMAGE); + remove(BOOT_DUMP_IMAGE); + remove(RECOVERY_DUMP_IMAGE); + + return ret; +} diff --git a/loki/main.c b/loki/main.c new file mode 100644 index 000000000..0037c494b --- /dev/null +++ b/loki/main.c @@ -0,0 +1,49 @@ +/* + * loki_patch + * + * A utility to patch unsigned boot and recovery images to make + * them suitable for booting on the AT&T/Verizon Samsung + * Galaxy S4, Galaxy Stellar, and various locked LG devices + * + * by Dan Rosenberg (@djrbliss) + * modified for use in recovery by Seth Shelnutt, adapted by PhilZ + * + */ + +#include +#include +#include "loki.h" + +static int print_help(const char* cmd) { + printf("Usage\n"); + printf("> Patch partition file image:\n"); + printf("%s [patch] [boot|recovery] [aboot.img] [in.img] [out.lok]\n", cmd); + printf("\n"); + printf("> Flash loki image to boot|recovery:\n"); + printf("%s [flash] [boot|recovery] [in.lok]\n", cmd); + printf("\n"); + printf("> Find offset from aboot image:\n"); + printf("%s [find] [aboot.img]\n", cmd); + return 1; +} + +int main(int argc, char **argv) { + printf("Loki tool v%s\n", VERSION); + + if (argc == 6 && strcmp(argv[1], "patch") == 0) { + // argv[2]: partition_label + // argv[3]: aboot_image + // argv[4]: in_image + // argv[5]: out_image + return loki_patch(argv[2], argv[3], argv[4], argv[5]); + } else if (argc == 4 && strcmp(argv[1], "flash") == 0) { + // argv[2]: partition_label + // argv[3]: loki_image + return loki_flash(argv[2], argv[3]); + } else if (argc == 3 && strcmp(argv[1], "find") == 0) { + // argv[2]: aboot_image + return loki_find(argv[2]); + } + + return print_help(argv[0]); +} diff --git a/minadbd/Android.mk b/minadbd/Android.mk new file mode 100644 index 000000000..5734f875c --- /dev/null +++ b/minadbd/Android.mk @@ -0,0 +1,34 @@ +# Copyright 2005 The Android Open Source Project +# +# Android.mk for adb +# + +LOCAL_PATH:= $(call my-dir) + +# minadbd library +# ========================================================= + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + adb.c \ + fdevent.c \ + transport.c \ + transport_usb.c \ + sockets.c \ + services.c \ + usb_linux_client.c \ + utils.c + +LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE + +LOCAL_MODULE := libminadbd + +LOCAL_C_INCLUDES += system/extras/ext4_utils system/core/fs_mgr/include + +LOCAL_STATIC_LIBRARIES := libcutils libc +include $(BUILD_STATIC_LIBRARY) + + + diff --git a/minadbd/README.txt b/minadbd/README.txt new file mode 100644 index 000000000..c9df484c3 --- /dev/null +++ b/minadbd/README.txt @@ -0,0 +1,39 @@ +The contents of this directory are copied from system/core/adb, with +the following changes: + +adb.c + - much support for host mode and non-linux OS's stripped out; this + version only runs as adbd on the device. + - always setuid/setgid's itself to the shell user + - only uses USB transport + - references to JDWP removed + - main() removed + - all ADB_HOST and win32 code removed + - removed listeners, logging code, background server (for host) + +adb.h + - minor changes to match adb.c changes + +sockets.c + - references to JDWP removed + - ADB_HOST code removed + +services.c + - all services except echo_service (which is commented out) removed + - all host mode support removed + - sideload_service() added; this is the only service supported. It + receives a single blob of data, writes it to a fixed filename, and + makes the process exit. + +Android.mk + - only builds in adbd mode; builds as static library instead of a + standalone executable. + +sysdeps.h + - changes adb_creat() to use O_NOFOLLOW + +transport.c + - removed ADB_HOST code + +transport_usb.c + - removed ADB_HOST code diff --git a/minadbd/adb.c b/minadbd/adb.c new file mode 100644 index 000000000..4a1cbaac9 --- /dev/null +++ b/minadbd/adb.c @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define TRACE_TAG TRACE_ADB + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sysdeps.h" +#include "adb.h" +#include "../common.h" + +#include + +#if ADB_TRACE +ADB_MUTEX_DEFINE( D_lock ); +#endif + +int HOST = 0; + +static const char *adb_device_banner = "sideload"; + +void fatal(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(-1); +} + +void fatal_errno(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "error: %s: ", strerror(errno)); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(-1); +} + +int adb_trace_mask; + +/* read a comma/space/colum/semi-column separated list of tags + * from the ADB_TRACE environment variable and build the trace + * mask from it. note that '1' and 'all' are special cases to + * enable all tracing + */ +void adb_trace_init(void) +{ + const char* p = getenv("ADB_TRACE"); + const char* q; + + static const struct { + const char* tag; + int flag; + } tags[] = { + { "1", 0 }, + { "all", 0 }, + { "adb", TRACE_ADB }, + { "sockets", TRACE_SOCKETS }, + { "packets", TRACE_PACKETS }, + { "rwx", TRACE_RWX }, + { "usb", TRACE_USB }, + { "sync", TRACE_SYNC }, + { "sysdeps", TRACE_SYSDEPS }, + { "transport", TRACE_TRANSPORT }, + { "jdwp", TRACE_JDWP }, + { "services", TRACE_SERVICES }, + { NULL, 0 } + }; + + if (p == NULL) + return; + + /* use a comma/column/semi-colum/space separated list */ + while (*p) { + int len, tagn; + + q = strpbrk(p, " ,:;"); + if (q == NULL) { + q = p + strlen(p); + } + len = q - p; + + for (tagn = 0; tags[tagn].tag != NULL; tagn++) + { + int taglen = strlen(tags[tagn].tag); + + if (len == taglen && !memcmp(tags[tagn].tag, p, len) ) + { + int flag = tags[tagn].flag; + if (flag == 0) { + adb_trace_mask = ~0; + return; + } + adb_trace_mask |= (1 << flag); + break; + } + } + p = q; + if (*p) + p++; + } +} + + +apacket *get_apacket(void) +{ + apacket *p = malloc(sizeof(apacket)); + if(p == 0) fatal("failed to allocate an apacket"); + memset(p, 0, sizeof(apacket) - MAX_PAYLOAD); + return p; +} + +void put_apacket(apacket *p) +{ + free(p); +} + +void handle_online(void) +{ + D("adb: online\n"); +} + +void handle_offline(atransport *t) +{ + D("adb: offline\n"); + //Close the associated usb + run_transport_disconnects(t); +} + +#if TRACE_PACKETS +#define DUMPMAX 32 +void print_packet(const char *label, apacket *p) +{ + char *tag; + char *x; + unsigned count; + + switch(p->msg.command){ + case A_SYNC: tag = "SYNC"; break; + case A_CNXN: tag = "CNXN" ; break; + case A_OPEN: tag = "OPEN"; break; + case A_OKAY: tag = "OKAY"; break; + case A_CLSE: tag = "CLSE"; break; + case A_WRTE: tag = "WRTE"; break; + default: tag = "????"; break; + } + + fprintf(stderr, "%s: %s %08x %08x %04x \"", + label, tag, p->msg.arg0, p->msg.arg1, p->msg.data_length); + count = p->msg.data_length; + x = (char*) p->data; + if(count > DUMPMAX) { + count = DUMPMAX; + tag = "\n"; + } else { + tag = "\"\n"; + } + while(count-- > 0){ + if((*x >= ' ') && (*x < 127)) { + fputc(*x, stderr); + } else { + fputc('.', stderr); + } + x++; + } + fprintf(stderr, tag); +} +#endif + +static void send_ready(unsigned local, unsigned remote, atransport *t) +{ + D("Calling send_ready \n"); + apacket *p = get_apacket(); + p->msg.command = A_OKAY; + p->msg.arg0 = local; + p->msg.arg1 = remote; + send_packet(p, t); +} + +static void send_close(unsigned local, unsigned remote, atransport *t) +{ + D("Calling send_close \n"); + apacket *p = get_apacket(); + p->msg.command = A_CLSE; + p->msg.arg0 = local; + p->msg.arg1 = remote; + send_packet(p, t); +} + +static void send_connect(atransport *t) +{ + D("Calling send_connect \n"); + apacket *cp = get_apacket(); + cp->msg.command = A_CNXN; + cp->msg.arg0 = A_VERSION; + cp->msg.arg1 = MAX_PAYLOAD; + snprintf((char*) cp->data, sizeof cp->data, "%s::", + HOST ? "host" : adb_device_banner); + cp->msg.data_length = strlen((char*) cp->data) + 1; + send_packet(cp, t); +} + +void parse_banner(char *banner, atransport *t) +{ + char *type, *product, *end; + + D("parse_banner: %s\n", banner); + type = banner; + product = strchr(type, ':'); + if(product) { + *product++ = 0; + } else { + product = ""; + } + + /* remove trailing ':' */ + end = strchr(product, ':'); + if(end) *end = 0; + + /* save product name in device structure */ + if (t->product == NULL) { + t->product = strdup(product); + } else if (strcmp(product, t->product) != 0) { + free(t->product); + t->product = strdup(product); + } + + if(!strcmp(type, "bootloader")){ + D("setting connection_state to CS_BOOTLOADER\n"); + t->connection_state = CS_BOOTLOADER; + update_transports(); + return; + } + + if(!strcmp(type, "device")) { + D("setting connection_state to CS_DEVICE\n"); + t->connection_state = CS_DEVICE; + update_transports(); + return; + } + + if(!strcmp(type, "recovery")) { + D("setting connection_state to CS_RECOVERY\n"); + t->connection_state = CS_RECOVERY; + update_transports(); + return; + } + + if(!strcmp(type, "sideload")) { + D("setting connection_state to CS_SIDELOAD\n"); + t->connection_state = CS_SIDELOAD; + update_transports(); + return; + } + + t->connection_state = CS_HOST; +} + +void handle_packet(apacket *p, atransport *t) +{ + asocket *s; + + D("handle_packet() %c%c%c%c\n", ((char*) (&(p->msg.command)))[0], + ((char*) (&(p->msg.command)))[1], + ((char*) (&(p->msg.command)))[2], + ((char*) (&(p->msg.command)))[3]); + print_packet("recv", p); + + switch(p->msg.command){ + case A_SYNC: + if(p->msg.arg0){ + send_packet(p, t); + if(HOST) send_connect(t); + } else { + t->connection_state = CS_OFFLINE; + handle_offline(t); + send_packet(p, t); + } + return; + + case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */ + /* XXX verify version, etc */ + if(t->connection_state != CS_OFFLINE) { + t->connection_state = CS_OFFLINE; + handle_offline(t); + } + parse_banner((char*) p->data, t); + handle_online(); + if(!HOST) send_connect(t); + break; + + case A_OPEN: /* OPEN(local-id, 0, "destination") */ + if(t->connection_state != CS_OFFLINE) { + char *name = (char*) p->data; + name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0; + s = create_local_service_socket(name); + if(s == 0) { + send_close(0, p->msg.arg0, t); + } else { + s->peer = create_remote_socket(p->msg.arg0, t); + s->peer->peer = s; + send_ready(s->id, s->peer->id, t); + s->ready(s); + } + } + break; + + case A_OKAY: /* READY(local-id, remote-id, "") */ + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + if(s->peer == 0) { + s->peer = create_remote_socket(p->msg.arg0, t); + s->peer->peer = s; + } + s->ready(s); + } + } + break; + + case A_CLSE: /* CLOSE(local-id, remote-id, "") */ + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + s->close(s); + } + } + break; + + case A_WRTE: + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + unsigned rid = p->msg.arg0; + p->len = p->msg.data_length; + + if(s->enqueue(s, p) == 0) { + D("Enqueue the socket\n"); + send_ready(s->id, rid, t); + } + return; + } + } + break; + + default: + printf("handle_packet: what is %08x?!\n", p->msg.command); + } + + put_apacket(p); +} + +static void adb_cleanup(void) +{ + usb_cleanup(); +} + +int adb_main() +{ + atexit(adb_cleanup); +#if defined(HAVE_FORKEXEC) + // No SIGCHLD. Let the service subproc handle its children. + signal(SIGPIPE, SIG_IGN); +#endif + + init_transport_registration(); + + // The minimal version of adbd only uses USB. + if (access(USB_ADB_PATH, F_OK) == 0 || access(USB_FFS_ADB_EP0, F_OK) == 0) { + // listen on USB + LOGE("listen on USB\n"); + usb_init(); + } + + if (setgid(AID_SHELL) != 0) { + LOGE("failed to setgid to shell\n"); + exit(1); + } + if (setuid(AID_SHELL) != 0) { + LOGE("failed to setuid to shell\n"); + exit(1); + } + LOGE("userid is %d\n", getuid()); + + LOGE("Event loop starting\n"); + + fdevent_loop(); + + usb_cleanup(); + + return 0; +} diff --git a/minadbd/adb.h b/minadbd/adb.h new file mode 100644 index 000000000..d389165ae --- /dev/null +++ b/minadbd/adb.h @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __ADB_H +#define __ADB_H + +#include + +#include "transport.h" /* readx(), writex() */ +#include "fdevent.h" + +#define MAX_PAYLOAD 4096 + +#define A_SYNC 0x434e5953 +#define A_CNXN 0x4e584e43 +#define A_OPEN 0x4e45504f +#define A_OKAY 0x59414b4f +#define A_CLSE 0x45534c43 +#define A_WRTE 0x45545257 + +#define A_VERSION 0x01000000 // ADB protocol version + +#define ADB_VERSION_MAJOR 1 // Used for help/version information +#define ADB_VERSION_MINOR 0 // Used for help/version information + +#define ADB_SERVER_VERSION 29 // Increment this when we want to force users to start a new adb server + +typedef struct amessage amessage; +typedef struct apacket apacket; +typedef struct asocket asocket; +typedef struct aservice aservice; +typedef struct atransport atransport; +typedef struct adisconnect adisconnect; +typedef struct usb_handle usb_handle; + +struct amessage { + unsigned command; /* command identifier constant */ + unsigned arg0; /* first argument */ + unsigned arg1; /* second argument */ + unsigned data_length; /* length of payload (0 is allowed) */ + unsigned data_check; /* checksum of data payload */ + unsigned magic; /* command ^ 0xffffffff */ +}; + +struct apacket +{ + apacket *next; + + unsigned len; + unsigned char *ptr; + + amessage msg; + unsigned char data[MAX_PAYLOAD]; +}; + +/* An asocket represents one half of a connection between a local and +** remote entity. A local asocket is bound to a file descriptor. A +** remote asocket is bound to the protocol engine. +*/ +struct asocket { + /* chain pointers for the local/remote list of + ** asockets that this asocket lives in + */ + asocket *next; + asocket *prev; + + /* the unique identifier for this asocket + */ + unsigned id; + + /* flag: set when the socket's peer has closed + ** but packets are still queued for delivery + */ + int closing; + + /* the asocket we are connected to + */ + + asocket *peer; + + /* For local asockets, the fde is used to bind + ** us to our fd event system. For remote asockets + ** these fields are not used. + */ + fdevent fde; + int fd; + + /* queue of apackets waiting to be written + */ + apacket *pkt_first; + apacket *pkt_last; + + /* enqueue is called by our peer when it has data + ** for us. It should return 0 if we can accept more + ** data or 1 if not. If we return 1, we must call + ** peer->ready() when we once again are ready to + ** receive data. + */ + int (*enqueue)(asocket *s, apacket *pkt); + + /* ready is called by the peer when it is ready for + ** us to send data via enqueue again + */ + void (*ready)(asocket *s); + + /* close is called by the peer when it has gone away. + ** we are not allowed to make any further calls on the + ** peer once our close method is called. + */ + void (*close)(asocket *s); + + /* socket-type-specific extradata */ + void *extra; + + /* A socket is bound to atransport */ + atransport *transport; +}; + + +/* the adisconnect structure is used to record a callback that +** will be called whenever a transport is disconnected (e.g. by the user) +** this should be used to cleanup objects that depend on the +** transport (e.g. remote sockets, etc...) +*/ +struct adisconnect +{ + void (*func)(void* opaque, atransport* t); + void* opaque; + adisconnect* next; + adisconnect* prev; +}; + + +/* a transport object models the connection to a remote device or emulator +** there is one transport per connected device/emulator. a "local transport" +** connects through TCP (for the emulator), while a "usb transport" through +** USB (for real devices) +** +** note that kTransportHost doesn't really correspond to a real transport +** object, it's a special value used to indicate that a client wants to +** connect to a service implemented within the ADB server itself. +*/ +typedef enum transport_type { + kTransportUsb, + kTransportLocal, + kTransportAny, + kTransportHost, +} transport_type; + +struct atransport +{ + atransport *next; + atransport *prev; + + int (*read_from_remote)(apacket *p, atransport *t); + int (*write_to_remote)(apacket *p, atransport *t); + void (*close)(atransport *t); + void (*kick)(atransport *t); + + int fd; + int transport_socket; + fdevent transport_fde; + int ref_count; + unsigned sync_token; + int connection_state; + transport_type type; + + /* usb handle or socket fd as needed */ + usb_handle *usb; + int sfd; + + /* used to identify transports for clients */ + char *serial; + char *product; + int adb_port; // Use for emulators (local transport) + + /* a list of adisconnect callbacks called when the transport is kicked */ + int kicked; + adisconnect disconnects; +}; + + +void print_packet(const char *label, apacket *p); + +asocket *find_local_socket(unsigned id); +void install_local_socket(asocket *s); +void remove_socket(asocket *s); +void close_all_sockets(atransport *t); + +#define LOCAL_CLIENT_PREFIX "emulator-" + +asocket *create_local_socket(int fd); +asocket *create_local_service_socket(const char *destination); + +asocket *create_remote_socket(unsigned id, atransport *t); +void connect_to_remote(asocket *s, const char *destination); +void connect_to_smartsocket(asocket *s); + +void fatal(const char *fmt, ...); +void fatal_errno(const char *fmt, ...); + +void handle_packet(apacket *p, atransport *t); +void send_packet(apacket *p, atransport *t); + +void get_my_path(char *s, size_t maxLen); +int launch_server(int server_port); +int adb_main(); + + +/* transports are ref-counted +** get_device_transport does an acquire on your behalf before returning +*/ +void init_transport_registration(void); +int list_transports(char *buf, size_t bufsize); +void update_transports(void); + +asocket* create_device_tracker(void); + +/* Obtain a transport from the available transports. +** If state is != CS_ANY, only transports in that state are considered. +** If serial is non-NULL then only the device with that serial will be chosen. +** If no suitable transport is found, error is set. +*/ +atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char **error_out); +void add_transport_disconnect( atransport* t, adisconnect* dis ); +void remove_transport_disconnect( atransport* t, adisconnect* dis ); +void run_transport_disconnects( atransport* t ); +void kick_transport( atransport* t ); + +/* initialize a transport object's func pointers and state */ +#if ADB_HOST +int get_available_local_transport_index(); +#endif +void init_usb_transport(atransport *t, usb_handle *usb, int state); + +/* for MacOS X cleanup */ +void close_usb_devices(); + +/* these should only be used for the "adb disconnect" command */ +void unregister_transport(atransport *t); +void unregister_all_tcp_transports(); + +void register_usb_transport(usb_handle *h, const char *serial, unsigned writeable); + +/* this should only be used for transports with connection_state == CS_NOPERM */ +void unregister_usb_transport(usb_handle *usb); + +atransport *find_transport(const char *serial); +#if ADB_HOST +atransport* find_emulator_transport_by_adb_port(int adb_port); +#endif + +int service_to_fd(const char *name); +#if ADB_HOST +asocket *host_service_to_socket(const char* name, const char *serial); +#endif + +#if !ADB_HOST +typedef enum { + BACKUP, + RESTORE +} BackupOperation; +int backup_service(BackupOperation operation, char* args); +void framebuffer_service(int fd, void *cookie); +void log_service(int fd, void *cookie); +void remount_service(int fd, void *cookie); +char * get_log_file_path(const char * log_name); +#endif + +/* packet allocator */ +apacket *get_apacket(void); +void put_apacket(apacket *p); + +int check_header(apacket *p); +int check_data(apacket *p); + +/* define ADB_TRACE to 1 to enable tracing support, or 0 to disable it */ + +#define ADB_TRACE 1 + +/* IMPORTANT: if you change the following list, don't + * forget to update the corresponding 'tags' table in + * the adb_trace_init() function implemented in adb.c + */ +typedef enum { + TRACE_ADB = 0, /* 0x001 */ + TRACE_SOCKETS, + TRACE_PACKETS, + TRACE_TRANSPORT, + TRACE_RWX, /* 0x010 */ + TRACE_USB, + TRACE_SYNC, + TRACE_SYSDEPS, + TRACE_JDWP, /* 0x100 */ + TRACE_SERVICES, +} AdbTrace; + +#if ADB_TRACE + + extern int adb_trace_mask; + extern unsigned char adb_trace_output_count; + void adb_trace_init(void); + +# define ADB_TRACING ((adb_trace_mask & (1 << TRACE_TAG)) != 0) + + /* you must define TRACE_TAG before using this macro */ +# define D(...) \ + do { \ + if (ADB_TRACING) { \ + int save_errno = errno; \ + adb_mutex_lock(&D_lock); \ + fprintf(stderr, "%s::%s():", \ + __FILE__, __FUNCTION__); \ + errno = save_errno; \ + fprintf(stderr, __VA_ARGS__ ); \ + fflush(stderr); \ + adb_mutex_unlock(&D_lock); \ + errno = save_errno; \ + } \ + } while (0) +# define DR(...) \ + do { \ + if (ADB_TRACING) { \ + int save_errno = errno; \ + adb_mutex_lock(&D_lock); \ + errno = save_errno; \ + fprintf(stderr, __VA_ARGS__ ); \ + fflush(stderr); \ + adb_mutex_unlock(&D_lock); \ + errno = save_errno; \ + } \ + } while (0) +#else +# define D(...) ((void)0) +# define DR(...) ((void)0) +# define ADB_TRACING 0 +#endif + + +#if !TRACE_PACKETS +#define print_packet(tag,p) do {} while (0) +#endif + +#if ADB_HOST_ON_TARGET +/* adb and adbd are coexisting on the target, so use 5038 for adb + * to avoid conflicting with adbd's usage of 5037 + */ +# define DEFAULT_ADB_PORT 5038 +#else +# define DEFAULT_ADB_PORT 5037 +#endif + +#define DEFAULT_ADB_LOCAL_TRANSPORT_PORT 5555 + +#define ADB_CLASS 0xff +#define ADB_SUBCLASS 0x42 +#define ADB_PROTOCOL 0x1 + + +void local_init(int port); +int local_connect(int port); +int local_connect_arbitrary_ports(int console_port, int adb_port); + +/* usb host/client interface */ +void usb_init(); +void usb_cleanup(); +int usb_write(usb_handle *h, const void *data, int len); +int usb_read(usb_handle *h, void *data, int len); +int usb_close(usb_handle *h); +void usb_kick(usb_handle *h); + +/* used for USB device detection */ +#if ADB_HOST +int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol); +#endif + +unsigned host_to_le32(unsigned n); +int adb_commandline(int argc, char **argv); + +int connection_state(atransport *t); + +#define CS_ANY -1 +#define CS_OFFLINE 0 +#define CS_BOOTLOADER 1 +#define CS_DEVICE 2 +#define CS_HOST 3 +#define CS_RECOVERY 4 +#define CS_NOPERM 5 /* Insufficient permissions to communicate with the device */ +#define CS_SIDELOAD 6 + +extern int HOST; +extern int SHELL_EXIT_NOTIFY_FD; + +#define CHUNK_SIZE (64*1024) + +#if !ADB_HOST +#define USB_ADB_PATH "/dev/android_adb" + +#define USB_FFS_ADB_PATH "/dev/usb-ffs/adb/" +#define USB_FFS_ADB_EP(x) USB_FFS_ADB_PATH#x + +#define USB_FFS_ADB_EP0 USB_FFS_ADB_EP(ep0) +#define USB_FFS_ADB_OUT USB_FFS_ADB_EP(ep1) +#define USB_FFS_ADB_IN USB_FFS_ADB_EP(ep2) +#endif + +int sendfailmsg(int fd, const char *reason); +int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s); + +#define ADB_SIDELOAD_FILENAME "/tmp/update.zip" + +#endif diff --git a/minadbd/fdevent.c b/minadbd/fdevent.c new file mode 100644 index 000000000..5c374a71b --- /dev/null +++ b/minadbd/fdevent.c @@ -0,0 +1,695 @@ +/* http://frotznet.googlecode.com/svn/trunk/utils/fdevent.c +** +** Copyright 2006, Brian Swetland +** +** 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 + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "fdevent.h" +#include "transport.h" +#include "sysdeps.h" + + +/* !!! Do not enable DEBUG for the adb that will run as the server: +** both stdout and stderr are used to communicate between the client +** and server. Any extra output will cause failures. +*/ +#define DEBUG 0 /* non-0 will break adb server */ + +// This socket is used when a subproc shell service exists. +// It wakes up the fdevent_loop() and cause the correct handling +// of the shell's pseudo-tty master. I.e. force close it. +int SHELL_EXIT_NOTIFY_FD = -1; + +static void fatal(const char *fn, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:", fn); + vfprintf(stderr, fmt, ap); + va_end(ap); + abort(); +} + +#define FATAL(x...) fatal(__FUNCTION__, x) + +#if DEBUG +#define D(...) \ + do { \ + adb_mutex_lock(&D_lock); \ + int save_errno = errno; \ + fprintf(stderr, "%s::%s():", __FILE__, __FUNCTION__); \ + errno = save_errno; \ + fprintf(stderr, __VA_ARGS__); \ + adb_mutex_unlock(&D_lock); \ + errno = save_errno; \ + } while(0) +static void dump_fde(fdevent *fde, const char *info) +{ + adb_mutex_lock(&D_lock); + fprintf(stderr,"FDE #%03d %c%c%c %s\n", fde->fd, + fde->state & FDE_READ ? 'R' : ' ', + fde->state & FDE_WRITE ? 'W' : ' ', + fde->state & FDE_ERROR ? 'E' : ' ', + info); + adb_mutex_unlock(&D_lock); +} +#else +#define D(...) ((void)0) +#define dump_fde(fde, info) do { } while(0) +#endif + +#define FDE_EVENTMASK 0x00ff +#define FDE_STATEMASK 0xff00 + +#define FDE_ACTIVE 0x0100 +#define FDE_PENDING 0x0200 +#define FDE_CREATED 0x0400 + +static void fdevent_plist_enqueue(fdevent *node); +static void fdevent_plist_remove(fdevent *node); +static fdevent *fdevent_plist_dequeue(void); +static void fdevent_subproc_event_func(int fd, unsigned events, void *userdata); + +static fdevent list_pending = { + .next = &list_pending, + .prev = &list_pending, +}; + +static fdevent **fd_table = 0; +static int fd_table_max = 0; + +#ifdef CRAPTASTIC +//HAVE_EPOLL + +#include + +static int epoll_fd = -1; + +static void fdevent_init() +{ + /* XXX: what's a good size for the passed in hint? */ + epoll_fd = epoll_create(256); + + if(epoll_fd < 0) { + perror("epoll_create() failed"); + exit(1); + } + + /* mark for close-on-exec */ + fcntl(epoll_fd, F_SETFD, FD_CLOEXEC); +} + +static void fdevent_connect(fdevent *fde) +{ + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = 0; + ev.data.ptr = fde; + +#if 0 + if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fde->fd, &ev)) { + perror("epoll_ctl() failed\n"); + exit(1); + } +#endif +} + +static void fdevent_disconnect(fdevent *fde) +{ + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = 0; + ev.data.ptr = fde; + + /* technically we only need to delete if we + ** were actively monitoring events, but let's + ** be aggressive and do it anyway, just in case + ** something's out of sync + */ + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fde->fd, &ev); +} + +static void fdevent_update(fdevent *fde, unsigned events) +{ + struct epoll_event ev; + int active; + + active = (fde->state & FDE_EVENTMASK) != 0; + + memset(&ev, 0, sizeof(ev)); + ev.events = 0; + ev.data.ptr = fde; + + if(events & FDE_READ) ev.events |= EPOLLIN; + if(events & FDE_WRITE) ev.events |= EPOLLOUT; + if(events & FDE_ERROR) ev.events |= (EPOLLERR | EPOLLHUP); + + fde->state = (fde->state & FDE_STATEMASK) | events; + + if(active) { + /* we're already active. if we're changing to *no* + ** events being monitored, we need to delete, otherwise + ** we need to just modify + */ + if(ev.events) { + if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fde->fd, &ev)) { + perror("epoll_ctl() failed\n"); + exit(1); + } + } else { + if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fde->fd, &ev)) { + perror("epoll_ctl() failed\n"); + exit(1); + } + } + } else { + /* we're not active. if we're watching events, we need + ** to add, otherwise we can just do nothing + */ + if(ev.events) { + if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fde->fd, &ev)) { + perror("epoll_ctl() failed\n"); + exit(1); + } + } + } +} + +static void fdevent_process() +{ + struct epoll_event events[256]; + fdevent *fde; + int i, n; + + n = epoll_wait(epoll_fd, events, 256, -1); + + if(n < 0) { + if(errno == EINTR) return; + perror("epoll_wait"); + exit(1); + } + + for(i = 0; i < n; i++) { + struct epoll_event *ev = events + i; + fde = ev->data.ptr; + + if(ev->events & EPOLLIN) { + fde->events |= FDE_READ; + } + if(ev->events & EPOLLOUT) { + fde->events |= FDE_WRITE; + } + if(ev->events & (EPOLLERR | EPOLLHUP)) { + fde->events |= FDE_ERROR; + } + if(fde->events) { + if(fde->state & FDE_PENDING) continue; + fde->state |= FDE_PENDING; + fdevent_plist_enqueue(fde); + } + } +} + +#else /* USE_SELECT */ + +#ifdef HAVE_WINSOCK +#include +#else +#include +#endif + +static fd_set read_fds; +static fd_set write_fds; +static fd_set error_fds; + +static int select_n = 0; + +static void fdevent_init(void) +{ + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + FD_ZERO(&error_fds); +} + +static void fdevent_connect(fdevent *fde) +{ + if(fde->fd >= select_n) { + select_n = fde->fd + 1; + } +} + +static void fdevent_disconnect(fdevent *fde) +{ + int i, n; + + FD_CLR(fde->fd, &read_fds); + FD_CLR(fde->fd, &write_fds); + FD_CLR(fde->fd, &error_fds); + + for(n = 0, i = 0; i < select_n; i++) { + if(fd_table[i] != 0) n = i; + } + select_n = n + 1; +} + +static void fdevent_update(fdevent *fde, unsigned events) +{ + if(events & FDE_READ) { + FD_SET(fde->fd, &read_fds); + } else { + FD_CLR(fde->fd, &read_fds); + } + if(events & FDE_WRITE) { + FD_SET(fde->fd, &write_fds); + } else { + FD_CLR(fde->fd, &write_fds); + } + if(events & FDE_ERROR) { + FD_SET(fde->fd, &error_fds); + } else { + FD_CLR(fde->fd, &error_fds); + } + + fde->state = (fde->state & FDE_STATEMASK) | events; +} + +/* Looks at fd_table[] for bad FDs and sets bit in fds. +** Returns the number of bad FDs. +*/ +static int fdevent_fd_check(fd_set *fds) +{ + int i, n = 0; + fdevent *fde; + + for(i = 0; i < select_n; i++) { + fde = fd_table[i]; + if(fde == 0) continue; + if(fcntl(i, F_GETFL, NULL) < 0) { + FD_SET(i, fds); + n++; + // fde->state |= FDE_DONT_CLOSE; + + } + } + return n; +} + +#if !DEBUG +static inline void dump_all_fds(const char *extra_msg) {} +#else +static void dump_all_fds(const char *extra_msg) +{ +int i; + fdevent *fde; + // per fd: 4 digits (but really: log10(FD_SETSIZE)), 1 staus, 1 blank + char msg_buff[FD_SETSIZE*6 + 1], *pb=msg_buff; + size_t max_chars = FD_SETSIZE * 6 + 1; + int printed_out; +#define SAFE_SPRINTF(...) \ + do { \ + printed_out = snprintf(pb, max_chars, __VA_ARGS__); \ + if (printed_out <= 0) { \ + D("... snprintf failed.\n"); \ + return; \ + } \ + if (max_chars < (unsigned int)printed_out) { \ + D("... snprintf out of space.\n"); \ + return; \ + } \ + pb += printed_out; \ + max_chars -= printed_out; \ + } while(0) + + for(i = 0; i < select_n; i++) { + fde = fd_table[i]; + SAFE_SPRINTF("%d", i); + if(fde == 0) { + SAFE_SPRINTF("? "); + continue; + } + if(fcntl(i, F_GETFL, NULL) < 0) { + SAFE_SPRINTF("b"); + } + SAFE_SPRINTF(" "); + } + D("%s fd_table[]->fd = {%s}\n", extra_msg, msg_buff); +} +#endif + +static void fdevent_process() +{ + int i, n; + fdevent *fde; + unsigned events; + fd_set rfd, wfd, efd; + + memcpy(&rfd, &read_fds, sizeof(fd_set)); + memcpy(&wfd, &write_fds, sizeof(fd_set)); + memcpy(&efd, &error_fds, sizeof(fd_set)); + + dump_all_fds("pre select()"); + + n = select(select_n, &rfd, &wfd, &efd, NULL); + int saved_errno = errno; + D("select() returned n=%d, errno=%d\n", n, n<0?saved_errno:0); + + dump_all_fds("post select()"); + + if(n < 0) { + switch(saved_errno) { + case EINTR: return; + case EBADF: + // Can't trust the FD sets after an error. + FD_ZERO(&wfd); + FD_ZERO(&efd); + FD_ZERO(&rfd); + break; + default: + D("Unexpected select() error=%d\n", saved_errno); + return; + } + } + if(n <= 0) { + // We fake a read, as the rest of the code assumes + // that errors will be detected at that point. + n = fdevent_fd_check(&rfd); + } + + for(i = 0; (i < select_n) && (n > 0); i++) { + events = 0; + if(FD_ISSET(i, &rfd)) { events |= FDE_READ; n--; } + if(FD_ISSET(i, &wfd)) { events |= FDE_WRITE; n--; } + if(FD_ISSET(i, &efd)) { events |= FDE_ERROR; n--; } + + if(events) { + fde = fd_table[i]; + if(fde == 0) + FATAL("missing fde for fd %d\n", i); + + fde->events |= events; + + D("got events fde->fd=%d events=%04x, state=%04x\n", + fde->fd, fde->events, fde->state); + if(fde->state & FDE_PENDING) continue; + fde->state |= FDE_PENDING; + fdevent_plist_enqueue(fde); + } + } +} + +#endif + +static void fdevent_register(fdevent *fde) +{ + if(fde->fd < 0) { + FATAL("bogus negative fd (%d)\n", fde->fd); + } + + if(fde->fd >= fd_table_max) { + int oldmax = fd_table_max; + if(fde->fd > 32000) { + FATAL("bogus huuuuge fd (%d)\n", fde->fd); + } + if(fd_table_max == 0) { + fdevent_init(); + fd_table_max = 256; + } + while(fd_table_max <= fde->fd) { + fd_table_max *= 2; + } + fd_table = realloc(fd_table, sizeof(fdevent*) * fd_table_max); + if(fd_table == 0) { + FATAL("could not expand fd_table to %d entries\n", fd_table_max); + } + memset(fd_table + oldmax, 0, sizeof(int) * (fd_table_max - oldmax)); + } + + fd_table[fde->fd] = fde; +} + +static void fdevent_unregister(fdevent *fde) +{ + if((fde->fd < 0) || (fde->fd >= fd_table_max)) { + FATAL("fd out of range (%d)\n", fde->fd); + } + + if(fd_table[fde->fd] != fde) { + FATAL("fd_table out of sync [%d]\n", fde->fd); + } + + fd_table[fde->fd] = 0; + + if(!(fde->state & FDE_DONT_CLOSE)) { + dump_fde(fde, "close"); + adb_close(fde->fd); + } +} + +static void fdevent_plist_enqueue(fdevent *node) +{ + fdevent *list = &list_pending; + + node->next = list; + node->prev = list->prev; + node->prev->next = node; + list->prev = node; +} + +static void fdevent_plist_remove(fdevent *node) +{ + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; +} + +static fdevent *fdevent_plist_dequeue(void) +{ + fdevent *list = &list_pending; + fdevent *node = list->next; + + if(node == list) return 0; + + list->next = node->next; + list->next->prev = list; + node->next = 0; + node->prev = 0; + + return node; +} + +static void fdevent_call_fdfunc(fdevent* fde) +{ + unsigned events = fde->events; + fde->events = 0; + if(!(fde->state & FDE_PENDING)) return; + fde->state &= (~FDE_PENDING); + dump_fde(fde, "callback"); + fde->func(fde->fd, events, fde->arg); +} + +static void fdevent_subproc_event_func(int fd, unsigned ev, void *userdata) +{ + + D("subproc handling on fd=%d ev=%04x\n", fd, ev); + + // Hook oneself back into the fde's suitable for select() on read. + if((fd < 0) || (fd >= fd_table_max)) { + FATAL("fd %d out of range for fd_table \n", fd); + } + fdevent *fde = fd_table[fd]; + fdevent_add(fde, FDE_READ); + + if(ev & FDE_READ){ + int subproc_fd; + + if(readx(fd, &subproc_fd, sizeof(subproc_fd))) { + FATAL("Failed to read the subproc's fd from fd=%d\n", fd); + } + if((subproc_fd < 0) || (subproc_fd >= fd_table_max)) { + D("subproc_fd %d out of range 0, fd_table_max=%d\n", + subproc_fd, fd_table_max); + return; + } + fdevent *subproc_fde = fd_table[subproc_fd]; + if(!subproc_fde) { + D("subproc_fd %d cleared from fd_table\n", subproc_fd); + return; + } + if(subproc_fde->fd != subproc_fd) { + // Already reallocated? + D("subproc_fd %d != fd_table[].fd %d\n", subproc_fd, subproc_fde->fd); + return; + } + + subproc_fde->force_eof = 1; + + int rcount = 0; + ioctl(subproc_fd, FIONREAD, &rcount); + D("subproc with fd=%d has rcount=%d err=%d\n", + subproc_fd, rcount, errno); + + if(rcount) { + // If there is data left, it will show up in the select(). + // This works because there is no other thread reading that + // data when in this fd_func(). + return; + } + + D("subproc_fde.state=%04x\n", subproc_fde->state); + subproc_fde->events |= FDE_READ; + if(subproc_fde->state & FDE_PENDING) { + return; + } + subproc_fde->state |= FDE_PENDING; + fdevent_call_fdfunc(subproc_fde); + } +} + +fdevent *fdevent_create(int fd, fd_func func, void *arg) +{ + fdevent *fde = (fdevent*) malloc(sizeof(fdevent)); + if(fde == 0) return 0; + fdevent_install(fde, fd, func, arg); + fde->state |= FDE_CREATED; + return fde; +} + +void fdevent_destroy(fdevent *fde) +{ + if(fde == 0) return; + if(!(fde->state & FDE_CREATED)) { + FATAL("fde %p not created by fdevent_create()\n", fde); + } + fdevent_remove(fde); +} + +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg) +{ + memset(fde, 0, sizeof(fdevent)); + fde->state = FDE_ACTIVE; + fde->fd = fd; + fde->force_eof = 0; + fde->func = func; + fde->arg = arg; + +#ifndef HAVE_WINSOCK + fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + fdevent_register(fde); + dump_fde(fde, "connect"); + fdevent_connect(fde); + fde->state |= FDE_ACTIVE; +} + +void fdevent_remove(fdevent *fde) +{ + if(fde->state & FDE_PENDING) { + fdevent_plist_remove(fde); + } + + if(fde->state & FDE_ACTIVE) { + fdevent_disconnect(fde); + dump_fde(fde, "disconnect"); + fdevent_unregister(fde); + } + + fde->state = 0; + fde->events = 0; +} + + +void fdevent_set(fdevent *fde, unsigned events) +{ + events &= FDE_EVENTMASK; + + if((fde->state & FDE_EVENTMASK) == events) return; + + if(fde->state & FDE_ACTIVE) { + fdevent_update(fde, events); + dump_fde(fde, "update"); + } + + fde->state = (fde->state & FDE_STATEMASK) | events; + + if(fde->state & FDE_PENDING) { + /* if we're pending, make sure + ** we don't signal an event that + ** is no longer wanted. + */ + fde->events &= (~events); + if(fde->events == 0) { + fdevent_plist_remove(fde); + fde->state &= (~FDE_PENDING); + } + } +} + +void fdevent_add(fdevent *fde, unsigned events) +{ + fdevent_set( + fde, (fde->state & FDE_EVENTMASK) | (events & FDE_EVENTMASK)); +} + +void fdevent_del(fdevent *fde, unsigned events) +{ + fdevent_set( + fde, (fde->state & FDE_EVENTMASK) & (~(events & FDE_EVENTMASK))); +} + +void fdevent_subproc_setup() +{ + int s[2]; + + if(adb_socketpair(s)) { + FATAL("cannot create shell-exit socket-pair\n"); + } + SHELL_EXIT_NOTIFY_FD = s[0]; + fdevent *fde; + fde = fdevent_create(s[1], fdevent_subproc_event_func, NULL); + if(!fde) + FATAL("cannot create fdevent for shell-exit handler\n"); + fdevent_add(fde, FDE_READ); +} + +void fdevent_loop() +{ + fdevent *fde; + fdevent_subproc_setup(); + + for(;;) { + D("--- ---- waiting for events\n"); + + fdevent_process(); + + while((fde = fdevent_plist_dequeue())) { + fdevent_call_fdfunc(fde); + } + } +} diff --git a/minadbd/fdevent.h b/minadbd/fdevent.h new file mode 100644 index 000000000..a0ebe2a7e --- /dev/null +++ b/minadbd/fdevent.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FDEVENT_H +#define __FDEVENT_H + +#include /* for int64_t */ + +/* events that may be observed */ +#define FDE_READ 0x0001 +#define FDE_WRITE 0x0002 +#define FDE_ERROR 0x0004 +#define FDE_TIMEOUT 0x0008 + +/* features that may be set (via the events set/add/del interface) */ +#define FDE_DONT_CLOSE 0x0080 + +typedef struct fdevent fdevent; + +typedef void (*fd_func)(int fd, unsigned events, void *userdata); + +/* Allocate and initialize a new fdevent object + * Note: use FD_TIMER as 'fd' to create a fd-less object + * (used to implement timers). +*/ +fdevent *fdevent_create(int fd, fd_func func, void *arg); + +/* Uninitialize and deallocate an fdevent object that was +** created by fdevent_create() +*/ +void fdevent_destroy(fdevent *fde); + +/* Initialize an fdevent object that was externally allocated +*/ +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg); + +/* Uninitialize an fdevent object that was initialized by +** fdevent_install() +*/ +void fdevent_remove(fdevent *item); + +/* Change which events should cause notifications +*/ +void fdevent_set(fdevent *fde, unsigned events); +void fdevent_add(fdevent *fde, unsigned events); +void fdevent_del(fdevent *fde, unsigned events); + +void fdevent_set_timeout(fdevent *fde, int64_t timeout_ms); + +/* loop forever, handling events. +*/ +void fdevent_loop(); + +struct fdevent +{ + fdevent *next; + fdevent *prev; + + int fd; + int force_eof; + + unsigned short state; + unsigned short events; + + fd_func func; + void *arg; +}; + + +#endif diff --git a/minadbd/mutex_list.h b/minadbd/mutex_list.h new file mode 100644 index 000000000..652dd7341 --- /dev/null +++ b/minadbd/mutex_list.h @@ -0,0 +1,26 @@ +/* the list of mutexes used by adb */ +/* #ifndef __MUTEX_LIST_H + * Do not use an include-guard. This file is included once to declare the locks + * and once in win32 to actually do the runtime initialization. + */ +#ifndef ADB_MUTEX +#error ADB_MUTEX not defined when including this file +#endif +ADB_MUTEX(dns_lock) +ADB_MUTEX(socket_list_lock) +ADB_MUTEX(transport_lock) +#if ADB_HOST +ADB_MUTEX(local_transports_lock) +#endif +ADB_MUTEX(usb_lock) + +// Sadly logging to /data/adb/adb-... is not thread safe. +// After modifying adb.h::D() to count invocations: +// DEBUG(jpa):0:Handling main() +// DEBUG(jpa):1:[ usb_init - starting thread ] +// (Oopsies, no :2:, and matching message is also gone.) +// DEBUG(jpa):3:[ usb_thread - opening device ] +// DEBUG(jpa):4:jdwp control socket started (10) +ADB_MUTEX(D_lock) + +#undef ADB_MUTEX diff --git a/minadbd/services.c b/minadbd/services.c new file mode 100644 index 000000000..a2d45aa2c --- /dev/null +++ b/minadbd/services.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include // for uintptr_t +#include +#include +#include +#include + +#include "sysdeps.h" +#include "fdevent.h" + +#define TRACE_TAG TRACE_SERVICES +#include "adb.h" + +typedef struct stinfo stinfo; + +struct stinfo { + void (*func)(int fd, void *cookie); + int fd; + void *cookie; +}; + + +void *service_bootstrap_func(void *x) +{ + stinfo *sti = x; + sti->func(sti->fd, sti->cookie); + free(sti); + return 0; +} + +static void sideload_service(int s, void *cookie) +{ + unsigned char buf[4096]; + unsigned count = (unsigned)(uintptr_t)cookie; + int fd; + + fprintf(stderr, "sideload_service invoked\n"); + + fd = adb_creat(ADB_SIDELOAD_FILENAME, 0644); + if(fd < 0) { + fprintf(stderr, "failed to create %s\n", ADB_SIDELOAD_FILENAME); + adb_close(s); + exit(1); + } + + while(count > 0) { + unsigned xfer = (count > 4096) ? 4096 : count; + if(readx(s, buf, xfer)) break; + if(writex(fd, buf, xfer)) break; + count -= xfer; + } + + if(count == 0) { + writex(s, "OKAY", 4); + } else { + writex(s, "FAIL", 4); + } + adb_close(fd); + adb_close(s); + + if (count == 0) { + fprintf(stderr, "adbd exiting after successful sideload\n"); + sleep(1); + exit(0); + } +} + + +#if 0 +static void echo_service(int fd, void *cookie) +{ + char buf[4096]; + int r; + char *p; + int c; + + for(;;) { + r = read(fd, buf, 4096); + if(r == 0) goto done; + if(r < 0) { + if(errno == EINTR) continue; + else goto done; + } + + c = r; + p = buf; + while(c > 0) { + r = write(fd, p, c); + if(r > 0) { + c -= r; + p += r; + continue; + } + if((r < 0) && (errno == EINTR)) continue; + goto done; + } + } +done: + close(fd); +} +#endif + +static int create_service_thread(void (*func)(int, void *), void *cookie) +{ + stinfo *sti; + adb_thread_t t; + int s[2]; + + if(adb_socketpair(s)) { + printf("cannot create service socket pair\n"); + return -1; + } + + sti = malloc(sizeof(stinfo)); + if(sti == 0) fatal("cannot allocate stinfo"); + sti->func = func; + sti->cookie = cookie; + sti->fd = s[1]; + + if(adb_thread_create( &t, service_bootstrap_func, sti)){ + free(sti); + adb_close(s[0]); + adb_close(s[1]); + printf("cannot create service thread\n"); + return -1; + } + + D("service thread started, %d:%d\n",s[0], s[1]); + return s[0]; +} + +int service_to_fd(const char *name) +{ + int ret = -1; + + if (!strncmp(name, "sideload:", 9)) { + ret = create_service_thread(sideload_service, (void*)(uintptr_t)atoi(name + 9)); +#if 0 + } else if(!strncmp(name, "echo:", 5)){ + ret = create_service_thread(echo_service, 0); +#endif + } + if (ret >= 0) { + close_on_exec(ret); + } + return ret; +} diff --git a/minadbd/sockets.c b/minadbd/sockets.c new file mode 100644 index 000000000..2dd646159 --- /dev/null +++ b/minadbd/sockets.c @@ -0,0 +1,730 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_SOCKETS +#include "adb.h" + +ADB_MUTEX_DEFINE( socket_list_lock ); + +static void local_socket_close_locked(asocket *s); + +int sendfailmsg(int fd, const char *reason) +{ + char buf[9]; + int len; + len = strlen(reason); + if(len > 0xffff) len = 0xffff; + snprintf(buf, sizeof buf, "FAIL%04x", len); + if(writex(fd, buf, 8)) return -1; + return writex(fd, reason, len); +} + +//extern int online; + +static unsigned local_socket_next_id = 1; + +static asocket local_socket_list = { + .next = &local_socket_list, + .prev = &local_socket_list, +}; + +/* the the list of currently closing local sockets. +** these have no peer anymore, but still packets to +** write to their fd. +*/ +static asocket local_socket_closing_list = { + .next = &local_socket_closing_list, + .prev = &local_socket_closing_list, +}; + +asocket *find_local_socket(unsigned id) +{ + asocket *s; + asocket *result = NULL; + + adb_mutex_lock(&socket_list_lock); + for (s = local_socket_list.next; s != &local_socket_list; s = s->next) { + if (s->id == id) { + result = s; + break; + } + } + adb_mutex_unlock(&socket_list_lock); + + return result; +} + +static void +insert_local_socket(asocket* s, asocket* list) +{ + s->next = list; + s->prev = s->next->prev; + s->prev->next = s; + s->next->prev = s; +} + + +void install_local_socket(asocket *s) +{ + adb_mutex_lock(&socket_list_lock); + + s->id = local_socket_next_id++; + insert_local_socket(s, &local_socket_list); + + adb_mutex_unlock(&socket_list_lock); +} + +void remove_socket(asocket *s) +{ + // socket_list_lock should already be held + if (s->prev && s->next) + { + s->prev->next = s->next; + s->next->prev = s->prev; + s->next = 0; + s->prev = 0; + s->id = 0; + } +} + +void close_all_sockets(atransport *t) +{ + asocket *s; + + /* this is a little gross, but since s->close() *will* modify + ** the list out from under you, your options are limited. + */ + adb_mutex_lock(&socket_list_lock); +restart: + for(s = local_socket_list.next; s != &local_socket_list; s = s->next){ + if(s->transport == t || (s->peer && s->peer->transport == t)) { + local_socket_close_locked(s); + goto restart; + } + } + adb_mutex_unlock(&socket_list_lock); +} + +static int local_socket_enqueue(asocket *s, apacket *p) +{ + D("LS(%d): enqueue %d\n", s->id, p->len); + + p->ptr = p->data; + + /* if there is already data queue'd, we will receive + ** events when it's time to write. just add this to + ** the tail + */ + if(s->pkt_first) { + goto enqueue; + } + + /* write as much as we can, until we + ** would block or there is an error/eof + */ + while(p->len > 0) { + int r = adb_write(s->fd, p->ptr, p->len); + if(r > 0) { + p->len -= r; + p->ptr += r; + continue; + } + if((r == 0) || (errno != EAGAIN)) { + D( "LS(%d): not ready, errno=%d: %s\n", s->id, errno, strerror(errno) ); + s->close(s); + return 1; /* not ready (error) */ + } else { + break; + } + } + + if(p->len == 0) { + put_apacket(p); + return 0; /* ready for more data */ + } + +enqueue: + p->next = 0; + if(s->pkt_first) { + s->pkt_last->next = p; + } else { + s->pkt_first = p; + } + s->pkt_last = p; + + /* make sure we are notified when we can drain the queue */ + fdevent_add(&s->fde, FDE_WRITE); + + return 1; /* not ready (backlog) */ +} + +static void local_socket_ready(asocket *s) +{ + /* far side is ready for data, pay attention to + readable events */ + fdevent_add(&s->fde, FDE_READ); +// D("LS(%d): ready()\n", s->id); +} + +static void local_socket_close(asocket *s) +{ + adb_mutex_lock(&socket_list_lock); + local_socket_close_locked(s); + adb_mutex_unlock(&socket_list_lock); +} + +// be sure to hold the socket list lock when calling this +static void local_socket_destroy(asocket *s) +{ + apacket *p, *n; + D("LS(%d): destroying fde.fd=%d\n", s->id, s->fde.fd); + + /* IMPORTANT: the remove closes the fd + ** that belongs to this socket + */ + fdevent_remove(&s->fde); + + /* dispose of any unwritten data */ + for(p = s->pkt_first; p; p = n) { + D("LS(%d): discarding %d bytes\n", s->id, p->len); + n = p->next; + put_apacket(p); + } + remove_socket(s); + free(s); +} + + +static void local_socket_close_locked(asocket *s) +{ + D("entered. LS(%d) fd=%d\n", s->id, s->fd); + if(s->peer) { + D("LS(%d): closing peer. peer->id=%d peer->fd=%d\n", + s->id, s->peer->id, s->peer->fd); + s->peer->peer = 0; + // tweak to avoid deadlock + if (s->peer->close == local_socket_close) { + local_socket_close_locked(s->peer); + } else { + s->peer->close(s->peer); + } + s->peer = 0; + } + + /* If we are already closing, or if there are no + ** pending packets, destroy immediately + */ + if (s->closing || s->pkt_first == NULL) { + int id = s->id; + local_socket_destroy(s); + D("LS(%d): closed\n", id); + return; + } + + /* otherwise, put on the closing list + */ + D("LS(%d): closing\n", s->id); + s->closing = 1; + fdevent_del(&s->fde, FDE_READ); + remove_socket(s); + D("LS(%d): put on socket_closing_list fd=%d\n", s->id, s->fd); + insert_local_socket(s, &local_socket_closing_list); +} + +static void local_socket_event_func(int fd, unsigned ev, void *_s) +{ + asocket *s = _s; + + D("LS(%d): event_func(fd=%d(==%d), ev=%04x)\n", s->id, s->fd, fd, ev); + + /* put the FDE_WRITE processing before the FDE_READ + ** in order to simplify the code. + */ + if(ev & FDE_WRITE){ + apacket *p; + + while((p = s->pkt_first) != 0) { + while(p->len > 0) { + int r = adb_write(fd, p->ptr, p->len); + if(r > 0) { + p->ptr += r; + p->len -= r; + continue; + } + if(r < 0) { + /* returning here is ok because FDE_READ will + ** be processed in the next iteration loop + */ + if(errno == EAGAIN) return; + if(errno == EINTR) continue; + } + D(" closing after write because r=%d and errno is %d\n", r, errno); + s->close(s); + return; + } + + if(p->len == 0) { + s->pkt_first = p->next; + if(s->pkt_first == 0) s->pkt_last = 0; + put_apacket(p); + } + } + + /* if we sent the last packet of a closing socket, + ** we can now destroy it. + */ + if (s->closing) { + D(" closing because 'closing' is set after write\n"); + s->close(s); + return; + } + + /* no more packets queued, so we can ignore + ** writable events again and tell our peer + ** to resume writing + */ + fdevent_del(&s->fde, FDE_WRITE); + s->peer->ready(s->peer); + } + + + if(ev & FDE_READ){ + apacket *p = get_apacket(); + unsigned char *x = p->data; + size_t avail = MAX_PAYLOAD; + int r; + int is_eof = 0; + + while(avail > 0) { + r = adb_read(fd, x, avail); + D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%d\n", s->id, s->fd, r, r<0?errno:0, avail); + if(r > 0) { + avail -= r; + x += r; + continue; + } + if(r < 0) { + if(errno == EAGAIN) break; + if(errno == EINTR) continue; + } + + /* r = 0 or unhandled error */ + is_eof = 1; + break; + } + D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d\n", + s->id, s->fd, r, is_eof, s->fde.force_eof); + if((avail == MAX_PAYLOAD) || (s->peer == 0)) { + put_apacket(p); + } else { + p->len = MAX_PAYLOAD - avail; + + r = s->peer->enqueue(s->peer, p); + D("LS(%d): fd=%d post peer->enqueue(). r=%d\n", s->id, s->fd, r); + + if(r < 0) { + /* error return means they closed us as a side-effect + ** and we must return immediately. + ** + ** note that if we still have buffered packets, the + ** socket will be placed on the closing socket list. + ** this handler function will be called again + ** to process FDE_WRITE events. + */ + return; + } + + if(r > 0) { + /* if the remote cannot accept further events, + ** we disable notification of READs. They'll + ** be enabled again when we get a call to ready() + */ + fdevent_del(&s->fde, FDE_READ); + } + } + /* Don't allow a forced eof if data is still there */ + if((s->fde.force_eof && !r) || is_eof) { + D(" closing because is_eof=%d r=%d s->fde.force_eof=%d\n", is_eof, r, s->fde.force_eof); + s->close(s); + } + } + + if(ev & FDE_ERROR){ + /* this should be caught be the next read or write + ** catching it here means we may skip the last few + ** bytes of readable data. + */ +// s->close(s); + D("LS(%d): FDE_ERROR (fd=%d)\n", s->id, s->fd); + + return; + } +} + +asocket *create_local_socket(int fd) +{ + asocket *s = calloc(1, sizeof(asocket)); + if (s == NULL) fatal("cannot allocate socket"); + s->fd = fd; + s->enqueue = local_socket_enqueue; + s->ready = local_socket_ready; + s->close = local_socket_close; + install_local_socket(s); + + fdevent_install(&s->fde, fd, local_socket_event_func, s); +/* fdevent_add(&s->fde, FDE_ERROR); */ + //fprintf(stderr, "Created local socket in create_local_socket \n"); + D("LS(%d): created (fd=%d)\n", s->id, s->fd); + return s; +} + +asocket *create_local_service_socket(const char *name) +{ + asocket *s; + int fd; + + fd = service_to_fd(name); + if(fd < 0) return 0; + + s = create_local_socket(fd); + D("LS(%d): bound to '%s' via %d\n", s->id, name, fd); + return s; +} + +/* a Remote socket is used to send/receive data to/from a given transport object +** it needs to be closed when the transport is forcibly destroyed by the user +*/ +typedef struct aremotesocket { + asocket socket; + adisconnect disconnect; +} aremotesocket; + +static int remote_socket_enqueue(asocket *s, apacket *p) +{ + D("entered remote_socket_enqueue RS(%d) WRITE fd=%d peer.fd=%d\n", + s->id, s->fd, s->peer->fd); + p->msg.command = A_WRTE; + p->msg.arg0 = s->peer->id; + p->msg.arg1 = s->id; + p->msg.data_length = p->len; + send_packet(p, s->transport); + return 1; +} + +static void remote_socket_ready(asocket *s) +{ + D("entered remote_socket_ready RS(%d) OKAY fd=%d peer.fd=%d\n", + s->id, s->fd, s->peer->fd); + apacket *p = get_apacket(); + p->msg.command = A_OKAY; + p->msg.arg0 = s->peer->id; + p->msg.arg1 = s->id; + send_packet(p, s->transport); +} + +static void remote_socket_close(asocket *s) +{ + D("entered remote_socket_close RS(%d) CLOSE fd=%d peer->fd=%d\n", + s->id, s->fd, s->peer?s->peer->fd:-1); + apacket *p = get_apacket(); + p->msg.command = A_CLSE; + if(s->peer) { + p->msg.arg0 = s->peer->id; + s->peer->peer = 0; + D("RS(%d) peer->close()ing peer->id=%d peer->fd=%d\n", + s->id, s->peer->id, s->peer->fd); + s->peer->close(s->peer); + } + p->msg.arg1 = s->id; + send_packet(p, s->transport); + D("RS(%d): closed\n", s->id); + remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect ); + free(s); +} + +static void remote_socket_disconnect(void* _s, atransport* t) +{ + asocket* s = _s; + asocket* peer = s->peer; + + D("remote_socket_disconnect RS(%d)\n", s->id); + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect ); + free(s); +} + +asocket *create_remote_socket(unsigned id, atransport *t) +{ + asocket *s = calloc(1, sizeof(aremotesocket)); + adisconnect* dis = &((aremotesocket*)s)->disconnect; + + if (s == NULL) fatal("cannot allocate socket"); + s->id = id; + s->enqueue = remote_socket_enqueue; + s->ready = remote_socket_ready; + s->close = remote_socket_close; + s->transport = t; + + dis->func = remote_socket_disconnect; + dis->opaque = s; + add_transport_disconnect( t, dis ); + D("RS(%d): created\n", s->id); + return s; +} + +void connect_to_remote(asocket *s, const char *destination) +{ + D("Connect_to_remote call RS(%d) fd=%d\n", s->id, s->fd); + apacket *p = get_apacket(); + int len = strlen(destination) + 1; + + if(len > (MAX_PAYLOAD-1)) { + fatal("destination oversized"); + } + + D("LS(%d): connect('%s')\n", s->id, destination); + p->msg.command = A_OPEN; + p->msg.arg0 = s->id; + p->msg.data_length = len; + strcpy((char*) p->data, destination); + send_packet(p, s->transport); +} + + +/* this is used by magic sockets to rig local sockets to + send the go-ahead message when they connect */ +static void local_socket_ready_notify(asocket *s) +{ + s->ready = local_socket_ready; + s->close = local_socket_close; + adb_write(s->fd, "OKAY", 4); + s->ready(s); +} + +/* this is used by magic sockets to rig local sockets to + send the failure message if they are closed before + connected (to avoid closing them without a status message) */ +static void local_socket_close_notify(asocket *s) +{ + s->ready = local_socket_ready; + s->close = local_socket_close; + sendfailmsg(s->fd, "closed"); + s->close(s); +} + +unsigned unhex(unsigned char *s, int len) +{ + unsigned n = 0, c; + + while(len-- > 0) { + switch((c = *s++)) { + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': + case '9': + c -= '0'; + break; + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + c = c - 'a' + 10; + break; + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + c = c - 'A' + 10; + break; + default: + return 0xffffffff; + } + + n = (n << 4) | c; + } + + return n; +} + +/* skip_host_serial return the position in a string + skipping over the 'serial' parameter in the ADB protocol, + where parameter string may be a host:port string containing + the protocol delimiter (colon). */ +char *skip_host_serial(char *service) { + char *first_colon, *serial_end; + + first_colon = strchr(service, ':'); + if (!first_colon) { + /* No colon in service string. */ + return NULL; + } + serial_end = first_colon; + if (isdigit(serial_end[1])) { + serial_end++; + while ((*serial_end) && isdigit(*serial_end)) { + serial_end++; + } + if ((*serial_end) != ':') { + // Something other than numbers was found, reset the end. + serial_end = first_colon; + } + } + return serial_end; +} + +static int smart_socket_enqueue(asocket *s, apacket *p) +{ + unsigned len; + + D("SS(%d): enqueue %d\n", s->id, p->len); + + if(s->pkt_first == 0) { + s->pkt_first = p; + s->pkt_last = p; + } else { + if((s->pkt_first->len + p->len) > MAX_PAYLOAD) { + D("SS(%d): overflow\n", s->id); + put_apacket(p); + goto fail; + } + + memcpy(s->pkt_first->data + s->pkt_first->len, + p->data, p->len); + s->pkt_first->len += p->len; + put_apacket(p); + + p = s->pkt_first; + } + + /* don't bother if we can't decode the length */ + if(p->len < 4) return 0; + + len = unhex(p->data, 4); + if((len < 1) || (len > 1024)) { + D("SS(%d): bad size (%d)\n", s->id, len); + goto fail; + } + + D("SS(%d): len is %d\n", s->id, len ); + /* can't do anything until we have the full header */ + if((len + 4) > p->len) { + D("SS(%d): waiting for %d more bytes\n", s->id, len+4 - p->len); + return 0; + } + + p->data[len + 4] = 0; + + D("SS(%d): '%s'\n", s->id, (char*) (p->data + 4)); + + if (s->transport == NULL) { + char* error_string = "unknown failure"; + s->transport = acquire_one_transport (CS_ANY, + kTransportAny, NULL, &error_string); + + if (s->transport == NULL) { + sendfailmsg(s->peer->fd, error_string); + goto fail; + } + } + + if(!(s->transport) || (s->transport->connection_state == CS_OFFLINE)) { + /* if there's no remote we fail the connection + ** right here and terminate it + */ + sendfailmsg(s->peer->fd, "device offline (x)"); + goto fail; + } + + + /* instrument our peer to pass the success or fail + ** message back once it connects or closes, then + ** detach from it, request the connection, and + ** tear down + */ + s->peer->ready = local_socket_ready_notify; + s->peer->close = local_socket_close_notify; + s->peer->peer = 0; + /* give him our transport and upref it */ + s->peer->transport = s->transport; + + connect_to_remote(s->peer, (char*) (p->data + 4)); + s->peer = 0; + s->close(s); + return 1; + +fail: + /* we're going to close our peer as a side-effect, so + ** return -1 to signal that state to the local socket + ** who is enqueueing against us + */ + s->close(s); + return -1; +} + +static void smart_socket_ready(asocket *s) +{ + D("SS(%d): ready\n", s->id); +} + +static void smart_socket_close(asocket *s) +{ + D("SS(%d): closed\n", s->id); + if(s->pkt_first){ + put_apacket(s->pkt_first); + } + if(s->peer) { + s->peer->peer = 0; + s->peer->close(s->peer); + s->peer = 0; + } + free(s); +} + +asocket *create_smart_socket(void (*action_cb)(asocket *s, const char *act)) +{ + D("Creating smart socket \n"); + asocket *s = calloc(1, sizeof(asocket)); + if (s == NULL) fatal("cannot allocate socket"); + s->enqueue = smart_socket_enqueue; + s->ready = smart_socket_ready; + s->close = smart_socket_close; + s->extra = action_cb; + + D("SS(%d): created %p\n", s->id, action_cb); + return s; +} + +void smart_socket_action(asocket *s, const char *act) +{ + +} + +void connect_to_smartsocket(asocket *s) +{ + D("Connecting to smart socket \n"); + asocket *ss = create_smart_socket(smart_socket_action); + s->peer = ss; + ss->peer = s; + s->ready(s); +} diff --git a/minadbd/sysdeps.h b/minadbd/sysdeps.h new file mode 100644 index 000000000..800ddb753 --- /dev/null +++ b/minadbd/sysdeps.h @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* this file contains system-dependent definitions used by ADB + * they're related to threads, sockets and file descriptors + */ +#ifndef _ADB_SYSDEPS_H +#define _ADB_SYSDEPS_H + +#ifdef __CYGWIN__ +# undef _WIN32 +#endif + +#ifdef _WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OS_PATH_SEPARATOR '\\' +#define OS_PATH_SEPARATOR_STR "\\" + +typedef CRITICAL_SECTION adb_mutex_t; + +#define ADB_MUTEX_DEFINE(x) adb_mutex_t x + +/* declare all mutexes */ +/* For win32, adb_sysdeps_init() will do the mutex runtime initialization. */ +#define ADB_MUTEX(x) extern adb_mutex_t x; +#include "mutex_list.h" + +extern void adb_sysdeps_init(void); + +static __inline__ void adb_mutex_lock( adb_mutex_t* lock ) +{ + EnterCriticalSection( lock ); +} + +static __inline__ void adb_mutex_unlock( adb_mutex_t* lock ) +{ + LeaveCriticalSection( lock ); +} + +typedef struct { unsigned tid; } adb_thread_t; + +typedef void* (*adb_thread_func_t)(void* arg); + +typedef void (*win_thread_func_t)(void* arg); + +static __inline__ int adb_thread_create( adb_thread_t *thread, adb_thread_func_t func, void* arg) +{ + thread->tid = _beginthread( (win_thread_func_t)func, 0, arg ); + if (thread->tid == (unsigned)-1L) { + return -1; + } + return 0; +} + +static __inline__ void close_on_exec(int fd) +{ + /* nothing really */ +} + +extern void disable_tcp_nagle(int fd); + +#define lstat stat /* no symlinks on Win32 */ + +#define S_ISLNK(m) 0 /* no symlinks on Win32 */ + +static __inline__ int adb_unlink(const char* path) +{ + int rc = unlink(path); + + if (rc == -1 && errno == EACCES) { + /* unlink returns EACCES when the file is read-only, so we first */ + /* try to make it writable, then unlink again... */ + rc = chmod(path, _S_IREAD|_S_IWRITE ); + if (rc == 0) + rc = unlink(path); + } + return rc; +} +#undef unlink +#define unlink ___xxx_unlink + +static __inline__ int adb_mkdir(const char* path, int mode) +{ + return _mkdir(path); +} +#undef mkdir +#define mkdir ___xxx_mkdir + +extern int adb_open(const char* path, int options); +extern int adb_creat(const char* path, int mode); +extern int adb_read(int fd, void* buf, int len); +extern int adb_write(int fd, const void* buf, int len); +extern int adb_lseek(int fd, int pos, int where); +extern int adb_shutdown(int fd); +extern int adb_close(int fd); + +static __inline__ int unix_close(int fd) +{ + return close(fd); +} +#undef close +#define close ____xxx_close + +static __inline__ int unix_read(int fd, void* buf, size_t len) +{ + return read(fd, buf, len); +} +#undef read +#define read ___xxx_read + +static __inline__ int unix_write(int fd, const void* buf, size_t len) +{ + return write(fd, buf, len); +} +#undef write +#define write ___xxx_write + +static __inline__ int adb_open_mode(const char* path, int options, int mode) +{ + return adb_open(path, options); +} + +static __inline__ int unix_open(const char* path, int options,...) +{ + if ((options & O_CREAT) == 0) + { + return open(path, options); + } + else + { + int mode; + va_list args; + va_start( args, options ); + mode = va_arg( args, int ); + va_end( args ); + return open(path, options, mode); + } +} +#define open ___xxx_unix_open + + +/* normally provided by */ +extern void* load_file(const char* pathname, unsigned* psize); + +/* normally provided by */ +extern int socket_loopback_client(int port, int type); +extern int socket_network_client(const char *host, int port, int type); +extern int socket_loopback_server(int port, int type); +extern int socket_inaddr_any_server(int port, int type); + +/* normally provided by "fdevent.h" */ + +#define FDE_READ 0x0001 +#define FDE_WRITE 0x0002 +#define FDE_ERROR 0x0004 +#define FDE_DONT_CLOSE 0x0080 + +typedef struct fdevent fdevent; + +typedef void (*fd_func)(int fd, unsigned events, void *userdata); + +fdevent *fdevent_create(int fd, fd_func func, void *arg); +void fdevent_destroy(fdevent *fde); +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg); +void fdevent_remove(fdevent *item); +void fdevent_set(fdevent *fde, unsigned events); +void fdevent_add(fdevent *fde, unsigned events); +void fdevent_del(fdevent *fde, unsigned events); +void fdevent_loop(); + +struct fdevent { + fdevent *next; + fdevent *prev; + + int fd; + int force_eof; + + unsigned short state; + unsigned short events; + + fd_func func; + void *arg; +}; + +static __inline__ void adb_sleep_ms( int mseconds ) +{ + Sleep( mseconds ); +} + +extern int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen); + +#undef accept +#define accept ___xxx_accept + +static __inline__ int adb_socket_setbufsize( int fd, int bufsize ) +{ + int opt = bufsize; + return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char*)&opt, sizeof(opt)); +} + +extern int adb_socketpair( int sv[2] ); + +static __inline__ char* adb_dirstart( const char* path ) +{ + char* p = strchr(path, '/'); + char* p2 = strchr(path, '\\'); + + if ( !p ) + p = p2; + else if ( p2 && p2 > p ) + p = p2; + + return p; +} + +static __inline__ char* adb_dirstop( const char* path ) +{ + char* p = strrchr(path, '/'); + char* p2 = strrchr(path, '\\'); + + if ( !p ) + p = p2; + else if ( p2 && p2 > p ) + p = p2; + + return p; +} + +static __inline__ int adb_is_absolute_host_path( const char* path ) +{ + return isalpha(path[0]) && path[1] == ':' && path[2] == '\\'; +} + +#else /* !_WIN32 a.k.a. Unix */ + +#include "fdevent.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define OS_PATH_SEPARATOR '/' +#define OS_PATH_SEPARATOR_STR "/" + +typedef pthread_mutex_t adb_mutex_t; + +#define ADB_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +#define adb_mutex_init pthread_mutex_init +#define adb_mutex_lock pthread_mutex_lock +#define adb_mutex_unlock pthread_mutex_unlock +#define adb_mutex_destroy pthread_mutex_destroy + +#define ADB_MUTEX_DEFINE(m) adb_mutex_t m = PTHREAD_MUTEX_INITIALIZER + +#define adb_cond_t pthread_cond_t +#define adb_cond_init pthread_cond_init +#define adb_cond_wait pthread_cond_wait +#define adb_cond_broadcast pthread_cond_broadcast +#define adb_cond_signal pthread_cond_signal +#define adb_cond_destroy pthread_cond_destroy + +/* declare all mutexes */ +#define ADB_MUTEX(x) extern adb_mutex_t x; +#include "mutex_list.h" + +static __inline__ void close_on_exec(int fd) +{ + fcntl( fd, F_SETFD, FD_CLOEXEC ); +} + +static __inline__ int unix_open(const char* path, int options,...) +{ + if ((options & O_CREAT) == 0) + { + return open(path, options); + } + else + { + int mode; + va_list args; + va_start( args, options ); + mode = va_arg( args, int ); + va_end( args ); + return open(path, options, mode); + } +} + +static __inline__ int adb_open_mode( const char* pathname, int options, int mode ) +{ + return open( pathname, options, mode ); +} + +static __inline__ int adb_creat(const char* path, int mode) +{ + int fd = open(path, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, mode); + + if ( fd < 0 ) + return -1; + + close_on_exec(fd); + return fd; +} +#undef creat +#define creat ___xxx_creat + +static __inline__ int adb_open( const char* pathname, int options ) +{ + int fd = open( pathname, options ); + if (fd < 0) + return -1; + close_on_exec( fd ); + return fd; +} +#undef open +#define open ___xxx_open + +static __inline__ int adb_shutdown(int fd) +{ + return shutdown(fd, SHUT_RDWR); +} +#undef shutdown +#define shutdown ____xxx_shutdown + +static __inline__ int adb_close(int fd) +{ + return close(fd); +} +#undef close +#define close ____xxx_close + + +static __inline__ int adb_read(int fd, void* buf, size_t len) +{ + return read(fd, buf, len); +} + +#undef read +#define read ___xxx_read + +static __inline__ int adb_write(int fd, const void* buf, size_t len) +{ + return write(fd, buf, len); +} +#undef write +#define write ___xxx_write + +static __inline__ int adb_lseek(int fd, int pos, int where) +{ + return lseek(fd, pos, where); +} +#undef lseek +#define lseek ___xxx_lseek + +static __inline__ int adb_unlink(const char* path) +{ + return unlink(path); +} +#undef unlink +#define unlink ___xxx_unlink + +static __inline__ int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen) +{ + int fd; + + fd = accept(serverfd, addr, addrlen); + if (fd >= 0) + close_on_exec(fd); + + return fd; +} + +#undef accept +#define accept ___xxx_accept + +#define unix_read adb_read +#define unix_write adb_write +#define unix_close adb_close + +typedef pthread_t adb_thread_t; + +typedef void* (*adb_thread_func_t)( void* arg ); + +static __inline__ int adb_thread_create( adb_thread_t *pthread, adb_thread_func_t start, void* arg ) +{ + pthread_attr_t attr; + + pthread_attr_init (&attr); + pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + + return pthread_create( pthread, &attr, start, arg ); +} + +static __inline__ int adb_socket_setbufsize( int fd, int bufsize ) +{ + int opt = bufsize; + return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); +} + +static __inline__ void disable_tcp_nagle(int fd) +{ + int on = 1; + setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on) ); +} + + +static __inline__ int unix_socketpair( int d, int type, int protocol, int sv[2] ) +{ + return socketpair( d, type, protocol, sv ); +} + +static __inline__ int adb_socketpair( int sv[2] ) +{ + int rc; + + rc = unix_socketpair( AF_UNIX, SOCK_STREAM, 0, sv ); + if (rc < 0) + return -1; + + close_on_exec( sv[0] ); + close_on_exec( sv[1] ); + return 0; +} + +#undef socketpair +#define socketpair ___xxx_socketpair + +static __inline__ void adb_sleep_ms( int mseconds ) +{ + usleep( mseconds*1000 ); +} + +static __inline__ int adb_mkdir(const char* path, int mode) +{ + return mkdir(path, mode); +} +#undef mkdir +#define mkdir ___xxx_mkdir + +static __inline__ void adb_sysdeps_init(void) +{ +} + +static __inline__ char* adb_dirstart(const char* path) +{ + return strchr(path, '/'); +} + +static __inline__ char* adb_dirstop(const char* path) +{ + return strrchr(path, '/'); +} + +static __inline__ int adb_is_absolute_host_path( const char* path ) +{ + return path[0] == '/'; +} + +#endif /* !_WIN32 */ + +#endif /* _ADB_SYSDEPS_H */ diff --git a/minadbd/transport.c b/minadbd/transport.c new file mode 100644 index 000000000..4c0c97f75 --- /dev/null +++ b/minadbd/transport.c @@ -0,0 +1,803 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_TRANSPORT +#include "adb.h" + +static void transport_unref(atransport *t); + +static atransport transport_list = { + .next = &transport_list, + .prev = &transport_list, +}; + +ADB_MUTEX_DEFINE( transport_lock ); + +#if ADB_TRACE +#define MAX_DUMP_HEX_LEN 16 +static void dump_hex( const unsigned char* ptr, size_t len ) +{ + int nn, len2 = len; + // Build a string instead of logging each character. + // MAX chars in 2 digit hex, one space, MAX chars, one '\0'. + char buffer[MAX_DUMP_HEX_LEN *2 + 1 + MAX_DUMP_HEX_LEN + 1 ], *pb = buffer; + + if (len2 > MAX_DUMP_HEX_LEN) len2 = MAX_DUMP_HEX_LEN; + + for (nn = 0; nn < len2; nn++) { + sprintf(pb, "%02x", ptr[nn]); + pb += 2; + } + sprintf(pb++, " "); + + for (nn = 0; nn < len2; nn++) { + int c = ptr[nn]; + if (c < 32 || c > 127) + c = '.'; + *pb++ = c; + } + *pb++ = '\0'; + DR("%s\n", buffer); +} +#endif + +void +kick_transport(atransport* t) +{ + if (t && !t->kicked) + { + int kicked; + + adb_mutex_lock(&transport_lock); + kicked = t->kicked; + if (!kicked) + t->kicked = 1; + adb_mutex_unlock(&transport_lock); + + if (!kicked) + t->kick(t); + } +} + +void +run_transport_disconnects(atransport* t) +{ + adisconnect* dis = t->disconnects.next; + + D("%s: run_transport_disconnects\n", t->serial); + while (dis != &t->disconnects) { + adisconnect* next = dis->next; + dis->func( dis->opaque, t ); + dis = next; + } +} + +#if ADB_TRACE +static void +dump_packet(const char* name, const char* func, apacket* p) +{ + unsigned command = p->msg.command; + int len = p->msg.data_length; + char cmd[9]; + char arg0[12], arg1[12]; + int n; + + for (n = 0; n < 4; n++) { + int b = (command >> (n*8)) & 255; + if (b < 32 || b >= 127) + break; + cmd[n] = (char)b; + } + if (n == 4) { + cmd[4] = 0; + } else { + /* There is some non-ASCII name in the command, so dump + * the hexadecimal value instead */ + snprintf(cmd, sizeof cmd, "%08x", command); + } + + if (p->msg.arg0 < 256U) + snprintf(arg0, sizeof arg0, "%d", p->msg.arg0); + else + snprintf(arg0, sizeof arg0, "0x%x", p->msg.arg0); + + if (p->msg.arg1 < 256U) + snprintf(arg1, sizeof arg1, "%d", p->msg.arg1); + else + snprintf(arg1, sizeof arg1, "0x%x", p->msg.arg1); + + D("%s: %s: [%s] arg0=%s arg1=%s (len=%d) ", + name, func, cmd, arg0, arg1, len); + dump_hex(p->data, len); +} +#endif /* ADB_TRACE */ + +static int +read_packet(int fd, const char* name, apacket** ppacket) +{ + char *p = (char*)ppacket; /* really read a packet address */ + int r; + int len = sizeof(*ppacket); + char buff[8]; + if (!name) { + snprintf(buff, sizeof buff, "fd=%d", fd); + name = buff; + } + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("%s: read_packet (fd=%d), error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno)); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + +#if ADB_TRACE + if (ADB_TRACING) { + dump_packet(name, "from remote", *ppacket); + } +#endif + return 0; +} + +static int +write_packet(int fd, const char* name, apacket** ppacket) +{ + char *p = (char*) ppacket; /* we really write the packet address */ + int r, len = sizeof(ppacket); + char buff[8]; + if (!name) { + snprintf(buff, sizeof buff, "fd=%d", fd); + name = buff; + } + +#if ADB_TRACE + if (ADB_TRACING) { + dump_packet(name, "to remote", *ppacket); + } +#endif + len = sizeof(ppacket); + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("%s: write_packet (fd=%d) error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno)); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + return 0; +} + +static void transport_socket_events(int fd, unsigned events, void *_t) +{ + atransport *t = _t; + D("transport_socket_events(fd=%d, events=%04x,...)\n", fd, events); + if(events & FDE_READ){ + apacket *p = 0; + if(read_packet(fd, t->serial, &p)){ + D("%s: failed to read packet from transport socket on fd %d\n", t->serial, fd); + } else { + handle_packet(p, (atransport *) _t); + } + } +} + +void send_packet(apacket *p, atransport *t) +{ + unsigned char *x; + unsigned sum; + unsigned count; + + p->msg.magic = p->msg.command ^ 0xffffffff; + + count = p->msg.data_length; + x = (unsigned char *) p->data; + sum = 0; + while(count-- > 0){ + sum += *x++; + } + p->msg.data_check = sum; + + print_packet("send", p); + + if (t == NULL) { + D("Transport is null \n"); + // Zap errno because print_packet() and other stuff have errno effect. + errno = 0; + fatal_errno("Transport is null"); + } + + if(write_packet(t->transport_socket, t->serial, &p)){ + fatal_errno("cannot enqueue packet on transport socket"); + } +} + +/* The transport is opened by transport_register_func before +** the input and output threads are started. +** +** The output thread issues a SYNC(1, token) message to let +** the input thread know to start things up. In the event +** of transport IO failure, the output thread will post a +** SYNC(0,0) message to ensure shutdown. +** +** The transport will not actually be closed until both +** threads exit, but the input thread will kick the transport +** on its way out to disconnect the underlying device. +*/ + +static void *output_thread(void *_t) +{ + atransport *t = _t; + apacket *p; + + D("%s: starting transport output thread on fd %d, SYNC online (%d)\n", + t->serial, t->fd, t->sync_token + 1); + p = get_apacket(); + p->msg.command = A_SYNC; + p->msg.arg0 = 1; + p->msg.arg1 = ++(t->sync_token); + p->msg.magic = A_SYNC ^ 0xffffffff; + if(write_packet(t->fd, t->serial, &p)) { + put_apacket(p); + D("%s: failed to write SYNC packet\n", t->serial); + goto oops; + } + + D("%s: data pump started\n", t->serial); + for(;;) { + p = get_apacket(); + + if(t->read_from_remote(p, t) == 0){ + D("%s: received remote packet, sending to transport\n", + t->serial); + if(write_packet(t->fd, t->serial, &p)){ + put_apacket(p); + D("%s: failed to write apacket to transport\n", t->serial); + goto oops; + } + } else { + D("%s: remote read failed for transport\n", t->serial); + put_apacket(p); + break; + } + } + + D("%s: SYNC offline for transport\n", t->serial); + p = get_apacket(); + p->msg.command = A_SYNC; + p->msg.arg0 = 0; + p->msg.arg1 = 0; + p->msg.magic = A_SYNC ^ 0xffffffff; + if(write_packet(t->fd, t->serial, &p)) { + put_apacket(p); + D("%s: failed to write SYNC apacket to transport", t->serial); + } + +oops: + D("%s: transport output thread is exiting\n", t->serial); + kick_transport(t); + transport_unref(t); + return 0; +} + +static void *input_thread(void *_t) +{ + atransport *t = _t; + apacket *p; + int active = 0; + + D("%s: starting transport input thread, reading from fd %d\n", + t->serial, t->fd); + + for(;;){ + if(read_packet(t->fd, t->serial, &p)) { + D("%s: failed to read apacket from transport on fd %d\n", + t->serial, t->fd ); + break; + } + if(p->msg.command == A_SYNC){ + if(p->msg.arg0 == 0) { + D("%s: transport SYNC offline\n", t->serial); + put_apacket(p); + break; + } else { + if(p->msg.arg1 == t->sync_token) { + D("%s: transport SYNC online\n", t->serial); + active = 1; + } else { + D("%s: transport ignoring SYNC %d != %d\n", + t->serial, p->msg.arg1, t->sync_token); + } + } + } else { + if(active) { + D("%s: transport got packet, sending to remote\n", t->serial); + t->write_to_remote(p, t); + } else { + D("%s: transport ignoring packet while offline\n", t->serial); + } + } + + put_apacket(p); + } + + // this is necessary to avoid a race condition that occured when a transport closes + // while a client socket is still active. + close_all_sockets(t); + + D("%s: transport input thread is exiting, fd %d\n", t->serial, t->fd); + kick_transport(t); + transport_unref(t); + return 0; +} + + +static int transport_registration_send = -1; +static int transport_registration_recv = -1; +static fdevent transport_registration_fde; + +void update_transports(void) +{ + // nothing to do on the device side +} + +typedef struct tmsg tmsg; +struct tmsg +{ + atransport *transport; + int action; +}; + +static int +transport_read_action(int fd, struct tmsg* m) +{ + char *p = (char*)m; + int len = sizeof(*m); + int r; + + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if((r < 0) && (errno == EINTR)) continue; + D("transport_read_action: on fd %d, error %d: %s\n", + fd, errno, strerror(errno)); + return -1; + } + } + return 0; +} + +static int +transport_write_action(int fd, struct tmsg* m) +{ + char *p = (char*)m; + int len = sizeof(*m); + int r; + + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if((r < 0) && (errno == EINTR)) continue; + D("transport_write_action: on fd %d, error %d: %s\n", + fd, errno, strerror(errno)); + return -1; + } + } + return 0; +} + +static void transport_registration_func(int _fd, unsigned ev, void *data) +{ + tmsg m; + adb_thread_t output_thread_ptr; + adb_thread_t input_thread_ptr; + int s[2]; + atransport *t; + + if(!(ev & FDE_READ)) { + return; + } + + if(transport_read_action(_fd, &m)) { + fatal_errno("cannot read transport registration socket"); + } + + t = m.transport; + + if(m.action == 0){ + D("transport: %s removing and free'ing %d\n", t->serial, t->transport_socket); + + /* IMPORTANT: the remove closes one half of the + ** socket pair. The close closes the other half. + */ + fdevent_remove(&(t->transport_fde)); + adb_close(t->fd); + + adb_mutex_lock(&transport_lock); + t->next->prev = t->prev; + t->prev->next = t->next; + adb_mutex_unlock(&transport_lock); + + run_transport_disconnects(t); + + if (t->product) + free(t->product); + if (t->serial) + free(t->serial); + + memset(t,0xee,sizeof(atransport)); + free(t); + + update_transports(); + return; + } + + /* don't create transport threads for inaccessible devices */ + if (t->connection_state != CS_NOPERM) { + /* initial references are the two threads */ + t->ref_count = 2; + + if(adb_socketpair(s)) { + fatal_errno("cannot open transport socketpair"); + } + + D("transport: %s (%d,%d) starting\n", t->serial, s[0], s[1]); + + t->transport_socket = s[0]; + t->fd = s[1]; + + fdevent_install(&(t->transport_fde), + t->transport_socket, + transport_socket_events, + t); + + fdevent_set(&(t->transport_fde), FDE_READ); + + if(adb_thread_create(&input_thread_ptr, input_thread, t)){ + fatal_errno("cannot create input thread"); + } + + if(adb_thread_create(&output_thread_ptr, output_thread, t)){ + fatal_errno("cannot create output thread"); + } + } + + /* put us on the master device list */ + adb_mutex_lock(&transport_lock); + t->next = &transport_list; + t->prev = transport_list.prev; + t->next->prev = t; + t->prev->next = t; + adb_mutex_unlock(&transport_lock); + + t->disconnects.next = t->disconnects.prev = &t->disconnects; + + update_transports(); +} + +void init_transport_registration(void) +{ + int s[2]; + + if(adb_socketpair(s)){ + fatal_errno("cannot open transport registration socketpair"); + } + + transport_registration_send = s[0]; + transport_registration_recv = s[1]; + + fdevent_install(&transport_registration_fde, + transport_registration_recv, + transport_registration_func, + 0); + + fdevent_set(&transport_registration_fde, FDE_READ); +} + +/* the fdevent select pump is single threaded */ +static void register_transport(atransport *transport) +{ + tmsg m; + m.transport = transport; + m.action = 1; + D("transport: %s registered\n", transport->serial); + if(transport_write_action(transport_registration_send, &m)) { + fatal_errno("cannot write transport registration socket\n"); + } +} + +static void remove_transport(atransport *transport) +{ + tmsg m; + m.transport = transport; + m.action = 0; + D("transport: %s removed\n", transport->serial); + if(transport_write_action(transport_registration_send, &m)) { + fatal_errno("cannot write transport registration socket\n"); + } +} + + +static void transport_unref_locked(atransport *t) +{ + t->ref_count--; + if (t->ref_count == 0) { + D("transport: %s unref (kicking and closing)\n", t->serial); + if (!t->kicked) { + t->kicked = 1; + t->kick(t); + } + t->close(t); + remove_transport(t); + } else { + D("transport: %s unref (count=%d)\n", t->serial, t->ref_count); + } +} + +static void transport_unref(atransport *t) +{ + if (t) { + adb_mutex_lock(&transport_lock); + transport_unref_locked(t); + adb_mutex_unlock(&transport_lock); + } +} + +void add_transport_disconnect(atransport* t, adisconnect* dis) +{ + adb_mutex_lock(&transport_lock); + dis->next = &t->disconnects; + dis->prev = dis->next->prev; + dis->prev->next = dis; + dis->next->prev = dis; + adb_mutex_unlock(&transport_lock); +} + +void remove_transport_disconnect(atransport* t, adisconnect* dis) +{ + dis->prev->next = dis->next; + dis->next->prev = dis->prev; + dis->next = dis->prev = dis; +} + + +atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char** error_out) +{ + atransport *t; + atransport *result = NULL; + int ambiguous = 0; + +retry: + if (error_out) + *error_out = "device not found"; + + adb_mutex_lock(&transport_lock); + for (t = transport_list.next; t != &transport_list; t = t->next) { + if (t->connection_state == CS_NOPERM) { + if (error_out) + *error_out = "insufficient permissions for device"; + continue; + } + + /* check for matching serial number */ + if (serial) { + if (t->serial && !strcmp(serial, t->serial)) { + result = t; + break; + } + } else { + if (ttype == kTransportUsb && t->type == kTransportUsb) { + if (result) { + if (error_out) + *error_out = "more than one device"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } else if (ttype == kTransportLocal && t->type == kTransportLocal) { + if (result) { + if (error_out) + *error_out = "more than one emulator"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } else if (ttype == kTransportAny) { + if (result) { + if (error_out) + *error_out = "more than one device and emulator"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } + } + } + adb_mutex_unlock(&transport_lock); + + if (result) { + /* offline devices are ignored -- they are either being born or dying */ + if (result && result->connection_state == CS_OFFLINE) { + if (error_out) + *error_out = "device offline"; + result = NULL; + } + /* check for required connection state */ + if (result && state != CS_ANY && result->connection_state != state) { + if (error_out) + *error_out = "invalid device state"; + result = NULL; + } + } + + if (result) { + /* found one that we can take */ + if (error_out) + *error_out = NULL; + } else if (state != CS_ANY && (serial || !ambiguous)) { + adb_sleep_ms(1000); + goto retry; + } + + return result; +} + +void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable) +{ + atransport *t = calloc(1, sizeof(atransport)); + D("transport: %p init'ing for usb_handle %p (sn='%s')\n", t, usb, + serial ? serial : ""); + init_usb_transport(t, usb, (writeable ? CS_OFFLINE : CS_NOPERM)); + if(serial) { + t->serial = strdup(serial); + } + register_transport(t); +} + +/* this should only be used for transports with connection_state == CS_NOPERM */ +void unregister_usb_transport(usb_handle *usb) +{ + atransport *t; + adb_mutex_lock(&transport_lock); + for(t = transport_list.next; t != &transport_list; t = t->next) { + if (t->usb == usb && t->connection_state == CS_NOPERM) { + t->next->prev = t->prev; + t->prev->next = t->next; + break; + } + } + adb_mutex_unlock(&transport_lock); +} + +#undef TRACE_TAG +#define TRACE_TAG TRACE_RWX + +int readx(int fd, void *ptr, size_t len) +{ + char *p = ptr; + int r; +#if ADB_TRACE + int len0 = len; +#endif + D("readx: fd=%d wanted=%d\n", fd, (int)len); + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if (r < 0) { + D("readx: fd=%d error %d: %s\n", fd, errno, strerror(errno)); + if (errno == EINTR) + continue; + } else { + D("readx: fd=%d disconnected\n", fd); + } + return -1; + } + } + +#if ADB_TRACE + D("readx: fd=%d wanted=%d got=%d\n", fd, len0, len0 - len); + dump_hex( ptr, len0 ); +#endif + return 0; +} + +int writex(int fd, const void *ptr, size_t len) +{ + char *p = (char*) ptr; + int r; + +#if ADB_TRACE + D("writex: fd=%d len=%d: ", fd, (int)len); + dump_hex( ptr, len ); +#endif + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if (r < 0) { + D("writex: fd=%d error %d: %s\n", fd, errno, strerror(errno)); + if (errno == EINTR) + continue; + } else { + D("writex: fd=%d disconnected\n", fd); + } + return -1; + } + } + return 0; +} + +int check_header(apacket *p) +{ + if(p->msg.magic != (p->msg.command ^ 0xffffffff)) { + D("check_header(): invalid magic\n"); + return -1; + } + + if(p->msg.data_length > MAX_PAYLOAD) { + D("check_header(): %d > MAX_PAYLOAD\n", p->msg.data_length); + return -1; + } + + return 0; +} + +int check_data(apacket *p) +{ + unsigned count, sum; + unsigned char *x; + + count = p->msg.data_length; + x = p->data; + sum = 0; + while(count-- > 0) { + sum += *x++; + } + + if(sum != p->msg.data_check) { + return -1; + } else { + return 0; + } +} diff --git a/minadbd/transport.h b/minadbd/transport.h new file mode 100644 index 000000000..992e05285 --- /dev/null +++ b/minadbd/transport.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TRANSPORT_H +#define __TRANSPORT_H + +/* convenience wrappers around read/write that will retry on +** EINTR and/or short read/write. Returns 0 on success, -1 +** on error or EOF. +*/ +int readx(int fd, void *ptr, size_t len); +int writex(int fd, const void *ptr, size_t len); +#endif /* __TRANSPORT_H */ diff --git a/minadbd/transport_usb.c b/minadbd/transport_usb.c new file mode 100644 index 000000000..91cbf6151 --- /dev/null +++ b/minadbd/transport_usb.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#define TRACE_TAG TRACE_TRANSPORT +#include "adb.h" + +#ifdef HAVE_BIG_ENDIAN +#define H4(x) (((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24) +static inline void fix_endians(apacket *p) +{ + p->msg.command = H4(p->msg.command); + p->msg.arg0 = H4(p->msg.arg0); + p->msg.arg1 = H4(p->msg.arg1); + p->msg.data_length = H4(p->msg.data_length); + p->msg.data_check = H4(p->msg.data_check); + p->msg.magic = H4(p->msg.magic); +} +unsigned host_to_le32(unsigned n) +{ + return H4(n); +} +#else +#define fix_endians(p) do {} while (0) +unsigned host_to_le32(unsigned n) +{ + return n; +} +#endif + +static int remote_read(apacket *p, atransport *t) +{ + if(usb_read(t->usb, &p->msg, sizeof(amessage))){ + D("remote usb: read terminated (message)\n"); + return -1; + } + + fix_endians(p); + + if(check_header(p)) { + D("remote usb: check_header failed\n"); + return -1; + } + + if(p->msg.data_length) { + if(usb_read(t->usb, p->data, p->msg.data_length)){ + D("remote usb: terminated (data)\n"); + return -1; + } + } + + if(check_data(p)) { + D("remote usb: check_data failed\n"); + return -1; + } + + return 0; +} + +static int remote_write(apacket *p, atransport *t) +{ + unsigned size = p->msg.data_length; + + fix_endians(p); + + if(usb_write(t->usb, &p->msg, sizeof(amessage))) { + D("remote usb: 1 - write terminated\n"); + return -1; + } + if(p->msg.data_length == 0) return 0; + if(usb_write(t->usb, &p->data, size)) { + D("remote usb: 2 - write terminated\n"); + return -1; + } + + return 0; +} + +static void remote_close(atransport *t) +{ + usb_close(t->usb); + t->usb = 0; +} + +static void remote_kick(atransport *t) +{ + usb_kick(t->usb); +} + +void init_usb_transport(atransport *t, usb_handle *h, int state) +{ + D("transport: usb\n"); + t->close = remote_close; + t->kick = remote_kick; + t->read_from_remote = remote_read; + t->write_to_remote = remote_write; + t->sync_token = 1; + t->connection_state = state; + t->type = kTransportUsb; + t->usb = h; + + HOST = 0; +} diff --git a/minadbd/usb_linux_client.c b/minadbd/usb_linux_client.c new file mode 100644 index 000000000..c135d6396 --- /dev/null +++ b/minadbd/usb_linux_client.c @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + +#define MAX_PACKET_SIZE_FS 64 +#define MAX_PACKET_SIZE_HS 512 + +#define cpu_to_le16(x) htole16(x) +#define cpu_to_le32(x) htole32(x) + +struct usb_handle +{ + int fd; + adb_cond_t notify; + adb_mutex_t lock; + + int (*write)(usb_handle *h, const void *data, int len); + int (*read)(usb_handle *h, void *data, int len); + void (*kick)(usb_handle *h); + + int control; + int bulk_out; /* "out" from the host's perspective => source for adbd */ + int bulk_in; /* "in" from the host's perspective => sink for adbd */ +}; + +static const struct { + struct usb_functionfs_descs_head header; + struct { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio source; + struct usb_endpoint_descriptor_no_audio sink; + } __attribute__((packed)) fs_descs, hs_descs; +} __attribute__((packed)) descriptors = { + .header = { + .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC), + .length = cpu_to_le32(sizeof(descriptors)), + .fs_count = 3, + .hs_count = 3, + }, + .fs_descs = { + .intf = { + .bLength = sizeof(descriptors.fs_descs.intf), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = ADB_CLASS, + .bInterfaceSubClass = ADB_SUBCLASS, + .bInterfaceProtocol = ADB_PROTOCOL, + .iInterface = 1, /* first string from the provided table */ + }, + .source = { + .bLength = sizeof(descriptors.fs_descs.source), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_FS, + }, + .sink = { + .bLength = sizeof(descriptors.fs_descs.sink), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_FS, + }, + }, + .hs_descs = { + .intf = { + .bLength = sizeof(descriptors.hs_descs.intf), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = ADB_CLASS, + .bInterfaceSubClass = ADB_SUBCLASS, + .bInterfaceProtocol = ADB_PROTOCOL, + .iInterface = 1, /* first string from the provided table */ + }, + .source = { + .bLength = sizeof(descriptors.hs_descs.source), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_HS, + }, + .sink = { + .bLength = sizeof(descriptors.hs_descs.sink), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_HS, + }, + }, +}; + +#define STR_INTERFACE_ "ADB Interface" + +static const struct { + struct usb_functionfs_strings_head header; + struct { + __le16 code; + const char str1[sizeof(STR_INTERFACE_)]; + } __attribute__((packed)) lang0; +} __attribute__((packed)) strings = { + .header = { + .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC), + .length = cpu_to_le32(sizeof(strings)), + .str_count = cpu_to_le32(1), + .lang_count = cpu_to_le32(1), + }, + .lang0 = { + cpu_to_le16(0x0409), /* en-us */ + STR_INTERFACE_, + }, +}; + +void usb_cleanup() +{ + // nothing to do here +} + +static void *usb_adb_open_thread(void *x) +{ + struct usb_handle *usb = (struct usb_handle *)x; + int fd; + + while (1) { + // wait until the USB device needs opening + adb_mutex_lock(&usb->lock); + while (usb->fd != -1) + adb_cond_wait(&usb->notify, &usb->lock); + adb_mutex_unlock(&usb->lock); + + D("[ usb_thread - opening device ]\n"); + do { + /* XXX use inotify? */ + fd = unix_open("/dev/android_adb", O_RDWR); + if (fd < 0) { + // to support older kernels + fd = unix_open("/dev/android", O_RDWR); + fprintf(stderr, "usb_adb_open_thread: %d\n", fd ); + } + if (fd < 0) { + adb_sleep_ms(1000); + } + } while (fd < 0); + D("[ opening device succeeded ]\n"); + + close_on_exec(fd); + usb->fd = fd; + + D("[ usb_thread - registering device ]\n"); + register_usb_transport(usb, 0, 1); + } + + // never gets here + return 0; +} + +static int usb_adb_write(usb_handle *h, const void *data, int len) +{ + int n; + + D("about to write (fd=%d, len=%d)\n", h->fd, len); + n = adb_write(h->fd, data, len); + if(n != len) { + D("ERROR: fd = %d, n = %d, errno = %d (%s)\n", + h->fd, n, errno, strerror(errno)); + return -1; + } + D("[ done fd=%d ]\n", h->fd); + return 0; +} + +static int usb_adb_read(usb_handle *h, void *data, int len) +{ + int n; + + D("about to read (fd=%d, len=%d)\n", h->fd, len); + n = adb_read(h->fd, data, len); + if(n != len) { + D("ERROR: fd = %d, n = %d, errno = %d (%s)\n", + h->fd, n, errno, strerror(errno)); + return -1; + } + D("[ done fd=%d ]\n", h->fd); + return 0; +} + +static void usb_adb_kick(usb_handle *h) +{ + D("usb_kick\n"); + adb_mutex_lock(&h->lock); + adb_close(h->fd); + h->fd = -1; + + // notify usb_adb_open_thread that we are disconnected + adb_cond_signal(&h->notify); + adb_mutex_unlock(&h->lock); +} + +static void usb_adb_init() +{ + usb_handle *h; + adb_thread_t tid; + int fd; + + h = calloc(1, sizeof(usb_handle)); + + h->write = usb_adb_write; + h->read = usb_adb_read; + h->kick = usb_adb_kick; + h->fd = -1; + + adb_cond_init(&h->notify, 0); + adb_mutex_init(&h->lock, 0); + + fprintf(stderr, "Starting to open usb_init()\n"); + // Open the file /dev/android_adb_enable to trigger + // the enabling of the adb USB function in the kernel. + // We never touch this file again - just leave it open + // indefinitely so the kernel will know when we are running + // and when we are not. + fd = unix_open("/dev/android_adb_enable", O_RDWR); + fprintf(stderr, "unix_open to open usb_init(): %d\n", fd); + if (fd < 0) { + D("failed to open /dev/android_adb_enable\n"); + } else { + close_on_exec(fd); + } + + D("[ usb_init - starting thread ]\n"); + if(adb_thread_create(&tid, usb_adb_open_thread, h)){ + fatal_errno("cannot create usb thread"); + fprintf(stderr, "cannot create the usb thread()\n"); + } +} + + +static void init_functionfs(struct usb_handle *h) +{ + ssize_t ret; + + D("OPENING %s\n", USB_FFS_ADB_EP0); + h->control = adb_open(USB_FFS_ADB_EP0, O_RDWR); + if (h->control < 0) { + D("[ %s: cannot open control endpoint: errno=%d]\n", USB_FFS_ADB_EP0, errno); + goto err; + } + + ret = adb_write(h->control, &descriptors, sizeof(descriptors)); + if (ret < 0) { + D("[ %s: write descriptors failed: errno=%d ]\n", USB_FFS_ADB_EP0, errno); + goto err; + } + + ret = adb_write(h->control, &strings, sizeof(strings)); + if (ret < 0) { + D("[ %s: writing strings failed: errno=%d]\n", USB_FFS_ADB_EP0, errno); + goto err; + } + + h->bulk_out = adb_open(USB_FFS_ADB_OUT, O_RDWR); + if (h->bulk_out < 0) { + D("[ %s: cannot open bulk-out ep: errno=%d ]\n", USB_FFS_ADB_OUT, errno); + goto err; + } + + h->bulk_in = adb_open(USB_FFS_ADB_IN, O_RDWR); + if (h->bulk_in < 0) { + D("[ %s: cannot open bulk-in ep: errno=%d ]\n", USB_FFS_ADB_IN, errno); + goto err; + } + + return; + +err: + if (h->bulk_in > 0) { + adb_close(h->bulk_in); + h->bulk_in = -1; + } + if (h->bulk_out > 0) { + adb_close(h->bulk_out); + h->bulk_out = -1; + } + if (h->control > 0) { + adb_close(h->control); + h->control = -1; + } + return; +} + +static void *usb_ffs_open_thread(void *x) +{ + struct usb_handle *usb = (struct usb_handle *)x; + + while (1) { + // wait until the USB device needs opening + adb_mutex_lock(&usb->lock); + while (usb->control != -1) + adb_cond_wait(&usb->notify, &usb->lock); + adb_mutex_unlock(&usb->lock); + + while (1) { + init_functionfs(usb); + + if (usb->control >= 0) + break; + + adb_sleep_ms(1000); + } + + D("[ usb_thread - registering device ]\n"); + register_usb_transport(usb, 0, 1); + } + + // never gets here + return 0; +} + +static int bulk_write(int bulk_in, const char *buf, size_t length) +{ + size_t count = 0; + int ret; + + do { + ret = adb_write(bulk_in, buf + count, length - count); + if (ret < 0) { + if (errno != EINTR) + return ret; + } else { + count += ret; + } + } while (count < length); + + D("[ bulk_write done fd=%d ]\n", bulk_in); + return count; +} + +static int usb_ffs_write(usb_handle *h, const void *data, int len) +{ + int n; + + D("about to write (fd=%d, len=%d)\n", h->bulk_in, len); + n = bulk_write(h->bulk_in, data, len); + if (n != len) { + D("ERROR: fd = %d, n = %d, errno = %d (%s)\n", + h->bulk_in, n, errno, strerror(errno)); + return -1; + } + D("[ done fd=%d ]\n", h->bulk_in); + return 0; +} + +static int bulk_read(int bulk_out, char *buf, size_t length) +{ + size_t count = 0; + int ret; + + do { + ret = adb_read(bulk_out, buf + count, length - count); + if (ret < 0) { + if (errno != EINTR) { + D("[ bulk_read failed fd=%d length=%d count=%d ]\n", + bulk_out, length, count); + return ret; + } + } else { + count += ret; + } + } while (count < length); + + return count; +} + +static int usb_ffs_read(usb_handle *h, void *data, int len) +{ + int n; + + D("about to read (fd=%d, len=%d)\n", h->bulk_out, len); + n = bulk_read(h->bulk_out, data, len); + if (n != len) { + D("ERROR: fd = %d, n = %d, errno = %d (%s)\n", + h->bulk_out, n, errno, strerror(errno)); + return -1; + } + D("[ done fd=%d ]\n", h->bulk_out); + return 0; +} + +static void usb_ffs_kick(usb_handle *h) +{ + int err; + + err = ioctl(h->bulk_in, FUNCTIONFS_CLEAR_HALT); + if (err < 0) + D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in, errno); + + err = ioctl(h->bulk_out, FUNCTIONFS_CLEAR_HALT); + if (err < 0) + D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out, errno); + + adb_mutex_lock(&h->lock); + adb_close(h->control); + adb_close(h->bulk_out); + adb_close(h->bulk_in); + h->control = h->bulk_out = h->bulk_in = -1; + + // notify usb_ffs_open_thread that we are disconnected + adb_cond_signal(&h->notify); + adb_mutex_unlock(&h->lock); +} + +static void usb_ffs_init() +{ + usb_handle *h; + adb_thread_t tid; + + D("[ usb_init - using FunctionFS ]\n"); + + h = calloc(1, sizeof(usb_handle)); + + h->write = usb_ffs_write; + h->read = usb_ffs_read; + h->kick = usb_ffs_kick; + + h->control = -1; + h->bulk_out = -1; + h->bulk_out = -1; + + adb_cond_init(&h->notify, 0); + adb_mutex_init(&h->lock, 0); + + D("[ usb_init - starting thread ]\n"); + if (adb_thread_create(&tid, usb_ffs_open_thread, h)){ + fatal_errno("[ cannot create usb thread ]\n"); + } +} + +void usb_init() +{ + if (access(USB_FFS_ADB_EP0, F_OK) == 0) + usb_ffs_init(); + else + usb_adb_init(); +} + +int usb_write(usb_handle *h, const void *data, int len) +{ + return h->write(h, data, len); +} + +int usb_read(usb_handle *h, void *data, int len) +{ + return h->read(h, data, len); +} +int usb_close(usb_handle *h) +{ + // nothing to do here + return 0; +} + +void usb_kick(usb_handle *h) +{ + h->kick(h); +} diff --git a/minadbd/utils.c b/minadbd/utils.c new file mode 100644 index 000000000..91518bab6 --- /dev/null +++ b/minadbd/utils.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "utils.h" +#include +#include +#include + +char* +buff_addc (char* buff, char* buffEnd, int c) +{ + int avail = buffEnd - buff; + + if (avail <= 0) /* already in overflow mode */ + return buff; + + if (avail == 1) { /* overflowing, the last byte is reserved for zero */ + buff[0] = 0; + return buff + 1; + } + + buff[0] = (char) c; /* add char and terminating zero */ + buff[1] = 0; + return buff + 1; +} + +char* +buff_adds (char* buff, char* buffEnd, const char* s) +{ + int slen = strlen(s); + + return buff_addb(buff, buffEnd, s, slen); +} + +char* +buff_addb (char* buff, char* buffEnd, const void* data, int len) +{ + int avail = (buffEnd - buff); + + if (avail <= 0 || len <= 0) /* already overflowing */ + return buff; + + if (len > avail) + len = avail; + + memcpy(buff, data, len); + + buff += len; + + /* ensure there is a terminating zero */ + if (buff >= buffEnd) { /* overflow */ + buff[-1] = 0; + } else + buff[0] = 0; + + return buff; +} + +char* +buff_add (char* buff, char* buffEnd, const char* format, ... ) +{ + int avail; + + avail = (buffEnd - buff); + + if (avail > 0) { + va_list args; + int nn; + + va_start(args, format); + nn = vsnprintf( buff, avail, format, args); + va_end(args); + + if (nn < 0) { + /* some C libraries return -1 in case of overflow, + * but they will also do that if the format spec is + * invalid. We assume ADB is not buggy enough to + * trigger that last case. */ + nn = avail; + } + else if (nn > avail) { + nn = avail; + } + + buff += nn; + + /* ensure that there is a terminating zero */ + if (buff >= buffEnd) + buff[-1] = 0; + else + buff[0] = 0; + } + return buff; +} diff --git a/minadbd/utils.h b/minadbd/utils.h new file mode 100644 index 000000000..f70ecd24d --- /dev/null +++ b/minadbd/utils.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _ADB_UTILS_H +#define _ADB_UTILS_H + +/* bounded buffer functions */ + +/* all these functions are used to append data to a bounded buffer. + * + * after each operation, the buffer is guaranteed to be zero-terminated, + * even in the case of an overflow. they all return the new buffer position + * which allows one to use them in succession, only checking for overflows + * at the end. For example: + * + * BUFF_DECL(temp,p,end,1024); + * char* p; + * + * p = buff_addc(temp, end, '"'); + * p = buff_adds(temp, end, string); + * p = buff_addc(temp, end, '"'); + * + * if (p >= end) { + * overflow detected. note that 'temp' is + * zero-terminated for safety. + * } + * return strdup(temp); + */ + +/* tries to add a character to the buffer, in case of overflow + * this will only write a terminating zero and return buffEnd. + */ +char* buff_addc (char* buff, char* buffEnd, int c); + +/* tries to add a string to the buffer */ +char* buff_adds (char* buff, char* buffEnd, const char* s); + +/* tries to add a bytes to the buffer. the input can contain zero bytes, + * but a terminating zero will always be appended at the end anyway + */ +char* buff_addb (char* buff, char* buffEnd, const void* data, int len); + +/* tries to add a formatted string to a bounded buffer */ +char* buff_add (char* buff, char* buffEnd, const char* format, ... ); + +/* convenience macro used to define a bounded buffer, as well as + * a 'cursor' and 'end' variables all in one go. + * + * note: this doesn't place an initial terminating zero in the buffer, + * you need to use one of the buff_ functions for this. or simply + * do _cursor[0] = 0 manually. + */ +#define BUFF_DECL(_buff,_cursor,_end,_size) \ + char _buff[_size], *_cursor=_buff, *_end = _cursor + (_size) + +#endif /* _ADB_UTILS_H */ diff --git a/minelf/Android.mk b/minelf/Android.mk index 0f41ff528..9ec9dad37 100644 --- a/minelf/Android.mk +++ b/minelf/Android.mk @@ -18,7 +18,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ Retouch.c -LOCAL_C_INCLUDES += bootable/recovery +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. LOCAL_MODULE := libminelf diff --git a/minelf/Retouch.c b/minelf/Retouch.c index 33809cd6d..d75eec1e8 100644 --- a/minelf/Retouch.c +++ b/minelf/Retouch.c @@ -194,213 +194,3 @@ int retouch_mask_data(uint8_t *binary_object, if (retouch_offset != NULL) *retouch_offset = offset_candidate; return RETOUCH_DATA_MATCHED; } - -// On success, _override is set to the offset that was actually applied. -// This implies that once we randomize to an offset we stick with it. -// This in turn is necessary in order to guarantee recovery after crash. -bool retouch_one_library(const char *binary_name, - const char *binary_sha1, - int32_t retouch_offset, - int32_t *retouch_offset_override) { - bool success = true; - int result; - - FileContents file; - file.data = NULL; - - char binary_name_atomic[strlen(binary_name)+10]; - strcpy(binary_name_atomic, binary_name); - strcat(binary_name_atomic, ".atomic"); - - // We need a path that exists for calling statfs() later. - // - // Assume that binary_name (eg "/system/app/Foo.apk") is located - // on the same filesystem as its top-level directory ("/system"). - char target_fs[strlen(binary_name)+1]; - char* slash = strchr(binary_name+1, '/'); - if (slash != NULL) { - int count = slash - binary_name; - strncpy(target_fs, binary_name, count); - target_fs[count] = '\0'; - } else { - strcpy(target_fs, binary_name); - } - - result = LoadFileContents(binary_name, &file, RETOUCH_DONT_MASK); - - if (result == 0) { - // Figure out the *apparent* offset to which this file has been - // retouched. If it looks good, we will skip processing (we might - // have crashed and during this recovery pass we don't want to - // overwrite a valuable saved file in /cache---which would happen - // if we blindly retouch everything again). NOTE: This implies - // that we might have to override the supplied retouch offset. We - // can do the override only once though: everything should match - // afterward. - - int32_t inferred_offset; - int retouch_probe_result = retouch_mask_data(file.data, - file.size, - NULL, - &inferred_offset); - - if (retouch_probe_result == RETOUCH_DATA_MATCHED) { - if ((retouch_offset == inferred_offset) || - ((retouch_offset != 0 && inferred_offset != 0) && - (retouch_offset_override != NULL))) { - // This file is OK already and we are allowed to override. - // Let's just return the offset override value. It is critical - // to skip regardless of override: a broken file might need - // recovery down the list and we should not mess up the saved - // copy by doing unnecessary retouching. - // - // NOTE: If retouching was already started with a different - // value, we will not be allowed to override. This happens - // if on the retouch list there is a patched binary (which is - // masked in apply_patch()) before there is a non-patched - // binary. - if (retouch_offset_override != NULL) - *retouch_offset_override = inferred_offset; - success = true; - goto out; - } else { - // Retouch to zero (mask the retouching), to make sure that - // the SHA-1 check will pass below. - int32_t zero = 0; - retouch_mask_data(file.data, file.size, &zero, NULL); - SHA(file.data, file.size, file.sha1); - } - } - - if (retouch_probe_result == RETOUCH_DATA_NOTAPPLICABLE) { - // In the case of not retouchable, fake it. We do not want - // to do the normal processing and overwrite the backup file: - // we might be recovering! - // - // We return a zero override, which tells the caller that we - // simply skipped the file. - if (retouch_offset_override != NULL) - *retouch_offset_override = 0; - success = true; - goto out; - } - - // If we get here, either there was a mismatch in the offset, or - // the file has not been processed yet. Continue with normal - // processing. - } - - if (result != 0 || FindMatchingPatch(file.sha1, &binary_sha1, 1) < 0) { - free(file.data); - printf("Attempting to recover source from '%s' ...\n", - CACHE_TEMP_SOURCE); - result = LoadFileContents(CACHE_TEMP_SOURCE, &file, RETOUCH_DO_MASK); - if (result != 0 || FindMatchingPatch(file.sha1, &binary_sha1, 1) < 0) { - printf(" failed.\n"); - success = false; - goto out; - } - printf(" succeeded.\n"); - } - - // Retouch in-memory before worrying about backing up the original. - // - // Recovery steps will be oblivious to the actual retouch offset used, - // so might as well write out the already-retouched copy. Then, in the - // usual case, we will just swap the file locally, with no more writes - // needed. In the no-free-space case, we will then write the same to the - // original location. - - result = retouch_mask_data(file.data, file.size, &retouch_offset, NULL); - if (result != RETOUCH_DATA_MATCHED) { - success = false; - goto out; - } - if (retouch_offset_override != NULL) - *retouch_offset_override = retouch_offset; - - // How much free space do we need? - bool enough_space = false; - size_t free_space = FreeSpaceForFile(target_fs); - // 50% margin when estimating the space needed. - enough_space = (free_space > (file.size * 3 / 2)); - - // The experts say we have to allow for a retry of the - // whole process to avoid filesystem weirdness. - int retry = 1; - bool made_copy = false; - do { - // First figure out where to store a copy of the original. - // Ideally leave the original itself intact until the - // atomic swap. If no room on the same partition, fall back - // to the cache partition and remove the original. - - if (!enough_space) { - printf("Target is %ldB; free space is %ldB: not enough.\n", - (long)file.size, (long)free_space); - - retry = 0; - if (MakeFreeSpaceOnCache(file.size) < 0) { - printf("Not enough free space on '/cache'.\n"); - success = false; - goto out; - } - if (SaveFileContents(CACHE_TEMP_SOURCE, file) < 0) { - printf("Failed to back up source file.\n"); - success = false; - goto out; - } - made_copy = true; - unlink(binary_name); - - size_t free_space = FreeSpaceForFile(target_fs); - printf("(now %ld bytes free for target)\n", (long)free_space); - } - - result = SaveFileContents(binary_name_atomic, file); - if (result != 0) { - // Maybe the filesystem was optimistic: retry. - enough_space = false; - unlink(binary_name_atomic); - printf("Saving the retouched contents failed; retrying.\n"); - continue; - } - - // Succeeded; no need to retry. - break; - } while (retry-- > 0); - - // Give the .atomic file the same owner, group, and mode of the - // original source file. - if (chmod(binary_name_atomic, file.st.st_mode) != 0) { - printf("chmod of \"%s\" failed: %s\n", - binary_name_atomic, strerror(errno)); - success = false; - goto out; - } - if (chown(binary_name_atomic, file.st.st_uid, file.st.st_gid) != 0) { - printf("chown of \"%s\" failed: %s\n", - binary_name_atomic, - strerror(errno)); - success = false; - goto out; - } - - // Finally, rename the .atomic file to replace the target file. - if (rename(binary_name_atomic, binary_name) != 0) { - printf("rename of .atomic to \"%s\" failed: %s\n", - binary_name, strerror(errno)); - success = false; - goto out; - } - - // If this run created a copy, and we're here, we can delete it. - if (made_copy) unlink(CACHE_TEMP_SOURCE); - - out: - // clean up - free(file.data); - unlink(binary_name_atomic); - - return success; -} diff --git a/minelf/Retouch.h b/minelf/Retouch.h index 048d78e44..13bacd5ad 100644 --- a/minelf/Retouch.h +++ b/minelf/Retouch.h @@ -25,12 +25,6 @@ typedef struct { uint32_t blob_size; /* in bytes, located right before this struct */ } retouch_info_t __attribute__((packed)); -// Retouch a file. Use CACHED_SOURCE_TEMP to store a copy. -bool retouch_one_library(const char *binary_name, - const char *binary_sha1, - int32_t retouch_offset, - int32_t *retouch_offset_override); - #define RETOUCH_DONT_MASK 0 #define RETOUCH_DO_MASK 1 diff --git a/minui/Android.mk b/minui/Android.mk index 4c4d7c7b6..d0b8999ef 100644 --- a/minui/Android.mk +++ b/minui/Android.mk @@ -1,19 +1,51 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := graphics.c events.c resources.c +LOCAL_SRC_FILES := events.c resources.c +ifneq ($(BOARD_CUSTOM_GRAPHICS),) + LOCAL_SRC_FILES += $(BOARD_CUSTOM_GRAPHICS) +else + LOCAL_SRC_FILES += graphics.c graphics_overlay.c +endif LOCAL_C_INCLUDES +=\ external/libpng\ external/zlib +ifeq ($(call is-vendor-board-platform,QCOM),true) + LOCAL_ADDITIONAL_DEPENDENCIES := $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ/usr + LOCAL_C_INCLUDES += $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ/usr/include +endif + +ifeq ($(TARGET_USES_QCOM_BSP), true) + LOCAL_CFLAGS += -DMSM_BSP +endif + LOCAL_MODULE := libminui -ifeq ($(TARGET_RECOVERY_PIXEL_FORMAT),"RGBX_8888") +# 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. + +ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888) LOCAL_CFLAGS += -DRECOVERY_RGBX endif -ifeq ($(TARGET_RECOVERY_PIXEL_FORMAT),"BGRA_8888") +ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888) LOCAL_CFLAGS += -DRECOVERY_BGRA endif +ifneq ($(TARGET_RECOVERY_OVERSCAN_PERCENT),) + LOCAL_CFLAGS += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT) +else + LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0 +endif + +ifneq ($(BOARD_USE_CUSTOM_RECOVERY_FONT),) + LOCAL_CFLAGS += -DBOARD_USE_CUSTOM_RECOVERY_FONT=$(BOARD_USE_CUSTOM_RECOVERY_FONT) +endif + +ifneq ($(TARGET_RECOVERY_LCD_BACKLIGHT_PATH),) + LOCAL_CFLAGS += -DRECOVERY_LCD_BACKLIGHT_PATH=$(TARGET_RECOVERY_LCD_BACKLIGHT_PATH) +endif + include $(BUILD_STATIC_LIBRARY) diff --git a/minui/courier_extended_15x24.h b/minui/courier_extended_15x24.h new file mode 100644 index 000000000..8e41e31a6 --- /dev/null +++ b/minui/courier_extended_15x24.h @@ -0,0 +1,536 @@ +struct { + unsigned width; + unsigned height; + unsigned cwidth; + unsigned cheight; + unsigned char rundata[]; +} font = { + .width = 3360, + .height = 24, + .cwidth = 15, + .cheight = 24, + .rundata = { +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x43,0x81,0x03,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x5f,0x81,0x05,0x81,0x7f,0x12,0x84,0x37,0x81,0x05,0x81,0x7f,0x2e,0x81,0x12, +0x81,0x0b,0x81,0x2b,0x81,0x03,0x81,0x29,0x81,0x12,0x81,0x0c,0x81,0x1b,0x81, +0x11,0x81,0x0c,0x81,0x3a,0x81,0x11,0x81,0x0d,0x81,0x47,0x81,0x12,0x81,0x0c, +0x81,0x1f,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x73,0x81,0x7f,0x7f,0x7f,0x7f,0x7f, +0x1f,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x60,0x82,0x01,0x82,0x7e, +0x8e,0x05,0x81,0x04,0x81,0x37,0x82,0x01,0x82,0x7f,0x10,0x82,0x03,0x82,0x19, +0x82,0x0e,0x82,0x0a,0x82,0x01,0x82,0x0a,0x83,0x02,0x81,0x08,0x82,0x03,0x82, +0x0a,0x81,0x03,0x81,0x2a,0x82,0x0e,0x82,0x0b,0x82,0x01,0x82,0x09,0x82,0x03, +0x82,0x0a,0x82,0x0d,0x82,0x0b,0x82,0x01,0x82,0x09,0x82,0x03,0x82,0x18,0x83, +0x02,0x81,0x0b,0x82,0x0d,0x82,0x0c,0x82,0x01,0x82,0x0a,0x83,0x02,0x81,0x08, +0x82,0x03,0x82,0x27,0x82,0x0e,0x82,0x0b,0x82,0x01,0x82,0x09,0x82,0x03,0x82, +0x0b,0x82,0x76,0x83,0x7f,0x7f,0x7f,0x41,0x81,0x03,0x81,0x0b,0x81,0x7f,0x28, +0x81,0x7f,0x7b,0x83,0x7f,0x7f,0x7f,0x20,0x81,0x21,0x81,0x1c,0x81,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x7f,0x66,0x81,0x3b,0x81,0x38,0x84,0x57,0x81,0x04,0x81, +0x39,0x81,0x57,0x83,0x37,0x82,0x03,0x82,0x1b,0x81,0x0c,0x81,0x0b,0x81,0x05, +0x81,0x08,0x81,0x02,0x83,0x09,0x82,0x03,0x82,0x0b,0x83,0x2d,0x81,0x0c,0x81, +0x0c,0x81,0x05,0x81,0x08,0x82,0x03,0x82,0x0c,0x81,0x0b,0x81,0x0c,0x81,0x05, +0x81,0x08,0x82,0x03,0x82,0x17,0x81,0x02,0x83,0x0e,0x81,0x0b,0x81,0x0d,0x81, +0x05,0x81,0x08,0x81,0x02,0x83,0x09,0x82,0x03,0x82,0x29,0x81,0x0c,0x81,0x0c, +0x81,0x05,0x81,0x08,0x82,0x03,0x82,0x0a,0x81,0x77,0x81,0x03,0x81,0x7f,0x7f, +0x7f,0x22,0x81,0x0b,0x83,0x02,0x83,0x0a,0x81,0x03,0x81,0x0b,0x81,0x0b,0x84, +0x1b,0x83,0x10,0x81,0x09,0x81,0x10,0x81,0x4d,0x81,0x0a,0x84,0x0c,0x82,0x0b, +0x85,0x0a,0x85,0x0e,0x82,0x08,0x87,0x0c,0x85,0x05,0x89,0x08,0x85,0x0b,0x84, +0x64,0x82,0x03,0x81,0x7f,0x7f,0x7f,0x14,0x84,0x08,0x81,0x0e,0x84,0x0e,0x81, +0x1d,0x82,0x16,0x83,0x24,0x83,0x19,0x86,0x12,0x83,0x11,0x81,0x0f,0x81,0x09, +0x83,0x0d,0x84,0x7f,0x62,0x82,0x0c,0x81,0x0c,0x82,0x7f,0x7f,0x7f,0x7f,0x30, +0x81,0x47,0x87,0x07,0x81,0x05,0x81,0x09,0x86,0x0d,0x81,0x38,0x86,0x18,0x81, +0x04,0x81,0x0b,0x81,0x0d,0x84,0x0b,0x84,0x28,0x88,0x16,0x81,0x05,0x81,0x0b, +0x81,0x0b,0x81,0x03,0x81,0x7f,0x7f,0x7f,0x48,0x81,0x61,0x83,0x0c,0x81,0x11, +0x81,0x0d,0x81,0x2a,0x81,0x03,0x81,0x28,0x81,0x11,0x81,0x0d,0x81,0x19,0x81, +0x12,0x81,0x0c,0x81,0x1d,0x83,0x01,0x82,0x17,0x81,0x12,0x81,0x0c,0x81,0x48, +0x81,0x12,0x81,0x0c,0x81,0x1f,0x81,0x06,0x83,0x2f,0x81,0x0b,0x83,0x02,0x83, +0x0a,0x81,0x03,0x81,0x09,0x86,0x07,0x81,0x04,0x81,0x1a,0x83,0x10,0x81,0x09, +0x81,0x10,0x81,0x4d,0x81,0x09,0x81,0x04,0x81,0x08,0x83,0x01,0x81,0x0a,0x81, +0x05,0x81,0x07,0x82,0x05,0x81,0x0c,0x81,0x01,0x81,0x08,0x81,0x11,0x82,0x09, +0x81,0x07,0x81,0x07,0x81,0x05,0x81,0x09,0x81,0x04,0x81,0x54,0x85,0x09,0x81, +0x06,0x81,0x07,0x85,0x08,0x88,0x0a,0x85,0x02,0x81,0x04,0x89,0x07,0x8a,0x05, +0x8a,0x07,0x85,0x02,0x81,0x04,0x85,0x02,0x85,0x04,0x89,0x0a,0x87,0x03,0x85, +0x03,0x84,0x04,0x85,0x08,0x84,0x06,0x84,0x01,0x84,0x04,0x85,0x07,0x84,0x08, +0x89,0x09,0x84,0x07,0x88,0x0b,0x85,0x01,0x81,0x04,0x8b,0x03,0x85,0x03,0x85, +0x02,0x85,0x04,0x85,0x01,0x84,0x05,0x85,0x02,0x84,0x03,0x85,0x03,0x84,0x03, +0x84,0x06,0x88,0x0a,0x81,0x0b,0x81,0x11,0x81,0x0d,0x81,0x01,0x81,0x1e,0x81, +0x17,0x81,0x26,0x81,0x17,0x82,0x1a,0x81,0x11,0x81,0x0f,0x81,0x0b,0x81,0x10, +0x81,0x75,0x81,0x6a,0x81,0x0e,0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x2f,0x81, +0x0b,0x84,0x0c,0x86,0x01,0x81,0x03,0x84,0x03,0x84,0x08,0x85,0x01,0x81,0x06, +0x82,0x05,0x81,0x08,0x82,0x01,0x82,0x09,0x82,0x04,0x82,0x0c,0x81,0x37,0x82, +0x04,0x82,0x17,0x81,0x04,0x81,0x0b,0x81,0x0c,0x81,0x04,0x81,0x09,0x81,0x04, +0x81,0x08,0x88,0x16,0x81,0x02,0x81,0x02,0x81,0x19,0x82,0x01,0x82,0x0a,0x83, +0x0a,0x81,0x05,0x81,0x17,0x8a,0x12,0x84,0x03,0x84,0x15,0x85,0x0a,0x85,0x0a, +0x85,0x0a,0x85,0x0a,0x85,0x0a,0x85,0x0a,0x8a,0x06,0x85,0x02,0x81,0x05,0x8a, +0x05,0x8a,0x05,0x8a,0x05,0x8a,0x05,0x89,0x06,0x89,0x06,0x89,0x06,0x89,0x05, +0x89,0x05,0x84,0x04,0x85,0x07,0x84,0x0b,0x84,0x0b,0x84,0x0b,0x84,0x0b,0x84, +0x1a,0x84,0x02,0x81,0x03,0x85,0x03,0x85,0x02,0x85,0x03,0x85,0x02,0x85,0x03, +0x85,0x02,0x85,0x03,0x85,0x03,0x84,0x03,0x84,0x05,0x87,0x09,0x82,0x03,0x81, +0x0c,0x82,0x0d,0x82,0x0c,0x82,0x01,0x82,0x0a,0x83,0x02,0x81,0x07,0x82,0x03, +0x82,0x0a,0x81,0x03,0x81,0x29,0x82,0x0d,0x82,0x0c,0x82,0x01,0x82,0x09,0x82, +0x03,0x82,0x08,0x82,0x0e,0x82,0x0b,0x82,0x01,0x82,0x09,0x82,0x03,0x82,0x0c, +0x83,0x0b,0x83,0x02,0x81,0x09,0x82,0x0e,0x82,0x0b,0x82,0x01,0x82,0x0a,0x83, +0x02,0x81,0x08,0x82,0x03,0x82,0x28,0x82,0x0e,0x82,0x0b,0x82,0x01,0x82,0x0a, +0x82,0x03,0x82,0x0a,0x82,0x09,0x81,0x0f,0x82,0x03,0x82,0x19,0x81,0x0b,0x83, +0x02,0x83,0x09,0x81,0x03,0x81,0x09,0x81,0x04,0x82,0x07,0x81,0x04,0x81,0x0a, +0x84,0x0c,0x83,0x0f,0x81,0x0b,0x81,0x0f,0x81,0x0e,0x81,0x3d,0x81,0x09,0x81, +0x06,0x81,0x0b,0x81,0x09,0x81,0x07,0x81,0x0e,0x81,0x0a,0x81,0x02,0x81,0x08, +0x81,0x10,0x81,0x13,0x81,0x06,0x81,0x07,0x81,0x07,0x81,0x06,0x81,0x2c,0x82, +0x14,0x82,0x0e,0x81,0x05,0x81,0x08,0x81,0x06,0x81,0x0a,0x82,0x0a,0x81,0x05, +0x81,0x08,0x82,0x04,0x83,0x06,0x81,0x06,0x81,0x08,0x81,0x06,0x81,0x07,0x81, +0x06,0x81,0x06,0x81,0x05,0x83,0x06,0x81,0x06,0x81,0x0a,0x81,0x12,0x81,0x07, +0x81,0x06,0x81,0x08,0x81,0x0c,0x82,0x06,0x82,0x05,0x82,0x06,0x81,0x07,0x82, +0x04,0x82,0x08,0x81,0x06,0x81,0x06,0x82,0x04,0x82,0x07,0x81,0x05,0x81,0x09, +0x81,0x05,0x82,0x04,0x81,0x04,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x06,0x81, +0x08,0x81,0x04,0x81,0x09,0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x07, +0x81,0x06,0x81,0x0a,0x81,0x0c,0x81,0x10,0x81,0x0c,0x81,0x03,0x81,0x35,0x81, +0x26,0x81,0x17,0x81,0x1b,0x81,0x2d,0x81,0x10,0x81,0x75,0x81,0x6a,0x81,0x0e, +0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x2e,0x85,0x07,0x82,0x03,0x81,0x0a,0x81, +0x06,0x82,0x05,0x81,0x05,0x81,0x09,0x81,0x05,0x82,0x06,0x81,0x06,0x81,0x0a, +0x81,0x0a,0x81,0x08,0x81,0x07,0x85,0x36,0x81,0x08,0x81,0x17,0x84,0x0c,0x81, +0x11,0x81,0x0e,0x81,0x08,0x81,0x06,0x81,0x15,0x81,0x03,0x81,0x02,0x81,0x1b, +0x81,0x0e,0x81,0x0a,0x81,0x05,0x81,0x16,0x82,0x03,0x81,0x04,0x81,0x13,0x81, +0x07,0x81,0x19,0x82,0x0d,0x82,0x0d,0x82,0x0d,0x82,0x0d,0x82,0x0d,0x82,0x0c, +0x81,0x01,0x81,0x04,0x81,0x05,0x82,0x04,0x83,0x07,0x81,0x06,0x81,0x07,0x81, +0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x09,0x81,0x0e,0x81,0x0e, +0x81,0x0e,0x81,0x0b,0x81,0x06,0x81,0x06,0x82,0x06,0x81,0x07,0x82,0x04,0x82, +0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04, +0x82,0x16,0x82,0x04,0x82,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81, +0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x08,0x81,0x0d,0x81,0x05, +0x81,0x0d,0x81,0x0b,0x81,0x0d,0x81,0x05,0x81,0x08,0x81,0x02,0x83,0x08,0x82, +0x03,0x82,0x0b,0x83,0x2c,0x81,0x0b,0x81,0x0d,0x81,0x05,0x81,0x08,0x82,0x03, +0x82,0x0a,0x81,0x0c,0x81,0x0c,0x81,0x05,0x81,0x08,0x82,0x03,0x82,0x0a,0x82, +0x03,0x81,0x09,0x81,0x02,0x83,0x0c,0x81,0x0c,0x81,0x0c,0x81,0x05,0x81,0x08, +0x81,0x02,0x83,0x09,0x82,0x03,0x82,0x0a,0x82,0x1e,0x81,0x0c,0x81,0x0c,0x81, +0x05,0x81,0x09,0x82,0x03,0x82,0x09,0x81,0x0b,0x81,0x0f,0x82,0x03,0x82,0x19, +0x81,0x0c,0x81,0x04,0x81,0x0a,0x81,0x03,0x81,0x08,0x81,0x06,0x81,0x07,0x81, +0x04,0x81,0x09,0x82,0x02,0x81,0x0d,0x81,0x10,0x81,0x0b,0x81,0x0b,0x89,0x0a, +0x81,0x3d,0x81,0x08,0x81,0x08,0x81,0x0a,0x81,0x09,0x81,0x07,0x81,0x0e,0x81, +0x0a,0x81,0x02,0x81,0x08,0x81,0x0f,0x81,0x13,0x81,0x07,0x81,0x07,0x81,0x07, +0x81,0x07,0x81,0x29,0x82,0x18,0x82,0x0c,0x81,0x06,0x81,0x07,0x81,0x06,0x81, +0x09,0x81,0x02,0x81,0x09,0x81,0x06,0x81,0x06,0x81,0x08,0x81,0x06,0x81,0x07, +0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x05,0x81,0x08,0x81,0x06,0x81, +0x06,0x81,0x0a,0x81,0x12,0x81,0x07,0x81,0x05,0x81,0x09,0x81,0x0c,0x81,0x01, +0x81,0x04,0x81,0x01,0x81,0x05,0x81,0x01,0x81,0x05,0x81,0x06,0x81,0x08,0x81, +0x07,0x81,0x07,0x81,0x04,0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x07, +0x81,0x04,0x81,0x04,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x07,0x81,0x06,0x81, +0x05,0x81,0x09,0x81,0x07,0x81,0x04,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05, +0x81,0x0b,0x81,0x0c,0x81,0x10,0x81,0x0b,0x81,0x05,0x81,0x34,0x81,0x26,0x81, +0x17,0x81,0x1b,0x81,0x2d,0x81,0x10,0x81,0x75,0x81,0x6a,0x81,0x0e,0x81,0x0e, +0x81,0x7f,0x7f,0x7f,0x7f,0x2c,0x82,0x04,0x81,0x07,0x81,0x0e,0x81,0x08,0x81, +0x05,0x81,0x05,0x81,0x08,0x81,0x07,0x81,0x06,0x82,0x1a,0x81,0x03,0x85,0x02, +0x81,0x05,0x81,0x04,0x81,0x35,0x81,0x02,0x84,0x04,0x81,0x26,0x81,0x10,0x81, +0x0c,0x83,0x09,0x81,0x05,0x81,0x16,0x81,0x03,0x81,0x02,0x81,0x2a,0x81,0x0a, +0x81,0x05,0x81,0x15,0x81,0x05,0x81,0x04,0x81,0x14,0x81,0x05,0x81,0x19,0x81, +0x02,0x81,0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0b, +0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0b,0x81,0x01,0x81,0x04,0x81,0x04,0x81, +0x08,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x09,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x07,0x81, +0x05,0x81,0x01,0x81,0x05,0x81,0x06,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05, +0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x14,0x82,0x05,0x81, +0x01,0x81,0x05,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06, +0x81,0x07,0x81,0x07,0x81,0x05,0x81,0x09,0x81,0x0d,0x81,0x05,0x81,0x7f,0x7f, +0x01,0x81,0x63,0x82,0x64,0x81,0x2f,0x81,0x0c,0x81,0x04,0x81,0x0a,0x81,0x03, +0x81,0x08,0x81,0x0e,0x81,0x04,0x81,0x09,0x81,0x11,0x81,0x10,0x81,0x0b,0x81, +0x0f,0x81,0x0e,0x81,0x3c,0x81,0x09,0x81,0x08,0x81,0x0a,0x81,0x11,0x81,0x0e, +0x81,0x09,0x81,0x03,0x81,0x08,0x81,0x0e,0x81,0x14,0x81,0x07,0x81,0x07,0x81, +0x07,0x81,0x07,0x81,0x08,0x83,0x0d,0x83,0x0c,0x82,0x1c,0x82,0x11,0x81,0x07, +0x81,0x04,0x83,0x09,0x81,0x02,0x81,0x09,0x81,0x06,0x81,0x05,0x81,0x09,0x81, +0x06,0x81,0x07,0x82,0x06,0x81,0x0e,0x81,0x0b,0x81,0x10,0x81,0x06,0x81,0x0a, +0x81,0x12,0x81,0x07,0x81,0x04,0x81,0x0a,0x81,0x0c,0x81,0x01,0x81,0x04,0x81, +0x01,0x81,0x05,0x81,0x01,0x81,0x05,0x81,0x06,0x81,0x08,0x81,0x07,0x81,0x07, +0x81,0x04,0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x07,0x81,0x04,0x81, +0x04,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x07,0x81,0x06,0x81,0x05,0x81,0x04, +0x81,0x04,0x81,0x08,0x81,0x02,0x81,0x0a,0x81,0x03,0x81,0x09,0x81,0x04,0x81, +0x0c,0x81,0x0d,0x81,0x0f,0x81,0x0b,0x81,0x05,0x81,0x28,0x84,0x08,0x81,0x02, +0x84,0x0b,0x85,0x01,0x81,0x07,0x85,0x02,0x81,0x07,0x85,0x08,0x89,0x08,0x84, +0x02,0x83,0x05,0x81,0x02,0x83,0x09,0x84,0x0a,0x87,0x0a,0x81,0x02,0x85,0x09, +0x81,0x08,0x83,0x01,0x83,0x02,0x83,0x04,0x83,0x02,0x83,0x0a,0x85,0x06,0x83, +0x02,0x84,0x0a,0x84,0x02,0x83,0x05,0x83,0x03,0x83,0x07,0x85,0x01,0x81,0x06, +0x89,0x05,0x83,0x04,0x83,0x05,0x85,0x02,0x85,0x02,0x84,0x05,0x85,0x03,0x84, +0x02,0x84,0x03,0x84,0x04,0x85,0x05,0x89,0x09,0x81,0x0e,0x81,0x0e,0x81,0x7f, +0x7f,0x7f,0x7f,0x1e,0x83,0x0a,0x82,0x05,0x81,0x07,0x81,0x0d,0x81,0x09,0x81, +0x06,0x81,0x03,0x81,0x09,0x81,0x07,0x81,0x05,0x85,0x0c,0x85,0x01,0x81,0x04, +0x82,0x03,0x81,0x03,0x81,0x02,0x82,0x04,0x81,0x03,0x82,0x34,0x82,0x03,0x81, +0x02,0x81,0x03,0x82,0x25,0x81,0x0e,0x82,0x10,0x81,0x08,0x81,0x04,0x81,0x07, +0x83,0x04,0x83,0x06,0x81,0x03,0x81,0x02,0x81,0x17,0x89,0x0a,0x81,0x0b,0x81, +0x03,0x81,0x16,0x81,0x05,0x81,0x04,0x81,0x04,0x84,0x03,0x82,0x08,0x81,0x03, +0x81,0x0c,0x83,0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81, +0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0a,0x81,0x02, +0x81,0x04,0x81,0x03,0x81,0x09,0x81,0x07,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81, +0x10,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x07,0x81,0x05,0x81,0x01, +0x81,0x05,0x81,0x06,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81, +0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x14,0x81,0x06,0x81,0x01,0x81,0x05, +0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81, +0x08,0x81,0x03,0x81,0x0a,0x87,0x07,0x81,0x04,0x82,0x0a,0x84,0x0b,0x84,0x0b, +0x84,0x0b,0x84,0x0b,0x84,0x0b,0x84,0x08,0x84,0x03,0x82,0x09,0x85,0x01,0x81, +0x07,0x85,0x0a,0x85,0x0a,0x85,0x0a,0x85,0x09,0x84,0x0b,0x84,0x0b,0x84,0x0b, +0x84,0x0d,0x84,0x01,0x81,0x05,0x83,0x02,0x83,0x0a,0x85,0x0a,0x85,0x0a,0x85, +0x0a,0x85,0x0a,0x85,0x19,0x85,0x02,0x81,0x04,0x83,0x04,0x83,0x05,0x83,0x04, +0x83,0x05,0x83,0x04,0x83,0x05,0x83,0x04,0x83,0x04,0x84,0x04,0x85,0x04,0x81, +0x02,0x84,0x06,0x84,0x04,0x85,0x16,0x81,0x0c,0x81,0x04,0x81,0x08,0x89,0x06, +0x82,0x0e,0x84,0x0a,0x81,0x11,0x81,0x0f,0x81,0x0d,0x81,0x0e,0x82,0x0d,0x81, +0x3c,0x81,0x09,0x81,0x08,0x81,0x0a,0x81,0x10,0x81,0x0e,0x81,0x0a,0x81,0x03, +0x81,0x08,0x81,0x01,0x84,0x09,0x81,0x14,0x81,0x08,0x81,0x05,0x81,0x08,0x81, +0x06,0x82,0x08,0x83,0x0d,0x83,0x0b,0x81,0x0a,0x8c,0x0a,0x81,0x10,0x81,0x07, +0x81,0x02,0x82,0x02,0x81,0x09,0x81,0x02,0x81,0x09,0x81,0x06,0x81,0x05,0x81, +0x10,0x81,0x08,0x81,0x06,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x07,0x81,0x10, +0x81,0x06,0x81,0x0a,0x81,0x12,0x81,0x07,0x81,0x03,0x81,0x0b,0x81,0x0c,0x81, +0x02,0x81,0x03,0x81,0x01,0x81,0x05,0x81,0x02,0x81,0x04,0x81,0x05,0x81,0x0a, +0x81,0x06,0x81,0x07,0x81,0x03,0x81,0x0a,0x81,0x05,0x81,0x06,0x81,0x07,0x81, +0x0c,0x81,0x04,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x07,0x81,0x06,0x81,0x05, +0x81,0x04,0x81,0x03,0x81,0x09,0x81,0x01,0x81,0x0b,0x81,0x03,0x81,0x0e,0x81, +0x0c,0x81,0x0d,0x81,0x0f,0x81,0x0a,0x81,0x07,0x81,0x25,0x82,0x04,0x82,0x06, +0x81,0x01,0x81,0x04,0x81,0x09,0x81,0x05,0x82,0x06,0x81,0x05,0x81,0x01,0x81, +0x06,0x81,0x05,0x81,0x0a,0x81,0x0c,0x81,0x04,0x81,0x01,0x81,0x07,0x83,0x03, +0x81,0x0b,0x81,0x10,0x81,0x0a,0x81,0x03,0x81,0x0c,0x81,0x0a,0x82,0x03,0x82, +0x03,0x81,0x05,0x83,0x03,0x81,0x08,0x81,0x05,0x81,0x07,0x81,0x01,0x81,0x04, +0x81,0x07,0x82,0x04,0x81,0x01,0x81,0x09,0x81,0x01,0x82,0x03,0x81,0x05,0x81, +0x05,0x82,0x08,0x81,0x0d,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x05,0x81,0x09, +0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x07,0x81,0x06,0x81,0x0a,0x81, +0x0e,0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x1e,0x83,0x0a,0x81,0x0e,0x81,0x0d, +0x81,0x11,0x81,0x01,0x81,0x0a,0x81,0x0c,0x81,0x04,0x83,0x09,0x81,0x05,0x82, +0x04,0x81,0x03,0x81,0x08,0x81,0x05,0x83,0x01,0x82,0x0a,0x82,0x03,0x82,0x22, +0x81,0x04,0x81,0x02,0x81,0x04,0x81,0x20,0x8b,0x08,0x81,0x0d,0x81,0x04,0x81, +0x0d,0x81,0x09,0x81,0x06,0x81,0x06,0x81,0x03,0x81,0x02,0x81,0x0d,0x82,0x08, +0x81,0x06,0x81,0x0b,0x81,0x0c,0x83,0x08,0x82,0x03,0x82,0x07,0x81,0x06,0x81, +0x02,0x81,0x05,0x81,0x04,0x81,0x01,0x81,0x02,0x81,0x07,0x81,0x03,0x81,0x0c, +0x83,0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0b,0x81, +0x02,0x81,0x0b,0x81,0x02,0x81,0x0b,0x81,0x02,0x81,0x0a,0x81,0x02,0x81,0x02, +0x81,0x05,0x81,0x11,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81, +0x0a,0x81,0x03,0x81,0x0c,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x08, +0x81,0x04,0x81,0x02,0x81,0x04,0x81,0x05,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81, +0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x06,0x81,0x04, +0x81,0x06,0x81,0x06,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x06,0x81,0x07,0x81, +0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x08,0x81,0x03,0x81,0x0a,0x81,0x06, +0x81,0x06,0x81,0x02,0x83,0x09,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82, +0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x05, +0x81,0x04,0x81,0x01,0x81,0x02,0x81,0x07,0x81,0x05,0x82,0x06,0x81,0x05,0x81, +0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x0b,0x81,0x0e, +0x81,0x0e,0x81,0x0e,0x81,0x0b,0x82,0x04,0x81,0x01,0x81,0x06,0x83,0x03,0x81, +0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05, +0x81,0x08,0x81,0x05,0x81,0x17,0x81,0x05,0x82,0x07,0x81,0x06,0x81,0x07,0x81, +0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x06, +0x81,0x01,0x81,0x04,0x82,0x06,0x81,0x07,0x81,0x18,0x81,0x1c,0x81,0x03,0x81, +0x0a,0x84,0x0d,0x84,0x08,0x81,0x10,0x81,0x0f,0x81,0x0d,0x81,0x0d,0x81,0x02, +0x81,0x0c,0x81,0x3b,0x81,0x0a,0x81,0x08,0x81,0x0a,0x81,0x0f,0x81,0x0c,0x83, +0x0a,0x81,0x04,0x81,0x08,0x82,0x04,0x81,0x08,0x81,0x02,0x84,0x0d,0x81,0x0a, +0x85,0x0a,0x81,0x04,0x83,0x08,0x83,0x0d,0x83,0x09,0x82,0x22,0x82,0x0d,0x82, +0x07,0x81,0x02,0x81,0x03,0x81,0x08,0x81,0x04,0x81,0x08,0x81,0x05,0x81,0x06, +0x81,0x10,0x81,0x08,0x81,0x06,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x07,0x81, +0x10,0x81,0x06,0x81,0x0a,0x81,0x12,0x81,0x07,0x81,0x01,0x82,0x0c,0x81,0x0c, +0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x05,0x81,0x02,0x81,0x04,0x81,0x05,0x81, +0x0a,0x81,0x06,0x81,0x07,0x81,0x03,0x81,0x0a,0x81,0x05,0x81,0x05,0x81,0x09, +0x81,0x10,0x81,0x0a,0x81,0x07,0x81,0x08,0x81,0x04,0x81,0x07,0x81,0x02,0x81, +0x01,0x81,0x02,0x81,0x0a,0x81,0x0d,0x81,0x01,0x81,0x0e,0x81,0x0d,0x81,0x0e, +0x81,0x0e,0x81,0x3f,0x81,0x06,0x82,0x06,0x81,0x07,0x81,0x07,0x81,0x05,0x81, +0x07,0x82,0x05,0x81,0x07,0x81,0x09,0x81,0x0b,0x81,0x06,0x82,0x07,0x81,0x06, +0x81,0x0a,0x81,0x10,0x81,0x0a,0x81,0x02,0x81,0x0d,0x81,0x0a,0x81,0x04,0x81, +0x04,0x81,0x05,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x06,0x82,0x06,0x81,0x06, +0x81,0x06,0x82,0x09,0x82,0x0b,0x81,0x06,0x81,0x08,0x81,0x0d,0x81,0x06,0x81, +0x07,0x81,0x06,0x81,0x05,0x81,0x09,0x81,0x07,0x81,0x04,0x81,0x07,0x81,0x06, +0x81,0x08,0x81,0x05,0x81,0x0b,0x81,0x0e,0x81,0x0e,0x81,0x0c,0x82,0x7f,0x7f, +0x7f,0x7f,0x1d,0x81,0x0f,0x81,0x0b,0x89,0x0a,0x81,0x01,0x81,0x0b,0x81,0x0b, +0x81,0x07,0x82,0x07,0x81,0x06,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x14,0x82, +0x03,0x82,0x23,0x81,0x04,0x83,0x05,0x81,0x25,0x81,0x0c,0x86,0x0a,0x84,0x0d, +0x81,0x0a,0x81,0x06,0x81,0x07,0x81,0x02,0x81,0x02,0x81,0x0d,0x82,0x08,0x81, +0x05,0x81,0x0c,0x81,0x18,0x82,0x03,0x82,0x06,0x81,0x06,0x81,0x02,0x81,0x05, +0x81,0x05,0x81,0x04,0x81,0x07,0x81,0x01,0x81,0x1a,0x81,0x04,0x81,0x09,0x81, +0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09, +0x81,0x04,0x81,0x09,0x81,0x02,0x81,0x02,0x81,0x05,0x81,0x11,0x81,0x03,0x81, +0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0c,0x81,0x0e, +0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x08,0x81,0x04,0x81,0x02,0x81,0x04,0x81, +0x05,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a, +0x81,0x03,0x81,0x0a,0x81,0x07,0x81,0x02,0x81,0x07,0x81,0x05,0x81,0x04,0x81, +0x04,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x09,0x81,0x01,0x81,0x0b,0x81,0x07,0x81,0x05,0x81,0x05,0x81,0x0f,0x81, +0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x03,0x81,0x06, +0x81,0x07,0x81,0x05,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81, +0x06,0x81,0x07,0x81,0x0a,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x06, +0x82,0x06,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81, +0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x15,0x81,0x06,0x82,0x07, +0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81, +0x06,0x81,0x06,0x81,0x07,0x82,0x06,0x81,0x06,0x81,0x06,0x81,0x19,0x81,0x1c, +0x81,0x03,0x81,0x0e,0x81,0x08,0x84,0x0c,0x82,0x1f,0x81,0x0d,0x81,0x0c,0x81, +0x03,0x81,0x07,0x8b,0x14,0x8a,0x18,0x81,0x0a,0x81,0x08,0x81,0x0a,0x81,0x0e, +0x81,0x10,0x81,0x08,0x81,0x05,0x81,0x0f,0x81,0x07,0x81,0x01,0x81,0x04,0x81, +0x0c,0x81,0x09,0x81,0x05,0x81,0x0a,0x84,0x02,0x81,0x22,0x82,0x26,0x82,0x0a, +0x81,0x09,0x81,0x02,0x81,0x03,0x81,0x08,0x81,0x04,0x81,0x08,0x87,0x06,0x81, +0x10,0x81,0x08,0x81,0x06,0x85,0x0a,0x85,0x07,0x81,0x10,0x88,0x0a,0x81,0x12, +0x81,0x07,0x82,0x02,0x82,0x0a,0x81,0x0c,0x81,0x03,0x81,0x01,0x81,0x02,0x81, +0x05,0x81,0x03,0x81,0x03,0x81,0x05,0x81,0x0a,0x81,0x06,0x81,0x06,0x81,0x04, +0x81,0x0a,0x81,0x05,0x86,0x0b,0x85,0x0b,0x81,0x0a,0x81,0x07,0x81,0x08,0x81, +0x04,0x81,0x07,0x81,0x02,0x81,0x01,0x81,0x02,0x81,0x09,0x81,0x01,0x81,0x0d, +0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x3f,0x81,0x06,0x81,0x08,0x81, +0x05,0x81,0x0d,0x81,0x09,0x81,0x05,0x81,0x08,0x81,0x08,0x81,0x0a,0x81,0x08, +0x81,0x07,0x81,0x06,0x81,0x0a,0x81,0x10,0x81,0x0a,0x81,0x01,0x81,0x0e,0x81, +0x0a,0x81,0x04,0x81,0x04,0x81,0x05,0x81,0x06,0x81,0x05,0x81,0x09,0x81,0x05, +0x81,0x08,0x81,0x04,0x81,0x08,0x81,0x09,0x81,0x0c,0x81,0x0f,0x81,0x0d,0x81, +0x06,0x81,0x08,0x81,0x04,0x81,0x06,0x81,0x04,0x81,0x04,0x81,0x08,0x81,0x02, +0x81,0x09,0x81,0x05,0x81,0x0d,0x81,0x0c,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81, +0x02,0x81,0x04,0x81,0x7f,0x7f,0x7f,0x7f,0x17,0x81,0x0c,0x87,0x09,0x81,0x0e, +0x89,0x09,0x85,0x07,0x82,0x07,0x81,0x06,0x81,0x0b,0x81,0x03,0x81,0x08,0x81, +0x13,0x82,0x03,0x82,0x05,0x8c,0x13,0x81,0x04,0x81,0x02,0x81,0x04,0x81,0x25, +0x81,0x2c,0x81,0x0b,0x81,0x06,0x81,0x08,0x83,0x02,0x81,0x1c,0x81,0x0b,0x85, +0x17,0x82,0x03,0x82,0x05,0x81,0x06,0x84,0x04,0x81,0x06,0x81,0x04,0x81,0x08, +0x81,0x1b,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81, +0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x02,0x84,0x05, +0x81,0x11,0x85,0x0a,0x85,0x0a,0x85,0x0a,0x85,0x0c,0x81,0x0e,0x81,0x0e,0x81, +0x0e,0x81,0x09,0x87,0x04,0x81,0x04,0x81,0x03,0x81,0x03,0x81,0x05,0x81,0x0a, +0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81, +0x0a,0x81,0x08,0x82,0x08,0x81,0x04,0x81,0x05,0x81,0x04,0x81,0x07,0x81,0x06, +0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x0a,0x81,0x0c,0x81, +0x07,0x81,0x05,0x81,0x06,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0e, +0x81,0x0e,0x81,0x0b,0x81,0x04,0x81,0x04,0x81,0x0e,0x81,0x08,0x81,0x05,0x81, +0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x09,0x81,0x0e,0x81,0x0e, +0x81,0x0e,0x81,0x0a,0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x05,0x81,0x09,0x81, +0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09, +0x81,0x04,0x8b,0x04,0x81,0x06,0x81,0x02,0x81,0x06,0x81,0x06,0x81,0x07,0x81, +0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x05,0x81,0x07, +0x81,0x08,0x81,0x06,0x81,0x05,0x81,0x19,0x81,0x1c,0x81,0x03,0x81,0x0f,0x81, +0x0a,0x84,0x08,0x81,0x02,0x81,0x02,0x82,0x1a,0x81,0x0d,0x81,0x1d,0x81,0x3a, +0x81,0x0b,0x81,0x08,0x81,0x0a,0x81,0x0d,0x81,0x12,0x81,0x07,0x81,0x05,0x81, +0x0f,0x81,0x07,0x82,0x06,0x81,0x0b,0x81,0x08,0x81,0x07,0x81,0x0f,0x81,0x24, +0x82,0x22,0x82,0x0b,0x81,0x0a,0x81,0x02,0x81,0x03,0x81,0x08,0x81,0x04,0x81, +0x08,0x81,0x06,0x81,0x05,0x81,0x10,0x81,0x08,0x81,0x06,0x81,0x03,0x81,0x0a, +0x81,0x03,0x81,0x07,0x81,0x05,0x87,0x04,0x81,0x06,0x81,0x0a,0x81,0x0a,0x81, +0x07,0x81,0x07,0x81,0x05,0x81,0x09,0x81,0x0c,0x81,0x03,0x81,0x01,0x81,0x02, +0x81,0x05,0x81,0x04,0x81,0x02,0x81,0x05,0x81,0x0a,0x81,0x06,0x87,0x05,0x81, +0x0a,0x81,0x05,0x81,0x04,0x81,0x10,0x81,0x0a,0x81,0x0a,0x81,0x07,0x81,0x08, +0x81,0x04,0x81,0x07,0x81,0x02,0x81,0x01,0x81,0x02,0x81,0x09,0x81,0x02,0x81, +0x0c,0x81,0x0d,0x81,0x0f,0x81,0x0e,0x81,0x0e,0x81,0x39,0x87,0x06,0x81,0x08, +0x81,0x05,0x81,0x0d,0x81,0x09,0x81,0x05,0x8a,0x08,0x81,0x0a,0x81,0x08,0x81, +0x07,0x81,0x06,0x81,0x0a,0x81,0x10,0x81,0x0a,0x83,0x0e,0x81,0x0a,0x81,0x04, +0x81,0x04,0x81,0x05,0x81,0x06,0x81,0x05,0x81,0x09,0x81,0x05,0x81,0x08,0x81, +0x04,0x81,0x08,0x81,0x09,0x81,0x0d,0x85,0x0a,0x81,0x0d,0x81,0x06,0x81,0x08, +0x81,0x04,0x81,0x07,0x81,0x02,0x81,0x01,0x81,0x02,0x81,0x0a,0x82,0x0a,0x81, +0x05,0x81,0x0c,0x81,0x0b,0x82,0x0f,0x81,0x0f,0x82,0x08,0x81,0x04,0x81,0x02, +0x81,0x7f,0x7f,0x7f,0x7f,0x18,0x82,0x0e,0x81,0x0b,0x89,0x0b,0x81,0x12,0x81, +0x08,0x83,0x05,0x81,0x06,0x85,0x06,0x81,0x03,0x81,0x08,0x81,0x12,0x82,0x03, +0x82,0x11,0x81,0x13,0x81,0x04,0x81,0x02,0x81,0x04,0x81,0x25,0x81,0x2b,0x81, +0x0c,0x81,0x06,0x81,0x0a,0x81,0x02,0x81,0x1b,0x81,0x29,0x82,0x03,0x82,0x04, +0x81,0x06,0x81,0x02,0x81,0x04,0x81,0x06,0x86,0x08,0x81,0x1b,0x81,0x04,0x81, +0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04, +0x81,0x09,0x81,0x04,0x81,0x08,0x81,0x03,0x81,0x02,0x81,0x05,0x81,0x11,0x81, +0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0c, +0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x08,0x81,0x04,0x81,0x04,0x81, +0x02,0x81,0x05,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03, +0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x08,0x82,0x08,0x81,0x03,0x81,0x06,0x81, +0x04,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x0a,0x81,0x0c,0x81,0x07,0x81,0x05,0x81,0x07,0x81,0x07,0x87,0x08,0x87, +0x08,0x87,0x08,0x87,0x08,0x87,0x08,0x87,0x06,0x86,0x04,0x81,0x04,0x81,0x0e, +0x8a,0x05,0x8a,0x05,0x8a,0x05,0x8a,0x09,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81, +0x0a,0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x05,0x81,0x09,0x81,0x04,0x81,0x09, +0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x13,0x81, +0x04,0x82,0x03,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06, +0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x05,0x81,0x07,0x81,0x08,0x81,0x06,0x81, +0x05,0x81,0x19,0x81,0x1a,0x89,0x06,0x81,0x06,0x81,0x09,0x81,0x04,0x81,0x06, +0x81,0x03,0x81,0x02,0x81,0x1b,0x81,0x0d,0x81,0x1d,0x81,0x3a,0x81,0x0b,0x81, +0x08,0x81,0x0a,0x81,0x0c,0x81,0x13,0x81,0x07,0x88,0x0e,0x81,0x07,0x82,0x06, +0x81,0x0a,0x81,0x09,0x81,0x07,0x81,0x0f,0x81,0x26,0x81,0x0a,0x8c,0x0a,0x81, +0x0d,0x81,0x0a,0x81,0x03,0x85,0x06,0x88,0x07,0x81,0x07,0x81,0x04,0x81,0x10, +0x81,0x08,0x81,0x06,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x07,0x81,0x09,0x81, +0x06,0x81,0x06,0x81,0x0a,0x81,0x0a,0x81,0x07,0x81,0x07,0x81,0x05,0x81,0x09, +0x81,0x06,0x81,0x05,0x81,0x04,0x81,0x03,0x81,0x05,0x81,0x04,0x81,0x02,0x81, +0x05,0x81,0x0a,0x81,0x06,0x81,0x0b,0x81,0x0a,0x81,0x05,0x81,0x05,0x81,0x10, +0x81,0x09,0x81,0x0a,0x81,0x07,0x81,0x09,0x81,0x02,0x81,0x08,0x81,0x01,0x81, +0x03,0x81,0x01,0x81,0x08,0x81,0x04,0x81,0x0b,0x81,0x0d,0x81,0x04,0x81,0x0a, +0x81,0x0f,0x81,0x0d,0x81,0x38,0x81,0x06,0x81,0x06,0x81,0x08,0x81,0x05,0x81, +0x0d,0x81,0x09,0x81,0x05,0x81,0x11,0x81,0x0a,0x81,0x08,0x81,0x07,0x81,0x06, +0x81,0x0a,0x81,0x10,0x81,0x0a,0x81,0x02,0x81,0x0d,0x81,0x0a,0x81,0x04,0x81, +0x04,0x81,0x05,0x81,0x06,0x81,0x05,0x81,0x09,0x81,0x05,0x81,0x08,0x81,0x04, +0x81,0x08,0x81,0x09,0x81,0x12,0x81,0x09,0x81,0x0d,0x81,0x06,0x81,0x09,0x81, +0x02,0x81,0x08,0x81,0x02,0x81,0x01,0x81,0x02,0x81,0x09,0x81,0x02,0x81,0x0a, +0x81,0x03,0x81,0x0c,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x10,0x82,0x7f,0x7f, +0x7f,0x7f,0x0d,0x81,0x0c,0x82,0x04,0x81,0x08,0x81,0x0c,0x81,0x12,0x81,0x13, +0x81,0x09,0x83,0x03,0x81,0x0b,0x81,0x05,0x82,0x03,0x81,0x03,0x81,0x02,0x82, +0x11,0x82,0x03,0x82,0x12,0x81,0x13,0x82,0x03,0x81,0x03,0x81,0x02,0x82,0x25, +0x81,0x2b,0x81,0x04,0x81,0x07,0x81,0x06,0x81,0x0a,0x81,0x02,0x81,0x1a,0x81, +0x2b,0x82,0x03,0x82,0x03,0x81,0x06,0x81,0x02,0x81,0x04,0x81,0x06,0x81,0x0d, +0x81,0x0f,0x81,0x0a,0x88,0x07,0x88,0x07,0x88,0x07,0x88,0x07,0x88,0x07,0x88, +0x07,0x85,0x02,0x81,0x05,0x81,0x11,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a, +0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0c,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81, +0x0b,0x81,0x08,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x05,0x81,0x0a,0x81,0x03, +0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81,0x03,0x81,0x0a,0x81, +0x07,0x81,0x02,0x81,0x07,0x81,0x03,0x81,0x06,0x81,0x04,0x81,0x07,0x81,0x06, +0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x0a,0x81,0x0c,0x81, +0x05,0x82,0x06,0x81,0x07,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81, +0x04,0x82,0x05,0x86,0x04,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x12, +0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0a,0x81,0x08,0x81,0x06,0x81,0x06,0x81, +0x05,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09, +0x81,0x04,0x81,0x09,0x81,0x13,0x81,0x03,0x81,0x05,0x81,0x06,0x81,0x06,0x81, +0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x08,0x81,0x03, +0x81,0x08,0x81,0x08,0x81,0x07,0x81,0x03,0x81,0x37,0x81,0x03,0x81,0x08,0x82, +0x04,0x82,0x09,0x81,0x04,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x1b,0x81,0x0d, +0x81,0x1d,0x81,0x0c,0x83,0x2a,0x81,0x0c,0x81,0x08,0x81,0x0a,0x81,0x0b,0x81, +0x14,0x81,0x0d,0x81,0x0f,0x81,0x08,0x81,0x06,0x81,0x0a,0x81,0x09,0x81,0x07, +0x81,0x0e,0x81,0x18,0x83,0x0d,0x82,0x1c,0x82,0x19,0x81,0x0e,0x81,0x06,0x81, +0x07,0x81,0x07,0x81,0x05,0x81,0x0f,0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x07, +0x81,0x0b,0x81,0x09,0x81,0x06,0x81,0x06,0x81,0x0a,0x81,0x0a,0x81,0x07,0x81, +0x07,0x81,0x06,0x81,0x08,0x81,0x06,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x05, +0x81,0x01,0x81,0x06,0x81,0x08,0x81,0x07,0x81,0x0c,0x81,0x08,0x81,0x06,0x81, +0x06,0x81,0x06,0x81,0x08,0x81,0x09,0x81,0x0a,0x81,0x07,0x81,0x09,0x81,0x02, +0x81,0x08,0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x07,0x81,0x05,0x81,0x0b,0x81, +0x0c,0x81,0x05,0x81,0x0a,0x81,0x0f,0x81,0x0d,0x81,0x37,0x81,0x07,0x81,0x06, +0x81,0x08,0x81,0x05,0x81,0x0d,0x81,0x09,0x81,0x05,0x81,0x11,0x81,0x0a,0x81, +0x08,0x81,0x07,0x81,0x06,0x81,0x0a,0x81,0x10,0x81,0x0a,0x81,0x03,0x81,0x0c, +0x81,0x0a,0x81,0x04,0x81,0x04,0x81,0x05,0x81,0x06,0x81,0x05,0x81,0x09,0x81, +0x05,0x81,0x08,0x81,0x04,0x81,0x08,0x81,0x09,0x81,0x13,0x81,0x08,0x81,0x0d, +0x81,0x06,0x81,0x09,0x81,0x02,0x81,0x08,0x81,0x01,0x81,0x03,0x81,0x01,0x81, +0x08,0x81,0x04,0x81,0x09,0x81,0x03,0x81,0x0b,0x81,0x0f,0x81,0x0e,0x81,0x0e, +0x81,0x7f,0x7f,0x7f,0x7f,0x1f,0x81,0x0d,0x85,0x09,0x81,0x0c,0x81,0x0e,0x89, +0x06,0x81,0x08,0x81,0x0b,0x84,0x0d,0x81,0x05,0x81,0x04,0x83,0x03,0x81,0x13, +0x82,0x03,0x82,0x11,0x81,0x14,0x81,0x02,0x83,0x02,0x82,0x01,0x81,0x26,0x81, +0x2a,0x81,0x05,0x81,0x07,0x81,0x06,0x81,0x0a,0x81,0x02,0x81,0x19,0x81,0x2b, +0x82,0x03,0x82,0x05,0x81,0x05,0x81,0x07,0x81,0x06,0x81,0x0d,0x81,0x0f,0x81, +0x0a,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06, +0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x03,0x81,0x09,0x81, +0x10,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06, +0x81,0x09,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x07,0x82,0x04,0x81, +0x05,0x81,0x01,0x81,0x06,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08, +0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x07,0x81,0x04,0x81,0x07,0x81, +0x01,0x81,0x06,0x81,0x05,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x07,0x81,0x0a,0x81,0x0c,0x87,0x07,0x81,0x07,0x81,0x05,0x81, +0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06, +0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x04,0x81,0x06,0x81,0x09,0x81,0x0e,0x81, +0x0e,0x81,0x0e,0x81,0x0e,0x81,0x12,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0a, +0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x05,0x81,0x09,0x81,0x04,0x81,0x09,0x81, +0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x04,0x81,0x09,0x81,0x13,0x81,0x02, +0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81, +0x07,0x81,0x06,0x81,0x08,0x81,0x03,0x81,0x08,0x81,0x08,0x81,0x07,0x81,0x03, +0x81,0x37,0x81,0x03,0x81,0x08,0x81,0x01,0x85,0x0a,0x81,0x04,0x81,0x06,0x81, +0x05,0x81,0x1c,0x81,0x0d,0x81,0x1d,0x81,0x0c,0x82,0x1d,0x83,0x0b,0x81,0x0d, +0x81,0x06,0x81,0x0b,0x81,0x0a,0x81,0x15,0x81,0x0d,0x81,0x07,0x81,0x07,0x81, +0x08,0x81,0x06,0x81,0x0a,0x81,0x09,0x81,0x07,0x81,0x0d,0x81,0x0a,0x83,0x0c, +0x82,0x10,0x82,0x18,0x82,0x1b,0x81,0x0e,0x81,0x06,0x81,0x07,0x81,0x07,0x81, +0x05,0x81,0x08,0x81,0x06,0x81,0x07,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x0c, +0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x0a,0x81,0x0a,0x81,0x07,0x81,0x07,0x81, +0x06,0x81,0x08,0x81,0x06,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x05,0x81,0x01, +0x81,0x06,0x81,0x08,0x81,0x07,0x81,0x0c,0x81,0x08,0x81,0x06,0x81,0x06,0x81, +0x06,0x81,0x08,0x81,0x09,0x81,0x0a,0x81,0x07,0x81,0x09,0x81,0x02,0x81,0x08, +0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x07,0x81,0x06,0x81,0x0a,0x81,0x0b,0x81, +0x06,0x81,0x0a,0x81,0x10,0x81,0x0c,0x81,0x37,0x81,0x07,0x81,0x06,0x82,0x06, +0x81,0x07,0x81,0x07,0x81,0x05,0x81,0x07,0x82,0x06,0x81,0x10,0x81,0x0b,0x81, +0x06,0x82,0x07,0x81,0x06,0x81,0x0a,0x81,0x10,0x81,0x0a,0x81,0x04,0x81,0x0b, +0x81,0x0a,0x81,0x04,0x81,0x04,0x81,0x05,0x81,0x06,0x81,0x06,0x81,0x07,0x81, +0x06,0x82,0x06,0x81,0x06,0x81,0x06,0x82,0x09,0x81,0x0b,0x81,0x07,0x81,0x08, +0x81,0x0d,0x81,0x06,0x81,0x09,0x81,0x02,0x81,0x08,0x81,0x01,0x81,0x03,0x81, +0x01,0x81,0x07,0x81,0x06,0x81,0x09,0x81,0x01,0x81,0x0b,0x81,0x06,0x81,0x09, +0x81,0x0e,0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x1f,0x81,0x0f,0x81,0x0b,0x81, +0x05,0x81,0x07,0x81,0x08,0x81,0x08,0x81,0x0a,0x81,0x08,0x81,0x0d,0x81,0x06, +0x81,0x07,0x81,0x06,0x81,0x08,0x81,0x15,0x82,0x03,0x82,0x10,0x81,0x15,0x81, +0x08,0x81,0x51,0x81,0x06,0x81,0x07,0x81,0x05,0x82,0x0a,0x81,0x02,0x81,0x18, +0x81,0x06,0x81,0x24,0x82,0x03,0x82,0x06,0x81,0x05,0x81,0x05,0x81,0x02,0x81, +0x05,0x81,0x0d,0x81,0x0d,0x82,0x0b,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81, +0x06,0x81,0x04,0x81,0x05,0x81,0x03,0x81,0x08,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x09,0x81,0x0e,0x81, +0x0e,0x81,0x0e,0x81,0x0b,0x81,0x07,0x81,0x05,0x81,0x05,0x81,0x01,0x81,0x06, +0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81, +0x05,0x81,0x08,0x81,0x14,0x82,0x06,0x82,0x05,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x0a,0x81,0x0c,0x81,0x0d,0x81, +0x02,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x04,0x81, +0x06,0x81,0x0a,0x81,0x07,0x81,0x06,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x11, +0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x81,0x06,0x81,0x07,0x81,0x06,0x81, +0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x07,0x81,0x09,0x82,0x0a,0x82,0x06,0x81,0x07,0x81,0x06,0x81, +0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x09,0x81,0x01, +0x81,0x09,0x82,0x06,0x81,0x09,0x81,0x01,0x81,0x1a,0x83,0x1a,0x81,0x03,0x81, +0x0d,0x81,0x0c,0x81,0x04,0x81,0x07,0x81,0x03,0x82,0x1d,0x81,0x0b,0x81,0x1e, +0x81,0x0c,0x82,0x1d,0x83,0x0a,0x81,0x0f,0x81,0x04,0x81,0x0c,0x81,0x09,0x81, +0x07,0x81,0x06,0x81,0x06,0x81,0x0e,0x81,0x08,0x81,0x05,0x81,0x0a,0x81,0x04, +0x81,0x0a,0x81,0x0b,0x81,0x05,0x81,0x0d,0x81,0x0b,0x83,0x0c,0x82,0x12,0x82, +0x14,0x82,0x11,0x83,0x09,0x81,0x0d,0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x07, +0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x08,0x81,0x06,0x81,0x07,0x81,0x0d,0x81, +0x07,0x81,0x06,0x81,0x06,0x81,0x0a,0x81,0x0b,0x81,0x05,0x81,0x08,0x81,0x07, +0x81,0x07,0x81,0x06,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x06,0x82,0x07,0x82, +0x04,0x82,0x08,0x81,0x0d,0x82,0x04,0x82,0x07,0x81,0x07,0x81,0x05,0x83,0x05, +0x81,0x0a,0x81,0x0b,0x81,0x05,0x81,0x0b,0x82,0x09,0x81,0x01,0x81,0x03,0x81, +0x01,0x81,0x06,0x81,0x08,0x81,0x09,0x81,0x0a,0x81,0x07,0x81,0x0a,0x81,0x10, +0x81,0x0c,0x81,0x37,0x81,0x05,0x83,0x06,0x81,0x01,0x81,0x04,0x81,0x09,0x81, +0x05,0x81,0x07,0x81,0x05,0x81,0x01,0x81,0x07,0x81,0x06,0x81,0x08,0x81,0x0c, +0x81,0x04,0x81,0x01,0x81,0x07,0x81,0x06,0x81,0x0a,0x81,0x10,0x81,0x0a,0x81, +0x05,0x81,0x0a,0x81,0x0a,0x81,0x04,0x81,0x04,0x81,0x05,0x81,0x06,0x81,0x07, +0x81,0x05,0x81,0x07,0x81,0x01,0x81,0x04,0x81,0x07,0x81,0x05,0x81,0x01,0x81, +0x09,0x81,0x0b,0x82,0x05,0x81,0x09,0x81,0x05,0x82,0x06,0x81,0x05,0x82,0x0a, +0x82,0x09,0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x06,0x81,0x08,0x81,0x08,0x81, +0x01,0x81,0x0a,0x81,0x07,0x81,0x09,0x81,0x0e,0x81,0x0e,0x81,0x7f,0x7f,0x7f, +0x7f,0x1f,0x81,0x0f,0x81,0x0a,0x81,0x06,0x81,0x08,0x81,0x06,0x81,0x09,0x81, +0x0a,0x83,0x05,0x81,0x07,0x81,0x06,0x81,0x06,0x82,0x05,0x81,0x08,0x82,0x04, +0x82,0x17,0x82,0x03,0x82,0x0f,0x81,0x16,0x82,0x04,0x82,0x51,0x81,0x07,0x81, +0x07,0x82,0x03,0x83,0x0a,0x81,0x02,0x81,0x17,0x81,0x07,0x81,0x23,0x82,0x03, +0x82,0x08,0x82,0x03,0x81,0x05,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x03,0x81, +0x08,0x81,0x0c,0x81,0x0c,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08, +0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81, +0x04,0x81,0x05,0x81,0x04,0x81,0x06,0x81,0x08,0x81,0x06,0x81,0x07,0x81,0x06, +0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x09,0x81,0x0e,0x81,0x0e,0x81, +0x0e,0x81,0x0b,0x81,0x06,0x81,0x06,0x81,0x06,0x82,0x07,0x82,0x04,0x82,0x07, +0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82, +0x15,0x83,0x04,0x82,0x07,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05, +0x81,0x08,0x81,0x05,0x81,0x0b,0x81,0x0c,0x81,0x0d,0x81,0x02,0x81,0x03,0x81, +0x06,0x81,0x05,0x83,0x06,0x81,0x05,0x83,0x06,0x81,0x05,0x83,0x06,0x81,0x05, +0x83,0x06,0x81,0x05,0x83,0x06,0x81,0x05,0x83,0x05,0x81,0x04,0x83,0x03,0x81, +0x06,0x81,0x05,0x81,0x08,0x81,0x06,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x06, +0x81,0x07,0x81,0x06,0x81,0x09,0x81,0x0e,0x81,0x0e,0x81,0x0e,0x81,0x0b,0x82, +0x04,0x82,0x07,0x81,0x06,0x81,0x07,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08, +0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x08,0x81,0x05,0x81,0x0a,0x82,0x0a,0x82, +0x05,0x81,0x08,0x81,0x05,0x82,0x07,0x81,0x05,0x82,0x07,0x81,0x05,0x82,0x07, +0x81,0x05,0x82,0x09,0x81,0x01,0x81,0x09,0x81,0x01,0x81,0x04,0x82,0x09,0x81, +0x01,0x81,0x1a,0x83,0x1a,0x81,0x03,0x81,0x0d,0x81,0x0d,0x84,0x09,0x83,0x02, +0x82,0x1b,0x81,0x0b,0x81,0x2a,0x82,0x1e,0x83,0x0a,0x81,0x10,0x84,0x09,0x89, +0x05,0x89,0x07,0x86,0x0d,0x84,0x08,0x85,0x0c,0x84,0x0b,0x81,0x0c,0x85,0x09, +0x85,0x0c,0x83,0x0b,0x82,0x3c,0x83,0x0a,0x81,0x04,0x81,0x05,0x85,0x04,0x85, +0x02,0x89,0x09,0x86,0x06,0x89,0x07,0x8a,0x05,0x87,0x0a,0x87,0x05,0x85,0x02, +0x85,0x04,0x89,0x08,0x85,0x07,0x85,0x05,0x83,0x03,0x8a,0x03,0x85,0x04,0x85, +0x01,0x85,0x04,0x82,0x09,0x84,0x08,0x87,0x0a,0x85,0x07,0x85,0x05,0x83,0x03, +0x81,0x02,0x85,0x09,0x85,0x0a,0x85,0x0c,0x82,0x0a,0x81,0x05,0x81,0x06,0x84, +0x04,0x84,0x06,0x85,0x08,0x89,0x0a,0x81,0x11,0x81,0x0b,0x81,0x38,0x85,0x02, +0x83,0x02,0x83,0x02,0x84,0x0b,0x85,0x09,0x85,0x02,0x83,0x06,0x86,0x07,0x88, +0x08,0x84,0x02,0x81,0x05,0x85,0x02,0x85,0x04,0x89,0x0c,0x81,0x08,0x83,0x03, +0x85,0x04,0x89,0x04,0x85,0x02,0x83,0x02,0x82,0x02,0x85,0x02,0x85,0x06,0x85, +0x08,0x81,0x02,0x84,0x09,0x85,0x02,0x81,0x07,0x88,0x06,0x81,0x01,0x85,0x0b, +0x85,0x09,0x85,0x01,0x83,0x08,0x82,0x0a,0x81,0x05,0x81,0x06,0x84,0x04,0x84, +0x08,0x81,0x0b,0x89,0x09,0x81,0x0e,0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x1f, +0x81,0x0f,0x81,0x08,0x89,0x0a,0x86,0x07,0x87,0x07,0x81,0x02,0x85,0x08,0x81, +0x05,0x82,0x06,0x81,0x01,0x85,0x0a,0x86,0x19,0x82,0x03,0x82,0x26,0x86,0x24, +0x8b,0x23,0x89,0x07,0x85,0x02,0x83,0x08,0x81,0x02,0x81,0x17,0x89,0x22,0x82, +0x03,0x82,0x0b,0x8a,0x03,0x84,0x03,0x83,0x07,0x85,0x09,0x81,0x0b,0x85,0x04, +0x85,0x01,0x85,0x04,0x85,0x01,0x85,0x04,0x85,0x01,0x85,0x04,0x85,0x01,0x85, +0x04,0x85,0x01,0x85,0x04,0x85,0x01,0x8e,0x05,0x86,0x07,0x8a,0x05,0x8a,0x05, +0x8a,0x05,0x8a,0x05,0x89,0x06,0x89,0x06,0x89,0x06,0x89,0x05,0x89,0x05,0x85, +0x04,0x82,0x09,0x84,0x0b,0x84,0x0b,0x84,0x0b,0x84,0x0b,0x84,0x17,0x81,0x02, +0x84,0x0a,0x85,0x0a,0x85,0x0a,0x85,0x0a,0x85,0x0a,0x85,0x08,0x87,0x07,0x85, +0x01,0x83,0x08,0x85,0x02,0x83,0x05,0x85,0x02,0x83,0x05,0x85,0x02,0x83,0x05, +0x85,0x02,0x83,0x05,0x85,0x02,0x83,0x05,0x85,0x02,0x83,0x04,0x84,0x01,0x81, +0x01,0x83,0x08,0x85,0x0a,0x86,0x09,0x86,0x09,0x86,0x09,0x86,0x06,0x89,0x06, +0x89,0x06,0x89,0x06,0x89,0x09,0x84,0x07,0x85,0x02,0x85,0x06,0x85,0x0a,0x85, +0x0a,0x85,0x0a,0x85,0x0a,0x85,0x16,0x81,0x02,0x85,0x0a,0x85,0x01,0x83,0x06, +0x85,0x01,0x83,0x06,0x85,0x01,0x83,0x06,0x85,0x01,0x83,0x08,0x81,0x0a,0x81, +0x02,0x84,0x0c,0x81,0x38,0x81,0x03,0x81,0x0d,0x81,0x3c,0x81,0x0b,0x81,0x2a, +0x82,0x2a,0x81,0x7f,0x36,0x82,0x4a,0x84,0x7f,0x7c,0x81,0x7f,0x17,0x81,0x11, +0x81,0x0b,0x81,0x7f,0x1a,0x81,0x2a,0x81,0x53,0x81,0x16,0x81,0x73,0x81,0x1d, +0x81,0x0e,0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x1f,0x81,0x56,0x87,0x7f,0x4d, +0x81,0x0e,0x89,0x7e,0x81,0x7b,0x81,0x7f,0x79,0x81,0x7f,0x67,0x81,0x7f,0x7f, +0x4a,0x81,0x0a,0x81,0x12,0x81,0x4a,0x81,0x3d,0x81,0x09,0x81,0x2b,0x81,0x2b, +0x81,0x7f,0x36,0x81,0x7f,0x7f,0x4b,0x85,0x03,0x81,0x7f,0x10,0x81,0x12,0x81, +0x0a,0x81,0x7f,0x1a,0x81,0x2a,0x81,0x53,0x81,0x16,0x81,0x72,0x81,0x1e,0x81, +0x0e,0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x1f,0x81,0x7f,0x7f,0x2b,0x81,0x7f, +0x16,0x81,0x06,0x81,0x74,0x82,0x7f,0x7f,0x7f,0x61,0x82,0x7f,0x7f,0x48,0x81, +0x0b,0x81,0x11,0x81,0x7f,0x0a,0x81,0x09,0x81,0x2b,0x81,0x7f,0x7f,0x7f,0x7f, +0x2e,0x82,0x04,0x83,0x7f,0x11,0x84,0x17,0x84,0x7f,0x19,0x81,0x2a,0x81,0x54, +0x81,0x16,0x81,0x72,0x81,0x1f,0x82,0x0c,0x81,0x0c,0x82,0x7f,0x7f,0x7f,0x7f, +0x20,0x81,0x7f,0x7f,0x2b,0x81,0x7f,0x17,0x81,0x05,0x81,0x75,0x81,0x7f,0x7f, +0x7f,0x62,0x81,0x7f,0x7f,0x48,0x81,0x0b,0x81,0x11,0x81,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x3d,0x85,0x26,0x85,0x53,0x85,0x12,0x85,0x6c,0x86,0x7f, +0x7f,0x7f,0x7f,0x5b,0x81,0x7f,0x7f,0x2b,0x81,0x7f,0x18,0x85,0x73,0x84,0x7f, +0x7f,0x7f,0x5f,0x84,0x7f,0x7f,0x44,0x86,0x08,0x85,0x0b,0x86,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x79,0x8e,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, +0x7f,0x7f,0x7f,0x7f,0x7f,0x73, +0x00, + } +}; diff --git a/minui/events.c b/minui/events.c index 2918afaa8..42fb1cdc8 100644 --- a/minui/events.c +++ b/minui/events.c @@ -23,6 +23,7 @@ #include #include "minui.h" +#include "cutils/log.h" #define MAX_DEVICES 16 #define MAX_MISC_FDS 16 @@ -69,7 +70,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; } @@ -80,7 +81,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; } } diff --git a/minui/font_10x18.h b/minui/font_10x18.h index 7f96465cc..29d705344 100644 --- a/minui/font_10x18.h +++ b/minui/font_10x18.h @@ -3,7 +3,7 @@ struct { unsigned height; unsigned cwidth; unsigned cheight; - unsigned char rundata[]; + unsigned char rundata[2973]; } font = { .width = 960, .height = 18, diff --git a/minui/font_7x16.h b/minui/font_7x16.h new file mode 100644 index 000000000..31c94fce2 --- /dev/null +++ b/minui/font_7x16.h @@ -0,0 +1,15 @@ +struct { + unsigned width; + unsigned height; + unsigned cwidth; + unsigned cheight; + unsigned char rundata[]; +} font = { + .width = 668, + .height = 16, + .cwidth = 7, + .cheight = 16, + .rundata = { +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7e,0x82,0x03,0x82,0x7f,0x7f,0x5f,0x82,0x0b,0x82,0x14,0x81,0x0b,0x81,0x11,0x81,0x0c,0x82,0x09,0x81,0x08,0x81,0x07,0x81,0x03,0x81,0x06,0x83,0x68,0x83,0x04,0x81,0x04,0x83,0x17,0x81,0x05,0x81,0x01,0x81,0x0c,0x81,0x04,0x82,0x07,0x83,0x04,0x81,0x07,0x81,0x05,0x81,0x06,0x81,0x25,0x81,0x02,0x84,0x02,0x83,0x05,0x84,0x03,0x84,0x05,0x82,0x02,0x85,0x04,0x83,0x02,0x86,0x02,0x84,0x03,0x84,0x27,0x83,0x0b,0x82,0x03,0x85,0x04,0x83,0x02,0x84,0x03,0x86,0x01,0x86,0x03,0x83,0x02,0x81,0x04,0x81,0x01,0x85,0x04,0x83,0x02,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x82,0x03,0x81,0x02,0x84,0x02,0x85,0x03,0x84,0x02,0x85,0x03,0x84,0x01,0x87,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x82,0x05,0x81,0x01,0x81,0x04,0x82,0x05,0x81,0x01,0x86,0x03,0x81,0x04,0x81,0x08,0x81,0x05,0x82,0x0e,0x81,0x0a,0x81,0x11,0x81,0x0b,0x81,0x0b,0x81,0x14,0x81,0x08,0x81,0x37,0x81,0x30,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x05,0x81,0x01,0x81,0x05,0x81,0x01,0x81,0x03,0x83,0x02,0x81,0x02,0x81,0x05,0x81,0x07,0x81,0x07,0x81,0x05,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x22,0x81,0x03,0x81,0x02,0x81,0x04,0x81,0x04,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x04,0x82,0x02,0x81,0x07,0x81,0x03,0x81,0x05,0x82,0x01,0x81,0x04,0x81,0x01,0x82,0x02,0x81,0x26,0x81,0x03,0x81,0x03,0x83,0x04,0x82,0x03,0x81,0x04,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x82,0x02,0x82,0x01,0x82,0x03,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x01,0x81,0x04,0x82,0x02,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x06,0x82,0x03,0x81,0x05,0x81,0x07,0x81,0x04,0x81,0x02,0x81,0x18,0x81,0x11,0x81,0x0b,0x81,0x0b,0x81,0x14,0x81,0x08,0x81,0x37,0x81,0x30,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x05,0x81,0x01,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x05,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x04,0x83,0x05,0x81,0x1d,0x81,0x02,0x81,0x04,0x81,0x03,0x81,0x09,0x81,0x06,0x81,0x03,0x81,0x01,0x81,0x02,0x81,0x06,0x81,0x0a,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x14,0x81,0x08,0x81,0x0b,0x81,0x02,0x81,0x02,0x82,0x03,0x82,0x03,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x06,0x82,0x02,0x82,0x01,0x81,0x01,0x81,0x02,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x08,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x07,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x03,0x81,0x04,0x81,0x11,0x83,0x03,0x84,0x04,0x83,0x04,0x84,0x03,0x83,0x03,0x85,0x03,0x84,0x02,0x81,0x01,0x82,0x03,0x83,0x05,0x83,0x03,0x81,0x03,0x81,0x04,0x81,0x04,0x85,0x02,0x81,0x01,0x82,0x04,0x83,0x03,0x84,0x04,0x84,0x03,0x84,0x03,0x83,0x03,0x85,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x05,0x81,0x01,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x85,0x04,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x0b,0x86,0x01,0x81,0x01,0x81,0x04,0x82,0x02,0x81,0x03,0x82,0x0d,0x81,0x07,0x81,0x04,0x83,0x05,0x81,0x1c,0x81,0x03,0x81,0x04,0x81,0x03,0x81,0x09,0x81,0x06,0x81,0x02,0x82,0x01,0x81,0x02,0x85,0x02,0x81,0x01,0x83,0x06,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x06,0x81,0x06,0x83,0x0a,0x83,0x06,0x82,0x02,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x01,0x81,0x04,0x81,0x06,0x81,0x01,0x82,0x01,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x82,0x07,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x03,0x82,0x04,0x81,0x01,0x81,0x06,0x81,0x05,0x81,0x06,0x81,0x06,0x81,0x19,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x82,0x05,0x81,0x03,0x81,0x02,0x82,0x02,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x82,0x02,0x81,0x04,0x81,0x07,0x81,0x03,0x81,0x02,0x81,0x05,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x82,0x02,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x82,0x02,0x81,0x01,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x05,0x81,0x02,0x81,0x01,0x81,0x03,0x81,0x03,0x81,0x06,0x81,0x04,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x0c,0x81,0x01,0x81,0x03,0x83,0x06,0x82,0x04,0x82,0x0d,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x1c,0x81,0x03,0x81,0x02,0x81,0x01,0x81,0x03,0x81,0x08,0x81,0x04,0x83,0x03,0x81,0x02,0x81,0x06,0x82,0x01,0x82,0x02,0x82,0x04,0x81,0x04,0x84,0x02,0x81,0x03,0x82,0x03,0x81,0x06,0x81,0x04,0x82,0x05,0x86,0x05,0x82,0x03,0x82,0x03,0x81,0x02,0x83,0x02,0x81,0x02,0x81,0x02,0x85,0x02,0x81,0x06,0x81,0x04,0x81,0x01,0x86,0x01,0x86,0x01,0x81,0x03,0x82,0x01,0x86,0x03,0x81,0x08,0x81,0x02,0x83,0x04,0x81,0x06,0x81,0x01,0x82,0x01,0x81,0x01,0x81,0x01,0x82,0x01,0x81,0x01,0x81,0x04,0x81,0x01,0x85,0x02,0x81,0x04,0x81,0x01,0x85,0x03,0x84,0x04,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x03,0x82,0x05,0x81,0x06,0x82,0x05,0x81,0x06,0x81,0x06,0x81,0x1d,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x81,0x05,0x81,0x08,0x81,0x04,0x81,0x03,0x81,0x03,0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x03,0x82,0x07,0x81,0x07,0x82,0x02,0x83,0x10,0x81,0x0c,0x81,0x01,0x81,0x05,0x83,0x02,0x82,0x01,0x82,0x02,0x81,0x02,0x81,0x01,0x81,0x0a,0x81,0x07,0x81,0x05,0x81,0x03,0x87,0x09,0x83,0x0c,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x07,0x81,0x08,0x81,0x01,0x81,0x03,0x81,0x07,0x81,0x01,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x02,0x83,0x01,0x81,0x0f,0x82,0x10,0x82,0x03,0x81,0x04,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x06,0x81,0x01,0x82,0x01,0x81,0x01,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x03,0x81,0x07,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x03,0x82,0x05,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x05,0x81,0x1a,0x84,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x03,0x81,0x02,0x85,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x03,0x82,0x07,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x81,0x06,0x83,0x05,0x81,0x04,0x81,0x03,0x81,0x03,0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x05,0x81,0x01,0x81,0x05,0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x07,0x83,0x18,0x86,0x04,0x81,0x01,0x81,0x04,0x81,0x02,0x81,0x01,0x81,0x02,0x83,0x0a,0x81,0x07,0x81,0x0c,0x81,0x1b,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x06,0x81,0x09,0x81,0x01,0x86,0x06,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x06,0x81,0x11,0x83,0x02,0x86,0x02,0x83,0x0a,0x81,0x01,0x81,0x02,0x81,0x02,0x84,0x02,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x02,0x82,0x02,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x06,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x82,0x03,0x82,0x01,0x82,0x03,0x81,0x02,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x07,0x81,0x05,0x81,0x19,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x03,0x81,0x02,0x81,0x08,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x81,0x09,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x03,0x81,0x01,0x81,0x03,0x82,0x01,0x82,0x03,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x07,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x0b,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x02,0x81,0x01,0x82,0x02,0x81,0x0c,0x81,0x05,0x81,0x0d,0x81,0x06,0x81,0x0d,0x81,0x05,0x81,0x06,0x81,0x02,0x81,0x04,0x81,0x05,0x81,0x05,0x81,0x04,0x81,0x05,0x81,0x02,0x81,0x03,0x82,0x02,0x81,0x02,0x82,0x03,0x81,0x04,0x81,0x04,0x81,0x01,0x81,0x03,0x81,0x04,0x81,0x06,0x81,0x09,0x81,0x08,0x81,0x08,0x81,0x04,0x81,0x02,0x83,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x03,0x82,0x02,0x81,0x02,0x81,0x02,0x81,0x07,0x81,0x02,0x82,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x82,0x03,0x81,0x03,0x81,0x03,0x81,0x02,0x81,0x04,0x81,0x04,0x82,0x07,0x81,0x08,0x81,0x04,0x81,0x19,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x82,0x05,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x03,0x81,0x02,0x81,0x05,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x81,0x05,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x05,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x82,0x04,0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x0b,0x81,0x01,0x81,0x05,0x83,0x06,0x82,0x03,0x83,0x01,0x81,0x0b,0x81,0x05,0x81,0x0d,0x81,0x06,0x81,0x0d,0x81,0x05,0x81,0x06,0x84,0x02,0x85,0x02,0x86,0x02,0x84,0x06,0x81,0x03,0x84,0x03,0x84,0x03,0x81,0x06,0x84,0x03,0x83,0x05,0x81,0x06,0x81,0x1b,0x81,0x04,0x82,0x05,0x81,0x04,0x81,0x01,0x85,0x04,0x83,0x02,0x84,0x03,0x86,0x01,0x81,0x08,0x83,0x02,0x81,0x04,0x81,0x01,0x85,0x03,0x83,0x03,0x81,0x04,0x81,0x01,0x86,0x01,0x81,0x04,0x81,0x01,0x81,0x03,0x82,0x02,0x84,0x02,0x81,0x07,0x84,0x02,0x81,0x05,0x81,0x01,0x84,0x04,0x81,0x05,0x84,0x04,0x82,0x03,0x81,0x03,0x81,0x02,0x81,0x04,0x81,0x03,0x81,0x04,0x86,0x03,0x81,0x08,0x81,0x04,0x81,0x1a,0x84,0x02,0x84,0x04,0x83,0x04,0x84,0x03,0x83,0x05,0x81,0x05,0x84,0x02,0x81,0x03,0x81,0x02,0x85,0x05,0x81,0x03,0x81,0x03,0x81,0x05,0x82,0x02,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x03,0x83,0x03,0x84,0x04,0x84,0x03,0x81,0x06,0x83,0x05,0x83,0x03,0x84,0x04,0x81,0x05,0x81,0x01,0x81,0x03,0x81,0x03,0x81,0x04,0x81,0x04,0x85,0x04,0x81,0x06,0x81,0x06,0x81,0x2c,0x81,0x1c,0x82,0x03,0x82,0x13,0x81,0x13,0x81,0x54,0x81,0x22,0x81,0x79,0x81,0x43,0x82,0x08,0x81,0x02,0x82,0x47,0x81,0x13,0x81,0x26,0x81,0x0a,0x81,0x35,0x81,0x0d,0x83,0x04,0x81,0x04,0x83,0x2c,0x81,0x7f,0x44,0x83,0x76,0x81,0x7f,0x17,0x81,0x02,0x81,0x13,0x81,0x26,0x81,0x0a,0x81,0x34,0x81,0x15,0x81,0x7f,0x7f,0x7f,0x50,0x87,0x34,0x82,0x12,0x82,0x27,0x81,0x0a,0x81,0x33,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x4b,0x00,0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x00,0x49,0x44,0x41,0x54,0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x50,0x4c,0x54,0x45,0x00,0x00,0x00,0x00,0x62,0x4b,0x47,0x44,0x00,0x00,0x00,0x00,0x63,0x48,0x52,0x4d,0x00,0x00,0x00,0x00,0x67,0x41,0x4d,0x41,0x00,0x00,0x00,0x00,0x68,0x49,0x53,0x54,0x00,0x00,0x00,0x00,0x69,0x43,0x43,0x50,0x00,0x00,0x00,0x00,0x69,0x54,0x58,0x74,0x00,0x00,0x00,0x00,0x6f,0x46,0x46,0x73,0x00,0x00,0x00,0x00,0x70,0x43,0x41,0x4c,0x00,0x00,0x00,0x00,0x73,0x43,0x41,0x4c,0x00,0x00,0x00,0x00,0x70,0x48,0x59,0x73,0x00,0x00,0x00,0x00,0x73,0x42,0x49,0x54,0x00,0x00,0x00,0x00,0x73,0x50,0x4c,0x54,0x00,0x00,0x00,0x00,0x73,0x52,0x47,0x42,0x00,0x00,0x00,0x00,0x74,0x45,0x58,0x74,0x00,0x00,0x00,0x00,0x74,0x49,0x4d,0x45,0x00,0x00,0x00,0x00,0x74,0x52,0x4e,0x53,0x00,0x00,0x00,0x00,0x7a,0x54,0x58,0x74,0x00, + } +}; diff --git a/minui/graphics.c b/minui/graphics.c index de1cfdf3b..a578f6929 100644 --- a/minui/graphics.c +++ b/minui/graphics.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -30,7 +31,12 @@ #include +#ifdef BOARD_USE_CUSTOM_RECOVERY_FONT +#include BOARD_USE_CUSTOM_RECOVERY_FONT +#else #include "font_10x18.h" +#endif + #include "minui.h" #if defined(RECOVERY_BGRA) @@ -44,6 +50,10 @@ #define PIXEL_SIZE 2 #endif +#define NUM_BUFFERS 2 +#define ALIGN(x, align) (((x) + ((align)-1)) & ~((align)-1)) +#define MAX_DISPLAY_DIM 2048 + typedef struct { GGLSurface texture; unsigned cwidth; @@ -54,9 +64,13 @@ typedef struct { static GRFont *gr_font = 0; static GGLContext *gr_context = 0; static GGLSurface gr_font_texture; -static GGLSurface gr_framebuffer[2]; +static GGLSurface gr_framebuffer[NUM_BUFFERS]; static GGLSurface gr_mem_surface; static unsigned gr_active_fb = 0; +static unsigned double_buffering = 0; +static int overscan_percent = OVERSCAN_PERCENT; +static int overscan_offset_x = 0; +static int overscan_offset_y = 0; static int gr_fb_fd = -1; static int gr_vt_fd = -1; @@ -64,6 +78,18 @@ static int gr_vt_fd = -1; static struct fb_var_screeninfo vi; static struct fb_fix_screeninfo fi; +static bool has_overlay = false; +static int leftSplit = 0; +static int rightSplit = 0; + +bool target_has_overlay(char *version); +bool isTargetMdp5(void); +int free_ion_mem(void); +int alloc_ion_mem(unsigned int size); +int allocate_overlay(int fd, GGLSurface gr_fb[]); +int free_overlay(int fd); +int overlay_display_frame(int fd, GGLubyte* data, size_t size); + static int get_framebuffer(GGLSurface *fb) { int fd; @@ -81,71 +107,98 @@ static int get_framebuffer(GGLSurface *fb) return -1; } - vi.bits_per_pixel = PIXEL_SIZE * 8; - if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_BGRA_8888) { - vi.red.offset = 8; - vi.red.length = 8; - vi.green.offset = 16; - vi.green.length = 8; - vi.blue.offset = 24; - vi.blue.length = 8; - vi.transp.offset = 0; - vi.transp.length = 8; - } else if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_RGBX_8888) { - vi.red.offset = 24; - vi.red.length = 8; - vi.green.offset = 16; - vi.green.length = 8; - vi.blue.offset = 8; - vi.blue.length = 8; - vi.transp.offset = 0; - vi.transp.length = 8; - } else { /* RGB565*/ - vi.red.offset = 11; - vi.red.length = 5; - vi.green.offset = 5; - vi.green.length = 6; - vi.blue.offset = 0; - vi.blue.length = 5; - vi.transp.offset = 0; - vi.transp.length = 0; - } - if (ioctl(fd, FBIOPUT_VSCREENINFO, &vi) < 0) { - perror("failed to put fb0 info"); - close(fd); - return -1; - } - if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { perror("failed to get fb0 info"); close(fd); return -1; } - bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (bits == MAP_FAILED) { - perror("failed to mmap framebuffer"); - close(fd); - return -1; + has_overlay = target_has_overlay(fi.id); + + if(isTargetMdp5()) + setDisplaySplit(); + + if (!has_overlay) { + vi.bits_per_pixel = PIXEL_SIZE * 8; + if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_BGRA_8888) { + vi.red.offset = 8; + vi.red.length = 8; + vi.green.offset = 16; + vi.green.length = 8; + vi.blue.offset = 24; + vi.blue.length = 8; + vi.transp.offset = 0; + vi.transp.length = 8; + } else if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_RGBX_8888) { + vi.red.offset = 24; + vi.red.length = 8; + vi.green.offset = 16; + vi.green.length = 8; + vi.blue.offset = 8; + vi.blue.length = 8; + vi.transp.offset = 0; + vi.transp.length = 8; + } else { /* RGB565*/ + vi.red.offset = 11; + vi.red.length = 5; + vi.green.offset = 5; + vi.green.length = 6; + vi.blue.offset = 0; + vi.blue.length = 5; + vi.transp.offset = 0; + vi.transp.length = 0; + } + if (ioctl(fd, FBIOPUT_VSCREENINFO, &vi) < 0) { + perror("failed to put fb0 info"); + close(fd); + return -1; + } + if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { + perror("failed to get fb0 info"); + close(fd); + return -1; + } + + bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (bits == MAP_FAILED) { + perror("failed to mmap framebuffer"); + close(fd); + return -1; + } + } else { + fi.line_length = ALIGN(vi.xres, 32) * PIXEL_SIZE; } + overscan_offset_x = vi.xres * overscan_percent / 100; + overscan_offset_y = vi.yres * overscan_percent / 100; + fb->version = sizeof(*fb); fb->width = vi.xres; fb->height = vi.yres; fb->stride = fi.line_length/PIXEL_SIZE; - fb->data = bits; fb->format = PIXEL_FORMAT; - memset(fb->data, 0, vi.yres * fi.line_length); + if (!has_overlay) { + fb->data = bits; + memset(fb->data, 0, vi.yres * fi.line_length); + } fb++; + /* check if we can use double buffering */ + if (vi.yres * fi.line_length * 2 > fi.smem_len) + return fd; + + double_buffering = 1; + fb->version = sizeof(*fb); fb->width = vi.xres; fb->height = vi.yres; fb->stride = fi.line_length/PIXEL_SIZE; - fb->data = (void*) (((unsigned) bits) + vi.yres * fi.line_length); fb->format = PIXEL_FORMAT; - memset(fb->data, 0, vi.yres * fi.line_length); + if (!has_overlay) { + fb->data = (void*) (((unsigned) bits) + vi.yres * fi.line_length); + memset(fb->data, 0, vi.yres * fi.line_length); + } return fd; } @@ -159,10 +212,63 @@ static void get_memory_surface(GGLSurface* ms) { ms->format = PIXEL_FORMAT; } +void setDisplaySplit(void) { + char split[64] = {0}; + FILE* fp = fopen("/sys/class/graphics/fb0/msm_fb_split", "r"); + if (fp) { + //Format "left right" space as delimiter + if(fread(split, sizeof(char), 64, fp)) { + leftSplit = atoi(split); + printf("Left Split=%d\n",leftSplit); + char *rght = strpbrk(split, " "); + if (rght) + rightSplit = atoi(rght + 1); + printf("Right Split=%d\n", rightSplit); + } + } else { + printf("Failed to open mdss_fb_split node\n"); + } + if (fp) + fclose(fp); +} + +int getLeftSplit(void) { + //Default even split for all displays with high res + int lSplit = vi.xres / 2; + + //Override if split published by driver + if (leftSplit) + lSplit = leftSplit; + + return lSplit; +} + +int getRightSplit(void) { + return rightSplit; +} + +bool isDisplaySplit(void) { + if (vi.xres > MAX_DISPLAY_DIM) + return true; + //check if right split is set by driver + if (getRightSplit()) + return true; + + return false; +} + +int getFbXres(void) { + return vi.xres; +} + +int getFbYres (void) { + return vi.yres; +} + static void set_active_framebuffer(unsigned n) { - if (n > 1) return; - vi.yres_virtual = vi.yres * PIXEL_SIZE; + if (n > 1 || !double_buffering) return; + vi.yres_virtual = vi.yres * NUM_BUFFERS; vi.yoffset = n * vi.yres; vi.bits_per_pixel = PIXEL_SIZE * 8; if (ioctl(gr_fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { @@ -172,18 +278,30 @@ static void set_active_framebuffer(unsigned n) void gr_flip(void) { - GGLContext *gl = gr_context; + if (has_overlay) { + // Allocate overly. It'll exit early if overlay already + // allocated and allocate it if not already allocated. + allocate_overlay(gr_fb_fd, gr_framebuffer); + if (overlay_display_frame(gr_fb_fd,gr_mem_surface.data, + (fi.line_length * vi.yres)) < 0) { + // Free overlay in failure case + free_overlay(gr_fb_fd); + } + } else { + GGLContext *gl = gr_context; - /* swap front and back buffers */ - gr_active_fb = (gr_active_fb + 1) & 1; + /* swap front and back buffers */ + if (double_buffering) + gr_active_fb = (gr_active_fb + 1) & 1; - /* copy data from the in-memory surface to the buffer we're about - * to make active. */ - memcpy(gr_framebuffer[gr_active_fb].data, gr_mem_surface.data, - fi.line_length * vi.yres); + /* copy data from the in-memory surface to the buffer we're about + * to make active. */ + memcpy(gr_framebuffer[gr_active_fb].data, gr_mem_surface.data, + fi.line_length * vi.yres); - /* inform the display driver */ - set_active_framebuffer(gr_active_fb); + /* inform the display driver */ + set_active_framebuffer(gr_active_fb); + } } void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) @@ -208,12 +326,15 @@ void gr_font_size(int *x, int *y) *y = gr_font->cheight; } -int gr_text(int x, int y, const char *s) +int gr_text(int x, int y, const char *s, int bold) { GGLContext *gl = gr_context; GRFont *font = gr_font; unsigned off; + x += overscan_offset_x; + y += overscan_offset_y; + y -= font->ascent; gl->bindTexture(gl, &font->texture); @@ -234,19 +355,50 @@ int gr_text(int x, int y, const char *s) return x; } -void gr_fill(int x, int y, int w, int h) +void gr_texticon(int x, int y, gr_surface icon) { + if (gr_context == NULL || icon == NULL) { + return; + } + GGLContext* gl = gr_context; + + x += overscan_offset_x; + y += overscan_offset_y; + + gl->bindTexture(gl, (GGLSurface*) icon); + gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); + gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); + gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); + gl->enable(gl, GGL_TEXTURE_2D); + + int w = gr_get_width(icon); + int h = gr_get_height(icon); + + gl->texCoord2i(gl, -x, -y); + gl->recti(gl, x, y, x+gr_get_width(icon), y+gr_get_height(icon)); +} + +void gr_fill(int x1, int y1, int x2, int y2) { + x1 += overscan_offset_x; + y1 += overscan_offset_y; + + x2 += overscan_offset_x; + y2 += overscan_offset_y; + GGLContext *gl = gr_context; gl->disable(gl, GGL_TEXTURE_2D); - gl->recti(gl, x, y, w, h); + gl->recti(gl, x1, y1, x2, y2); } void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy) { - if (gr_context == NULL) { + if (gr_context == NULL || source == NULL) { return; } GGLContext *gl = gr_context; + dx += overscan_offset_x; + dy += overscan_offset_y; + gl->bindTexture(gl, (GGLSurface*) source); gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); @@ -327,9 +479,10 @@ int gr_init(void) fprintf(stderr, "framebuffer: fd %d (%d x %d)\n", gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height); - /* start with 0 as front (displayed) and 1 as back (drawing) */ + /* start with 0 as front (displayed) and 1 as back (drawing) */ gr_active_fb = 0; - set_active_framebuffer(0); + if (!has_overlay) + set_active_framebuffer(0); gl->colorBuffer(gl, &gr_mem_surface); gl->activeTexture(gl, 0); @@ -339,11 +492,23 @@ int gr_init(void) gr_fb_blank(true); gr_fb_blank(false); + if (has_overlay) { + if (alloc_ion_mem(fi.line_length * vi.yres) || + allocate_overlay(gr_fb_fd, gr_framebuffer)) { + free_ion_mem(); + } + } + return 0; } void gr_exit(void) { + if (has_overlay) { + free_overlay(gr_fb_fd); + free_ion_mem(); + } + close(gr_fb_fd); gr_fb_fd = -1; @@ -356,12 +521,12 @@ void gr_exit(void) int gr_fb_width(void) { - return gr_framebuffer[0].width; + return gr_framebuffer[0].width - 2*overscan_offset_x; } int gr_fb_height(void) { - return gr_framebuffer[0].height; + return gr_framebuffer[0].height - 2*overscan_offset_y; } gr_pixel *gr_fb_data(void) @@ -371,9 +536,28 @@ gr_pixel *gr_fb_data(void) void gr_fb_blank(bool blank) { +#ifdef RECOVERY_LCD_BACKLIGHT_PATH + int fd; + + fd = open(RECOVERY_LCD_BACKLIGHT_PATH, O_RDWR); + if (fd < 0) { + perror("cannot open LCD backlight"); + return; + } + write(fd, blank ? "000" : "127", 3); + close(fd); +#else int ret; + if (has_overlay && blank) { + free_overlay(gr_fb_fd); + } ret = ioctl(gr_fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); if (ret < 0) perror("ioctl(): blank"); + + if (has_overlay && !blank) { + allocate_overlay(gr_fb_fd, gr_framebuffer); + } +#endif } diff --git a/minui/graphics_overlay.c b/minui/graphics_overlay.c new file mode 100644 index 000000000..7e00bc420 --- /dev/null +++ b/minui/graphics_overlay.c @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifdef MSM_BSP +#include +#include +#endif + +#include + +#include "minui.h" + +#define MDP_V4_0 400 + +#ifdef MSM_BSP +#define ALIGN(x, align) (((x) + ((align)-1)) & ~((align)-1)) + +typedef struct { + unsigned char *mem_buf; + int size; + int ion_fd; + int mem_fd; + struct ion_handle_data handle_data; +} memInfo; + +//Left and right overlay id +static int overlayL_id = MSMFB_NEW_REQUEST; +static int overlayR_id = MSMFB_NEW_REQUEST; + +static memInfo mem_info; + +static int map_mdp_pixel_format() +{ + int format = MDP_RGB_565; +#if defined(RECOVERY_BGRA) + format = MDP_BGRA_8888; +#elif defined(RECOVERY_RGBX) + format = MDP_RGBA_8888; +#endif + return format; +} +#endif // #ifdef MSM_BSP + +static bool overlay_supported = false; +static bool isMDP5 = false; + +bool target_has_overlay(char *version) +{ +#ifdef MSM_BSP + int mdp_version; + + if (strlen(version) >= 8) { + if(!strncmp(version, "msmfb", strlen("msmfb"))) { + char str_ver[4]; + memcpy(str_ver, version + strlen("msmfb"), 3); + str_ver[3] = '\0'; + mdp_version = atoi(str_ver); + if (mdp_version >= MDP_V4_0) { + overlay_supported = true; + } + } else if (!strncmp(version, "mdssfb", strlen("mdssfb"))) { + overlay_supported = true; + isMDP5 = true; + } + } +#endif + return overlay_supported; +} + +bool isTargetMdp5() +{ + if (isMDP5) + return true; + + return false; +} + +#ifdef MSM_BSP + +int free_ion_mem(void) { + if (!overlay_supported) + return -EINVAL; + + int ret = 0; + + if (mem_info.mem_buf) + munmap(mem_info.mem_buf, mem_info.size); + + if (mem_info.ion_fd >= 0) { + ret = ioctl(mem_info.ion_fd, ION_IOC_FREE, &mem_info.handle_data); + if (ret < 0) + perror("free_mem failed "); + } + + if (mem_info.mem_fd >= 0) + close(mem_info.mem_fd); + if (mem_info.ion_fd >= 0) + close(mem_info.ion_fd); + + memset(&mem_info, 0, sizeof(mem_info)); + mem_info.mem_fd = -1; + mem_info.ion_fd = -1; + return 0; +} + +int alloc_ion_mem(unsigned int size) +{ + if (!overlay_supported) + return -EINVAL; + int result; + struct ion_fd_data fd_data; + struct ion_allocation_data ionAllocData; + + mem_info.ion_fd = open("/dev/ion", O_RDWR|O_DSYNC); + if (mem_info.ion_fd < 0) { + perror("ERROR: Can't open ion "); + return -errno; + } + + ionAllocData.flags = 0; + ionAllocData.len = size; + ionAllocData.align = sysconf(_SC_PAGESIZE); + ionAllocData.heap_mask = + ION_HEAP(ION_IOMMU_HEAP_ID) | + ION_HEAP(ION_SYSTEM_CONTIG_HEAP_ID); + + result = ioctl(mem_info.ion_fd, ION_IOC_ALLOC, &ionAllocData); + if(result){ + perror("ION_IOC_ALLOC Failed "); + close(mem_info.ion_fd); + return result; + } + + fd_data.handle = ionAllocData.handle; + mem_info.handle_data.handle = ionAllocData.handle; + result = ioctl(mem_info.ion_fd, ION_IOC_MAP, &fd_data); + if (result) { + perror("ION_IOC_MAP Failed "); + free_ion_mem(); + return result; + } + mem_info.mem_buf = (unsigned char *)mmap(NULL, size, PROT_READ | + PROT_WRITE, MAP_SHARED, fd_data.fd, 0); + mem_info.mem_fd = fd_data.fd; + + if (!mem_info.mem_buf) { + perror("ERROR: mem_buf MAP_FAILED "); + free_ion_mem(); + return -ENOMEM; + } + + return 0; +} + +int allocate_overlay(int fd, GGLSurface gr_fb[]) +{ + int ret = 0; + + if (!overlay_supported) + return -EINVAL; + + if (!isDisplaySplit()) { + // Check if overlay is already allocated + if (MSMFB_NEW_REQUEST == overlayL_id) { + struct mdp_overlay overlayL; + + memset(&overlayL, 0 , sizeof (struct mdp_overlay)); + + /* Fill Overlay Data */ + overlayL.src.width = ALIGN(gr_fb[0].width, 32); + overlayL.src.height = gr_fb[0].height; + overlayL.src.format = map_mdp_pixel_format(); + overlayL.src_rect.w = gr_fb[0].width; + overlayL.src_rect.h = gr_fb[0].height; + overlayL.dst_rect.w = gr_fb[0].width; + overlayL.dst_rect.h = gr_fb[0].height; + overlayL.alpha = 0xFF; + overlayL.transp_mask = MDP_TRANSP_NOP; + overlayL.id = MSMFB_NEW_REQUEST; + ret = ioctl(fd, MSMFB_OVERLAY_SET, &overlayL); + if (ret < 0) { + perror("Overlay Set Failed"); + return ret; + } + overlayL_id = overlayL.id; + } + } else { + float xres = getFbXres(); + int lSplit = getLeftSplit(); + float lSplitRatio = lSplit / xres; + float lCropWidth = gr_fb[0].width * lSplitRatio; + int lWidth = lSplit; + int rWidth = gr_fb[0].width - lSplit; + int height = gr_fb[0].height; + + if (MSMFB_NEW_REQUEST == overlayL_id) { + + struct mdp_overlay overlayL; + + memset(&overlayL, 0 , sizeof (struct mdp_overlay)); + + /* Fill OverlayL Data */ + overlayL.src.width = ALIGN(gr_fb[0].width, 32); + overlayL.src.height = gr_fb[0].height; + overlayL.src.format = map_mdp_pixel_format(); + overlayL.src_rect.x = 0; + overlayL.src_rect.y = 0; + overlayL.src_rect.w = lCropWidth; + overlayL.src_rect.h = gr_fb[0].height; + overlayL.dst_rect.x = 0; + overlayL.dst_rect.y = 0; + overlayL.dst_rect.w = lWidth; + overlayL.dst_rect.h = height; + overlayL.alpha = 0xFF; + overlayL.transp_mask = MDP_TRANSP_NOP; + overlayL.id = MSMFB_NEW_REQUEST; + ret = ioctl(fd, MSMFB_OVERLAY_SET, &overlayL); + if (ret < 0) { + perror("OverlayL Set Failed"); + return ret; + } + overlayL_id = overlayL.id; + } + if (MSMFB_NEW_REQUEST == overlayR_id) { + struct mdp_overlay overlayR; + + memset(&overlayR, 0 , sizeof (struct mdp_overlay)); + + /* Fill OverlayR Data */ + overlayR.src.width = ALIGN(gr_fb[0].width, 32); + overlayR.src.height = gr_fb[0].height; + overlayR.src.format = map_mdp_pixel_format(); + overlayR.src_rect.x = lCropWidth; + overlayR.src_rect.y = 0; + overlayR.src_rect.w = gr_fb[0].width - lCropWidth; + overlayR.src_rect.h = gr_fb[0].height; + overlayR.dst_rect.x = 0; + overlayR.dst_rect.y = 0; + overlayR.dst_rect.w = rWidth; + overlayR.dst_rect.h = height; + overlayR.alpha = 0xFF; + overlayR.flags = MDSS_MDP_RIGHT_MIXER; + overlayR.transp_mask = MDP_TRANSP_NOP; + overlayR.id = MSMFB_NEW_REQUEST; + ret = ioctl(fd, MSMFB_OVERLAY_SET, &overlayR); + if (ret < 0) { + perror("OverlayR Set Failed"); + return ret; + } + overlayR_id = overlayR.id; + } + + } + return 0; +} + +int free_overlay(int fd) +{ + if (!overlay_supported) + return -EINVAL; + + int ret = 0; + struct mdp_display_commit ext_commit; + + if (!isDisplaySplit()) { + if (overlayL_id != MSMFB_NEW_REQUEST) { + ret = ioctl(fd, MSMFB_OVERLAY_UNSET, &overlayL_id); + if (ret) { + perror("Overlay Unset Failed"); + overlayL_id = MSMFB_NEW_REQUEST; + return ret; + } + } + } else { + + if (overlayL_id != MSMFB_NEW_REQUEST) { + ret = ioctl(fd, MSMFB_OVERLAY_UNSET, &overlayL_id); + if (ret) { + perror("OverlayL Unset Failed"); + overlayL_id = MSMFB_NEW_REQUEST; + return ret; + } + } + + if (overlayR_id != MSMFB_NEW_REQUEST) { + ret = ioctl(fd, MSMFB_OVERLAY_UNSET, &overlayR_id); + if (ret) { + perror("OverlayR Unset Failed"); + overlayR_id = MSMFB_NEW_REQUEST; + return ret; + } + } + } + memset(&ext_commit, 0, sizeof(struct mdp_display_commit)); + ext_commit.flags = MDP_DISPLAY_COMMIT_OVERLAY; + ext_commit.wait_for_finish = 1; + ret = ioctl(fd, MSMFB_DISPLAY_COMMIT, &ext_commit); + if (ret < 0) { + perror("ERROR: Clear MSMFB_DISPLAY_COMMIT failed!"); + overlayL_id = MSMFB_NEW_REQUEST; + overlayR_id = MSMFB_NEW_REQUEST; + return ret; + } + overlayL_id = MSMFB_NEW_REQUEST; + overlayR_id = MSMFB_NEW_REQUEST; + + return 0; +} + +int overlay_display_frame(int fd, GGLubyte* data, size_t size) +{ + if (!overlay_supported) + return -EINVAL; + + int ret = 0; + struct msmfb_overlay_data ovdataL, ovdataR; + struct mdp_display_commit ext_commit; + + if (!isDisplaySplit()) { + if (overlayL_id == MSMFB_NEW_REQUEST) { + perror("display_frame failed, no overlay\n"); + return -EINVAL; + } + + memcpy(mem_info.mem_buf, data, size); + + memset(&ovdataL, 0, sizeof(struct msmfb_overlay_data)); + + ovdataL.id = overlayL_id; + ovdataL.data.flags = 0; + ovdataL.data.offset = 0; + ovdataL.data.memory_id = mem_info.mem_fd; + ret = ioctl(fd, MSMFB_OVERLAY_PLAY, &ovdataL); + if (ret < 0) { + perror("overlay_display_frame failed, overlay play Failed\n"); + return ret; + } + } else { + + if (overlayL_id == MSMFB_NEW_REQUEST) { + perror("display_frame failed, no overlayL \n"); + return -EINVAL; + } + + memcpy(mem_info.mem_buf, data, size); + + memset(&ovdataL, 0, sizeof(struct msmfb_overlay_data)); + + ovdataL.id = overlayL_id; + ovdataL.data.flags = 0; + ovdataL.data.offset = 0; + ovdataL.data.memory_id = mem_info.mem_fd; + ret = ioctl(fd, MSMFB_OVERLAY_PLAY, &ovdataL); + if (ret < 0) { + perror("overlay_display_frame failed, overlayL play Failed\n"); + return ret; + } + + if (overlayR_id == MSMFB_NEW_REQUEST) { + perror("display_frame failed, no overlayR \n"); + return -EINVAL; + } + memset(&ovdataR, 0, sizeof(struct msmfb_overlay_data)); + + ovdataR.id = overlayR_id; + ovdataR.data.flags = 0; + ovdataR.data.offset = 0; + ovdataR.data.memory_id = mem_info.mem_fd; + ret = ioctl(fd, MSMFB_OVERLAY_PLAY, &ovdataR); + if (ret < 0) { + perror("overlay_display_frame failed, overlayR play Failed\n"); + return ret; + } + } + memset(&ext_commit, 0, sizeof(struct mdp_display_commit)); + ext_commit.flags = MDP_DISPLAY_COMMIT_OVERLAY; + ext_commit.wait_for_finish = 1; + ret = ioctl(fd, MSMFB_DISPLAY_COMMIT, &ext_commit); + if (ret < 0) { + perror("overlay_display_frame failed, overlay commit Failed\n!"); + } + + return ret; +} + +#else + +int free_ion_mem(void) { + return -EINVAL; +} + +int alloc_ion_mem(unsigned int size) +{ + return -EINVAL; +} + +int allocate_overlay(int fd, GGLSurface gr_fb[]) +{ + return -EINVAL; +} + +int free_overlay(int fd) +{ + return -EINVAL; +} + +int overlay_display_frame(int fd, GGLubyte* data, size_t size) +{ + return -EINVAL; +} + +#endif //#ifdef MSM_BSP diff --git a/minui/minui.h b/minui/minui.h index 2e2f1f477..e4b262311 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -32,8 +32,8 @@ void gr_flip(void); void gr_fb_blank(bool blank); void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); -void gr_fill(int x, int y, int w, int h); -int gr_text(int x, int y, const char *s); +void gr_fill(int x1, int y1, int x2, int y2); +int gr_text(int x, int y, const char *s, int bold); int gr_measure(const char *s); void gr_font_size(int *x, int *y); @@ -41,6 +41,12 @@ void gr_blit(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); +// overlays +int getFbXres(void); +int getLeftSplit(void); +void setDisplaySplit(void); +bool isDisplaySplit(void); + // input event structure, include for the definition. // see http://www.mjmwired.net/kernel/Documentation/input/ for info. struct input_event; @@ -67,6 +73,9 @@ void ev_dispatch(void); // Returns 0 if no error, else negative. int res_create_surface(const char* name, gr_surface* pSurface); +static inline int res_create_display_surface(const char* name, gr_surface* pSurface) { + return res_create_surface(name, pSurface); +} void res_free_surface(gr_surface surface); #endif diff --git a/minui/resources.c b/minui/resources.c index b437a87cb..6d11aeaf6 100644 --- a/minui/resources.c +++ b/minui/resources.c @@ -130,7 +130,7 @@ int res_create_surface(const char* name, gr_surface* pSurface) { alpha = 1; } - int y; + unsigned y; if (channels == 3 || (channels == 1 && !alpha)) { for (y = 0; y < height; ++y) { unsigned char* pRow = pData + y * stride; diff --git a/minui/roboto_10x18.h b/minui/roboto_10x18.h new file mode 100644 index 000000000..3119c81c2 --- /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 000000000..7271d74bb --- /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 000000000..6e7566efe --- /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 b1ee67439..045f35570 100644 --- a/minzip/Android.mk +++ b/minzip/Android.mk @@ -8,10 +8,12 @@ LOCAL_SRC_FILES := \ Inlines.c \ Zip.c -LOCAL_C_INCLUDES += \ +LOCAL_C_INCLUDES := \ external/zlib \ external/safe-iop/include - + +LOCAL_STATIC_LIBRARIES := libselinux + LOCAL_MODULE := libminzip LOCAL_CFLAGS += -Wall diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c index 20c89cd6f..8dd5da1da 100644 --- a/minzip/DirUtil.c +++ b/minzip/DirUtil.c @@ -54,7 +54,8 @@ getPathDirStatus(const char *path) int dirCreateHierarchy(const char *path, int mode, - const struct utimbuf *timestamp, bool stripFileName) + const struct utimbuf *timestamp, bool stripFileName, + struct selabel_handle *sehnd) { DirStatus ds; @@ -144,7 +145,20 @@ dirCreateHierarchy(const char *path, int mode, } else if (ds == DMISSING) { int err; + char *secontext = NULL; + + if (sehnd) { + selabel_lookup(sehnd, &secontext, cpath, mode); + setfscreatecon(secontext); + } + err = mkdir(cpath, mode); + + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } + if (err != 0) { free(cpath); return -1; diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h index 5d881f562..a5cfa761b 100644 --- a/minzip/DirUtil.h +++ b/minzip/DirUtil.h @@ -20,6 +20,13 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + /* Like "mkdir -p", try to guarantee that all directories * specified in path are present, creating as many directories * as necessary. The specified mode is passed to all mkdir @@ -34,7 +41,8 @@ * (usually if some element of path is not a directory). */ int dirCreateHierarchy(const char *path, int mode, - const struct utimbuf *timestamp, bool stripFileName); + const struct utimbuf *timestamp, bool stripFileName, + struct selabel_handle* sehnd); /* rm -rf */ @@ -48,4 +56,8 @@ int dirUnlinkHierarchy(const char *path); int dirSetHierarchyPermissions(const char *path, int uid, int gid, int dirMode, int fileMode); +#ifdef __cplusplus +} +#endif + #endif // MINZIP_DIRUTIL_H_ diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c index 49a2522d6..31c76d6d4 100644 --- a/minzip/SysUtil.c +++ b/minzip/SysUtil.c @@ -95,16 +95,16 @@ int sysLoadFileInShmem(int fd, MemMapping* pMap) if (memPtr == NULL) return -1; - actual = read(fd, memPtr, length); + pMap->baseAddr = pMap->addr = memPtr; + pMap->baseLength = pMap->length = length; + + actual = TEMP_FAILURE_RETRY(read(fd, memPtr, length)); if (actual != length) { LOGE("only read %d of %d bytes\n", (int) actual, (int) length); sysReleaseShmem(pMap); return -1; } - pMap->baseAddr = pMap->addr = memPtr; - pMap->baseLength = pMap->length = length; - return 0; } diff --git a/minzip/Zip.c b/minzip/Zip.c index 46d2f829e..2e4654ec9 100644 --- a/minzip/Zip.c +++ b/minzip/Zip.c @@ -772,7 +772,7 @@ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, static bool writeProcessFunction(const unsigned char *data, int dataLen, void *cookie) { - int fd = (int)cookie; + int fd = (int)(intptr_t)cookie; ssize_t soFar = 0; while (true) { @@ -802,7 +802,7 @@ bool mzExtractZipEntryToFile(const ZipArchive *pArchive, const ZipEntry *pEntry, int fd) { bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction, - (void*)fd); + (void*)(intptr_t)fd); if (!ret) { LOGE("Can't extract entry to file.\n"); return false; @@ -930,7 +930,8 @@ static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry) bool mzExtractRecursive(const ZipArchive *pArchive, const char *zipDir, const char *targetDir, int flags, const struct utimbuf *timestamp, - void (*callback)(const char *fn, void *), void *cookie) + void (*callback)(const char *fn, void *), void *cookie, + struct selabel_handle *sehnd) { if (zipDir[0] == '/') { LOGE("mzExtractRecursive(): zipDir must be a relative path.\n"); @@ -1045,7 +1046,7 @@ bool mzExtractRecursive(const ZipArchive *pArchive, if (pEntry->fileName[pEntry->fileNameLen-1] == '/') { if (!(flags & MZ_EXTRACT_FILES_ONLY)) { int ret = dirCreateHierarchy( - targetFile, UNZIP_DIRMODE, timestamp, false); + targetFile, UNZIP_DIRMODE, timestamp, false, sehnd); if (ret != 0) { LOGE("Can't create containing directory for \"%s\": %s\n", targetFile, strerror(errno)); @@ -1059,7 +1060,7 @@ bool mzExtractRecursive(const ZipArchive *pArchive, * the containing directory exists. */ int ret = dirCreateHierarchy( - targetFile, UNZIP_DIRMODE, timestamp, true); + targetFile, UNZIP_DIRMODE, timestamp, true, sehnd); if (ret != 0) { LOGE("Can't create containing directory for \"%s\": %s\n", targetFile, strerror(errno)); @@ -1113,7 +1114,21 @@ bool mzExtractRecursive(const ZipArchive *pArchive, /* The entry is a regular file. * Open the target for writing. */ + + char *secontext = NULL; + + if (sehnd) { + selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE); + setfscreatecon(secontext); + } + int fd = creat(targetFile, UNZIP_FILEMODE); + + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } + if (fd < 0) { LOGE("Can't create target file \"%s\": %s\n", targetFile, strerror(errno)); diff --git a/minzip/Zip.h b/minzip/Zip.h index 9f99fba5b..c94282827 100644 --- a/minzip/Zip.h +++ b/minzip/Zip.h @@ -14,6 +14,13 @@ #include "Hash.h" #include "SysUtil.h" +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + /* * One entry in the Zip archive. Treat this as opaque -- use accessors below. * @@ -208,6 +215,11 @@ enum { MZ_EXTRACT_FILES_ONLY = 1, MZ_EXTRACT_DRY_RUN = 2 }; bool mzExtractRecursive(const ZipArchive *pArchive, const char *zipDir, const char *targetDir, int flags, const struct utimbuf *timestamp, - void (*callback)(const char *fn, void*), void *cookie); + void (*callback)(const char *fn, void*), void *cookie, + struct selabel_handle *sehnd); + +#ifdef __cplusplus +} +#endif #endif /*_MINZIP_ZIP*/ diff --git a/minzip/inline_magic.h b/minzip/inline_magic.h index 8c185e155..59c659f77 100644 --- a/minzip/inline_magic.h +++ b/minzip/inline_magic.h @@ -18,7 +18,7 @@ #define MINZIP_INLINE_MAGIC_H_ #ifndef MINZIP_GENERATE_INLINES -#define INLINE extern __inline__ +#define INLINE extern inline __attribute((__gnu_inline__)) #else #define INLINE #endif diff --git a/mmcutils/Android.mk b/mmcutils/Android.mk new file mode 100644 index 000000000..e29bf41b6 --- /dev/null +++ b/mmcutils/Android.mk @@ -0,0 +1,14 @@ +ifneq ($(TARGET_SIMULATOR),true) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + mmcutils.c + +LOCAL_MODULE := libmmcutils +LOCAL_MODULE_TAGS := eng + +include $(BUILD_STATIC_LIBRARY) + +endif # !TARGET_SIMULATOR diff --git a/mmcutils/mmcutils.c b/mmcutils/mmcutils.c new file mode 100644 index 000000000..f9c7b702a --- /dev/null +++ b/mmcutils/mmcutils.c @@ -0,0 +1,650 @@ +/* + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Code Aurora Forum, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for _IOW, _IOR, mount() + +#include "mmcutils.h" + +unsigned ext3_count = 0; +char *ext3_partitions[] = {"system", "userdata", "cache", "NONE"}; + +unsigned vfat_count = 0; +char *vfat_partitions[] = {"modem", "NONE"}; + +struct MmcPartition { + char *device_index; + char *filesystem; + char *name; + unsigned dstatus; + unsigned dtype ; + unsigned dfirstsec; + unsigned dsize; +}; + +typedef struct { + MmcPartition *partitions; + int partitions_allocd; + int partition_count; +} MmcState; + +static MmcState g_mmc_state = { + NULL, // partitions + 0, // partitions_allocd + -1 // partition_count +}; + +#define MMC_DEVICENAME "/dev/block/mmcblk0" + +static void +mmc_partition_name (MmcPartition *mbr, unsigned int type) { + switch(type) + { + char name[64]; + case MMC_BOOT_TYPE: + sprintf(name,"boot"); + mbr->name = strdup(name); + break; + case MMC_RECOVERY_TYPE: + sprintf(name,"recovery"); + mbr->name = strdup(name); + break; + case MMC_EXT3_TYPE: + if (strcmp("NONE", ext3_partitions[ext3_count])) { + strcpy((char *)name,(const char *)ext3_partitions[ext3_count]); + mbr->name = strdup(name); + ext3_count++; + } + mbr->filesystem = strdup("ext3"); + break; + case MMC_VFAT_TYPE: + if (strcmp("NONE", vfat_partitions[vfat_count])) { + strcpy((char *)name,(const char *)vfat_partitions[vfat_count]); + mbr->name = strdup(name); + vfat_count++; + } + mbr->filesystem = strdup("vfat"); + break; + }; +} + +static int +mmc_read_mbr (const char *device, MmcPartition *mbr) { + FILE *fd; + unsigned char buffer[512]; + int idx, i; + unsigned mmc_partition_count = 0; + unsigned int dtype; + unsigned int dfirstsec; + unsigned int EBR_first_sec; + unsigned int EBR_current_sec; + int ret = -1; + + fd = fopen(device, "r"); + if(fd == NULL) + { + printf("Can't open device: \"%s\"\n", device); + goto ERROR2; + } + if ((fread(buffer, 512, 1, fd)) != 1) + { + printf("Can't read device: \"%s\"\n", device); + goto ERROR1; + } + /* Check to see if signature exists */ + if ((buffer[TABLE_SIGNATURE] != 0x55) || \ + (buffer[TABLE_SIGNATURE + 1] != 0xAA)) + { + printf("Incorrect mbr signatures!\n"); + goto ERROR1; + } + idx = TABLE_ENTRY_0; + for (i = 0; i < 4; i++) + { + char device_index[128]; + + mbr[mmc_partition_count].dstatus = \ + buffer[idx + i * TABLE_ENTRY_SIZE + OFFSET_STATUS]; + mbr[mmc_partition_count].dtype = \ + buffer[idx + i * TABLE_ENTRY_SIZE + OFFSET_TYPE]; + mbr[mmc_partition_count].dfirstsec = \ + GET_LWORD_FROM_BYTE(&buffer[idx + \ + i * TABLE_ENTRY_SIZE + \ + OFFSET_FIRST_SEC]); + mbr[mmc_partition_count].dsize = \ + GET_LWORD_FROM_BYTE(&buffer[idx + \ + i * TABLE_ENTRY_SIZE + \ + OFFSET_SIZE]); + dtype = mbr[mmc_partition_count].dtype; + dfirstsec = mbr[mmc_partition_count].dfirstsec; + mmc_partition_name(&mbr[mmc_partition_count], \ + mbr[mmc_partition_count].dtype); + + sprintf(device_index, "%sp%d", device, (mmc_partition_count+1)); + mbr[mmc_partition_count].device_index = strdup(device_index); + + mmc_partition_count++; + if (mmc_partition_count == MAX_PARTITIONS) + goto SUCCESS; + } + + /* See if the last partition is EBR, if not, parsing is done */ + if (dtype != 0x05) + { + goto SUCCESS; + } + + EBR_first_sec = dfirstsec; + EBR_current_sec = dfirstsec; + + fseek (fd, (EBR_first_sec * 512), SEEK_SET); + if ((fread(buffer, 512, 1, fd)) != 1) + goto ERROR1; + + /* Loop to parse the EBR */ + for (i = 0;; i++) + { + char device_index[128]; + + if ((buffer[TABLE_SIGNATURE] != 0x55) || (buffer[TABLE_SIGNATURE + 1] != 0xAA)) + { + break; + } + mbr[mmc_partition_count].dstatus = \ + buffer[TABLE_ENTRY_0 + OFFSET_STATUS]; + mbr[mmc_partition_count].dtype = \ + buffer[TABLE_ENTRY_0 + OFFSET_TYPE]; + mbr[mmc_partition_count].dfirstsec = \ + GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_0 + \ + OFFSET_FIRST_SEC]) + \ + EBR_current_sec; + mbr[mmc_partition_count].dsize = \ + GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_0 + \ + OFFSET_SIZE]); + mmc_partition_name(&mbr[mmc_partition_count], \ + mbr[mmc_partition_count].dtype); + + sprintf(device_index, "%sp%d", device, (mmc_partition_count+1)); + mbr[mmc_partition_count].device_index = strdup(device_index); + + mmc_partition_count++; + if (mmc_partition_count == MAX_PARTITIONS) + goto SUCCESS; + + dfirstsec = GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_1 + OFFSET_FIRST_SEC]); + if(dfirstsec == 0) + { + /* Getting to the end of the EBR tables */ + break; + } + /* More EBR to follow - read in the next EBR sector */ + fseek (fd, ((EBR_first_sec + dfirstsec) * 512), SEEK_SET); + if ((fread(buffer, 512, 1, fd)) != 1) + goto ERROR1; + + EBR_current_sec = EBR_first_sec + dfirstsec; + } + +SUCCESS: + ret = mmc_partition_count; +ERROR1: + fclose(fd); +ERROR2: + return ret; +} + +int +mmc_scan_partitions() { + int i; + ssize_t nbytes; + + if (g_mmc_state.partitions == NULL) { + const int nump = MAX_PARTITIONS; + MmcPartition *partitions = malloc(nump * sizeof(*partitions)); + if (partitions == NULL) { + errno = ENOMEM; + return -1; + } + g_mmc_state.partitions = partitions; + g_mmc_state.partitions_allocd = nump; + memset(partitions, 0, nump * sizeof(*partitions)); + } + g_mmc_state.partition_count = 0; + ext3_count = 0; + vfat_count = 0; + + /* Initialize all of the entries to make things easier later. + * (Lets us handle sparsely-numbered partitions, which + * may not even be possible.) + */ + for (i = 0; i < g_mmc_state.partitions_allocd; i++) { + MmcPartition *p = &g_mmc_state.partitions[i]; + if (p->device_index != NULL) { + free(p->device_index); + p->device_index = NULL; + } + if (p->name != NULL) { + free(p->name); + p->name = NULL; + } + if (p->filesystem != NULL) { + free(p->filesystem); + p->filesystem = NULL; + } + } + + g_mmc_state.partition_count = mmc_read_mbr(MMC_DEVICENAME, g_mmc_state.partitions); + if(g_mmc_state.partition_count == -1) + { + printf("Error in reading mbr!\n"); + // keep "partitions" around so we can free the names on a rescan. + g_mmc_state.partition_count = -1; + } + return g_mmc_state.partition_count; +} + +static const MmcPartition * +mmc_find_partition_by_device_index(const char *device_index) +{ + if (g_mmc_state.partitions != NULL) { + int i; + for (i = 0; i < g_mmc_state.partitions_allocd; i++) { + MmcPartition *p = &g_mmc_state.partitions[i]; + if (p->device_index !=NULL && p->name != NULL) { + if (strcmp(p->device_index, device_index) == 0) { + return p; + } + } + } + } + return NULL; +} + +const MmcPartition * +mmc_find_partition_by_name(const char *name) +{ + if (name[0] == '/') { + return mmc_find_partition_by_device_index(name); + } + + if (g_mmc_state.partitions != NULL) { + int i; + for (i = 0; i < g_mmc_state.partitions_allocd; i++) { + MmcPartition *p = &g_mmc_state.partitions[i]; + if (p->device_index !=NULL && p->name != NULL) { + if (strcmp(p->name, name) == 0) { + return p; + } + } + } + } + return NULL; +} + +#define MKE2FS_BIN "/sbin/mke2fs" +#define TUNE2FS_BIN "/sbin/tune2fs" +#define E2FSCK_BIN "/sbin/e2fsck" + +int +run_exec_process ( char **argv) { + pid_t pid; + int status; + pid = fork(); + if (pid == 0) { + execv(argv[0], argv); + fprintf(stderr, "E:Can't run (%s)\n",strerror(errno)); + _exit(-1); + } + + waitpid(pid, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + return 1; + } + return 0; +} + +int +format_ext3_device (const char *device) { + char *const mke2fs[] = {MKE2FS_BIN, "-j", "-q", device, NULL}; + char *const tune2fs[] = {TUNE2FS_BIN, "-C", "1", device, NULL}; + // Run mke2fs + if(run_exec_process(mke2fs)) { + printf("failure while running mke2fs\n"); + return -1; + } + + // Run tune2fs + if(run_exec_process(tune2fs)) { + printf("failure while running mke2fs\n"); + return -1; + } + + // Run e2fsck + char *const e2fsck[] = {E2FSCK_BIN, "-fy", device, NULL}; + if(run_exec_process(e2fsck)) { + printf("failure while running e2fsck\n"); + return -1; + } + + return 0; +} + +int +format_ext2_device (const char *device) { + // Run mke2fs + char *const mke2fs[] = {MKE2FS_BIN, device, NULL}; + if(run_exec_process(mke2fs)) + return -1; + + // Run tune2fs + char *const tune2fs[] = {TUNE2FS_BIN, "-C", "1", device, NULL}; + if(run_exec_process(tune2fs)) + return -1; + + // Run e2fsck + char *const e2fsck[] = {E2FSCK_BIN, "-fy", device, NULL}; + if(run_exec_process(e2fsck)) + return -1; + + return 0; +} + +int +mmc_format_ext3 (MmcPartition *partition) { + char device[128]; + strcpy(device, partition->device_index); + return format_ext3_device(device); +} + +int +mmc_mount_partition(const MmcPartition *partition, const char *mount_point, + int read_only) +{ + const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME; + char devname[128]; + int rv = -1; + strcpy(devname, partition->device_index); + if (partition->filesystem == NULL) { + printf("Null filesystem!\n"); + return rv; + } + if (!read_only) { + rv = mount(devname, mount_point, partition->filesystem, flags, NULL); + } + if (read_only || rv < 0) { + rv = mount(devname, mount_point, partition->filesystem, flags | MS_RDONLY, 0); + if (rv < 0) { + printf("Failed to mount %s on %s: %s\n", + devname, mount_point, strerror(errno)); + } else { + printf("Mount %s on %s read-only\n", devname, mount_point); + } + } + return rv; +} + +int +mmc_raw_copy (const MmcPartition *partition, char *in_file) { + int ch; + FILE *in; + FILE *out; + int val = 0; + char buf[512]; + unsigned sz = 0; + unsigned i; + int ret = -1; + char *out_file = partition->device_index; + + in = fopen ( in_file, "r" ); + if (in == NULL) + goto ERROR3; + + out = fopen ( out_file, "w" ); + if (out == NULL) + goto ERROR2; + + fseek(in, 0L, SEEK_END); + sz = ftell(in); + fseek(in, 0L, SEEK_SET); + + if (sz % 512) + { + while ( ( ch = fgetc ( in ) ) != EOF ) + fputc ( ch, out ); + } + else + { + for (i=0; i< (sz/512); i++) + { + if ((fread(buf, 512, 1, in)) != 1) + goto ERROR1; + if ((fwrite(buf, 512, 1, out)) != 1) + goto ERROR1; + } + } + + fsync(out); + ret = 0; +ERROR1: + fclose ( out ); +ERROR2: + fclose ( in ); +ERROR3: + return ret; + +} + + +int +mmc_raw_dump_internal (const char* in_file, const char *out_file) { + int ch; + FILE *in; + FILE *out; + int val = 0; + char buf[512]; + unsigned sz = 0; + unsigned i; + int ret = -1; + + in = fopen ( in_file, "r" ); + if (in == NULL) + goto ERROR3; + + out = fopen ( out_file, "w" ); + if (out == NULL) + goto ERROR2; + + fseek(in, 0L, SEEK_END); + sz = ftell(in); + fseek(in, 0L, SEEK_SET); + + if (sz % 512) + { + while ( ( ch = fgetc ( in ) ) != EOF ) + fputc ( ch, out ); + } + else + { + for (i=0; i< (sz/512); i++) + { + if ((fread(buf, 512, 1, in)) != 1) + goto ERROR1; + if ((fwrite(buf, 512, 1, out)) != 1) + goto ERROR1; + } + } + + fsync(out); + ret = 0; +ERROR1: + fclose ( out ); +ERROR2: + fclose ( in ); +ERROR3: + return ret; + +} + +// TODO: refactor this to not be a giant copy paste mess +int +mmc_raw_dump (const MmcPartition *partition, char *out_file) { + return mmc_raw_dump_internal(partition->device_index, out_file); +} + + +int +mmc_raw_read (const MmcPartition *partition, char *data, int data_size) { + int ch; + FILE *in; + int val = 0; + char buf[512]; + unsigned sz = 0; + unsigned i; + int ret = -1; + char *in_file = partition->device_index; + + in = fopen ( in_file, "r" ); + if (in == NULL) + goto ERROR3; + + fseek(in, 0L, SEEK_END); + sz = ftell(in); + fseek(in, 0L, SEEK_SET); + + fread(data, data_size, 1, in); + + ret = 0; +ERROR1: +ERROR2: + fclose ( in ); +ERROR3: + return ret; + +} + +int +mmc_raw_write (const MmcPartition *partition, char *data, int data_size) { + int ch; + FILE *out; + int val = 0; + char buf[512]; + unsigned sz = 0; + unsigned i; + int ret = -1; + char *out_file = partition->device_index; + + out = fopen ( out_file, "w" ); + if (out == NULL) + goto ERROR3; + + fwrite(data, data_size, 1, out); + + ret = 0; +ERROR1: +ERROR2: + fclose ( out ); +ERROR3: + return ret; + +} + +int cmd_mmc_restore_raw_partition(const char *partition, const char *filename) +{ + if (partition[0] != '/') { + mmc_scan_partitions(); + const MmcPartition *p; + p = mmc_find_partition_by_name(partition); + if (p == NULL) + return -1; + return mmc_raw_copy(p, filename); + } + else { + return mmc_raw_dump_internal(filename, partition); + } +} + +int cmd_mmc_backup_raw_partition(const char *partition, const char *filename) +{ + if (partition[0] != '/') { + mmc_scan_partitions(); + const MmcPartition *p; + p = mmc_find_partition_by_name(partition); + if (p == NULL) + return -1; + return mmc_raw_dump(p, filename); + } + else { + return mmc_raw_dump_internal(partition, filename); + } +} + +int cmd_mmc_erase_raw_partition(const char *partition) +{ + return 0; +} + +int cmd_mmc_erase_partition(const char *partition, const char *filesystem) +{ + mmc_scan_partitions(); + const MmcPartition *p; + p = mmc_find_partition_by_name(partition); + if (p == NULL) + return -1; + return mmc_format_ext3 (p); +} + +int cmd_mmc_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only) +{ + mmc_scan_partitions(); + const MmcPartition *p; + p = mmc_find_partition_by_name(partition); + if (p == NULL) + return -1; + return mmc_mount_partition(p, mount_point, read_only); +} + +int cmd_mmc_get_partition_device(const char *partition, char *device) +{ + mmc_scan_partitions(); + const MmcPartition *p; + p = mmc_find_partition_by_name(partition); + if (p == NULL) + return -1; + strcpy(device, p->device_index); + return 0; +} diff --git a/mmcutils/mmcutils.h b/mmcutils/mmcutils.h new file mode 100644 index 000000000..5b10fdca4 --- /dev/null +++ b/mmcutils/mmcutils.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Code Aurora Forum, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MMCUTILS_H_ +#define MMCUTILS_H_ + +/* Some useful define used to access the MBR/EBR table */ +#define BLOCK_SIZE 0x200 +#define TABLE_ENTRY_0 0x1BE +#define TABLE_ENTRY_1 0x1CE +#define TABLE_ENTRY_2 0x1DE +#define TABLE_ENTRY_3 0x1EE +#define TABLE_SIGNATURE 0x1FE +#define TABLE_ENTRY_SIZE 0x010 + +#define OFFSET_STATUS 0x00 +#define OFFSET_TYPE 0x04 +#define OFFSET_FIRST_SEC 0x08 +#define OFFSET_SIZE 0x0C +#define COPYBUFF_SIZE (1024 * 16) +#define BINARY_IN_TABLE_SIZE (16 * 512) +#define MAX_FILE_ENTRIES 20 + +#define MMC_BOOT_TYPE 0x48 +#define MMC_SYSTEM_TYPE 0x82 +#define MMC_USERDATA_TYPE 0x83 +#define MMC_RECOVERY_TYPE 0x71 + +#define MMC_RCA 2 + +#define MAX_PARTITIONS 64 + +#define GET_LWORD_FROM_BYTE(x) ((unsigned)*(x) | \ + ((unsigned)*((x)+1) << 8) | \ + ((unsigned)*((x)+2) << 16) | \ + ((unsigned)*((x)+3) << 24)) + +#define PUT_LWORD_TO_BYTE(x, y) do{*(x) = (y) & 0xff; \ + *((x)+1) = ((y) >> 8) & 0xff; \ + *((x)+2) = ((y) >> 16) & 0xff; \ + *((x)+3) = ((y) >> 24) & 0xff; }while(0) + +#define GET_PAR_NUM_FROM_POS(x) ((((x) & 0x0000FF00) >> 8) + ((x) & 0x000000FF)) + +#define MMC_BOOT_TYPE 0x48 +#define MMC_EXT3_TYPE 0x83 +#define MMC_VFAT_TYPE 0xC +typedef struct MmcPartition MmcPartition; + +/* Functions */ +int mmc_scan_partitions(); +const MmcPartition *mmc_find_partition_by_name(const char *name); +int mmc_format_ext3 (MmcPartition *partition); +int mmc_mount_partition(const MmcPartition *partition, const char *mount_point, \ + int read_only); +int mmc_raw_copy (const MmcPartition *partition, char *in_file); +int mmc_raw_read (const MmcPartition *partition, char *data, int data_size); +int mmc_raw_write (const MmcPartition *partition, char *data, int data_size); + +int format_ext2_device(const char *device); +int format_ext3_device(const char *device); + +#endif // MMCUTILS_H_ + + diff --git a/mtdutils/mounts.c b/mounts.c similarity index 87% rename from mtdutils/mounts.c rename to mounts.c index c90fc8acf..866da60bd 100644 --- a/mtdutils/mounts.c +++ b/mounts.c @@ -23,13 +23,6 @@ #include "mounts.h" -struct MountedVolume { - const char *device; - const char *mount_point; - const char *filesystem; - const char *flags; -}; - typedef struct { MountedVolume *volumes; int volumes_allocd; @@ -113,10 +106,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. @@ -220,3 +213,27 @@ remount_read_only(const MountedVolume* volume) MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0); } + +const MountedVolume * +find_mounted_volume_by_real_node(const char *node) +{ + if (g_mounts_state.volumes != NULL) { + int i; + for (i = 0; i < g_mounts_state.volume_count; i++) { + MountedVolume *v = &g_mounts_state.volumes[i]; + /* May be null if it was unmounted and we haven't rescanned. + */ + if (v->device != NULL) { + ssize_t len; + char path_resolved[PATH_MAX]; + if((len = readlink(v->device, path_resolved, sizeof(path_resolved)-1)) != -1) + path_resolved[len] = '\0'; + + if (strcmp(path_resolved, node) == 0) { + return v; + } + } + } + } + return NULL; +} diff --git a/mtdutils/mounts.h b/mounts.h similarity index 83% rename from mtdutils/mounts.h rename to mounts.h index 30b2927c2..6134038c3 100644 --- a/mtdutils/mounts.h +++ b/mounts.h @@ -17,7 +17,12 @@ #ifndef MTDUTILS_MOUNTS_H_ #define MTDUTILS_MOUNTS_H_ -typedef struct MountedVolume MountedVolume; +typedef struct { + const char *device; + const char *mount_point; + const char *filesystem; + const char *flags; +} MountedVolume; int scan_mounted_volumes(void); @@ -30,4 +35,7 @@ int unmount_mounted_volume(const MountedVolume *volume); int remount_read_only(const MountedVolume* volume); +const MountedVolume * +find_mounted_volume_by_real_node(const char *node); + #endif // MTDUTILS_MOUNTS_H_ diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk index ef417fa19..1180f1c12 100644 --- a/mtdutils/Android.mk +++ b/mtdutils/Android.mk @@ -1,18 +1,29 @@ LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - mtdutils.c \ - mounts.c +include $(CLEAR_VARS) +LOCAL_SRC_FILES := mtdutils.c LOCAL_MODULE := libmtdutils +include $(BUILD_STATIC_LIBRARY) +ifeq ($(BOARD_USES_BML_OVER_MTD),true) +include $(CLEAR_VARS) +LOCAL_SRC_FILES := bml_over_mtd.c +LOCAL_C_INCLUDES += $(LOCAL_PATH) +LOCAL_MODULE := libbml_over_mtd +LOCAL_MODULE_TAGS := eng +LOCAL_CFLAGS += -Dmain=bml_over_mtd_main include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) -LOCAL_SRC_FILES := flash_image.c -LOCAL_MODULE := flash_image +LOCAL_SRC_FILES := bml_over_mtd.c +LOCAL_MODULE := bml_over_mtd LOCAL_MODULE_TAGS := eng -LOCAL_STATIC_LIBRARIES := libmtdutils -LOCAL_SHARED_LIBRARIES := libcutils libc +LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities +LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities +LOCAL_MODULE_STEM := bml_over_mtd +LOCAL_C_INCLUDES += $(LOCAL_PATH) +LOCAL_STATIC_LIBRARIES := libmtdutils libcutils libc +LOCAL_FORCE_STATIC_EXECUTABLE := true include $(BUILD_EXECUTABLE) +endif diff --git a/mtdutils/bml_over_mtd.c b/mtdutils/bml_over_mtd.c new file mode 100644 index 000000000..c40179281 --- /dev/null +++ b/mtdutils/bml_over_mtd.c @@ -0,0 +1,798 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils/log.h" + +#include + +#include "mtdutils.h" + +typedef struct BmlOverMtdReadContext { + const MtdPartition *partition; + char *buffer; + size_t consumed; + int fd; +} BmlOverMtdReadContext; + +typedef struct BmlOverMtdWriteContext { + const MtdPartition *partition; + char *buffer; + size_t stored; + int fd; + + off_t* bad_block_offsets; + int bad_block_alloc; + int bad_block_count; +} BmlOverMtdWriteContext; + + +static BmlOverMtdReadContext *bml_over_mtd_read_partition(const MtdPartition *partition) +{ + BmlOverMtdReadContext *ctx = (BmlOverMtdReadContext*) malloc(sizeof(BmlOverMtdReadContext)); + if (ctx == NULL) return NULL; + + ctx->buffer = malloc(partition->erase_size); + if (ctx->buffer == NULL) { + free(ctx); + return NULL; + } + + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + ctx->fd = open(mtddevname, O_RDONLY); + if (ctx->fd < 0) { + free(ctx); + free(ctx->buffer); + return NULL; + } + + ctx->partition = partition; + ctx->consumed = partition->erase_size; + return ctx; +} + +static void bml_over_mtd_read_close(BmlOverMtdReadContext *ctx) +{ + close(ctx->fd); + free(ctx->buffer); + free(ctx); +} + +static BmlOverMtdWriteContext *bml_over_mtd_write_partition(const MtdPartition *partition) +{ + BmlOverMtdWriteContext *ctx = (BmlOverMtdWriteContext*) malloc(sizeof(BmlOverMtdWriteContext)); + if (ctx == NULL) return NULL; + + ctx->bad_block_offsets = NULL; + ctx->bad_block_alloc = 0; + ctx->bad_block_count = 0; + + ctx->buffer = malloc(partition->erase_size); + if (ctx->buffer == NULL) { + free(ctx); + return NULL; + } + + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + ctx->fd = open(mtddevname, O_RDWR); + if (ctx->fd < 0) { + free(ctx->buffer); + free(ctx); + return NULL; + } + + ctx->partition = partition; + ctx->stored = 0; + return ctx; +} + +static int bml_over_mtd_write_close(BmlOverMtdWriteContext *ctx) +{ + int r = 0; + if (close(ctx->fd)) r = -1; + free(ctx->bad_block_offsets); + free(ctx->buffer); + free(ctx); + return r; +} + + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "bml_over_mtd" + +#define BLOCK_SIZE 2048 +#define SPARE_SIZE (BLOCK_SIZE >> 5) + +#define EXIT_CODE_BAD_BLOCKS 15 + +static int die(const char *msg, ...) { + int err = errno; + va_list args; + va_start(args, msg); + char buf[1024]; + vsnprintf(buf, sizeof(buf), msg, args); + va_end(args); + + if (err != 0) { + strlcat(buf, ": ", sizeof(buf)); + strlcat(buf, strerror(err), sizeof(buf)); + } + + fprintf(stderr, "%s\n", buf); + return 1; +} + +static unsigned short* CreateEmptyBlockMapping(const MtdPartition* pSrcPart) +{ + size_t srcTotal, srcErase, srcWrite; + if (mtd_partition_info(pSrcPart, &srcTotal, &srcErase, &srcWrite) != 0) + { + fprintf(stderr, "Failed to access partition.\n"); + return NULL; + } + + int numSrcBlocks = srcTotal/srcErase; + + unsigned short* pMapping = malloc(numSrcBlocks * sizeof(unsigned short)); + if (pMapping == NULL) + { + fprintf(stderr, "Failed to allocate block mapping memory.\n"); + return NULL; + } + memset(pMapping, 0xFF, numSrcBlocks * sizeof(unsigned short)); + return pMapping; +} + +static const unsigned short* CreateBlockMapping(const MtdPartition* pSrcPart, int srcPartStartBlock, + const MtdPartition *pReservoirPart, int reservoirPartStartBlock) +{ + size_t srcTotal, srcErase, srcWrite; + if (mtd_partition_info(pSrcPart, &srcTotal, &srcErase, &srcWrite) != 0) + { + fprintf(stderr, "Failed to access partition.\n"); + return NULL; + } + + int numSrcBlocks = srcTotal/srcErase; + + unsigned short* pMapping = malloc(numSrcBlocks * sizeof(unsigned short)); + if (pMapping == NULL) + { + fprintf(stderr, "Failed to allocate block mapping memory.\n"); + return NULL; + } + memset(pMapping, 0xFF, numSrcBlocks * sizeof(unsigned short)); + + size_t total, erase, write; + if (mtd_partition_info(pReservoirPart, &total, &erase, &write) != 0) + { + fprintf(stderr, "Failed to access reservoir partition.\n"); + free(pMapping); + return NULL; + } + + if (erase != srcErase || write != srcWrite) + { + fprintf(stderr, "Source partition and reservoir partition differ in size properties.\n"); + free(pMapping); + return NULL; + } + + printf("Partition info: Total %d, Erase %d, write %d\n", total, erase, write); + + BmlOverMtdReadContext *readctx = bml_over_mtd_read_partition(pReservoirPart); + if (readctx == NULL) + { + fprintf(stderr, "Failed to open reservoir partition for reading.\n"); + free(pMapping); + return NULL; + } + + if (total < erase || total > INT_MAX) + { + fprintf(stderr, "Unsuitable reservoir partition properties.\n"); + free(pMapping); + bml_over_mtd_read_close(readctx); + return NULL; + } + + int foundMappingTable = 0; + + int currOffset = total; //Offset *behind* the last byte + while (currOffset > 0) + { + currOffset -= erase; + loff_t pos = lseek64(readctx->fd, currOffset, SEEK_SET); + int mgbb = ioctl(readctx->fd, MEMGETBADBLOCK, &pos); + if (mgbb != 0) + { + printf("Bad block %d in reservoir area, skipping.\n", currOffset/erase); + continue; + } + ssize_t readBytes = read(readctx->fd, readctx->buffer, erase); + if (readBytes != (ssize_t)erase) + { + fprintf(stderr, "Failed to read good block in reservoir area (%s).\n", + strerror(errno)); + free(pMapping); + bml_over_mtd_read_close(readctx); + return NULL; + } + if (readBytes >= 0x2000) + { + char* buf = readctx->buffer; + if (buf[0]=='U' && buf[1]=='P' && buf[2]=='C' && buf[3]=='H') + { + printf ("Found mapping block mark at 0x%x (block %d).\n", currOffset, currOffset/erase); + + unsigned short* mappings = (unsigned short*) &buf[0x1000]; + if (mappings[0]==0 && mappings[1]==0xffff) + { + printf("Found start of mapping table.\n"); + foundMappingTable = 1; + //Skip first entry (dummy) + unsigned short* mappingEntry = mappings + 2; + while (mappingEntry - mappings < 100 + && mappingEntry[0] != 0xffff) + { + unsigned short rawSrcBlk = mappingEntry[0]; + unsigned short rawDstBlk = mappingEntry[1]; + + printf("Found raw block mapping %d -> %d\n", rawSrcBlk, + rawDstBlk); + + unsigned int srcAbsoluteStartAddress = srcPartStartBlock * erase; + unsigned int resAbsoluteStartAddress = reservoirPartStartBlock * erase; + + int reservoirLastBlock = reservoirPartStartBlock + numSrcBlocks - 1; + if (rawDstBlk < reservoirPartStartBlock + || rawDstBlk*erase >= resAbsoluteStartAddress+currOffset) + { + fprintf(stderr, "Mapped block not within reasonable reservoir area.\n"); + foundMappingTable = 0; + break; + } + + int srcLastBlock = srcPartStartBlock + numSrcBlocks - 1; + if (rawSrcBlk >= srcPartStartBlock && rawSrcBlk <= srcLastBlock) + { + + unsigned short relSrcBlk = rawSrcBlk - srcPartStartBlock; + unsigned short relDstBlk = rawDstBlk - reservoirPartStartBlock; + printf("Partition relative block mapping %d -> %d\n",relSrcBlk, relDstBlk); + + printf("Absolute mapped start addresses 0x%x -> 0x%x\n", + srcAbsoluteStartAddress+relSrcBlk*erase, + resAbsoluteStartAddress+relDstBlk*erase); + printf("Partition relative mapped start addresses 0x%x -> 0x%x\n", + relSrcBlk*erase, relDstBlk*erase); + + //Set mapping entry. For duplicate entries, later entries replace former ones. + //*Assumption*: Bad blocks in reservoir area will not be mapped themselves in + //the mapping table. User partition blocks will not be mapped to bad blocks + //(only) in the reservoir area. This has to be confirmed on a wider range of + //devices. + pMapping[relSrcBlk] = relDstBlk; + + } + mappingEntry+=2; + } + break; //We found the mapping table, no need to search further + } + + + } + } + + } + bml_over_mtd_read_close(readctx); + + if (foundMappingTable == 0) + { + fprintf(stderr, "Cannot find mapping table in reservoir partition.\n"); + free(pMapping); + return NULL; + } + + //Consistency and validity check + int mappingValid = 1; + readctx = bml_over_mtd_read_partition(pSrcPart); + if (readctx == NULL) + { + fprintf(stderr, "Cannot open source partition for reading.\n"); + free(pMapping); + return NULL; + } + int currBlock = 0; + for (;currBlock < numSrcBlocks; ++currBlock) + { + loff_t pos = lseek64(readctx->fd, currBlock*erase, SEEK_SET); + int mgbb = ioctl(readctx->fd, MEMGETBADBLOCK, &pos); + if (mgbb == 0) + { + if (pMapping[currBlock]!=0xffff) + { + fprintf(stderr, "Consistency error: Good block has mapping entry %d -> %d\n", currBlock, pMapping[currBlock]); + mappingValid = 0; + } + } else + { + //Bad block! + if (pMapping[currBlock]==0xffff) + { + fprintf(stderr, "Consistency error: Bad block has no mapping entry \n"); + mappingValid = 0; + } else + { + BmlOverMtdReadContext* reservoirReadCtx = bml_over_mtd_read_partition(pReservoirPart); + if (reservoirReadCtx == 0) + { + fprintf(stderr, "Reservoir partition cannot be opened for reading in consistency check.\n"); + mappingValid = 0; + } else + { + pos = lseek64(reservoirReadCtx->fd, pMapping[currBlock]*erase, SEEK_SET); + mgbb = ioctl(reservoirReadCtx->fd, MEMGETBADBLOCK, &pos); + if (mgbb == 0) + { + printf("Bad block has properly mapped reservoir block %d -> %d\n",currBlock, pMapping[currBlock]); + } + else + { + fprintf(stderr, "Consistency error: Mapped block is bad, too. (%d -> %d)\n",currBlock, pMapping[currBlock]); + mappingValid = 0; + } + + } + bml_over_mtd_read_close(reservoirReadCtx); + } + + } + + } + bml_over_mtd_read_close(readctx); + + + if (!mappingValid) + { + free(pMapping); + return NULL; + } + + return pMapping; +} + +static void ReleaseBlockMapping(const unsigned short* blockMapping) +{ + free((void*)blockMapping); +} + +static int dump_bml_partition(const MtdPartition* pSrcPart, const MtdPartition* pReservoirPart, + const unsigned short* blockMapping, const char* filename) +{ + int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fd < 0) + { + fprintf(stderr, "error opening %s", filename); + return -1; + } + BmlOverMtdReadContext* pSrcRead = bml_over_mtd_read_partition(pSrcPart); + if (pSrcRead == NULL) + { + close(fd); + fprintf(stderr, "dump_bml_partition: Error opening src part for reading.\n"); + return -1; + } + + BmlOverMtdReadContext* pResRead = bml_over_mtd_read_partition(pReservoirPart); + if (pResRead == NULL) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + fprintf(stderr, "dump_bml_partition: Error opening reservoir part for reading.\n"); + return -1; + } + + + int numBlocks = pSrcPart->size / pSrcPart->erase_size; + int currblock = 0; + for (;currblock < numBlocks; ++currblock) + { + int srcFd = -1; + if (blockMapping[currblock] == 0xffff) + { + //Good block, use src partition + srcFd = pSrcRead->fd; + if (lseek64(pSrcRead->fd, currblock*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: lseek in src partition failed\n"); + return -1; + } + } else + { + //Bad block, use mapped block in reservoir partition + srcFd = pResRead->fd; + if (lseek64(pResRead->fd, blockMapping[currblock]*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: lseek in reservoir partition failed\n"); + return -1; + } + } + size_t blockBytesRead = 0; + while (blockBytesRead < pSrcPart->erase_size) + { + ssize_t len = read(srcFd, pSrcRead->buffer + blockBytesRead, + pSrcPart->erase_size - blockBytesRead); + if (len <= 0) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: reading partition failed\n"); + return -1; + } + blockBytesRead += len; + } + + size_t blockBytesWritten = 0; + while (blockBytesWritten < pSrcPart->erase_size) + { + ssize_t len = write(fd, pSrcRead->buffer + blockBytesWritten, + pSrcPart->erase_size - blockBytesWritten); + if (len <= 0) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: writing partition dump file failed\n"); + return -1; + } + blockBytesWritten += len; + } + + } + + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + + if (close(fd)) { + unlink(filename); + printf("error closing %s", filename); + return -1; + } + + return 0; +} + +static ssize_t bml_over_mtd_write_block(int fd, ssize_t erase_size, char* data) +{ + off_t pos = lseek(fd, 0, SEEK_CUR); + if (pos == (off_t) -1) return -1; + + ssize_t size = erase_size; + loff_t bpos = pos; + int ret = ioctl(fd, MEMGETBADBLOCK, &bpos); + if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) { + fprintf(stderr, + "Mapping failure: Trying to write bad block at 0x%08lx (ret %d errno %d)\n", + pos, ret, errno); + return -1; + } + + struct erase_info_user erase_info; + erase_info.start = pos; + erase_info.length = size; + int retry; + for (retry = 0; retry < 2; ++retry) { + if (ioctl(fd, MEMERASE, &erase_info) < 0) { + fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + if (lseek(fd, pos, SEEK_SET) != pos || + write(fd, data, size) != size) { + fprintf(stderr, "mtd: write error at 0x%08lx (%s)\n", + pos, strerror(errno)); + } + + char verify[size]; + if (lseek(fd, pos, SEEK_SET) != pos || + read(fd, verify, size) != size) { + fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + if (memcmp(data, verify, size) != 0) { + fprintf(stderr, "mtd: verification error at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + + if (retry > 0) { + fprintf(stderr, "mtd: wrote block after %d retries\n", retry); + } + fprintf(stderr, "mtd: successfully wrote block at %llx\n", pos); + return size; // Success! + } + + + fprintf(stderr, "mtd: Block at %llx could not be properly written.\n", pos); + // Ran out of space on the device + errno = ENOSPC; + return -1; +} + +static int flash_bml_partition(const MtdPartition* pSrcPart, const MtdPartition* pReservoirPart, + const unsigned short* blockMapping, const char* filename) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) + { + fprintf(stderr, "error opening %s", filename); + return -1; + } + BmlOverMtdWriteContext* pSrcWrite = bml_over_mtd_write_partition(pSrcPart); + if (pSrcWrite == NULL) + { + close(fd); + fprintf(stderr, "flash_bml_partition: Error opening src part for writing.\n"); + return -1; + } + +#ifdef DUMMY_WRITING + close(pSrcWrite->fd); + pSrcWrite->fd = open("/sdcard/srcPartWriteDummy.bin", O_WRONLY|O_CREAT|O_TRUNC, 0666); +#endif + + BmlOverMtdWriteContext* pResWrite = bml_over_mtd_write_partition(pReservoirPart); + if (pResWrite == NULL) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + fprintf(stderr, "flash_bml_partition: Error opening reservoir part for writing.\n"); + return -1; + } +#ifdef DUMMY_WRITING + close(pResWrite->fd); + pResWrite->fd = open("/sdcard/resPartWriteDummy.bin", O_WRONLY|O_CREAT|O_TRUNC, 0666); +#endif + + struct stat fileStat; + if (fstat(fd, &fileStat) != 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: Failed to stat source file.\n"); + return -1; + + } + if (fileStat.st_size > pSrcPart->size) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: Source file too large for target partition.\n"); + return -1; + } + + int numBlocks = (fileStat.st_size + pSrcPart->erase_size - 1) / pSrcPart->erase_size; + int currblock; + for (currblock = 0 ;currblock < numBlocks; ++currblock) + { + memset(pSrcWrite->buffer, 0xFF, pSrcPart->erase_size); + size_t blockBytesRead = 0; + while (blockBytesRead < pSrcPart->erase_size) + { + ssize_t len = read(fd, pSrcWrite->buffer + blockBytesRead, + pSrcPart->erase_size - blockBytesRead); + if (len < 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: read source file failed\n"); + return -1; + } + if (len == 0) + { + //End of file + break; + } + + blockBytesRead += len; + } + + + + int srcFd = -1; + if (blockMapping[currblock] == 0xffff) + { + //Good block, use src partition + srcFd = pSrcWrite->fd; + if (lseek64(pSrcWrite->fd, currblock*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: lseek in src partition failed\n"); + return -1; + } + } else + { + //Bad block, use mapped block in reservoir partition + srcFd = pResWrite->fd; + if (lseek64(pResWrite->fd, blockMapping[currblock]*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: lseek in reservoir partition failed\n"); + return -1; + } + } + size_t blockBytesWritten = 0; + while (blockBytesWritten < pSrcPart->erase_size) + { +#ifdef DUMMY_WRITING + ssize_t len = write(srcFd, pSrcWrite->buffer + blockBytesWritten, + pSrcPart->erase_size - blockBytesWritten); +#else + ssize_t len = bml_over_mtd_write_block(srcFd, pSrcPart->erase_size, pSrcWrite->buffer); +#endif + if (len <= 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: writing to partition failed\n"); + return -1; + } + blockBytesWritten += len; + } + + + } + + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + + if (close(fd)) { + printf("error closing %s", filename); + return -1; + } + + return 0; +} + +static int scan_partition(const MtdPartition* pPart) +{ + BmlOverMtdReadContext* readCtx = bml_over_mtd_read_partition(pPart); + if (readCtx == NULL) + { + fprintf(stderr, "Failed to open partition for reading.\n"); + return -1; + } + + int numBadBlocks = 0; + size_t numBlocks = pPart->size / pPart->erase_size; + size_t currBlock; + for (currBlock = 0; currBlock < numBlocks; ++currBlock) + { + + loff_t pos = currBlock * pPart->erase_size; + int mgbb = ioctl(readCtx->fd, MEMGETBADBLOCK, &pos); + if (mgbb != 0) + { + printf("Bad block %d at 0x%x.\n", currBlock, (unsigned int)pos); + numBadBlocks++; + } + } + + bml_over_mtd_read_close(readCtx); + if (numBadBlocks == 0) + { + printf("No bad blocks.\n"); + return 0; + } + return -1 ; +} + +int main(int argc, char **argv) +{ + if (argc != 7 && (argc != 3 || (argc == 3 && strcmp(argv[1],"scan"))!=0) + && (argc != 6 || (argc == 6 && strcmp(argv[1],"scan"))!=0)) + return die("Usage: %s dump|flash \n" + "E.g. %s dump boot 72 reservoir 2004 file.bin\n" + "Usage: %s scan [ ]\n" + ,argv[0], argv[0], argv[0]); + int num_partitions = mtd_scan_partitions(); + const MtdPartition *pSrcPart = mtd_find_partition_by_name(argv[2]); + if (pSrcPart == NULL) + return die("Cannot find partition %s", argv[2]); + + int scanResult = scan_partition(pSrcPart); + + if (argc == 3 && strcmp(argv[1],"scan")==0) + { + return (scanResult == 0 ? 0 : EXIT_CODE_BAD_BLOCKS); + } + + int retVal = 0; + const MtdPartition* pReservoirPart = mtd_find_partition_by_name(argv[4]); + if (pReservoirPart == NULL) + return die("Cannot find partition %s", argv[4]); + + int srcPartStartBlock = atoi(argv[3]); + int reservoirPartStartBlock = atoi(argv[5]); + const unsigned short* pMapping = CreateBlockMapping(pSrcPart, srcPartStartBlock, + pReservoirPart, reservoirPartStartBlock); + + if (pMapping == NULL && scanResult == 0) + { + printf("Generating empty block mapping table for error-free partition.\n"); + pMapping = CreateEmptyBlockMapping(pSrcPart); + } + + if (argc == 6 && strcmp(argv[1],"scan")==0) + { + retVal = (scanResult == 0 ? 0 : EXIT_CODE_BAD_BLOCKS); + } + + if (pMapping == NULL) + return die("Failed to create block mapping table"); + + if (strcmp(argv[1],"dump")==0) + { + retVal = dump_bml_partition(pSrcPart, pReservoirPart, pMapping, argv[6]); + if (retVal == 0) + printf("Successfully dumped partition to %s\n", argv[6]); + } + + if (strcmp(argv[1],"flash")==0) + { + retVal = flash_bml_partition(pSrcPart, pReservoirPart, pMapping, argv[6]); + if (retVal == 0) + printf("Successfully wrote %s to partition\n", argv[6]); + + } + + + ReleaseBlockMapping(pMapping); + return retVal; +} + diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c index e4d2a6064..8b8725fbf 100644 --- a/mtdutils/mtdutils.c +++ b/mtdutils/mtdutils.c @@ -28,13 +28,6 @@ #include "mtdutils.h" -struct MtdPartition { - int device_index; - unsigned int size; - unsigned int erase_size; - char *name; -}; - struct MtdReadContext { const MtdPartition *partition; char *buffer; @@ -345,7 +338,7 @@ ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len) read += ctx->partition->erase_size; } - if (read >= len) { + if (read >= (int)len) { return read; } @@ -413,6 +406,11 @@ static int write_block(MtdWriteContext *ctx, const char *data) if (pos == (off_t) -1) return 1; ssize_t size = partition->erase_size; + + char *verify = malloc(size); + if (verify == NULL) + return 1; + while (pos + size <= (int) partition->size) { loff_t bpos = pos; int ret = ioctl(fd, MEMGETBADBLOCK, &bpos); @@ -441,7 +439,6 @@ static int write_block(MtdWriteContext *ctx, const char *data) pos, strerror(errno)); } - char verify[size]; if (lseek(fd, pos, SEEK_SET) != pos || read(fd, verify, size) != size) { fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n", @@ -458,6 +455,7 @@ static int write_block(MtdWriteContext *ctx, const char *data) fprintf(stderr, "mtd: wrote block after %d retries\n", retry); } fprintf(stderr, "mtd: successfully wrote block at %llx\n", pos); + free(verify); return 0; // Success! } @@ -468,6 +466,8 @@ static int write_block(MtdWriteContext *ctx, const char *data) pos += partition->erase_size; } + free(verify); + // Ran out of space on the device errno = ENOSPC; return -1; @@ -569,3 +569,209 @@ off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos) { } return pos; } + +#define BLOCK_SIZE 2048 +#define SPARE_SIZE (BLOCK_SIZE >> 5) +#define HEADER_SIZE 2048 + +int cmd_mtd_restore_raw_partition(const char *partition_name, const char *filename) +{ + const MtdPartition *ptn; + MtdWriteContext *write; + void *data; + + FILE* f = fopen(filename, "rb"); + if (f == NULL) { + fprintf(stderr, "error opening %s", filename); + return -1; + } + + if (mtd_scan_partitions() <= 0) + { + fprintf(stderr, "error scanning partitions"); + return -1; + } + const MtdPartition *mtd = mtd_find_partition_by_name(partition_name); + if (mtd == NULL) + { + fprintf(stderr, "can't find %s partition", partition_name); + return -1; + } + + int fd = open(filename, O_RDONLY); + if (fd < 0) + { + printf("error opening %s", filename); + return -1; + } + + MtdWriteContext* ctx = mtd_write_partition(mtd); + if (ctx == NULL) { + printf("error writing %s", partition_name); + return -1; + } + + int success = 1; + char* buffer = malloc(BUFSIZ); + int read; + while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) { + int wrote = mtd_write_data(ctx, buffer, read); + success = success && (wrote == read); + } + free(buffer); + fclose(f); + + if (!success) { + fprintf(stderr, "error writing %s", partition_name); + return -1; + } + + if (mtd_erase_blocks(ctx, -1) == -1) { + fprintf(stderr, "error erasing blocks of %s\n", partition_name); + } + if (mtd_write_close(ctx) != 0) { + fprintf(stderr, "error closing write of %s\n", partition_name); + } + printf("%s %s partition\n", success ? "wrote" : "failed to write", partition_name); + return 0; +} + + +int cmd_mtd_backup_raw_partition(const char *partition_name, const char *filename) +{ + MtdReadContext *in; + const MtdPartition *partition; + char buf[BLOCK_SIZE + SPARE_SIZE]; + size_t partition_size; + size_t read_size; + size_t total; + int fd; + int wrote; + int len; + + if (mtd_scan_partitions() <= 0) + { + printf("error scanning partitions"); + return -1; + } + + partition = mtd_find_partition_by_name(partition_name); + if (partition == NULL) + { + printf("can't find %s partition", partition_name); + return -1; + } + + if (mtd_partition_info(partition, &partition_size, NULL, NULL)) { + printf("can't get info of partition %s", partition_name); + return -1; + } + + if (!strcmp(filename, "-")) { + fd = fileno(stdout); + } + else { + fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); + } + + if (fd < 0) + { + printf("error opening %s", filename); + return -1; + } + + in = mtd_read_partition(partition); + if (in == NULL) { + close(fd); + unlink(filename); + printf("error opening %s: %s\n", partition_name, strerror(errno)); + return -1; + } + + total = 0; + while ((len = mtd_read_data(in, buf, BLOCK_SIZE)) > 0) { + wrote = write(fd, buf, len); + if (wrote != len) { + close(fd); + unlink(filename); + printf("error writing %s", filename); + return -1; + } + total += BLOCK_SIZE; + } + + mtd_read_close(in); + + if (close(fd)) { + unlink(filename); + printf("error closing %s", filename); + return -1; + } + return 0; +} + +int cmd_mtd_erase_raw_partition(const char *partition_name) +{ + MtdWriteContext *out; + size_t erased; + size_t total_size; + size_t erase_size; + + if (mtd_scan_partitions() <= 0) + { + printf("error scanning partitions"); + return -1; + } + const MtdPartition *p = mtd_find_partition_by_name(partition_name); + if (p == NULL) + { + printf("can't find %s partition", partition_name); + return -1; + } + + out = mtd_write_partition(p); + if (out == NULL) + { + printf("could not estabilish write context for %s", partition_name); + return -1; + } + + // do the actual erase, -1 = full partition erase + erased = mtd_erase_blocks(out, -1); + + // erased = bytes erased, if zero, something borked + if (!erased) + { + printf("error erasing %s", partition_name); + return -1; + } + + return 0; +} + +int cmd_mtd_erase_partition(const char *partition, const char *filesystem) +{ + return cmd_mtd_erase_raw_partition(partition); +} + + +int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only) +{ + mtd_scan_partitions(); + const MtdPartition *p; + p = mtd_find_partition_by_name(partition); + if (p == NULL) { + return -1; + } + return mtd_mount_partition(p, mount_point, filesystem, read_only); +} + +int cmd_mtd_get_partition_device(const char *partition, char *device) +{ + mtd_scan_partitions(); + MtdPartition *p = mtd_find_partition_by_name(partition); + if (p == NULL) + return -1; + sprintf(device, "/dev/block/mtdblock%d", p->device_index); + return 0; +} diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h index 45d3ebc91..c57d45d50 100644 --- a/mtdutils/mtdutils.h +++ b/mtdutils/mtdutils.h @@ -53,4 +53,11 @@ off_t mtd_erase_blocks(MtdWriteContext *, int blocks); /* 0 ok, -1 for all */ off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos); int mtd_write_close(MtdWriteContext *); +struct MtdPartition { + int device_index; + unsigned int size; + unsigned int erase_size; + char *name; +}; + #endif // MTDUTILS_H_ diff --git a/nandroid.c b/nandroid.c new file mode 100644 index 000000000..8ddd4c2f4 --- /dev/null +++ b/nandroid.c @@ -0,0 +1,1040 @@ +/* + * Copyright (C) 2014 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bootloader.h" +#include "common.h" +#include "cutils/properties.h" +#include "extendedcommands.h" +#include "firmware.h" +#include "flashutils/flashutils.h" +#include "install.h" +#include "libcrecovery/common.h" +#include "minui/minui.h" +#include "minzip/DirUtil.h" +#include "mounts.h" +#include "nandroid.h" +#include "nandroid_md5.h" +#include "recovery_settings.h" +#include "recovery_ui.h" +#include "roots.h" + +#define NANDROID_FIELD_DEDUPE_CLEARED_SPACE 1 + +typedef void (*file_event_callback)(const char* filename); +typedef int (*nandroid_backup_handler)(const char* backup_path, const char* backup_file_image, int callback); +typedef int (*nandroid_restore_handler)(const char* backup_file_image, const char* backup_path, int callback); + +static int nandroid_backup_bitfield = 0; +static unsigned int nandroid_files_total = 0; +static unsigned int nandroid_files_count = 0; + +static void nandroid_generate_timestamp_path(char* backup_path) { + time_t t = time(NULL); + struct tm *tmp = localtime(&t); + if (tmp == NULL) { + struct timeval tp; + gettimeofday(&tp, NULL); + snprintf(backup_path, PATH_MAX, "%s/clockworkmod/backup/%ld", get_primary_storage_path(), tp.tv_sec); + } else { + char str[PATH_MAX]; + strftime(str, PATH_MAX, "clockworkmod/backup/%F.%H.%M.%S", tmp); + snprintf(backup_path, PATH_MAX, "%s/%s", get_primary_storage_path(), str); + } +} + +static void ensure_directory(const char* dir) { + char tmp[PATH_MAX]; + sprintf(tmp, "mkdir -p %s && chmod 777 %s", dir, dir); + __system(tmp); +} + +static int print_and_error(const char* message, int ret) { + ui_reset_progress(); + ui_set_background(BACKGROUND_ICON_ERROR); + if (message != NULL) + LOGE("%s", message); // Assumes message has line termination + + return ret; +} + +static void nandroid_callback(const char* filename) { + if (filename == NULL) + return; + + char tmp[PATH_MAX]; + strcpy(tmp, filename); + if (tmp[strlen(tmp) - 1] == '\n') + tmp[strlen(tmp) - 1] = '\0'; + LOGI("%s\n", tmp); + + if (nandroid_files_total != 0) { + nandroid_files_count++; + float progress_decimal = (float)((double)nandroid_files_count / + (double)nandroid_files_total); + ui_set_progress(progress_decimal); + } +} + +static void compute_directory_stats(const char* directory) { + char tmp[PATH_MAX]; + char count_text[100]; + + // reset file count if we ever return before setting it + nandroid_files_count = 0; + nandroid_files_total = 0; + + sprintf(tmp, "find %s | %s wc -l > /tmp/dircount", directory, strcmp(directory, "/data") == 0 && is_data_media() ? "grep -v /data/media |" : ""); + __system(tmp); + + FILE* f = fopen("/tmp/dircount", "r"); + if (f == NULL) + return; + + if (fgets(count_text, sizeof(count_text), f) == NULL) { + fclose(f); + return; + } + + size_t len = strlen(count_text); + if (count_text[len - 1] == '\n') + count_text[len - 1] = '\0'; + + fclose(f); + nandroid_files_total = atoi(count_text); + ui_reset_progress(); + ui_show_progress(1, 0); +} + +static int mkyaffs2image_wrapper(const char* backup_path, const char* backup_file_image, int callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd %s ; mkyaffs2image . %s.img ; exit $?", backup_path, backup_file_image); + + FILE *fp = __popen(tmp, "r"); + if (fp == NULL) { + ui_print("Unable to execute mkyaffs2image.\n"); + return -1; + } + + while (fgets(tmp, PATH_MAX, fp) != NULL) { + tmp[PATH_MAX - 1] = '\0'; + if (callback) + nandroid_callback(tmp); + } + + return __pclose(fp); +} + +static int do_tar_compress(char* command, int callback) { + char buf[PATH_MAX]; + + set_perf_mode(1); + FILE *fp = __popen(command, "r"); + if (fp == NULL) { + ui_print("Unable to execute tar command!\n"); + set_perf_mode(0); + return -1; + } + + while (fgets(buf, PATH_MAX, fp) != NULL) { + buf[PATH_MAX - 1] = '\0'; + if (callback) + nandroid_callback(buf); + } + + set_perf_mode(0); + return __pclose(fp); +} + +static int tar_compress_wrapper(const char* backup_path, const char* backup_file_image, int callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd $(dirname %s) ; touch %s.tar ; set -o pipefail ; (tar -cpv --exclude=data/data/com.google.android.music/files/* %s $(basename %s) | split -a 1 -b 1000000000 /proc/self/fd/0 %s.tar.) 2> /proc/self/fd/1 ; exit $?", backup_path, backup_file_image, strcmp(backup_path, "/data") == 0 && is_data_media() ? "--exclude=data/media" : "", backup_path, backup_file_image); + + return do_tar_compress(tmp, callback); +} + +static int tar_gzip_compress_wrapper(const char* backup_path, const char* backup_file_image, int callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd $(dirname %s) ; touch %s.tar.gz ; set -o pipefail ; (tar -cpv --exclude=data/data/com.google.android.music/files/* %s $(basename %s) | pigz -c | split -a 1 -b 1000000000 /proc/self/fd/0 %s.tar.gz.) 2> /proc/self/fd/1 ; exit $?", backup_path, backup_file_image, strcmp(backup_path, "/data") == 0 && is_data_media() ? "--exclude=data/media" : "", backup_path, backup_file_image); + + return do_tar_compress(tmp, callback); +} + +static int tar_dump_wrapper(const char* backup_path, const char* backup_file_image, int callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd $(dirname %s); set -o pipefail ; tar -cpv --exclude=data/data/com.google.android.music/files/* %s $(basename %s) 2> /dev/null | cat", backup_path, strcmp(backup_path, "/data") == 0 && is_data_media() ? "--exclude=data/media" : "", backup_path); + + return __system(tmp); +} + +void nandroid_dedupe_gc(const char* blob_dir) { + char backup_dir[PATH_MAX]; + strcpy(backup_dir, blob_dir); + char *d = dirname(backup_dir); + strcpy(backup_dir, d); + strcat(backup_dir, "/backup"); + ui_print("Freeing space...\n"); + char tmp[PATH_MAX]; + sprintf(tmp, "dedupe gc %s $(find %s -name '*.dup')", blob_dir, backup_dir); + __system(tmp); + ui_print("Done freeing space.\n"); +} + +static int dedupe_compress_wrapper(const char* backup_path, const char* backup_file_image, int callback) { + char tmp[PATH_MAX]; + char blob_dir[PATH_MAX]; + strcpy(blob_dir, backup_file_image); + char *d = dirname(blob_dir); + strcpy(blob_dir, d); + d = dirname(blob_dir); + strcpy(blob_dir, d); + d = dirname(blob_dir); + strcpy(blob_dir, d); + strcat(blob_dir, "/blobs"); + ensure_directory(blob_dir); + + if (!(nandroid_backup_bitfield & NANDROID_FIELD_DEDUPE_CLEARED_SPACE)) { + nandroid_backup_bitfield |= NANDROID_FIELD_DEDUPE_CLEARED_SPACE; + nandroid_dedupe_gc(blob_dir); + } + + sprintf(tmp, "dedupe c %s %s %s.dup %s", backup_path, blob_dir, backup_file_image, strcmp(backup_path, "/data") == 0 && is_data_media() ? "./media" : ""); + + FILE *fp = __popen(tmp, "r"); + if (fp == NULL) { + ui_print("Unable to execute dedupe.\n"); + return -1; + } + + while (fgets(tmp, PATH_MAX, fp) != NULL) { + tmp[PATH_MAX - 1] = '\0'; + if (callback) + nandroid_callback(tmp); + } + + return __pclose(fp); +} + +static void build_configuration_path(char *path_buf, const char *file) { + sprintf(path_buf, "%s%s%s", get_primary_storage_path(), (is_data_media() ? "/0/" : "/"), file); +} + +static nandroid_backup_handler default_backup_handler = tar_compress_wrapper; +static char forced_backup_format[5] = ""; +void nandroid_force_backup_format(const char* fmt) { + strcpy(forced_backup_format, fmt); +} + +static void refresh_default_backup_handler() { + char fmt[5]; + if (strlen(forced_backup_format) > 0) { + strcpy(fmt, forced_backup_format); + } else { + char path[PATH_MAX]; + + build_configuration_path(path, NANDROID_BACKUP_FORMAT_FILE); + ensure_path_mounted(path); + FILE* f = fopen(path, "r"); + if (NULL == f) { + default_backup_handler = tar_compress_wrapper; + return; + } + fread(fmt, 1, sizeof(fmt), f); + fclose(f); + } + + fmt[3] = '\0'; + if (0 == strcmp(fmt, "dup")) + default_backup_handler = dedupe_compress_wrapper; + else if (0 == strcmp(fmt, "tgz")) + default_backup_handler = tar_gzip_compress_wrapper; + else if (0 == strcmp(fmt, "tar")) + default_backup_handler = tar_compress_wrapper; + else + default_backup_handler = tar_compress_wrapper; +} + +unsigned int nandroid_get_default_backup_format() { + refresh_default_backup_handler(); + if (default_backup_handler == dedupe_compress_wrapper) { + return NANDROID_BACKUP_FORMAT_DUP; + } else if (default_backup_handler == tar_gzip_compress_wrapper) { + return NANDROID_BACKUP_FORMAT_TGZ; + } else { + return NANDROID_BACKUP_FORMAT_TAR; + } +} + +static nandroid_backup_handler get_backup_handler(const char *backup_path) { + Volume *v = volume_for_path(backup_path); + if (v == NULL) { + ui_print("Unable to find volume.\n"); + return NULL; + } + const MountedVolume *mv = find_mounted_volume_by_mount_point(v->mount_point); + if (mv == NULL) { + ui_print("Unable to find mounted volume: %s\n", v->mount_point); + return NULL; + } + + if (strcmp(backup_path, "/data") == 0 && is_data_media()) { + return default_backup_handler; + } + + if (strlen(forced_backup_format) > 0) + return default_backup_handler; + + // Disable tar backups of yaffs2 by default + char prefer_tar[PROPERTY_VALUE_MAX]; + property_get("ro.cwm.prefer_tar", prefer_tar, "false"); + if (strcmp("yaffs2", mv->filesystem) == 0 && strcmp("false", prefer_tar) == 0) { + return mkyaffs2image_wrapper; + } + + return default_backup_handler; +} + +static int nandroid_backup_partition_extended(const char* backup_path, const char* mount_point, int umount_when_finished) { + int ret = 0; + char name[PATH_MAX]; + char tmp[PATH_MAX]; + strcpy(name, basename(mount_point)); + + struct stat file_info; + build_configuration_path(tmp, NANDROID_HIDE_PROGRESS_FILE); + ensure_path_mounted(tmp); + int callback = stat(tmp, &file_info) != 0; + + ui_print("Backing up %s...\n", name); + if (0 != (ret = ensure_path_mounted(mount_point) != 0)) { + ui_print("Can't mount %s!\n", mount_point); + return ret; + } + compute_directory_stats(mount_point); + scan_mounted_volumes(); + Volume *v = volume_for_path(mount_point); + const MountedVolume *mv = NULL; + if (v != NULL) + mv = find_mounted_volume_by_mount_point(v->mount_point); + + if (strcmp(backup_path, "-") == 0) + sprintf(tmp, "/proc/self/fd/1"); + else if (mv == NULL || mv->filesystem == NULL) + sprintf(tmp, "%s/%s.auto", backup_path, name); + else + sprintf(tmp, "%s/%s.%s", backup_path, name, mv->filesystem); + nandroid_backup_handler backup_handler = get_backup_handler(mount_point); + + if (backup_handler == NULL) { + ui_print("Error finding an appropriate backup handler.\n"); + return -2; + } + ret = backup_handler(mount_point, tmp, callback); + if (umount_when_finished) { + ensure_path_unmounted(mount_point); + } + if (0 != ret) { + ui_print("Error while making a backup image of %s!\n", mount_point); + return ret; + } + ui_print("Backup of %s completed.\n", name); + return 0; +} + +static int nandroid_backup_partition(const char* backup_path, const char* root) { + Volume *vol = volume_for_path(root); + // make sure the volume exists before attempting anything... + if (vol == NULL || vol->fs_type == NULL) + return 0; + + // see if we need a raw backup (mtd) + char tmp[PATH_MAX]; + int ret; + if (strcmp(vol->fs_type, "mtd") == 0 || + strcmp(vol->fs_type, "bml") == 0 || + strcmp(vol->fs_type, "emmc") == 0) { + const char* name = basename(root); + if (strcmp(backup_path, "-") == 0) + strcpy(tmp, "/proc/self/fd/1"); + else + sprintf(tmp, "%s/%s.img", backup_path, name); + + ui_print("Backing up %s image...\n", name); + if (0 != (ret = backup_raw_partition(vol->fs_type, vol->blk_device, tmp))) { + ui_print("Error while backing up %s image!", name); + return ret; + } + + ui_print("Backup of %s image completed.\n", name); + return 0; + } + + return nandroid_backup_partition_extended(backup_path, root, 1); +} + +int nandroid_backup(const char* backup_path) { + nandroid_backup_bitfield = 0; + refresh_default_backup_handler(); + + if (ensure_path_mounted(backup_path) != 0) { + return print_and_error("Can't mount backup path.\n", NANDROID_ERROR_GENERAL); + } + + Volume* volume; + if (is_data_media_volume_path(backup_path)) + volume = volume_for_path("/data"); + else + volume = volume_for_path(backup_path); + if (NULL == volume) + return print_and_error("Unable to find volume for backup path.\n", NANDROID_ERROR_GENERAL); + int ret; + struct statfs sfs; + struct stat s; + if (NULL != volume) { + if (0 != (ret = statfs(volume->mount_point, &sfs))) + return print_and_error("Unable to stat backup path.\n", ret); + uint64_t bavail = sfs.f_bavail; + uint64_t bsize = sfs.f_bsize; + uint64_t sdcard_free = bavail * bsize; + uint64_t sdcard_free_mb = sdcard_free / (uint64_t)(1024 * 1024); + ui_print("SD Card space free: %lluMB\n", sdcard_free_mb); + if (sdcard_free_mb < 150) + ui_print("There may not be enough free space to complete backup... continuing...\n"); + } + char tmp[PATH_MAX]; + ensure_directory(backup_path); + ui_set_background(BACKGROUND_ICON_INSTALLING); + + if (0 != (ret = nandroid_backup_partition(backup_path, "/boot"))) + return print_and_error(NULL, ret); + + if (0 != (ret = nandroid_backup_partition(backup_path, "/recovery"))) + return print_and_error(NULL, ret); + + Volume *vol = volume_for_path("/wimax"); + if (vol != NULL && 0 == stat(vol->blk_device, &s)) { + char serialno[PROPERTY_VALUE_MAX]; + ui_print("Backing up WiMAX...\n"); + serialno[0] = 0; + property_get("ro.serialno", serialno, ""); + sprintf(tmp, "%s/wimax.%s.img", backup_path, serialno); + ret = backup_raw_partition(vol->fs_type, vol->blk_device, tmp); + if (0 != ret) + return print_and_error("Error while dumping WiMAX image!\n", NANDROID_ERROR_GENERAL); + } + + if (0 != (ret = nandroid_backup_partition(backup_path, "/system"))) + return print_and_error(NULL, ret); + + if (0 != (ret = nandroid_backup_partition(backup_path, "/data"))) + return print_and_error(NULL, ret); + + if (has_datadata()) { + if (0 != (ret = nandroid_backup_partition(backup_path, "/datadata"))) + return print_and_error(NULL, ret); + } + + if (is_data_media() || 0 != stat(get_android_secure_path(), &s)) { + ui_print("No .android_secure found. Skipping backup of applications on external storage.\n"); + } else { + if (0 != (ret = nandroid_backup_partition_extended(backup_path, get_android_secure_path(), 0))) + return print_and_error(NULL, ret); + } + + if (0 != (ret = nandroid_backup_partition_extended(backup_path, "/cache", 0))) + return print_and_error(NULL, ret); + + vol = volume_for_path("/sd-ext"); + if (vol == NULL || 0 != stat(vol->blk_device, &s)) { + LOGI("No sd-ext found. Skipping backup of sd-ext.\n"); + } else { + if (0 != ensure_path_mounted("/sd-ext")) + LOGI("Could not mount sd-ext. sd-ext backup may not be supported on this device. Skipping backup of sd-ext.\n"); + else if (0 != (ret = nandroid_backup_partition(backup_path, "/sd-ext"))) + return print_and_error(NULL, ret); + } + + if (0 != (ret = nandroid_backup_md5_gen(backup_path))) + return print_and_error(NULL, ret); + + sprintf(tmp, "cp /tmp/recovery.log %s/recovery.log", backup_path); + __system(tmp); + + char base_dir[PATH_MAX]; + strcpy(base_dir, backup_path); + char *d = dirname(base_dir); + strcpy(base_dir, d); + d = dirname(base_dir); + strcpy(base_dir, d); + + sprintf(tmp, "chmod -R 777 %s ; chmod -R u+r,u+w,g+r,g+w,o+r,o+w %s ; chmod u+x,g+x,o+x %s/backup", backup_path, base_dir, base_dir); + __system(tmp); + + sprintf(tmp, "if [ -d %s/blobs ]; then chmod u+x,g+x,o+x %s/blobs; fi", base_dir, base_dir); + __system(tmp); + + sync(); + ui_set_background(BACKGROUND_ICON_CLOCKWORK); + ui_reset_progress(); + ui_print("\nBackup complete!\n"); + return 0; +} + +static int nandroid_dump(const char* partition) { + // silence our ui_print statements and other logging + ui_set_log_stdout(0); + + nandroid_backup_bitfield = 0; + refresh_default_backup_handler(); + + // override our default to be the basic tar dumper + default_backup_handler = tar_dump_wrapper; + + if (strcmp(partition, "boot") == 0) { + Volume *vol = volume_for_path("/boot"); + // make sure the volume exists before attempting anything... + if (vol == NULL || vol->fs_type == NULL) + return 1; + char cmd[PATH_MAX]; + sprintf(cmd, "cat %s", vol->blk_device); + return __system(cmd); + // return nandroid_backup_partition("-", "/boot"); + } + + if (strcmp(partition, "recovery") == 0) { + return __system("set -o pipefail ; dump_image recovery /proc/self/fd/1 | cat"); + } + + if (strcmp(partition, "data") == 0) { + return nandroid_backup_partition("-", "/data"); + } + + if (strcmp(partition, "system") == 0) { + return nandroid_backup_partition("-", "/system"); + } + + return 1; +} + +static int unyaffs_wrapper(const char* backup_file_image, const char* backup_path, int callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd %s ; unyaffs %s ; exit $?", backup_path, backup_file_image); + FILE *fp = __popen(tmp, "r"); + if (fp == NULL) { + ui_print("Unable to execute unyaffs.\n"); + return -1; + } + + while (fgets(tmp, PATH_MAX, fp) != NULL) { + tmp[PATH_MAX - 1] = '\0'; + if (callback) + nandroid_callback(tmp); + } + + return __pclose(fp); +} + +static int do_tar_extract(char* command, int callback) { + char buf[PATH_MAX]; + + set_perf_mode(1); + FILE *fp = __popen(command, "r"); + if (fp == NULL) { + ui_print("Unable to execute tar command.\n"); + set_perf_mode(0); + return -1; + } + + while (fgets(buf, PATH_MAX, fp) != NULL) { + buf[PATH_MAX - 1] = '\0'; + if (callback) + nandroid_callback(buf); + } + + set_perf_mode(0); + return __pclose(fp); +} + +static int tar_gzip_extract_wrapper(const char* backup_file_image, const char* backup_path, int callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd $(dirname %s) ; set -o pipefail ; cat %s* | pigz -d -c | tar -xpv ; exit $?", backup_path, backup_file_image); + + return do_tar_extract(tmp, callback); +} + +static int tar_extract_wrapper(const char* backup_file_image, const char* backup_path, int callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd $(dirname %s) ; set -o pipefail ; cat %s* | tar -xpv ; exit $?", backup_path, backup_file_image); + + return do_tar_extract(tmp, callback); +} + +static int dedupe_extract_wrapper(const char* backup_file_image, const char* backup_path, int callback) { + char tmp[PATH_MAX]; + char blob_dir[PATH_MAX]; + strcpy(blob_dir, backup_file_image); + char *bd = dirname(blob_dir); + strcpy(blob_dir, bd); + bd = dirname(blob_dir); + strcpy(blob_dir, bd); + bd = dirname(blob_dir); + sprintf(tmp, "dedupe x %s %s/blobs %s; exit $?", backup_file_image, bd, backup_path); + + char path[PATH_MAX]; + FILE *fp = __popen(tmp, "r"); + if (fp == NULL) { + ui_print("Unable to execute dedupe.\n"); + return -1; + } + + while (fgets(path, PATH_MAX, fp) != NULL) { + if (callback) + nandroid_callback(path); + } + + return __pclose(fp); +} + +static int tar_undump_wrapper(const char* backup_file_image, const char* backup_path, int callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd $(dirname %s) ; tar -xpv ", backup_path); + + return __system(tmp); +} + +static nandroid_restore_handler get_restore_handler(const char *backup_path) { + Volume *v = volume_for_path(backup_path); + if (v == NULL) { + ui_print("Unable to find volume.\n"); + return NULL; + } + scan_mounted_volumes(); + const MountedVolume *mv = find_mounted_volume_by_mount_point(v->mount_point); + if (mv == NULL) { + ui_print("Unable to find mounted volume: %s\n", v->mount_point); + return NULL; + } + + if (strcmp(backup_path, "/data") == 0 && is_data_media()) { + return tar_extract_wrapper; + } + + // Disable tar backups of yaffs2 by default + char prefer_tar[PROPERTY_VALUE_MAX]; + property_get("ro.cwm.prefer_tar", prefer_tar, "false"); + if (strcmp("yaffs2", mv->filesystem) == 0 && strcmp("false", prefer_tar) == 0) { + return unyaffs_wrapper; + } + + return tar_extract_wrapper; +} + +static int nandroid_restore_partition_extended(const char* backup_path, const char* mount_point, int umount_when_finished) { + int ret = 0; + char* name = basename(mount_point); + + nandroid_restore_handler restore_handler = NULL; + const char *filesystems[] = { "yaffs2", "ext2", "ext3", "ext4", "vfat", "rfs", "f2fs", NULL }; + const char* backup_filesystem = NULL; + Volume *vol = volume_for_path(mount_point); + const char *device = NULL; + if (vol != NULL) + device = vol->blk_device; + + char tmp[PATH_MAX]; + sprintf(tmp, "%s/%s.img", backup_path, name); + struct stat file_info; + if (strcmp(backup_path, "-") == 0) { + if (vol) + backup_filesystem = vol->fs_type; + restore_handler = tar_extract_wrapper; + strcpy(tmp, "/proc/self/fd/0"); + } else if (0 != (ret = stat(tmp, &file_info))) { + // can't find the backup, it may be the new backup format? + // iterate through the backup types + printf("couldn't find default\n"); + const char *filesystem; + int i = 0; + while ((filesystem = filesystems[i]) != NULL) { + sprintf(tmp, "%s/%s.%s.img", backup_path, name, filesystem); + if (0 == (ret = stat(tmp, &file_info))) { + backup_filesystem = filesystem; + restore_handler = unyaffs_wrapper; + break; + } + sprintf(tmp, "%s/%s.%s.tar", backup_path, name, filesystem); + if (0 == (ret = stat(tmp, &file_info))) { + backup_filesystem = filesystem; + restore_handler = tar_extract_wrapper; + break; + } + sprintf(tmp, "%s/%s.%s.tar.gz", backup_path, name, filesystem); + if (0 == (ret = stat(tmp, &file_info))) { + backup_filesystem = filesystem; + restore_handler = tar_gzip_extract_wrapper; + break; + } + sprintf(tmp, "%s/%s.%s.dup", backup_path, name, filesystem); + if (0 == (ret = stat(tmp, &file_info))) { + backup_filesystem = filesystem; + restore_handler = dedupe_extract_wrapper; + break; + } + i++; + } + + if (backup_filesystem == NULL || restore_handler == NULL) { + ui_print("%s.img not found. Skipping restore of %s.\n", name, mount_point); + return 0; + } else { + printf("Found new backup image: %s\n", tmp); + } + } + // If the fs_type of this volume is "auto" or mount_point is /data + // and is_data_media, let's revert + // to using a rm -rf, rather than trying to do a + // ext3/ext4/whatever format. + // This is because some phones (like DroidX) will freak out if you + // reformat the /system or /data partitions, and not boot due to + // a locked bootloader. + // Other devices, like the Galaxy Nexus, XOOM, and Galaxy Tab 10.1 + // have a /sdcard symlinked to /data/media. + // Or of volume does not exist (.android_secure), just rm -rf. + if (vol == NULL || 0 == strcmp(vol->fs_type, "auto")) + backup_filesystem = NULL; + else if (0 == strcmp(vol->mount_point, "/data") && is_data_media()) + backup_filesystem = NULL; + + ensure_directory(mount_point); + + char path[PATH_MAX]; + build_configuration_path(path, NANDROID_HIDE_PROGRESS_FILE); + ensure_path_mounted(path); + int callback = stat(path, &file_info) != 0; + + ui_print("Restoring %s...\n", name); + if (backup_filesystem == NULL) { + if (0 != (ret = format_volume(mount_point))) { + ui_print("Error while formatting %s!\n", mount_point); + return ret; + } + } else if (0 != (ret = format_device(device, mount_point, backup_filesystem))) { + ui_print("Error while formatting %s!\n", mount_point); + return ret; + } + + if (0 != (ret = ensure_path_mounted(mount_point))) { + ui_print("Can't mount %s!\n", mount_point); + return ret; + } + + if (restore_handler == NULL) + restore_handler = get_restore_handler(mount_point); + + // override restore handler for undump + if (strcmp(backup_path, "-") == 0) { + restore_handler = tar_undump_wrapper; + } + + if (restore_handler == NULL) { + ui_print("Error finding an appropriate restore handler.\n"); + return -2; + } + + if (0 != (ret = restore_handler(tmp, mount_point, callback))) { + ui_print("Error while restoring %s!\n", mount_point); + return ret; + } + + if (umount_when_finished) { + ensure_path_unmounted(mount_point); + } + + return 0; +} + +static int nandroid_restore_partition(const char* backup_path, const char* root) { + Volume *vol = volume_for_path(root); + // make sure the volume exists... + if (vol == NULL || vol->fs_type == NULL) + return 0; + + // see if we need a raw restore (mtd) + char tmp[PATH_MAX]; + if (strcmp(vol->fs_type, "mtd") == 0 || + strcmp(vol->fs_type, "bml") == 0 || + strcmp(vol->fs_type, "emmc") == 0) { + int ret; + const char* name = basename(root); + ui_print("Erasing %s before restore...\n", name); + if (0 != (ret = format_volume(root))) { + ui_print("Error while erasing %s image!", name); + return ret; + } + + if (strcmp(backup_path, "-") == 0) + strcpy(tmp, backup_path); + else + sprintf(tmp, "%s%s.img", backup_path, root); + + ui_print("Restoring %s image...\n", name); + if (0 != (ret = restore_raw_partition(vol->fs_type, vol->blk_device, tmp))) { + ui_print("Error while flashing %s image!\n", name); + return ret; + } + return 0; + } + return nandroid_restore_partition_extended(backup_path, root, 1); +} + +int nandroid_restore(const char* backup_path, unsigned char flags) { + ui_set_background(BACKGROUND_ICON_INSTALLING); + ui_show_indeterminate_progress(); + nandroid_files_total = 0; + int ret; + + int restore_boot = ((flags & NANDROID_BOOT) == NANDROID_BOOT); + int restore_system = ((flags & NANDROID_SYSTEM) == NANDROID_SYSTEM); + int restore_data = ((flags & NANDROID_DATA) == NANDROID_DATA); + int restore_cache = ((flags & NANDROID_CACHE) == NANDROID_CACHE); + int restore_sdext = ((flags & NANDROID_SDEXT) == NANDROID_SDEXT); + int restore_wimax = ((flags & NANDROID_WIMAX) == NANDROID_WIMAX); + + if (ensure_path_mounted(backup_path) != 0) + return print_and_error("Can't mount backup path\n", NANDROID_ERROR_GENERAL); + + char tmp[PATH_MAX]; + + if (0 != (ret = nandroid_restore_md5_check(backup_path, flags))) + return print_and_error(NULL, ret); + + if (restore_boot && NULL != volume_for_path("/boot") && 0 != (ret = nandroid_restore_partition(backup_path, "/boot"))) + return print_and_error(NULL, ret); + + struct stat s; + Volume *vol = volume_for_path("/wimax"); + if (restore_wimax && vol != NULL && 0 == stat(vol->blk_device, &s)) { + char serialno[PROPERTY_VALUE_MAX]; + + serialno[0] = 0; + property_get("ro.serialno", serialno, ""); + sprintf(tmp, "%s/wimax.%s.img", backup_path, serialno); + + struct stat st; + if (0 != stat(tmp, &st)) { + ui_print("WARNING: WiMAX partition exists, but nandroid\n"); + ui_print(" backup does not contain WiMAX image.\n"); + ui_print(" You should create a new backup to\n"); + ui_print(" protect your WiMAX keys.\n"); + } else { + ui_print("Erasing WiMAX before restore...\n"); + if (0 != (ret = format_volume("/wimax"))) + return print_and_error("Error while formatting wimax!\n", NANDROID_ERROR_GENERAL); + ui_print("Restoring WiMAX image...\n"); + if (0 != (ret = restore_raw_partition(vol->fs_type, vol->blk_device, tmp))) + return print_and_error(NULL, ret); + } + } + + if (restore_system && 0 != (ret = nandroid_restore_partition(backup_path, "/system"))) + return print_and_error(NULL, ret); + + if (restore_data && 0 != (ret = nandroid_restore_partition(backup_path, "/data"))) + return print_and_error(NULL, ret); + + if (has_datadata()) { + if (restore_data && 0 != (ret = nandroid_restore_partition(backup_path, "/datadata"))) + return print_and_error(NULL, ret); + } + + if (restore_data && 0 != (ret = nandroid_restore_partition_extended(backup_path, get_android_secure_path(), 0))) + return print_and_error(NULL, ret); + + if (restore_cache && 0 != (ret = nandroid_restore_partition_extended(backup_path, "/cache", 0))) + return print_and_error(NULL, ret); + + if (restore_sdext && 0 != (ret = nandroid_restore_partition(backup_path, "/sd-ext"))) + return print_and_error(NULL, ret); + + sync(); + ui_set_background(BACKGROUND_ICON_CLOCKWORK); + ui_reset_progress(); + ui_print("\nRestore complete!\n"); + return 0; +} + +static int nandroid_undump(const char* partition) { + nandroid_files_total = 0; + + int ret; + + if (strcmp(partition, "boot") == 0) { + Volume *vol = volume_for_path("/boot"); + // make sure the volume exists before attempting anything... + if (vol == NULL || vol->fs_type == NULL) + return 1; + char cmd[PATH_MAX]; + sprintf(cmd, "cat /proc/self/fd/0 > %s", vol->blk_device); + return __system(cmd); + // return __system("flash_image boot /proc/self/fd/0"); + } + + if (strcmp(partition, "recovery") == 0) { + if (0 != (ret = nandroid_restore_partition("-", "/recovery"))) + return ret; + } + + if (strcmp(partition, "system") == 0) { + if (0 != (ret = nandroid_restore_partition("-", "/system"))) + return ret; + } + + if (strcmp(partition, "data") == 0) { + if (0 != (ret = nandroid_restore_partition("-", "/data"))) + return ret; + } + + sync(); + return 0; +} + +static int nandroid_usage() { + printf("Usage: nandroid backup\n"); + printf("Usage: nandroid restore \n"); + printf("Usage: nandroid dump \n"); + printf("Usage: nandroid undump \n"); + return 1; +} + +static int bu_usage() { + printf("Usage: bu backup partition\n"); + printf("Usage: Prior to restore:\n"); + printf("Usage: echo -n > /tmp/ro.bu.restore\n"); + printf("Usage: bu restore\n"); + return 1; +} + +int bu_main(int argc, char** argv) { + load_volume_table(); + + if (strcmp(argv[2], "backup") == 0) { + if (argc != 4) { + return bu_usage(); + } + + int fd = atoi(argv[1]); + char* partition = argv[3]; + + if (fd != STDOUT_FILENO) { + dup2(fd, STDOUT_FILENO); + close(fd); + } + + int ret = nandroid_dump(partition); + sleep(10); + return ret; + } else if (strcmp(argv[2], "restore") == 0) { + if (argc != 3) { + return bu_usage(); + } + + int fd = atoi(argv[1]); + if (fd != STDIN_FILENO) { + dup2(fd, STDIN_FILENO); + close(fd); + } + + char partition[100]; + FILE* f = fopen("/tmp/ro.bu.restore", "r"); + if (f == NULL) { + printf("cannot open ro.bu.restore\n"); + return bu_usage(); + } + + if (fgets(partition, sizeof(partition), f) == NULL) { + fclose(f); + printf("nothing to restore!\n"); + return bu_usage(); + } + + size_t len = strlen(partition); + if (partition[len - 1] == '\n') + partition[len - 1] = '\0'; + + return nandroid_undump(partition); + } + + return bu_usage(); +} + +int nandroid_main(int argc, char** argv) { + load_volume_table(); + vold_init(); + char backup_path[PATH_MAX]; + + if (argc > 3 || argc < 2) + return nandroid_usage(); + + if (strcmp("backup", argv[1]) == 0) { + if (argc != 2) + return nandroid_usage(); + + nandroid_generate_timestamp_path(backup_path); + return nandroid_backup(backup_path); + } + + if (strcmp("restore", argv[1]) == 0) { + if (argc != 3) + return nandroid_usage(); + unsigned char flags = NANDROID_BOOT | NANDROID_SYSTEM | NANDROID_DATA + | NANDROID_CACHE | NANDROID_SDEXT; + return nandroid_restore(argv[2], flags); + } + + if (strcmp("dump", argv[1]) == 0) { + if (argc != 3) + return nandroid_usage(); + return nandroid_dump(argv[2]); + } + + if (strcmp("undump", argv[1]) == 0) { + if (argc != 3) + return nandroid_usage(); + return nandroid_undump(argv[2]); + } + + return nandroid_usage(); +} diff --git a/nandroid.h b/nandroid.h new file mode 100644 index 000000000..2d521a9c3 --- /dev/null +++ b/nandroid.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 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 NANDROID_H +#define NANDROID_H + +int nandroid_main(int argc, char** argv); +int bu_main(int argc, char** argv); + +int nandroid_backup(const char* backup_path); +int nandroid_restore(const char* backup_path, unsigned char flags); +void nandroid_dedupe_gc(const char* blob_dir); +void nandroid_force_backup_format(const char* fmt); +unsigned int nandroid_get_default_backup_format(); + +#define NANDROID_BACKUP_FORMAT_TAR 0 +#define NANDROID_BACKUP_FORMAT_DUP 1 +#define NANDROID_BACKUP_FORMAT_TGZ 2 + +#define NANDROID_ERROR_GENERAL 1 + +#define NANDROID_NONE 0 +#define NANDROID_BOOT 1 +#define NANDROID_SYSTEM 2 +#define NANDROID_DATA 4 +#define NANDROID_CACHE 8 +#define NANDROID_SDEXT 16 +#define NANDROID_WIMAX 32 + +#endif diff --git a/nandroid_md5.c b/nandroid_md5.c new file mode 100644 index 000000000..864c21749 --- /dev/null +++ b/nandroid_md5.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2014 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "extendedcommands.h" +#include "nandroid.h" +#include "nandroid_md5.h" +#include "recovery_ui.h" + +#define MAX_FILES_CHECKED 20 +#define HASH_LENGTH 2*MD5_DIGEST_LENGTH +#define BUFSIZE 1024 + +typedef struct { + int is_missing; + char *filename; +} MissingFiles; + +static void to_md5_hash(char *str, unsigned char* md) { + int i; + for (i = 0; i < MD5_DIGEST_LENGTH; i++) + sprintf(&str[2*i], "%02x", (unsigned int)md[i]); + str[HASH_LENGTH] = '\0'; +} + +static int calculate_md5(char *str, const char *path) { + FILE *fd; + fd = fopen(path, "r"); + if (fd != NULL) { + MD5_CTX c; + size_t i; + static unsigned char buf[BUFSIZE]; + unsigned char md5dig[MD5_DIGEST_LENGTH]; + MD5_Init(&c); + while ((i = fread(buf, 1, BUFSIZE, fd)) > 0) + MD5_Update(&c, buf, i); + MD5_Final(&(md5dig[0]), &c); + fclose(fd); + to_md5_hash(str, md5dig); + } else { + return 1; + } + + return 0; +} + +static int is_selected_for_restore(const char *file, const unsigned char flags) { + int check_boot = ((flags & NANDROID_BOOT) == NANDROID_BOOT); + int check_system = ((flags & NANDROID_SYSTEM) == NANDROID_SYSTEM); + int check_data = ((flags & NANDROID_DATA) == NANDROID_DATA); + int check_cache = ((flags & NANDROID_CACHE) == NANDROID_CACHE); + int check_sdext = ((flags & NANDROID_SDEXT) == NANDROID_SDEXT); + int check_wimax = ((flags & NANDROID_WIMAX) == NANDROID_WIMAX); + + if (check_boot && strstr(file, "boot")) + return 1; + if (check_system && strstr(file, "system")) + return 1; + if (check_data && (strstr(file, "data") || strstr(file, "android_secure"))) + return 1; + if (check_cache && strstr(file, "cache")) + return 1; + if (check_sdext && strstr(file, "sd-ext")) + return 1; + if (check_wimax && strstr(file, "wimax")) + return 1; + + return 0; +} + +int nandroid_backup_md5_gen(const char *backup_path) { + DIR *dp; + FILE *fd; + int i = 0; + int j = 0; + int len = 0; + int ret = 0; + int filecount = 0; + + // Dynamically allocated, free them at the end + char *filenames[MAX_FILES_CHECKED]; + char *filepaths[MAX_FILES_CHECKED]; + + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "%s", backup_path); + len = strlen(path); + if (path[len-1] == '/') + path[len-1] = '\0'; + + ui_print("Generating MD5 checksums...\n"); + + // Read files available in backup path + dp = opendir(path); + if (dp != NULL) { + struct dirent *ep; + while ((ep = readdir(dp)) && i < MAX_FILES_CHECKED) { + if (strcmp(ep->d_name, ".") != 0 && strcmp(ep->d_name, "..") != 0 + && strcmp(ep->d_name, "recovery.log") != 0 + && strcmp(ep->d_name, "nandroid.md5") != 0) { + len = strlen(ep->d_name); + filenames[i] = malloc(sizeof(char[len+1])); + snprintf(filenames[i], len+1, "%s", ep->d_name); + + len += 1 + strlen(path); + filepaths[i] = malloc(sizeof(char[len+1])); + snprintf(filepaths[i], len+1, "%s/%s", path, ep->d_name); + i++; + } + } + closedir(dp); + filecount = i; + } + if (filecount == 0) { + ret = -1; + LOGE("No files found in %s for MD5 generation\n", path); + goto out; + } + + // Prepare backup_path/nandroid.md5 for writing + char md5path[PATH_MAX]; + snprintf(md5path, PATH_MAX, "%s/%s", path, "nandroid.md5"); + fd = fopen(md5path, "w"); + if (fd == NULL) { + ret = -1; + LOGE("Unable to create nandroid.md5\n"); + goto out; + } + + // Generate MD5s and save to nandroid.md5 + char md5calc[HASH_LENGTH+1]; + char tmp[PATH_MAX]; + for (i = 0; i < filecount; i++) { + if (calculate_md5(md5calc, filepaths[i]) != 0) { + LOGE("Unable to generate MD5 for %s\n", filenames[i]); + // Attempt to continue for other files + } else { + snprintf(tmp, PATH_MAX, "%s %s\n", md5calc, filenames[i]); + fputs(tmp, fd); + } + } + fclose(fd); + ui_print("MD5 checksums generated\n"); + +out: + for (i = 0; i < filecount; i++) { + free(filenames[i]); + free(filepaths[i]); + } + + return ret; +} + +int nandroid_restore_md5_check(const char *backup_path, unsigned char flags) { + DIR *dp; + FILE *fd; + int i = 0; + int j = 0; + int len = 0; + int ret = 0; + int filecount = 0; + int md5count = 0; + int use_ui = is_ui_initialized(); + + if (empty_nandroid_bitmask(flags)) { + LOGE("Nothing selected for restore.\n"); + return -1; + } + + // Dynamically allocated, free them at the end + char *filenames[MAX_FILES_CHECKED]; + char *filepaths[MAX_FILES_CHECKED]; + char *md5files[MAX_FILES_CHECKED]; + char *md5hashes[MAX_FILES_CHECKED]; + int mf_allocated = 0; // for MissingFiles *mf + int mm_allocated = 0; // for MissingFiles *mm + + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "%s", backup_path); + len = strlen(path); + if (path[len-1] == '/') + path[len-1] = '\0'; + + ui_print("Checking MD5 sums...\n"); + + // Read files available in backup path + dp = opendir(path); + if (dp != NULL) { + struct dirent *ep; + while ((ep = readdir(dp)) && i < MAX_FILES_CHECKED) { + if (is_selected_for_restore(ep->d_name, flags)) { + len = strlen(ep->d_name); + filenames[i] = malloc(sizeof(char[len+1])); + snprintf(filenames[i], len+1, "%s", ep->d_name); + + len += 1 + strlen(path); + filepaths[i] = malloc(sizeof(char[len+1])); + snprintf(filepaths[i], len+1, "%s/%s", path, ep->d_name); + i++; + } + } + closedir(dp); + filecount = i; + } + if (filecount == 0) { + ret = -1; + LOGE("No backup files found in %s\n", path); + goto out; + } + + // Read files + hashes available in nandroid.md5 + i = 0; + char md5path[PATH_MAX]; + snprintf(md5path, PATH_MAX, "%s/%s", path, "nandroid.md5"); + fd = fopen(md5path, "r"); + if (fd != NULL) { + char tmp[PATH_MAX]; + while (fgets(tmp, PATH_MAX, fd) && i < MAX_FILES_CHECKED) { + if (tmp[strlen(tmp)-1] == '\n') + tmp[strlen(tmp)-1] = '\0'; + if (is_selected_for_restore(tmp, flags)) { + md5hashes[i] = malloc(sizeof(char[HASH_LENGTH+1])); + snprintf(md5hashes[i], HASH_LENGTH+1, "%s", tmp); + + // md5 hash is followed by two spaces + len = strlen(tmp) - (HASH_LENGTH+2); + md5files[i] = malloc(sizeof(char[len+1])); + snprintf(md5files[i], len+1, "%s", &tmp[HASH_LENGTH+2]); + i++; + } + } + fclose(fd); + md5count = i; + } + +#if DEBUG_MD5_CHECKER + LOGI("[MD5] backup_path: %s\n", path); + for (i = 0; i < filecount; i++) { + LOGI("[MD5] files in path: %s\n", filenames[i]); + } + for (i = 0; i < md5count; i++) { + LOGI("[MD5] reference: %s: %s\n", md5files[i], md5hashes[i]); + } +#endif + + // Cross-reference files in directory to those in nandroid.md5 + // Mark files that are in nandroid.md5 but not in directory (potentially fatal) + int foundfile; + int totalmissing = 0; + MissingFiles *mm = malloc(md5count * sizeof(MissingFiles)); + mm_allocated = 1; + for (i = 0; i < md5count; i++) { + foundfile = 0; + for (j = 0; j < filecount; j++) { + if (strcmp(md5files[i], filenames[j]) == 0) { + mm[i] = (MissingFiles){ 0, "" }; + foundfile = 1; + } + } + if (!foundfile) { + mm[i] = (MissingFiles){ 1, md5files[i] }; + totalmissing++; + } + } + + // Warn user of failure for missing files + if (totalmissing && use_ui) { + int uiback = ui_is_showing_back_button(); + ui_set_showing_back_button(0); + + const char* headers[totalmissing+8]; + headers[0] = "Backup files are missing:"; + int hi = 1; + for (i = 0; i < md5count; i++) { + if (mm[i].is_missing) + headers[hi++] = mm[i].filename; + } + headers[totalmissing+1] = ""; + headers[totalmissing+2] = "Attempting to restore any"; + headers[totalmissing+3] = "of these will fail."; + headers[totalmissing+4] = ""; + headers[totalmissing+5] = "Continue anyways?"; + headers[totalmissing+6] = ""; + headers[totalmissing+7] = NULL; + + static char* list[] = { "Yes", "No", NULL }; + + int chosen_item = get_menu_selection(headers, list, 0, 0); + if (chosen_item == 0) { + ui_set_showing_back_button(uiback); + } else { + ret = -1; + ui_set_showing_back_button(uiback); + LOGE("Aborting\n"); + goto out; + } + } else if (totalmissing) { + // No UI, so no prompt possible; fail the restore + ret = -1; + LOGE("Backup files are missing:\n"); + for (i = 0; i < md5count; i++) { + if (mm[i].is_missing) + LOGE("%s\n", mm[i].filename); + } + LOGE("Aborting\n"); + goto out; + } + + // Cross-reference files in directory to those in nandroid.md5 + // Mark files that are in directory but not in nandroid.md5 (non-fatal) + totalmissing = 0; + MissingFiles *mf = malloc(filecount * sizeof(MissingFiles)); + mf_allocated = 1; + for (i = 0; i < filecount; i++) { + foundfile = 0; + // Ignore files that are not scheduled for restore + if (!is_selected_for_restore(filenames[i], flags)) { + mf[i] = (MissingFiles){ 0, "" }; + continue; + } + for (j = 0; j < md5count; j++) { + if (strcmp(filenames[i], md5files[j]) == 0) { + mf[i] = (MissingFiles){ 0, "" }; + foundfile = 1; + } + } + if (!foundfile) { + mf[i] = (MissingFiles){ 1, filenames[i] }; + totalmissing++; + } + } + + // Warn user about missing md5 references for files + if (totalmissing && use_ui) { + int uiback = ui_is_showing_back_button(); + ui_set_showing_back_button(0); + + const char* headers[totalmissing+5]; + headers[0] = "Could not find reference MD5 for:"; + int hi = 1; + for (i = 0; i < filecount; i++) { + if (mf[i].is_missing) + headers[hi++] = mf[i].filename; + } + headers[totalmissing+1] = ""; + headers[totalmissing+2] = "Continue anyways?"; + headers[totalmissing+3] = ""; + headers[totalmissing+4] = NULL; + + static char* list[] = { "Yes", "No", NULL }; + + int chosen_item = get_menu_selection(headers, list, 0, 0); + if (chosen_item == 0) { + ui_set_showing_back_button(uiback); + } else{ + ret = -1; + ui_set_showing_back_button(uiback); + LOGE("Aborting\n"); + goto out; + } + } else if (totalmissing) { + // No UI, so no prompt possible; fail the restore + ret = -1; + LOGE("Could not find reference MD5 for:\n"); + for (i = 0; i < filecount; i++) { + if (mf[i].is_missing) + LOGE("%s\n", mf[i].filename); + } + LOGE("Aborting\n"); + goto out; + } + + // Compare MD5s of non-missing files that are selected for restore + int md5matches = 0; + char md5calc[HASH_LENGTH+1]; + for (i = 0; i < filecount; i++) { + if (!is_selected_for_restore(filenames[i], flags)) + continue; + for (j = 0; j < md5count; j++) { + if (strcmp(filenames[i], md5files[j]) == 0) { + if (calculate_md5(md5calc, filepaths[i]) != 0) { + ret = -1; + LOGE("Unable to check MD5 of %s\nAborting\n", filenames[i]); + goto out; + } + if (strcmp(md5calc, md5hashes[j]) != 0) { + ret = -1; + LOGE("MD5 mismatch for %s\nAborting\n", filenames[i]); + goto out; + } else { + md5matches++; + } + } + } + } + if (md5matches) { + ui_print("All MD5 checksums verified\n"); + } else { + ui_print("No MD5 verification performed\n"); + } + +out: + if (mf_allocated) + free(mf); + if (mm_allocated) + free(mm); + for (i = 0; i < filecount; i++) { + free(filenames[i]); + free(filepaths[i]); + } + for (i = 0; i < md5count; i++) { + free(md5files[i]); + free(md5hashes[i]); + } + + return ret; +} diff --git a/nandroid_md5.h b/nandroid_md5.h new file mode 100644 index 000000000..63a8c996f --- /dev/null +++ b/nandroid_md5.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 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 _NANDROID_MD5_H +#define _NANDROID_MD5_H + +#define DEBUG_MD5_CHECKER 0 + +int nandroid_backup_md5_gen(const char *backup_path); +int nandroid_restore_md5_check(const char *backup_path, unsigned char flags); + +#endif diff --git a/prop.c b/prop.c new file mode 100644 index 000000000..913d77e7c --- /dev/null +++ b/prop.c @@ -0,0 +1,84 @@ +/* + * 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 +#include +#include + +#include "../../system/core/toolbox/dynarray.h" + +int setprop_main(int argc, char *argv[]) +{ + if(argc != 3) { + fprintf(stderr,"usage: setprop \n"); + return 1; + } + + if(property_set(argv[1], argv[2])){ + fprintf(stderr,"could not set property\n"); + return 1; + } + + return 0; +} + +static void record_prop(const char* key, const char* name, void* opaque) +{ + strlist_t* list = opaque; + char temp[PROP_VALUE_MAX + PROP_NAME_MAX + 16]; + snprintf(temp, sizeof temp, "[%s]: [%s]", key, name); + strlist_append_dup(list, temp); +} + +static void list_properties(void) +{ + strlist_t list[1] = { STRLIST_INITIALIZER }; + + /* Record properties in the string list */ + (void)property_list(record_prop, list); + + /* Sort everything */ + strlist_sort(list); + + /* print everything */ + STRLIST_FOREACH(list, str, printf("%s\n", str)); + + /* voila */ + strlist_done(list); +} + +int __system_property_wait(prop_info *pi); + +int getprop_main(int argc, char *argv[]) +{ + int n = 0; + + if (argc == 1) { + list_properties(); + } else { + char value[PROPERTY_VALUE_MAX]; + char *default_value; + if(argc > 2) { + default_value = argv[2]; + } else { + default_value = ""; + } + + property_get(argv[1], value, default_value); + printf("%s\n", value); + } + return 0; +} diff --git a/propsrvc/legacy_properties.h b/propsrvc/legacy_properties.h new file mode 100644 index 000000000..44cdd958d --- /dev/null +++ b/propsrvc/legacy_properties.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _INCLUDE_LEGACY_PROPERTIES_H +#define _INCLUDE_LEGACY_PROPERTIES_H + +#include + +typedef struct prop_area prop_area; +typedef struct prop_msg prop_msg; + +#define PROP_AREA_MAGIC 0x504f5250 +#define PROP_AREA_VERSION 0x45434f76 + +#define PROP_SERVICE_NAME "property_service" + +/* #define PROP_MAX_ENTRIES 247 */ +/* 247 -> 32620 bytes (<32768) */ + +#define TOC_NAME_LEN(toc) ((toc) >> 24) +#define TOC_TO_INFO(area, toc) ((prop_info*) (((char*) area) + ((toc) & 0xFFFFFF))) + +struct prop_area { + unsigned volatile count; + unsigned volatile serial; + unsigned magic; + unsigned version; + unsigned reserved[4]; + unsigned toc[1]; +}; + +#define SERIAL_VALUE_LEN(serial) ((serial) >> 24) +#define SERIAL_DIRTY(serial) ((serial) & 1) + +struct prop_info { + char name[PROP_NAME_MAX]; + unsigned volatile serial; + char value[PROP_VALUE_MAX]; +}; + +struct prop_msg +{ + unsigned cmd; + char name[PROP_NAME_MAX]; + char value[PROP_VALUE_MAX]; +}; + +#define PROP_MSG_SETPROP 1 + +/* +** Rules: +** +** - there is only one writer, but many readers +** - prop_area.count will never decrease in value +** - once allocated, a prop_info's name will not change +** - once allocated, a prop_info's offset will not change +** - reading a value requires the following steps +** 1. serial = pi->serial +** 2. if SERIAL_DIRTY(serial), wait*, then goto 1 +** 3. memcpy(local, pi->value, SERIAL_VALUE_LEN(serial) + 1) +** 4. if pi->serial != serial, goto 2 +** +** - writing a value requires the following steps +** 1. pi->serial = pi->serial | 1 +** 2. memcpy(pi->value, local_value, value_len) +** 3. pi->serial = (value_len << 24) | ((pi->serial + 1) & 0xffffff) +** +** Improvements: +** - maintain the toc sorted by pi->name to allow lookup +** by binary search +** +*/ + +#define PROP_PATH_RAMDISK_DEFAULT "/default.prop" +#define PROP_PATH_SYSTEM_BUILD "/system/build.prop" +#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop" +#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop" + +#endif diff --git a/propsrvc/legacy_property_service.c b/propsrvc/legacy_property_service.c new file mode 100644 index 000000000..13f3f0c3d --- /dev/null +++ b/propsrvc/legacy_property_service.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "legacy_properties.h" +#include "legacy_property_service.h" + +static int persistent_properties_loaded = 0; +static int property_area_inited = 0; + +static int property_set_fd = -1; + +typedef struct { + void *data; + size_t size; + int fd; +} workspace; + +static int init_workspace(workspace *w, size_t size) +{ + void *data; + int fd; + + /* dev is a tmpfs that we can use to carve a shared workspace + * out of, so let's do that... + */ + fd = open("/dev/__legacy_properties__", O_RDWR | O_CREAT, 0600); + if (fd < 0) + return -1; + + if (ftruncate(fd, size) < 0) + goto out; + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + goto out; + + close(fd); + + fd = open("/dev/__legacy_properties__", O_RDONLY); + if (fd < 0) + return -1; + + unlink("/dev/__legacy_properties__"); + + w->data = data; + w->size = size; + w->fd = fd; + return 0; + +out: + close(fd); + return -1; +} + +/* (8 header words + 247 toc words) = 1020 bytes */ +/* 1024 bytes header and toc + 247 prop_infos @ 128 bytes = 32640 bytes */ + +#define PA_COUNT_MAX 247 +#define PA_INFO_START 1024 +#define PA_SIZE 32768 + +static workspace pa_workspace; +static prop_info *pa_info_array; + +prop_area *__legacy_property_area__; + +static int init_property_area(void) +{ + prop_area *pa; + + if (pa_info_array) + return -1; + + if (init_workspace(&pa_workspace, PA_SIZE)) + return -1; + + fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); + + pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START); + + pa = pa_workspace.data; + memset(pa, 0, PA_SIZE); + pa->magic = PROP_AREA_MAGIC; + pa->version = PROP_AREA_VERSION; + + /* plug into the lib property services */ + __legacy_property_area__ = pa; + property_area_inited = 1; + return 0; +} + +static void update_prop_info(prop_info *pi, const char *value, unsigned len) +{ + pi->serial = pi->serial | 1; + memcpy(pi->value, value, len + 1); + pi->serial = (len << 24) | ((pi->serial + 1) & 0xffffff); + __futex_wake(&pi->serial, INT32_MAX); +} + +static const prop_info *__legacy_property_find(const char *name) +{ + prop_area *pa = __legacy_property_area__; + unsigned count = pa->count; + unsigned *toc = pa->toc; + unsigned len = strlen(name); + prop_info *pi; + + while (count--) { + unsigned entry = *toc++; + if (TOC_NAME_LEN(entry) != len) continue; + + pi = TOC_TO_INFO(pa, entry); + if (memcmp(name, pi->name, len)) continue; + + return pi; + } + + return 0; +} + +static int legacy_property_set(const char *name, const char *value) +{ + prop_area *pa; + prop_info *pi; + + int namelen = strlen(name); + int valuelen = strlen(value); + + if (namelen >= PROP_NAME_MAX) + return -1; + if (valuelen >= PROP_VALUE_MAX) + return -1; + if (namelen < 1) + return -1; + + pi = (prop_info*) __legacy_property_find(name); + + + if (pi != 0) { + /* ro.* properties may NEVER be modified once set */ + if (!strncmp(name, "ro.", 3)) + return -1; + + pa = __legacy_property_area__; + update_prop_info(pi, value, valuelen); + pa->serial++; + __futex_wake(&pa->serial, INT32_MAX); + } else { + pa = __legacy_property_area__; + if (pa->count == PA_COUNT_MAX) + return -1; + + pi = pa_info_array + pa->count; + pi->serial = (valuelen << 24); + memcpy(pi->name, name, namelen + 1); + memcpy(pi->value, value, valuelen + 1); + + pa->toc[pa->count] = + (namelen << 24) | (((unsigned) pi) - ((unsigned) pa)); + + pa->count++; + pa->serial++; + __futex_wake(&pa->serial, INT32_MAX); + } + + return 0; +} + +void legacy_get_property_workspace(int *fd, int *sz) +{ + *fd = pa_workspace.fd; + *sz = pa_workspace.size; +} + +static void copy_property_to_legacy(const char *key, const char *value, void *cookie) +{ + legacy_property_set(key, value); +} + +int legacy_properties_init() +{ + if (init_property_area() != 0) + return -1; + + if (property_list(copy_property_to_legacy, 0) != 0) + return -1; + + return 0; +} diff --git a/propsrvc/legacy_property_service.h b/propsrvc/legacy_property_service.h new file mode 100644 index 000000000..d20bdeff6 --- /dev/null +++ b/propsrvc/legacy_property_service.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LEGACY_PROPERTY_H +#define _LEGACY_PROPERTY_H + +#include + +void legacy_get_property_workspace(int *fd, int *sz); +int legacy_properties_init(); + +#endif /* _LEGACY_PROPERTY_H */ diff --git a/reboot.c b/reboot.c new file mode 100644 index 000000000..374907d28 --- /dev/null +++ b/reboot.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +int reboot_main(int argc, char *argv[]) +{ + int ret; + size_t prop_len; + char property_val[PROPERTY_VALUE_MAX]; + const char *cmd = "reboot"; + char *optarg = ""; + + opterr = 0; + do { + int c; + + c = getopt(argc, argv, "p"); + + if (c == EOF) { + break; + } + + switch (c) { + case 'p': + cmd = "shutdown"; + break; + case '?': + fprintf(stderr, "usage: %s [-p] [rebootcommand]\n", argv[0]); + exit(EXIT_FAILURE); + } + } while (1); + + if(argc > optind + 1) { + fprintf(stderr, "%s: too many arguments\n", argv[0]); + exit(EXIT_FAILURE); + } + + if (argc > optind) + optarg = argv[optind]; + + prop_len = snprintf(property_val, sizeof(property_val), "%s,%s", cmd, optarg); + if (prop_len >= sizeof(property_val)) { + fprintf(stderr, "reboot command too long: %s\n", optarg); + exit(EXIT_FAILURE); + } + + ret = property_set(ANDROID_RB_PROPERTY, property_val); + if(ret < 0) { + perror("reboot"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "Done\n"); + return 0; +} diff --git a/recovery.c b/recovery.c index 1e3eb5afb..619049b38 100644 --- a/recovery.c +++ b/recovery.c @@ -1,5 +1,7 @@ /* * Copyright (C) 2007 The Android Open Source Project + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * Copyright (C) 2014 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. @@ -15,6 +17,7 @@ */ #include +#include #include #include #include @@ -27,7 +30,6 @@ #include #include #include -#include #include "bootloader.h" #include "common.h" @@ -38,23 +40,39 @@ #include "minzip/DirUtil.h" #include "roots.h" #include "recovery_ui.h" +#include "adb_install.h" +#include "minadbd/adb.h" + +#include "dedupe/dedupe.h" +#include "firmware.h" +#include "extendedcommands.h" +#include "flashutils/flashutils.h" +#include "recovery_cmds.h" +#include "voldclient/voldclient.h" + +struct selabel_handle *sehandle = NULL; 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' }, { "show_text", no_argument, NULL, 't' }, + { "sideload", no_argument, NULL, 'l' }, + { "shutdown_after", no_argument, NULL, 'p' }, { NULL, 0, NULL, 0 }, }; +#define LAST_LOG_FILE "/cache/recovery/last_log" +static const char *CACHE_LOG_DIR = "/cache/recovery"; static const char *COMMAND_FILE = "/cache/recovery/command"; static const char *INTENT_FILE = "/cache/recovery/intent"; static const char *LOG_FILE = "/cache/recovery/log"; -static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; +static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; 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 *SIDELOAD_TEMP_DIR = "/tmp/sideload"; extern UIParameters ui_parameters; // from ui.c @@ -130,9 +148,10 @@ fopen_path(const char *path, const char *mode) { // When writing, try to create the containing directory, if necessary. // Use generous permissions, the system (init.rc) will reset them. - if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1); + if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1, sehandle); FILE *fp = fopen(path, mode); + if (fp == NULL && path != COMMAND_FILE) LOGE("Can't open %s\n", path); return fp; } @@ -162,8 +181,10 @@ get_args(int *argc, char ***argv) { LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status); } + struct stat file_info; + // --- if arguments weren't supplied, look in the bootloader control block - if (*argc <= 1) { + if (*argc <= 1 && 0 != stat("/tmp/.ignorebootmessage", &file_info)) { boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination const char *arg = strtok(boot.recovery, "\n"); if (arg != NULL && !strcmp(arg, "recovery")) { @@ -183,6 +204,7 @@ get_args(int *argc, char ***argv) { if (*argc <= 1) { FILE *fp = fopen_path(COMMAND_FILE, "r"); if (fp != NULL) { + char *token; char *argv0 = (*argv)[0]; *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); (*argv)[0] = argv0; // use the same program name @@ -190,7 +212,12 @@ get_args(int *argc, char ***argv) { char buf[MAX_ARG_LENGTH]; for (*argc = 1; *argc < MAX_ARGS; ++*argc) { if (!fgets(buf, sizeof(buf), fp)) break; - (*argv)[*argc] = strdup(strtok(buf, "\r\n")); // Strip newline. + token = strtok(buf, "\r\n"); + if (token != NULL) { + (*argv)[*argc] = strdup(token); // Strip newline. + } else { + --*argc; + } } check_and_fclose(fp, COMMAND_FILE); @@ -210,7 +237,7 @@ get_args(int *argc, char ***argv) { set_bootloader_message(&boot); } -static void +void set_sdcard_update_bootloader_message() { struct bootloader_message boot; memset(&boot, 0, sizeof(boot)); @@ -223,15 +250,13 @@ set_sdcard_update_bootloader_message() { static long tmplog_offset = 0; static void -copy_log_file(const char* destination, int append) { +copy_log_file(const char* source, const char* destination, int append) { FILE *log = fopen_path(destination, append ? "a" : "w"); if (log == NULL) { LOGE("Can't open %s\n", destination); } else { - FILE *tmplog = fopen(TEMPORARY_LOG_FILE, "r"); - if (tmplog == NULL) { - LOGE("Can't open %s\n", TEMPORARY_LOG_FILE); - } else { + FILE *tmplog = fopen(source, "r"); + if (tmplog != NULL) { if (append) { fseek(tmplog, tmplog_offset, SEEK_SET); // Since last write } @@ -240,12 +265,40 @@ copy_log_file(const char* destination, int append) { if (append) { tmplog_offset = ftell(tmplog); } - check_and_fclose(tmplog, TEMPORARY_LOG_FILE); + check_and_fclose(tmplog, source); } check_and_fclose(log, destination); } } +// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max +// Overwrites any existing last_log.$max. +static void +rotate_last_logs(int max) { + char oldfn[256]; + char newfn[256]; + + int i; + for (i = max-1; i >= 0; --i) { + snprintf(oldfn, sizeof(oldfn), (i==0) ? LAST_LOG_FILE : (LAST_LOG_FILE ".%d"), i); + snprintf(newfn, sizeof(newfn), LAST_LOG_FILE ".%d", i+1); + // ignore errors + rename(oldfn, newfn); + } +} + +static void +copy_logs() { + // Copy logs to cache so the system can find out what happened. + copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true); + copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false); + copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false); + chmod(LOG_FILE, 0600); + chown(LOG_FILE, 1000, 1000); // system user + chmod(LAST_LOG_FILE, 0640); + chmod(LAST_INSTALL_FILE, 0644); + sync(); +} // clear the recovery command and prepare to boot a (hopefully working) system, // copy our log file to cache as well (for the system to read), and @@ -264,12 +317,9 @@ finish_recovery(const char *send_intent) { } } - // Copy logs to cache so the system can find out what happened. - copy_log_file(LOG_FILE, true); - copy_log_file(LAST_LOG_FILE, false); - chmod(LAST_LOG_FILE, 0640); + copy_logs(); - // Reset to mormal system boot so recovery won't cycle indefinitely. + // Reset to normal system boot so recovery won't cycle indefinitely. struct bootloader_message boot; memset(&boot, 0, sizeof(boot)); set_bootloader_message(&boot); @@ -283,20 +333,97 @@ finish_recovery(const char *send_intent) { sync(); // For good measure. } +typedef struct _saved_log_file { + char* name; + struct stat st; + unsigned char* data; + struct _saved_log_file* next; +} saved_log_file; + static int erase_volume(const char *volume) { + bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); + ui_set_background(BACKGROUND_ICON_INSTALLING); ui_show_indeterminate_progress(); + + saved_log_file* head = NULL; + + if (is_cache) { + // If we're reformatting /cache, we load any + // "/cache/recovery/last*" files into memory, so we can restore + // them after the reformat. + + ensure_path_mounted(volume); + + DIR* d; + struct dirent* de; + d = opendir(CACHE_LOG_DIR); + if (d) { + char path[PATH_MAX]; + strcpy(path, CACHE_LOG_DIR); + strcat(path, "/"); + int path_len = strlen(path); + while ((de = readdir(d)) != NULL) { + if (strncmp(de->d_name, "last", 4) == 0) { + saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file)); + strcpy(path+path_len, de->d_name); + p->name = strdup(path); + if (stat(path, &(p->st)) == 0) { + // truncate files to 512kb + if (p->st.st_size > (1 << 19)) { + p->st.st_size = 1 << 19; + } + p->data = (unsigned char*) malloc(p->st.st_size); + FILE* f = fopen(path, "rb"); + fread(p->data, 1, p->st.st_size, f); + fclose(f); + p->next = head; + head = p; + } else { + free(p); + } + } + } + closedir(d); + } else { + if (errno != ENOENT) { + printf("opendir failed: %s\n", strerror(errno)); + } + } + } + ui_print("Formatting %s...\n", volume); - if (strcmp(volume, "/cache") == 0) { + ensure_path_unmounted(volume); + int result = format_volume(volume); + + if (is_cache) { + while (head) { + FILE* f = fopen_path(head->name, "wb"); + if (f) { + fwrite(head->data, 1, head->st.st_size, f); + fclose(f); + chmod(head->name, head->st.st_mode); + chown(head->name, head->st.st_uid, head->st.st_gid); + } + free(head->name); + free(head->data); + saved_log_file* temp = head->next; + free(head); + head = temp; + } + // Any part of the log we'd copied to cache is now gone. // Reset the pointer so we copy from the beginning of the temp // log. tmplog_offset = 0; + copy_logs(); } - return format_volume(volume); + ui_set_background(BACKGROUND_ICON_CLOCKWORK); + ui_reset_progress(); + return result; } static char* @@ -389,22 +516,21 @@ copy_sideloaded_package(const char* original_path) { return strdup(copy_path); } -static char** +static const char** prepend_title(const char** headers) { - char* title[] = { "Android system recovery <" - EXPAND(RECOVERY_API_VERSION) "e>", + const char* title[] = { EXPAND(RECOVERY_VERSION), "", NULL }; // count the number of lines in our title, plus the // caller-provided headers. int count = 0; - char** p; + const char** p; for (p = title; *p; ++p, ++count); for (p = headers; *p; ++p, ++count); - char** new_headers = malloc((count+1) * sizeof(char*)); - char** h = new_headers; + const char** new_headers = malloc((count+1) * sizeof(const char*)); + const char** h = new_headers; for (p = title; *p; ++p, ++h) *h = *p; for (p = headers; *p; ++p, ++h) *h = *p; *h = NULL; @@ -412,18 +538,19 @@ prepend_title(const char** headers) { return new_headers; } -static int -get_menu_selection(char** headers, char** items, int menu_only, +int +get_menu_selection(const char** headers, char** items, int menu_only, int initial_selection) { // throw away keys pressed previously, so user doesn't // accidentally trigger menu items. ui_clear_key_queue(); - ui_start_menu(headers, items, initial_selection); + int item_count = ui_start_menu(headers, items, initial_selection); int selected = initial_selection; - int chosen_item = -1; + int chosen_item = -1; // NO_ACTION + int wrap_count = 0; - while (chosen_item < 0) { + while (chosen_item < 0 && chosen_item != GO_BACK) { int key = ui_wait_key(); int visible = ui_text_visible(); @@ -436,8 +563,17 @@ get_menu_selection(char** headers, char** items, int menu_only, return ITEM_REBOOT; } } + else if (key == -2) { // we are returning from ui_cancel_wait_key(): trigger a GO_BACK + return GO_BACK; + } + else if (key == -3) { // an USB device was plugged in (returning from ui_wait_key()) + return REFRESH; + } + + int action = ui_handle_key(key, visible); - int action = device_handle_key(key, visible); + int old_selected = selected; + selected = ui_get_selected_item(); if (action < 0) { switch (action) { @@ -451,16 +587,40 @@ get_menu_selection(char** headers, char** items, int menu_only, break; case SELECT_ITEM: chosen_item = selected; + if (ui_is_showing_back_button()) { + if (chosen_item == item_count) { + chosen_item = GO_BACK; + } + } break; case NO_ACTION: break; + case GO_BACK: + chosen_item = GO_BACK; + break; } } else if (!menu_only) { chosen_item = action; } + + if (abs(selected - old_selected) > 1) { + wrap_count++; + if (wrap_count == 5) { + wrap_count = 0; + if (ui_get_rainbow_mode()) { + ui_set_rainbow_mode(0); + ui_print("Rainbow mode disabled\n"); + } + else { + ui_set_rainbow_mode(1); + ui_print("Rainbow mode enabled!\n"); + } + } + } } ui_end_menu(); + ui_clear_key_queue(); return chosen_item; } @@ -487,7 +647,7 @@ update_directory(const char* path, const char* unmount_when_done) { return 0; } - char** headers = prepend_title(MENU_HEADERS); + const char** headers = prepend_title(MENU_HEADERS); int d_size = 0; int d_alloc = 10; @@ -597,52 +757,46 @@ update_directory(const char* path, const char* unmount_when_done) { static void wipe_data(int confirm) { - if (confirm) { - static char** title_headers = NULL; - - if (title_headers == NULL) { - char* headers[] = { "Confirm wipe of all user data?", - " THIS CAN NOT BE UNDONE.", - "", - NULL }; - title_headers = prepend_title((const char**)headers); - } - - 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); - if (chosen_item != 7) { - return; - } - } + if (confirm && !confirm_selection( "Confirm wipe of all user data?", "Yes - Wipe all user data")) + return; ui_print("\n-- Wiping data...\n"); device_wipe_data(); erase_volume("/data"); erase_volume("/cache"); + if (has_datadata()) { + erase_volume("/datadata"); + } + erase_volume("/sd-ext"); + erase_volume(get_android_secure_path()); ui_print("Data wipe complete.\n"); } +static void headless_wait() { + ui_show_text(0); + const char** headers = prepend_title((const char**)MENU_HEADERS); + for(;;) { + finish_recovery(NULL); + get_menu_selection(headers, MENU_ITEMS, 0, 0); + } +} + +int ui_menu_level = 1; +int ui_root_menu = 0; static void prompt_and_wait() { - char** headers = prepend_title((const char**)MENU_HEADERS); + const char** headers = prepend_title((const char**)MENU_HEADERS); for (;;) { finish_recovery(NULL); ui_reset_progress(); + ui_root_menu = 1; + // ui_menu_level is a legacy variable that i am keeping around to prevent build breakage. + ui_menu_level = 0; int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0); + ui_menu_level = 1; + ui_root_menu = 0; // device-specific code may take some action here. It may // return one of the core actions handled in the switch @@ -650,50 +804,49 @@ prompt_and_wait() { chosen_item = device_perform_action(chosen_item); int status; - switch (chosen_item) { - case ITEM_REBOOT: - return; - - case ITEM_WIPE_DATA: - wipe_data(ui_text_visible()); - if (!ui_text_visible()) return; - break; - - case ITEM_WIPE_CACHE: - ui_print("\n-- Wiping cache...\n"); - erase_volume("/cache"); - ui_print("Cache wipe complete.\n"); - if (!ui_text_visible()) return; - break; - - case ITEM_APPLY_SDCARD: - status = update_directory(SDCARD_ROOT, SDCARD_ROOT); - if (status >= 0) { - if (status != INSTALL_SUCCESS) { - ui_set_background(BACKGROUND_ICON_ERROR); - ui_print("Installation aborted.\n"); - } else if (!ui_text_visible()) { - return; // reboot if logs aren't visible - } else { - ui_print("\nInstall from sdcard complete.\n"); - } - } - break; - case ITEM_APPLY_CACHE: - // Don't unmount cache at the end of this. - status = update_directory(CACHE_ROOT, NULL); - if (status >= 0) { - if (status != INSTALL_SUCCESS) { - ui_set_background(BACKGROUND_ICON_ERROR); - ui_print("Installation aborted.\n"); - } else if (!ui_text_visible()) { - return; // reboot if logs aren't visible - } else { - ui_print("\nInstall from cache complete.\n"); + int ret = 0; + + for (;;) { + switch (chosen_item) { + case ITEM_REBOOT: + return; + + case ITEM_WIPE_DATA: + wipe_data(ui_text_visible()); + if (!ui_text_visible()) return; + break; + + case ITEM_WIPE_CACHE: + if (confirm_selection("Confirm wipe?", "Yes - Wipe Cache")) + { + ui_print("\n-- Wiping cache...\n"); + erase_volume("/cache"); + ui_print("Cache wipe complete.\n"); + if (!ui_text_visible()) return; } - } - break; + break; + + case ITEM_APPLY_ZIP: + ret = show_install_update_menu(); + break; + + case ITEM_NANDROID: + ret = show_nandroid_menu(); + break; + case ITEM_PARTITION: + ret = show_partition_menu(); + break; + + case ITEM_ADVANCED: + ret = show_advanced_menu(); + break; + } + if (ret == REFRESH) { + ret = 0; + continue; + } + break; } } } @@ -703,41 +856,222 @@ print_property(const char *key, const char *name, void *cookie) { printf("%s=%s\n", key, name); } +static void +setup_adbd() { + struct stat f; + static char *key_src = "/data/misc/adb/adb_keys"; + static 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); + } + } + preserve_data_media(0); + ensure_path_unmounted("/data"); + preserve_data_media(1); + + // Trigger (re)start of adb daemon + property_set("service.adb.root", "1"); +} + +// call a clean reboot +void reboot_main_system(int cmd, int flags, char *arg) { + write_recovery_version(); + +#ifdef BOARD_NATIVE_DUALBOOT + device_verify_root_and_recovery(); +#else + verify_root_and_recovery(); +#endif + + finish_recovery(NULL); // sync() in here + vold_unmount_all(); + android_reboot(cmd, flags, arg); +} + +static int v_changed = 0; +int volumes_changed() { + int ret = v_changed; + if (v_changed == 1) + v_changed = 0; + return ret; +} + +static int handle_volume_hotswap(char* label, char* path) { + v_changed = 1; + return 0; +} + +static int handle_volume_state_changed(char* label, char* path, int state) { + int log = -1; + if (state == State_Checking || state == State_Mounted || state == State_Idle) { + // do not ever log to screen mount/unmount events for sdcards + if (strncmp(path, "/storage/sdcard", 15) == 0) + log = 0; + else log = 1; + } + else if (state == State_Formatting || state == State_Shared) { + log = 1; + } + + if (log == 0) + LOGI("%s: %s\n", path, volume_state_to_string(state)); + else if (log == 1) + ui_print("%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, +}; + +void vold_init() { + vold_client_start(&v_callbacks, 0); + vold_set_automount(1); +} + int main(int argc, char **argv) { + + if (argc == 2 && strcmp(argv[1], "adbd") == 0) { + adb_main(); + return 0; + } + + // Recovery needs to install world-readable files, so clear umask + // set by init + umask(0); + + 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); + +#ifdef BOARD_RECOVERY_HANDLES_MOUNT + if (!strcmp(command, "mount") && argc == 2) + { + load_volume_table(); + return ensure_path_mounted(argv[1]); + } +#endif + if (!strcmp(command, "setup_adbd")) { + load_volume_table(); + setup_adbd(); + return 0; + } + if (!strcmp(command, "start")) { + property_set("ctl.start", argv[1]); + return 0; + } + if (!strcmp(command, "stop")) { + property_set("ctl.stop", argv[1]); + return 0; + } + return busybox_driver(argc, argv); + } + __system("/sbin/postrecoveryboot.sh"); + + int is_user_initiated_recovery = 0; time_t start = time(NULL); // If these fail, there's not really anywhere to complain... freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL); freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL); - printf("Starting recovery on %s", ctime(&start)); + printf("Starting recovery on %s\n", ctime(&start)); device_ui_init(&ui_parameters); ui_init(); - ui_set_background(BACKGROUND_ICON_INSTALLING); + ui_print(EXPAND(RECOVERY_VERSION)"\n"); + +#ifdef BOARD_RECOVERY_SWIPE +#ifndef BOARD_TOUCH_RECOVERY + //display directions for swipe controls + ui_print("Swipe up/down to change selections.\n"); + ui_print("Swipe to the right for enter.\n"); + ui_print("Swipe to the left for back.\n"); +#endif +#endif + load_volume_table(); + process_volumes(); + vold_init(); + setup_legacy_storage_paths(); + LOGI("Processing arguments.\n"); + ensure_path_mounted(LAST_LOG_FILE); + rotate_last_logs(10); get_args(&argc, &argv); - int previous_runs = 0; const char *send_intent = NULL; const char *update_package = NULL; int wipe_data = 0, wipe_cache = 0; + int sideload = 0; + int headless = 0; + int shutdown_after = 0; + LOGI("Checking arguments.\n"); int arg; while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) { switch (arg) { - case 'p': previous_runs = atoi(optarg); break; case 's': send_intent = optarg; break; case 'u': update_package = optarg; break; - case 'w': wipe_data = wipe_cache = 1; break; + case 'w': +#ifndef BOARD_RECOVERY_ALWAYS_WIPES + wipe_data = wipe_cache = 1; +#endif + break; + case 'h': + ui_set_background(BACKGROUND_ICON_CID); + ui_show_text(0); + headless = 1; + break; case 'c': wipe_cache = 1; break; case 't': ui_show_text(1); break; + case 'l': sideload = 1; break; + case 'p': shutdown_after = 1; break; case '?': LOGE("Invalid command argument\n"); continue; } } + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + + if (!sehandle) { + fprintf(stderr, "Warning: No file_contexts\n"); + ui_print("Warning: No file_contexts\n"); + } + + LOGI("device_recovery_start()\n"); device_recovery_start(); printf("Command:"); @@ -769,27 +1103,91 @@ main(int argc, char **argv) { if (update_package != NULL) { status = install_package(update_package); - if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n"); + if (status != INSTALL_SUCCESS) { + copy_logs(); + ui_print("Installation aborted.\n"); + } } else if (wipe_data) { if (device_wipe_data()) status = INSTALL_ERROR; + preserve_data_media(0); if (erase_volume("/data")) status = INSTALL_ERROR; + preserve_data_media(1); + if (has_datadata() && erase_volume("/datadata")) status = INSTALL_ERROR; if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; - if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n"); + if (status != INSTALL_SUCCESS) { + copy_logs(); + 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"); + if (status != INSTALL_SUCCESS) { + copy_logs(); + ui_print("Cache wipe failed.\n"); + } } else { + LOGI("Checking for extendedcommand...\n"); status = INSTALL_ERROR; // No command specified + // we are starting up in user initiated recovery here + // let's set up some default options + signature_check_enabled = 0; + is_user_initiated_recovery = 1; + if (!headless) { + ui_set_show_text(1); + ui_set_background(BACKGROUND_ICON_CLOCKWORK); + } + + if (extendedcommand_file_exists()) { + LOGI("Running extendedcommand...\n"); + int ret; + if (0 == (ret = run_and_remove_extendedcommand())) { + status = INSTALL_SUCCESS; + ui_set_show_text(0); + } + else { + handle_failure(ret); + } + } else { + LOGI("Skipping execution of extendedcommand, file not found...\n"); + } + } + + if (sideload) { + signature_check_enabled = 0; + if (!headless) + ui_set_show_text(1); + if (0 == apply_from_adb()) { + status = INSTALL_SUCCESS; + ui_set_show_text(0); + } } - if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR); - if (status != INSTALL_SUCCESS || ui_text_visible()) { + if (headless) { + headless_wait(); + } + if (status != INSTALL_SUCCESS && !is_user_initiated_recovery) { + ui_set_show_text(1); + ui_set_background(BACKGROUND_ICON_ERROR); + } + else if (status != INSTALL_SUCCESS || ui_text_visible()) { prompt_and_wait(); } + // We reach here when in main menu we choose reboot main system or for some wipe commands on start + // If there is a radio image pending, reboot now to install it. + maybe_install_firmware_update(send_intent); + // Otherwise, get ready to boot the main system... finish_recovery(send_intent); - ui_print("Rebooting...\n"); - android_reboot(ANDROID_RB_RESTART, 0, 0); + if (shutdown_after) { + ui_print("Shutting down...\n"); + reboot_main_system(ANDROID_RB_POWEROFF, 0, 0); + } else { + ui_print("Rebooting...\n"); + reboot_main_system(ANDROID_RB_RESTART, 0, 0); + } return EXIT_SUCCESS; } + +void set_perf_mode(int on) { + property_set("recovery.perf.mode", on ? "1" : "0"); +} diff --git a/recovery_cmds.h b/recovery_cmds.h new file mode 100644 index 000000000..3d242371a --- /dev/null +++ b/recovery_cmds.h @@ -0,0 +1,94 @@ +/* + * 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 +#include + +#include "dedupe/dedupe.h" +#include "edifyscripting.h" +#include "extendedcommands.h" +#include "nandroid.h" + +extern int minizip_main(int argc, char **argv); +extern int flash_image_main(int argc, char **argv); +extern int edify_main(int argc, char **argv); +extern int dump_image_main(int argc, char **argv); +extern int erase_image_main(int argc, char **argv); +extern int mkyaffs2image_main(int argc, char **argv); +extern int unyaffs_main(int argc, char **argv); +extern int make_ext4fs_main(int argc, char **argv); +extern int reboot_main(int argc, char **argv); +extern int poweroff_main(int argc, char **argv); +extern int setprop_main(int argc, char **argv); +extern int getprop_main(int argc, char **argv); +extern int fsck_msdos_main(int argc, char **argv); +extern int newfs_msdos_main(int argc, char **argv); +extern int vdc_main(int argc, char **argv); +extern int pigz_main(int argc, char **argv); +extern int sdcard_main(int argc, char **argv); + +extern 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 }, + { "dedupe", dedupe_main }, + { "flash_image", flash_image_main }, + { "volume", volume_main }, + { "edify", edify_main }, + { "dump_image", dump_image_main }, + { "erase_image", erase_image_main }, + { "mkyaffs2image", mkyaffs2image_main }, + { "unyaffs", unyaffs_main }, + { "make_ext4fs", make_ext4fs_main }, + { "nandroid", nandroid_main }, + { "bu", bu_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 }, +#ifdef USE_F2FS + { "mkfs.f2fs", make_f2fs_main }, + { "fsck.f2fs", fsck_f2fs_main }, + { "fibmap.f2fs", fibmap_main }, +#endif + { NULL, NULL }, +}; + +inline 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 // _RECOVERY_CMDS_H diff --git a/recovery_settings.h b/recovery_settings.h new file mode 100644 index 000000000..13dd9e7e3 --- /dev/null +++ b/recovery_settings.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 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_SETTINGS_H +#define _RECOVERY_SETTINGS_H + +#define RECOVERY_NO_CONFIRM_FILE "clockworkmod/.no_confirm" +#define RECOVERY_MANY_CONFIRM_FILE "clockworkmod/.many_confirm" +#define RECOVERY_VERSION_FILE "clockworkmod/.recovery_version" +#define RECOVERY_LAST_INSTALL_FILE "clockworkmod/.last_install_path" + +// nandroid settings +#define NANDROID_HIDE_PROGRESS_FILE "clockworkmod/.hidenandroidprogress" +#define NANDROID_BACKUP_FORMAT_FILE "clockworkmod/.default_backup_format" + +#endif // _RECOVERY_SETTINGS_H \ No newline at end of file diff --git a/recovery_ui.h b/recovery_ui.h index 5f0177045..4884cdc9b 100644 --- a/recovery_ui.h +++ b/recovery_ui.h @@ -18,6 +18,7 @@ #define _RECOVERY_UI_H #include "common.h" +#include "minzip/Zip.h" // Called before UI library is initialized. Can change things like // how many frames are included in various animations, etc. @@ -26,12 +27,6 @@ extern void device_ui_init(UIParameters* ui_parameters); // Called when recovery starts up. Returns 0. extern int device_recovery_start(); -// Called in the input thread when a new key (key_code) is pressed. -// *key_pressed is an array of KEY_MAX+1 bytes indicating which other -// keys are already pressed. Return true if the text display should -// be toggled. -extern int device_toggle_display(volatile char* key_pressed, int key_code); - // Called in the input thread when a new key (key_code) is pressed. // *key_pressed is an array of KEY_MAX+1 bytes indicating which other // keys are already pressed. Return true if the device should reboot @@ -65,18 +60,59 @@ extern int device_perform_action(int which); // are erased after this returns (whether it returns success or not). int device_wipe_data(); +#ifdef BOARD_NATIVE_DUALBOOT +// Called instead of 'verify_root_and_recovery' and should be used +// to invoke 'verify_root_and_recovery' for every system. +extern int device_verify_root_and_recovery(void); + +// Called from 'confirm_selection' to prepare the displayed title +// 'buf' will be used as title after this function has been called +// which means that you at least should write the title into this buffer. +// This should be used to add a prefix to the title which tells the user +// on which system the current action will be performed to. +extern int device_build_selection_title(char* buf, const char* title); + +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA +extern void device_toggle_truedualboot(void); +extern void device_choose_bootmode(void); +extern int device_get_truedualboot_entry(char* tdb_name); +extern int device_get_bootmode(char* bootmode_name); +extern int device_truedualboot_mount(const char* path, const char* mount_point); +extern int device_truedualboot_unmount(const char* path); +extern int device_truedualboot_format_volume(const char* volume); +extern int device_truedualboot_format_device(const char *device, const char *path, const char *fs_type); +extern int device_truedualboot_before_update(const char *path, ZipArchive *zip); +extern void device_truedualboot_after_load_volume_table(); +#endif +#endif + +// ui_wait_key() special return codes +/* +#define REBOOT -1 // ui_wait_key() timeout to reboot +#define CANCEL -2 // ui_cancel_wait_key() +*/ +#define REFRESH -3 + +// return actions by ui_handle_key() for get_menu_selection() #define NO_ACTION -1 #define HIGHLIGHT_UP -2 #define HIGHLIGHT_DOWN -3 #define SELECT_ITEM -4 +#define GO_BACK -5 +// main menu items for prompt_and_wait() #define ITEM_REBOOT 0 #define ITEM_APPLY_EXT 1 #define ITEM_APPLY_SDCARD 1 // historical synonym for ITEM_APPLY_EXT +#define ITEM_APPLY_ZIP 1 // used for installing an update from a zip #define ITEM_WIPE_DATA 2 #define ITEM_WIPE_CACHE 3 +// unused in cwr #define ITEM_APPLY_CACHE 4 +#define ITEM_NANDROID 4 +#define ITEM_PARTITION 5 +#define ITEM_ADVANCED 6 // Header text to display above the main menu. extern char* MENU_HEADERS[]; @@ -84,4 +120,15 @@ extern char* MENU_HEADERS[]; // Text of menu items. extern char* MENU_ITEMS[]; +// Loosely track the depth of the current menu +extern int ui_root_menu; + +int get_menu_selection(const char** headers, char** items, int menu_only, int initial_selection); + +void set_sdcard_update_bootloader_message(); + +extern int ui_handle_key(int key, int visible); + +void reboot_main_system(int cmd, int flags, char *arg); + #endif diff --git a/res/images/icon_cid.png b/res/images/icon_cid.png new file mode 100644 index 000000000..fbc15f85f Binary files /dev/null and b/res/images/icon_cid.png differ diff --git a/res/images/icon_clockwork.png b/res/images/icon_clockwork.png new file mode 100644 index 000000000..e696bc5db Binary files /dev/null and b/res/images/icon_clockwork.png differ diff --git a/res/images/icon_error.png b/res/images/icon_error.png index cb3d1ab22..676d17a0d 100644 Binary files a/res/images/icon_error.png and b/res/images/icon_error.png differ diff --git a/res/images/icon_firmware_error.png b/res/images/icon_firmware_error.png new file mode 100644 index 000000000..0bf7a6f04 Binary files /dev/null and b/res/images/icon_firmware_error.png differ diff --git a/res/images/icon_firmware_install.png b/res/images/icon_firmware_install.png new file mode 100644 index 000000000..d5b31ddc3 Binary files /dev/null and b/res/images/icon_firmware_install.png differ diff --git a/res/images/icon_installing.png b/res/images/icon_installing.png index 571eb8b0f..58a88d65d 100644 Binary files a/res/images/icon_installing.png and b/res/images/icon_installing.png differ diff --git a/res/images/icon_installing_overlay01.png b/res/images/icon_installing_overlay01.png index e762d6cbe..39fe05242 100644 Binary files a/res/images/icon_installing_overlay01.png and b/res/images/icon_installing_overlay01.png differ diff --git a/res/images/icon_installing_overlay02.png b/res/images/icon_installing_overlay02.png index f7a853017..ee905fd6c 100644 Binary files a/res/images/icon_installing_overlay02.png and b/res/images/icon_installing_overlay02.png differ diff --git a/res/images/icon_installing_overlay03.png b/res/images/icon_installing_overlay03.png index 1a1d738e4..3049f565c 100644 Binary files a/res/images/icon_installing_overlay03.png and b/res/images/icon_installing_overlay03.png differ diff --git a/res/images/icon_installing_overlay04.png b/res/images/icon_installing_overlay04.png index a74903d33..5fffdc7a0 100644 Binary files a/res/images/icon_installing_overlay04.png and b/res/images/icon_installing_overlay04.png differ diff --git a/res/images/icon_installing_overlay05.png b/res/images/icon_installing_overlay05.png index d17bdc006..7fa6ec2a8 100644 Binary files a/res/images/icon_installing_overlay05.png and b/res/images/icon_installing_overlay05.png differ diff --git a/res/images/icon_installing_overlay06.png b/res/images/icon_installing_overlay06.png index 1200b75cb..91e5afb4a 100644 Binary files a/res/images/icon_installing_overlay06.png and b/res/images/icon_installing_overlay06.png differ diff --git a/res/images/icon_installing_overlay07.png b/res/images/icon_installing_overlay07.png index 3838a85ad..d13a8a51d 100644 Binary files a/res/images/icon_installing_overlay07.png and b/res/images/icon_installing_overlay07.png differ diff --git a/res/images/stitch.png b/res/images/stitch.png new file mode 100644 index 000000000..8a822ee7a Binary files /dev/null and b/res/images/stitch.png differ diff --git a/roots.c b/roots.c index cb7e067a1..190b0ccee 100644 --- a/roots.c +++ b/roots.c @@ -22,121 +22,226 @@ #include #include +#include #include "mtdutils/mtdutils.h" -#include "mtdutils/mounts.h" +#include "mounts.h" #include "roots.h" #include "common.h" #include "make_ext4fs.h" -static int num_volumes = 0; -static Volume* device_volumes = NULL; +#include -static int parse_options(char* options, Volume* volume) { - char* option; - while (option = strtok(options, ",")) { - options = NULL; +#include "extendedcommands.h" +#include "flashutils/flashutils.h" +#include "recovery_ui.h" +#include "voldclient/voldclient.h" - if (strncmp(option, "length=", 7) == 0) { - volume->length = strtoll(option+7, NULL, 10); - } else { - LOGE("bad option \"%s\"\n", option); - return -1; - } - } - return 0; +static struct fstab *fstab = NULL; + +extern struct selabel_handle *sehandle; + +int get_num_volumes() { + return fstab->num_entries; +} + +Volume* get_device_volumes() { + return fstab->recs; } void load_volume_table() { - int alloc = 2; - device_volumes = malloc(alloc * sizeof(Volume)); - - // Insert an entry for /tmp, which is the ramdisk and is always mounted. - device_volumes[0].mount_point = "/tmp"; - device_volumes[0].fs_type = "ramdisk"; - device_volumes[0].device = NULL; - device_volumes[0].device2 = NULL; - device_volumes[0].length = 0; - num_volumes = 1; - - FILE* fstab = fopen("/etc/recovery.fstab", "r"); - if (fstab == NULL) { - LOGE("failed to open /etc/recovery.fstab (%s)\n", strerror(errno)); + int i; + int ret; + + fstab = fs_mgr_read_fstab("/etc/recovery.fstab"); + if (!fstab) { + LOGE("failed to read /etc/recovery.fstab\n"); return; } - char buffer[1024]; - int i; - while (fgets(buffer, sizeof(buffer)-1, fstab)) { - for (i = 0; buffer[i] && isspace(buffer[i]); ++i); - if (buffer[i] == '\0' || buffer[i] == '#') continue; - - char* original = strdup(buffer); - - char* mount_point = strtok(buffer+i, " \t\n"); - char* fs_type = strtok(NULL, " \t\n"); - char* device = strtok(NULL, " \t\n"); - // lines may optionally have a second device, to use if - // mounting the first one fails. - char* options = NULL; - char* device2 = strtok(NULL, " \t\n"); - if (device2) { - if (device2[0] == '/') { - options = strtok(NULL, " \t\n"); - } else { - options = device2; - device2 = NULL; - } - } + ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk", 0); + if (ret < 0 ) { + LOGE("failed to add /tmp entry to fstab\n"); + fs_mgr_free_fstab(fstab); + fstab = NULL; + return; + } - if (mount_point && fs_type && device) { - while (num_volumes >= alloc) { - alloc *= 2; - device_volumes = realloc(device_volumes, alloc*sizeof(Volume)); - } - device_volumes[num_volumes].mount_point = strdup(mount_point); - device_volumes[num_volumes].fs_type = strdup(fs_type); - device_volumes[num_volumes].device = strdup(device); - device_volumes[num_volumes].device2 = - device2 ? strdup(device2) : NULL; - - device_volumes[num_volumes].length = 0; - if (parse_options(options, device_volumes + num_volumes) != 0) { - LOGE("skipping malformed recovery.fstab line: %s\n", original); - } else { - ++num_volumes; - } - } else { - LOGE("skipping malformed recovery.fstab line: %s\n", original); + // Process vold-managed volumes with mount point "auto" + for (i = 0; i < fstab->num_entries; ++i) { + Volume* v = &fstab->recs[i]; + if (fs_mgr_is_voldmanaged(v) && strcmp(v->mount_point, "auto") == 0) { + char mount[PATH_MAX]; + + // Set the mount point to /storage/label which as used by vold + snprintf(mount, PATH_MAX, "/storage/%s", v->label); + free(v->mount_point); + v->mount_point = strdup(mount); } - free(original); } - fclose(fstab); +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + device_truedualboot_after_load_volume_table(); +#endif - printf("recovery filesystem table\n"); - printf("=========================\n"); - for (i = 0; i < num_volumes; ++i) { - Volume* v = &device_volumes[i]; - printf(" %d %s %s %s %s %lld\n", i, v->mount_point, v->fs_type, - v->device, v->device2, v->length); + fprintf(stderr, "recovery filesystem table\n"); + fprintf(stderr, "=========================\n"); + for (i = 0; i < fstab->num_entries; ++i) { + Volume* v = &fstab->recs[i]; + fprintf(stderr, " %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, + v->blk_device, v->length); } - printf("\n"); + fprintf(stderr, "\n"); } Volume* volume_for_path(const char* path) { + return fs_mgr_get_entry_for_mount_point(fstab, path); +} + +int is_primary_storage_voldmanaged() { + Volume* v; + v = volume_for_path("/storage/sdcard0"); + return fs_mgr_is_voldmanaged(v); +} + +static char* primary_storage_path = NULL; +char* get_primary_storage_path() { + if (primary_storage_path == NULL) { + if (volume_for_path("/storage/sdcard0")) + primary_storage_path = "/storage/sdcard0"; + else + primary_storage_path = "/sdcard"; + } + return primary_storage_path; +} + +int get_num_extra_volumes() { + int num = 0; + int i; + for (i = 0; i < get_num_volumes(); i++) { + Volume* v = get_device_volumes() + i; + if ((strcmp("/external_sd", v->mount_point) == 0) || + ((strcmp(get_primary_storage_path(), v->mount_point) != 0) && + fs_mgr_is_voldmanaged(v) && vold_is_volume_available(v->mount_point))) + num++; + } + return num; +} + +char** get_extra_storage_paths() { + int i = 0, j = 0; + static char* paths[MAX_NUM_MANAGED_VOLUMES]; + int num_extra_volumes = get_num_extra_volumes(); + + if (num_extra_volumes == 0) + return NULL; + + for (i = 0; i < get_num_volumes(); i++) { + Volume* v = get_device_volumes() + i; + if ((strcmp("/external_sd", v->mount_point) == 0) || + ((strcmp(get_primary_storage_path(), v->mount_point) != 0) && + fs_mgr_is_voldmanaged(v) && vold_is_volume_available(v->mount_point))) { + paths[j] = v->mount_point; + j++; + } + } + paths[j] = NULL; + + return paths; +} + +static char* android_secure_path = NULL; +char* get_android_secure_path() { + if (android_secure_path == NULL) { + android_secure_path = malloc(sizeof("/.android_secure") + strlen(get_primary_storage_path()) + 1); + sprintf(android_secure_path, "%s/.android_secure", primary_storage_path); + } + return android_secure_path; +} + +int try_mount(const char* device, const char* mount_point, const char* fs_type, const char* fs_options) { + if (device == NULL || mount_point == NULL || fs_type == NULL) + return -1; + int ret = 0; + if (fs_options == NULL) { + ret = mount(device, mount_point, fs_type, + MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); + } + else { + char mount_cmd[PATH_MAX]; + sprintf(mount_cmd, "mount -t %s -o%s %s %s", fs_type, fs_options, device, mount_point); + ret = __system(mount_cmd); + } + if (ret == 0) + return 0; + LOGW("failed to mount %s (%s)\n", device, strerror(errno)); + return ret; +} + +int is_data_media() { + int i; + int has_sdcard = 0; + for (i = 0; i < get_num_volumes(); i++) { + Volume* vol = get_device_volumes() + i; + if (strcmp(vol->fs_type, "datamedia") == 0) + return 1; + if (strcmp(vol->mount_point, "/sdcard") == 0) + has_sdcard = 1; + if (fs_mgr_is_voldmanaged(vol) && + (strcmp(vol->mount_point, "/storage/sdcard0") == 0)) + has_sdcard = 1; + } + return !has_sdcard; +} + +void setup_data_media() { int i; - for (i = 0; i < num_volumes; ++i) { - Volume* v = device_volumes+i; - int len = strlen(v->mount_point); - if (strncmp(path, v->mount_point, len) == 0 && - (path[len] == '\0' || path[len] == '/')) { - return v; + char* mount_point = "/sdcard"; + for (i = 0; i < get_num_volumes(); i++) { + Volume* vol = get_device_volumes() + i; + if (strcmp(vol->fs_type, "datamedia") == 0) { + mount_point = vol->mount_point; + break; } } - return NULL; + + // recreate /data/media with proper permissions + rmdir(mount_point); + mkdir("/data/media", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + symlink("/data/media", mount_point); +} + +int is_data_media_volume_path(const char* path) { + Volume* v = volume_for_path(path); + if (v != NULL) + return strcmp(v->fs_type, "datamedia") == 0; + + if (!is_data_media()) { + return 0; + } + + return strcmp(path, "/sdcard") == 0 || path == strstr(path, "/sdcard/"); } int ensure_path_mounted(const char* path) { + return ensure_path_mounted_at_mount_point(path, NULL); +} + +int ensure_path_mounted_at_mount_point(const char* path, const char* mount_point) { +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + if(device_truedualboot_mount(path, mount_point) <= 0) + return 0; +#endif + + if (is_data_media_volume_path(path)) { + if (ui_should_log_stdout()) { + LOGI("using /data/media for %s.\n", path); + } + int ret; + if (0 != (ret = ensure_path_mounted("/data"))) + return ret; + setup_data_media(); + return 0; + } Volume* v = volume_for_path(path); if (v == NULL) { LOGE("unknown volume for path [%s]\n", path); @@ -154,54 +259,73 @@ int ensure_path_mounted(const char* path) { return -1; } + if (NULL == mount_point) + mount_point = v->mount_point; + const MountedVolume* mv = - find_mounted_volume_by_mount_point(v->mount_point); + find_mounted_volume_by_mount_point(mount_point); if (mv) { // volume is already mounted return 0; } - mkdir(v->mount_point, 0755); // in case it doesn't already exist + mkdir(mount_point, 0755); // in case it doesn't already exist + + if (fs_mgr_is_voldmanaged(v)) { + return vold_mount_volume(mount_point, 1) == CommandOkay ? 0 : -1; - if (strcmp(v->fs_type, "yaffs2") == 0) { + } else if (strcmp(v->fs_type, "yaffs2") == 0) { // mount an MTD partition as a YAFFS2 filesystem. mtd_scan_partitions(); const MtdPartition* partition; - partition = mtd_find_partition_by_name(v->device); + partition = mtd_find_partition_by_name(v->blk_device); if (partition == NULL) { LOGE("failed to find \"%s\" partition to mount at \"%s\"\n", - v->device, v->mount_point); + v->blk_device, mount_point); return -1; } - return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0); + return mtd_mount_partition(partition, mount_point, v->fs_type, 0); } else if (strcmp(v->fs_type, "ext4") == 0 || + strcmp(v->fs_type, "ext3") == 0 || + strcmp(v->fs_type, "rfs") == 0 || strcmp(v->fs_type, "vfat") == 0) { - result = mount(v->device, v->mount_point, v->fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); - if (result == 0) return 0; - - if (v->device2) { - LOGW("failed to mount %s (%s); trying %s\n", - v->device, strerror(errno), v->device2); - result = mount(v->device2, v->mount_point, v->fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); - if (result == 0) return 0; - } - - LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno)); - return -1; + if ((result = try_mount(v->blk_device, mount_point, v->fs_type, v->fs_options)) == 0) + return 0; + if ((result = try_mount(v->blk_device, mount_point, v->fs_type2, v->fs_options2)) == 0) + return 0; + if ((result = try_mount(v->blk_device2, mount_point, v->fs_type2, v->fs_options2)) == 0) + return 0; + return result; + } else { + // let's try mounting with the mount binary and hope for the best. + char mount_cmd[PATH_MAX]; + sprintf(mount_cmd, "mount %s", mount_point); + return __system(mount_cmd); } - LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point); return -1; } int ensure_path_unmounted(const char* path) { +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + if(device_truedualboot_unmount(path) <= 0) + return 0; +#endif + + // if we are using /data/media, do not ever unmount volumes /data or /sdcard + if (is_data_media_volume_path(path)) { + return ensure_path_unmounted("/data"); + } + if (strstr(path, "/data") == path && is_data_media() && is_data_media_preserved()) { + return 0; + } + Volume* v = volume_for_path(path); if (v == NULL) { LOGE("unknown volume for path [%s]\n", path); return -1; } + if (strcmp(v->fs_type, "ramdisk") == 0) { // the ramdisk is always mounted; you can't unmount it. return -1; @@ -221,23 +345,69 @@ int ensure_path_unmounted(const char* path) { return 0; } + if (fs_mgr_is_voldmanaged(volume_for_path(v->mount_point))) + return vold_unmount_volume(v->mount_point, 0, 1) == CommandOkay ? 0 : -1; + return unmount_mounted_volume(mv); } int format_volume(const char* volume) { +#ifdef BOARD_NATIVE_DUALBOOT_SINGLEDATA + if(device_truedualboot_format_volume(volume) <= 0) + return 0; +#endif + + if (is_data_media_volume_path(volume)) { + return format_unknown_device(NULL, volume, NULL); + } + // check to see if /data is being formatted, and if it is /data/media + // Note: the /sdcard check is redundant probably, just being safe. + if (strstr(volume, "/data") == volume && is_data_media() && is_data_media_preserved()) { + return format_unknown_device(NULL, volume, NULL); + } + Volume* v = volume_for_path(volume); if (v == NULL) { - LOGE("unknown volume \"%s\"\n", volume); + // silent failure for sd-ext + if (strcmp(volume, "/sd-ext") != 0) + LOGE("unknown volume '%s'\n", volume); return -1; } + // silent failure to format non existing sd-ext when defined in recovery.fstab + if (strcmp(volume, "/sd-ext") == 0) { + struct stat s; + if (0 != stat(v->blk_device, &s)) { + LOGI("Skipping format of sd-ext\n"); + 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); + } + if (strcmp(v->fs_type, "auto") == 0) { + // Format with current filesystem + return vold_format_volume(v->mount_point, 1) == CommandOkay ? 0 : -1; + } else { + // Format filesystem defined in fstab + return vold_custom_format_volume(v->mount_point, v->fs_type, 1) == CommandOkay ? 0 : -1; + } + } + if (strcmp(v->fs_type, "ramdisk") == 0) { // you can't format the ramdisk. LOGE("can't format_volume \"%s\"", volume); return -1; } if (strcmp(v->mount_point, volume) != 0) { +#if 0 LOGE("can't give path \"%s\" to format_volume\n", volume); return -1; +#endif + return format_unknown_device(v->blk_device, volume, NULL); } if (ensure_path_unmounted(volume) != 0) { @@ -247,36 +417,68 @@ int format_volume(const char* volume) { 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->device); + const MtdPartition* partition = mtd_find_partition_by_name(v->blk_device); if (partition == NULL) { - LOGE("format_volume: no MTD partition \"%s\"\n", v->device); + LOGE("format_volume: no MTD partition \"%s\"\n", v->blk_device); return -1; } MtdWriteContext *write = mtd_write_partition(partition); if (write == NULL) { - LOGW("format_volume: can't open MTD \"%s\"\n", v->device); + LOGW("format_volume: can't open MTD \"%s\"\n", v->blk_device); return -1; } else if (mtd_erase_blocks(write, -1) == (off_t) -1) { - LOGW("format_volume: can't erase MTD \"%s\"\n", v->device); + LOGW("format_volume: can't erase MTD \"%s\"\n", v->blk_device); mtd_write_close(write); return -1; } else if (mtd_write_close(write)) { - LOGW("format_volume: can't close MTD \"%s\"\n", v->device); + LOGW("format_volume: can't close MTD \"%s\"\n", v->blk_device); return -1; } return 0; } if (strcmp(v->fs_type, "ext4") == 0) { - int result = make_ext4fs(v->device, v->length); + int result = make_ext4fs(v->blk_device, v->length, volume, sehandle); if (result != 0) { - LOGE("format_volume: make_extf4fs failed on %s\n", v->device); + LOGE("format_volume: make_extf4fs failed on %s\n", v->blk_device); return -1; } return 0; } +#ifdef USE_F2FS + if (strcmp(v->fs_type, "f2fs") == 0) { + char* args[] = { "mkfs.f2fs", v->blk_device }; + if (make_f2fs_main(2, args) != 0) { + LOGE("format_volume: mkfs.f2fs failed on %s\n", v->blk_device); + return -1; + } + return 0; + } +#endif + +#if 0 LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type); return -1; +#endif + return format_unknown_device(v->blk_device, volume, v->fs_type); +} + +static int data_media_preserved_state = 1; +void preserve_data_media(int val) { + data_media_preserved_state = val; +} + +int is_data_media_preserved() { + return data_media_preserved_state; +} + +void setup_legacy_storage_paths() { + char* primary_path = get_primary_storage_path(); + + if (!is_data_media_volume_path(primary_path)) { + rmdir("/sdcard"); + symlink(primary_path, "/sdcard"); + } } diff --git a/roots.h b/roots.h index cf59bfdf3..5ed8cb3a2 100644 --- a/roots.h +++ b/roots.h @@ -28,6 +28,7 @@ Volume* volume_for_path(const char* path); // Make sure that the volume 'path' is on is mounted. Returns 0 on // success (volume is mounted). int ensure_path_mounted(const char* path); +int ensure_path_mounted_at_mount_point(const char* path, const char* mount_point); // Make sure that the volume 'path' is on is mounted. Returns 0 on // success (volume is unmounted); @@ -38,4 +39,21 @@ int ensure_path_unmounted(const char* path); // it is mounted. int format_volume(const char* volume); +char* get_primary_storage_path(); +char** get_extra_storage_paths(); +char* get_android_secure_path(); +void setup_legacy_storage_paths(); +int get_num_extra_volumes(); +int get_num_volumes(); + +Volume* get_device_volumes(); + +int is_data_media(); +void setup_data_media(); +int is_data_media_volume_path(const char* path); +void preserve_data_media(int val); +int is_data_media_preserved(); + +#define MAX_NUM_MANAGED_VOLUMES 10 + #endif // RECOVERY_ROOTS_H_ diff --git a/su/Android.mk b/su/Android.mk new file mode 100644 index 000000000..9e8ecc862 --- /dev/null +++ b/su/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + + +LOCAL_MODULE := su.recovery +LOCAL_MODULE_TAGS := eng debug +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_STATIC_LIBRARIES := libc liblog libcutils +LOCAL_C_INCLUDES := external/sqlite/dist +LOCAL_SRC_FILES := ../../../external/koush/Superuser/Superuser/jni/su/su.c ../../../external/koush/Superuser/Superuser/jni/su/daemon.c ../../../external/koush/Superuser/Superuser/jni/su/activity.c ../../../external/koush/Superuser/Superuser/jni/su/utils.c ../../../external/koush/Superuser/Superuser/jni/su/pts.c dbstub.c +LOCAL_CFLAGS := -DSQLITE_OMIT_LOAD_EXTENSION -DREQUESTOR=\"$(SUPERUSER_PACKAGE)\" +ifdef SUPERUSER_PACKAGE_PREFIX + LOCAL_CFLAGS += -DREQUESTOR_PREFIX=\"$(SUPERUSER_PACKAGE_PREFIX)\" +endif +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_MODULE := install-su.sh +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := run-su-daemon.sh +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := ../../../external/koush/Superuser/Superuser/assets/install-recovery.sh +include $(BUILD_PREBUILT) diff --git a/su/dbstub.c b/su/dbstub.c new file mode 100644 index 000000000..f06bf1b81 --- /dev/null +++ b/su/dbstub.c @@ -0,0 +1,11 @@ +#include +#include +#include +#include +#include + +#include "../../../external/koush/Superuser/Superuser/jni/su/su.h" + +policy_t database_check(struct su_context *ctx) { + return ALLOW; +} diff --git a/su/install-su.sh b/su/install-su.sh new file mode 100644 index 000000000..136b09e3d --- /dev/null +++ b/su/install-su.sh @@ -0,0 +1,26 @@ +#!/sbin/sh +cp /sbin/su.recovery /system/xbin/su +chmod 6755 /system/xbin/su +ln -sf /system/xbin/su /system/bin/su + +# if the system is at least 4.3, and there is no su daemon built in, +# let's try to install it using install-recovery.sh +BUILD_RELEASE_VERSION=$(cat /system/build.prop | grep ro\\.build\\.version\\.release) +IS_43=$(echo $BUILD_RELEASE_VERSION | grep 4\\.3) +if [ ! -z "$IS_43" ] +then + if [ -o "$IS_43" \> "4.3" -o "$IS_43" == "4.3" ] + then + # check for rom su daemon before clobbering install-recovery.sh + if [ ! -f "/system/etc/.has_su_daemon" ] + then + chattr -i /system/etc/install-recovery.sh + cp /sbin/run-su-daemon.sh /system/etc/install-recovery.sh + chmod 755 /system/etc/install-recovery.sh + # note that an post install su daemon was installed + # so recovery doesn't freak out and recommend you disable + # the install-recovery.sh execute bit. + touch /system/etc/.installed_su_daemon + fi + fi +fi diff --git a/testdata/otasigned_f4.zip b/testdata/otasigned_f4.zip new file mode 100644 index 000000000..dd1e4dd40 Binary files /dev/null and b/testdata/otasigned_f4.zip differ diff --git a/testdata/otasigned_f4_sha256.zip b/testdata/otasigned_f4_sha256.zip new file mode 100644 index 000000000..3af408c40 Binary files /dev/null and b/testdata/otasigned_f4_sha256.zip differ diff --git a/testdata/otasigned_sha256.zip b/testdata/otasigned_sha256.zip new file mode 100644 index 000000000..0ed4409b3 Binary files /dev/null and b/testdata/otasigned_sha256.zip differ diff --git a/testdata/test_f4.pk8 b/testdata/test_f4.pk8 new file mode 100644 index 000000000..3052613c5 Binary files /dev/null and b/testdata/test_f4.pk8 differ diff --git a/testdata/test_f4.x509.pem b/testdata/test_f4.x509.pem new file mode 100644 index 000000000..814abcf99 --- /dev/null +++ b/testdata/test_f4.x509.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIJAKhkCO1dDYMaMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV +BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBW +aWV3MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMT +B1Rlc3QxMjMwHhcNMTIwNzI1MTg1NzAzWhcNMzkxMjExMTg1NzAzWjBvMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQD +EwdUZXN0MTIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu8WwMN9x +4Mz7YgkG2qy9g8/kl5ZoYrUM0ApHhaITAcL7RXLZaNipCf0w/YjYTQgj+75MK30x +TsnPeWNOEwA62gkHrZyyWfxBRO6kBYuIuI4roGDBJOmKQ1OEaDeIRKu7q5V8v3Cs +0wQDAQWTbhpxBZr9UYFgJUg8XWBfPrGJLVwsoiy4xrMhoTlNZKHfwOMMqVtSHkZX +qydYrcIzyjh+TO0e/xSNQ8MMRRbtqWgCHN6Rzhog3IHZu0RaPoukariopjXM/s0V +gTm3rHDHCOpna2pNblyiFlvbkoCs769mtNmx/yrDShO30jg/xaG8RypKDvTChzOT +oWW/XQ5VEXjbHwIDAQABo4HUMIHRMB0GA1UdDgQWBBRlT2dEZJY1tmUM8mZ0xnhS +GdD9TTCBoQYDVR0jBIGZMIGWgBRlT2dEZJY1tmUM8mZ0xnhSGdD9TaFzpHEwbzEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50 +YWluIFZpZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4G +A1UEAxMHVGVzdDEyM4IJAKhkCO1dDYMaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN +AQEFBQADggEBAHqnXHtE+h3hvGmHh24GT51vGAYLc68WUUtCVlMIU85zQ757wlxZ +BmRypZ1i9hSqnXj5n+mETV5rFX3g2gvdAPVHkRycuDa2aUdZSE8cW4Z6qYFx6SaD +e+3SyXokpUquW64RuHJrf/yd/FnGjneBe3Qpm2reuzGWNH90qZGdbsfNaCm5kx2L +X+ZNHM3CcGMLaphY5++sM0JxSEcju5EK33ZYgLf4YdlbyMp8LDFVNd7ff0SFi9fF +0ZlAsJWoS3QmVCj2744BFdsCu7UHpnYpG6X3MT4SHAawdOaT5zSuaCl2xx6H0O7t +w/Fvbl/KVD1ZmLHgBKjDMNSh0OB9mSsDWpw= +-----END CERTIFICATE----- diff --git a/testdata/test_f4_sha256.x509.pem b/testdata/test_f4_sha256.x509.pem new file mode 100644 index 000000000..9d5376b45 --- /dev/null +++ b/testdata/test_f4_sha256.x509.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIJAKhkCO1dDYMaMA0GCSqGSIb3DQEBCwUAMG8xCzAJBgNV +BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBW +aWV3MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMT +B1Rlc3QxMjMwHhcNMTMwNDEwMTcyMzUyWhcNMTMwNTEwMTcyMzUyWjBvMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQD +EwdUZXN0MTIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu8WwMN9x +4Mz7YgkG2qy9g8/kl5ZoYrUM0ApHhaITAcL7RXLZaNipCf0w/YjYTQgj+75MK30x +TsnPeWNOEwA62gkHrZyyWfxBRO6kBYuIuI4roGDBJOmKQ1OEaDeIRKu7q5V8v3Cs +0wQDAQWTbhpxBZr9UYFgJUg8XWBfPrGJLVwsoiy4xrMhoTlNZKHfwOMMqVtSHkZX +qydYrcIzyjh+TO0e/xSNQ8MMRRbtqWgCHN6Rzhog3IHZu0RaPoukariopjXM/s0V +gTm3rHDHCOpna2pNblyiFlvbkoCs769mtNmx/yrDShO30jg/xaG8RypKDvTChzOT +oWW/XQ5VEXjbHwIDAQABo4HUMIHRMB0GA1UdDgQWBBRlT2dEZJY1tmUM8mZ0xnhS +GdD9TTCBoQYDVR0jBIGZMIGWgBRlT2dEZJY1tmUM8mZ0xnhSGdD9TaFzpHEwbzEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50 +YWluIFZpZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4G +A1UEAxMHVGVzdDEyM4IJAKhkCO1dDYMaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN +AQELBQADggEBAKWWQ9S0V9wWjrMJe8exj1gklwD1Ysi0vi+h2tfixahelrpsNkWi +EFjoUSHEkW9ThLmtui646uAlwSiWtSn1XkGGmIJ3s+gmAFUcMc0CaK0dgoq/M9zn +fQ0Vkzc1tK4MLsf+CbPDywPycb6+T3dBkerbWn9GUpjGl1ANWlciXZZ3657m61sL +HhwUOBxbZZ6sYP4ed2SVCf45GgMyJ0VoUg5yI2JzPAgOkGfeEIPVXE1M94edJY4G +8eHYvXovJZwXvKFI+ZyS0KBPx8cpfw89RB9qmkxqNBIm8qWb3qBiuBEIPj+NF/7w +sC/Fv8NNXkVquy0xa0qdyJBABzWE18zGcXs= +-----END CERTIFICATE----- diff --git a/testdata/testkey.pk8 b/testdata/testkey.pk8 new file mode 100644 index 000000000..586c1bd5c Binary files /dev/null and b/testdata/testkey.pk8 differ diff --git a/testdata/testkey.x509.pem b/testdata/testkey.x509.pem new file mode 100644 index 000000000..e242d83e2 --- /dev/null +++ b/testdata/testkey.x509.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM +qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 +wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy +4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU +RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s +zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw +HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ +AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa +J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y +LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe ++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX +31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr +sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= +-----END CERTIFICATE----- diff --git a/testdata/testkey_sha256.x509.pem b/testdata/testkey_sha256.x509.pem new file mode 100644 index 000000000..002ce8968 --- /dev/null +++ b/testdata/testkey_sha256.x509.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0xMzA0MTAxODA1MzZaFw0xMzA1MTAxODA1MzZaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM +qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 +wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy +4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU +RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s +zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw +HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ +AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKRVj9hOaozH1W8Wb4CNj7sCWixh +UMMZJXkxUtvUVHZGefp6MdtYiD/ZM7YRwZphm9aNhkykbHJdZ3lPzeL2csCa+sDQ +8sIzGu0/aD6p4zgIKQZmz0mZHqPGbHoLWOmA9EexRCFZ7vO/kO56ZbyhfFz2DI3S +Yez65CabErOFhNX6WukSPbV3zfsHRDD5JUStb/ko6t99HXsvIO0Ax9poj60PpCC1 +SiFzHZUY9mOnUfJFs+3NWCwKtP9nho3mZ3pJ1i+SeF6JiqbE3KHl4CDBeVGcu3CK +fiUZ8e8iXVN471Cgc5GD6Ud1pS7ifNZJsKhbETQ63KmvHCLRPi4NmP67uDE= +-----END CERTIFICATE----- diff --git a/tools/ota/check-lost+found.c b/tools/ota/check-lost+found.c index f85627544..da02f4602 100644 --- a/tools/ota/check-lost+found.c +++ b/tools/ota/check-lost+found.c @@ -44,7 +44,7 @@ static const char *kPartitions[] = { "/system", "/data", "/cache", NULL }; * 2. Write a log entry with the number of files in lost+found directories. */ -int main(int argc, char **argv) { +int main(int argc __attribute__((unused)), char **argv __attribute__((unused))) { mkdir(kOutputDir, 0755); chown(kOutputDir, AID_SYSTEM, AID_SYSTEM); FILE *out = fopen(kOutputFile, "a"); diff --git a/ui.c b/ui.c index 25df3d043..10527e289 100644 --- a/ui.c +++ b/ui.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2014 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. @@ -29,17 +30,44 @@ #include #include "common.h" -#include +#include "cutils/android_reboot.h" +#include "cutils/properties.h" #include "minui/minui.h" #include "recovery_ui.h" +#include "voldclient/voldclient.h" + +#ifdef BOARD_RECOVERY_SWIPE +#include "input/touch.h" +#endif + +extern int __system(const char *command); +extern int volumes_changed(); + +#if defined(BOARD_HAS_NO_SELECT_BUTTON) || defined(BOARD_TOUCH_RECOVERY) +static int gShowBackButton = 1; +#else +static int gShowBackButton = 0; +#endif #define MAX_COLS 96 #define MAX_ROWS 32 +#define MENU_MAX_COLS 64 +#define MENU_MAX_ROWS 250 +#define MENU_ITEM_HEADER " - " +#define MENU_ITEM_HEADER_LENGTH strlen(MENU_ITEM_HEADER) + +#define MIN_LOG_ROWS 3 + +#define CHAR_WIDTH BOARD_RECOVERY_CHAR_WIDTH +#define CHAR_HEIGHT BOARD_RECOVERY_CHAR_HEIGHT -#define CHAR_WIDTH 10 -#define CHAR_HEIGHT 18 +// Delay in seconds to refresh clock and USB plugged volumes +#define REFRESH_TIME_USB_INTERVAL 5 -#define UI_WAIT_KEY_TIMEOUT_SEC 120 +#define UI_WAIT_KEY_TIMEOUT_SEC 3600 +#define UI_KEY_REPEAT_INTERVAL 80 +#define UI_KEY_WAIT_REPEAT 400 +#define UI_MIN_PROG_DELTA_MS 200 UIParameters ui_parameters = { 6, // indeterminate progress bar frames @@ -54,18 +82,32 @@ static gr_surface *gInstallationOverlay; static gr_surface *gProgressBarIndeterminate; static gr_surface gProgressBarEmpty; static gr_surface gProgressBarFill; +static gr_surface gBackground; +static int ui_has_initialized = 0; +static int ui_log_stdout = 1; + +static int boardEnableKeyRepeat = 0; +static int boardRepeatableKeys[64]; +static int boardNumRepeatableKeys = 0; static const struct { gr_surface* surface; const char *name; } BITMAPS[] = { - { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" }, - { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" }, - { &gProgressBarEmpty, "progress_empty" }, - { &gProgressBarFill, "progress_fill" }, - { NULL, NULL }, + { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" }, + { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" }, + { &gBackgroundIcon[BACKGROUND_ICON_CLOCKWORK], "icon_clockwork" }, + { &gBackgroundIcon[BACKGROUND_ICON_CID], "icon_cid" }, + { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_INSTALLING], "icon_firmware_install" }, + { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_ERROR], "icon_firmware_error" }, + { &gProgressBarEmpty, "progress_empty" }, + { &gProgressBarFill, "progress_fill" }, + { &gBackground, "stitch" }, + { NULL, NULL }, }; static int gCurrentIcon = 0; static int gInstallingFrame = 0; +static struct timeval lastprogupd = (struct timeval) {0}; + static enum ProgressBarType { PROGRESSBAR_TYPE_NONE, PROGRESSBAR_TYPE_INDETERMINATE, @@ -73,30 +115,55 @@ static enum ProgressBarType { } gProgressBarType = PROGRESSBAR_TYPE_NONE; // Progress bar scope of current operation -static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0; -static double gProgressScopeTime, gProgressScopeDuration; +static float gProgressScopeStart = 0.0; +static float gProgressScopeSize = 0.0; +static float gProgress = 0.0; +static double gProgressScopeTime; +static double gProgressScopeDuration; // Set to 1 when both graphics pages are the same (except for the progress bar) static int gPagesIdentical = 0; // Log text overlay, displayed when a magic key is pressed static char text[MAX_ROWS][MAX_COLS]; -static int text_cols = 0, text_rows = 0; -static int text_col = 0, text_row = 0, text_top = 0; +static int text_cols = 0; +static int text_rows = 0; +static int text_col = 0; +static int text_row = 0; +static int text_top = 0; static int show_text = 0; -static int show_text_ever = 0; // has show_text ever been 1? +static int show_text_ever = 0; // i.e. has show_text ever been 1? -static char menu[MAX_ROWS][MAX_COLS]; +static char menu[MENU_MAX_ROWS][MENU_MAX_COLS]; +static int menuTextColor[4] = {MENU_TEXT_COLOR}; static int show_menu = 0; -static int menu_top = 0, menu_items = 0, menu_sel = 0; +static int menu_top = 0; +static int menu_items = 0; +static int menu_sel = 0; +static int menu_show_start = 0; // line at which menu display starts +static int max_menu_rows; + +static unsigned cur_rainbow_color = 0; +static int gRainbowMode = 0; // Key event input queue static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER; static int key_queue[256], key_queue_len = 0; +static unsigned long key_last_repeat[KEY_MAX + 1]; +static unsigned long key_press_time[KEY_MAX + 1]; static volatile char key_pressed[KEY_MAX + 1]; -// Return the current time as a double (including fractions of a second). +// Prototypes for static functions that are used before defined +static void update_screen_locked(void); +static int ui_wait_key_with_repeat(); +static void ui_rainbow_mode(); + +#ifdef BOARD_TOUCH_RECOVERY +#include "../../vendor/koush/recovery/touch.c" +#endif + +// Current time static double now() { struct timeval tv; gettimeofday(&tv, NULL); @@ -120,11 +187,18 @@ static void draw_install_overlay_locked(int frame) { // Clear the screen and draw the currently selected background icon (if any). // Should only be called with gUpdateMutex locked. -static void draw_background_locked(int icon) -{ +static void draw_background_locked(int icon) { gPagesIdentical = 0; - gr_color(0, 0, 0, 255); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + int bw = gr_get_width(gBackground); + int bh = gr_get_height(gBackground); + int bx = 0; + int by = 0; + for (by = 0; by < gr_fb_height(); by += bh) { + for (bx = 0; bx < gr_fb_width(); bx += bw) { + gr_blit(gBackground, 0, 0, bw, bh, bx, by); + } + } if (icon) { gr_surface surface = gBackgroundIcon[icon]; @@ -139,11 +213,25 @@ static void draw_background_locked(int icon) } } -// Draw the progress bar (if any) on the screen. Does not flip pages. -// Should only be called with gUpdateMutex locked. -static void draw_progress_locked() -{ +static void ui_increment_frame() { + if (!ui_has_initialized) return; + gInstallingFrame = + (gInstallingFrame + 1) % ui_parameters.installing_frames; +} + +static long delta_milliseconds(struct timeval from, struct timeval to) { + long delta_sec = (to.tv_sec - from.tv_sec)*1000; + long delta_usec = (to.tv_usec - from.tv_usec)/1000; + return (delta_sec + delta_usec); +} + +// Draw the progress bar (if any) on the screen; does not flip pages +// Should only be called with gUpdateMutex locked and if ui_has_initialized is true +static void draw_progress_locked() { if (gCurrentIcon == BACKGROUND_ICON_INSTALLING) { + // update the installation animation, if active + if (ui_parameters.installing_frames > 0) + ui_increment_frame(); draw_install_overlay_locked(gInstallingFrame); } @@ -177,65 +265,119 @@ static void draw_progress_locked() frame = (frame + 1) % ui_parameters.indeterminate_frames; } } + + gettimeofday(&lastprogupd, NULL); } static void draw_text_line(int row, const char* t) { if (t[0] != '\0') { - gr_text(0, (row+1)*CHAR_HEIGHT-1, t); + if (ui_get_rainbow_mode()) ui_rainbow_mode(); + gr_text(0, (row+1)*CHAR_HEIGHT-1, t, 0); } } +void ui_setMenuTextColor(int r, int g, int b, int a) { + menuTextColor[0] = r; + menuTextColor[1] = g; + menuTextColor[2] = b; + menuTextColor[3] = a; +} + // Redraw everything on the screen. Does not flip pages. // Should only be called with gUpdateMutex locked. -static void draw_screen_locked(void) -{ +static void draw_screen_locked(void) { + if (!ui_has_initialized) + return; + draw_background_locked(gCurrentIcon); draw_progress_locked(); if (show_text) { - gr_color(0, 0, 0, 160); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - + int total_rows = gr_fb_height() / CHAR_HEIGHT; int i = 0; + int j = 0; + int row = 0; // current row that we are drawing on if (show_menu) { - gr_color(64, 96, 255, 255); - gr_fill(0, (menu_top+menu_sel) * CHAR_HEIGHT, - gr_fb_width(), (menu_top+menu_sel+1)*CHAR_HEIGHT+1); +#ifndef BOARD_TOUCH_RECOVERY + gr_color(menuTextColor[0], menuTextColor[1], menuTextColor[2], menuTextColor[3]); + gr_fill(0, (menu_top + menu_sel - menu_show_start) * CHAR_HEIGHT, + gr_fb_width(), (menu_top + menu_sel - menu_show_start + 1)*CHAR_HEIGHT+1); + + gr_color(HEADER_TEXT_COLOR); + for (i = 0; i < menu_top; ++i) { + draw_text_line(i, menu[i]); + row++; + } - for (; i < menu_top + menu_items; ++i) { + if (menu_items - menu_show_start + menu_top >= max_menu_rows) + j = max_menu_rows - menu_top; + else + j = menu_items - menu_show_start; + + gr_color(menuTextColor[0], menuTextColor[1], menuTextColor[2], menuTextColor[3]); + for (i = menu_show_start + menu_top; i < (menu_show_start + menu_top + j); ++i) { if (i == menu_top + menu_sel) { gr_color(255, 255, 255, 255); - draw_text_line(i, menu[i]); - gr_color(64, 96, 255, 255); + draw_text_line(i - menu_show_start , menu[i]); + gr_color(menuTextColor[0], menuTextColor[1], menuTextColor[2], menuTextColor[3]); } else { - draw_text_line(i, menu[i]); + gr_color(menuTextColor[0], menuTextColor[1], menuTextColor[2], menuTextColor[3]); + draw_text_line(i - menu_show_start, menu[i]); } + row++; + if (row >= max_menu_rows) + break; } - gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1, - gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1); - ++i; - } - gr_color(255, 255, 0, 255); + gr_fill(0, row*CHAR_HEIGHT+CHAR_HEIGHT/2-1, + gr_fb_width(), row*CHAR_HEIGHT+CHAR_HEIGHT/2+1); +#else + row = draw_touch_menu(menu, menu_items, menu_top, menu_sel, menu_show_start); +#endif + } - for (; i < text_rows; ++i) { - draw_text_line(i, text[(i+text_top) % text_rows]); + gr_color(NORMAL_TEXT_COLOR); + int cur_row = text_row; + int available_rows = total_rows - row - 1; + int start_row = row + 1; + if (available_rows < MAX_ROWS) + cur_row = (cur_row + (MAX_ROWS - available_rows)) % MAX_ROWS; + else + start_row = total_rows - MAX_ROWS; + + int r; + for (r = 0; r < (available_rows < MAX_ROWS ? available_rows : MAX_ROWS); r++) { + draw_text_line(start_row + r, text[(cur_row + r) % MAX_ROWS]); } } } // Redraw everything on the screen and flip the screen (make it visible). // Should only be called with gUpdateMutex locked. -static void update_screen_locked(void) -{ +static void update_screen_locked(void) { + if (!ui_has_initialized) + return; + draw_screen_locked(); gr_flip(); } // Updates only the progress bar, if possible, otherwise redraws the screen. // Should only be called with gUpdateMutex locked. -static void update_progress_locked(void) -{ +static void update_progress_locked(void) { + if (!ui_has_initialized) + return; + + // set minimum delay between progress updates if we have a text overlay + // exception: gProgressScopeDuration != 0: to keep zip installer refresh behavior + struct timeval curtime; + gettimeofday(&curtime, NULL); + long delta_ms = delta_milliseconds(lastprogupd, curtime); + if (show_text && gProgressScopeDuration == 0 && lastprogupd.tv_sec > 0 + && delta_ms < UI_MIN_PROG_DELTA_MS) { + return; + } + if (show_text || !gPagesIdentical) { draw_screen_locked(); // Must redraw the whole screen gPagesIdentical = 1; @@ -246,8 +388,7 @@ static void update_progress_locked(void) } // Keeps the progress bar updated, even when the process is otherwise busy. -static void *progress_thread(void *cookie) -{ +static void *progress_thread(void *cookie) { double interval = 1.0 / ui_parameters.update_fps; for (;;) { double start = now(); @@ -255,31 +396,24 @@ static void *progress_thread(void *cookie) int redraw = 0; - // update the installation animation, if active - // skip this if we have a text overlay (too expensive to update) - if (gCurrentIcon == BACKGROUND_ICON_INSTALLING && - ui_parameters.installing_frames > 0 && - !show_text) { - gInstallingFrame = - (gInstallingFrame + 1) % ui_parameters.installing_frames; - redraw = 1; - } - // update the progress bar animation, if active - // skip this if we have a text overlay (too expensive to update) - if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) { + // update the spinning cube animation, even if no progress bar + if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE || + gCurrentIcon == BACKGROUND_ICON_INSTALLING) { redraw = 1; } // move the progress bar forward on timed intervals, if configured int duration = gProgressScopeDuration; - if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) { - double elapsed = now() - gProgressScopeTime; - float progress = 1.0 * elapsed / duration; - if (progress > 1.0) progress = 1.0; - if (progress > gProgress) { - gProgress = progress; - redraw = 1; + if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) { + if (duration > 0) { + double elapsed = now() - gProgressScopeTime; + float progress = 1.0 * elapsed / duration; + if (progress > 1.0) progress = 1.0; + if (progress > gProgress) { + gProgress = progress; + redraw = 1; + } } } @@ -296,9 +430,7 @@ static void *progress_thread(void *cookie) } static int rel_sum = 0; - -static int input_callback(int fd, short revents, void *data) -{ +static int input_callback(int fd, short revents, void *data) { struct input_event ev; int ret; int fake_key = 0; @@ -307,6 +439,13 @@ static int input_callback(int fd, short revents, void *data) if (ret) return -1; +#ifdef BOARD_TOUCH_RECOVERY + if (touch_handle_input(fd, ev)) + return 0; +#elif defined(BOARD_RECOVERY_SWIPE) + touch_handle_input(fd, &ev); +#endif + if (ev.type == EV_SYN) { return 0; } else if (ev.type == EV_REL) { @@ -337,6 +476,10 @@ static int input_callback(int fd, short revents, void *data) if (ev.type != EV_KEY || ev.code > KEY_MAX) return 0; + if (ev.value == 2) { + boardEnableKeyRepeat = 0; + } + pthread_mutex_lock(&key_queue_mutex); if (!fake_key) { // our "fake" keys only report a key-down event (no @@ -347,28 +490,28 @@ static int input_callback(int fd, short revents, void *data) const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); if (ev.value > 0 && key_queue_len < queue_max) { key_queue[key_queue_len++] = ev.code; + + if (boardEnableKeyRepeat) { + struct timeval now; + gettimeofday(&now, NULL); + + key_press_time[ev.code] = (now.tv_sec * 1000) + (now.tv_usec / 1000); + key_last_repeat[ev.code] = 0; + } + pthread_cond_signal(&key_queue_cond); } pthread_mutex_unlock(&key_queue_mutex); - if (ev.value > 0 && device_toggle_display(key_pressed, ev.code)) { - pthread_mutex_lock(&gUpdateMutex); - show_text = !show_text; - if (show_text) show_text_ever = 1; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); - } - if (ev.value > 0 && device_reboot_now(key_pressed, ev.code)) { - android_reboot(ANDROID_RB_RESTART, 0, 0); + reboot_main_system(ANDROID_RB_RESTART, 0, 0); } return 0; } // Reads input events, handles special hot keys, and adds to the key queue. -static void *input_thread(void *cookie) -{ +static void *input_thread(void *cookie) { for (;;) { if (!ev_wait(-1)) ev_dispatch(); @@ -376,13 +519,22 @@ static void *input_thread(void *cookie) return NULL; } -void ui_init(void) -{ +void ui_init(void) { + ui_has_initialized = 1; gr_init(); ev_init(input_callback, NULL); +#ifdef BOARD_TOUCH_RECOVERY + touch_init(); +#endif text_col = text_row = 0; text_rows = gr_fb_height() / CHAR_HEIGHT; + max_menu_rows = text_rows - MIN_LOG_ROWS; +#ifdef BOARD_TOUCH_RECOVERY + max_menu_rows = get_max_menu_rows(max_menu_rows); +#endif + if (max_menu_rows > MENU_MAX_ROWS) + max_menu_rows = MENU_MAX_ROWS; if (text_rows > MAX_ROWS) text_rows = MAX_ROWS; text_top = 1; @@ -436,21 +588,60 @@ void ui_init(void) gInstallationOverlay = NULL; } + char enable_key_repeat[PROPERTY_VALUE_MAX]; + property_get("ro.cwm.enable_key_repeat", enable_key_repeat, ""); + if (!strcmp(enable_key_repeat, "true") || !strcmp(enable_key_repeat, "1")) { + boardEnableKeyRepeat = 1; + + char key_list[PROPERTY_VALUE_MAX]; + property_get("ro.cwm.repeatable_keys", key_list, ""); + if (strlen(key_list) == 0) { + boardRepeatableKeys[boardNumRepeatableKeys++] = KEY_UP; + boardRepeatableKeys[boardNumRepeatableKeys++] = KEY_DOWN; + boardRepeatableKeys[boardNumRepeatableKeys++] = KEY_VOLUMEUP; + boardRepeatableKeys[boardNumRepeatableKeys++] = KEY_VOLUMEDOWN; + } else { + char *pch = strtok(key_list, ","); + while (pch != NULL) { + boardRepeatableKeys[boardNumRepeatableKeys++] = atoi(pch); + pch = strtok(NULL, ","); + } + } + } + pthread_t t; pthread_create(&t, NULL, progress_thread, NULL); pthread_create(&t, NULL, input_thread, NULL); } -void ui_set_background(int icon) -{ +char *ui_copy_image(int icon, int *width, int *height, int *bpp) { + pthread_mutex_lock(&gUpdateMutex); + draw_background_locked(icon); + *width = gr_fb_width(); + *height = gr_fb_height(); + *bpp = sizeof(gr_pixel) * 8; + int size = *width * *height * sizeof(gr_pixel); + char *ret = malloc(size); + if (ret == NULL) { + LOGE("Can't allocate %d bytes for image\n", size); + } else { + memcpy(ret, gr_fb_data(), size); + } + pthread_mutex_unlock(&gUpdateMutex); + return ret; +} + +void ui_set_background(int icon) { pthread_mutex_lock(&gUpdateMutex); gCurrentIcon = icon; update_screen_locked(); pthread_mutex_unlock(&gUpdateMutex); } -void ui_show_indeterminate_progress() -{ +void ui_show_indeterminate_progress() { + if (!ui_has_initialized) + return; + pthread_mutex_lock(&gUpdateMutex); if (gProgressBarType != PROGRESSBAR_TYPE_INDETERMINATE) { gProgressBarType = PROGRESSBAR_TYPE_INDETERMINATE; @@ -459,8 +650,10 @@ void ui_show_indeterminate_progress() pthread_mutex_unlock(&gUpdateMutex); } -void ui_show_progress(float portion, int seconds) -{ +void ui_show_progress(float portion, int seconds) { + if (!ui_has_initialized) + return; + pthread_mutex_lock(&gUpdateMutex); gProgressBarType = PROGRESSBAR_TYPE_NORMAL; gProgressScopeStart += gProgressScopeSize; @@ -472,8 +665,10 @@ void ui_show_progress(float portion, int seconds) pthread_mutex_unlock(&gUpdateMutex); } -void ui_set_progress(float fraction) -{ +void ui_set_progress(float fraction) { + if (!ui_has_initialized) + return; + pthread_mutex_lock(&gUpdateMutex); if (fraction < 0.0) fraction = 0.0; if (fraction > 1.0) fraction = 1.0; @@ -489,26 +684,34 @@ void ui_set_progress(float fraction) pthread_mutex_unlock(&gUpdateMutex); } -void ui_reset_progress() -{ +void ui_reset_progress() { + if (!ui_has_initialized) + return; + pthread_mutex_lock(&gUpdateMutex); gProgressBarType = PROGRESSBAR_TYPE_NONE; - gProgressScopeStart = gProgressScopeSize = 0; - gProgressScopeTime = gProgressScopeDuration = 0; + gProgressScopeStart = 0; + gProgressScopeSize = 0; + gProgressScopeTime = 0; + gProgressScopeDuration = 0; gProgress = 0; update_screen_locked(); pthread_mutex_unlock(&gUpdateMutex); } -void ui_print(const char *fmt, ...) -{ +int ui_get_text_cols() { + return text_cols; +} + +void ui_print(const char *fmt, ...) { char buf[256]; va_list ap; va_start(ap, fmt); vsnprintf(buf, 256, fmt, ap); va_end(ap); - fputs(buf, stdout); + if (ui_log_stdout) + fputs(buf, stdout); // This can get called before ui_init(), so be careful. pthread_mutex_lock(&gUpdateMutex); @@ -529,7 +732,30 @@ void ui_print(const char *fmt, ...) pthread_mutex_unlock(&gUpdateMutex); } -void ui_start_menu(char** headers, char** items, int initial_selection) { +void ui_printlogtail(int nb_lines) { + char * log_data; + char tmp[PATH_MAX]; + FILE * f; + int line=0; + //don't log output to recovery.log + ui_log_stdout=0; + sprintf(tmp, "tail -n %d /tmp/recovery.log > /tmp/tail.log", nb_lines); + __system(tmp); + f = fopen("/tmp/tail.log", "rb"); + if (f != NULL) { + while (line < nb_lines) { + log_data = fgets(tmp, PATH_MAX, f); + if (log_data == NULL) break; + ui_print("%s", tmp); + line++; + } + fclose(f); + } + ui_print("Return to menu with any key.\n"); + ui_log_stdout=1; +} + +int ui_start_menu(const char** headers, char** items, int initial_selection) { int i; pthread_mutex_lock(&gUpdateMutex); if (text_rows > 0 && text_cols > 0) { @@ -539,17 +765,28 @@ void ui_start_menu(char** headers, char** items, int initial_selection) { menu[i][text_cols-1] = '\0'; } menu_top = i; - for (; i < text_rows; ++i) { + for (; i < MENU_MAX_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'; + strcpy(menu[i], MENU_ITEM_HEADER); + strncpy(menu[i] + MENU_ITEM_HEADER_LENGTH, items[i-menu_top], MENU_MAX_COLS - 1 - MENU_ITEM_HEADER_LENGTH); + menu[i][MENU_MAX_COLS-1] = '\0'; } + + if (gShowBackButton && !ui_root_menu) { + strcpy(menu[i], " - +++++Go Back+++++"); + ++i; + } + menu_items = i - menu_top; show_menu = 1; - menu_sel = initial_selection; + menu_sel = menu_show_start = initial_selection; update_screen_locked(); } pthread_mutex_unlock(&gUpdateMutex); + if (gShowBackButton && !ui_root_menu) { + return menu_items - 1; + } + return menu_items; } int ui_menu_select(int sel) { @@ -558,9 +795,21 @@ int ui_menu_select(int sel) { if (show_menu > 0) { old_sel = menu_sel; menu_sel = sel; - if (menu_sel < 0) menu_sel = 0; - if (menu_sel >= menu_items) menu_sel = menu_items-1; + + if (menu_sel < 0) menu_sel = menu_items + menu_sel; + if (menu_sel >= menu_items) 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 + menu_top >= max_menu_rows) { + menu_show_start = menu_sel + menu_top - max_menu_rows + 1; + } + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); } pthread_mutex_unlock(&gUpdateMutex); @@ -577,24 +826,21 @@ void ui_end_menu() { pthread_mutex_unlock(&gUpdateMutex); } -int ui_text_visible() -{ +int ui_text_visible() { pthread_mutex_lock(&gUpdateMutex); int visible = show_text; pthread_mutex_unlock(&gUpdateMutex); return visible; } -int ui_text_ever_visible() -{ +int ui_text_ever_visible() { pthread_mutex_lock(&gUpdateMutex); int ever_visible = show_text_ever; pthread_mutex_unlock(&gUpdateMutex); return ever_visible; } -void ui_show_text(int visible) -{ +void ui_show_text(int visible) { pthread_mutex_lock(&gUpdateMutex); show_text = visible; if (show_text) show_text_ever = 1; @@ -602,7 +848,6 @@ void ui_show_text(int visible) pthread_mutex_unlock(&gUpdateMutex); } -// Return true if USB is connected. static int usb_connected() { int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); if (fd < 0) { @@ -621,26 +866,42 @@ static int usb_connected() { return connected; } -int ui_wait_key() -{ +void ui_cancel_wait_key() { + 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 ui_wait_key() { + if (boardEnableKeyRepeat) + return ui_wait_key_with_repeat(); + 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. + // Time out after REFRESH_TIME_USB_INTERVAL seconds to catch volume changes, and loop for + // UI_WAIT_KEY_TIMEOUT_SEC to restart a device not connected to USB do { struct timeval now; struct timespec timeout; 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 += REFRESH_TIME_USB_INTERVAL; int rc = 0; while (key_queue_len == 0 && rc != ETIMEDOUT) { rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + if (volumes_changed()) { + pthread_mutex_unlock(&key_queue_mutex); + return REFRESH; + } } - } while (usb_connected() && key_queue_len == 0); + timeouts -= REFRESH_TIME_USB_INTERVAL; + } while ((timeouts > 0 || usb_connected()) && key_queue_len == 0); int key = -1; if (key_queue_len > 0) { @@ -651,8 +912,109 @@ int ui_wait_key() return key; } -int ui_key_pressed(int key) -{ +static int key_can_repeat(int key) { + int k = 0; + for (;k < boardNumRepeatableKeys; ++k) { + if (boardRepeatableKeys[k] == key) { + break; + } + } + if (k < boardNumRepeatableKeys) return 1; + return 0; +} + +static int ui_wait_key_with_repeat() { + int key = -1; + + // Loop to wait for more keys + do { + int timeouts = UI_WAIT_KEY_TIMEOUT_SEC; + int rc = 0; + struct timeval now; + struct timespec timeout; + pthread_mutex_lock(&key_queue_mutex); + while (key_queue_len == 0 && timeouts > 0) { + gettimeofday(&now, NULL); + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = now.tv_usec * 1000; + timeout.tv_sec += REFRESH_TIME_USB_INTERVAL; + + rc = 0; + while (key_queue_len == 0 && rc != ETIMEDOUT) { + rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, + &timeout); + if (volumes_changed()) { + pthread_mutex_unlock(&key_queue_mutex); + return REFRESH; + } + } + timeouts -= REFRESH_TIME_USB_INTERVAL; + } + pthread_mutex_unlock(&key_queue_mutex); + + if (rc == ETIMEDOUT && !usb_connected()) { + return -1; + } + + // Loop to wait wait for more keys, or repeated keys to be ready. + while (1) { + unsigned long now_msec; + + gettimeofday(&now, NULL); + now_msec = (now.tv_sec * 1000) + (now.tv_usec / 1000); + + pthread_mutex_lock(&key_queue_mutex); + + // Replacement for the while conditional, so we don't have to lock the entire + // loop, because that prevents the input system from touching the variables while + // the loop is running which causes problems. + if (key_queue_len == 0) { + pthread_mutex_unlock(&key_queue_mutex); + break; + } + + key = key_queue[0]; + memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + + // sanity check the returned key. + if (key < 0) { + pthread_mutex_unlock(&key_queue_mutex); + return key; + } + + // Check for already released keys and drop them if they've repeated. + if (!key_pressed[key] && key_last_repeat[key] > 0) { + pthread_mutex_unlock(&key_queue_mutex); + continue; + } + + if (key_can_repeat(key)) { + // Re-add the key if a repeat is expected, since we just popped it. The + // if below will determine when the key is actually repeated (returned) + // in the mean time, the key will be passed through the queue over and + // over and re-evaluated each time. + if (key_pressed[key]) { + key_queue[key_queue_len] = key; + key_queue_len++; + } + if ((now_msec > key_press_time[key] + UI_KEY_WAIT_REPEAT && now_msec > key_last_repeat[key] + UI_KEY_REPEAT_INTERVAL) || + key_last_repeat[key] == 0) { + key_last_repeat[key] = now_msec; + } else { + // Not ready + pthread_mutex_unlock(&key_queue_mutex); + continue; + } + } + pthread_mutex_unlock(&key_queue_mutex); + return key; + } + } while (1); + + return key; +} + +int ui_key_pressed(int key) { // This is a volatile static array, don't bother locking return key_pressed[key]; } @@ -662,3 +1024,72 @@ void ui_clear_key_queue() { key_queue_len = 0; pthread_mutex_unlock(&key_queue_mutex); } + +void ui_set_log_stdout(int enabled) { + ui_log_stdout = enabled; +} + +int ui_should_log_stdout() { + return ui_log_stdout; +} + +void ui_set_show_text(int value) { + show_text = value; +} + +void ui_set_showing_back_button(int showBackButton) { + gShowBackButton = showBackButton; +} + +int ui_is_showing_back_button() { + return gShowBackButton && !ui_root_menu; +} + +int ui_get_selected_item() { + return menu_sel; +} + +int ui_handle_key(int key, int visible) { +#ifdef BOARD_TOUCH_RECOVERY + return touch_handle_key(key, visible); +#else + return device_handle_key(key, visible); +#endif +} + +void ui_delete_line() { + pthread_mutex_lock(&gUpdateMutex); + text[text_row][0] = '\0'; + text_row = (text_row - 1 + text_rows) % text_rows; + text_col = 0; + pthread_mutex_unlock(&gUpdateMutex); +} + +static void ui_rainbow_mode() { + static int 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 + + gr_color(colors[cur_rainbow_color], colors[cur_rainbow_color+1], colors[cur_rainbow_color+2], 255); + cur_rainbow_color += 3; + if (cur_rainbow_color >= (sizeof(colors) / sizeof(colors[0]))) cur_rainbow_color = 0; +} + +int ui_get_rainbow_mode() { + return gRainbowMode; +} + +void ui_set_rainbow_mode(int rainbowMode) { + gRainbowMode = rainbowMode; + + pthread_mutex_lock(&gUpdateMutex); + update_screen_locked(); + pthread_mutex_unlock(&gUpdateMutex); +} + +int is_ui_initialized() { + return ui_has_initialized; +} diff --git a/updater/Android.mk b/updater/Android.mk index 8d731db3e..51a7a0591 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -3,6 +3,7 @@ LOCAL_PATH := $(call my-dir) updater_src_files := \ + ../mounts.c \ install.c \ updater.c @@ -21,14 +22,19 @@ LOCAL_SRC_FILES := $(updater_src_files) ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) LOCAL_CFLAGS += -DUSE_EXT4 LOCAL_C_INCLUDES += system/extras/ext4_utils -LOCAL_STATIC_LIBRARIES += libext4_utils libz +LOCAL_STATIC_LIBRARIES += \ + libext4_utils_static \ + libsparse_static \ + libz endif +LOCAL_STATIC_LIBRARIES += libflashutils libmtdutils libmmcutils libbmlutils 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 += libminelf LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc +LOCAL_STATIC_LIBRARIES += libselinux LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. # Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function @@ -44,18 +50,19 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc -# During the first pass of reading the makefiles, we dump the list of -# extension libs to a temp file, then copy that to the ".list" file if -# it is different than the existing .list (if any). The register.inc -# file then uses the .list as a prerequisite, so it is only rebuilt -# (and updater.o recompiled) when the list of extension libs changes. - -junk := $(shell mkdir -p $(dir $(inc));\ - echo $(TARGET_RECOVERY_UPDATER_LIBS) > $(inc).temp;\ - diff -q $(inc).temp $(inc).list 2>/dev/null || cp -f $(inc).temp $(inc).list) +# 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, +# so no obsolete dependecy file gets used if you switch back to an old value. +inc_dep_file := $(inc).dep.$(subst $(space),-,$(sort $(TARGET_RECOVERY_UPDATER_LIBS))) +$(inc_dep_file): stem := $(inc).dep +$(inc_dep_file) : + $(hide) mkdir -p $(dir $@) + $(hide) rm -f $(stem).* + $(hide) touch $@ $(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS) -$(inc) : $(inc).list +$(inc) : $(inc_dep_file) $(hide) mkdir -p $(dir $@) $(hide) echo "" > $@ $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;) @@ -66,6 +73,9 @@ $(inc) : $(inc).list $(call intermediates-dir-for,EXECUTABLES,updater)/updater.o : $(inc) LOCAL_C_INCLUDES += $(dir $(inc)) +inc := +inc_dep_file := + LOCAL_MODULE := updater LOCAL_FORCE_STATIC_EXECUTABLE := true diff --git a/updater/install.c b/updater/install.c index 0396bae6c..7ba8c756b 100644 --- a/updater/install.c +++ b/updater/install.c @@ -27,18 +27,28 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include "cutils/misc.h" #include "cutils/properties.h" #include "edify/expr.h" #include "mincrypt/sha.h" #include "minzip/DirUtil.h" -#include "minelf/Retouch.h" -#include "mtdutils/mounts.h" +#include "mounts.h" #include "mtdutils/mtdutils.h" #include "updater.h" #include "applypatch/applypatch.h" +#include + +static char bakfiles[PATH_MAX][512]; +static int totalbaks = 0; + #ifdef USE_EXT4 #include "make_ext4fs.h" #endif @@ -79,8 +89,20 @@ Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { goto done; } + char *secontext = NULL; + + if (sehandle) { + selabel_lookup(sehandle, &secontext, mount_point, 0755); + setfscreatecon(secontext); + } + mkdir(mount_point, 0755); + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } + if (strcmp(partition_type, "MTD") == 0) { mtd_scan_partitions(); const MtdPartition* mtd; @@ -177,23 +199,25 @@ Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { } -// format(fs_type, partition_type, location, fs_size) +// format(fs_type, partition_type, location, fs_size, mount_point) // -// fs_type="yaffs2" partition_type="MTD" location=partition fs_size= -// fs_type="ext4" partition_type="EMMC" location=device fs_size= +// fs_type="yaffs2" partition_type="MTD" location=partition fs_size= mount_point= +// fs_type="ext4" partition_type="EMMC" location=device fs_size= mount_point= // if fs_size == 0, then make_ext4fs 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 Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; - if (argc != 4) { - return ErrorAbort(state, "%s() expects 4 args, got %d", name, argc); + if (argc != 5) { + return ErrorAbort(state, "%s() expects 5 args, got %d", name, argc); } char* fs_type; char* partition_type; char* location; char* fs_size; - if (ReadArgs(state, argv, 4, &fs_type, &partition_type, &location, &fs_size) < 0) { + char* mount_point; + + if (ReadArgs(state, argv, 5, &fs_type, &partition_type, &location, &fs_size, &mount_point) < 0) { return NULL; } @@ -211,6 +235,11 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { goto done; } + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to %s() can't be empty", name); + goto done; + } + if (strcmp(partition_type, "MTD") == 0) { mtd_scan_partitions(); const MtdPartition* mtd = mtd_find_partition_by_name(location); @@ -240,7 +269,7 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { result = location; #ifdef USE_EXT4 } else if (strcmp(fs_type, "ext4") == 0) { - int status = make_ext4fs(location, atoll(fs_size)); + int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle); if (status != 0) { fprintf(stderr, "%s: make_ext4fs failed (%d) on %s", name, status, location); @@ -261,10 +290,45 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(result); } +Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* src_name; + char* dst_name; + + if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) { + return NULL; + } + if (strlen(src_name) == 0) { + ErrorAbort(state, "src_name argument to %s() can't be empty", name); + goto done; + } + if (strlen(dst_name) == 0) { + ErrorAbort(state, "dst_name argument to %s() can't be empty", + name); + goto done; + } + + if (rename(src_name, dst_name) != 0) { + ErrorAbort(state, "Rename of %s() to %s() failed, error %s()", + src_name, dst_name, strerror(errno)); + } else { + result = dst_name; + } + +done: + free(src_name); + if (result != dst_name) free(dst_name); + return StringValue(result); +} 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) { @@ -347,7 +411,7 @@ Value* PackageExtractDirFn(const char* name, State* state, bool success = mzExtractRecursive(za, zip_path, dest_path, MZ_EXTRACT_FILES_ONLY, ×tamp, - NULL, NULL); + NULL, NULL, sehandle); free(zip_path); free(dest_path); return StringValue(strdup(success ? "t" : "")); @@ -434,122 +498,27 @@ Value* PackageExtractFileFn(const char* name, State* state, } } - -// retouch_binaries(lib1, lib2, ...) -Value* RetouchBinariesFn(const char* name, State* state, - int argc, Expr* argv[]) { - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - - char **retouch_entries = ReadVarArgs(state, argc, argv); - if (retouch_entries == NULL) { - return StringValue(strdup("t")); - } - - // some randomness from the clock - int32_t override_base; - bool override_set = false; - int32_t random_base = time(NULL) % 1024; - // some more randomness from /dev/random - FILE *f_random = fopen("/dev/random", "rb"); - uint16_t random_bits = 0; - if (f_random != NULL) { - fread(&random_bits, 2, 1, f_random); - random_bits = random_bits % 1024; - fclose(f_random); - } - random_base = (random_base + random_bits) % 1024; - fprintf(ui->cmd_pipe, "ui_print Random offset: 0x%x\n", random_base); - fprintf(ui->cmd_pipe, "ui_print\n"); - - // make sure we never randomize to zero; this let's us look at a file - // and know for sure whether it has been processed; important in the - // crash recovery process - if (random_base == 0) random_base = 1; - // make sure our randomization is page-aligned - random_base *= -0x1000; - override_base = random_base; - - int i = 0; - bool success = true; - while (i < (argc - 1)) { - success = success && retouch_one_library(retouch_entries[i], - retouch_entries[i+1], - random_base, - override_set ? - NULL : - &override_base); - if (!success) - ErrorAbort(state, "Failed to retouch '%s'.", retouch_entries[i]); - - free(retouch_entries[i]); - free(retouch_entries[i+1]); - i += 2; - - if (success && override_base != 0) { - random_base = override_base; - override_set = true; +// Create all parent directories of name, if necessary. +static int make_parents(char* name) { + char* p; + for (p = name + (strlen(name)-1); p > name; --p) { + if (*p != '/') continue; + *p = '\0'; + if (make_parents(name) < 0) return -1; + int result = mkdir(name, 0700); + if (result == 0) fprintf(stderr, "symlink(): created [%s]\n", name); + *p = '/'; + if (result == 0 || errno == EEXIST) { + // successfully created or already existed; we're done + return 0; + } else { + fprintf(stderr, "failed to mkdir %s: %s\n", name, strerror(errno)); + return -1; } } - if (i < argc) { - free(retouch_entries[i]); - success = false; - } - free(retouch_entries); - - if (!success) { - Value* v = malloc(sizeof(Value)); - v->type = VAL_STRING; - v->data = NULL; - v->size = -1; - return v; - } - return StringValue(strdup("t")); -} - - -// undo_retouch_binaries(lib1, lib2, ...) -Value* UndoRetouchBinariesFn(const char* name, State* state, - int argc, Expr* argv[]) { - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - - char **retouch_entries = ReadVarArgs(state, argc, argv); - if (retouch_entries == NULL) { - return StringValue(strdup("t")); - } - - int i = 0; - bool success = true; - int32_t override_base; - while (i < (argc-1)) { - success = success && retouch_one_library(retouch_entries[i], - retouch_entries[i+1], - 0 /* undo => offset==0 */, - NULL); - if (!success) - ErrorAbort(state, "Failed to unretouch '%s'.", - retouch_entries[i]); - - free(retouch_entries[i]); - free(retouch_entries[i+1]); - i += 2; - } - if (i < argc) { - free(retouch_entries[i]); - success = false; - } - free(retouch_entries); - - if (!success) { - Value* v = malloc(sizeof(Value)); - v->type = VAL_STRING; - v->data = NULL; - v->size = -1; - return v; - } - return StringValue(strdup("t")); + return 0; } - // symlink target src1 src2 ... // unlinks any previously existing src1, src2, etc before creating symlinks. Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { @@ -566,21 +535,32 @@ Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { return NULL; } + int bad = 0; int i; for (i = 0; i < argc-1; ++i) { if (unlink(srcs[i]) < 0) { if (errno != ENOENT) { fprintf(stderr, "%s: failed to remove %s: %s\n", name, srcs[i], strerror(errno)); + ++bad; } } + if (make_parents(srcs[i])) { + fprintf(stderr, "%s: failed to symlink %s to %s: making parents failed\n", + name, srcs[i], target); + ++bad; + } if (symlink(target, srcs[i]) < 0) { fprintf(stderr, "%s: failed to symlink %s to %s: %s\n", name, srcs[i], target, strerror(errno)); + ++bad; } free(srcs[i]); } free(srcs); + if (bad) { + return ErrorAbort(state, "%s: some symlinks failed", name); + } return StringValue(strdup("")); } @@ -591,7 +571,8 @@ Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { int min_args = 4 + (recursive ? 1 : 0); if (argc < min_args) { - return ErrorAbort(state, "%s() expects %d+ args, got %d", name, argc); + return ErrorAbort(state, "%s() expects %d+ args, got %d", + name, min_args, argc); } char** args = ReadVarArgs(state, argc, argv); @@ -599,6 +580,7 @@ Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { char* end; int i; + int bad = 0; int uid = strtoul(args[0], &end, 0); if (*end != '\0' || args[0][0] == 0) { @@ -640,10 +622,12 @@ Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { if (chown(args[i], uid, gid) < 0) { fprintf(stderr, "%s: chown of %s to %d %d failed: %s\n", name, args[i], uid, gid, strerror(errno)); + ++bad; } if (chmod(args[i], mode) < 0) { fprintf(stderr, "%s: chmod of %s to %o failed: %s\n", name, args[i], mode, strerror(errno)); + ++bad; } } } @@ -655,9 +639,279 @@ Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { } free(args); + if (bad) { + free(result); + return ErrorAbort(state, "%s: some changes failed", name); + } return StringValue(result); } +struct perm_parsed_args { + bool has_uid; + uid_t uid; + bool has_gid; + gid_t gid; + bool has_mode; + mode_t mode; + bool has_fmode; + mode_t fmode; + bool has_dmode; + mode_t dmode; + bool has_selabel; + char* selabel; + bool has_capabilities; + uint64_t capabilities; +}; + +static struct perm_parsed_args ParsePermArgs(int argc, char** args) { + int i; + struct perm_parsed_args parsed; + int bad = 0; + static int max_warnings = 20; + + memset(&parsed, 0, sizeof(parsed)); + + for (i = 1; i < argc; i += 2) { + if (strcmp("uid", args[i]) == 0) { + int64_t uid; + if (sscanf(args[i+1], "%" SCNd64, &uid) == 1) { + parsed.uid = uid; + parsed.has_uid = true; + } else { + printf("ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("gid", args[i]) == 0) { + int64_t gid; + if (sscanf(args[i+1], "%" SCNd64, &gid) == 1) { + parsed.gid = gid; + parsed.has_gid = true; + } else { + printf("ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("mode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.mode = mode; + parsed.has_mode = true; + } else { + printf("ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("dmode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.dmode = mode; + parsed.has_dmode = true; + } else { + printf("ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("fmode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.fmode = mode; + parsed.has_fmode = true; + } else { + printf("ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("capabilities", args[i]) == 0) { + int64_t capabilities; + if (sscanf(args[i+1], "%" SCNi64, &capabilities) == 1) { + parsed.capabilities = capabilities; + parsed.has_capabilities = true; + } else { + printf("ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("selabel", args[i]) == 0) { + if (args[i+1][0] != '\0') { + parsed.selabel = args[i+1]; + parsed.has_selabel = true; + } else { + printf("ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (max_warnings != 0) { + printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i]); + max_warnings--; + if (max_warnings == 0) { + printf("ParsedPermArgs: suppressing further warnings\n"); + } + } + } + return parsed; +} + +static int ApplyParsedPerms( + const char* filename, + const struct stat *statptr, + struct perm_parsed_args parsed) +{ + int bad = 0; + + /* ignore symlinks */ + if (S_ISLNK(statptr->st_mode)) { + return 0; + } + + if (parsed.has_uid) { + if (chown(filename, parsed.uid, -1) < 0) { + printf("ApplyParsedPerms: chown of %s to %d failed: %s\n", + filename, parsed.uid, strerror(errno)); + bad++; + } + } + + if (parsed.has_gid) { + if (chown(filename, -1, parsed.gid) < 0) { + printf("ApplyParsedPerms: chgrp of %s to %d failed: %s\n", + filename, parsed.gid, strerror(errno)); + bad++; + } + } + + if (parsed.has_mode) { + if (chmod(filename, parsed.mode) < 0) { + printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.mode, strerror(errno)); + bad++; + } + } + + if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) { + if (chmod(filename, parsed.dmode) < 0) { + printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.dmode, strerror(errno)); + bad++; + } + } + + if (parsed.has_fmode && S_ISREG(statptr->st_mode)) { + if (chmod(filename, parsed.fmode) < 0) { + printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.fmode, strerror(errno)); + bad++; + } + } + + if (parsed.has_selabel) { + // TODO: Don't silently ignore ENOTSUP + if (lsetfilecon(filename, parsed.selabel) && (errno != ENOTSUP)) { + printf("ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", + filename, parsed.selabel, strerror(errno)); + bad++; + } + } + + if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) { + if (parsed.capabilities == 0) { + if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && ((errno != ENODATA) +#ifdef RECOVERY_CANT_USE_CONFIG_EXT4_FS_XATTR + && (errno != EOPNOTSUPP) +#endif + )) { + // Report failure unless it's ENODATA (attribute not set) + printf("ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno)); + bad++; + } + } else { + struct vfs_cap_data cap_data; + memset(&cap_data, 0, sizeof(cap_data)); + cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE; + cap_data.data[0].permitted = (uint32_t) (parsed.capabilities & 0xffffffff); + cap_data.data[0].inheritable = 0; + cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32); + cap_data.data[1].inheritable = 0; + if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0 +#ifdef RECOVERY_CANT_USE_CONFIG_EXT4_FS_XATTR + && (errno != EOPNOTSUPP) +#endif + ) { + printf("ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno)); + bad++; + } + } + } + + return bad; +} + +// nftw doesn't allow us to pass along context, so we need to use +// global variables. *sigh* +static struct perm_parsed_args recursive_parsed_args; + +static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr, + int fileflags, struct FTW *pfwt) { + return ApplyParsedPerms(filename, statptr, recursive_parsed_args); +} + +static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) { + int i; + int bad = 0; + static int nwarnings = 0; + struct stat sb; + Value* result = NULL; + + bool recursive = (strcmp(name, "set_metadata_recursive") == 0); + + if ((argc % 2) != 1) { + return ErrorAbort(state, "%s() expects an odd number of arguments, got %d", + name, argc); + } + + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) return NULL; + + if (lstat(args[0], &sb) == -1) { + result = ErrorAbort(state, "%s: Error on lstat of \"%s\": %s", name, args[0], strerror(errno)); + goto done; + } + + struct perm_parsed_args parsed = ParsePermArgs(argc, args); + + if (recursive) { + recursive_parsed_args = parsed; + bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS); + memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args)); + } else { + bad += ApplyParsedPerms(args[0], &sb, parsed); + } + +done: + for (i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + + if (result != NULL) { + return result; + } + + if (bad > 0) { + return ErrorAbort(state, "%s: some changes failed", name); + } + + return StringValue(strdup("")); +} Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 1) { @@ -706,7 +960,7 @@ Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { buffer = malloc(st.st_size+1); if (buffer == NULL) { - ErrorAbort(state, "%s: failed to alloc %d bytes", name, st.st_size+1); + ErrorAbort(state, "%s: failed to alloc %lld bytes", name, st.st_size+1); goto done; } @@ -718,7 +972,7 @@ Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { } if (fread(buffer, 1, st.st_size, f) != st.st_size) { - ErrorAbort(state, "%s: failed to read %d bytes from %s", + ErrorAbort(state, "%s: failed to read %lld bytes from %s", name, st.st_size+1, filename); fclose(f); goto done; @@ -806,66 +1060,14 @@ Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) { goto done; } - mtd_scan_partitions(); - const MtdPartition* mtd = mtd_find_partition_by_name(partition); - if (mtd == NULL) { - fprintf(stderr, "%s: no mtd partition named \"%s\"\n", name, partition); - result = strdup(""); - goto done; - } - - MtdWriteContext* ctx = mtd_write_partition(mtd); - if (ctx == NULL) { - fprintf(stderr, "%s: can't write mtd partition \"%s\"\n", - name, partition); + char* filename = contents->data; + if (0 == restore_raw_partition(NULL, partition, filename)) + result = strdup(partition); + else { result = strdup(""); goto done; } - bool success; - - if (contents->type == VAL_STRING) { - // we're given a filename as the contents - char* filename = contents->data; - FILE* f = fopen(filename, "rb"); - if (f == NULL) { - fprintf(stderr, "%s: can't open %s: %s\n", - name, filename, strerror(errno)); - result = strdup(""); - goto done; - } - - success = true; - char* buffer = malloc(BUFSIZ); - int read; - while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) { - int wrote = mtd_write_data(ctx, buffer, read); - success = success && (wrote == read); - } - free(buffer); - fclose(f); - } else { - // we're given a blob as the contents - ssize_t wrote = mtd_write_data(ctx, contents->data, contents->size); - success = (wrote == contents->size); - } - if (!success) { - fprintf(stderr, "mtd_write_data to %s failed: %s\n", - partition, strerror(errno)); - } - - if (mtd_erase_blocks(ctx, -1) == -1) { - fprintf(stderr, "%s: error erasing blocks of %s\n", name, partition); - } - if (mtd_write_close(ctx) != 0) { - fprintf(stderr, "%s: error closing write of %s\n", name, partition); - } - - printf("%s %s partition\n", - success ? "wrote" : "failed to write", partition); - - result = success ? partition : strdup(""); - done: if (result != partition) FreeValue(partition_value); FreeValue(contents); @@ -910,6 +1112,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; icookie))->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) { @@ -925,7 +1141,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); @@ -954,7 +1169,7 @@ Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { int result = applypatch(source_filename, target_filename, target_sha1, target_size, - patchcount, patch_sha_str, patches); + patchcount, patch_sha_str, patches, NULL); for (i = 0; i < patchcount; ++i) { FreeValue(patches[i]); @@ -978,12 +1193,30 @@ Value* ApplyPatchCheckFn(const char* name, State* state, return NULL; } + int i=0; + /* Skip files listed in the backup table */ + for (i=0; icookie))->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]); } @@ -1024,6 +1257,70 @@ Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(buffer); } +Value* WipeCacheFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 0) { + return ErrorAbort(state, "%s() expects no args, got %d", name, argc); + } + fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "wipe_cache\n"); + 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); @@ -1106,7 +1403,7 @@ Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(strdup("")); } uint8_t digest[SHA_DIGEST_SIZE]; - SHA(args[0]->data, args[0]->size, digest); + SHA_hash(args[0]->data, args[0]->size, digest); FreeValue(args[0]); if (argc == 1) { @@ -1181,12 +1478,25 @@ void RegisterInstallFunctions() { RegisterFunction("delete_recursive", DeleteFn); RegisterFunction("package_extract_dir", PackageExtractDirFn); RegisterFunction("package_extract_file", PackageExtractFileFn); - RegisterFunction("retouch_binaries", RetouchBinariesFn); - RegisterFunction("undo_retouch_binaries", UndoRetouchBinariesFn); RegisterFunction("symlink", SymlinkFn); + + // Maybe, at some future point, we can delete these functions? They have been + // replaced by perm_set and perm_set_recursive. RegisterFunction("set_perm", SetPermFn); RegisterFunction("set_perm_recursive", SetPermFn); + // Usage: + // set_metadata("filename", "key1", "value1", "key2", "value2", ...) + // Example: + // set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); + RegisterFunction("set_metadata", SetMetadataFn); + + // Usage: + // set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...) + // Example: + // set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); + RegisterFunction("set_metadata_recursive", SetMetadataFn); + RegisterFunction("getprop", GetPropFn); RegisterFunction("file_getprop", FileGetPropFn); RegisterFunction("write_raw_image", WriteRawImageFn); @@ -1197,8 +1507,12 @@ void RegisterInstallFunctions() { RegisterFunction("read_file", ReadFileFn); RegisterFunction("sha1_check", Sha1CheckFn); + RegisterFunction("rename", RenameFn); + + RegisterFunction("wipe_cache", WipeCacheFn); RegisterFunction("ui_print", UIPrintFn); RegisterFunction("run_program", RunProgramFn); + RegisterFunction("collect_backup_data", CollectBackupDataFn); } diff --git a/updater/updater.c b/updater/updater.c index aa626d29b..bf665cab0 100644 --- a/updater/updater.c +++ b/updater/updater.c @@ -32,6 +32,8 @@ // (Note it's "updateR-script", not the older "update-script".) #define SCRIPT_NAME "META-INF/com/google/android/updater-script" +struct selabel_handle *sehandle; + int main(int argc, char** argv) { // Various things log information to stdout or stderr more or less // at random. The log file makes more sense if buffering is @@ -103,6 +105,17 @@ int main(int argc, char** argv) { return 6; } + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + + if (!sehandle) { + fprintf(stderr, "Warning: No file_contexts\n"); + // fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n"); + } + // Evaluate the parsed script. UpdaterInfo updater_info; diff --git a/updater/updater.h b/updater/updater.h index bd60dc1fd..d2e901141 100644 --- a/updater/updater.h +++ b/updater/updater.h @@ -20,10 +20,15 @@ #include #include "minzip/Zip.h" +#include +#include + typedef struct { FILE* cmd_pipe; ZipArchive* package_zip; int version; } UpdaterInfo; +extern struct selabel_handle *sehandle; + #endif diff --git a/utilities/Android.mk b/utilities/Android.mk new file mode 100755 index 000000000..49174fee8 --- /dev/null +++ b/utilities/Android.mk @@ -0,0 +1,50 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := parted +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := sdparted +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +ifneq ($(TARGET_RECOVERY_FSTAB),) + BOARD_RECOVERY_RFS_CHECK := $(shell grep rfs $(TARGET_RECOVERY_FSTAB)) +else + BOARD_RECOVERY_RFS_CHECK := $(shell grep rfs $(TARGET_DEVICE_DIR)/recovery.fstab) +endif + +ifneq ($(BOARD_RECOVERY_RFS_CHECK),) +include $(CLEAR_VARS) +LOCAL_MODULE := fat.format +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +endif + +include $(CLEAR_VARS) +LOCAL_STATIC_LIBRARIES := libz +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := libminizip +LOCAL_CFLAGS := -Dmain=minizip_main -D__ANDROID__ -DIOAPI_NO_64 +LOCAL_C_INCLUDES := external/zlib +LOCAL_SRC_FILES := ../../../external/zlib/src/contrib/minizip/minizip.c ../../../external/zlib/src/contrib/minizip/zip.c ../../../external/zlib/src/contrib/minizip/ioapi.c +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := libmake_ext4fs +LOCAL_CFLAGS := -Dmain=make_ext4fs_main +LOCAL_SRC_FILES := ../../../system/extras/ext4_utils/make_ext4fs_main.c +include $(BUILD_STATIC_LIBRARY) diff --git a/utilities/e2fsck b/utilities/e2fsck new file mode 100755 index 000000000..2844a1d93 Binary files /dev/null and b/utilities/e2fsck differ diff --git a/utilities/fat.format b/utilities/fat.format new file mode 100755 index 000000000..6a743e777 Binary files /dev/null and b/utilities/fat.format differ diff --git a/utilities/mke2fs b/utilities/mke2fs new file mode 100755 index 000000000..327015412 Binary files /dev/null and b/utilities/mke2fs differ diff --git a/utilities/parted b/utilities/parted new file mode 100755 index 000000000..bb3d432a2 Binary files /dev/null and b/utilities/parted differ diff --git a/utilities/sdparted b/utilities/sdparted new file mode 100755 index 000000000..74e24a64e --- /dev/null +++ b/utilities/sdparted @@ -0,0 +1,655 @@ +#!/sbin/sh + +# do logging, if not excluded with -x +LOGFILE="/data/sdparted.log" +[ "$1" != "-x" ] && echo "$0" "$@" >> "$LOGFILE" && "$0" -x "$@" 2>&1 | tee -a "$LOGFILE" && exit +shift + +ShowError() { echo ; echo " err: $1" ; echo ; exit 1 ; } + +ShowMessage() { echo ; echo " msg: $1" ; } + +ShowHelp() { + +cat <. + default=total sdcard size - (ext + swap) + + --extsize|-es SIZE[MG] set the size of the ext partition to . + default=$EXTSIZE + + --swapsize|-ss SIZE[MG] set the size of the swap partition to . + if set to 0, no swap partition will be created. + default=$SWAPSIZE + + --extfs|-efs TYPE set the filesystem of ext partition to . + valid types=ext2, ext3, ext4 + default=$EXTFS + + + --upgradefs|-ufs TYPE upgrades existing ext partition to . + this operation will NOT wipe your sdcard and + cannot be used with any partition creation options. + valid types=ext3, ext4 + + --downgradefs|-dfs TYPE downgrades existing ext partition to . + this operation will NOT wipe your sdcard and + cannot be used with any partition creation options. + valid types=ext2 + + + --interactive|-i interactive mode + + --help|-h display this help + + --printonly|-po display sdcard information + + --silent|-s do not prompt user, not even initial warning. + + +examples: + $SCRIPTNAME creates swap=$SWAPSIZE ext2=$EXTSIZE fat32=remaining free space + $SCRIPTNAME -efs ext4 creates swap=$SWAPSIZE ext4=$EXTSIZE fat32=remaining free space + $SCRIPTNAME -fs 1.5G -efs ext3 creates swap=$SWAPSIZE ext3=$EXTSIZE fat32=1536 + $SCRIPTNAME -es 256M -ss 0 creates no swap ext2=256 fat32=remaining free space + $SCRIPTNAME -ufs ext4 upgrades ext partition to ext4 + +DONEHELP + +} + +UserAbort() { + + WHILEEXIT= + + while [ -z "$WHILEEXIT" ] + do + echo -n "do you want to continue? (Y/n) " + read response + echo + [ "$response" = "Y" ] || [ "$response" = "n" ] || [ "$response" = "N" ] && WHILEEXIT="$response" + done + + echo "$response" > /dev/null 2>&1 >>"$LOGFILE" + + [ "$response" != "Y" ] + +} + +UnmountAll () { + + # unmount all partitions so we can work with $SDPATH + # i'm assuming no more than 3 partitions + # maybe make a little more elegant later + echo -n "unmounting all partitions..." + umount "$FATPATH" > /dev/null 2>&1 >>"$LOGFILE" + umount "$EXTPATH" > /dev/null 2>&1 >>"$LOGFILE" + umount "$SWAPPATH" > /dev/null 2>&1 >>"$LOGFILE" + echo "done" + echo + +} + + +CheckReqs() { + + echo -n "checking script requirements..." + # check for valid sdcard + [ -e $SDPATH ] || ShowError "$SDPATH does not exist!" + + # look for necessary programs + [ -e $CMPARTED ] || ShowError "$CMPARTED does not exist!" + [ -e $CMTUNE2FS ] || ShowError "$CMTUNE2FS does not exist!" + [ -e $CME2FSCK ] || ShowError "$CME2FSCK does not exist!" + + # verify cm-v1.4 + PARTEDREV=`"$CMPARTED" "$SDPATH" version | grep Parted | cut -d" " -f3` + [ "$PARTEDREV" == "1.8.8.1.179-aef3" ] || ShowError "you are not using parted v1.8.8.1.179-aef3!" + echo "done" + echo + +} + +CheckTableType() { + + TABLETYPE=`"$CMPARTED" "$SDPATH" print | grep Table: | cut -d" " -f3` + + [ "$TABLETYPE" == "loop" -o "$TABLETYPE" == "msdos" ] && TTISOK=1 || TTISOK=0 + [ "$TABLETYPE" == "loop" ] && TTISLOOP=1 || TTISLOOP=0 + [ "$TABLETYPE" == "msdos" ] && TTISMSDOS=1 || TTISMOSDOS=0 + +} + +ValidateExtArg() { + + FUNC_RET="nonzero" + + # validating argument + [ "$1" != "ext2" ] && [ "$1" != "ext3" ] && [ "$1" != "ext4" ] && FUNC_RET= + + [ -z "$FUNC_RET" ] && [ -z "$IMODE" ] && ShowError "$1 is not a valid filesystem." + [ -z "$FUNC_RET" ] && [ -n "$IMODE" ] && ShowMessage "$1 is not a valid filesystem." + + # return valid argument + [ -n "$FUNC_RET" ] && FUNC_RET="$1" + +} + +ValidateSizeArg() { + + # check for zero-length arg to protect expr length + [ -z "$1" ] && ShowError "zero-length argument passed to size-validator" + + SIZEMB= + ARGLEN=`expr length $1` + SIZELEN=$(($ARGLEN-1)) + SIZEARG=`expr substr $1 1 $SIZELEN` + SIZEUNIT=`expr substr $1 $ARGLEN 1` + + # check if SIZEARG is an integer + if [ $SIZEARG -eq $SIZEARG 2> /dev/null ] ; then + # look for G + [ "$SIZEUNIT" == "G" ] && SIZEMB=$(($SIZEARG * 1024)) + # look for M + [ "$SIZEUNIT" == "M" ] && SIZEMB=$SIZEARG + # no units on arg AND prevents using bogus size units + [ -z "$SIZEMB" ] && [ $SIZEUNIT -eq $SIZEUNIT 2> /dev/null ] && SIZEMB=$1 + # check if SIZEARG is a floating point number, GB only + elif [ `expr index "$SIZEARG" .` != 0 ] && [ "$SIZEUNIT" == "G" ] ; then + INT=`echo "$SIZEARG" | cut -d"." -f1` + FRAC=`echo "$SIZEARG" | cut -d"." -f2` + SIGDIGITS=`expr length $FRAC` + + [ -z "$INT" ] && INT=0 + INTMB=$(($INT * 1024)) + FRACMB=$((($FRAC * 1024) / (10**$SIGDIGITS))) + SIZEMB=$(($INTMB + $FRACMB)) + # it's not a valid size + else + [ -z "$IMODE" ] && ShowError "$1 is not a valid size" + fi + + [ -z "$SIZEMB" ] && [ -n "$IMODE" ] && ShowMessage "$1 is not a valid size" + + # return valid argument in MB + FUNC_RET=$SIZEMB + +} + +CalculatePartitions() { + + # get size of sdcard in MB & do some math + SDSIZEMB=`"$CMPARTED" "$SDPATH" unit MB print | grep $SDPATH | cut -d" " -f3` + SDSIZE=${SDSIZEMB%MB} + [ -n "$FATSIZE" ] || FATSIZE=$(($SDSIZE - $EXTSIZE - $SWAPSIZE)) + EXTEND=$(($FATSIZE + $EXTSIZE)) + SWAPEND=$(($EXTEND + $SWAPSIZE)) + + # check for fatsize of 0 + [ $FATSIZE -le 0 ] && ShowError "must have a fat32 partition greater than 0MB" + + # check for zero-length sdsize... + # indicative of parted not reporting length + # correctly b/c of error on sdcard + [ -z "$SDSIZE" ] && ShowError "zero-length argument passed to partition-calculator" + + # make sure we're not being asked to do the impossible + [ $(($FATSIZE + $EXTSIZE + $SWAPSIZE)) -gt $SDSIZE ] && [ -z "$IMODE" ] && ShowError "sum of requested partitions is greater than sdcard size" + +} + + +UpgradeDowngradeOnly() { + + if [ -n "$UEXTFSONLY" ] ; then + echo + [ -n "$CREATEPART" ] && ShowError "cannot use upgrade option when creating partitions, use -efs instead" + [ -n "$DEXTFSONLY" ] && ShowError "cannot upgrade AND downgrade, it just doesn't make sense" + echo "you have chosen to upgrade $EXTPATH to $UEXTFSONLY." + echo "this action will NOT delete any data from sdcard." + echo + [ -z "$SILENTRUN" ] && UserAbort && ShowError "script canceled by user" + echo + UpgradeExt "$UEXTFSONLY" + ShowCardInfo + elif [ -n "$DEXTFSONLY" ] ; then + echo + [ -n "$CREATEPART" ] && ShowError "cannot use downgrade option when creating partitions." + [ -n "$UEXTFSONLY" ] && ShowError "cannot downgrade AND upgrade, it just doesn't make sense." + echo "you have chosen to downgrade $EXTPATH to $DEXTFSONLY." + echo "this action will NOT delete any data from sdcard." + echo + [ -z "$SILENTRUN" ] && UserAbort && ShowError "script canceled by user" + echo + DowngradeExt "$DEXTFSONLY" + ShowCardInfo + fi + +} + +PrepareSdCard() { + + echo + if [ $TTISOK -eq 0 ] ; then + echo "partition 1 may not be aligned to cylinder boundaries." + echo "to continue, this must be corrected." + elif [ $TTISLOOP -gt 0 ] ; then + echo "your sdcard's partition table type is $TABLETYPE." + echo "to continue, partition table must be set to 'msdos'." + elif [ $TTISMSDOS -gt 0 ] ; then + # just a reminder..in a later version, + # i may implement resizing of partitions, + # so this will be unnecessary. but until then... + echo "to continue, all existing partitions must be removed." + else + # this is not good, and should never happen + # if it does, there is a serious problem + ShowError "sdcard failed table type check." + fi + + echo + echo "this action will remove all data from your sdcard." + echo + [ -z "$SILENTRUN" ] && UserAbort && ShowError "script canceled by user" + + [ $TTISOK -eq 0 ] && echo -n "correcting cylinder boundaries..." + [ $TTISLOOP -gt 0 ] && echo -n "setting partition table to msdos..." + [ $TTISMSDOS -gt 0 ] && echo -n "removing all partitions..." + + "$CMPARTED" -s "$SDPATH" mklabel msdos 2>&1 >>"$LOGFILE" + echo "done" + echo + +} + +ShowActions() { + + echo + echo "total size of sdcard=$SDSIZEMB" + echo + echo "the following actions will be performed:" + echo " -create $FATSIZE""MB fat32 partition" + [ $EXTSIZE -gt 0 ] && echo " -create $EXTSIZE""MB ext2 partition" + [ $SWAPSIZE -gt 0 ] && echo " -create $SWAPSIZE""MB swap partition" + [ "$EXTFS" != "ext2" ] && echo " -ext2 partition will be upgraded to $EXTFS" + echo + [ -z "$SILENTRUN" ] && UserAbort && ShowError "script canceled by user" + echo + +} + +ShowCardInfo() { + + CheckTableType + + echo + echo "retrieving current sdcard information..." + + if [ $TTISOK -gt 0 ] ; then + echo + parted "$SDPATH" print + echo + echo "script log is located @ /data/sdparted.log" + exit 0 + else + echo + echo "partition 1 may not be aligned to cylinder boundaries." + ShowError "cannot complete print operation." + fi + echo + +} + + +PartitionSdCard() { + + echo "performing selected actions..." + echo + + if [ $FATSIZE -gt 0 ] ; then + echo -n "creating fat32 partition..." + "$CMPARTED" -s "$SDPATH" mkpartfs primary fat32 0 "$FATSIZE"MB 2>&1 >>"$LOGFILE" + echo "done" + fi + + if [ $EXTSIZE -gt 0 ] ; then + echo -n "creating ext2 partition..." + "$CMPARTED" -s "$SDPATH" mkpartfs primary ext2 "$FATSIZE"MB "$EXTEND"MB 2>&1 >>"$LOGFILE" + "$CMTUNE2FS" -L sd-ext "$EXTPATH" 2>&1 >>"$LOGFILE" + echo "done" + fi + + if [ $SWAPSIZE -gt 0 ] ; then + echo -n "creating swap partition..." + "$CMPARTED" -s "$SDPATH" mkpartfs primary linux-swap "$EXTEND"MB "$SWAPEND"MB 2>&1 >>"$LOGFILE" + echo "done" + fi + echo + +} + +UpgradeExt() { + + # check for no upgrade + [ "$1" == "ext2" ] && return + # check for ext partition + [ ! -e "$EXTPATH" ] && ShowError "$EXTPATH does not exist" + + # have to use -m switch for this check b/c parted incorrectly + # reports all ext partitions as ext2 when running print + CHECKEXTFS=`"$CMPARTED" -m "$SDPATH" print | grep ext | cut -d":" -f5` + [ "$CHECKEXTFS" == "$1" ] && ShowError "$EXTPATH is already $1" + + # grabbed the code bits for ext3 from upgrade_fs(credit:cyanogen) + # check for ext2...must upgrade to ext3 first b4 ext4 + if [ "$1" == "ext3" -o "$1" == "ext4" ] ; then + echo -n "adding journaling to $EXTPATH..." + umount /system/sd > /dev/null 2>&1 >>"$LOGFILE" + "$CME2FSCK" -p "$EXTPATH" 2>&1 >>"$LOGFILE" + "$CMTUNE2FS" -c0 -i0 -j "$EXTPATH" 2>&1 >>"$LOGFILE" + echo "done" + fi + + # and got convert to ext4 from xda-forum(credit:Denkai) + if [ "$1" == "ext4" ] ; then + echo -n "converting $EXTPATH to ext4 filesystem..." + umount /system/sd > /dev/null 2>&1 >>"$LOGFILE" + "$CMTUNE2FS" -O extents,uninit_bg,dir_index "$EXTPATH" 2>&1 >>"$LOGFILE" + "$CME2FSCK" -fpDC0 "$EXTPATH" 2>&1 >>"$LOGFILE" + echo "done" + fi + echo + +} + +DowngradeExt() { + + # check for ext partition + [ ! -e "$EXTPATH" ] && ShowError "$EXTPATH does not exist" + + # have to use print for this check b/c parted incorrectly + # reports all ext partitions as ext2 when running print + CHECKEXTFS=`"$CMPARTED" -m "$SDPATH" print | grep ext | cut -d":" -f5` + [ "$CHECKEXTFS" == "$1" ] && ShowError "$EXTPATH is already $1" + + if [ "$CHECKEXTFS" == "ext4" -o "$1" == "ext3" ] ; then + # interweb says downgrading from ext4 is not possible + # without a backup/restore procedure. + # if i figure it out, i'll implement it. + ShowError "downgrading from ext4 is not currently supported" + fi + + if [ "$1" == "ext2" ] ; then + echo -n "removing journaling from $EXTPATH..." + umount /system/sd > /dev/null 2>&1 >>"$LOGFILE" + "$CMTUNE2FS" -O ^has_journal "$EXTPATH" 2>&1 >>"$LOGFILE" + "$CME2FSCK" -fp "$EXTPATH" 2>&1 >>"$LOGFILE" + echo "done" + fi + echo + +} + + +Interactive() { + +cat < /dev/null 2>&1 >>"$LOGFILE" + + ValidateSizeArg "$SWAPRESP" + SWAPTEST="$FUNC_RET" + [ -n "$SWAPTEST" ] && [ $SWAPTEST -gt $SDSIZE ] && ShowMessage "$SWAPRESP > available space($(($SDSIZE))M)." && SWAPTEST= + done + + SWAPSIZE=$SWAPTEST + +} + +GetExtSize() { + + EXTTEST= + + while [ -z "$EXTTEST" ] + do + echo + echo -n "ext partition size [default=$EXTSIZE]: " + read EXTRESP + + [ -z "$EXTRESP" ] && EXTRESP="$EXTSIZE" + echo "$EXTRESP" > /dev/null 2>&1 >>"$LOGFILE" + + ValidateSizeArg "$EXTRESP" + EXTTEST="$FUNC_RET" + + [ -n "$EXTTEST" ] && [ $EXTTEST -gt $(($SDSIZE - $SWAPSIZE)) ] && ShowMessage "$EXTRESP > available space($(($SDSIZE - $SWAPSIZE))M)." && EXTTEST= + done + + EXTSIZE=$EXTTEST + +} + +GetExtType() { + + FSTEST= + + while [ -z "$FSTEST" ] + do + echo + echo -n "ext partition type [default=$EXTFS]: " + read FSRESP + + [ -z "$FSRESP" ] && FSRESP="$EXTFS" + echo "$FSRESP" > /dev/null 2>&1 >>"$LOGFILE" + + ValidateExtArg "$FSRESP" + FSTEST="$FUNC_RET" + done + + EXTFS="$FSTEST" + +} + +GetFatSize() { + + FATTEST= + + while [ -z "$FATTEST" ] + do + echo + echo -n "fat partition size [default=$FATSIZE]: " + read FATRESP + + [ -z "$FATRESP" ] && FATRESP="$FATSIZE" + echo "$FATRESP" > /dev/null 2>&1 >>"$LOGFILE" + + ValidateSizeArg "$FATRESP" + FATTEST="$FUNC_RET" + + [ -n "$FATTEST" ] && [ $FATTEST -gt $FATSIZE ] && ShowMessage "$FATRESP > available space($(($SDSIZE - $SWAPSIZE - $EXTSIZE))M)." && FATTEST= + [ -n "$FATTEST" ] && [ $FATTEST -le 0 ] && ShowMessage "must have a fat32 partition greater than 0MB" && FATTEST= + done + + FATSIZE=$FATTEST + +} + + +SCRIPTNAME="sdparted" +SCRIPTREV="0.6" +MYNAME="51dusty" + +IMODE= +SILENTRUN= +CREATEPART= +FUNC_RET= + +UEXTFSONLY= +DEXTFSONLY= + +TTISOK= +TTISLOOP= +TTISMSDOS= + +SDSIZE= +SDSIZEMB= +SDINFO=$(cat /etc/fstab | grep /sdcard | awk '{print $1}') +if [ -L "$SDINFO" ] +then + SDPATH=$(ls -l $SDINFO | awk '{print $11}') +else + SDPATH=$SDINFO +fi +# we may now have an SDPATH, let's make sure its on mmcblkX or mmcblkXp1 +CHECK_SDPATH1=$(echo $SDPATH | grep mmcblk.$) +CHECK_SDPATH2=$(echo $SDPATH | grep mmcblk.p1$) +if [ -z "$CHECK_SDPATH1" ] +then + if [ -z "$CHECK_SDPATH2" ] + then + echo fail1 + unset SDPATH + else + LEN=${#SDPATH} + BLKLEN=$(expr $LEN - 2) + SDPATH=${SDPATH:0:$BLKLEN} + fi +fi + + +FATSIZE= +FATTYPE="fat32" +FATPATH=$SDPATH"p1" + +EXTSIZE=512 +EXTFS="ext2" +EXTPATH=$SDPATH"p2" +EXTEND= + +SWAPSIZE=32 +SWAPTYPE="linux-swap" +SWAPPATH=$SDPATH"p3" +SWAPEND= + +CMPARTED="/sbin/parted" +CMTUNE2FS="/sbin/tune2fs" +CME2FSCK="/sbin/e2fsck" + +# give the output some breathing room +echo "$SCRIPTREV" >> "$LOGFILE" +echo + +# check for arguments +while [ $# -gt 0 ] ; do + case "$1" in + + -h|--help) ShowHelp ; exit 0 ;; + + -fs|--fatsize) shift ; ValidateSizeArg "$1" ; FATSIZE="$FUNC_RET" ; CREATEPART="$1" ;; + -es|--extsize) shift ; ValidateSizeArg "$1" ; EXTSIZE="$FUNC_RET" ; CREATEPART="$1" ;; + -ss|--swapsize) shift ; ValidateSizeArg "$1" ; SWAPSIZE="$FUNC_RET" ; CREATEPART="$1" ;; + -efs|--extfs) shift ; ValidateExtArg "$1" ; EXTFS="$FUNC_RET" ; CREATEPART="$1" ;; + + -ufs|--upgradefs) shift ; ValidateExtArg "$1" ; UEXTFSONLY="$FUNC_RET" ;; + -dfs|--downgradefs) shift ; ValidateExtArg "$1" ; DEXTFSONLY="$FUNC_RET" ;; + + -i|--interactive) IMODE="$1" ;; + + -s|--silent) SILENTRUN="$1" ;; + + -po|--printonly) ShowCardInfo ;; + + *) ShowHelp ; ShowError "unknown argument '$1'" ;; + + esac + shift +done + +# can't do silent when in interactive mode +[ -n "$IMODE" ] && SILENTRUN= + +# make sure sdcard exists and all needed files are here +CheckReqs + +# unmount all +UnmountAll + +# upgrade only? downgrade only? +UpgradeDowngradeOnly + +# check table +CheckTableType + +# prep card +PrepareSdCard + +# check for interactive mode +[ -n "$IMODE" ] && Interactive + +# do some math +CalculatePartitions + +# last chance to cancel +ShowActions + +# partition card +PartitionSdCard + +# upgrade fs if necessary +UpgradeExt "$EXTFS" + +# say goodbye and show print output +ShowCardInfo diff --git a/utilities/tune2fs b/utilities/tune2fs new file mode 100755 index 000000000..4bd02f6d0 Binary files /dev/null and b/utilities/tune2fs differ diff --git a/verifier.c b/verifier.c index 729e085cf..a63361130 100644 --- a/verifier.c +++ b/verifier.c @@ -19,10 +19,12 @@ #include "mincrypt/rsa.h" #include "mincrypt/sha.h" +#include "mincrypt/sha256.h" #include #include #include +#include // Look for an RSA signature embedded in the .ZIP file comment given // the path to the zip. Verify it matches one of the given public @@ -31,7 +33,7 @@ // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered // or no key matches the signature). -int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) { +int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys) { ui_set_progress(0.0); FILE* f = fopen(path, "rb"); @@ -65,6 +67,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey } if (footer[2] != 0xff || footer[3] != 0xff) { + LOGE("footer is wrong\n"); fclose(f); return VERIFY_FAILURE; } @@ -120,7 +123,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey return VERIFY_FAILURE; } - int i; + unsigned int i; for (i = 4; i < eocd_size-3; ++i) { if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { @@ -136,9 +139,20 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey #define BUFFER_SIZE 4096 - SHA_CTX ctx; - SHA_init(&ctx); - unsigned char* buffer = malloc(BUFFER_SIZE); + bool need_sha1 = false; + bool need_sha256 = false; + for (i = 0; i < numKeys; ++i) { + switch (pKeys[i].hash_len) { + case SHA_DIGEST_SIZE: need_sha1 = true; break; + case SHA256_DIGEST_SIZE: need_sha256 = true; break; + } + } + + SHA_CTX sha1_ctx; + SHA256_CTX sha256_ctx; + SHA_init(&sha1_ctx); + SHA256_init(&sha256_ctx); + unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE); if (buffer == NULL) { LOGE("failed to alloc memory for sha1 buffer\n"); fclose(f); @@ -149,14 +163,15 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey size_t so_far = 0; fseek(f, 0, SEEK_SET); while (so_far < signed_len) { - int size = BUFFER_SIZE; + unsigned int size = BUFFER_SIZE; if (signed_len - so_far < size) size = signed_len - so_far; if (fread(buffer, 1, size, f) != size) { LOGE("failed to read data from %s (%s)\n", path, strerror(errno)); fclose(f); return VERIFY_FAILURE; } - SHA_update(&ctx, buffer, size); + if (need_sha1) SHA_update(&sha1_ctx, buffer, size); + if (need_sha256) SHA256_update(&sha256_ctx, buffer, size); so_far += size; double f = so_far / (double)signed_len; if (f > frac + 0.02 || size == so_far) { @@ -167,18 +182,152 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey fclose(f); free(buffer); - const uint8_t* sha1 = SHA_final(&ctx); + const uint8_t* sha1 = SHA_final(&sha1_ctx); + const uint8_t* sha256 = SHA256_final(&sha256_ctx); + for (i = 0; i < numKeys; ++i) { + const uint8_t* hash; + switch (pKeys[i].hash_len) { + case SHA_DIGEST_SIZE: hash = sha1; break; + case SHA256_DIGEST_SIZE: hash = sha256; break; + default: continue; + } + // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that // the signing tool appends after the signature itself. - if (RSA_verify(pKeys+i, eocd + eocd_size - 6 - RSANUMBYTES, - RSANUMBYTES, sha1)) { + if (RSA_verify(pKeys[i].public_key, eocd + eocd_size - 6 - RSANUMBYTES, + RSANUMBYTES, hash, pKeys[i].hash_len)) { LOGI("whole-file signature verified against key %d\n", i); free(eocd); return VERIFY_SUCCESS; + } else { + LOGI("failed to verify against key %d\n", i); } } free(eocd); LOGE("failed to verify whole-file signature\n"); return VERIFY_FAILURE; } + +// Reads a file containing one or more public keys as produced by +// DumpPublicKey: this is an RSAPublicKey struct as it would appear +// as a C source literal, eg: +// +// "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" +// +// For key versions newer than the original 2048-bit e=3 keys +// supported by Android, the string is preceded by a version +// identifier, eg: +// +// "v2 {64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" +// +// (Note that the braces and commas in this example are actual +// characters the parser expects to find in the file; the ellipses +// indicate more numbers omitted from this example.) +// +// The file may contain multiple keys in this format, separated by +// commas. The last key must not be followed by a comma. +// +// A Certificate is a pair of an RSAPublicKey and a particular hash +// (we support SHA-1 and SHA-256; we store the hash length to signify +// which is being used). The hash used is implied by the version number. +// +// 1: 2048-bit RSA key with e=3 and SHA-1 hash +// 2: 2048-bit RSA key with e=65537 and SHA-1 hash +// 3: 2048-bit RSA key with e=3 and SHA-256 hash +// 4: 2048-bit RSA key with e=65537 and SHA-256 hash +// +// Returns NULL if the file failed to parse, or if it contain zero keys. +Certificate* +load_keys(const char* filename, int* numKeys) { + Certificate* out = NULL; + *numKeys = 0; + + FILE* f = fopen(filename, "r"); + if (f == NULL) { + LOGE("opening %s: %s\n", filename, strerror(errno)); + goto exit; + } + + { + int i; + bool done = false; + while (!done) { + ++*numKeys; + out = (Certificate*)realloc(out, *numKeys * sizeof(Certificate)); + Certificate* cert = out + (*numKeys - 1); + cert->public_key = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); + + char start_char; + if (fscanf(f, " %c", &start_char) != 1) goto exit; + if (start_char == '{') { + // a version 1 key has no version specifier. + cert->public_key->exponent = 3; + cert->hash_len = SHA_DIGEST_SIZE; + } else if (start_char == 'v') { + int version; + if (fscanf(f, "%d {", &version) != 1) goto exit; + switch (version) { + case 2: + cert->public_key->exponent = 65537; + cert->hash_len = SHA_DIGEST_SIZE; + break; + case 3: + cert->public_key->exponent = 3; + cert->hash_len = SHA256_DIGEST_SIZE; + break; + case 4: + cert->public_key->exponent = 65537; + cert->hash_len = SHA256_DIGEST_SIZE; + break; + default: + goto exit; + } + } + + RSAPublicKey* key = cert->public_key; + if (fscanf(f, " %i , 0x%x , { %u", + &(key->len), &(key->n0inv), &(key->n[0])) != 3) { + goto exit; + } + if (key->len != RSANUMWORDS) { + LOGE("key length (%d) does not match expected size\n", key->len); + goto exit; + } + for (i = 1; i < key->len; ++i) { + if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; + } + if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; + for (i = 1; i < key->len; ++i) { + if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; + } + fscanf(f, " } } "); + + // if the line ends in a comma, this file has more keys. + switch (fgetc(f)) { + case ',': + // more keys to come. + break; + + case EOF: + done = true; + break; + + default: + LOGE("unexpected character between keys\n"); + goto exit; + } + + LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len); + } + } + + fclose(f); + return out; + +exit: + if (f) fclose(f); + free(out); + *numKeys = 0; + return NULL; +} diff --git a/verifier.h b/verifier.h index 1bdfca6dd..6ce1b44d1 100644 --- a/verifier.h +++ b/verifier.h @@ -19,10 +19,17 @@ #include "mincrypt/rsa.h" +typedef struct Certificate { + int hash_len; // SHA_DIGEST_SIZE (SHA-1) or SHA256_DIGEST_SIZE (SHA-256) + RSAPublicKey* public_key; +} Certificate; + /* Look in the file for a signature footer, and verify that it * matches one of the given keys. Return one of the constants below. */ -int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys); +int verify_file(const char* path, const Certificate *pKeys, unsigned int numKeys); + +Certificate* load_keys(const char* filename, int* numKeys); #define VERIFY_SUCCESS 0 #define VERIFY_FAILURE 1 diff --git a/verifier_test.c b/verifier_test.c index 5b6c1f451..ffdb1c098 100644 --- a/verifier_test.c +++ b/verifier_test.c @@ -19,43 +19,83 @@ #include #include "verifier.h" +#include "mincrypt/sha.h" +#include "mincrypt/sha256.h" // This is build/target/product/security/testkey.x509.pem after being // dumped out by dumpkey.jar. RSAPublicKey test_key = { 64, 0xc926ad21, - { 1795090719, 2141396315, 950055447, -1713398866, - -26044131, 1920809988, 546586521, -795969498, - 1776797858, -554906482, 1805317999, 1429410244, - 129622599, 1422441418, 1783893377, 1222374759, - -1731647369, 323993566, 28517732, 609753416, - 1826472888, 215237850, -33324596, -245884705, - -1066504894, 774857746, 154822455, -1797768399, - -1536767878, -1275951968, -1500189652, 87251430, - -1760039318, 120774784, 571297800, -599067824, - -1815042109, -483341846, -893134306, -1900097649, - -1027721089, 950095497, 555058928, 414729973, - 1136544882, -1250377212, 465547824, -236820568, - -1563171242, 1689838846, -404210357, 1048029507, - 895090649, 247140249, 178744550, -747082073, - -1129788053, 109881576, -350362881, 1044303212, - -522594267, -1309816990, -557446364, -695002876}, - { -857949815, -510492167, -1494742324, -1208744608, - 251333580, 2131931323, 512774938, 325948880, - -1637480859, 2102694287, -474399070, 792812816, - 1026422502, 2053275343, -1494078096, -1181380486, - 165549746, -21447327, -229719404, 1902789247, - 772932719, -353118870, -642223187, 216871947, - -1130566647, 1942378755, -298201445, 1055777370, - 964047799, 629391717, -2062222979, -384408304, - 191868569, -1536083459, -612150544, -1297252564, - -1592438046, -724266841, -518093464, -370899750, - -739277751, -1536141862, 1323144535, 61311905, - 1997411085, 376844204, 213777604, -217643712, - 9135381, 1625809335, -1490225159, -1342673351, - 1117190829, -57654514, 1825108855, -1281819325, - 1111251351, -1726129724, 1684324211, -1773988491, - 367251975, 810756730, -1941182952, 1175080310 } + { 0x6afee91fu, 0x7fa31d5bu, 0x38a0b217u, 0x99df9baeu, + 0xfe72991du, 0x727d3c04u, 0x20943f99u, 0xd08e7826u, + 0x69e7c8a2u, 0xdeeccc8eu, 0x6b9af76fu, 0x553311c4u, + 0x07b9e247u, 0x54c8bbcau, 0x6a540d81u, 0x48dbf567u, + 0x98c92877u, 0x134fbfdeu, 0x01b32564u, 0x24581948u, + 0x6cddc3b8u, 0x0cd444dau, 0xfe0381ccu, 0xf15818dfu, + 0xc06e6d42u, 0x2e2f6412u, 0x093a6737u, 0x94d83b31u, + 0xa466c87au, 0xb3f284a0u, 0xa694ec2cu, 0x053359e6u, + 0x9717ee6au, 0x0732e080u, 0x220d5008u, 0xdc4af350u, + 0x93d0a7c3u, 0xe330c9eau, 0xcac3da1eu, 0x8ebecf8fu, + 0xc2be387fu, 0x38a14e89u, 0x211586f0u, 0x18b846f5u, + 0x43be4c72u, 0xb578c204u, 0x1bbfb230u, 0xf1e267a8u, + 0xa2d3e656u, 0x64b8e4feu, 0xe7e83d4bu, 0x3e77a943u, + 0x3559ffd9u, 0x0ebb0f99u, 0x0aa76ce6u, 0xd3786ea7u, + 0xbca8cd6bu, 0x068ca8e8u, 0xeb1de2ffu, 0x3e3ecd6cu, + 0xe0d9d825u, 0xb1edc762u, 0xdec60b24u, 0xd6931904u}, + { 0xccdcb989u, 0xe19281f9u, 0xa6e80accu, 0xb7f40560u, + 0x0efb0bccu, 0x7f12b0bbu, 0x1e90531au, 0x136d95d0u, + 0x9e660665u, 0x7d54918fu, 0xe3b93ea2u, 0x2f415d10u, + 0x3d2df6e6u, 0x7a627ecfu, 0xa6f22d70u, 0xb995907au, + 0x09de16b2u, 0xfeb8bd61u, 0xf24ec294u, 0x716a427fu, + 0x2e12046fu, 0xeaf3d56au, 0xd9b873adu, 0x0ced340bu, + 0xbc9cec09u, 0x73c65903u, 0xee39ce9bu, 0x3eede25au, + 0x397633b7u, 0x2583c165u, 0x8514f97du, 0xe9166510u, + 0x0b6fae99u, 0xa47139fdu, 0xdb8352f0u, 0xb2ad7f2cu, + 0xa11552e2u, 0xd4d490a7u, 0xe11e8568u, 0xe9e484dau, + 0xd3ef8449u, 0xa47055dau, 0x4edd9557u, 0x03a78ba1u, + 0x770e130du, 0x16762facu, 0x0cbdfcc4u, 0xf3070540u, + 0x008b6515u, 0x60e7e1b7u, 0xa72cf7f9u, 0xaff86e39u, + 0x4296faadu, 0xfc90430eu, 0x6cc8f377u, 0xb398fd43u, + 0x423c5997u, 0x991d59c4u, 0x6464bf73u, 0x96431575u, + 0x15e3d207u, 0x30532a7au, 0x8c4be618u, 0x460a4d76u }, + 3 + }; + +RSAPublicKey test_f4_key = + { 64, 0xc9bd1f21, + { 0x1178db1fu, 0xbf5d0e55u, 0x3393a165u, 0x0ef4c287u, + 0xbc472a4au, 0x383fc5a1u, 0x4a13b7d2u, 0xb1ff2ac3u, + 0xaf66b4d9u, 0x9280acefu, 0xa2165bdbu, 0x6a4d6e5cu, + 0x08ea676bu, 0xb7ac70c7u, 0xcd158139u, 0xa635ccfeu, + 0xa46ab8a8u, 0x445a3e8bu, 0xdc81d9bbu, 0x91ce1a20u, + 0x68021cdeu, 0x4516eda9u, 0x8d43c30cu, 0xed1eff14u, + 0xca387e4cu, 0x58adc233u, 0x4657ab27u, 0xa95b521eu, + 0xdfc0e30cu, 0x394d64a1u, 0xc6b321a1u, 0x2ca22cb8u, + 0xb1892d5cu, 0x5d605f3eu, 0x6025483cu, 0x9afd5181u, + 0x6e1a7105u, 0x03010593u, 0x70acd304u, 0xab957cbfu, + 0x8844abbbu, 0x53846837u, 0x24e98a43u, 0x2ba060c1u, + 0x8b88b88eu, 0x44eea405u, 0xb259fc41u, 0x0907ad9cu, + 0x13003adau, 0xcf79634eu, 0x7d314ec9u, 0xfbbe4c2bu, + 0xd84d0823u, 0xfd30fd88u, 0x68d8a909u, 0xfb4572d9u, + 0xa21301c2u, 0xd00a4785u, 0x6862b50cu, 0xcfe49796u, + 0xdaacbd83u, 0xfb620906u, 0xdf71e0ccu, 0xbbc5b030u }, + { 0x69a82189u, 0x1a8b22f4u, 0xcf49207bu, 0x68cc056au, + 0xb206b7d2u, 0x1d449bbdu, 0xe9d342f2u, 0x29daea58u, + 0xb19d011au, 0xc62f15e4u, 0x9452697au, 0xb62bb87eu, + 0x60f95cc2u, 0x279ebb2du, 0x17c1efd8u, 0xec47558bu, + 0xc81334d1u, 0x88fe7601u, 0x79992eb1u, 0xb4555615u, + 0x2022ac8cu, 0xc79a4b8cu, 0xb288b034u, 0xd6b942f0u, + 0x0caa32fbu, 0xa065ba51u, 0x4de9f154u, 0x29f64f6cu, + 0x7910af5eu, 0x3ed4636au, 0xe4c81911u, 0x9183f37du, + 0x5811e1c4u, 0x29c7a58cu, 0x9715d4d3u, 0xc7e2dce3u, + 0x140972ebu, 0xf4c8a69eu, 0xa104d424u, 0x5dabbdfbu, + 0x41cb4c6bu, 0xd7f44717u, 0x61785ff7u, 0x5e0bc273u, + 0x36426c70u, 0x2aa6f08eu, 0x083badbfu, 0x3cab941bu, + 0x8871da23u, 0x1ab3dbaeu, 0x7115a21du, 0xf5aa0965u, + 0xf766f562u, 0x7f110225u, 0x86d96a04u, 0xc50a120eu, + 0x3a751ca3u, 0xc21aa186u, 0xba7359d0u, 0x3ff2b257u, + 0xd116e8bbu, 0xfc1318c0u, 0x070e5b1du, 0x83b759a6u }, + 65537 }; void ui_print(const char* fmt, ...) { @@ -72,17 +112,36 @@ void ui_set_progress(float fraction) { } int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + if (argc < 2 || argc > 4) { + fprintf(stderr, "Usage: %s [-sha256] [-f4 | -file ] \n", argv[0]); return 2; } - int result = verify_file(argv[1], &test_key, 1); + Certificate default_cert; + Certificate* cert = &default_cert; + cert->public_key = &test_key; + cert->hash_len = SHA_DIGEST_SIZE; + int num_keys = 1; + ++argv; + if (strcmp(argv[0], "-sha256") == 0) { + ++argv; + cert->hash_len = SHA256_DIGEST_SIZE; + } + if (strcmp(argv[0], "-f4") == 0) { + ++argv; + cert->public_key = &test_f4_key; + } else if (strcmp(argv[0], "-file") == 0) { + ++argv; + cert = load_keys(argv[0], &num_keys); + ++argv; + } + + int result = verify_file(*argv, cert, num_keys); if (result == VERIFY_SUCCESS) { - printf("SUCCESS\n"); + printf("VERIFIED\n"); return 0; } else if (result == VERIFY_FAILURE) { - printf("FAILURE\n"); + printf("NOT VERIFIED\n"); return 1; } else { printf("bad return value\n"); diff --git a/verifier_test.sh b/verifier_test.sh index 6350e80d3..65f77f401 100755 --- a/verifier_test.sh +++ b/verifier_test.sh @@ -1,11 +1,7 @@ #!/bin/bash # -# A test suite for applypatch. Run in a client where you have done -# envsetup, choosecombo, etc. -# -# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your -# system partition. -# +# A test suite for recovery's package signature verifier. Run in a +# client where you have done envsetup, lunch, etc. # # TODO: find some way to get this run regularly along with the rest of # the tests. @@ -68,18 +64,39 @@ $ADB push $ANDROID_PRODUCT_OUT/system/bin/verifier_test \ expect_succeed() { testname "$1 (should succeed)" $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip - run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip || fail + shift + run_command $WORK_DIR/verifier_test "$@" $WORK_DIR/package.zip || fail } expect_fail() { testname "$1 (should fail)" $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip - run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip && fail + shift + run_command $WORK_DIR/verifier_test "$@" $WORK_DIR/package.zip && fail } +# not signed at all expect_fail unsigned.zip +# signed in the pre-donut way expect_fail jarsigned.zip + +# success cases expect_succeed otasigned.zip +expect_succeed otasigned_f4.zip -f4 +expect_succeed otasigned_sha256.zip -sha256 +expect_succeed otasigned_f4_sha256.zip -sha256 -f4 + +# verified against different key +expect_fail otasigned.zip -f4 +expect_fail otasigned_f4.zip + +# verified against right key but wrong hash algorithm +expect_fail otasigned.zip -sha256 +expect_fail otasigned_f4.zip -sha256 -f4 +expect_fail otasigned_sha256.zip +expect_fail otasigned_f4_sha256.zip -f4 + +# various other cases expect_fail random.zip expect_fail fake-eocd.zip expect_fail alter-metadata.zip diff --git a/voldclient/Android.mk b/voldclient/Android.mk new file mode 100644 index 000000000..0c3f50571 --- /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.c dispatcher.c event_loop.c +LOCAL_CFLAGS := -DMINIVOLD -Werror +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.c b/voldclient/commands.c new file mode 100644 index 000000000..0287ad233 --- /dev/null +++ b/voldclient/commands.c @@ -0,0 +1,139 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#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, int wait) { + + const char *cmd[3] = { "volume", "mount", path }; + int state = vold_get_volume_state(path); + + if (state == State_Mounted) { + LOGI("Volume %s already mounted\n", path); + return 0; + } + + if (state != 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, int force, int wait) { + + const char *cmd[4] = { "volume", "unmount", path, "force" }; + int state = vold_get_volume_state(path); + + if (state <= State_Idle) { + LOGI("Volume %s is not mounted\n", path); + return 0; + } + + if (state != State_Mounted) { + LOGI("Volume %s cannot be unmounted in state %d\n", path, state); + return -1; + } + + return vold_command(force ? 4: 3, 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 == State_Mounted) + vold_unmount_volume(path, 0, 1); + + return vold_command(4, cmd, 1); +} + +int vold_unshare_volume(const char* path, int mount) { + + const char *cmd[4] = { "volume", "unshare", path, "ums" }; + int state = vold_get_volume_state(path); + int ret = 0; + + if (state != 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, int wait) { + + const char* cmd[3] = { "volume", "format", path }; + return vold_command(3, cmd, wait); +} + +int vold_custom_format_volume(const char* path, const char* fstype, int wait) { + const char* cmd[4] = { "volume", "format", path, fstype }; + return vold_command(4, cmd, wait); +} + +const char* volume_state_to_string(int state) { + if (state == State_Init) + return "Initializing"; + else if (state == State_NoMedia) + return "No-Media"; + else if (state == State_Idle) + return "Idle-Unmounted"; + else if (state == State_Pending) + return "Pending"; + else if (state == State_Mounted) + return "Mounted"; + else if (state == State_Unmounting) + return "Unmounting"; + else if (state == State_Checking) + return "Checking"; + else if (state == State_Formatting) + return "Formatting"; + else if (state == State_Shared) + return "Shared-Unmounted"; + else if (state == State_SharedMnt) + return "Shared-Mounted"; + else + return "Unknown-Error"; +} + diff --git a/voldclient/dispatcher.c b/voldclient/dispatcher.c new file mode 100644 index 000000000..1bfe96fd2 --- /dev/null +++ b/voldclient/dispatcher.c @@ -0,0 +1,231 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include "ResponseCode.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 == State_Idle) { + vold_mount_volume(node->path, 0); + } + } + 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 >= State_Shared) { + vold_unshare_volume(node->path, 0); + } + if (node->state == State_Mounted) { + vold_unmount_volume(node->path, 1, 1); + } + } + 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, State_Idle); + + if (callbacks != NULL && callbacks->disk_added != NULL) + callbacks->disk_added(label, path); + + if (should_automount) + vold_mount_volume(path, 0); +} + +static void vold_handle_volume_removed(char* label, char* path) { + + set_volume_state(path, 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 == VolumeListResult) { + //