diff options
72 files changed, 3015 insertions, 6661 deletions
@@ -30,16 +30,17 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_SRC_FILES := \ - recovery.cpp \ + adb_install.cpp \ + asn1_decoder.cpp \ bootloader.cpp \ + device.cpp \ + fuse_sdcard_provider.c \ install.cpp \ + recovery.cpp \ roots.cpp \ - ui.cpp \ screen_ui.cpp \ - asn1_decoder.cpp \ + ui.cpp \ verifier.cpp \ - adb_install.cpp \ - fuse_sdcard_provider.c LOCAL_MODULE := recovery @@ -54,6 +55,11 @@ RECOVERY_FSTAB_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_C_INCLUDES += \ + system/vold \ + system/extras/ext4_utils \ + system/core/adb \ + LOCAL_STATIC_LIBRARIES := \ libext4_utils_static \ libsparse_static \ @@ -66,6 +72,7 @@ LOCAL_STATIC_LIBRARIES := \ libminui \ libpng \ libfs_mgr \ + libbase \ libcutils \ liblog \ libselinux \ @@ -75,15 +82,11 @@ LOCAL_STATIC_LIBRARIES := \ ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) LOCAL_CFLAGS += -DUSE_EXT4 - LOCAL_C_INCLUDES += system/extras/ext4_utils system/vold + LOCAL_C_INCLUDES += system/extras/ext4_utils LOCAL_STATIC_LIBRARIES += libext4_utils_static libz endif -# This binary is in the recovery ramdisk, which is otherwise a copy of root. -# It gets copied there in config/Makefile. LOCAL_MODULE_TAGS suppresses -# a (redundant) copy of the binary in /system/bin for user builds. -# TODO: Build the ramdisk image in a more principled way. -LOCAL_MODULE_TAGS := eng +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin ifeq ($(TARGET_RECOVERY_UI_LIB),) LOCAL_SRC_FILES += default_device.cpp @@ -91,9 +94,6 @@ else LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) endif -LOCAL_C_INCLUDES += system/extras/ext4_utils -LOCAL_C_INCLUDES += external/openssl/include - include $(BUILD_EXECUTABLE) # All the APIs for testing @@ -108,7 +108,6 @@ include $(CLEAR_VARS) LOCAL_MODULE := verifier_test LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := tests -LOCAL_CFLAGS += -DNO_RECOVERY_MOUNT LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_SRC_FILES := \ verifier_test.cpp \ diff --git a/README.md b/README.md new file mode 100644 index 0000000..bab7e87 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +The Recovery Image +================== + +Quick turn-around testing +------------------------- + + mm -j && m ramdisk-nodeps && m recoveryimage-nodeps + + # To boot into the new recovery image + # without flashing the recovery partition: + adb reboot bootloader + fastboot boot $ANDROID_PRODUCT_OUT/recovery.img diff --git a/adb_install.cpp b/adb_install.cpp index be3b9a0..ebd4cac 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -30,10 +30,8 @@ #include "install.h" #include "common.h" #include "adb_install.h" -extern "C" { #include "minadbd/fuse_adb_provider.h" #include "fuse_sideload.h" -} static RecoveryUI* ui = NULL; @@ -61,9 +59,7 @@ stop_adbd() { 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') { + if (is_ro_debuggable()) { ui->Print("Restarting adbd...\n"); set_usb_driver(true); property_set("ctl.start", "adbd"); @@ -75,7 +71,9 @@ maybe_restart_adbd() { #define ADB_INSTALL_TIMEOUT 300 int -apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) { +apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) { + modified_flash = true; + ui = ui_; stop_adbd(); @@ -109,7 +107,7 @@ apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) { sleep(1); continue; } else { - ui->Print("\nTimed out waiting for package.\n\n", strerror(errno)); + ui->Print("\nTimed out waiting for package.\n\n"); result = INSTALL_ERROR; kill(child, SIGKILL); break; diff --git a/adb_install.h b/adb_install.h index a18b712..efad436 100644 --- a/adb_install.h +++ b/adb_install.h @@ -19,6 +19,6 @@ class RecoveryUI; -int apply_from_adb(RecoveryUI* h, int* wipe_cache, const char* install_file); +int apply_from_adb(RecoveryUI* h, bool* wipe_cache, const char* install_file); #endif diff --git a/applypatch/bspatch.c b/applypatch/bspatch.c index b34ec2a..b57760e 100644 --- a/applypatch/bspatch.c +++ b/applypatch/bspatch.c @@ -23,6 +23,7 @@ #include <stdio.h> #include <sys/stat.h> #include <errno.h> +#include <malloc.h> #include <unistd.h> #include <string.h> diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c index 05c4f25..3bac8be 100644 --- a/applypatch/imgdiff.c +++ b/applypatch/imgdiff.c @@ -408,6 +408,7 @@ unsigned char* ReadImage(const char* filename, p[2] == 0x08 && // deflate compression p[3] == 0x00) { // no header flags // 'pos' is the offset of the start of a gzip chunk. + size_t chunk_offset = pos; *num_chunks += 3; *chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk)); @@ -453,6 +454,14 @@ unsigned char* ReadImage(const char* filename, strm.avail_out = allocated - curr->len; strm.next_out = curr->data + curr->len; ret = inflate(&strm, Z_NO_FLUSH); + if (ret < 0) { + printf("Error: inflate failed [%s] at file offset [%zu]\n" + "imgdiff only supports gzip kernel compression," + " did you try CONFIG_KERNEL_LZO?\n", + strm.msg, chunk_offset); + free(img); + return NULL; + } curr->len = allocated - strm.avail_out; if (strm.avail_out == 0) { allocated *= 2; diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c index 33c4487..09b0a73 100644 --- a/applypatch/imgpatch.c +++ b/applypatch/imgpatch.c @@ -21,6 +21,7 @@ #include <sys/cdefs.h> #include <sys/stat.h> #include <errno.h> +#include <malloc.h> #include <unistd.h> #include <string.h> diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp index 7280f74..e7aef78 100644 --- a/asn1_decoder.cpp +++ b/asn1_decoder.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <malloc.h> #include <stdint.h> #include <string.h> @@ -17,6 +17,7 @@ #ifndef RECOVERY_COMMON_H #define RECOVERY_COMMON_H +#include <stdbool.h> #include <stdio.h> #include <stdarg.h> @@ -39,6 +40,7 @@ extern "C" { #define STRINGIFY(x) #x #define EXPAND(x) STRINGIFY(x) +extern bool modified_flash; typedef struct fstab_rec Volume; // fopen a file, mounting volumes and making parent dirs as necessary. @@ -46,6 +48,8 @@ FILE* fopen_path(const char *path, const char *mode); void ui_print(const char* format, ...); +bool is_ro_debuggable(); + #ifdef __cplusplus } #endif diff --git a/default_device.cpp b/default_device.cpp index 97806ac..a971866 100644 --- a/default_device.cpp +++ b/default_device.cpp @@ -14,74 +14,9 @@ * limitations under the License. */ -#include <linux/input.h> - -#include "common.h" #include "device.h" #include "screen_ui.h" -static const char* HEADERS[] = { "Volume up/down to move highlight;", - "enter button to select.", - "", - NULL }; - -static const char* ITEMS[] = {"reboot system now", - "apply update from ADB", - "wipe data/factory reset", - "wipe cache partition", - "reboot to bootloader", - "power down", - "view recovery logs", - NULL }; - -class DefaultDevice : public Device { - public: - DefaultDevice() : - ui(new ScreenRecoveryUI) { - } - - RecoveryUI* GetUI() { return ui; } - - int HandleMenuKey(int key, int visible) { - if (visible) { - switch (key) { - case KEY_DOWN: - case KEY_VOLUMEDOWN: - return kHighlightDown; - - case KEY_UP: - case KEY_VOLUMEUP: - return kHighlightUp; - - case KEY_ENTER: - case KEY_POWER: - return kInvokeItem; - } - } - - return kNoAction; - } - - BuiltinAction InvokeMenuItem(int menu_position) { - switch (menu_position) { - case 0: return REBOOT; - case 1: return APPLY_ADB_SIDELOAD; - case 2: return WIPE_DATA; - case 3: return WIPE_CACHE; - case 4: return REBOOT_BOOTLOADER; - case 5: return SHUTDOWN; - case 6: return READ_RECOVERY_LASTLOG; - default: return NO_ACTION; - } - } - - const char* const* GetMenuHeaders() { return HEADERS; } - const char* const* GetMenuItems() { return ITEMS; } - - private: - RecoveryUI* ui; -}; - Device* make_device() { - return new DefaultDevice(); + return new Device(new ScreenRecoveryUI); } diff --git a/device.cpp b/device.cpp new file mode 100644 index 0000000..fd1a987 --- /dev/null +++ b/device.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 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 "device.h" + +static const char* MENU_ITEMS[] = { + "Reboot system now", + "Reboot to bootloader", + "Apply update from ADB", + "Apply update from SD card", + "Wipe data/factory reset", + "Wipe cache partition", + "Mount /system", + "View recovery logs", + "Power off", + NULL +}; + +const char* const* Device::GetMenuItems() { + return MENU_ITEMS; +} + +Device::BuiltinAction Device::InvokeMenuItem(int menu_position) { + switch (menu_position) { + case 0: return REBOOT; + case 1: return REBOOT_BOOTLOADER; + case 2: return APPLY_ADB_SIDELOAD; + case 3: return APPLY_SDCARD; + case 4: return WIPE_DATA; + case 5: return WIPE_CACHE; + case 6: return MOUNT_SYSTEM; + case 7: return VIEW_RECOVERY_LOGS; + case 8: return SHUTDOWN; + default: return NO_ACTION; + } +} + +int Device::HandleMenuKey(int key, int visible) { + if (!visible) { + return kNoAction; + } + + switch (key) { + case KEY_DOWN: + case KEY_VOLUMEDOWN: + return kHighlightDown; + + case KEY_UP: + case KEY_VOLUMEUP: + return kHighlightUp; + + case KEY_ENTER: + case KEY_POWER: + return kInvokeItem; + + default: + // If you have all of the above buttons, any other buttons + // are ignored. Otherwise, any button cycles the highlight. + return ui_->HasThreeButtons() ? kNoAction : kHighlightDown; + } +} @@ -21,29 +21,20 @@ class Device { public: + Device(RecoveryUI* ui) : ui_(ui) { } virtual ~Device() { } // Called to obtain the UI object that should be used to display // the recovery user interface for this device. You should not // have called Init() on the UI object already, the caller will do // that after this method returns. - virtual RecoveryUI* GetUI() = 0; + virtual RecoveryUI* GetUI() { return ui_; } // Called when recovery starts up (after the UI has been obtained // and initialized and after the arguments have been parsed, but // before anything else). virtual void StartRecovery() { }; - // enum KeyAction { NONE, TOGGLE, REBOOT }; - - // // 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 a - // // KeyAction to indicate action should be taken immediately. - // // These actions happen when recovery is not waiting for input - // // (eg, in the midst of installing a package). - // virtual KeyAction CheckImmediateKeyAction(volatile char* key_pressed, int key_code) = 0; - // Called from the main thread when recovery is at the main menu // and waiting for input, and a key is pressed. (Note that "at" // the main menu does not necessarily mean the menu is visible; @@ -63,12 +54,26 @@ class Device { // - invoke the highlighted item (kInvokeItem) // - do nothing (kNoAction) // - invoke a specific action (a menu position: any non-negative number) - virtual int HandleMenuKey(int key, int visible) = 0; + virtual int HandleMenuKey(int key, int visible); + + enum BuiltinAction { + NO_ACTION = 0, + REBOOT = 1, + APPLY_SDCARD = 2, + // APPLY_CACHE was 3. + APPLY_ADB_SIDELOAD = 4, + WIPE_DATA = 5, + WIPE_CACHE = 6, + REBOOT_BOOTLOADER = 7, + SHUTDOWN = 8, + VIEW_RECOVERY_LOGS = 9, + MOUNT_SYSTEM = 10, + }; - enum BuiltinAction { NO_ACTION, REBOOT, APPLY_EXT, - APPLY_CACHE, // APPLY_CACHE is deprecated; has no effect - APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE, - REBOOT_BOOTLOADER, SHUTDOWN, READ_RECOVERY_LASTLOG }; + // Return the list of menu items (an array of strings, + // NULL-terminated). The menu_position passed to InvokeMenuItem + // will correspond to the indexes into this array. + virtual const char* const* GetMenuItems(); // Perform a recovery action selected from the menu. // 'menu_position' will be the item number of the selected menu @@ -79,7 +84,7 @@ class Device { // builtin actions, you can just return the corresponding enum // value. If it is an action specific to your device, you // actually perform it here and return NO_ACTION. - virtual BuiltinAction InvokeMenuItem(int menu_position) = 0; + virtual BuiltinAction InvokeMenuItem(int menu_position); static const int kNoAction = -1; static const int kHighlightUp = -2; @@ -94,16 +99,8 @@ class Device { // are erased AFTER this returns (whether it returns success or not). virtual int WipeData() { return 0; } - // Return the headers (an array of strings, one per line, - // NULL-terminated) for the main menu. Typically these tell users - // what to push to move the selection and invoke the selected - // item. - virtual const char* const* GetMenuHeaders() = 0; - - // Return the list of menu items (an array of strings, - // NULL-terminated). The menu_position passed to InvokeMenuItem - // will correspond to the indexes into this array. - virtual const char* const* GetMenuItems() = 0; + private: + RecoveryUI* ui_; }; // The device-specific library must define this function (or the diff --git a/edify/Android.mk b/edify/Android.mk index 61ed6fa..03c04e4 100644 --- a/edify/Android.mk +++ b/edify/Android.mk @@ -7,9 +7,10 @@ edify_src_files := \ parser.y \ expr.c -# "-x c" forces the lex/yacc files to be compiled as c; -# the build system otherwise forces them to be c++. -edify_cflags := -x c +# "-x c" forces the lex/yacc files to be compiled as c the build system +# otherwise forces them to be c++. Need to also add an explicit -std because the +# build system will soon default C++ to -std=c++11. +edify_cflags := -x c -std=gnu89 # # Build the host-side command line tool diff --git a/edify/main.c b/edify/main.c index b3fad53..b1baa0b 100644 --- a/edify/main.c +++ b/edify/main.c @@ -25,13 +25,12 @@ extern int yyparse(Expr** root, int* error_count); int expect(const char* expr_str, const char* expected, int* errors) { Expr* e; - int error; char* result; printf("."); int error_count = parse_string(expr_str, &e, &error_count); - if (error > 0 || error_count > 0) { + if (error_count > 0) { printf("error parsing \"%s\" (%d errors)\n", expr_str, error_count); ++*errors; diff --git a/etc/init.rc b/etc/init.rc index c78a44a..6c07c60 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -1,13 +1,6 @@ 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 diff --git a/fuse_sdcard_provider.c b/fuse_sdcard_provider.c index 19fb52d..ca8c914 100644 --- a/fuse_sdcard_provider.c +++ b/fuse_sdcard_provider.c @@ -16,6 +16,7 @@ #include <stdlib.h> #include <stdio.h> +#include <string.h> #include <errno.h> #include <pthread.h> #include <sys/mount.h> diff --git a/fuse_sdcard_provider.h b/fuse_sdcard_provider.h index dc2982c..dbfbcd5 100644 --- a/fuse_sdcard_provider.h +++ b/fuse_sdcard_provider.h @@ -17,7 +17,13 @@ #ifndef __FUSE_SDCARD_PROVIDER_H #define __FUSE_SDCARD_PROVIDER_H +#include <sys/cdefs.h> + +__BEGIN_DECLS + void* start_sdcard_fuse(const char* path); void finish_sdcard_fuse(void* token); +__END_DECLS + #endif diff --git a/fuse_sideload.c b/fuse_sideload.c index ab91def..1dd84e9 100644 --- a/fuse_sideload.c +++ b/fuse_sideload.c @@ -53,6 +53,7 @@ #include <string.h> #include <sys/inotify.h> #include <sys/mount.h> +#include <sys/param.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/statfs.h> @@ -105,27 +106,52 @@ static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, siz vec[0].iov_base = &hdr; vec[0].iov_len = sizeof(hdr); - vec[1].iov_base = data; + vec[1].iov_base = /* const_cast */(void*)(data); vec[1].iov_len = len; res = writev(fd->ffd, vec, 2); if (res < 0) { - printf("*** REPLY FAILED *** %d\n", errno); + printf("*** REPLY FAILED *** %s\n", strerror(errno)); } } static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { const struct fuse_init_in* req = data; struct fuse_init_out out; + size_t fuse_struct_size; + + + /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out + * defined (fuse version 7.6). The structure is the same from 7.6 through + * 7.22. Beginning with 7.23, the structure increased in size and added + * new parameters. + */ + if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) { + printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6", + req->major, req->minor, FUSE_KERNEL_VERSION); + return -1; + } + + out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION); + fuse_struct_size = sizeof(out); +#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) + /* FUSE_KERNEL_VERSION >= 23. */ + + /* If the kernel only works on minor revs older than or equal to 22, + * then use the older structure size since this code only uses the 7.22 + * version of the structure. */ + if (req->minor <= 22) { + fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE; + } +#endif out.major = FUSE_KERNEL_VERSION; - out.minor = FUSE_KERNEL_MINOR_VERSION; out.max_readahead = req->max_readahead; out.flags = 0; out.max_background = 32; out.congestion_threshold = 32; out.max_write = 4096; - fuse_reply(fd, hdr->unique, &out, sizeof(out)); + fuse_reply(fd, hdr->unique, &out, fuse_struct_size); return NO_STATUS; } @@ -404,7 +430,7 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, char opts[256]; snprintf(opts, sizeof(opts), - ("fd=%d,user_id=%d,group_id=%d,max_read=%zu," + ("fd=%d,user_id=%d,group_id=%d,max_read=%u," "allow_other,rootmode=040000"), fd.ffd, fd.uid, fd.gid, block_size); diff --git a/fuse_sideload.h b/fuse_sideload.h index c0b16ef..f9e3bf0 100644 --- a/fuse_sideload.h +++ b/fuse_sideload.h @@ -17,6 +17,10 @@ #ifndef __FUSE_SIDELOAD_H #define __FUSE_SIDELOAD_H +#include <sys/cdefs.h> + +__BEGIN_DECLS + // define the filenames created by the sideload FUSE filesystem #define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload" #define FUSE_SIDELOAD_HOST_FILENAME "package.zip" @@ -35,4 +39,6 @@ struct provider_vtab { int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size, uint32_t block_size); +__END_DECLS + #endif diff --git a/install.cpp b/install.cpp index 9db5640..c7d382f 100644 --- a/install.cpp +++ b/install.cpp @@ -18,6 +18,7 @@ #include <errno.h> #include <fcntl.h> #include <limits.h> +#include <string.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> @@ -47,7 +48,7 @@ static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; // If the package contains an update binary, extract it and run it. static int -try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { +try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) { const ZipEntry* binary_entry = mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); if (binary_entry == NULL) { @@ -87,7 +88,7 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { // fill up the next <frac> part of of the progress bar // over <secs> seconds. If <secs> is zero, use // set_progress commands to manually control the - // progress of this segment of the bar + // progress of this segment of the bar. // // set_progress <frac> // <frac> should be between 0.0 and 1.0; sets the @@ -106,6 +107,18 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { // ui_print <string> // display <string> on the screen. // + // wipe_cache + // a wipe of cache will be performed following a successful + // installation. + // + // clear_display + // turn off the text display. + // + // enable_reboot + // packages can explicitly request that they want the user + // to be able to reboot during installation (useful for + // debugging packages that don't exit). + // // - the name of the package zip file. // @@ -128,7 +141,7 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { } close(pipefd[1]); - *wipe_cache = 0; + *wipe_cache = false; char buffer[1024]; FILE* from_child = fdopen(pipefd[0], "r"); @@ -157,7 +170,7 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { } fflush(stdout); } else if (strcmp(command, "wipe_cache") == 0) { - *wipe_cache = 1; + *wipe_cache = true; } else if (strcmp(command, "clear_display") == 0) { ui->SetBackground(RecoveryUI::NONE); } else if (strcmp(command, "enable_reboot") == 0) { @@ -182,7 +195,7 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { } static int -really_install_package(const char *path, int* wipe_cache, bool needs_mount) +really_install_package(const char *path, bool* wipe_cache, bool needs_mount) { ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); ui->Print("Finding update package...\n"); @@ -252,9 +265,11 @@ really_install_package(const char *path, int* wipe_cache, bool needs_mount) } int -install_package(const char* path, int* wipe_cache, const char* install_file, +install_package(const char* path, bool* wipe_cache, const char* install_file, bool needs_mount) { + modified_flash = true; + FILE* install_log = fopen_path(install_file, "w"); if (install_log) { fputs(path, install_log); @@ -27,7 +27,7 @@ enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE }; // Install the package specified by root_path. If INSTALL_SUCCESS is // returned and *wipe_cache is true on exit, caller should wipe the // cache partition. -int install_package(const char *root_path, int* wipe_cache, +int install_package(const char* root_path, bool* wipe_cache, const char* install_file, bool needs_mount); #ifdef __cplusplus diff --git a/minadbd/Android.mk b/minadbd/Android.mk index 04956d8..cbfd76e 100644 --- a/minadbd/Android.mk +++ b/minadbd/Android.mk @@ -1,32 +1,36 @@ # Copyright 2005 The Android Open Source Project -# -# Android.mk for adb -# LOCAL_PATH:= $(call my-dir) -# minadbd library -# ========================================================= +minadbd_cflags := \ + -Wall -Werror \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -DADB_HOST=0 \ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ - adb.c \ - fdevent.c \ - fuse_adb_provider.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_C_INCLUDES += bootable/recovery + adb_main.cpp \ + fuse_adb_provider.cpp \ + services.cpp \ LOCAL_MODULE := libminadbd - -LOCAL_STATIC_LIBRARIES := libfusesideload libcutils libc +LOCAL_CFLAGS := $(minadbd_cflags) +LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration +LOCAL_C_INCLUDES := bootable/recovery system/core/adb +LOCAL_WHOLE_STATIC_LIBRARIES := libadbd include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_CLANG := true +LOCAL_MODULE := minadbd_test +LOCAL_SRC_FILES := fuse_adb_provider_test.cpp +LOCAL_CFLAGS := $(minadbd_cflags) +LOCAL_C_INCLUDES := $(LOCAL_PATH) system/core/adb +LOCAL_STATIC_LIBRARIES := libminadbd +LOCAL_SHARED_LIBRARIES := liblog + +include $(BUILD_NATIVE_TEST) diff --git a/minadbd/README.txt b/minadbd/README.txt index c9df484..e69dc87 100644 --- a/minadbd/README.txt +++ b/minadbd/README.txt @@ -1,39 +1,8 @@ -The contents of this directory are copied from system/core/adb, with -the following changes: +minadbd is now mostly built from libadbd. The fuse features are unique to +minadbd, and services.c has been modified as follows: -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 services 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 deleted file mode 100644 index 127d072..0000000 --- a/minadbd/adb.c +++ /dev/null @@ -1,402 +0,0 @@ -/* - * 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 <stdio.h> -#include <stdlib.h> -#include <ctype.h> -#include <stdarg.h> -#include <errno.h> -#include <string.h> -#include <time.h> -#include <sys/time.h> - -#include "sysdeps.h" -#include "adb.h" - -#include <private/android_filesystem_config.h> - -#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 - usb_init(); - } - - D("Event loop starting\n"); - - fdevent_loop(); - - usb_cleanup(); - - return 0; -} diff --git a/minadbd/adb.h b/minadbd/adb.h deleted file mode 100644 index 714868f..0000000 --- a/minadbd/adb.h +++ /dev/null @@ -1,424 +0,0 @@ -/* - * 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 <limits.h> - -#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 -#define CS_UNAUTHORIZED 7 - -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); - -#endif diff --git a/minadbd/transport.h b/minadbd/adb_main.cpp index 992e052..f6e2401 100644 --- a/minadbd/transport.h +++ b/minadbd/adb_main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2015 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. @@ -14,13 +14,32 @@ * limitations under the License. */ -#ifndef __TRANSPORT_H -#define __TRANSPORT_H +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.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 */ +#define TRACE_TAG TRACE_ADB + +#include "sysdeps.h" + +#include "adb.h" +#include "transport.h" + +int adb_main(int is_daemon, int server_port) +{ + atexit(usb_cleanup); + + adb_device_banner = "sideload"; + + // No SIGCHLD. Let the service subproc handle its children. + signal(SIGPIPE, SIG_IGN); + + init_transport_registration(); + usb_init(); + + D("Event loop starting\n"); + fdevent_loop(); + + return 0; +} diff --git a/minadbd/fdevent.c b/minadbd/fdevent.c deleted file mode 100644 index 5c374a7..0000000 --- a/minadbd/fdevent.c +++ /dev/null @@ -1,695 +0,0 @@ -/* http://frotznet.googlecode.com/svn/trunk/utils/fdevent.c -** -** Copyright 2006, Brian Swetland <swetland@frotz.net> -** -** 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 <sys/ioctl.h> - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <errno.h> - -#include <fcntl.h> - -#include <stdarg.h> -#include <stddef.h> - -#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 <sys/epoll.h> - -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 <winsock2.h> -#else -#include <sys/select.h> -#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 deleted file mode 100644 index a0ebe2a..0000000 --- a/minadbd/fdevent.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 <stdint.h> /* 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/fuse_adb_provider.c b/minadbd/fuse_adb_provider.cpp index f80533a..5da7fd7 100644 --- a/minadbd/fuse_adb_provider.c +++ b/minadbd/fuse_adb_provider.cpp @@ -16,29 +16,28 @@ #include <stdlib.h> #include <stdio.h> +#include <string.h> #include <errno.h> +#include "sysdeps.h" + #include "adb.h" +#include "adb_io.h" +#include "fuse_adb_provider.h" #include "fuse_sideload.h" -struct adb_data { - int sfd; // file descriptor for the adb channel - - uint64_t file_size; - uint32_t block_size; -}; - -static int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) { +int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, + uint32_t fetch_size) { struct adb_data* ad = (struct adb_data*)cookie; char buf[10]; snprintf(buf, sizeof(buf), "%08u", block); - if (writex(ad->sfd, buf, 8) < 0) { + if (!WriteStringFully(ad->sfd, buf)) { fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno)); return -EIO; } - if (readx(ad->sfd, buffer, fetch_size) < 0) { + if (!ReadFdExactly(ad->sfd, buffer, fetch_size)) { fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno)); return -EIO; } @@ -49,7 +48,7 @@ static int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, uint32_ static void close_adb(void* cookie) { struct adb_data* ad = (struct adb_data*)cookie; - writex(ad->sfd, "DONEDONE", 8); + WriteStringFully(ad->sfd, "DONEDONE"); } int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size) { diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h index 0eb1f79..9941709 100644 --- a/minadbd/fuse_adb_provider.h +++ b/minadbd/fuse_adb_provider.h @@ -17,6 +17,16 @@ #ifndef __FUSE_ADB_PROVIDER_H #define __FUSE_ADB_PROVIDER_H +#include <stdint.h> + +struct adb_data { + int sfd; // file descriptor for the adb channel + + uint64_t file_size; + uint32_t block_size; +}; + +int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size); int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size); #endif diff --git a/minadbd/fuse_adb_provider_test.cpp b/minadbd/fuse_adb_provider_test.cpp new file mode 100644 index 0000000..0f2e881 --- /dev/null +++ b/minadbd/fuse_adb_provider_test.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015 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 "fuse_adb_provider.h" + +#include <gtest/gtest.h> + +#include <errno.h> +#include <fcntl.h> +#include <sys/socket.h> + +#include <string> + +#include "adb_io.h" + +TEST(fuse_adb_provider, read_block_adb) { + adb_data data = {}; + int sockets[2]; + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)); + data.sfd = sockets[0]; + + int host_socket = sockets[1]; + fcntl(host_socket, F_SETFL, O_NONBLOCK); + + const char expected_data[] = "foobar"; + char block_data[sizeof(expected_data)] = {}; + + // If we write the result of read_block_adb's request before the request is + // actually made we can avoid needing an extra thread for this test. + ASSERT_TRUE(WriteFdExactly(host_socket, expected_data, + strlen(expected_data))); + + uint32_t block = 1234U; + const char expected_block[] = "00001234"; + ASSERT_EQ(0, read_block_adb(reinterpret_cast<void*>(&data), block, + reinterpret_cast<uint8_t*>(block_data), + sizeof(expected_data) - 1)); + + // Check that read_block_adb requested the right block. + char block_req[sizeof(expected_block)] = {}; + ASSERT_TRUE(ReadFdExactly(host_socket, block_req, 8)); + ASSERT_EQ(0, block_req[8]); + ASSERT_EQ(8U, strlen(block_req)); + ASSERT_STREQ(expected_block, block_req); + + // Check that read_block_adb returned the right data. + ASSERT_EQ(0, block_req[8]); + ASSERT_STREQ(expected_data, block_data); + + // Check that nothing else was written to the socket. + char tmp; + errno = 0; + ASSERT_EQ(-1, read(host_socket, &tmp, 1)); + ASSERT_EQ(EWOULDBLOCK, errno); + + close(sockets[0]); + close(sockets[1]); +} + +TEST(fuse_adb_provider, read_block_adb_fail_write) { + adb_data data = {}; + int sockets[2]; + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)); + data.sfd = sockets[0]; + + ASSERT_EQ(0, close(sockets[1])); + + char buf[1]; + ASSERT_EQ(-EIO, read_block_adb(reinterpret_cast<void*>(&data), 0, + reinterpret_cast<uint8_t*>(buf), 1)); + + close(sockets[0]); +} diff --git a/minadbd/mutex_list.h b/minadbd/mutex_list.h deleted file mode 100644 index 652dd73..0000000 --- a/minadbd/mutex_list.h +++ /dev/null @@ -1,26 +0,0 @@ -/* 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.cpp index 218b84a..a832567 100644 --- a/minadbd/services.c +++ b/minadbd/services.cpp @@ -14,18 +14,19 @@ * limitations under the License. */ -#include <stdlib.h> +#include <errno.h> +#include <inttypes.h> #include <stdio.h> -#include <unistd.h> +#include <stdlib.h> #include <string.h> -#include <errno.h> +#include <unistd.h> #include "sysdeps.h" -#include "fdevent.h" -#include "fuse_adb_provider.h" #define TRACE_TAG TRACE_SERVICES #include "adb.h" +#include "fdevent.h" +#include "fuse_adb_provider.h" typedef struct stinfo stinfo; @@ -35,24 +36,22 @@ struct stinfo { void *cookie; }; - -void *service_bootstrap_func(void *x) -{ - stinfo *sti = x; +void* service_bootstrap_func(void* x) { + stinfo* sti = reinterpret_cast<stinfo*>(x); sti->func(sti->fd, sti->cookie); free(sti); return 0; } -static void sideload_host_service(int sfd, void* cookie) -{ +static void sideload_host_service(int sfd, void* cookie) { char* saveptr; - const char* s = strtok_r(cookie, ":", &saveptr); + const char* s = adb_strtok_r(reinterpret_cast<char*>(cookie), ":", &saveptr); uint64_t file_size = strtoull(s, NULL, 10); - s = strtok_r(NULL, ":", &saveptr); + s = adb_strtok_r(NULL, ":", &saveptr); uint32_t block_size = strtoul(s, NULL, 10); - printf("sideload-host file size %llu block size %lu\n", file_size, block_size); + printf("sideload-host file size %" PRIu64 " block size %" PRIu32 "\n", + file_size, block_size); int result = run_adb_fuse(sfd, file_size, block_size); @@ -61,58 +60,22 @@ static void sideload_host_service(int sfd, void* cookie) exit(result == 0 ? 0 : 1); } -#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)); + stinfo* sti = reinterpret_cast<stinfo*>(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)){ + adb_thread_t t; + if (adb_thread_create( &t, service_bootstrap_func, sti)){ free(sti); adb_close(s[0]); adb_close(s[1]); @@ -124,8 +87,7 @@ static int create_service_thread(void (*func)(int, void *), void *cookie) return s[0]; } -int service_to_fd(const char *name) -{ +int service_to_fd(const char* name) { int ret = -1; if (!strncmp(name, "sideload:", 9)) { @@ -135,10 +97,6 @@ int service_to_fd(const char *name) exit(3); } else if (!strncmp(name, "sideload-host:", 14)) { ret = create_service_thread(sideload_host_service, (void*)(name + 14)); -#if 0 - } else if(!strncmp(name, "echo:", 5)){ - ret = create_service_thread(echo_service, 0); -#endif } if (ret >= 0) { close_on_exec(ret); diff --git a/minadbd/sockets.c b/minadbd/sockets.c deleted file mode 100644 index 817410d..0000000 --- a/minadbd/sockets.c +++ /dev/null @@ -1,731 +0,0 @@ -/* - * 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 <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> -#include <ctype.h> - -#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=%zu\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 deleted file mode 100644 index 800ddb7..0000000 --- a/minadbd/sysdeps.h +++ /dev/null @@ -1,494 +0,0 @@ -/* - * 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 <windows.h> -#include <winsock2.h> -#include <ws2tcpip.h> -#include <process.h> -#include <fcntl.h> -#include <io.h> -#include <sys/stat.h> -#include <errno.h> -#include <ctype.h> - -#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 <cutils/misc.h> */ -extern void* load_file(const char* pathname, unsigned* psize); - -/* normally provided by <cutils/sockets.h> */ -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 <cutils/sockets.h> -#include <cutils/properties.h> -#include <cutils/misc.h> -#include <signal.h> -#include <sys/wait.h> -#include <sys/stat.h> -#include <fcntl.h> - -#include <pthread.h> -#include <unistd.h> -#include <fcntl.h> -#include <stdarg.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <string.h> - -#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 deleted file mode 100644 index 92679f5..0000000 --- a/minadbd/transport.c +++ /dev/null @@ -1,803 +0,0 @@ -/* - * 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 <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> - -#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 - size_t 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=%zu got=%zu\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_usb.c b/minadbd/transport_usb.c deleted file mode 100644 index 91cbf61..0000000 --- a/minadbd/transport_usb.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <sysdeps.h> - -#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 deleted file mode 100644 index 29bab15..0000000 --- a/minadbd/usb_linux_client.c +++ /dev/null @@ -1,493 +0,0 @@ -/* - * 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 <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> - -#include <linux/usb/ch9.h> -#include <linux/usb/functionfs.h> -#include <sys/ioctl.h> -#include <sys/types.h> -#include <dirent.h> -#include <errno.h> - -#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=%zu count=%zu ]\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 deleted file mode 100644 index 91518ba..0000000 --- a/minadbd/utils.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 <stdarg.h> -#include <stdio.h> -#include <string.h> - -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 deleted file mode 100644 index f70ecd2..0000000 --- a/minadbd/utils.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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/minui/Android.mk b/minui/Android.mk index df4aac1..52f0662 100644 --- a/minui/Android.mk +++ b/minui/Android.mk @@ -1,21 +1,27 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := graphics.c graphics_adf.c graphics_fbdev.c events.c \ - resources.c - -LOCAL_C_INCLUDES +=\ - external/libpng\ - external/zlib +LOCAL_SRC_FILES := \ + events.cpp \ + graphics.cpp \ + graphics_adf.cpp \ + graphics_fbdev.cpp \ + resources.cpp \ LOCAL_WHOLE_STATIC_LIBRARIES += libadf +LOCAL_STATIC_LIBRARIES += libpng LOCAL_MODULE := libminui +LOCAL_CLANG := true + # 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)),ABGR_8888) + LOCAL_CFLAGS += -DRECOVERY_ABGR +endif ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888) LOCAL_CFLAGS += -DRECOVERY_RGBX endif @@ -30,3 +36,10 @@ else endif include $(BUILD_STATIC_LIBRARY) + +# Used by OEMs for factory test images. +include $(CLEAR_VARS) +LOCAL_MODULE := libminui +LOCAL_WHOLE_STATIC_LIBRARIES += libminui +LOCAL_SHARED_LIBRARIES := libpng +include $(BUILD_SHARED_LIBRARY) diff --git a/minui/events.c b/minui/events.c deleted file mode 100644 index df7dad4..0000000 --- a/minui/events.c +++ /dev/null @@ -1,211 +0,0 @@ -/* - * 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 <stdio.h> -#include <stdlib.h> -#include <fcntl.h> -#include <dirent.h> -#include <sys/epoll.h> - -#include <linux/input.h> - -#include "minui.h" - -#define MAX_DEVICES 16 -#define MAX_MISC_FDS 16 - -#define BITS_PER_LONG (sizeof(unsigned long) * 8) -#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG) - -#define test_bit(bit, array) \ - ((array)[(bit)/BITS_PER_LONG] & (1 << ((bit) % BITS_PER_LONG))) - -struct fd_info { - int fd; - ev_callback cb; - void *data; -}; - -static int epollfd; -static struct epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS]; -static int npolledevents; - -static struct fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS]; - -static unsigned ev_count = 0; -static unsigned ev_dev_count = 0; -static unsigned ev_misc_count = 0; - -int ev_init(ev_callback input_cb, void *data) -{ - DIR *dir; - struct dirent *de; - int fd; - struct epoll_event ev; - bool epollctlfail = false; - - epollfd = epoll_create(MAX_DEVICES + MAX_MISC_FDS); - if (epollfd == -1) - return -1; - - dir = opendir("/dev/input"); - if(dir != 0) { - while((de = readdir(dir))) { - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; - -// fprintf(stderr,"/dev/input/%s\n", de->d_name); - if(strncmp(de->d_name,"event",5)) continue; - fd = openat(dirfd(dir), de->d_name, O_RDONLY); - if(fd < 0) continue; - - /* read the evbits of the input device */ - if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0) { - close(fd); - continue; - } - - /* 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)) { - close(fd); - continue; - } - - ev.events = EPOLLIN | EPOLLWAKEUP; - ev.data.ptr = (void *)&ev_fdinfo[ev_count]; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev)) { - close(fd); - epollctlfail = true; - continue; - } - - ev_fdinfo[ev_count].fd = fd; - ev_fdinfo[ev_count].cb = input_cb; - ev_fdinfo[ev_count].data = data; - ev_count++; - ev_dev_count++; - if(ev_dev_count == MAX_DEVICES) break; - } - } - - if (epollctlfail && !ev_count) { - close(epollfd); - epollfd = -1; - return -1; - } - - return 0; -} - -int ev_add_fd(int fd, ev_callback cb, void *data) -{ - struct epoll_event ev; - int ret; - - if (ev_misc_count == MAX_MISC_FDS || cb == NULL) - return -1; - - ev.events = EPOLLIN | EPOLLWAKEUP; - ev.data.ptr = (void *)&ev_fdinfo[ev_count]; - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); - if (!ret) { - ev_fdinfo[ev_count].fd = fd; - ev_fdinfo[ev_count].cb = cb; - ev_fdinfo[ev_count].data = data; - ev_count++; - ev_misc_count++; - } - - return ret; -} - -int ev_get_epollfd(void) -{ - return epollfd; -} - -void ev_exit(void) -{ - while (ev_count > 0) { - close(ev_fdinfo[--ev_count].fd); - } - ev_misc_count = 0; - ev_dev_count = 0; - close(epollfd); -} - -int ev_wait(int timeout) -{ - npolledevents = epoll_wait(epollfd, polledevents, ev_count, timeout); - if (npolledevents <= 0) - return -1; - return 0; -} - -void ev_dispatch(void) -{ - int n; - int ret; - - for (n = 0; n < npolledevents; n++) { - struct fd_info *fdi = polledevents[n].data.ptr; - ev_callback cb = fdi->cb; - if (cb) - cb(fdi->fd, polledevents[n].events, fdi->data); - } -} - -int ev_get_input(int fd, uint32_t epevents, struct input_event *ev) -{ - int r; - - if (epevents & EPOLLIN) { - r = read(fd, ev, sizeof(*ev)); - if (r == sizeof(*ev)) - return 0; - } - return -1; -} - -int ev_sync_key_state(ev_set_key_callback set_key_cb, void *data) -{ - unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; - unsigned i; - int ret; - - for (i = 0; i < ev_dev_count; i++) { - int code; - - memset(key_bits, 0, sizeof(key_bits)); - memset(ev_bits, 0, sizeof(ev_bits)); - - ret = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits); - if (ret < 0 || !test_bit(EV_KEY, ev_bits)) - continue; - - ret = ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits); - if (ret < 0) - continue; - - for (code = 0; code <= KEY_MAX; code++) { - if (test_bit(code, key_bits)) - set_key_cb(code, 1, data); - } - } - - return 0; -} diff --git a/minui/events.cpp b/minui/events.cpp new file mode 100644 index 0000000..2d47a58 --- /dev/null +++ b/minui/events.cpp @@ -0,0 +1,231 @@ +/* + * 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 <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <unistd.h> + +#include <linux/input.h> + +#include "minui.h" + +#define MAX_DEVICES 16 +#define MAX_MISC_FDS 16 + +#define BITS_PER_LONG (sizeof(unsigned long) * 8) +#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG) + +struct fd_info { + int fd; + ev_callback cb; + void* data; +}; + +static int g_epoll_fd; +static epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS]; +static int npolledevents; + +static fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS]; + +static unsigned ev_count = 0; +static unsigned ev_dev_count = 0; +static unsigned ev_misc_count = 0; + +static bool test_bit(size_t bit, unsigned long* array) { + return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0; +} + +int ev_init(ev_callback input_cb, void* data) { + bool epollctlfail = false; + + g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS); + if (g_epoll_fd == -1) { + return -1; + } + + DIR* dir = opendir("/dev/input"); + if (dir != NULL) { + dirent* de; + while ((de = readdir(dir))) { + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; + +// fprintf(stderr,"/dev/input/%s\n", de->d_name); + if (strncmp(de->d_name, "event", 5)) continue; + int fd = openat(dirfd(dir), de->d_name, O_RDONLY); + if (fd == -1) continue; + + // Read the evbits of the input device. + if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + close(fd); + continue; + } + + // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. + if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { + close(fd); + continue; + } + + epoll_event ev; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = &ev_fdinfo[ev_count]; + if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + close(fd); + epollctlfail = true; + continue; + } + + ev_fdinfo[ev_count].fd = fd; + ev_fdinfo[ev_count].cb = input_cb; + ev_fdinfo[ev_count].data = data; + ev_count++; + ev_dev_count++; + if (ev_dev_count == MAX_DEVICES) break; + } + + closedir(dir); + } + + if (epollctlfail && !ev_count) { + close(g_epoll_fd); + g_epoll_fd = -1; + return -1; + } + + return 0; +} + +int ev_get_epollfd(void) { + return g_epoll_fd; +} + +int ev_add_fd(int fd, ev_callback cb, void* data) { + if (ev_misc_count == MAX_MISC_FDS || cb == NULL) { + return -1; + } + + epoll_event ev; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = (void *)&ev_fdinfo[ev_count]; + int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (!ret) { + ev_fdinfo[ev_count].fd = fd; + ev_fdinfo[ev_count].cb = cb; + ev_fdinfo[ev_count].data = data; + ev_count++; + ev_misc_count++; + } + + return ret; +} + +void ev_exit(void) { + while (ev_count > 0) { + close(ev_fdinfo[--ev_count].fd); + } + ev_misc_count = 0; + ev_dev_count = 0; + close(g_epoll_fd); +} + +int ev_wait(int timeout) { + npolledevents = epoll_wait(g_epoll_fd, polledevents, ev_count, timeout); + if (npolledevents <= 0) { + return -1; + } + return 0; +} + +void ev_dispatch(void) { + for (int n = 0; n < npolledevents; n++) { + fd_info* fdi = reinterpret_cast<fd_info*>(polledevents[n].data.ptr); + ev_callback cb = fdi->cb; + if (cb) { + cb(fdi->fd, polledevents[n].events, fdi->data); + } + } +} + +int ev_get_input(int fd, uint32_t epevents, input_event* ev) { + if (epevents & EPOLLIN) { + ssize_t r = read(fd, ev, sizeof(*ev)); + if (r == sizeof(*ev)) { + return 0; + } + } + return -1; +} + +int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data) { + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; + unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; + + for (size_t i = 0; i < ev_dev_count; ++i) { + memset(ev_bits, 0, sizeof(ev_bits)); + memset(key_bits, 0, sizeof(key_bits)); + + if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + continue; + } + if (!test_bit(EV_KEY, ev_bits)) { + continue; + } + if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) { + continue; + } + + for (int code = 0; code <= KEY_MAX; code++) { + if (test_bit(code, key_bits)) { + set_key_cb(code, 1, data); + } + } + } + + return 0; +} + +void ev_iterate_available_keys(std::function<void(int)> f) { + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; + unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; + + for (size_t i = 0; i < ev_dev_count; ++i) { + memset(ev_bits, 0, sizeof(ev_bits)); + memset(key_bits, 0, sizeof(key_bits)); + + // Does this device even have keys? + if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + continue; + } + if (!test_bit(EV_KEY, ev_bits)) { + continue; + } + + int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits); + if (rc == -1) { + continue; + } + + for (int key_code = 0; key_code <= KEY_MAX; ++key_code) { + if (test_bit(key_code, key_bits)) { + f(key_code); + } + } + } +} diff --git a/minui/graphics.c b/minui/graphics.cpp index 6049d85..f09f1c6 100644 --- a/minui/graphics.c +++ b/minui/graphics.cpp @@ -16,6 +16,7 @@ #include <stdbool.h> #include <stdlib.h> +#include <string.h> #include <unistd.h> #include <fcntl.h> @@ -34,11 +35,11 @@ #include "minui.h" #include "graphics.h" -typedef struct { +struct GRFont { GRSurface* texture; int cwidth; int cheight; -} GRFont; +}; static GRFont* gr_font = NULL; static minui_backend* gr_backend = NULL; @@ -47,8 +48,6 @@ static int overscan_percent = OVERSCAN_PERCENT; static int overscan_offset_x = 0; static int overscan_offset_y = 0; -static int gr_vt_fd = -1; - static unsigned char gr_current_r = 255; static unsigned char gr_current_g = 255; static unsigned char gr_current_b = 255; @@ -76,11 +75,10 @@ static void text_blend(unsigned char* src_p, int src_row_bytes, unsigned char* dst_p, int dst_row_bytes, int width, int height) { - int i, j; - for (j = 0; j < height; ++j) { + for (int j = 0; j < height; ++j) { unsigned char* sx = src_p; unsigned char* px = dst_p; - for (i = 0; i < width; ++i) { + for (int i = 0; i < width; ++i) { unsigned char a = *sx++; if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255; if (a == 255) { @@ -105,34 +103,33 @@ static void text_blend(unsigned char* src_p, int src_row_bytes, } } - -void gr_text(int x, int y, const char *s, int bold) +void gr_text(int x, int y, const char *s, bool bold) { - GRFont *font = gr_font; - unsigned off; + GRFont* font = gr_font; - if (!font->texture) return; - if (gr_current_a == 0) return; + if (!font->texture || gr_current_a == 0) return; bold = bold && (font->texture->height != font->cheight); x += overscan_offset_x; y += overscan_offset_y; - while((off = *s++)) { - off -= 32; + unsigned char ch; + while ((ch = *s++)) { if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break; - if (off < 96) { - unsigned char* src_p = font->texture->data + (off * font->cwidth) + - (bold ? font->cheight * font->texture->row_bytes : 0); - unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes; + if (ch < ' ' || ch > '~') { + ch = '?'; + } - text_blend(src_p, font->texture->row_bytes, - dst_p, gr_draw->row_bytes, - font->cwidth, font->cheight); + unsigned char* src_p = font->texture->data + ((ch - ' ') * font->cwidth) + + (bold ? font->cheight * font->texture->row_bytes : 0); + unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes; + + text_blend(src_p, font->texture->row_bytes, + dst_p, gr_draw->row_bytes, + font->cwidth, font->cheight); - } x += font->cwidth; } } @@ -160,22 +157,27 @@ void gr_texticon(int x, int y, GRSurface* icon) { void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + gr_current_r = b; + gr_current_g = g; + gr_current_b = r; + gr_current_a = a; +#else gr_current_r = r; gr_current_g = g; gr_current_b = b; gr_current_a = a; +#endif } void gr_clear() { - if (gr_current_r == gr_current_g && - gr_current_r == gr_current_b) { + if (gr_current_r == gr_current_g && gr_current_r == gr_current_b) { memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes); } else { - int x, y; unsigned char* px = gr_draw->data; - for (y = 0; y < gr_draw->height; ++y) { - for (x = 0; x < gr_draw->width; ++x) { + for (int y = 0; y < gr_draw->height; ++y) { + for (int x = 0; x < gr_draw->width; ++x) { *px++ = gr_current_r; *px++ = gr_current_g; *px++ = gr_current_b; @@ -267,7 +269,7 @@ unsigned int gr_get_height(GRSurface* surface) { static void gr_init_font(void) { - gr_font = calloc(sizeof(*gr_font), 1); + gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1)); int res = res_create_alpha_surface("font", &(gr_font->texture)); if (res == 0) { @@ -280,14 +282,14 @@ static void gr_init_font(void) printf("failed to read font: res=%d\n", res); // fall back to the compiled-in font. - gr_font->texture = malloc(sizeof(*gr_font->texture)); + gr_font->texture = reinterpret_cast<GRSurface*>(malloc(sizeof(*gr_font->texture))); gr_font->texture->width = font.width; gr_font->texture->height = font.height; gr_font->texture->row_bytes = font.width; gr_font->texture->pixel_bytes = 1; - unsigned char* bits = malloc(font.width * font.height); - gr_font->texture->data = (void*) bits; + unsigned char* bits = reinterpret_cast<unsigned char*>(malloc(font.width * font.height)); + gr_font->texture->data = reinterpret_cast<unsigned char*>(bits); unsigned char data; unsigned char* in = font.rundata; @@ -324,7 +326,7 @@ static void gr_test() { gr_clear(); gr_color(255, 0, 0, 255); - gr_surface frame = images[x%frames]; + GRSurface* frame = images[x%frames]; gr_blit(frame, 0, 0, frame->width, frame->height, x, 0); gr_color(255, 0, 0, 128); @@ -358,17 +360,6 @@ int gr_init(void) { gr_init_font(); - gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC); - if (gr_vt_fd < 0) { - // This is non-fatal; post-Cupcake kernels don't have tty0. - perror("can't open /dev/tty0"); - } else if (ioctl(gr_vt_fd, KDSETMODE, (void*) KD_GRAPHICS)) { - // However, if we do open tty0, we expect the ioctl to work. - perror("failed KDSETMODE to KD_GRAPHICS on tty0"); - gr_exit(); - return -1; - } - gr_backend = open_adf(); if (gr_backend) { gr_draw = gr_backend->init(gr_backend); @@ -397,10 +388,6 @@ int gr_init(void) void gr_exit(void) { gr_backend->exit(gr_backend); - - ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT); - close(gr_vt_fd); - gr_vt_fd = -1; } int gr_fb_width(void) diff --git a/minui/graphics.h b/minui/graphics.h index 993e986..81a9233 100644 --- a/minui/graphics.h +++ b/minui/graphics.h @@ -17,34 +17,26 @@ #ifndef _GRAPHICS_H_ #define _GRAPHICS_H_ -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdbool.h> #include "minui.h" -typedef struct minui_backend { - // Initializes the backend and returns a gr_surface to draw into. - gr_surface (*init)(struct minui_backend*); +// TODO: lose the function pointers. +struct minui_backend { + // Initializes the backend and returns a GRSurface* to draw into. + GRSurface* (*init)(minui_backend*); // Causes the current drawing surface (returned by the most recent // call to flip() or init()) to be displayed, and returns a new // drawing surface. - gr_surface (*flip)(struct minui_backend*); + GRSurface* (*flip)(minui_backend*); // Blank (or unblank) the screen. - void (*blank)(struct minui_backend*, bool); + void (*blank)(minui_backend*, bool); // Device cleanup when drawing is done. - void (*exit)(struct minui_backend*); -} minui_backend; + void (*exit)(minui_backend*); +}; minui_backend* open_fbdev(); minui_backend* open_adf(); -#ifdef __cplusplus -} -#endif - #endif diff --git a/minui/graphics_adf.c b/minui/graphics_adf.cpp index ac6d64e..5d0867f 100644 --- a/minui/graphics_adf.c +++ b/minui/graphics_adf.cpp @@ -19,6 +19,7 @@ #include <stdbool.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <unistd.h> #include <sys/cdefs.h> @@ -43,15 +44,13 @@ struct adf_pdata { unsigned int current_surface; unsigned int n_surfaces; - struct adf_surface_pdata surfaces[2]; + adf_surface_pdata surfaces[2]; }; -static gr_surface adf_flip(struct minui_backend *backend); -static void adf_blank(struct minui_backend *backend, bool blank); +static GRSurface* adf_flip(minui_backend *backend); +static void adf_blank(minui_backend *backend, bool blank); -static int adf_surface_init(struct adf_pdata *pdata, - struct drm_mode_modeinfo *mode, struct adf_surface_pdata *surf) -{ +static int adf_surface_init(adf_pdata *pdata, drm_mode_modeinfo *mode, adf_surface_pdata *surf) { memset(surf, 0, sizeof(*surf)); surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay, @@ -64,8 +63,9 @@ static int adf_surface_init(struct adf_pdata *pdata, surf->base.row_bytes = surf->pitch; surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4; - surf->base.data = mmap(NULL, surf->pitch * surf->base.height, PROT_WRITE, - MAP_SHARED, surf->fd, surf->offset); + surf->base.data = reinterpret_cast<uint8_t*>(mmap(NULL, + surf->pitch * surf->base.height, PROT_WRITE, + MAP_SHARED, surf->fd, surf->offset)); if (surf->base.data == MAP_FAILED) { close(surf->fd); return -errno; @@ -74,9 +74,9 @@ static int adf_surface_init(struct adf_pdata *pdata, return 0; } -static int adf_interface_init(struct adf_pdata *pdata) +static int adf_interface_init(adf_pdata *pdata) { - struct adf_interface_data intf_data; + adf_interface_data intf_data; int ret = 0; int err; @@ -106,7 +106,7 @@ done: return ret; } -static int adf_device_init(struct adf_pdata *pdata, struct adf_device *dev) +static int adf_device_init(adf_pdata *pdata, adf_device *dev) { adf_id_t intf_id; int intf_fd; @@ -134,14 +134,16 @@ static int adf_device_init(struct adf_pdata *pdata, struct adf_device *dev) return err; } -static gr_surface adf_init(minui_backend *backend) +static GRSurface* adf_init(minui_backend *backend) { - struct adf_pdata *pdata = (struct adf_pdata *)backend; + adf_pdata *pdata = (adf_pdata *)backend; adf_id_t *dev_ids = NULL; ssize_t n_dev_ids, i; - gr_surface ret; + GRSurface* ret; -#if defined(RECOVERY_BGRA) +#if defined(RECOVERY_ABGR) + pdata->format = DRM_FORMAT_ABGR8888; +#elif defined(RECOVERY_BGRA) pdata->format = DRM_FORMAT_BGRA8888; #elif defined(RECOVERY_RGBX) pdata->format = DRM_FORMAT_RGBX8888; @@ -161,7 +163,7 @@ static gr_surface adf_init(minui_backend *backend) pdata->intf_fd = -1; for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) { - struct adf_device dev; + adf_device dev; int err = adf_device_open(dev_ids[i], O_RDWR, &dev); if (err < 0) { @@ -191,10 +193,10 @@ static gr_surface adf_init(minui_backend *backend) return ret; } -static gr_surface adf_flip(struct minui_backend *backend) +static GRSurface* adf_flip(minui_backend *backend) { - struct adf_pdata *pdata = (struct adf_pdata *)backend; - struct adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface]; + adf_pdata *pdata = (adf_pdata *)backend; + adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface]; int fence_fd = adf_interface_simple_post(pdata->intf_fd, pdata->eng_id, surf->base.width, surf->base.height, pdata->format, surf->fd, @@ -206,22 +208,22 @@ static gr_surface adf_flip(struct minui_backend *backend) return &pdata->surfaces[pdata->current_surface].base; } -static void adf_blank(struct minui_backend *backend, bool blank) +static void adf_blank(minui_backend *backend, bool blank) { - struct adf_pdata *pdata = (struct adf_pdata *)backend; + adf_pdata *pdata = (adf_pdata *)backend; adf_interface_blank(pdata->intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON); } -static void adf_surface_destroy(struct adf_surface_pdata *surf) +static void adf_surface_destroy(adf_surface_pdata *surf) { munmap(surf->base.data, surf->pitch * surf->base.height); close(surf->fd); } -static void adf_exit(struct minui_backend *backend) +static void adf_exit(minui_backend *backend) { - struct adf_pdata *pdata = (struct adf_pdata *)backend; + adf_pdata *pdata = (adf_pdata *)backend; unsigned int i; for (i = 0; i < pdata->n_surfaces; i++) @@ -233,7 +235,7 @@ static void adf_exit(struct minui_backend *backend) minui_backend *open_adf() { - struct adf_pdata *pdata = calloc(1, sizeof(*pdata)); + adf_pdata* pdata = reinterpret_cast<adf_pdata*>(calloc(1, sizeof(*pdata))); if (!pdata) { perror("allocating adf backend failed"); return NULL; diff --git a/minui/graphics_fbdev.c b/minui/graphics_fbdev.cpp index 6df2726..997e9ca 100644 --- a/minui/graphics_fbdev.c +++ b/minui/graphics_fbdev.cpp @@ -16,6 +16,7 @@ #include <stdbool.h> #include <stdlib.h> +#include <string.h> #include <unistd.h> #include <fcntl.h> @@ -32,8 +33,8 @@ #include "minui.h" #include "graphics.h" -static gr_surface fbdev_init(minui_backend*); -static gr_surface fbdev_flip(minui_backend*); +static GRSurface* fbdev_init(minui_backend*); +static GRSurface* fbdev_flip(minui_backend*); static void fbdev_blank(minui_backend*, bool); static void fbdev_exit(minui_backend*); @@ -42,7 +43,7 @@ static bool double_buffered; static GRSurface* gr_draw = NULL; static int displayed_buffer; -static struct fb_var_screeninfo vi; +static fb_var_screeninfo vi; static int fb_fd = -1; static minui_backend my_backend = { @@ -78,18 +79,14 @@ static void set_displayed_framebuffer(unsigned n) displayed_buffer = n; } -static gr_surface fbdev_init(minui_backend* backend) { - int fd; - void *bits; - - struct fb_fix_screeninfo fi; - - fd = open("/dev/graphics/fb0", O_RDWR); - if (fd < 0) { +static GRSurface* fbdev_init(minui_backend* backend) { + int fd = open("/dev/graphics/fb0", O_RDWR); + if (fd == -1) { perror("cannot open fb0"); return NULL; } + fb_fix_screeninfo fi; if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { perror("failed to get fb0 info"); close(fd); @@ -123,7 +120,7 @@ static gr_surface fbdev_init(minui_backend* backend) { vi.green.offset, vi.green.length, vi.blue.offset, vi.blue.length); - bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + void* 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); @@ -136,7 +133,7 @@ static gr_surface fbdev_init(minui_backend* backend) { gr_framebuffer[0].height = vi.yres; gr_framebuffer[0].row_bytes = fi.line_length; gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8; - gr_framebuffer[0].data = bits; + gr_framebuffer[0].data = reinterpret_cast<uint8_t*>(bits); memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes); /* check if we can use double buffering */ @@ -177,7 +174,7 @@ static gr_surface fbdev_init(minui_backend* backend) { return gr_draw; } -static gr_surface fbdev_flip(minui_backend* backend __unused) { +static GRSurface* fbdev_flip(minui_backend* backend __unused) { if (double_buffered) { #if defined(RECOVERY_BGRA) // In case of BGRA, do some byte swapping @@ -198,21 +195,8 @@ static gr_surface fbdev_flip(minui_backend* backend __unused) { set_displayed_framebuffer(1-displayed_buffer); } else { // Copy from the in-memory surface to the framebuffer. - -#if defined(RECOVERY_BGRA) - unsigned int idx; - unsigned char* ucfb_vaddr = (unsigned char*)gr_framebuffer[0].data; - unsigned char* ucbuffer_vaddr = (unsigned char*)gr_draw->data; - for (idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes); idx += 4) { - ucfb_vaddr[idx ] = ucbuffer_vaddr[idx + 2]; - ucfb_vaddr[idx + 1] = ucbuffer_vaddr[idx + 1]; - ucfb_vaddr[idx + 2] = ucbuffer_vaddr[idx ]; - ucfb_vaddr[idx + 3] = ucbuffer_vaddr[idx + 3]; - } -#else memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes); -#endif } return gr_draw; } diff --git a/minui/minui.h b/minui/minui.h index 733b675..bdde083 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -19,67 +19,70 @@ #include <sys/types.h> -#include <stdbool.h> +#include <functional> -#ifdef __cplusplus -extern "C" { -#endif +// +// Graphics. +// -typedef struct { +struct GRSurface { int width; int height; int row_bytes; int pixel_bytes; unsigned char* data; -} GRSurface; +}; -typedef GRSurface* gr_surface; +int gr_init(); +void gr_exit(); -int gr_init(void); -void gr_exit(void); +int gr_fb_width(); +int gr_fb_height(); -int gr_fb_width(void); -int gr_fb_height(void); - -void gr_flip(void); +void gr_flip(); void gr_fb_blank(bool blank); void gr_clear(); // clear entire surface to current color void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); void gr_fill(int x1, int y1, int x2, int y2); -void gr_text(int x, int y, const char *s, int bold); -void gr_texticon(int x, int y, gr_surface icon); +void gr_text(int x, int y, const char *s, bool bold); +void gr_texticon(int x, int y, GRSurface* icon); int gr_measure(const char *s); void gr_font_size(int *x, int *y); -void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy); -unsigned int gr_get_width(gr_surface surface); -unsigned int gr_get_height(gr_surface surface); +void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy); +unsigned int gr_get_width(GRSurface* surface); +unsigned int gr_get_height(GRSurface* surface); + +// +// Input events. +// -// input event structure, include <linux/input.h> for the definition. -// see http://www.mjmwired.net/kernel/Documentation/input/ for info. struct input_event; -typedef int (*ev_callback)(int fd, uint32_t epevents, void *data); -typedef int (*ev_set_key_callback)(int code, int value, void *data); +// TODO: move these over to std::function. +typedef int (*ev_callback)(int fd, uint32_t epevents, void* data); +typedef int (*ev_set_key_callback)(int code, int value, void* data); -int ev_init(ev_callback input_cb, void *data); -void ev_exit(void); -int ev_add_fd(int fd, ev_callback cb, void *data); -int ev_sync_key_state(ev_set_key_callback set_key_cb, void *data); +int ev_init(ev_callback input_cb, void* data); +void ev_exit(); +int ev_add_fd(int fd, ev_callback cb, void* data); +void ev_iterate_available_keys(std::function<void(int)> f); +int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data); -/* timeout has the same semantics as for poll - * 0 : don't block - * < 0 : block forever - * > 0 : block for 'timeout' milliseconds - */ +// 'timeout' has the same semantics as poll(2). +// 0 : don't block +// < 0 : block forever +// > 0 : block for 'timeout' milliseconds int ev_wait(int timeout); -int ev_get_input(int fd, uint32_t epevents, struct input_event *ev); -void ev_dispatch(void); -int ev_get_epollfd(void); +int ev_get_input(int fd, uint32_t epevents, input_event* ev); +void ev_dispatch(); +int ev_get_epollfd(); +// // Resources +// // res_create_*_surface() functions return 0 if no error, else // negative. @@ -92,17 +95,17 @@ int ev_get_epollfd(void); // All these functions load PNG images from "/res/images/${name}.png". // Load a single display surface from a PNG image. -int res_create_display_surface(const char* name, gr_surface* pSurface); +int res_create_display_surface(const char* name, GRSurface** pSurface); // Load an array of display surfaces from a single PNG image. The PNG // should have a 'Frames' text chunk whose value is the number of // frames this image represents. The pixel data itself is interlaced // by row. int res_create_multi_display_surface(const char* name, - int* frames, gr_surface** pSurface); + int* frames, GRSurface*** pSurface); // Load a single alpha surface from a grayscale PNG image. -int res_create_alpha_surface(const char* name, gr_surface* pSurface); +int res_create_alpha_surface(const char* name, GRSurface** pSurface); // Load part of a grayscale PNG image that is the first match for the // given locale. The image is expected to be a composite of multiple @@ -111,14 +114,10 @@ int res_create_alpha_surface(const char* name, gr_surface* pSurface); // development/tools/recovery_l10n for an app that will generate these // specialized images from Android resources. int res_create_localized_alpha_surface(const char* name, const char* locale, - gr_surface* pSurface); + GRSurface** pSurface); // Free a surface allocated by any of the res_create_*_surface() // functions. -void res_free_surface(gr_surface surface); - -#ifdef __cplusplus -} -#endif +void res_free_surface(GRSurface* surface); #endif diff --git a/minui/resources.c b/minui/resources.cpp index 2bae4de..5e47892 100644 --- a/minui/resources.c +++ b/minui/resources.cpp @@ -15,6 +15,7 @@ */ #include <stdlib.h> +#include <string.h> #include <unistd.h> #include <fcntl.h> @@ -35,10 +36,11 @@ extern char* locale; #define SURFACE_DATA_ALIGNMENT 8 -static gr_surface malloc_surface(size_t data_size) { - unsigned char* temp = malloc(sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT); +static GRSurface* malloc_surface(size_t data_size) { + size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT; + unsigned char* temp = reinterpret_cast<unsigned char*>(malloc(size)); if (temp == NULL) return NULL; - gr_surface surface = (gr_surface) temp; + GRSurface* surface = reinterpret_cast<GRSurface*>(temp); surface->data = temp + sizeof(GRSurface) + (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT)); return surface; @@ -49,6 +51,8 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, char resPath[256]; unsigned char header[8]; int result = 0; + int color_type, bit_depth; + size_t bytesRead; snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name); resPath[sizeof(resPath)-1] = '\0'; @@ -58,7 +62,7 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, goto exit; } - size_t bytesRead = fread(header, 1, sizeof(header), fp); + bytesRead = fread(header, 1, sizeof(header), fp); if (bytesRead != sizeof(header)) { result = -2; goto exit; @@ -90,7 +94,6 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, png_set_sig_bytes(*png_ptr, sizeof(header)); png_read_info(*png_ptr, *info_ptr); - int color_type, bit_depth; png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth, &color_type, NULL, NULL, NULL); @@ -135,12 +138,10 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, // framebuffer pixel format; they need to be modified if the // framebuffer format changes (but nothing else should). -// Allocate and return a gr_surface sufficient for storing an image of +// Allocate and return a GRSurface* sufficient for storing an image of // the indicated size in the framebuffer pixel format. -static gr_surface init_display_surface(png_uint_32 width, png_uint_32 height) { - gr_surface surface; - - surface = malloc_surface(width * height * 4); +static GRSurface* init_display_surface(png_uint_32 width, png_uint_32 height) { + GRSurface* surface = malloc_surface(width * height * 4); if (surface == NULL) return NULL; surface->width = width; @@ -196,13 +197,15 @@ static void transform_rgb_to_draw(unsigned char* input_row, } } -int res_create_display_surface(const char* name, gr_surface* pSurface) { - gr_surface surface = NULL; +int res_create_display_surface(const char* name, GRSurface** pSurface) { + GRSurface* surface = NULL; int result = 0; png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_uint_32 width, height; png_byte channels; + unsigned char* p_row; + unsigned int y; *pSurface = NULL; @@ -215,8 +218,11 @@ int res_create_display_surface(const char* name, gr_surface* pSurface) { goto exit; } - unsigned char* p_row = malloc(width * 4); - unsigned int y; +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + png_set_bgr(png_ptr); +#endif + + p_row = reinterpret_cast<unsigned char*>(malloc(width * 4)); for (y = 0; y < height; ++y) { png_read_row(png_ptr, p_row, NULL); transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width); @@ -231,14 +237,18 @@ int res_create_display_surface(const char* name, gr_surface* pSurface) { return result; } -int res_create_multi_display_surface(const char* name, int* frames, gr_surface** pSurface) { - gr_surface* surface = NULL; +int res_create_multi_display_surface(const char* name, int* frames, GRSurface*** pSurface) { + GRSurface** surface = NULL; int result = 0; png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_uint_32 width, height; png_byte channels; int i; + png_textp text; + int num_text; + unsigned char* p_row; + unsigned int y; *pSurface = NULL; *frames = -1; @@ -247,8 +257,6 @@ int res_create_multi_display_surface(const char* name, int* frames, gr_surface** if (result < 0) return result; *frames = 1; - png_textp text; - int num_text; if (png_get_text(png_ptr, info_ptr, &text, &num_text)) { for (i = 0; i < num_text; ++i) { if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) { @@ -265,7 +273,7 @@ int res_create_multi_display_surface(const char* name, int* frames, gr_surface** goto exit; } - surface = malloc(*frames * sizeof(gr_surface)); + surface = reinterpret_cast<GRSurface**>(malloc(*frames * sizeof(GRSurface*))); if (surface == NULL) { result = -8; goto exit; @@ -278,8 +286,11 @@ int res_create_multi_display_surface(const char* name, int* frames, gr_surface** } } - unsigned char* p_row = malloc(width * 4); - unsigned int y; +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + png_set_bgr(png_ptr); +#endif + + p_row = reinterpret_cast<unsigned char*>(malloc(width * 4)); for (y = 0; y < height; ++y) { png_read_row(png_ptr, p_row, NULL); int frame = y % *frames; @@ -289,7 +300,7 @@ int res_create_multi_display_surface(const char* name, int* frames, gr_surface** } free(p_row); - *pSurface = (gr_surface*) surface; + *pSurface = reinterpret_cast<GRSurface**>(surface); exit: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); @@ -305,8 +316,8 @@ exit: return result; } -int res_create_alpha_surface(const char* name, gr_surface* pSurface) { - gr_surface surface = NULL; +int res_create_alpha_surface(const char* name, GRSurface** pSurface) { + GRSurface* surface = NULL; int result = 0; png_structp png_ptr = NULL; png_infop info_ptr = NULL; @@ -333,6 +344,10 @@ int res_create_alpha_surface(const char* name, gr_surface* pSurface) { surface->row_bytes = width; surface->pixel_bytes = 1; +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + png_set_bgr(png_ptr); +#endif + unsigned char* p_row; unsigned int y; for (y = 0; y < height; ++y) { @@ -367,13 +382,15 @@ static int matches_locale(const char* loc, const char* locale) { int res_create_localized_alpha_surface(const char* name, const char* locale, - gr_surface* pSurface) { - gr_surface surface = NULL; + GRSurface** pSurface) { + GRSurface* surface = NULL; int result = 0; png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_uint_32 width, height; png_byte channels; + unsigned char* row; + png_uint_32 y; *pSurface = NULL; @@ -394,8 +411,7 @@ int res_create_localized_alpha_surface(const char* name, goto exit; } - unsigned char* row = malloc(width); - png_uint_32 y; + row = reinterpret_cast<unsigned char*>(malloc(width)); for (y = 0; y < height; ++y) { png_read_row(png_ptr, row, NULL); int w = (row[1] << 8) | row[0]; @@ -422,7 +438,7 @@ int res_create_localized_alpha_surface(const char* name, memcpy(surface->data + i*w, row, w); } - *pSurface = (gr_surface) surface; + *pSurface = reinterpret_cast<GRSurface*>(surface); break; } else { int i; @@ -438,6 +454,6 @@ exit: return result; } -void res_free_surface(gr_surface surface) { +void res_free_surface(GRSurface* surface) { free(surface); } diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c index fe2c880..97cb2e0 100644 --- a/minzip/DirUtil.c +++ b/minzip/DirUtil.c @@ -85,7 +85,7 @@ dirCreateHierarchy(const char *path, int mode, c--; } if (c == cpath) { -//xxx test this path + //xxx test this path /* No directory component. Act like the path was empty. */ errno = ENOENT; @@ -206,7 +206,7 @@ dirUnlinkHierarchy(const char *path) /* recurse over components */ errno = 0; while ((de = readdir(dir)) != NULL) { -//TODO: don't blow the stack + //TODO: don't blow the stack char dn[PATH_MAX]; if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) { continue; diff --git a/minzip/Hash.c b/minzip/Hash.c index 8c6ca9b..8f8ed68 100644 --- a/minzip/Hash.c +++ b/minzip/Hash.c @@ -140,7 +140,6 @@ static bool resizeHash(HashTable* pHashTable, int newSize) int i; assert(countTombStones(pHashTable) == pHashTable->numDeadEntries); - //LOGI("before: dead=%d\n", pHashTable->numDeadEntries); pNewEntries = (HashEntry*) calloc(newSize, sizeof(HashTable)); if (pNewEntries == NULL) @@ -196,7 +195,6 @@ void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item (*cmpFunc)(pEntry->data, item) == 0) { /* match */ - //LOGD("+++ match on entry %d\n", pEntry - pHashTable->pEntries); break; } @@ -206,8 +204,6 @@ void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item break; /* edge case - single-entry table */ pEntry = pHashTable->pEntries; } - - //LOGI("+++ look probing %d...\n", pEntry - pHashTable->pEntries); } if (pEntry->data == NULL) { @@ -228,10 +224,6 @@ void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item abort(); } /* note "pEntry" is now invalid */ - } else { - //LOGW("okay %d/%d/%d\n", - // pHashTable->numEntries, pHashTable->tableSize, - // (pHashTable->tableSize * LOAD_NUMER) / LOAD_DENOM); } /* full table is bad -- search for nonexistent never halts */ @@ -264,7 +256,6 @@ bool mzHashTableRemove(HashTable* pHashTable, unsigned int itemHash, void* item) pEnd = &pHashTable->pEntries[pHashTable->tableSize]; while (pEntry->data != NULL) { if (pEntry->data == item) { - //LOGI("+++ stepping on entry %d\n", pEntry - pHashTable->pEntries); pEntry->data = HASH_TOMBSTONE; pHashTable->numEntries--; pHashTable->numDeadEntries++; @@ -277,8 +268,6 @@ bool mzHashTableRemove(HashTable* pHashTable, unsigned int itemHash, void* item) break; /* edge case - single-entry table */ pEntry = pHashTable->pEntries; } - - //LOGI("+++ del probing %d...\n", pEntry - pHashTable->pEntries); } return false; diff --git a/minzip/Zip.c b/minzip/Zip.c index 70aff00..d3ff79b 100644 --- a/minzip/Zip.c +++ b/minzip/Zip.c @@ -327,10 +327,6 @@ static bool parseZipArchive(ZipArchive* pArchive) #else pEntry = &pArchive->pEntries[i]; #endif - - //LOGI("%d: localHdr=%d fnl=%d el=%d cl=%d\n", - // i, localHdrOffset, fileNameLen, extraLen, commentLen); - pEntry->fileNameLen = fileNameLen; pEntry->fileName = fileName; @@ -488,7 +484,7 @@ const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive, /* * Return true if the entry is a symbolic link. */ -bool mzIsZipEntrySymlink(const ZipEntry* pEntry) +static bool mzIsZipEntrySymlink(const ZipEntry* pEntry) { if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) { return S_ISLNK(pEntry->externalFileAttributes >> 16); @@ -632,30 +628,6 @@ static bool crcProcessFunction(const unsigned char *data, int dataLen, return true; } -/* - * Check the CRC on this entry; return true if it is correct. - * May do other internal checks as well. - */ -bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry) -{ - unsigned long crc; - bool ret; - - crc = crc32(0L, Z_NULL, 0); - ret = mzProcessZipEntryContents(pArchive, pEntry, crcProcessFunction, - (void *)&crc); - if (!ret) { - LOGE("Can't calculate CRC for entry\n"); - return false; - } - if (crc != (unsigned long)pEntry->crc32) { - LOGW("CRC for entry %.*s (0x%08lx) != expected (0x%08lx)\n", - pEntry->fileNameLen, pEntry->fileName, crc, pEntry->crc32); - return false; - } - return true; -} - typedef struct { char *buf; int bufLen; @@ -737,23 +709,6 @@ bool mzExtractZipEntryToFile(const ZipArchive *pArchive, return true; } -/* - * Obtain a pointer to the in-memory representation of a stored entry. - */ -bool mzGetStoredEntry(const ZipArchive *pArchive, - const ZipEntry *pEntry, unsigned char **addr, size_t *length) -{ - if (pEntry->compression != STORED) { - LOGE("Can't getStoredEntry for '%s'; not stored\n", - pEntry->fileName); - return false; - } - - *addr = pArchive->addr + pEntry->offset; - *length = pEntry->uncompLen; - return true; -} - typedef struct { unsigned char* buffer; long len; @@ -873,7 +828,7 @@ 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, + const struct utimbuf *timestamp, void (*callback)(const char *fn, void *), void *cookie, struct selabel_handle *sehnd) { @@ -923,8 +878,8 @@ bool mzExtractRecursive(const ZipArchive *pArchive, /* Walk through the entries and extract anything whose path begins * with zpath. -//TODO: since the entries are sorted, binary search for the first match -// and stop after the first non-match. + //TODO: since the entries are sorted, binary search for the first match + // and stop after the first non-match. */ unsigned int i; bool seenMatch = false; @@ -933,10 +888,10 @@ bool mzExtractRecursive(const ZipArchive *pArchive, for (i = 0; i < pArchive->numEntries; i++) { ZipEntry *pEntry = pArchive->pEntries + i; if (pEntry->fileNameLen < zipDirLen) { -//TODO: look out for a single empty directory entry that matches zpath, but -// missing the trailing slash. Most zip files seem to include -// the trailing slash, but I think it's legal to leave it off. -// e.g., zpath "a/b/", entry "a/b", with no children of the entry. + //TODO: look out for a single empty directory entry that matches zpath, but + // missing the trailing slash. Most zip files seem to include + // the trailing slash, but I think it's legal to leave it off. + // e.g., zpath "a/b/", entry "a/b", with no children of the entry. /* No chance of matching. */ #if SORT_ENTRIES @@ -977,30 +932,19 @@ bool mzExtractRecursive(const ZipArchive *pArchive, break; } - /* With DRY_RUN set, invoke the callback but don't do anything else. - */ - if (flags & MZ_EXTRACT_DRY_RUN) { - if (callback != NULL) callback(targetFile, cookie); - continue; - } - - /* Create the file or directory. - */ #define UNZIP_DIRMODE 0755 #define UNZIP_FILEMODE 0644 - if (pEntry->fileName[pEntry->fileNameLen-1] == '/') { - if (!(flags & MZ_EXTRACT_FILES_ONLY)) { - int ret = dirCreateHierarchy( - targetFile, UNZIP_DIRMODE, timestamp, false, sehnd); - if (ret != 0) { - LOGE("Can't create containing directory for \"%s\": %s\n", - targetFile, strerror(errno)); - ok = false; - break; - } - LOGD("Extracted dir \"%s\"\n", targetFile); - } - } else { + /* + * Create the file or directory. We ignore directory entries + * because we recursively create paths to each file entry we encounter + * in the zip archive anyway. + * + * NOTE: A "directory entry" in a zip archive is just a zero length + * entry that ends in a "/". They're not mandatory and many tools get + * rid of them. We need to process them only if we want to preserve + * empty directories from the archive. + */ + if (pEntry->fileName[pEntry->fileNameLen-1] != '/') { /* This is not a directory. First, make sure that * the containing directory exists. */ @@ -1013,97 +957,62 @@ bool mzExtractRecursive(const ZipArchive *pArchive, break; } - /* With FILES_ONLY set, we need to ignore metadata entirely, - * so treat symlinks as regular files. + /* + * The entry is a regular file or a symlink. Open the target for writing. + * + * TODO: This behavior for symlinks seems rather bizarre. For a + * symlink foo/bar/baz -> foo/tar/taz, we will create a file called + * "foo/bar/baz" whose contents are the literal "foo/tar/taz". We + * warn about this for now and preserve older behavior. */ - if (!(flags & MZ_EXTRACT_FILES_ONLY) && mzIsZipEntrySymlink(pEntry)) { - /* The entry is a symbolic link. - * The relative target of the symlink is in the - * data section of this entry. - */ - if (pEntry->uncompLen == 0) { - LOGE("Symlink entry \"%s\" has no target\n", - targetFile); - ok = false; - break; - } - char *linkTarget = malloc(pEntry->uncompLen + 1); - if (linkTarget == NULL) { - ok = false; - break; - } - ok = mzReadZipEntry(pArchive, pEntry, linkTarget, - pEntry->uncompLen); - if (!ok) { - LOGE("Can't read symlink target for \"%s\"\n", - targetFile); - free(linkTarget); - break; - } - linkTarget[pEntry->uncompLen] = '\0'; - - /* Make the link. - */ - ret = symlink(linkTarget, targetFile); - if (ret != 0) { - LOGE("Can't symlink \"%s\" to \"%s\": %s\n", - targetFile, linkTarget, strerror(errno)); - free(linkTarget); - ok = false; - break; - } - LOGD("Extracted symlink \"%s\" -> \"%s\"\n", - targetFile, linkTarget); - free(linkTarget); - } else { - /* The entry is a regular file. - * Open the target for writing. - */ - - char *secontext = NULL; + if (mzIsZipEntrySymlink(pEntry)) { + LOGE("Symlink entry \"%.*s\" will be output as a regular file.", + pEntry->fileNameLen, pEntry->fileName); + } - if (sehnd) { - selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE); - setfscreatecon(secontext); - } + char *secontext = NULL; - int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC - , UNZIP_FILEMODE); + if (sehnd) { + selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE); + setfscreatecon(secontext); + } - if (secontext) { - freecon(secontext); - setfscreatecon(NULL); - } + int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC, + UNZIP_FILEMODE); - if (fd < 0) { - LOGE("Can't create target file \"%s\": %s\n", - targetFile, strerror(errno)); - ok = false; - break; - } + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } - bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd); - if (ok) { - ok = (fsync(fd) == 0); - } - if (close(fd) != 0) { - ok = false; - } - if (!ok) { - LOGE("Error extracting \"%s\"\n", targetFile); - ok = false; - break; - } + if (fd < 0) { + LOGE("Can't create target file \"%s\": %s\n", + targetFile, strerror(errno)); + ok = false; + break; + } - if (timestamp != NULL && utime(targetFile, timestamp)) { - LOGE("Error touching \"%s\"\n", targetFile); - ok = false; - break; - } + bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd); + if (ok) { + ok = (fsync(fd) == 0); + } + if (close(fd) != 0) { + ok = false; + } + if (!ok) { + LOGE("Error extracting \"%s\"\n", targetFile); + ok = false; + break; + } - LOGV("Extracted file \"%s\"\n", targetFile); - ++extractCount; + if (timestamp != NULL && utime(targetFile, timestamp)) { + LOGE("Error touching \"%s\"\n", targetFile); + ok = false; + break; } + + LOGV("Extracted file \"%s\"\n", targetFile); + ++extractCount; } if (callback != NULL) callback(targetFile, cookie); diff --git a/minzip/Zip.h b/minzip/Zip.h index 2054b38..86d8db5 100644 --- a/minzip/Zip.h +++ b/minzip/Zip.h @@ -85,56 +85,12 @@ void mzCloseZipArchive(ZipArchive* pArchive); const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive, const char* entryName); -/* - * Get the number of entries in the Zip archive. - */ -INLINE unsigned int mzZipEntryCount(const ZipArchive* pArchive) { - return pArchive->numEntries; -} - -/* - * Get an entry by index. Returns NULL if the index is out-of-bounds. - */ -INLINE const ZipEntry* -mzGetZipEntryAt(const ZipArchive* pArchive, unsigned int index) -{ - if (index < pArchive->numEntries) { - return pArchive->pEntries + index; - } - return NULL; -} - -/* - * Get the index number of an entry in the archive. - */ -INLINE unsigned int -mzGetZipEntryIndex(const ZipArchive *pArchive, const ZipEntry *pEntry) { - return pEntry - pArchive->pEntries; -} - -/* - * Simple accessors. - */ -INLINE UnterminatedString mzGetZipEntryFileName(const ZipEntry* pEntry) { - UnterminatedString ret; - ret.str = pEntry->fileName; - ret.len = pEntry->fileNameLen; - return ret; -} INLINE long mzGetZipEntryOffset(const ZipEntry* pEntry) { return pEntry->offset; } INLINE long mzGetZipEntryUncompLen(const ZipEntry* pEntry) { return pEntry->uncompLen; } -INLINE long mzGetZipEntryModTime(const ZipEntry* pEntry) { - return pEntry->modTime; -} -INLINE long mzGetZipEntryCrc32(const ZipEntry* pEntry) { - return pEntry->crc32; -} -bool mzIsZipEntrySymlink(const ZipEntry* pEntry); - /* * Type definition for the callback function used by @@ -164,12 +120,6 @@ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, char* buf, int bufLen); /* - * Check the CRC on this entry; return true if it is correct. - * May do other internal checks as well. - */ -bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry); - -/* * Inflate and write an entry to a file. */ bool mzExtractZipEntryToFile(const ZipArchive *pArchive, @@ -183,20 +133,12 @@ bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, const ZipEntry *pEntry, unsigned char* buffer); /* - * Return a pointer and length for a given entry. The returned region - * should be valid until pArchive is closed, and should be treated as - * read-only. - * - * Only makes sense for entries which are stored (ie, not compressed). - * No guarantees are made regarding alignment of the returned pointer. - */ -bool mzGetStoredEntry(const ZipArchive *pArchive, - const ZipEntry* pEntry, unsigned char **addr, size_t *length); - -/* - * Inflate all entries under zipDir to the directory specified by + * Inflate all files under zipDir to the directory specified by * targetDir, which must exist and be a writable directory. * + * Directory entries and symlinks are not extracted. + * + * * The immediate children of zipDir will become the immediate * children of targetDir; e.g., if the archive contains the entries * @@ -211,21 +153,15 @@ bool mzGetStoredEntry(const ZipArchive *pArchive, * /tmp/two * /tmp/d/three * - * flags is zero or more of the following: - * - * MZ_EXTRACT_FILES_ONLY - only unpack files, not directories or symlinks - * MZ_EXTRACT_DRY_RUN - don't do anything, but do invoke the callback - * * If timestamp is non-NULL, file timestamps will be set accordingly. * * If callback is non-NULL, it will be invoked with each unpacked file. * * Returns true on success, false on failure. */ -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, + const struct utimbuf *timestamp, void (*callback)(const char *fn, void*), void *cookie, struct selabel_handle *sehnd); diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c index c90fc8a..6a9b03d 100644 --- a/mtdutils/mounts.c +++ b/mtdutils/mounts.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <mntent.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -59,10 +60,8 @@ free_volume_internals(const MountedVolume *volume, int zero) int scan_mounted_volumes() { - char buf[2048]; - const char *bufp; - int fd; - ssize_t nbytes; + FILE* fp; + struct mntent* mentry; if (g_mounts_state.volumes == NULL) { const int numv = 32; @@ -84,80 +83,20 @@ scan_mounted_volumes() } g_mounts_state.volume_count = 0; - /* Open and read the file contents. - */ - fd = open(PROC_MOUNTS_FILENAME, O_RDONLY); - if (fd < 0) { - goto bail; - } - nbytes = read(fd, buf, sizeof(buf) - 1); - close(fd); - if (nbytes < 0) { - goto bail; + /* Open and read mount table entries. */ + fp = setmntent(PROC_MOUNTS_FILENAME, "r"); + if (fp == NULL) { + return -1; } - buf[nbytes] = '\0'; - - /* Parse the contents of the file, which looks like: - * - * # cat /proc/mounts - * rootfs / rootfs rw 0 0 - * /dev/pts /dev/pts devpts rw 0 0 - * /proc /proc proc rw 0 0 - * /sys /sys sysfs rw 0 0 - * /dev/block/mtdblock4 /system yaffs2 rw,nodev,noatime,nodiratime 0 0 - * /dev/block/mtdblock5 /data yaffs2 rw,nodev,noatime,nodiratime 0 0 - * /dev/block/mmcblk0p1 /sdcard vfat rw,sync,dirsync,fmask=0000,dmask=0000,codepage=cp437,iocharset=iso8859-1,utf8 0 0 - * - * The zeroes at the end are dummy placeholder fields to make the - * output match Linux's /etc/mtab, but don't represent anything here. - */ - bufp = buf; - while (nbytes > 0) { - char device[64]; - char mount_point[64]; - char filesystem[64]; - char flags[128]; - int matches; - - /* %as is a gnu extension that malloc()s a string for each field. - */ - matches = sscanf(bufp, "%63s %63s %63s %127s", - device, mount_point, filesystem, flags); - - if (matches == 4) { - device[sizeof(device)-1] = '\0'; - mount_point[sizeof(mount_point)-1] = '\0'; - filesystem[sizeof(filesystem)-1] = '\0'; - flags[sizeof(flags)-1] = '\0'; - - MountedVolume *v = - &g_mounts_state.volumes[g_mounts_state.volume_count++]; - v->device = strdup(device); - v->mount_point = strdup(mount_point); - v->filesystem = strdup(filesystem); - v->flags = strdup(flags); - } else { -printf("matches was %d on <<%.40s>>\n", matches, bufp); - } - - /* Eat the line. - */ - while (nbytes > 0 && *bufp != '\n') { - bufp++; - nbytes--; - } - if (nbytes > 0) { - bufp++; - nbytes--; - } + while ((mentry = getmntent(fp)) != NULL) { + MountedVolume* v = &g_mounts_state.volumes[g_mounts_state.volume_count++]; + v->device = strdup(mentry->mnt_fsname); + v->mount_point = strdup(mentry->mnt_dir); + v->filesystem = strdup(mentry->mnt_type); + v->flags = strdup(mentry->mnt_opts); } - + endmntent(fp); return 0; - -bail: -//TODO: free the strings we've allocated. - g_mounts_state.volume_count = 0; - return -1; } const MountedVolume * diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c index d04b26e..9a17e38 100644 --- a/mtdutils/mtdutils.c +++ b/mtdutils/mtdutils.c @@ -313,8 +313,8 @@ static int read_block(const MtdPartition *partition, int fd, char *data) memcpy(&before, &after, sizeof(struct mtd_ecc_stats)); } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) { fprintf(stderr, - "mtd: MEMGETBADBLOCK returned %d at 0x%08llx (errno=%d)\n", - mgbb, pos, errno); + "mtd: MEMGETBADBLOCK returned %d at 0x%08llx: %s\n", + mgbb, pos, strerror(errno)); } else { return 0; // Success! } @@ -419,8 +419,8 @@ static int write_block(MtdWriteContext *ctx, const char *data) if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) { add_bad_block_offset(ctx, pos); fprintf(stderr, - "mtd: not writing bad block at 0x%08lx (ret %d errno %d)\n", - pos, ret, errno); + "mtd: not writing bad block at 0x%08lx (ret %d): %s\n", + pos, ret, strerror(errno)); pos += partition->erase_size; continue; // Don't try to erase known factory-bad blocks. } diff --git a/recovery.cpp b/recovery.cpp index 575e287..4dd8279 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -43,20 +43,20 @@ #include "screen_ui.h" #include "device.h" #include "adb_install.h" -extern "C" { -#include "minadbd/adb.h" +#include "adb.h" #include "fuse_sideload.h" #include "fuse_sdcard_provider.h" -} struct selabel_handle *sehandle; static const struct option OPTIONS[] = { - { "send_intent", required_argument, NULL, 's' }, + { "send_intent", required_argument, NULL, 'i' }, { "update_package", required_argument, NULL, 'u' }, { "wipe_data", no_argument, NULL, 'w' }, { "wipe_cache", no_argument, NULL, 'c' }, { "show_text", no_argument, NULL, 't' }, + { "sideload", no_argument, NULL, 's' }, + { "sideload_auto_reboot", no_argument, NULL, 'a' }, { "just_exit", no_argument, NULL, 'x' }, { "locale", required_argument, NULL, 'l' }, { "stages", required_argument, NULL, 'g' }, @@ -82,14 +82,11 @@ static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; #define KEEP_LOG_COUNT 10 -// Number of lines per page when displaying a file on screen -#define LINES_PER_PAGE 30 - RecoveryUI* ui = NULL; char* locale = NULL; -char recovery_version[PROPERTY_VALUE_MAX+1]; char* stage = NULL; char* reason = NULL; +bool modified_flash = false; /* * The recovery tool communicates with the main system through /cache files. @@ -169,6 +166,11 @@ fopen_path(const char *path, const char *mode) { return fp; } +bool is_ro_debuggable() { + char value[PROPERTY_VALUE_MAX+1]; + return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); +} + static void redirect_stdio(const char* filename) { // If these fail, there's not really anywhere to complain... freopen(filename, "a", stdout); setbuf(stdout, NULL); @@ -330,13 +332,18 @@ copy_log_file(const char* source, const char* destination, int append) { // 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) { +static void rotate_last_logs(int max) { + // Logs should only be rotated once. + static bool rotated = false; + if (rotated) { + return; + } + rotated = true; + ensure_path_mounted(LAST_LOG_FILE); + char oldfn[256]; char newfn[256]; - - int i; - for (i = max-1; i >= 0; --i) { + for (int 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 @@ -344,8 +351,17 @@ rotate_last_logs(int max) { } } -static void -copy_logs() { +static void copy_logs() { + // We only rotate and record the log of the current session if there are + // actual attempts to modify the flash, such as wipes, installs from BCB + // or menu selections. This is to avoid unnecessary rotation (and + // possible deletion) of log files, if it does not do anything loggable. + if (!modified_flash) { + return; + } + + rotate_last_logs(KEEP_LOG_COUNT); + // 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); @@ -497,25 +513,6 @@ erase_volume(const char *volume) { return result; } -static const char** -prepend_title(const char* const* headers) { - // count the number of lines in our title, plus the - // caller-provided headers. - int count = 3; // our title has 3 lines - const char* const* p; - for (p = headers; *p; ++p, ++count); - - const char** new_headers = (const char**)malloc((count+1) * sizeof(char*)); - const char** h = new_headers; - *(h++) = "Android system recovery <" EXPAND(RECOVERY_API_VERSION) "e>"; - *(h++) = recovery_version; - *(h++) = ""; - for (p = headers; *p; ++p, ++h) *h = *p; - *h = NULL; - - return new_headers; -} - static int get_menu_selection(const char* const * headers, const char* const * items, int menu_only, int initial_selection, Device* device) { @@ -546,12 +543,10 @@ get_menu_selection(const char* const * headers, const char* const * items, if (action < 0) { switch (action) { case Device::kHighlightUp: - --selected; - selected = ui->SelectMenu(selected); + selected = ui->SelectMenu(--selected); break; case Device::kHighlightDown: - ++selected; - selected = ui->SelectMenu(selected); + selected = ui->SelectMenu(++selected); break; case Device::kInvokeItem: chosen_item = selected; @@ -573,24 +568,15 @@ static int compare_string(const void* a, const void* b) { } // Returns a malloc'd path, or NULL. -static char* -browse_directory(const char* path, Device* device) { +static char* browse_directory(const char* path, Device* device) { ensure_path_mounted(path); - const char* MENU_HEADERS[] = { "Choose a package to install:", - path, - "", - NULL }; - DIR* d; - struct dirent* de; - d = opendir(path); + DIR* d = opendir(path); if (d == NULL) { LOGE("error opening %s: %s\n", path, strerror(errno)); return NULL; } - const char** headers = prepend_title(MENU_HEADERS); - int d_size = 0; int d_alloc = 10; char** dirs = (char**)malloc(d_alloc * sizeof(char*)); @@ -599,6 +585,7 @@ browse_directory(const char* path, Device* device) { char** zips = (char**)malloc(z_alloc * sizeof(char*)); zips[0] = strdup("../"); + struct dirent* de; while ((de = readdir(d)) != NULL) { int name_len = strlen(de->d_name); @@ -642,6 +629,8 @@ browse_directory(const char* path, Device* device) { z_size += d_size; zips[z_size] = NULL; + const char* headers[] = { "Choose a package to install:", path, NULL }; + char* result; int chosen_item = 0; while (true) { @@ -672,103 +661,54 @@ browse_directory(const char* path, Device* device) { } } - int i; - for (i = 0; i < z_size; ++i) free(zips[i]); + for (int i = 0; i < z_size; ++i) free(zips[i]); free(zips); - free(headers); return result; } -static void -wipe_data(int confirm, Device* device) { - if (confirm) { - static const char** title_headers = NULL; - - if (title_headers == NULL) { - const char* headers[] = { "Confirm wipe of all user data?", - " THIS CAN NOT BE UNDONE.", - "", - NULL }; - title_headers = prepend_title((const char**)headers); - } - - const char* items[] = { " No", - " No", - " No", - " No", - " No", - " No", - " No", - " Yes -- delete all user data", // [7] - " No", - " No", - " No", - NULL }; - - int chosen_item = get_menu_selection(title_headers, items, 1, 0, device); - if (chosen_item != 7) { - return; - } - } +static bool yes_no(Device* device, const char* question1, const char* question2) { + const char* headers[] = { question1, question2, NULL }; + const char* items[] = { " No", " Yes", NULL }; - ui->Print("\n-- Wiping data...\n"); - device->WipeData(); - erase_volume("/data"); - erase_volume("/cache"); - ui->Print("Data wipe complete.\n"); + int chosen_item = get_menu_selection(headers, items, 1, 0, device); + return (chosen_item == 1); } -static void file_to_ui(const char* fn) { - FILE *fp = fopen_path(fn, "re"); - if (fp == NULL) { - ui->Print(" Unable to open %s: %s\n", fn, strerror(errno)); - return; +// Return true on success. +static bool wipe_data(int should_confirm, Device* device) { + if (should_confirm && !yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!")) { + return false; } - char line[1024]; - int ct = 0; - int key = 0; - redirect_stdio("/dev/null"); - while(fgets(line, sizeof(line), fp) != NULL) { - ui->Print("%s", line); - ct++; - if (ct % LINES_PER_PAGE == 0) { - // give the user time to glance at the entries - key = ui->WaitKey(); - - if (key == KEY_POWER) { - break; - } - if (key == KEY_VOLUMEUP) { - // Go back by seeking to the beginning and dumping ct - n - // lines. It's ugly, but this way we don't need to store - // the previous offsets. The files we're dumping here aren't - // expected to be very large. - int i; + modified_flash = true; - ct -= 2 * LINES_PER_PAGE; - if (ct < 0) { - ct = 0; - } - fseek(fp, 0, SEEK_SET); - for (i = 0; i < ct; i++) { - fgets(line, sizeof(line), fp); - } - ui->Print("^^^^^^^^^^\n"); - } - } + ui->Print("\n-- Wiping data...\n"); + if (device->WipeData() == 0 && erase_volume("/data") == 0 && erase_volume("/cache") == 0) { + ui->Print("Data wipe complete.\n"); + return true; + } else { + ui->Print("Data wipe failed.\n"); + return false; } +} - // If the user didn't abort, then give the user time to glance at - // the end of the log, sorry, no rewind here - if (key != KEY_POWER) { - ui->Print("\n--END-- (press any key)\n"); - ui->WaitKey(); +// Return true on success. +static bool wipe_cache(bool should_confirm, Device* device) { + if (should_confirm && !yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!")) { + return false; } - redirect_stdio(TEMPORARY_LOG_FILE); - fclose(fp); + modified_flash = true; + + ui->Print("\n-- Wiping cache...\n"); + if (erase_volume("/cache") == 0) { + ui->Print("Cache wipe complete.\n"); + return true; + } else { + ui->Print("Cache wipe failed.\n"); + return false; + } } static void choose_recovery_file(Device* device) { @@ -776,9 +716,6 @@ static void choose_recovery_file(Device* device) { unsigned int n; static const char** title_headers = NULL; char *filename; - const char* headers[] = { "Select file to view", - "", - NULL }; // "Go back" + LAST_KMSG_FILE + KEEP_LOG_COUNT + terminating NULL entry char* entries[KEEP_LOG_COUNT + 3]; memset(entries, 0, sizeof(entries)); @@ -806,12 +743,16 @@ static void choose_recovery_file(Device* device) { entries[n++] = filename; } - title_headers = prepend_title((const char**)headers); + const char* headers[] = { "Select file to view", NULL }; - while(1) { - int chosen_item = get_menu_selection(title_headers, entries, 1, 0, device); + while (true) { + int chosen_item = get_menu_selection(headers, entries, 1, 0, device); if (chosen_item == 0) break; - file_to_ui(entries[chosen_item]); + + // TODO: do we need to redirect? ShowFile could just avoid writing to stdio. + redirect_stdio("/dev/null"); + ui->ShowFile(entries[chosen_item]); + redirect_stdio(TEMPORARY_LOG_FILE); } for (i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) { @@ -819,13 +760,37 @@ static void choose_recovery_file(Device* device) { } } +static int apply_from_sdcard(Device* device, bool* wipe_cache) { + modified_flash = true; + + if (ensure_path_mounted(SDCARD_ROOT) != 0) { + ui->Print("\n-- Couldn't mount %s.\n", SDCARD_ROOT); + return INSTALL_ERROR; + } + + char* path = browse_directory(SDCARD_ROOT, device); + if (path == NULL) { + ui->Print("\n-- No package file selected.\n"); + return INSTALL_ERROR; + } + + ui->Print("\n-- Install %s ...\n", path); + set_sdcard_update_bootloader_message(); + void* token = start_sdcard_fuse(path); + + int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, + TEMPORARY_INSTALL_FILE, false); + + finish_sdcard_fuse(token); + ensure_path_unmounted(SDCARD_ROOT); + return status; +} + // Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION // means to take the default, which is to reboot or shutdown depending // on if the --shutdown_after flag was passed to recovery. static Device::BuiltinAction prompt_and_wait(Device* device, int status) { - const char* const* headers = prepend_title(device->GetMenuHeaders()); - for (;;) { finish_recovery(NULL); switch (status) { @@ -841,14 +806,14 @@ prompt_and_wait(Device* device, int status) { } ui->SetProgressType(RecoveryUI::EMPTY); - int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device); + int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), 0, 0, device); // device-specific code may take some action here. It may // return one of the core actions handled in the switch // statement below. Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item); - int wipe_cache = 0; + bool should_wipe_cache = false; switch (chosen_action) { case Device::NO_ACTION: break; @@ -864,72 +829,45 @@ prompt_and_wait(Device* device, int status) { break; case Device::WIPE_CACHE: - ui->Print("\n-- Wiping cache...\n"); - erase_volume("/cache"); - ui->Print("Cache wipe complete.\n"); + wipe_cache(ui->IsTextVisible(), device); if (!ui->IsTextVisible()) return Device::NO_ACTION; break; - case Device::APPLY_EXT: { - ensure_path_mounted(SDCARD_ROOT); - char* path = browse_directory(SDCARD_ROOT, device); - if (path == NULL) { - ui->Print("\n-- No package file selected.\n", path); - break; - } - - ui->Print("\n-- Install %s ...\n", path); - set_sdcard_update_bootloader_message(); - void* token = start_sdcard_fuse(path); - - int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache, - TEMPORARY_INSTALL_FILE, false); - - finish_sdcard_fuse(token); - ensure_path_unmounted(SDCARD_ROOT); - - if (status == INSTALL_SUCCESS && wipe_cache) { - ui->Print("\n-- Wiping cache (at package request)...\n"); - if (erase_volume("/cache")) { - ui->Print("Cache wipe failed.\n"); + case Device::APPLY_ADB_SIDELOAD: + case Device::APPLY_SDCARD: + { + bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); + if (adb) { + status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); } else { - ui->Print("Cache wipe complete.\n"); + status = apply_from_sdcard(device, &should_wipe_cache); + } + + if (status == INSTALL_SUCCESS && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } } - } - if (status >= 0) { if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); + copy_logs(); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { - ui->Print("\nInstall from sdcard complete.\n"); + ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); } } break; - } - case Device::APPLY_CACHE: - ui->Print("\nAPPLY_CACHE is deprecated.\n"); - break; - - case Device::READ_RECOVERY_LASTLOG: + case Device::VIEW_RECOVERY_LOGS: choose_recovery_file(device); break; - case Device::APPLY_ADB_SIDELOAD: - status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE); - if (status >= 0) { - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - copy_logs(); - } else if (!ui->IsTextVisible()) { - return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from ADB complete.\n"); - } + case Device::MOUNT_SYSTEM: + if (ensure_path_mounted("/system") != -1) { + ui->Print("Mounted /system.\n"); } break; } @@ -992,31 +930,35 @@ main(int argc, char **argv) { // only way recovery should be run with this argument is when it // starts a copy of itself from the apply_from_adb() function. if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { - adb_main(); + adb_main(0, DEFAULT_ADB_PORT); return 0; } printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table(); - ensure_path_mounted(LAST_LOG_FILE); - rotate_last_logs(KEEP_LOG_COUNT); get_args(&argc, &argv); const char *send_intent = NULL; const char *update_package = NULL; - int wipe_data = 0, wipe_cache = 0, show_text = 0; + bool should_wipe_data = false; + bool should_wipe_cache = false; + bool show_text = false; + bool sideload = false; + bool sideload_auto_reboot = false; bool just_exit = false; bool shutdown_after = false; int arg; while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) { switch (arg) { - case 's': send_intent = optarg; break; + case 'i': send_intent = optarg; break; case 'u': update_package = optarg; break; - case 'w': wipe_data = wipe_cache = 1; break; - case 'c': wipe_cache = 1; break; - case 't': show_text = 1; break; + case 'w': should_wipe_data = true; break; + case 'c': should_wipe_cache = true; break; + case 't': show_text = true; break; + case 's': sideload = true; break; + case 'a': sideload = true; sideload_auto_reboot = true; break; case 'x': just_exit = true; break; case 'l': locale = optarg; break; case 'g': { @@ -1092,52 +1034,71 @@ main(int argc, char **argv) { printf("\n"); property_list(print_property, NULL); - property_get("ro.build.display.id", recovery_version, ""); printf("\n"); + ui->Print("Supported API: %d\n", RECOVERY_API_VERSION); + int status = INSTALL_SUCCESS; if (update_package != NULL) { - status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true); - if (status == INSTALL_SUCCESS && wipe_cache) { - if (erase_volume("/cache")) { - LOGE("Cache wipe (requested by package) failed."); - } + status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true); + if (status == INSTALL_SUCCESS && should_wipe_cache) { + wipe_cache(false, device); } if (status != INSTALL_SUCCESS) { ui->Print("Installation aborted.\n"); - ui->Print("OTA failed! Please power off the device to keep it in this state and file a bug report!\n"); // If this is an eng or userdebug build, then automatically // turn the text display on if the script fails so the error // message is visible. - char buffer[PROPERTY_VALUE_MAX+1]; - property_get("ro.build.fingerprint", buffer, ""); - if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) { + if (is_ro_debuggable()) { ui->ShowText(true); } } - } else if (wipe_data) { - if (device->WipeData()) status = INSTALL_ERROR; - if (erase_volume("/data")) status = INSTALL_ERROR; - if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; - if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n"); - } else if (wipe_cache) { - if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; - if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n"); + } else if (should_wipe_data) { + if (!wipe_data(false, device)) { + status = INSTALL_ERROR; + } + } else if (should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } + } else if (sideload) { + // 'adb reboot sideload' acts the same as user presses key combinations + // to enter the sideload mode. When 'sideload-auto-reboot' is used, text + // display will NOT be turned on by default. And it will reboot after + // sideload finishes even if there are errors. Unless one turns on the + // text display during the installation. This is to enable automated + // testing. + if (!sideload_auto_reboot) { + ui->ShowText(true); + } + status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); + if (status == INSTALL_SUCCESS && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } + } + ui->Print("\nInstall from ADB complete (status: %d).\n", status); + if (sideload_auto_reboot) { + ui->Print("Rebooting automatically.\n"); + } } else if (!just_exit) { status = INSTALL_NONE; // No command specified ui->SetBackground(RecoveryUI::NO_COMMAND); } - if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) { + if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) { copy_logs(); ui->SetBackground(RecoveryUI::ERROR); } + Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; - if (status != INSTALL_SUCCESS || ui->IsTextVisible()) { + if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) { Device::BuiltinAction temp = prompt_and_wait(device, status); - if (temp != Device::NO_ACTION) after = temp; + if (temp != Device::NO_ACTION) { + after = temp; + } } // Save logs and clean up before rebooting or shutting down. @@ -111,9 +111,10 @@ int ensure_path_mounted(const char* path) { } return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0); } else if (strcmp(v->fs_type, "ext4") == 0 || + strcmp(v->fs_type, "squashfs") == 0 || strcmp(v->fs_type, "vfat") == 0) { result = mount(v->blk_device, v->mount_point, v->fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); + v->flags, v->fs_options); if (result == 0) return 0; LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno)); diff --git a/screen_ui.cpp b/screen_ui.cpp index 03ef049..5e73d37 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -28,6 +28,10 @@ #include <time.h> #include <unistd.h> +#include <vector> + +#include "base/strings.h" +#include "cutils/properties.h" #include "common.h" #include "device.h" #include "minui/minui.h" @@ -37,28 +41,24 @@ static int char_width; static int char_height; -// There's only (at most) one of these objects, and global callbacks -// (for pthread_create, and the input event system) need to find it, -// so use a global variable. -static ScreenRecoveryUI* self = NULL; - // Return the current time as a double (including fractions of a second). static double now() { struct timeval tv; - gettimeofday(&tv, NULL); + gettimeofday(&tv, nullptr); return tv.tv_sec + tv.tv_usec / 1000000.0; } ScreenRecoveryUI::ScreenRecoveryUI() : currentIcon(NONE), installingFrame(0), - locale(NULL), + locale(nullptr), rtl_locale(false), progressBarType(EMPTY), progressScopeStart(0), progressScopeSize(0), progress(0), pagesIdentical(false), + text(nullptr), text_cols(0), text_rows(0), text_col(0), @@ -66,8 +66,8 @@ ScreenRecoveryUI::ScreenRecoveryUI() : text_top(0), show_text(false), show_text_ever(false), + menu(nullptr), show_menu(false), - menu_top(0), menu_items(0), menu_sel(0), animation_fps(20), @@ -75,29 +75,25 @@ ScreenRecoveryUI::ScreenRecoveryUI() : stage(-1), max_stage(-1) { - for (int i = 0; i < 5; i++) - backgroundIcon[i] = NULL; - - memset(text, 0, sizeof(text)); - - pthread_mutex_init(&updateMutex, NULL); - self = this; + for (int i = 0; i < 5; i++) { + backgroundIcon[i] = nullptr; + } + pthread_mutex_init(&updateMutex, nullptr); } // Clear the screen and draw the currently selected background icon (if any). // Should only be called with updateMutex locked. -void ScreenRecoveryUI::draw_background_locked(Icon icon) -{ +void ScreenRecoveryUI::draw_background_locked(Icon icon) { pagesIdentical = false; gr_color(0, 0, 0, 255); gr_clear(); if (icon) { - gr_surface surface = backgroundIcon[icon]; + GRSurface* surface = backgroundIcon[icon]; if (icon == INSTALLING_UPDATE || icon == ERASING) { surface = installation[installingFrame]; } - gr_surface text_surface = backgroundText[icon]; + GRSurface* text_surface = backgroundText[icon]; int iconWidth = gr_get_width(surface); int iconHeight = gr_get_height(surface); @@ -132,12 +128,11 @@ void ScreenRecoveryUI::draw_background_locked(Icon icon) // Draw the progress bar (if any) on the screen. Does not flip pages. // Should only be called with updateMutex locked. -void ScreenRecoveryUI::draw_progress_locked() -{ +void ScreenRecoveryUI::draw_progress_locked() { if (currentIcon == ERROR) return; if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - gr_surface icon = installation[installingFrame]; + GRSurface* icon = installation[installingFrame]; gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY); } @@ -180,6 +175,9 @@ void ScreenRecoveryUI::draw_progress_locked() void ScreenRecoveryUI::SetColor(UIElement e) { switch (e) { + case INFO: + gr_color(249, 194, 0, 255); + break; case HEADER: gr_color(247, 0, 6, 255); break; @@ -187,11 +185,14 @@ void ScreenRecoveryUI::SetColor(UIElement e) { case MENU_SEL_BG: gr_color(0, 106, 157, 255); break; + case MENU_SEL_BG_ACTIVE: + gr_color(0, 156, 100, 255); + break; case MENU_SEL_FG: gr_color(255, 255, 255, 255); break; case LOG: - gr_color(249, 194, 0, 255); + gr_color(196, 196, 196, 255); break; case TEXT_FILL: gr_color(0, 0, 0, 160); @@ -202,10 +203,38 @@ void ScreenRecoveryUI::SetColor(UIElement e) { } } +void ScreenRecoveryUI::DrawHorizontalRule(int* y) { + SetColor(MENU); + *y += 4; + gr_fill(0, *y, gr_fb_width(), *y + 2); + *y += 4; +} + +void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) { + gr_text(4, *y, line, bold); + *y += char_height + 4; +} + +void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) { + for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { + DrawTextLine(y, lines[i], false); + } +} + +static const char* REGULAR_HELP[] = { + "Use volume up/down and power.", + NULL +}; + +static const char* LONG_PRESS_HELP[] = { + "Any button cycles highlight.", + "Long-press activates.", + NULL +}; + // Redraw everything on the screen. Does not flip pages. // Should only be called with updateMutex locked. -void ScreenRecoveryUI::draw_screen_locked() -{ +void ScreenRecoveryUI::draw_screen_locked() { if (!show_text) { draw_background_locked(currentIcon); draw_progress_locked(); @@ -214,44 +243,50 @@ void ScreenRecoveryUI::draw_screen_locked() gr_clear(); int y = 0; - int i = 0; if (show_menu) { - SetColor(HEADER); + char recovery_fingerprint[PROPERTY_VALUE_MAX]; + property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); - for (; i < menu_top + menu_items; ++i) { - if (i == menu_top) SetColor(MENU); + SetColor(INFO); + DrawTextLine(&y, "Android Recovery", true); + for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { + DrawTextLine(&y, chunk.c_str(), false); + } + DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); - if (i == menu_top + menu_sel) { - // draw the highlight bar - SetColor(MENU_SEL_BG); - gr_fill(0, y-2, gr_fb_width(), y+char_height+2); - // white text of selected item + SetColor(HEADER); + DrawTextLines(&y, menu_headers); + + SetColor(MENU); + DrawHorizontalRule(&y); + y += 4; + for (int i = 0; i < menu_items; ++i) { + if (i == menu_sel) { + // Draw the highlight bar. + SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); + gr_fill(0, y - 2, gr_fb_width(), y + char_height + 2); + // Bold white text for the selected item. SetColor(MENU_SEL_FG); - if (menu[i][0]) gr_text(4, y, menu[i], 1); + gr_text(4, y, menu[i], true); SetColor(MENU); } else { - if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top); + gr_text(4, y, menu[i], false); } - y += char_height+4; + y += char_height + 4; } - SetColor(MENU); - y += 4; - gr_fill(0, y, gr_fb_width(), y+2); - y += 4; - ++i; + DrawHorizontalRule(&y); } - SetColor(LOG); - // display from the bottom up, until we hit the top of the // screen, the bottom of the menu, or we've displayed the // entire text buffer. - int ty; + SetColor(LOG); int row = (text_top+text_rows-1) % text_rows; - for (int ty = gr_fb_height() - char_height, count = 0; - ty > y+2 && count < text_rows; + size_t count = 0; + for (int ty = gr_fb_height() - char_height; + ty >= y && count < text_rows; ty -= char_height, ++count) { - gr_text(4, ty, text[row], 0); + gr_text(0, ty, text[row], false); --row; if (row < 0) row = text_rows-1; } @@ -260,16 +295,14 @@ void ScreenRecoveryUI::draw_screen_locked() // Redraw everything on the screen and flip the screen (make it visible). // Should only be called with updateMutex locked. -void ScreenRecoveryUI::update_screen_locked() -{ +void ScreenRecoveryUI::update_screen_locked() { draw_screen_locked(); gr_flip(); } // Updates only the progress bar, if possible, otherwise redraws the screen. // Should only be called with updateMutex locked. -void ScreenRecoveryUI::update_progress_locked() -{ +void ScreenRecoveryUI::update_progress_locked() { if (show_text || !pagesIdentical) { draw_screen_locked(); // Must redraw the whole screen pagesIdentical = true; @@ -280,14 +313,14 @@ void ScreenRecoveryUI::update_progress_locked() } // Keeps the progress bar updated, even when the process is otherwise busy. -void* ScreenRecoveryUI::progress_thread(void *cookie) { - self->progress_loop(); - return NULL; +void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { + reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); + return nullptr; } -void ScreenRecoveryUI::progress_loop() { +void ScreenRecoveryUI::ProgressThreadLoop() { double interval = 1.0 / animation_fps; - for (;;) { + while (true) { double start = now(); pthread_mutex_lock(&updateMutex); @@ -324,44 +357,52 @@ void ScreenRecoveryUI::progress_loop() { } } -void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) { +void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { int result = res_create_display_surface(filename, surface); if (result < 0) { LOGE("missing bitmap %s\n(Code %d)\n", filename, result); } } -void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, gr_surface** surface) { +void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, GRSurface*** surface) { int result = res_create_multi_display_surface(filename, frames, surface); if (result < 0) { LOGE("missing bitmap %s\n(Code %d)\n", filename, result); } } -void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) { +void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { int result = res_create_localized_alpha_surface(filename, locale, surface); if (result < 0) { LOGE("missing bitmap %s\n(Code %d)\n", filename, result); } } -void ScreenRecoveryUI::Init() -{ +static char** Alloc2d(size_t rows, size_t cols) { + char** result = new char*[rows]; + for (size_t i = 0; i < rows; ++i) { + result[i] = new char[cols]; + memset(result[i], 0, cols); + } + return result; +} + +void ScreenRecoveryUI::Init() { gr_init(); gr_font_size(&char_width, &char_height); + text_rows = gr_fb_height() / char_height; + text_cols = gr_fb_width() / char_width; + + text = Alloc2d(text_rows, text_cols + 1); + menu = Alloc2d(text_rows, text_cols + 1); text_col = text_row = 0; - text_rows = gr_fb_height() / char_height; - if (text_rows > kMaxRows) text_rows = kMaxRows; text_top = 1; - text_cols = gr_fb_width() / char_width; - if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; - - backgroundIcon[NONE] = NULL; + backgroundIcon[NONE] = nullptr; LoadBitmapArray("icon_installing", &installing_frames, &installation); - backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL; + backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr; backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; LoadBitmap("icon_error", &backgroundIcon[ERROR]); backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; @@ -376,7 +417,7 @@ void ScreenRecoveryUI::Init() LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); - pthread_create(&progress_t, NULL, progress_thread, NULL); + pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); RecoveryUI::Init(); } @@ -403,12 +444,11 @@ void ScreenRecoveryUI::SetLocale(const char* new_locale) { } free(lang); } else { - new_locale = NULL; + new_locale = nullptr; } } -void ScreenRecoveryUI::SetBackground(Icon icon) -{ +void ScreenRecoveryUI::SetBackground(Icon icon) { pthread_mutex_lock(&updateMutex); currentIcon = icon; @@ -417,8 +457,7 @@ void ScreenRecoveryUI::SetBackground(Icon icon) pthread_mutex_unlock(&updateMutex); } -void ScreenRecoveryUI::SetProgressType(ProgressType type) -{ +void ScreenRecoveryUI::SetProgressType(ProgressType type) { pthread_mutex_lock(&updateMutex); if (progressBarType != type) { progressBarType = type; @@ -430,8 +469,7 @@ void ScreenRecoveryUI::SetProgressType(ProgressType type) pthread_mutex_unlock(&updateMutex); } -void ScreenRecoveryUI::ShowProgress(float portion, float seconds) -{ +void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { pthread_mutex_lock(&updateMutex); progressBarType = DETERMINATE; progressScopeStart += progressScopeSize; @@ -443,8 +481,7 @@ void ScreenRecoveryUI::ShowProgress(float portion, float seconds) pthread_mutex_unlock(&updateMutex); } -void ScreenRecoveryUI::SetProgress(float fraction) -{ +void ScreenRecoveryUI::SetProgress(float fraction) { pthread_mutex_lock(&updateMutex); if (fraction < 0.0) fraction = 0.0; if (fraction > 1.0) fraction = 1.0; @@ -467,8 +504,7 @@ void ScreenRecoveryUI::SetStage(int current, int max) { pthread_mutex_unlock(&updateMutex); } -void ScreenRecoveryUI::Print(const char *fmt, ...) -{ +void ScreenRecoveryUI::Print(const char *fmt, ...) { char buf[256]; va_list ap; va_start(ap, fmt); @@ -477,11 +513,9 @@ void ScreenRecoveryUI::Print(const char *fmt, ...) fputs(buf, stdout); - // This can get called before ui_init(), so be careful. pthread_mutex_lock(&updateMutex); if (text_rows > 0 && text_cols > 0) { - char *ptr; - for (ptr = buf; *ptr != '\0'; ++ptr) { + for (const char* ptr = buf; *ptr != '\0'; ++ptr) { if (*ptr == '\n' || text_col >= text_cols) { text[text_row][text_col] = '\0'; text_col = 0; @@ -496,24 +530,100 @@ void ScreenRecoveryUI::Print(const char *fmt, ...) pthread_mutex_unlock(&updateMutex); } +void ScreenRecoveryUI::PutChar(char ch) { + pthread_mutex_lock(&updateMutex); + if (ch != '\n') text[text_row][text_col++] = ch; + if (ch == '\n' || text_col >= text_cols) { + text_col = 0; + ++text_row; + } + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::ClearText() { + pthread_mutex_lock(&updateMutex); + text_col = 0; + text_row = 0; + text_top = 1; + for (size_t i = 0; i < text_rows; ++i) { + memset(text[i], 0, text_cols + 1); + } + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::ShowFile(FILE* fp) { + std::vector<long> offsets; + offsets.push_back(ftell(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + Print("--(%d%% of %d bytes)--", + static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))), + static_cast<int>(sb.st_size)); + Redraw(); + while (show_prompt) { + show_prompt = false; + int key = WaitKey(); + if (key == KEY_POWER || key == KEY_ENTER) { + return; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + if (offsets.size() <= 1) { + show_prompt = true; + } else { + offsets.pop_back(); + fseek(fp, offsets.back(), SEEK_SET); + } + } else { + if (feof(fp)) { + return; + } + offsets.push_back(ftell(fp)); + } + } + ClearText(); + } + + int ch = getc(fp); + if (ch == EOF) { + text_row = text_top = text_rows - 2; + show_prompt = true; + } else { + PutChar(ch); + if (text_col == 0 && text_row >= text_rows - 2) { + text_top = text_row; + show_prompt = true; + } + } + } +} + +void ScreenRecoveryUI::ShowFile(const char* filename) { + FILE* fp = fopen_path(filename, "re"); + if (fp == nullptr) { + Print(" Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + ShowFile(fp); + fclose(fp); +} + void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items, int initial_selection) { - int i; pthread_mutex_lock(&updateMutex); if (text_rows > 0 && text_cols > 0) { - for (i = 0; i < text_rows; ++i) { - if (headers[i] == NULL) break; - strncpy(menu[i], headers[i], text_cols-1); - menu[i][text_cols-1] = '\0'; - } - menu_top = i; - for (; i < text_rows; ++i) { - if (items[i-menu_top] == NULL) break; - strncpy(menu[i], items[i-menu_top], text_cols-1); + menu_headers = headers; + size_t i = 0; + for (; i < text_rows && items[i] != nullptr; ++i) { + strncpy(menu[i], items[i], text_cols-1); menu[i][text_cols-1] = '\0'; } - menu_items = i - menu_top; - show_menu = 1; + menu_items = i; + show_menu = true; menu_sel = initial_selection; update_screen_locked(); } @@ -521,13 +631,15 @@ void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const } int ScreenRecoveryUI::SelectMenu(int sel) { - int old_sel; pthread_mutex_lock(&updateMutex); - if (show_menu > 0) { - old_sel = menu_sel; + if (show_menu) { + int old_sel = menu_sel; menu_sel = sel; - if (menu_sel < 0) menu_sel = 0; - if (menu_sel >= menu_items) menu_sel = menu_items-1; + + // Wrap at top and bottom. + if (menu_sel < 0) menu_sel = menu_items - 1; + if (menu_sel >= menu_items) menu_sel = 0; + sel = menu_sel; if (menu_sel != old_sel) update_screen_locked(); } @@ -536,43 +648,44 @@ int ScreenRecoveryUI::SelectMenu(int sel) { } void ScreenRecoveryUI::EndMenu() { - int i; pthread_mutex_lock(&updateMutex); - if (show_menu > 0 && text_rows > 0 && text_cols > 0) { - show_menu = 0; + if (show_menu && text_rows > 0 && text_cols > 0) { + show_menu = false; update_screen_locked(); } pthread_mutex_unlock(&updateMutex); } -bool ScreenRecoveryUI::IsTextVisible() -{ +bool ScreenRecoveryUI::IsTextVisible() { pthread_mutex_lock(&updateMutex); int visible = show_text; pthread_mutex_unlock(&updateMutex); return visible; } -bool ScreenRecoveryUI::WasTextEverVisible() -{ +bool ScreenRecoveryUI::WasTextEverVisible() { pthread_mutex_lock(&updateMutex); int ever_visible = show_text_ever; pthread_mutex_unlock(&updateMutex); return ever_visible; } -void ScreenRecoveryUI::ShowText(bool visible) -{ +void ScreenRecoveryUI::ShowText(bool visible) { pthread_mutex_lock(&updateMutex); show_text = visible; - if (show_text) show_text_ever = 1; + if (show_text) show_text_ever = true; update_screen_locked(); pthread_mutex_unlock(&updateMutex); } -void ScreenRecoveryUI::Redraw() -{ +void ScreenRecoveryUI::Redraw() { pthread_mutex_lock(&updateMutex); update_screen_locked(); pthread_mutex_unlock(&updateMutex); } + +void ScreenRecoveryUI::KeyLongPress(int) { + // Redraw so that if we're in the menu, the highlight + // will change color to indicate a successful long press. + Redraw(); +} diff --git a/screen_ui.h b/screen_ui.h index 01a33bf..46165d9 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -18,6 +18,7 @@ #define RECOVERY_SCREEN_UI_H #include <pthread.h> +#include <stdio.h> #include "ui.h" #include "minui/minui.h" @@ -47,18 +48,23 @@ class ScreenRecoveryUI : public RecoveryUI { bool WasTextEverVisible(); // printing messages - void Print(const char* fmt, ...); // __attribute__((format(printf, 1, 2))); + void Print(const char* fmt, ...) __printflike(2, 3); + void ShowFile(const char* filename); // menu display void StartMenu(const char* const * headers, const char* const * items, - int initial_selection); + int initial_selection); int SelectMenu(int sel); void EndMenu(); + void KeyLongPress(int); + void Redraw(); - enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL }; - virtual void SetColor(UIElement e); + enum UIElement { + HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO + }; + void SetColor(UIElement e); private: Icon currentIcon; @@ -67,43 +73,38 @@ class ScreenRecoveryUI : public RecoveryUI { bool rtl_locale; pthread_mutex_t updateMutex; - gr_surface backgroundIcon[5]; - gr_surface backgroundText[5]; - gr_surface *installation; - gr_surface progressBarEmpty; - gr_surface progressBarFill; - gr_surface stageMarkerEmpty; - gr_surface stageMarkerFill; + GRSurface* backgroundIcon[5]; + GRSurface* backgroundText[5]; + GRSurface** installation; + GRSurface* progressBarEmpty; + GRSurface* progressBarFill; + GRSurface* stageMarkerEmpty; + GRSurface* stageMarkerFill; ProgressType progressBarType; float progressScopeStart, progressScopeSize, progress; double progressScopeTime, progressScopeDuration; - // true when both graphics pages are the same (except for the - // progress bar) + // true when both graphics pages are the same (except for the progress bar). bool pagesIdentical; - static const int kMaxCols = 96; - static const int kMaxRows = 96; - - // Log text overlay, displayed when a magic key is pressed - char text[kMaxRows][kMaxCols]; - int text_cols, text_rows; - int text_col, text_row, text_top; + // Log text overlay, displayed when a magic key is pressed. + char** text; + size_t text_cols, text_rows; + size_t text_col, text_row, text_top; bool show_text; bool show_text_ever; // has show_text ever been true? - char menu[kMaxRows][kMaxCols]; + char** menu; + const char* const* menu_headers; bool show_menu; - int menu_top, menu_items, menu_sel; + int menu_items, menu_sel; - pthread_t progress_t; + pthread_t progress_thread_; int animation_fps; int installing_frames; - protected: - private: int iconX, iconY; @@ -114,12 +115,21 @@ class ScreenRecoveryUI : public RecoveryUI { void draw_screen_locked(); void update_screen_locked(); void update_progress_locked(); - static void* progress_thread(void* cookie); - void progress_loop(); - void LoadBitmap(const char* filename, gr_surface* surface); - void LoadBitmapArray(const char* filename, int* frames, gr_surface** surface); - void LoadLocalizedBitmap(const char* filename, gr_surface* surface); + static void* ProgressThreadStartRoutine(void* data); + void ProgressThreadLoop(); + + void ShowFile(FILE*); + void PutChar(char); + void ClearText(); + + void DrawHorizontalRule(int* y); + void DrawTextLine(int* y, const char* line, bool bold); + void DrawTextLines(int* y, const char* const* lines); + + void LoadBitmap(const char* filename, GRSurface** surface); + void LoadBitmapArray(const char* filename, int* frames, GRSurface*** surface); + void LoadLocalizedBitmap(const char* filename, GRSurface** surface); }; #endif // RECOVERY_UI_H diff --git a/tests/Android.mk b/tests/Android.mk index 4d99d52..02a272a 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -1,26 +1,25 @@ -# Build the unit tests. -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -# Build the unit tests. -test_src_files := \ - asn1_decoder_test.cpp +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# -shared_libraries := \ - liblog \ - libcutils - -static_libraries := \ - libgtest \ - libgtest_main \ - libverifier +LOCAL_PATH := $(call my-dir) -$(foreach file,$(test_src_files), \ - $(eval include $(CLEAR_VARS)) \ - $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ - $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ - $(eval LOCAL_SRC_FILES := $(file)) \ - $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ - $(eval LOCAL_C_INCLUDES := $(LOCAL_PATH)/..) \ - $(eval include $(BUILD_NATIVE_TEST)) \ -)
\ No newline at end of file +include $(CLEAR_VARS) +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +LOCAL_STATIC_LIBRARIES := libverifier +LOCAL_SRC_FILES := asn1_decoder_test.cpp +LOCAL_MODULE := asn1_decoder_test +LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. +include $(BUILD_NATIVE_TEST) diff --git a/tools/ota/check-lost+found.c b/tools/ota/check-lost+found.c index da02f46..cbf7926 100644 --- a/tools/ota/check-lost+found.c +++ b/tools/ota/check-lost+found.c @@ -26,6 +26,7 @@ #include <sys/stat.h> #include <sys/types.h> #include <time.h> +#include <unistd.h> #include "private/android_filesystem_config.h" @@ -39,40 +39,59 @@ #define UI_WAIT_KEY_TIMEOUT_SEC 120 -// There's only (at most) one of these objects, and global callbacks -// (for pthread_create, and the input event system) need to find it, -// so use a global variable. -static RecoveryUI* self = NULL; - -RecoveryUI::RecoveryUI() : - key_queue_len(0), - key_last_down(-1), - key_long_press(false), - key_down_count(0), - enable_reboot(true), - consecutive_power_keys(0), - consecutive_alternate_keys(0), - last_key(-1) { - pthread_mutex_init(&key_queue_mutex, NULL); - pthread_cond_init(&key_queue_cond, NULL); - self = this; +RecoveryUI::RecoveryUI() + : key_queue_len(0), + key_last_down(-1), + key_long_press(false), + key_down_count(0), + enable_reboot(true), + consecutive_power_keys(0), + last_key(-1), + has_power_key(false), + has_up_key(false), + has_down_key(false) { + pthread_mutex_init(&key_queue_mutex, nullptr); + pthread_cond_init(&key_queue_cond, nullptr); memset(key_pressed, 0, sizeof(key_pressed)); } -void RecoveryUI::Init() { - ev_init(input_callback, NULL); - pthread_create(&input_t, NULL, input_thread, NULL); +void RecoveryUI::OnKeyDetected(int key_code) { + if (key_code == KEY_POWER) { + has_power_key = true; + } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { + has_down_key = true; + } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { + has_up_key = true; + } } +int RecoveryUI::InputCallback(int fd, uint32_t epevents, void* data) { + return reinterpret_cast<RecoveryUI*>(data)->OnInputEvent(fd, epevents); +} -int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data) -{ - struct input_event ev; - int ret; +// Reads input events, handles special hot keys, and adds to the key queue. +static void* InputThreadLoop(void*) { + while (true) { + if (!ev_wait(-1)) { + ev_dispatch(); + } + } + return nullptr; +} + +void RecoveryUI::Init() { + ev_init(InputCallback, this); + + ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); - ret = ev_get_input(fd, epevents, &ev); - if (ret) + pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr); +} + +int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { + struct input_event ev; + if (ev_get_input(fd, epevents, &ev) == -1) { return -1; + } if (ev.type == EV_SYN) { return 0; @@ -82,23 +101,24 @@ int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data) // the trackball. When it exceeds a threshold // (positive or negative), fake an up/down // key event. - self->rel_sum += ev.value; - if (self->rel_sum > 3) { - self->process_key(KEY_DOWN, 1); // press down key - self->process_key(KEY_DOWN, 0); // and release it - self->rel_sum = 0; - } else if (self->rel_sum < -3) { - self->process_key(KEY_UP, 1); // press up key - self->process_key(KEY_UP, 0); // and release it - self->rel_sum = 0; + rel_sum += ev.value; + if (rel_sum > 3) { + ProcessKey(KEY_DOWN, 1); // press down key + ProcessKey(KEY_DOWN, 0); // and release it + rel_sum = 0; + } else if (rel_sum < -3) { + ProcessKey(KEY_UP, 1); // press up key + ProcessKey(KEY_UP, 0); // and release it + rel_sum = 0; } } } else { - self->rel_sum = 0; + rel_sum = 0; } - if (ev.type == EV_KEY && ev.code <= KEY_MAX) - self->process_key(ev.code, ev.value); + if (ev.type == EV_KEY && ev.code <= KEY_MAX) { + ProcessKey(ev.code, ev.value); + } return 0; } @@ -115,7 +135,7 @@ int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data) // a key is registered. // // updown == 1 for key down events; 0 for key up events -void RecoveryUI::process_key(int key_code, int updown) { +void RecoveryUI::ProcessKey(int key_code, int updown) { bool register_key = false; bool long_press = false; bool reboot_enabled; @@ -126,13 +146,13 @@ void RecoveryUI::process_key(int key_code, int updown) { ++key_down_count; key_last_down = key_code; key_long_press = false; - pthread_t th; key_timer_t* info = new key_timer_t; info->ui = this; info->key_code = key_code; info->count = key_down_count; - pthread_create(&th, NULL, &RecoveryUI::time_key_helper, info); - pthread_detach(th); + pthread_t thread; + pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info); + pthread_detach(thread); } else { if (key_last_down == key_code) { long_press = key_long_press; @@ -144,8 +164,7 @@ void RecoveryUI::process_key(int key_code, int updown) { pthread_mutex_unlock(&key_queue_mutex); if (register_key) { - NextCheckKeyIsLong(long_press); - switch (CheckKey(key_code)) { + switch (CheckKey(key_code, long_press)) { case RecoveryUI::IGNORE: break; @@ -162,13 +181,6 @@ void RecoveryUI::process_key(int key_code, int updown) { case RecoveryUI::ENQUEUE: EnqueueKey(key_code); break; - - case RecoveryUI::MOUNT_SYSTEM: -#ifndef NO_RECOVERY_MOUNT - ensure_path_mounted("/system"); - Print("Mounted /system."); -#endif - break; } } } @@ -177,7 +189,7 @@ void* RecoveryUI::time_key_helper(void* cookie) { key_timer_t* info = (key_timer_t*) cookie; info->ui->time_key(info->key_code, info->count); delete info; - return NULL; + return nullptr; } void RecoveryUI::time_key(int key_code, int count) { @@ -201,19 +213,7 @@ void RecoveryUI::EnqueueKey(int key_code) { pthread_mutex_unlock(&key_queue_mutex); } - -// Reads input events, handles special hot keys, and adds to the key queue. -void* RecoveryUI::input_thread(void *cookie) -{ - for (;;) { - if (!ev_wait(-1)) - ev_dispatch(); - } - return NULL; -} - -int RecoveryUI::WaitKey() -{ +int RecoveryUI::WaitKey() { pthread_mutex_lock(&key_queue_mutex); // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is @@ -221,17 +221,16 @@ int RecoveryUI::WaitKey() do { struct timeval now; struct timespec timeout; - gettimeofday(&now, NULL); + gettimeofday(&now, nullptr); timeout.tv_sec = now.tv_sec; timeout.tv_nsec = now.tv_usec * 1000; timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; int rc = 0; while (key_queue_len == 0 && rc != ETIMEDOUT) { - rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, - &timeout); + rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); } - } while (usb_connected() && key_queue_len == 0); + } while (IsUsbConnected() && key_queue_len == 0); int key = -1; if (key_queue_len > 0) { @@ -242,8 +241,7 @@ int RecoveryUI::WaitKey() return key; } -// Return true if USB is connected. -bool RecoveryUI::usb_connected() { +bool RecoveryUI::IsUsbConnected() { int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); if (fd < 0) { printf("failed to open /sys/class/android_usb/android0/state: %s\n", @@ -252,7 +250,7 @@ bool RecoveryUI::usb_connected() { } char buf; - /* USB is connected if android_usb state is CONNECTED or CONFIGURED */ + // USB is connected if android_usb state is CONNECTED or CONFIGURED. int connected = (read(fd, &buf, 1) == 1) && (buf == 'C'); if (close(fd) < 0) { printf("failed to close /sys/class/android_usb/android0/state: %s\n", @@ -261,31 +259,55 @@ bool RecoveryUI::usb_connected() { return connected; } -bool RecoveryUI::IsKeyPressed(int key) -{ +bool RecoveryUI::IsKeyPressed(int key) { pthread_mutex_lock(&key_queue_mutex); int pressed = key_pressed[key]; pthread_mutex_unlock(&key_queue_mutex); return pressed; } +bool RecoveryUI::IsLongPress() { + pthread_mutex_lock(&key_queue_mutex); + bool result = key_long_press; + pthread_mutex_unlock(&key_queue_mutex); + return result; +} + +bool RecoveryUI::HasThreeButtons() { + return has_power_key && has_up_key && has_down_key; +} + void RecoveryUI::FlushKeys() { pthread_mutex_lock(&key_queue_mutex); key_queue_len = 0; pthread_mutex_unlock(&key_queue_mutex); } -// The default CheckKey implementation assumes the device has power, -// volume up, and volume down keys. -// -// - Hold power and press vol-up to toggle display. -// - Press power seven times in a row to reboot. -// - Alternate vol-up and vol-down seven times to mount /system. -RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) { - if ((IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) || key == KEY_HOME) { - return TOGGLE; +RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { + pthread_mutex_lock(&key_queue_mutex); + key_long_press = false; + pthread_mutex_unlock(&key_queue_mutex); + + // If we have power and volume up keys, that chord is the signal to toggle the text display. + if (HasThreeButtons()) { + if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) { + return TOGGLE; + } + } else { + // Otherwise long press of any button toggles to the text display, + // and there's no way to toggle back (but that's pretty useless anyway). + if (is_long_press && !IsTextVisible()) { + return TOGGLE; + } + + // Also, for button-limited devices, a long press is translated to KEY_ENTER. + if (is_long_press && IsTextVisible()) { + EnqueueKey(KEY_ENTER); + return IGNORE; + } } + // Press power seven times in a row to reboot. if (key == KEY_POWER) { pthread_mutex_lock(&key_queue_mutex); bool reboot_enabled = enable_reboot; @@ -301,27 +323,11 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) { consecutive_power_keys = 0; } - if ((key == KEY_VOLUMEUP && - (last_key == KEY_VOLUMEDOWN || last_key == -1)) || - (key == KEY_VOLUMEDOWN && - (last_key == KEY_VOLUMEUP || last_key == -1))) { - ++consecutive_alternate_keys; - if (consecutive_alternate_keys >= 7) { - consecutive_alternate_keys = 0; - return MOUNT_SYSTEM; - } - } else { - consecutive_alternate_keys = 0; - } last_key = key; - - return ENQUEUE; -} - -void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) { + return IsTextVisible() ? ENQUEUE : IGNORE; } -void RecoveryUI::KeyLongPress(int key) { +void RecoveryUI::KeyLongPress(int) { } void RecoveryUI::SetEnableReboot(bool enabled) { @@ -31,10 +31,10 @@ class RecoveryUI { // Initialize the object; called before anything else. virtual void Init(); // Show a stage indicator. Call immediately after Init(). - virtual void SetStage(int current, int max) { } + virtual void SetStage(int current, int max) = 0; // After calling Init(), you can tell the UI what locale it is operating in. - virtual void SetLocale(const char* locale) { } + virtual void SetLocale(const char* locale) = 0; // Set the overall recovery state ("background image"). enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; @@ -63,34 +63,37 @@ class RecoveryUI { // Write a message to the on-screen log (shown if the user has // toggled on the text display). - virtual void Print(const char* fmt, ...) = 0; // __attribute__((format(printf, 1, 2))) = 0; + virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; + + virtual void ShowFile(const char* filename) = 0; // --- key handling --- - // Wait for keypress and return it. May return -1 after timeout. + // Wait for a key and return it. May return -1 after timeout. virtual int WaitKey(); virtual bool IsKeyPressed(int key); + virtual bool IsLongPress(); + + // Returns true if you have the volume up/down and power trio typical + // of phones and tablets, false otherwise. + virtual bool HasThreeButtons(); // Erase any queued-up keys. virtual void FlushKeys(); - // Called on each keypress, even while operations are in progress. + // Called on each key press, even while operations are in progress. // Return value indicates whether an immediate operation should be // triggered (toggling the display, rebooting the device), or if // the key should be enqueued for use by the main thread. - enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE, MOUNT_SYSTEM }; - virtual KeyAction CheckKey(int key); - - // Called immediately before each call to CheckKey(), tell you if - // the key was long-pressed. - virtual void NextCheckKeyIsLong(bool is_long_press); + enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE }; + virtual KeyAction CheckKey(int key, bool is_long_press); // Called when a key is held down long enough to have been a // long-press (but before the key is released). This means that // if the key is eventually registered (released without any other - // keys being pressed in the meantime), NextCheckKeyIsLong() will - // be called with "true". + // keys being pressed in the meantime), CheckKey will be called with + // 'is_long_press' true. virtual void KeyLongPress(int key); // Normally in recovery there's a key sequence that triggers @@ -108,8 +111,8 @@ class RecoveryUI { virtual void StartMenu(const char* const * headers, const char* const * items, int initial_selection) = 0; - // Set the menu highlight to the given index, and return it (capped to - // the range [0..numitems). + // Set the menu highlight to the given index, wrapping if necessary. + // Returns the actual item selected. virtual int SelectMenu(int sel) = 0; // End menu mode, resetting the text overlay so that ui_print() @@ -132,21 +135,27 @@ private: int rel_sum; int consecutive_power_keys; - int consecutive_alternate_keys; int last_key; - typedef struct { + bool has_power_key; + bool has_up_key; + bool has_down_key; + + struct key_timer_t { RecoveryUI* ui; int key_code; int count; - } key_timer_t; + }; + + pthread_t input_thread_; + + void OnKeyDetected(int key_code); - pthread_t input_t; + static int InputCallback(int fd, uint32_t epevents, void* data); + int OnInputEvent(int fd, uint32_t epevents); + void ProcessKey(int key_code, int updown); - static void* input_thread(void* cookie); - static int input_callback(int fd, uint32_t epevents, void* data); - void process_key(int key_code, int updown); - bool usb_connected(); + bool IsUsbConnected(); static void* time_key_helper(void* cookie); void time_key(int key_code, int count); diff --git a/uncrypt/uncrypt.c b/uncrypt/uncrypt.c index 7fb0989..aa75210 100644 --- a/uncrypt/uncrypt.c +++ b/uncrypt/uncrypt.c @@ -39,8 +39,10 @@ // Recovery can take this block map file and retrieve the underlying // file data to use as an update package. +#include <errno.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> diff --git a/updater/Android.mk b/updater/Android.mk index 11e7bb8..ff02a33 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -79,7 +79,7 @@ $(inc) : $(inc_dep_file) $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;) $(hide) echo "}" >> $@ -$(call intermediates-dir-for,EXECUTABLES,updater)/updater.o : $(inc) +$(call intermediates-dir-for,EXECUTABLES,updater,,,$(TARGET_PREFER_32_BIT))/updater.o : $(inc) LOCAL_C_INCLUDES += $(dir $(inc)) inc := diff --git a/updater/MODULE_LICENSE_GPL b/updater/MODULE_LICENSE_GPL deleted file mode 100644 index e69de29..0000000 --- a/updater/MODULE_LICENSE_GPL +++ /dev/null diff --git a/updater/NOTICE b/updater/NOTICE deleted file mode 100644 index e77696a..0000000 --- a/updater/NOTICE +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 675 Mass Ave, Cambridge, MA 02139, USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - <one line to give the program's name and a brief idea of what it does.> - Copyright (C) 19yy <name of author> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) 19yy name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - <signature of Ty Coon>, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff --git a/updater/blockimg.c b/updater/blockimg.c index 6060ac2..d5344f9 100644 --- a/updater/blockimg.c +++ b/updater/blockimg.c @@ -16,6 +16,7 @@ #include <ctype.h> #include <errno.h> +#include <dirent.h> #include <fcntl.h> #include <inttypes.h> #include <pthread.h> @@ -23,6 +24,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/ioctl.h> @@ -32,7 +34,7 @@ #include "applypatch/applypatch.h" #include "edify/expr.h" #include "mincrypt/sha.h" -#include "minzip/DirUtil.h" +#include "minzip/Hash.h" #include "updater.h" #define BLOCKSIZE 4096 @@ -46,6 +48,10 @@ #define BLKDISCARD _IO(0x12,119) #endif +#define STASH_DIRECTORY_BASE "/cache/recovery" +#define STASH_DIRECTORY_MODE 0700 +#define STASH_FILE_MODE 0600 + char* PrintSha1(const uint8_t* digest); typedef struct { @@ -80,44 +86,77 @@ static RangeSet* parse_range(char* text) { return out; } -static void readblock(int fd, uint8_t* data, size_t size) { +static int range_overlaps(RangeSet* r1, RangeSet* r2) { + int i, j, r1_0, r1_1, r2_0, r2_1; + + if (!r1 || !r2) { + return 0; + } + + for (i = 0; i < r1->count; ++i) { + r1_0 = r1->pos[i * 2]; + r1_1 = r1->pos[i * 2 + 1]; + + for (j = 0; j < r2->count; ++j) { + r2_0 = r2->pos[j * 2]; + r2_1 = r2->pos[j * 2 + 1]; + + if (!(r2_0 > r1_1 || r1_0 > r2_1)) { + return 1; + } + } + } + + return 0; +} + +static int read_all(int fd, uint8_t* data, size_t size) { size_t so_far = 0; while (so_far < size) { ssize_t r = read(fd, data+so_far, size-so_far); if (r < 0 && errno != EINTR) { fprintf(stderr, "read failed: %s\n", strerror(errno)); - return; + return -1; } else { so_far += r; } } + return 0; } -static void writeblock(int fd, const uint8_t* data, size_t size) { +static int write_all(int fd, const uint8_t* data, size_t size) { size_t written = 0; while (written < size) { ssize_t w = write(fd, data+written, size-written); if (w < 0 && errno != EINTR) { fprintf(stderr, "write failed: %s\n", strerror(errno)); - return; + return -1; } else { written += w; } } + + if (fsync(fd) == -1) { + fprintf(stderr, "fsync failed: %s\n", strerror(errno)); + return -1; + } + + return 0; } -static void check_lseek(int fd, off64_t offset, int whence) { +static int check_lseek(int fd, off64_t offset, int whence) { while (true) { off64_t ret = lseek64(fd, offset, whence); if (ret < 0) { if (errno != EINTR) { fprintf(stderr, "lseek64 failed: %s\n", strerror(errno)); - exit(1); + return -1; } } else { break; } } + return 0; } static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) { @@ -146,14 +185,21 @@ static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { if (rss->p_remain <= 0) { fprintf(stderr, "range sink write overrun"); - exit(1); + return 0; } ssize_t written = 0; while (size > 0) { size_t write_now = size; - if (rss->p_remain < write_now) write_now = rss->p_remain; - writeblock(rss->fd, data, write_now); + + if (rss->p_remain < write_now) { + write_now = rss->p_remain; + } + + if (write_all(rss->fd, data, write_now) == -1) { + break; + } + data += write_now; size -= write_now; @@ -164,12 +210,17 @@ static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { // move to the next block ++rss->p_block; if (rss->p_block < rss->tgt->count) { - rss->p_remain = (rss->tgt->pos[rss->p_block*2+1] - rss->tgt->pos[rss->p_block*2]) * BLOCKSIZE; - check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, SEEK_SET); + rss->p_remain = (rss->tgt->pos[rss->p_block * 2 + 1] - + rss->tgt->pos[rss->p_block * 2]) * BLOCKSIZE; + + if (check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, + SEEK_SET) == -1) { + break; + } } else { // we can't write any more; return how many bytes have // been written so far. - return written; + break; } } } @@ -245,6 +296,58 @@ static void* unzip_new_data(void* cookie) { return NULL; } +static int ReadBlocks(RangeSet* src, uint8_t* buffer, int fd) { + int i; + size_t p = 0; + size_t size; + + if (!src || !buffer) { + return -1; + } + + for (i = 0; i < src->count; ++i) { + if (check_lseek(fd, (off64_t) src->pos[i * 2] * BLOCKSIZE, SEEK_SET) == -1) { + return -1; + } + + size = (src->pos[i * 2 + 1] - src->pos[i * 2]) * BLOCKSIZE; + + if (read_all(fd, buffer + p, size) == -1) { + return -1; + } + + p += size; + } + + return 0; +} + +static int WriteBlocks(RangeSet* tgt, uint8_t* buffer, int fd) { + int i; + size_t p = 0; + size_t size; + + if (!tgt || !buffer) { + return -1; + } + + for (i = 0; i < tgt->count; ++i) { + if (check_lseek(fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET) == -1) { + return -1; + } + + size = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * BLOCKSIZE; + + if (write_all(fd, buffer + p, size) == -1) { + return -1; + } + + p += size; + } + + return 0; +} + // Do a source/target load for move/bsdiff/imgdiff in version 1. // 'wordsave' is the save_ptr of a strtok_r()-in-progress. We expect // to parse the remainder of the string as: @@ -255,30 +358,506 @@ static void* unzip_new_data(void* cookie) { // it to make it larger if necessary. The target ranges are returned // in *tgt, if tgt is non-NULL. -static void LoadSrcTgtVersion1(char* wordsave, RangeSet** tgt, int* src_blocks, +static int LoadSrcTgtVersion1(char** wordsave, RangeSet** tgt, int* src_blocks, uint8_t** buffer, size_t* buffer_alloc, int fd) { char* word; + int rc; - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); RangeSet* src = parse_range(word); if (tgt != NULL) { - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); *tgt = parse_range(word); } allocate(src->size * BLOCKSIZE, buffer, buffer_alloc); - size_t p = 0; - int i; - for (i = 0; i < src->count; ++i) { - check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET); - size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE; - readblock(fd, *buffer+p, sz); - p += sz; - } - + rc = ReadBlocks(src, *buffer, fd); *src_blocks = src->size; + free(src); + return rc; +} + +static int VerifyBlocks(const char *expected, const uint8_t *buffer, + size_t blocks, int printerror) { + char* hexdigest = NULL; + int rc = -1; + uint8_t digest[SHA_DIGEST_SIZE]; + + if (!expected || !buffer) { + return rc; + } + + SHA_hash(buffer, blocks * BLOCKSIZE, digest); + hexdigest = PrintSha1(digest); + + if (hexdigest != NULL) { + rc = strcmp(expected, hexdigest); + + if (rc != 0 && printerror) { + fprintf(stderr, "failed to verify blocks (expected %s, read %s)\n", + expected, hexdigest); + } + + free(hexdigest); + } + + return rc; +} + +static char* GetStashFileName(const char* base, const char* id, const char* postfix) { + char* fn; + int len; + int res; + + if (base == NULL) { + return NULL; + } + + if (id == NULL) { + id = ""; + } + + if (postfix == NULL) { + postfix = ""; + } + + len = strlen(STASH_DIRECTORY_BASE) + 1 + strlen(base) + 1 + strlen(id) + strlen(postfix) + 1; + fn = malloc(len); + + if (fn == NULL) { + fprintf(stderr, "failed to malloc %d bytes for fn\n", len); + return NULL; + } + + res = snprintf(fn, len, STASH_DIRECTORY_BASE "/%s/%s%s", base, id, postfix); + + if (res < 0 || res >= len) { + fprintf(stderr, "failed to format file name (return value %d)\n", res); + free(fn); + return NULL; + } + + return fn; +} + +typedef void (*StashCallback)(const char*, void*); + +// Does a best effort enumeration of stash files. Ignores possible non-file +// items in the stash directory and continues despite of errors. Calls the +// 'callback' function for each file and passes 'data' to the function as a +// parameter. + +static void EnumerateStash(const char* dirname, StashCallback callback, void* data) { + char* fn; + DIR* directory; + int len; + int res; + struct dirent* item; + + if (dirname == NULL || callback == NULL) { + return; + } + + directory = opendir(dirname); + + if (directory == NULL) { + if (errno != ENOENT) { + fprintf(stderr, "opendir \"%s\" failed: %s\n", dirname, strerror(errno)); + } + return; + } + + while ((item = readdir(directory)) != NULL) { + if (item->d_type != DT_REG) { + continue; + } + + len = strlen(dirname) + 1 + strlen(item->d_name) + 1; + fn = malloc(len); + + if (fn == NULL) { + fprintf(stderr, "failed to malloc %d bytes for fn\n", len); + continue; + } + + res = snprintf(fn, len, "%s/%s", dirname, item->d_name); + + if (res < 0 || res >= len) { + fprintf(stderr, "failed to format file name (return value %d)\n", res); + free(fn); + continue; + } + + callback(fn, data); + free(fn); + } + + if (closedir(directory) == -1) { + fprintf(stderr, "closedir \"%s\" failed: %s\n", dirname, strerror(errno)); + } +} + +static void UpdateFileSize(const char* fn, void* data) { + int* size = (int*) data; + struct stat st; + + if (!fn || !data) { + return; + } + + if (stat(fn, &st) == -1) { + fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno)); + return; + } + + *size += st.st_size; +} + +// Deletes the stash directory and all files in it. Assumes that it only +// contains files. There is nothing we can do about unlikely, but possible +// errors, so they are merely logged. + +static void DeleteFile(const char* fn, void* data) { + if (fn) { + fprintf(stderr, "deleting %s\n", fn); + + if (unlink(fn) == -1 && errno != ENOENT) { + fprintf(stderr, "unlink \"%s\" failed: %s\n", fn, strerror(errno)); + } + } +} + +static void DeletePartial(const char* fn, void* data) { + if (fn && strstr(fn, ".partial") != NULL) { + DeleteFile(fn, data); + } +} + +static void DeleteStash(const char* base) { + char* dirname; + + if (base == NULL) { + return; + } + + dirname = GetStashFileName(base, NULL, NULL); + + if (dirname == NULL) { + return; + } + + fprintf(stderr, "deleting stash %s\n", base); + EnumerateStash(dirname, DeleteFile, NULL); + + if (rmdir(dirname) == -1) { + if (errno != ENOENT && errno != ENOTDIR) { + fprintf(stderr, "rmdir \"%s\" failed: %s\n", dirname, strerror(errno)); + } + } + + free(dirname); +} + +static int LoadStash(const char* base, const char* id, int verify, int* blocks, uint8_t** buffer, + size_t* buffer_alloc, int printnoent) { + char *fn = NULL; + int blockcount = 0; + int fd = -1; + int rc = -1; + int res; + struct stat st; + + if (!base || !id || !buffer || !buffer_alloc) { + goto lsout; + } + + if (!blocks) { + blocks = &blockcount; + } + + fn = GetStashFileName(base, id, NULL); + + if (fn == NULL) { + goto lsout; + } + + res = stat(fn, &st); + + if (res == -1) { + if (errno != ENOENT || printnoent) { + fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno)); + } + goto lsout; + } + + fprintf(stderr, " loading %s\n", fn); + + if ((st.st_size % BLOCKSIZE) != 0) { + fprintf(stderr, "%s size %zd not multiple of block size %d", fn, st.st_size, BLOCKSIZE); + goto lsout; + } + + fd = TEMP_FAILURE_RETRY(open(fn, O_RDONLY)); + + if (fd == -1) { + fprintf(stderr, "open \"%s\" failed: %s\n", fn, strerror(errno)); + goto lsout; + } + + allocate(st.st_size, buffer, buffer_alloc); + + if (read_all(fd, *buffer, st.st_size) == -1) { + goto lsout; + } + + *blocks = st.st_size / BLOCKSIZE; + + if (verify && VerifyBlocks(id, *buffer, *blocks, 1) != 0) { + fprintf(stderr, "unexpected contents in %s\n", fn); + DeleteFile(fn, NULL); + goto lsout; + } + + rc = 0; + +lsout: + if (fd != -1) { + TEMP_FAILURE_RETRY(close(fd)); + } + + if (fn) { + free(fn); + } + + return rc; +} + +static int WriteStash(const char* base, const char* id, int blocks, uint8_t* buffer, + int checkspace, int *exists) { + char *fn = NULL; + char *cn = NULL; + int fd = -1; + int rc = -1; + int res; + struct stat st; + + if (base == NULL || buffer == NULL) { + goto wsout; + } + + if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) { + fprintf(stderr, "not enough space to write stash\n"); + goto wsout; + } + + fn = GetStashFileName(base, id, ".partial"); + cn = GetStashFileName(base, id, NULL); + + if (fn == NULL || cn == NULL) { + goto wsout; + } + + if (exists) { + res = stat(cn, &st); + + if (res == 0) { + // The file already exists and since the name is the hash of the contents, + // it's safe to assume the contents are identical (accidental hash collisions + // are unlikely) + fprintf(stderr, " skipping %d existing blocks in %s\n", blocks, cn); + *exists = 1; + rc = 0; + goto wsout; + } + + *exists = 0; + } + + fprintf(stderr, " writing %d blocks to %s\n", blocks, cn); + + fd = TEMP_FAILURE_RETRY(open(fn, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, STASH_FILE_MODE)); + + if (fd == -1) { + fprintf(stderr, "failed to create \"%s\": %s\n", fn, strerror(errno)); + goto wsout; + } + + if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) { + goto wsout; + } + + if (fsync(fd) == -1) { + fprintf(stderr, "fsync \"%s\" failed: %s\n", fn, strerror(errno)); + goto wsout; + } + + if (rename(fn, cn) == -1) { + fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", fn, cn, strerror(errno)); + goto wsout; + } + + rc = 0; + +wsout: + if (fd != -1) { + TEMP_FAILURE_RETRY(close(fd)); + } + + if (fn) { + free(fn); + } + + if (cn) { + free(cn); + } + + return rc; +} + +// Creates a directory for storing stash files and checks if the /cache partition +// hash enough space for the expected amount of blocks we need to store. Returns +// >0 if we created the directory, zero if it existed already, and <0 of failure. + +static int CreateStash(State* state, int maxblocks, const char* blockdev, char** base) { + char* dirname = NULL; + const uint8_t* digest; + int rc = -1; + int res; + int size = 0; + SHA_CTX ctx; + struct stat st; + + if (blockdev == NULL || base == NULL) { + goto csout; + } + + // Stash directory should be different for each partition to avoid conflicts + // when updating multiple partitions at the same time, so we use the hash of + // the block device name as the base directory + SHA_init(&ctx); + SHA_update(&ctx, blockdev, strlen(blockdev)); + digest = SHA_final(&ctx); + *base = PrintSha1(digest); + + if (*base == NULL) { + goto csout; + } + + dirname = GetStashFileName(*base, NULL, NULL); + + if (dirname == NULL) { + goto csout; + } + + res = stat(dirname, &st); + + if (res == -1 && errno != ENOENT) { + ErrorAbort(state, "stat \"%s\" failed: %s\n", dirname, strerror(errno)); + goto csout; + } else if (res != 0) { + fprintf(stderr, "creating stash %s\n", dirname); + res = mkdir(dirname, STASH_DIRECTORY_MODE); + + if (res != 0) { + ErrorAbort(state, "mkdir \"%s\" failed: %s\n", dirname, strerror(errno)); + goto csout; + } + + if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) { + ErrorAbort(state, "not enough space for stash\n"); + goto csout; + } + + rc = 1; // Created directory + goto csout; + } + + fprintf(stderr, "using existing stash %s\n", dirname); + + // If the directory already exists, calculate the space already allocated to + // stash files and check if there's enough for all required blocks. Delete any + // partially completed stash files first. + + EnumerateStash(dirname, DeletePartial, NULL); + EnumerateStash(dirname, UpdateFileSize, &size); + + size = (maxblocks * BLOCKSIZE) - size; + + if (size > 0 && CacheSizeCheck(size) != 0) { + ErrorAbort(state, "not enough space for stash (%d more needed)\n", size); + goto csout; + } + + rc = 0; // Using existing directory + +csout: + if (dirname) { + free(dirname); + } + + return rc; +} + +static int SaveStash(const char* base, char** wordsave, uint8_t** buffer, size_t* buffer_alloc, + int fd, int usehash, int* isunresumable) { + char *id = NULL; + int res = -1; + int blocks = 0; + + if (!wordsave || !buffer || !buffer_alloc || !isunresumable) { + return -1; + } + + id = strtok_r(NULL, " ", wordsave); + + if (id == NULL) { + fprintf(stderr, "missing id field in stash command\n"); + return -1; + } + + if (usehash && LoadStash(base, id, 1, &blocks, buffer, buffer_alloc, 0) == 0) { + // Stash file already exists and has expected contents. Do not + // read from source again, as the source may have been already + // overwritten during a previous attempt. + return 0; + } + + if (LoadSrcTgtVersion1(wordsave, NULL, &blocks, buffer, buffer_alloc, fd) == -1) { + return -1; + } + + if (usehash && VerifyBlocks(id, *buffer, blocks, 1) != 0) { + // Source blocks have unexpected contents. If we actually need this + // data later, this is an unrecoverable error. However, the command + // that uses the data may have already completed previously, so the + // possible failure will occur during source block verification. + fprintf(stderr, "failed to load source blocks for stash %s\n", id); + return 0; + } + + fprintf(stderr, "stashing %d blocks to %s\n", blocks, id); + return WriteStash(base, id, blocks, *buffer, 0, NULL); +} + +static int FreeStash(const char* base, const char* id) { + char *fn = NULL; + + if (base == NULL || id == NULL) { + return -1; + } + + fn = GetStashFileName(base, id, NULL); + + if (fn == NULL) { + return -1; + } + + DeleteFile(fn, NULL); + free(fn); + + return 0; } static void MoveRange(uint8_t* dest, RangeSet* locs, const uint8_t* source) { @@ -312,64 +891,614 @@ static void MoveRange(uint8_t* dest, RangeSet* locs, const uint8_t* source) { // On return, buffer is filled with the loaded source data (rearranged // and combined with stashed data as necessary). buffer may be // reallocated if needed to accommodate the source data. *tgt is the -// target RangeSet. Any stashes required are taken from stash_table -// and free()'d after being used. +// target RangeSet. Any stashes required are loaded using LoadStash. -static void LoadSrcTgtVersion2(char* wordsave, RangeSet** tgt, int* src_blocks, +static int LoadSrcTgtVersion2(char** wordsave, RangeSet** tgt, int* src_blocks, uint8_t** buffer, size_t* buffer_alloc, int fd, - uint8_t** stash_table) { + const char* stashbase, int* overlap) { char* word; + char* colonsave; + char* colon; + int id; + int res; + RangeSet* locs; + size_t stashalloc = 0; + uint8_t* stash = NULL; if (tgt != NULL) { - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); *tgt = parse_range(word); } - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); *src_blocks = strtol(word, NULL, 0); allocate(*src_blocks * BLOCKSIZE, buffer, buffer_alloc); - word = strtok_r(NULL, " ", &wordsave); + word = strtok_r(NULL, " ", wordsave); if (word[0] == '-' && word[1] == '\0') { // no source ranges, only stashes } else { RangeSet* src = parse_range(word); + res = ReadBlocks(src, *buffer, fd); - size_t p = 0; - int i; - for (i = 0; i < src->count; ++i) { - check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET); - size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE; - readblock(fd, *buffer+p, sz); - p += sz; + if (overlap && tgt) { + *overlap = range_overlaps(src, *tgt); } + free(src); - word = strtok_r(NULL, " ", &wordsave); + if (res == -1) { + return -1; + } + + word = strtok_r(NULL, " ", wordsave); if (word == NULL) { // no stashes, only source range - return; + return 0; } - RangeSet* locs = parse_range(word); + locs = parse_range(word); MoveRange(*buffer, locs, *buffer); + free(locs); } - while ((word = strtok_r(NULL, " ", &wordsave)) != NULL) { + while ((word = strtok_r(NULL, " ", wordsave)) != NULL) { // Each word is a an index into the stash table, a colon, and // then a rangeset describing where in the source block that // stashed data should go. - char* colonsave = NULL; - char* colon = strtok_r(word, ":", &colonsave); - int stash_id = strtol(colon, NULL, 0); + colonsave = NULL; + colon = strtok_r(word, ":", &colonsave); + + res = LoadStash(stashbase, colon, 0, NULL, &stash, &stashalloc, 1); + + if (res == -1) { + // These source blocks will fail verification if used later, but we + // will let the caller decide if this is a fatal failure + fprintf(stderr, "failed to load stash %s\n", colon); + continue; + } + colon = strtok_r(NULL, ":", &colonsave); - RangeSet* locs = parse_range(colon); - MoveRange(*buffer, locs, stash_table[stash_id]); - free(stash_table[stash_id]); - stash_table[stash_id] = NULL; + locs = parse_range(colon); + + MoveRange(*buffer, locs, stash); free(locs); } + + if (stash) { + free(stash); + } + + return 0; +} + +// Parameters for transfer list command functions +typedef struct { + char* cmdname; + char* cpos; + char* freestash; + char* stashbase; + int canwrite; + int createdstash; + int fd; + int foundwrites; + int isunresumable; + int version; + int written; + NewThreadInfo nti; + pthread_t thread; + size_t bufsize; + uint8_t* buffer; + uint8_t* patch_start; +} CommandParameters; + +// Do a source/target load for move/bsdiff/imgdiff in version 3. +// +// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which +// tells the function whether to expect separate source and targe block hashes, or +// if they are both the same and only one hash should be expected, and +// 'isunresumable', which receives a non-zero value if block verification fails in +// a way that the update cannot be resumed anymore. +// +// If the function is unable to load the necessary blocks or their contents don't +// match the hashes, the return value is -1 and the command should be aborted. +// +// If the return value is 1, the command has already been completed according to +// the contents of the target blocks, and should not be performed again. +// +// If the return value is 0, source blocks have expected content and the command +// can be performed. + +static int LoadSrcTgtVersion3(CommandParameters* params, RangeSet** tgt, int* src_blocks, + int onehash, int* overlap) { + char* srchash = NULL; + char* tgthash = NULL; + int stash_exists = 0; + int overlap_blocks = 0; + int rc = -1; + uint8_t* tgtbuffer = NULL; + + if (!params|| !tgt || !src_blocks || !overlap) { + goto v3out; + } + + srchash = strtok_r(NULL, " ", ¶ms->cpos); + + if (srchash == NULL) { + fprintf(stderr, "missing source hash\n"); + goto v3out; + } + + if (onehash) { + tgthash = srchash; + } else { + tgthash = strtok_r(NULL, " ", ¶ms->cpos); + + if (tgthash == NULL) { + fprintf(stderr, "missing target hash\n"); + goto v3out; + } + } + + if (LoadSrcTgtVersion2(¶ms->cpos, tgt, src_blocks, ¶ms->buffer, ¶ms->bufsize, + params->fd, params->stashbase, overlap) == -1) { + goto v3out; + } + + tgtbuffer = (uint8_t*) malloc((*tgt)->size * BLOCKSIZE); + + if (tgtbuffer == NULL) { + fprintf(stderr, "failed to allocate %d bytes\n", (*tgt)->size * BLOCKSIZE); + goto v3out; + } + + if (ReadBlocks(*tgt, tgtbuffer, params->fd) == -1) { + goto v3out; + } + + if (VerifyBlocks(tgthash, tgtbuffer, (*tgt)->size, 0) == 0) { + // Target blocks already have expected content, command should be skipped + rc = 1; + goto v3out; + } + + if (VerifyBlocks(srchash, params->buffer, *src_blocks, 1) == 0) { + // If source and target blocks overlap, stash the source blocks so we can + // resume from possible write errors + if (*overlap) { + fprintf(stderr, "stashing %d overlapping blocks to %s\n", *src_blocks, + srchash); + + if (WriteStash(params->stashbase, srchash, *src_blocks, params->buffer, 1, + &stash_exists) != 0) { + fprintf(stderr, "failed to stash overlapping source blocks\n"); + goto v3out; + } + + // Can be deleted when the write has completed + if (!stash_exists) { + params->freestash = srchash; + } + } + + // Source blocks have expected content, command can proceed + rc = 0; + goto v3out; + } + + if (*overlap && LoadStash(params->stashbase, srchash, 1, NULL, ¶ms->buffer, + ¶ms->bufsize, 1) == 0) { + // Overlapping source blocks were previously stashed, command can proceed. + // We are recovering from an interrupted command, so we don't know if the + // stash can safely be deleted after this command. + rc = 0; + goto v3out; + } + + // Valid source data not available, update cannot be resumed + fprintf(stderr, "partition has unexpected contents\n"); + params->isunresumable = 1; + +v3out: + if (tgtbuffer) { + free(tgtbuffer); + } + + return rc; +} + +static int PerformCommandMove(CommandParameters* params) { + int blocks = 0; + int overlap = 0; + int rc = -1; + int status = 0; + RangeSet* tgt = NULL; + + if (!params) { + goto pcmout; + } + + if (params->version == 1) { + status = LoadSrcTgtVersion1(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd); + } else if (params->version == 2) { + status = LoadSrcTgtVersion2(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd, params->stashbase, NULL); + } else if (params->version >= 3) { + status = LoadSrcTgtVersion3(params, &tgt, &blocks, 1, &overlap); + } + + if (status == -1) { + fprintf(stderr, "failed to read blocks for move\n"); + goto pcmout; + } + + if (status == 0) { + params->foundwrites = 1; + } else if (params->foundwrites) { + fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname); + } + + if (params->canwrite) { + if (status == 0) { + fprintf(stderr, " moving %d blocks\n", blocks); + + if (WriteBlocks(tgt, params->buffer, params->fd) == -1) { + goto pcmout; + } + } else { + fprintf(stderr, "skipping %d already moved blocks\n", blocks); + } + + } + + if (params->freestash) { + FreeStash(params->stashbase, params->freestash); + params->freestash = NULL; + } + + params->written += tgt->size; + rc = 0; + +pcmout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandStash(CommandParameters* params) { + if (!params) { + return -1; + } + + return SaveStash(params->stashbase, ¶ms->cpos, ¶ms->buffer, ¶ms->bufsize, + params->fd, (params->version >= 3), ¶ms->isunresumable); +} + +static int PerformCommandFree(CommandParameters* params) { + if (!params) { + return -1; + } + + if (params->createdstash || params->canwrite) { + return FreeStash(params->stashbase, params->cpos); + } + + return 0; +} + +static int PerformCommandZero(CommandParameters* params) { + char* range = NULL; + int i; + int j; + int rc = -1; + RangeSet* tgt = NULL; + + if (!params) { + goto pczout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + fprintf(stderr, "missing target blocks for zero\n"); + goto pczout; + } + + tgt = parse_range(range); + + fprintf(stderr, " zeroing %d blocks\n", tgt->size); + + allocate(BLOCKSIZE, ¶ms->buffer, ¶ms->bufsize); + memset(params->buffer, 0, BLOCKSIZE); + + if (params->canwrite) { + for (i = 0; i < tgt->count; ++i) { + if (check_lseek(params->fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET) == -1) { + goto pczout; + } + + for (j = tgt->pos[i * 2]; j < tgt->pos[i * 2 + 1]; ++j) { + if (write_all(params->fd, params->buffer, BLOCKSIZE) == -1) { + goto pczout; + } + } + } + } + + if (params->cmdname[0] == 'z') { + // Update only for the zero command, as the erase command will call + // this if DEBUG_ERASE is defined. + params->written += tgt->size; + } + + rc = 0; + +pczout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandNew(CommandParameters* params) { + char* range = NULL; + int rc = -1; + RangeSet* tgt = NULL; + RangeSinkState rss; + + if (!params) { + goto pcnout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + goto pcnout; + } + + tgt = parse_range(range); + + if (params->canwrite) { + fprintf(stderr, " writing %d blocks of new data\n", tgt->size); + + rss.fd = params->fd; + rss.tgt = tgt; + rss.p_block = 0; + rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; + + if (check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET) == -1) { + goto pcnout; + } + + pthread_mutex_lock(¶ms->nti.mu); + params->nti.rss = &rss; + pthread_cond_broadcast(¶ms->nti.cv); + + while (params->nti.rss) { + pthread_cond_wait(¶ms->nti.cv, ¶ms->nti.mu); + } + + pthread_mutex_unlock(¶ms->nti.mu); + } + + params->written += tgt->size; + rc = 0; + +pcnout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandDiff(CommandParameters* params) { + char* logparams = NULL; + char* value = NULL; + int blocks = 0; + int overlap = 0; + int rc = -1; + int status = 0; + RangeSet* tgt = NULL; + RangeSinkState rss; + size_t len = 0; + size_t offset = 0; + Value patch_value; + + if (!params) { + goto pcdout; + } + + logparams = strdup(params->cpos); + value = strtok_r(NULL, " ", ¶ms->cpos); + + if (value == NULL) { + fprintf(stderr, "missing patch offset for %s\n", params->cmdname); + goto pcdout; + } + + offset = strtoul(value, NULL, 0); + + value = strtok_r(NULL, " ", ¶ms->cpos); + + if (value == NULL) { + fprintf(stderr, "missing patch length for %s\n", params->cmdname); + goto pcdout; + } + + len = strtoul(value, NULL, 0); + + if (params->version == 1) { + status = LoadSrcTgtVersion1(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd); + } else if (params->version == 2) { + status = LoadSrcTgtVersion2(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd, params->stashbase, NULL); + } else if (params->version >= 3) { + status = LoadSrcTgtVersion3(params, &tgt, &blocks, 0, &overlap); + } + + if (status == -1) { + fprintf(stderr, "failed to read blocks for diff\n"); + goto pcdout; + } + + if (status == 0) { + params->foundwrites = 1; + } else if (params->foundwrites) { + fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname); + } + + if (params->canwrite) { + if (status == 0) { + fprintf(stderr, "patching %d blocks to %d\n", blocks, tgt->size); + + patch_value.type = VAL_BLOB; + patch_value.size = len; + patch_value.data = (char*) (params->patch_start + offset); + + rss.fd = params->fd; + rss.tgt = tgt; + rss.p_block = 0; + rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; + + if (check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET) == -1) { + goto pcdout; + } + + if (params->cmdname[0] == 'i') { // imgdiff + ApplyImagePatch(params->buffer, blocks * BLOCKSIZE, &patch_value, + &RangeSinkWrite, &rss, NULL, NULL); + } else { + ApplyBSDiffPatch(params->buffer, blocks * BLOCKSIZE, &patch_value, + 0, &RangeSinkWrite, &rss, NULL); + } + + // We expect the output of the patcher to fill the tgt ranges exactly. + if (rss.p_block != tgt->count || rss.p_remain != 0) { + fprintf(stderr, "range sink underrun?\n"); + } + } else { + fprintf(stderr, "skipping %d blocks already patched to %d [%s]\n", + blocks, tgt->size, logparams); + } + } + + if (params->freestash) { + FreeStash(params->stashbase, params->freestash); + params->freestash = NULL; + } + + params->written += tgt->size; + rc = 0; + +pcdout: + if (logparams) { + free(logparams); + } + + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandErase(CommandParameters* params) { + char* range = NULL; + int i; + int rc = -1; + RangeSet* tgt = NULL; + struct stat st; + uint64_t blocks[2]; + + if (DEBUG_ERASE) { + return PerformCommandZero(params); + } + + if (!params) { + goto pceout; + } + + if (fstat(params->fd, &st) == -1) { + fprintf(stderr, "failed to fstat device to erase: %s\n", strerror(errno)); + goto pceout; + } + + if (!S_ISBLK(st.st_mode)) { + fprintf(stderr, "not a block device; skipping erase\n"); + rc = 0; + goto pceout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + fprintf(stderr, "missing target blocks for zero\n"); + goto pceout; + } + + tgt = parse_range(range); + + if (params->canwrite) { + fprintf(stderr, " erasing %d blocks\n", tgt->size); + + for (i = 0; i < tgt->count; ++i) { + // offset in bytes + blocks[0] = tgt->pos[i * 2] * (uint64_t) BLOCKSIZE; + // length in bytes + blocks[1] = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * (uint64_t) BLOCKSIZE; + + if (ioctl(params->fd, BLKDISCARD, &blocks) == -1) { + fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno)); + // Continue anyway, nothing we can do + } + } + } + + rc = 0; + +pceout: + if (tgt) { + free(tgt); + } + + return rc; +} + +// Definitions for transfer list command functions +typedef int (*CommandFunction)(CommandParameters*); + +typedef struct { + const char* name; + CommandFunction f; +} Command; + +// CompareCommands and CompareCommandNames are for the hash table + +static int CompareCommands(const void* c1, const void* c2) { + return strcmp(((const Command*) c1)->name, ((const Command*) c2)->name); +} + +static int CompareCommandNames(const void* c1, const void* c2) { + return strcmp(((const Command*) c1)->name, (const char*) c2); +} + +// HashString is used to hash command names for the hash table + +static unsigned int HashString(const char *s) { + unsigned int hash = 0; + if (s) { + while (*s) { + hash = hash * 33 + *s++; + } + } + return hash; } // args: @@ -378,393 +1507,372 @@ static void LoadSrcTgtVersion2(char* wordsave, RangeSet** tgt, int* src_blocks, // - new data stream (filename within package.zip) // - patch stream (filename within package.zip, must be uncompressed) -Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { - Value* blockdev_filename; - Value* transfer_list_value; +static Value* PerformBlockImageUpdate(const char* name, State* state, int argc, Expr* argv[], + const Command* commands, int cmdcount, int dryrun) { + + char* line = NULL; + char* linesave = NULL; + char* logcmd = NULL; char* transfer_list = NULL; - Value* new_data_fn; - Value* patch_data_fn; - bool success = false; + CommandParameters params; + const Command* cmd = NULL; + const ZipEntry* new_entry = NULL; + const ZipEntry* patch_entry = NULL; + FILE* cmd_pipe = NULL; + HashTable* cmdht = NULL; + int i; + int res; + int rc = -1; + int stash_max_blocks = 0; + int total_blocks = 0; + pthread_attr_t attr; + unsigned int cmdhash; + UpdaterInfo* ui = NULL; + Value* blockdev_filename = NULL; + Value* new_data_fn = NULL; + Value* patch_data_fn = NULL; + Value* transfer_list_value = NULL; + ZipArchive* za = NULL; + + memset(¶ms, 0, sizeof(params)); + params.canwrite = !dryrun; + + fprintf(stderr, "performing %s\n", dryrun ? "verification" : "update"); if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value, - &new_data_fn, &patch_data_fn) < 0) { - return NULL; + &new_data_fn, &patch_data_fn) < 0) { + goto pbiudone; } if (blockdev_filename->type != VAL_STRING) { ErrorAbort(state, "blockdev_filename argument to %s must be string", name); - goto done; + goto pbiudone; } if (transfer_list_value->type != VAL_BLOB) { ErrorAbort(state, "transfer_list argument to %s must be blob", name); - goto done; + goto pbiudone; } if (new_data_fn->type != VAL_STRING) { ErrorAbort(state, "new_data_fn argument to %s must be string", name); - goto done; + goto pbiudone; } if (patch_data_fn->type != VAL_STRING) { ErrorAbort(state, "patch_data_fn argument to %s must be string", name); - goto done; + goto pbiudone; } - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - FILE* cmd_pipe = ui->cmd_pipe; + ui = (UpdaterInfo*) state->cookie; + + if (ui == NULL) { + goto pbiudone; + } + + cmd_pipe = ui->cmd_pipe; + za = ui->package_zip; + + if (cmd_pipe == NULL || za == NULL) { + goto pbiudone; + } - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + patch_entry = mzFindZipEntry(za, patch_data_fn->data); - const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data); if (patch_entry == NULL) { - ErrorAbort(state, "%s(): no file \"%s\" in package", name, patch_data_fn->data); - goto done; + fprintf(stderr, "%s(): no file \"%s\" in package", name, patch_data_fn->data); + goto pbiudone; } - uint8_t* patch_start = ((UpdaterInfo*)(state->cookie))->package_zip_addr + - mzGetZipEntryOffset(patch_entry); + params.patch_start = ui->package_zip_addr + mzGetZipEntryOffset(patch_entry); + new_entry = mzFindZipEntry(za, new_data_fn->data); - const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data); if (new_entry == NULL) { - ErrorAbort(state, "%s(): no file \"%s\" in package", name, new_data_fn->data); - goto done; + fprintf(stderr, "%s(): no file \"%s\" in package", name, new_data_fn->data); + goto pbiudone; } - // The transfer list is a text file containing commands to - // transfer data from one place to another on the target - // partition. We parse it and execute the commands in order: - // - // zero [rangeset] - // - fill the indicated blocks with zeros - // - // new [rangeset] - // - fill the blocks with data read from the new_data file - // - // erase [rangeset] - // - mark the given blocks as empty - // - // move <...> - // bsdiff <patchstart> <patchlen> <...> - // imgdiff <patchstart> <patchlen> <...> - // - read the source blocks, apply a patch (or not in the - // case of move), write result to target blocks. bsdiff or - // imgdiff specifies the type of patch; move means no patch - // at all. - // - // The format of <...> differs between versions 1 and 2; - // see the LoadSrcTgtVersion{1,2}() functions for a - // description of what's expected. - // - // stash <stash_id> <src_range> - // - (version 2 only) load the given source range and stash - // the data in the given slot of the stash table. - // - // The creator of the transfer list will guarantee that no block - // is read (ie, used as the source for a patch or move) after it - // has been written. - // - // In version 2, the creator will guarantee that a given stash is - // loaded (with a stash command) before it's used in a - // move/bsdiff/imgdiff command. - // - // Within one command the source and target ranges may overlap so - // in general we need to read the entire source into memory before - // writing anything to the target blocks. - // - // All the patch data is concatenated into one patch_data file in - // the update package. It must be stored uncompressed because we - // memory-map it in directly from the archive. (Since patches are - // already compressed, we lose very little by not compressing - // their concatenation.) - - pthread_t new_data_thread; - NewThreadInfo nti; - nti.za = za; - nti.entry = new_entry; - nti.rss = NULL; - pthread_mutex_init(&nti.mu, NULL); - pthread_cond_init(&nti.cv, NULL); + params.fd = TEMP_FAILURE_RETRY(open(blockdev_filename->data, O_RDWR)); - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - pthread_create(&new_data_thread, &attr, unzip_new_data, &nti); + if (params.fd == -1) { + fprintf(stderr, "open \"%s\" failed: %s\n", blockdev_filename->data, strerror(errno)); + goto pbiudone; + } - int i, j; + if (params.canwrite) { + params.nti.za = za; + params.nti.entry = new_entry; - char* linesave; - char* wordsave; + pthread_mutex_init(¶ms.nti.mu, NULL); + pthread_cond_init(¶ms.nti.cv, NULL); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - int fd = open(blockdev_filename->data, O_RDWR); - if (fd < 0) { - ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno)); - goto done; + int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); + if (error != 0) { + fprintf(stderr, "pthread_create failed: %s\n", strerror(error)); + goto pbiudone; + } } - char* line; - char* word; + // The data in transfer_list_value is not necessarily null-terminated, so we need + // to copy it to a new buffer and add the null that strtok_r will need. + transfer_list = malloc(transfer_list_value->size + 1); - // The data in transfer_list_value is not necessarily - // null-terminated, so we need to copy it to a new buffer and add - // the null that strtok_r will need. - transfer_list = malloc(transfer_list_value->size+1); if (transfer_list == NULL) { fprintf(stderr, "failed to allocate %zd bytes for transfer list\n", - transfer_list_value->size+1); - exit(1); + transfer_list_value->size + 1); + goto pbiudone; } + memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size); transfer_list[transfer_list_value->size] = '\0'; + // First line in transfer list is the version number line = strtok_r(transfer_list, "\n", &linesave); + params.version = strtol(line, NULL, 0); - int version; - // first line in transfer list is the version number; currently - // there's only version 1. - if (strcmp(line, "1") == 0) { - version = 1; - } else if (strcmp(line, "2") == 0) { - version = 2; - } else { - ErrorAbort(state, "unexpected transfer list version [%s]\n", line); - goto done; + if (params.version < 1 || params.version > 3) { + fprintf(stderr, "unexpected transfer list version [%s]\n", line); + goto pbiudone; } - printf("blockimg version is %d\n", version); - // second line in transfer list is the total number of blocks we - // expect to write. + fprintf(stderr, "blockimg version is %d\n", params.version); + + // Second line in transfer list is the total number of blocks we expect to write line = strtok_r(NULL, "\n", &linesave); - int total_blocks = strtol(line, NULL, 0); - // shouldn't happen, but avoid divide by zero. - if (total_blocks == 0) ++total_blocks; - int blocks_so_far = 0; - - uint8_t** stash_table = NULL; - if (version >= 2) { - // Next line is how many stash entries are needed simultaneously. + total_blocks = strtol(line, NULL, 0); + + if (total_blocks < 0) { + ErrorAbort(state, "unexpected block count [%s]\n", line); + goto pbiudone; + } else if (total_blocks == 0) { + rc = 0; + goto pbiudone; + } + + if (params.version >= 2) { + // Third line is how many stash entries are needed simultaneously + line = strtok_r(NULL, "\n", &linesave); + fprintf(stderr, "maximum stash entries %s\n", line); + + // Fourth line is the maximum number of blocks that will be stashed simultaneously line = strtok_r(NULL, "\n", &linesave); - int stash_entries = strtol(line, NULL, 0); + stash_max_blocks = strtol(line, NULL, 0); - stash_table = (uint8_t**) calloc(stash_entries, sizeof(uint8_t*)); - if (stash_table == NULL) { - fprintf(stderr, "failed to allocate %d-entry stash table\n", stash_entries); - exit(1); + if (stash_max_blocks < 0) { + ErrorAbort(state, "unexpected maximum stash blocks [%s]\n", line); + goto pbiudone; } - // Next line is the maximum number of blocks that will be - // stashed simultaneously. This could be used to verify that - // enough memory or scratch disk space is available. - line = strtok_r(NULL, "\n", &linesave); - int stash_max_blocks = strtol(line, NULL, 0); + if (stash_max_blocks >= 0) { + res = CreateStash(state, stash_max_blocks, blockdev_filename->data, + ¶ms.stashbase); + + if (res == -1) { + goto pbiudone; + } + + params.createdstash = res; + } } - uint8_t* buffer = NULL; - size_t buffer_alloc = 0; + // Build a hash table of the available commands + cmdht = mzHashTableCreate(cmdcount, NULL); - // third and subsequent lines are all individual transfer commands. + for (i = 0; i < cmdcount; ++i) { + cmdhash = HashString(commands[i].name); + mzHashTableLookup(cmdht, cmdhash, (void*) &commands[i], CompareCommands, true); + } + + // Subsequent lines are all individual transfer commands for (line = strtok_r(NULL, "\n", &linesave); line; line = strtok_r(NULL, "\n", &linesave)) { - char* style; - style = strtok_r(line, " ", &wordsave); - - if (strcmp("move", style) == 0) { - RangeSet* tgt; - int src_blocks; - if (version == 1) { - LoadSrcTgtVersion1(wordsave, &tgt, &src_blocks, - &buffer, &buffer_alloc, fd); - } else if (version == 2) { - LoadSrcTgtVersion2(wordsave, &tgt, &src_blocks, - &buffer, &buffer_alloc, fd, stash_table); - } + logcmd = strdup(line); + params.cmdname = strtok_r(line, " ", ¶ms.cpos); - printf(" moving %d blocks\n", src_blocks); + if (params.cmdname == NULL) { + fprintf(stderr, "missing command [%s]\n", line); + goto pbiudone; + } - size_t p = 0; - for (i = 0; i < tgt->count; ++i) { - check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET); - size_t sz = (tgt->pos[i*2+1] - tgt->pos[i*2]) * BLOCKSIZE; - writeblock(fd, buffer+p, sz); - p += sz; - } + cmdhash = HashString(params.cmdname); + cmd = (const Command*) mzHashTableLookup(cmdht, cmdhash, params.cmdname, + CompareCommandNames, false); - blocks_so_far += tgt->size; - fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); - fflush(cmd_pipe); + if (cmd == NULL) { + fprintf(stderr, "unexpected command [%s]\n", params.cmdname); + goto pbiudone; + } - free(tgt); - - } else if (strcmp("stash", style) == 0) { - word = strtok_r(NULL, " ", &wordsave); - int stash_id = strtol(word, NULL, 0); - int src_blocks; - size_t stash_alloc = 0; - - // Even though the "stash" style only appears in version - // 2, the version 1 source loader happens to do exactly - // what we want to read data into the stash_table. - LoadSrcTgtVersion1(wordsave, NULL, &src_blocks, - stash_table + stash_id, &stash_alloc, fd); - - } else if (strcmp("zero", style) == 0 || - (DEBUG_ERASE && strcmp("erase", style) == 0)) { - word = strtok_r(NULL, " ", &wordsave); - RangeSet* tgt = parse_range(word); - - printf(" zeroing %d blocks\n", tgt->size); - - allocate(BLOCKSIZE, &buffer, &buffer_alloc); - memset(buffer, 0, BLOCKSIZE); - for (i = 0; i < tgt->count; ++i) { - check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET); - for (j = tgt->pos[i*2]; j < tgt->pos[i*2+1]; ++j) { - writeblock(fd, buffer, BLOCKSIZE); - } - } + if (cmd->f != NULL && cmd->f(¶ms) == -1) { + fprintf(stderr, "failed to execute command [%s]\n", + logcmd ? logcmd : params.cmdname); + goto pbiudone; + } - if (style[0] == 'z') { // "zero" but not "erase" - blocks_so_far += tgt->size; - fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); - fflush(cmd_pipe); - } + if (logcmd) { + free(logcmd); + logcmd = NULL; + } - free(tgt); - } else if (strcmp("new", style) == 0) { + if (params.canwrite) { + fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks); + fflush(cmd_pipe); + } + } - word = strtok_r(NULL, " ", &wordsave); - RangeSet* tgt = parse_range(word); + if (params.canwrite) { + pthread_join(params.thread, NULL); - printf(" writing %d blocks of new data\n", tgt->size); + fprintf(stderr, "wrote %d blocks; expected %d\n", params.written, total_blocks); + fprintf(stderr, "max alloc needed was %zu\n", params.bufsize); - RangeSinkState rss; - rss.fd = fd; - rss.tgt = tgt; - rss.p_block = 0; - rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; - check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET); + // Delete stash only after successfully completing the update, as it + // may contain blocks needed to complete the update later. + DeleteStash(params.stashbase); + } else { + fprintf(stderr, "verified partition contents; update may be resumed\n"); + } - pthread_mutex_lock(&nti.mu); - nti.rss = &rss; - pthread_cond_broadcast(&nti.cv); - while (nti.rss) { - pthread_cond_wait(&nti.cv, &nti.mu); - } - pthread_mutex_unlock(&nti.mu); + rc = 0; - blocks_so_far += tgt->size; - fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); - fflush(cmd_pipe); +pbiudone: + if (params.fd != -1) { + if (fsync(params.fd) == -1) { + fprintf(stderr, "fsync failed: %s\n", strerror(errno)); + } + TEMP_FAILURE_RETRY(close(params.fd)); + } - free(tgt); - - } else if (strcmp("bsdiff", style) == 0 || - strcmp("imgdiff", style) == 0) { - word = strtok_r(NULL, " ", &wordsave); - size_t patch_offset = strtoul(word, NULL, 0); - word = strtok_r(NULL, " ", &wordsave); - size_t patch_len = strtoul(word, NULL, 0); - - RangeSet* tgt; - int src_blocks; - if (version == 1) { - LoadSrcTgtVersion1(wordsave, &tgt, &src_blocks, - &buffer, &buffer_alloc, fd); - } else if (version == 2) { - LoadSrcTgtVersion2(wordsave, &tgt, &src_blocks, - &buffer, &buffer_alloc, fd, stash_table); - } + if (logcmd) { + free(logcmd); + } - printf(" patching %d blocks to %d\n", src_blocks, tgt->size); + if (cmdht) { + mzHashTableFree(cmdht); + } - Value patch_value; - patch_value.type = VAL_BLOB; - patch_value.size = patch_len; - patch_value.data = (char*)(patch_start + patch_offset); + if (params.buffer) { + free(params.buffer); + } - RangeSinkState rss; - rss.fd = fd; - rss.tgt = tgt; - rss.p_block = 0; - rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; - check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET); + if (transfer_list) { + free(transfer_list); + } - int ret; - if (style[0] == 'i') { // imgdiff - ret = ApplyImagePatch(buffer, src_blocks * BLOCKSIZE, - &patch_value, - &RangeSinkWrite, &rss, NULL, NULL); - } else { - ret = ApplyBSDiffPatch(buffer, src_blocks * BLOCKSIZE, - &patch_value, 0, - &RangeSinkWrite, &rss, NULL); - } + if (blockdev_filename) { + FreeValue(blockdev_filename); + } - if (ret != 0) { - ErrorAbort(state, "patch failed\n"); - goto done; - } + if (transfer_list_value) { + FreeValue(transfer_list_value); + } - // We expect the output of the patcher to fill the tgt ranges exactly. - if (rss.p_block != tgt->count || rss.p_remain != 0) { - ErrorAbort(state, "range sink underrun?\n"); - goto done; - } + if (new_data_fn) { + FreeValue(new_data_fn); + } - blocks_so_far += tgt->size; - fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); - fflush(cmd_pipe); + if (patch_data_fn) { + FreeValue(patch_data_fn); + } - free(tgt); - } else if (!DEBUG_ERASE && strcmp("erase", style) == 0) { - struct stat st; - if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) { - word = strtok_r(NULL, " ", &wordsave); - RangeSet* tgt = parse_range(word); - - printf(" erasing %d blocks\n", tgt->size); - - for (i = 0; i < tgt->count; ++i) { - uint64_t range[2]; - // offset in bytes - range[0] = tgt->pos[i*2] * (uint64_t)BLOCKSIZE; - // len in bytes - range[1] = (tgt->pos[i*2+1] - tgt->pos[i*2]) * (uint64_t)BLOCKSIZE; - - if (ioctl(fd, BLKDISCARD, &range) < 0) { - ErrorAbort(state, " blkdiscard failed: %s\n", strerror(errno)); - goto done; - } - } + // Only delete the stash if the update cannot be resumed, or it's + // a verification run and we created the stash. + if (params.isunresumable || (!params.canwrite && params.createdstash)) { + DeleteStash(params.stashbase); + } - free(tgt); - } else { - printf(" ignoring erase (not block device)\n"); - } - } else { - ErrorAbort(state, "unknown transfer style \"%s\"\n", style); - goto done; - } + if (params.stashbase) { + free(params.stashbase); } - pthread_join(new_data_thread, NULL); - success = true; + return StringValue(rc == 0 ? strdup("t") : strdup("")); +} - free(buffer); - printf("wrote %d blocks; expected %d\n", blocks_so_far, total_blocks); - printf("max alloc needed was %zu\n", buffer_alloc); +// The transfer list is a text file containing commands to +// transfer data from one place to another on the target +// partition. We parse it and execute the commands in order: +// +// zero [rangeset] +// - fill the indicated blocks with zeros +// +// new [rangeset] +// - fill the blocks with data read from the new_data file +// +// erase [rangeset] +// - mark the given blocks as empty +// +// move <...> +// bsdiff <patchstart> <patchlen> <...> +// imgdiff <patchstart> <patchlen> <...> +// - read the source blocks, apply a patch (or not in the +// case of move), write result to target blocks. bsdiff or +// imgdiff specifies the type of patch; move means no patch +// at all. +// +// The format of <...> differs between versions 1 and 2; +// see the LoadSrcTgtVersion{1,2}() functions for a +// description of what's expected. +// +// stash <stash_id> <src_range> +// - (version 2+ only) load the given source range and stash +// the data in the given slot of the stash table. +// +// The creator of the transfer list will guarantee that no block +// is read (ie, used as the source for a patch or move) after it +// has been written. +// +// In version 2, the creator will guarantee that a given stash is +// loaded (with a stash command) before it's used in a +// move/bsdiff/imgdiff command. +// +// Within one command the source and target ranges may overlap so +// in general we need to read the entire source into memory before +// writing anything to the target blocks. +// +// All the patch data is concatenated into one patch_data file in +// the update package. It must be stored uncompressed because we +// memory-map it in directly from the archive. (Since patches are +// already compressed, we lose very little by not compressing +// their concatenation.) +// +// In version 3, commands that read data from the partition (i.e. +// move/bsdiff/imgdiff/stash) have one or more additional hashes +// before the range parameters, which are used to check if the +// command has already been completed and verify the integrity of +// the source data. + +Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) { + // Commands which are not tested are set to NULL to skip them completely + const Command commands[] = { + { "bsdiff", PerformCommandDiff }, + { "erase", NULL }, + { "free", PerformCommandFree }, + { "imgdiff", PerformCommandDiff }, + { "move", PerformCommandMove }, + { "new", NULL }, + { "stash", PerformCommandStash }, + { "zero", NULL } + }; + + // Perform a dry run without writing to test if an update can proceed + return PerformBlockImageUpdate(name, state, argc, argv, commands, + sizeof(commands) / sizeof(commands[0]), 1); +} -done: - free(transfer_list); - FreeValue(blockdev_filename); - FreeValue(transfer_list_value); - FreeValue(new_data_fn); - FreeValue(patch_data_fn); - if (success) { - return StringValue(strdup("t")); - } else { - // NULL will be passed to its caller at Evaluate() and abort the OTA - // process. - return NULL; - } +Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { + const Command commands[] = { + { "bsdiff", PerformCommandDiff }, + { "erase", PerformCommandErase }, + { "free", PerformCommandFree }, + { "imgdiff", PerformCommandDiff }, + { "move", PerformCommandMove }, + { "new", PerformCommandNew }, + { "stash", PerformCommandStash }, + { "zero", PerformCommandZero } + }; + + return PerformBlockImageUpdate(name, state, argc, argv, commands, + sizeof(commands) / sizeof(commands[0]), 0); } Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { @@ -786,7 +1894,7 @@ Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { int fd = open(blockdev_filename->data, O_RDWR); if (fd < 0) { - ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno)); + ErrorAbort(state, "open \"%s\" failed: %s", blockdev_filename->data, strerror(errno)); goto done; } @@ -798,26 +1906,37 @@ Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { int i, j; for (i = 0; i < rs->count; ++i) { - check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET); + if (check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET) == -1) { + ErrorAbort(state, "failed to seek %s: %s", blockdev_filename->data, + strerror(errno)); + goto done; + } + for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) { - readblock(fd, buffer, BLOCKSIZE); + if (read_all(fd, buffer, BLOCKSIZE) == -1) { + ErrorAbort(state, "failed to read %s: %s", blockdev_filename->data, + strerror(errno)); + goto done; + } + SHA_update(&ctx, buffer, BLOCKSIZE); } } digest = SHA_final(&ctx); close(fd); -done: + done: FreeValue(blockdev_filename); FreeValue(ranges); if (digest == NULL) { - return NULL; + return StringValue(strdup("")); } else { return StringValue(PrintSha1(digest)); } } void RegisterBlockImageFunctions() { + RegisterFunction("block_image_verify", BlockImageVerifyFn); RegisterFunction("block_image_update", BlockImageUpdateFn); RegisterFunction("range_sha1", RangeSha1Fn); } diff --git a/updater/install.c b/updater/install.c index 2b2ffb0..01a5dd2 100644 --- a/updater/install.c +++ b/updater/install.c @@ -496,7 +496,7 @@ Value* PackageExtractDirFn(const char* name, State* state, struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default bool success = mzExtractRecursive(za, zip_path, dest_path, - MZ_EXTRACT_FILES_ONLY, ×tamp, + ×tamp, NULL, NULL, sehandle); free(zip_path); free(dest_path); diff --git a/updater/updater.c b/updater/updater.c index 465e123..661f695 100644 --- a/updater/updater.c +++ b/updater/updater.c @@ -17,6 +17,7 @@ #include <stdio.h> #include <unistd.h> #include <stdlib.h> +#include <string.h> #include "edify/expr.h" #include "updater.h" diff --git a/verifier.cpp b/verifier.cpp index eeff95a..61e5adf 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -26,9 +26,10 @@ #include "mincrypt/sha.h" #include "mincrypt/sha256.h" -#include <string.h> -#include <stdio.h> #include <errno.h> +#include <malloc.h> +#include <stdio.h> +#include <string.h> extern RecoveryUI* ui; diff --git a/verifier_test.cpp b/verifier_test.cpp index 10a5dda..82546ed 100644 --- a/verifier_test.cpp +++ b/verifier_test.cpp @@ -14,12 +14,14 @@ * limitations under the License. */ +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> -#include <stdarg.h> +#include <string.h> #include <sys/types.h> #include <sys/stat.h> -#include <fcntl.h> #include "common.h" #include "verifier.h" @@ -122,6 +124,8 @@ RecoveryUI* ui = NULL; // nothing but print. class FakeUI : public RecoveryUI { void Init() { } + void SetStage(int, int) { } + void SetLocale(const char*) { } void SetBackground(Icon icon) { } void SetProgressType(ProgressType determinate) { } @@ -137,6 +141,7 @@ class FakeUI : public RecoveryUI { vfprintf(stderr, fmt, ap); va_end(ap); } + void ShowFile(const char*) { } void StartMenu(const char* const * headers, const char* const * items, int initial_selection) { } |