diff options
-rw-r--r-- | Android.mk | 25 | ||||
-rw-r--r-- | adb_install.cpp | 110 | ||||
-rw-r--r-- | adb_install.h | 24 | ||||
-rw-r--r-- | applypatch/applypatch.c | 64 | ||||
-rw-r--r-- | applypatch/applypatch.h | 4 | ||||
-rw-r--r-- | bootloader.cpp (renamed from bootloader.c) | 0 | ||||
-rw-r--r-- | bootloader.h | 8 | ||||
-rw-r--r-- | common.h | 82 | ||||
-rw-r--r-- | default_device.cpp | 90 | ||||
-rw-r--r-- | default_recovery_ui.c | 73 | ||||
-rw-r--r-- | device.h | 112 | ||||
-rw-r--r-- | etc/init.rc | 19 | ||||
-rw-r--r-- | install.cpp (renamed from install.c) | 96 | ||||
-rw-r--r-- | install.h | 8 | ||||
-rw-r--r-- | minadbd/Android.mk | 32 | ||||
-rw-r--r-- | minadbd/README.txt | 39 | ||||
-rw-r--r-- | minadbd/adb.c | 414 | ||||
-rw-r--r-- | minadbd/adb.h | 418 | ||||
-rw-r--r-- | minadbd/fdevent.c | 695 | ||||
-rw-r--r-- | minadbd/fdevent.h | 83 | ||||
-rw-r--r-- | minadbd/mutex_list.h | 26 | ||||
-rw-r--r-- | minadbd/services.c | 162 | ||||
-rw-r--r-- | minadbd/sockets.c | 730 | ||||
-rw-r--r-- | minadbd/sysdeps.h | 494 | ||||
-rw-r--r-- | minadbd/transport.c | 824 | ||||
-rw-r--r-- | minadbd/transport.h | 26 | ||||
-rw-r--r-- | minadbd/transport_usb.c | 121 | ||||
-rw-r--r-- | minadbd/usb_linux_client.c | 157 | ||||
-rw-r--r-- | minadbd/utils.c | 106 | ||||
-rw-r--r-- | minadbd/utils.h | 68 | ||||
-rw-r--r-- | minelf/Retouch.c | 210 | ||||
-rw-r--r-- | minelf/Retouch.h | 6 | ||||
-rw-r--r-- | minui/minui.h | 8 | ||||
-rw-r--r-- | minzip/DirUtil.h | 8 | ||||
-rw-r--r-- | minzip/Zip.h | 8 | ||||
-rw-r--r-- | mtdutils/flash_image.c | 10 | ||||
-rw-r--r-- | mtdutils/mounts.h | 8 | ||||
-rw-r--r-- | mtdutils/mtdutils.h | 8 | ||||
-rw-r--r-- | recovery.cpp (renamed from recovery.c) | 262 | ||||
-rw-r--r-- | recovery_ui.h | 87 | ||||
-rw-r--r-- | roots.cpp (renamed from roots.c) | 8 | ||||
-rw-r--r-- | roots.h | 8 | ||||
-rw-r--r-- | screen_ui.cpp | 488 | ||||
-rw-r--r-- | screen_ui.h | 110 | ||||
-rw-r--r-- | ui.c | 664 | ||||
-rw-r--r-- | ui.cpp | 225 | ||||
-rw-r--r-- | ui.h | 112 | ||||
-rw-r--r-- | updater/install.c | 134 | ||||
-rw-r--r-- | verifier.cpp (renamed from verifier.c) | 19 | ||||
-rw-r--r-- | verifier_test.cpp (renamed from verifier_test.c) | 41 | ||||
-rwxr-xr-x | verifier_test.sh | 8 |
51 files changed, 6084 insertions, 1458 deletions
diff --git a/Android.mk b/Android.mk index 251c92068..21e6946c2 100644 --- a/Android.mk +++ b/Android.mk @@ -4,12 +4,14 @@ include $(CLEAR_VARS) commands_recovery_local_path := $(LOCAL_PATH) LOCAL_SRC_FILES := \ - recovery.c \ - bootloader.c \ - install.c \ - roots.c \ - ui.c \ - verifier.c + recovery.cpp \ + bootloader.cpp \ + install.cpp \ + roots.cpp \ + ui.cpp \ + screen_ui.cpp \ + verifier.cpp \ + adb_install.cpp LOCAL_MODULE := recovery @@ -40,12 +42,12 @@ endif # HAVE_SELINUX LOCAL_MODULE_TAGS := eng ifeq ($(TARGET_RECOVERY_UI_LIB),) - LOCAL_SRC_FILES += default_recovery_ui.c + LOCAL_SRC_FILES += default_device.cpp else LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) endif -LOCAL_STATIC_LIBRARIES += libext4_utils libz -LOCAL_STATIC_LIBRARIES += libminzip libunz libmtdutils libmincrypt +LOCAL_STATIC_LIBRARIES += libext4_utils +LOCAL_STATIC_LIBRARIES += libminzip libz libmtdutils libmincrypt libminadbd LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils LOCAL_STATIC_LIBRARIES += libstdc++ libc @@ -62,7 +64,7 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) -LOCAL_SRC_FILES := verifier_test.c verifier.c +LOCAL_SRC_FILES := verifier_test.cpp verifier.cpp ui.cpp LOCAL_MODULE := verifier_test @@ -70,7 +72,7 @@ LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := tests -LOCAL_STATIC_LIBRARIES := libmincrypt libcutils libstdc++ libc +LOCAL_STATIC_LIBRARIES := libmincrypt libminui libcutils libstdc++ libc include $(BUILD_EXECUTABLE) @@ -78,6 +80,7 @@ include $(BUILD_EXECUTABLE) include $(commands_recovery_local_path)/minui/Android.mk include $(commands_recovery_local_path)/minelf/Android.mk include $(commands_recovery_local_path)/minzip/Android.mk +include $(commands_recovery_local_path)/minadbd/Android.mk include $(commands_recovery_local_path)/mtdutils/Android.mk include $(commands_recovery_local_path)/tools/Android.mk include $(commands_recovery_local_path)/edify/Android.mk diff --git a/adb_install.cpp b/adb_install.cpp new file mode 100644 index 000000000..a226ea571 --- /dev/null +++ b/adb_install.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <unistd.h> +#include <dirent.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <signal.h> +#include <fcntl.h> + +#include "ui.h" +#include "cutils/properties.h" +#include "install.h" +#include "common.h" +#include "adb_install.h" +extern "C" { +#include "minadbd/adb.h" +} + +static RecoveryUI* ui = NULL; + +static void +set_usb_driver(bool enabled) { + int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY); + if (fd < 0) { + ui->Print("failed to open driver control: %s\n", strerror(errno)); + return; + } + if (write(fd, enabled ? "1" : "0", 1) < 0) { + ui->Print("failed to set driver control: %s\n", strerror(errno)); + } + if (close(fd) < 0) { + ui->Print("failed to close driver control: %s\n", strerror(errno)); + } +} + +static void +stop_adbd() { + property_set("ctl.stop", "adbd"); + set_usb_driver(false); +} + + +static void +maybe_restart_adbd() { + char value[PROPERTY_VALUE_MAX+1]; + int len = property_get("ro.debuggable", value, NULL); + if (len == 1 && value[0] == '1') { + ui->Print("Restarting adbd...\n"); + set_usb_driver(true); + property_set("ctl.start", "adbd"); + } +} + +int +apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) { + ui = ui_; + + stop_adbd(); + set_usb_driver(true); + + ui->Print("\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload <filename>\"...\n"); + + pid_t child; + if ((child = fork()) == 0) { + execl("/sbin/recovery", "recovery", "--adbd", NULL); + _exit(-1); + } + int status; + // TODO(dougz): there should be a way to cancel waiting for a + // package (by pushing some button combo on the device). For now + // you just have to 'adb sideload' a file that's not a valid + // package, like "/dev/null". + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + ui->Print("status %d\n", WEXITSTATUS(status)); + } + + set_usb_driver(false); + maybe_restart_adbd(); + + struct stat st; + if (stat(ADB_SIDELOAD_FILENAME, &st) != 0) { + if (errno == ENOENT) { + ui->Print("No package received.\n"); + } else { + ui->Print("Error reading package:\n %s\n", strerror(errno)); + } + return INSTALL_ERROR; + } + return install_package(ADB_SIDELOAD_FILENAME, wipe_cache, install_file); +} diff --git a/adb_install.h b/adb_install.h new file mode 100644 index 000000000..a18b712a2 --- /dev/null +++ b/adb_install.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ADB_INSTALL_H +#define _ADB_INSTALL_H + +class RecoveryUI; + +int apply_from_adb(RecoveryUI* h, int* wipe_cache, const char* install_file); + +#endif diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c index 106091386..00004e9a8 100644 --- a/applypatch/applypatch.c +++ b/applypatch/applypatch.c @@ -30,10 +30,16 @@ #include "mtdutils/mtdutils.h" #include "edify/expr.h" -int SaveFileContents(const char* filename, FileContents file); static int LoadPartitionContents(const char* filename, FileContents* file); -int ParseSha1(const char* str, uint8_t* digest); static ssize_t FileSink(unsigned char* data, ssize_t len, void* token); +static int GenerateTarget(FileContents* source_file, + const Value* source_patch_value, + FileContents* copy_file, + const Value* copy_patch_value, + const char* source_filename, + const char* target_filename, + const uint8_t target_sha1[SHA_DIGEST_SIZE], + size_t target_size); static int mtd_partitions_scanned = 0; @@ -113,11 +119,6 @@ static int compare_size_indices(const void* a, const void* b) { } } -void FreeFileContents(FileContents* file) { - if (file) free(file->data); - free(file); -} - // Load the contents of an MTD or EMMC partition into the provided // FileContents. filename should be a string of the form // "MTD:<partition_name>:<size_1>:<sha1_1>:<size_2>:<sha1_2>:..." (or @@ -322,7 +323,7 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { // Save the contents of the given FileContents object under the given // filename. Return 0 on success. -int SaveFileContents(const char* filename, FileContents file) { +int SaveFileContents(const char* filename, const FileContents* file) { int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC); if (fd < 0) { printf("failed to open \"%s\" for write: %s\n", @@ -330,10 +331,10 @@ int SaveFileContents(const char* filename, FileContents file) { return -1; } - ssize_t bytes_written = FileSink(file.data, file.size, &fd); - if (bytes_written != file.size) { + ssize_t bytes_written = FileSink(file->data, file->size, &fd); + if (bytes_written != file->size) { printf("short write of \"%s\" (%ld bytes of %ld) (%s)\n", - filename, (long)bytes_written, (long)file.size, + filename, (long)bytes_written, (long)file->size, strerror(errno)); close(fd); return -1; @@ -341,11 +342,11 @@ int SaveFileContents(const char* filename, FileContents file) { fsync(fd); close(fd); - if (chmod(filename, file.st.st_mode) != 0) { + if (chmod(filename, file->st.st_mode) != 0) { printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); return -1; } - if (chown(filename, file.st.st_uid, file.st.st_gid) != 0) { + if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) { printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); return -1; } @@ -471,7 +472,7 @@ int ParseSha1(const char* str, uint8_t* digest) { // Search an array of sha1 strings for one matching the given sha1. // Return the index of the match on success, or -1 if no match is // found. -int FindMatchingPatch(uint8_t* sha1, char** const patch_sha1_str, +int FindMatchingPatch(uint8_t* sha1, const char** patch_sha1_str, int num_patches) { int i; uint8_t patch_sha1[SHA_DIGEST_SIZE]; @@ -503,6 +504,7 @@ int applypatch_check(const char* filename, "sha1 sums; checking cache\n", filename); free(file.data); + file.data = NULL; // If the source file is missing or corrupted, it might be because // we were killed in the middle of patching it. A copy of it @@ -631,9 +633,10 @@ int applypatch(const char* source_filename, FileContents copy_file; FileContents source_file; + copy_file.data = NULL; + source_file.data = NULL; const Value* source_patch_value = NULL; const Value* copy_patch_value = NULL; - int made_copy = 0; // We try to load the target file into the source_file object. if (LoadFileContents(target_filename, &source_file, @@ -643,6 +646,7 @@ int applypatch(const char* source_filename, // has the desired hash, nothing for us to do. printf("\"%s\" is already target; no patch needed\n", target_filename); + free(source_file.data); return 0; } } @@ -653,6 +657,7 @@ int applypatch(const char* source_filename, // Need to load the source file: either we failed to load the // target file, or we did but it's different from the source file. free(source_file.data); + source_file.data = NULL; LoadFileContents(source_filename, &source_file, RETOUCH_DO_MASK); } @@ -667,6 +672,7 @@ int applypatch(const char* source_filename, if (source_patch_value == NULL) { free(source_file.data); + source_file.data = NULL; printf("source file is bad; trying copy\n"); if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file, @@ -685,16 +691,36 @@ int applypatch(const char* source_filename, if (copy_patch_value == NULL) { // fail. printf("copy file doesn't match source SHA-1s either\n"); + free(copy_file.data); return 1; } } + int result = GenerateTarget(&source_file, source_patch_value, + ©_file, copy_patch_value, + source_filename, target_filename, + target_sha1, target_size); + free(source_file.data); + free(copy_file.data); + + return result; +} + +static int GenerateTarget(FileContents* source_file, + const Value* source_patch_value, + FileContents* copy_file, + const Value* copy_patch_value, + const char* source_filename, + const char* target_filename, + const uint8_t target_sha1[SHA_DIGEST_SIZE], + size_t target_size) { int retry = 1; SHA_CTX ctx; int output; MemorySinkInfo msi; FileContents* source_to_use; char* outname; + int made_copy = 0; // assume that target_filename (eg "/system/app/Foo.apk") is located // on the same filesystem as its top-level directory ("/system"). @@ -723,7 +749,7 @@ int applypatch(const char* source_filename, // We still write the original source to cache, in case // the partition write is interrupted. - if (MakeFreeSpaceOnCache(source_file.size) < 0) { + if (MakeFreeSpaceOnCache(source_file->size) < 0) { printf("not enough free space on /cache\n"); return 1; } @@ -763,7 +789,7 @@ int applypatch(const char* source_filename, return 1; } - if (MakeFreeSpaceOnCache(source_file.size) < 0) { + if (MakeFreeSpaceOnCache(source_file->size) < 0) { printf("not enough free space on /cache\n"); return 1; } @@ -782,10 +808,10 @@ int applypatch(const char* source_filename, const Value* patch; if (source_patch_value != NULL) { - source_to_use = &source_file; + source_to_use = source_file; patch = source_patch_value; } else { - source_to_use = ©_file; + source_to_use = copy_file; patch = copy_patch_value; } diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h index a78c89bfe..fb58843ba 100644 --- a/applypatch/applypatch.h +++ b/applypatch/applypatch.h @@ -62,9 +62,9 @@ int applypatch_check(const char* filename, int LoadFileContents(const char* filename, FileContents* file, int retouch_flag); -int SaveFileContents(const char* filename, FileContents file); +int SaveFileContents(const char* filename, const FileContents* file); void FreeFileContents(FileContents* file); -int FindMatchingPatch(uint8_t* sha1, char** const patch_sha1_str, +int FindMatchingPatch(uint8_t* sha1, const char** patch_sha1_str, int num_patches); // bsdiff.c diff --git a/bootloader.c b/bootloader.cpp index baaddc55f..baaddc55f 100644 --- a/bootloader.c +++ b/bootloader.cpp diff --git a/bootloader.h b/bootloader.h index 2e749aa12..712aa1a2d 100644 --- a/bootloader.h +++ b/bootloader.h @@ -17,6 +17,10 @@ #ifndef _RECOVERY_BOOTLOADER_H #define _RECOVERY_BOOTLOADER_H +#ifdef __cplusplus +extern "C" { +#endif + /* Bootloader Message * * This structure describes the content of a block in flash @@ -47,4 +51,8 @@ struct bootloader_message { int get_bootloader_message(struct bootloader_message *out); int set_bootloader_message(const struct bootloader_message *in); +#ifdef __cplusplus +} +#endif + #endif @@ -19,61 +19,12 @@ #include <stdio.h> -// Initialize the graphics system. -void ui_init(); - -// Use KEY_* codes from <linux/input.h> or KEY_DREAM_* from "minui/minui.h". -int ui_wait_key(); // waits for a key/button press, returns the code -int ui_key_pressed(int key); // returns >0 if the code is currently pressed -int ui_text_visible(); // returns >0 if text log is currently visible -int ui_text_ever_visible(); // returns >0 if text log was ever visible -void ui_show_text(int visible); -void ui_clear_key_queue(); - -// Write a message to the on-screen log shown with Alt-L (also to stderr). -// The screen is small, and users may need to report these messages to support, -// so keep the output short and not too cryptic. -void ui_print(const char *fmt, ...) __attribute__((format(printf, 1, 2))); - -// Display some header text followed by a menu of items, which appears -// at the top of the screen (in place of any scrolling ui_print() -// output, if necessary). -void ui_start_menu(char** headers, char** items, int initial_selection); -// Set the menu highlight to the given index, and return it (capped to -// the range [0..numitems). -int ui_menu_select(int sel); -// End menu mode, resetting the text overlay so that ui_print() -// statements will be displayed. -void ui_end_menu(); - -// Set the icon (normally the only thing visible besides the progress bar). -enum { - BACKGROUND_ICON_NONE, - BACKGROUND_ICON_INSTALLING, - BACKGROUND_ICON_ERROR, - NUM_BACKGROUND_ICONS -}; -void ui_set_background(int icon); - -// Show a progress bar and define the scope of the next operation: -// portion - fraction of the progress bar the next operation will use -// seconds - expected time interval (progress bar moves at this minimum rate) -void ui_show_progress(float portion, int seconds); -void ui_set_progress(float fraction); // 0.0 - 1.0 within the defined scope - -// Default allocation of progress bar segments to operations -static const int VERIFICATION_PROGRESS_TIME = 60; -static const float VERIFICATION_PROGRESS_FRACTION = 0.25; -static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; -static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; - -// Show a rotating "barberpole" for ongoing operations. Updates automatically. -void ui_show_indeterminate_progress(); - -// Hide and reset the progress bar. -void ui_reset_progress(); +#ifdef __cplusplus +extern "C" { +#endif -#define LOGE(...) ui_print("E:" __VA_ARGS__) +// TODO: restore ui_print for LOGE +#define LOGE(...) fprintf(stdout, "E:" __VA_ARGS__) #define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__) #define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__) @@ -107,26 +58,11 @@ typedef struct { // (that much). } Volume; -typedef struct { - // number of frames in indeterminate progress bar animation - int indeterminate_frames; - - // number of frames per second to try to maintain when animating - int update_fps; - - // number of frames in installing animation. may be zero for a - // static installation icon. - int installing_frames; - - // the install icon is animated by drawing images containing the - // changing part over the base icon. These specify the - // coordinates of the upper-left corner. - int install_overlay_offset_x; - int install_overlay_offset_y; - -} UIParameters; - // fopen a file, mounting volumes and making parent dirs as necessary. FILE* fopen_path(const char *path, const char *mode); +#ifdef __cplusplus +} +#endif + #endif // RECOVERY_COMMON_H diff --git a/default_device.cpp b/default_device.cpp new file mode 100644 index 000000000..648eaec4e --- /dev/null +++ b/default_device.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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", + NULL }; + +class DefaultUI : public ScreenRecoveryUI { + public: + virtual KeyAction CheckKey(int key) { + if (key == KEY_HOME) { + return TOGGLE; + } + return ENQUEUE; + } +}; + +class DefaultDevice : public Device { + public: + DefaultDevice() : + ui(new DefaultUI) { + } + + 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: + 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; + 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(); +} diff --git a/default_recovery_ui.c b/default_recovery_ui.c deleted file mode 100644 index d56164e7e..000000000 --- a/default_recovery_ui.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <linux/input.h> - -#include "recovery_ui.h" -#include "common.h" - -char* MENU_HEADERS[] = { "Android system recovery utility", - "", - NULL }; - -char* MENU_ITEMS[] = { "reboot system now", - "apply update from external storage", - "wipe data/factory reset", - "wipe cache partition", - "apply update from cache", - NULL }; - -void device_ui_init(UIParameters* ui_parameters) { -} - -int device_recovery_start() { - return 0; -} - -int device_toggle_display(volatile char* key_pressed, int key_code) { - return key_code == KEY_HOME; -} - -int device_reboot_now(volatile char* key_pressed, int key_code) { - return 0; -} - -int device_handle_key(int key_code, int visible) { - if (visible) { - switch (key_code) { - case KEY_DOWN: - case KEY_VOLUMEDOWN: - return HIGHLIGHT_DOWN; - - case KEY_UP: - case KEY_VOLUMEUP: - return HIGHLIGHT_UP; - - case KEY_ENTER: - return SELECT_ITEM; - } - } - - return NO_ACTION; -} - -int device_perform_action(int which) { - return which; -} - -int device_wipe_data() { - return 0; -} diff --git a/device.h b/device.h new file mode 100644 index 000000000..583de75ef --- /dev/null +++ b/device.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _RECOVERY_DEVICE_H +#define _RECOVERY_DEVICE_H + +#include "ui.h" + +class Device { + public: + 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; + + // 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; + // recovery will be at the main menu with it invisible after an + // unsuccessful operation [ie OTA package failure], or if recovery + // is started with no command.) + // + // key is the code of the key just pressed. (You can call + // IsKeyPressed() on the RecoveryUI object you returned from GetUI + // if you want to find out if other keys are held down.) + // + // visible is true if the menu is visible. + // + // Return one of the defined constants below in order to: + // + // - move the menu highlight (kHighlight{Up,Down}) + // - 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; + + enum BuiltinAction { NO_ACTION, REBOOT, APPLY_EXT, APPLY_CACHE, + APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE }; + + // Perform a recovery action selected from the menu. + // 'menu_position' will be the item number of the selected menu + // item, or a non-negative number returned from + // device_handle_key(). The menu will be hidden when this is + // called; implementations can call ui_print() to print + // information to the screen. If the menu position is one of the + // 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; + + static const int kNoAction = -1; + static const int kHighlightUp = -2; + static const int kHighlightDown = -3; + static const int kInvokeItem = -4; + + // Called when we do a wipe data/factory reset operation (either via a + // reboot from the main system with the --wipe_data flag, or when the + // user boots into recovery manually and selects the option from the + // menu.) Can perform whatever device-specific wiping actions are + // needed. Return 0 on success. The userdata and cache partitions + // 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; +}; + +// The device-specific library must define this function (or the +// default one will be used, if there is no device-specific library). +// It returns the Device object that recovery should use. +Device* make_device(); + +#endif // _DEVICE_H diff --git a/etc/init.rc b/etc/init.rc index 554d4e634..89a161e70 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -15,6 +15,18 @@ on init mkdir /cache mount /tmp /tmp tmpfs + chown root shell /tmp + chmod 0775 /tmp + + write /sys/class/android_usb/android0/enable 0 + write /sys/class/android_usb/android0/idVendor 18D1 + write /sys/class/android_usb/android0/idProduct D001 + write /sys/class/android_usb/android0/functions adb + write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer} + write /sys/class/android_usb/android0/iProduct ${ro.product.model} + write /sys/class/android_usb/android0/iSerial ${ro.serialno} + + on boot ifup lo @@ -33,14 +45,7 @@ service adbd /sbin/adbd recovery # Always start adbd on userdebug and eng builds on property:ro.debuggable=1 - write /sys/class/android_usb/android0/enable 0 - write /sys/class/android_usb/android0/idVendor 18D1 - write /sys/class/android_usb/android0/idProduct D001 - write /sys/class/android_usb/android0/functions adb write /sys/class/android_usb/android0/enable 1 - write /sys/class/android_usb/android0/iManufacturer $ro.product.manufacturer - write /sys/class/android_usb/android0/iProduct $ro.product.model - write /sys/class/android_usb/android0/iSerial $ro.serialno start adbd # Restart adbd so it can run as root diff --git a/install.c b/install.cpp index 9d7595e1a..078343332 100644 --- a/install.c +++ b/install.cpp @@ -32,10 +32,19 @@ #include "mtdutils/mtdutils.h" #include "roots.h" #include "verifier.h" +#include "ui.h" + +extern RecoveryUI* ui; #define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" #define PUBLIC_KEYS_FILE "/res/keys" +// Default allocation of progress bar segments to operations +static const int VERIFICATION_PROGRESS_TIME = 60; +static const float VERIFICATION_PROGRESS_FRACTION = 0.25; +static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; +static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; + // 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) { @@ -46,7 +55,7 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { return INSTALL_CORRUPT; } - char* binary = "/tmp/update_binary"; + const char* binary = "/tmp/update_binary"; unlink(binary); int fd = creat(binary, 0755); if (fd < 0) { @@ -100,18 +109,19 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { // - the name of the package zip file. // - char** args = malloc(sizeof(char*) * 5); + const char** args = (const char**)malloc(sizeof(char*) * 5); args[0] = binary; args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk - args[2] = malloc(10); - sprintf(args[2], "%d", pipefd[1]); + char* temp = (char*)malloc(10); + sprintf(temp, "%d", pipefd[1]); + args[2] = temp; args[3] = (char*)path; args[4] = NULL; pid_t pid = fork(); if (pid == 0) { close(pipefd[0]); - execv(binary, args); + execv(binary, (char* const*)args); fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); _exit(-1); } @@ -132,18 +142,17 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { float fraction = strtof(fraction_s, NULL); int seconds = strtol(seconds_s, NULL, 10); - ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), - seconds); + ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds); } else if (strcmp(command, "set_progress") == 0) { char* fraction_s = strtok(NULL, " \n"); float fraction = strtof(fraction_s, NULL); - ui_set_progress(fraction); + ui->SetProgress(fraction); } else if (strcmp(command, "ui_print") == 0) { char* str = strtok(NULL, "\n"); if (str) { - ui_print("%s", str); + ui->Print("%s", str); } else { - ui_print("\n"); + ui->Print("\n"); } } else if (strcmp(command, "wipe_cache") == 0) { *wipe_cache = 1; @@ -188,31 +197,32 @@ load_keys(const char* filename, int* numKeys) { goto exit; } - int i; - bool done = false; - while (!done) { - ++*numKeys; - out = realloc(out, *numKeys * sizeof(RSAPublicKey)); - RSAPublicKey* key = out + (*numKeys - 1); - if (fscanf(f, " { %i , 0x%x , { %u", - &(key->len), &(key->n0inv), &(key->n[0])) != 3) { - goto exit; - } - if (key->len != RSANUMWORDS) { - LOGE("key length (%d) does not match expected size\n", key->len); - goto exit; - } - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; - } - if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; - } - fscanf(f, " } } "); + { + int i; + bool done = false; + while (!done) { + ++*numKeys; + out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey)); + RSAPublicKey* key = out + (*numKeys - 1); + if (fscanf(f, " { %i , 0x%x , { %u", + &(key->len), &(key->n0inv), &(key->n[0])) != 3) { + goto exit; + } + if (key->len != RSANUMWORDS) { + LOGE("key length (%d) does not match expected size\n", key->len); + goto exit; + } + for (i = 1; i < key->len; ++i) { + if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; + } + if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; + for (i = 1; i < key->len; ++i) { + if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; + } + fscanf(f, " } } "); - // if the line ends in a comma, this file has more keys. - switch (fgetc(f)) { + // if the line ends in a comma, this file has more keys. + switch (fgetc(f)) { case ',': // more keys to come. break; @@ -224,6 +234,7 @@ load_keys(const char* filename, int* numKeys) { default: LOGE("unexpected character between keys\n"); goto exit; + } } } @@ -240,9 +251,9 @@ exit: static int really_install_package(const char *path, int* wipe_cache) { - ui_set_background(BACKGROUND_ICON_INSTALLING); - ui_print("Finding update package...\n"); - ui_show_indeterminate_progress(); + ui->SetBackground(RecoveryUI::INSTALLING); + ui->Print("Finding update package...\n"); + ui->SetProgressType(RecoveryUI::INDETERMINATE); LOGI("Update location: %s\n", path); if (ensure_path_mounted(path) != 0) { @@ -250,7 +261,7 @@ really_install_package(const char *path, int* wipe_cache) return INSTALL_CORRUPT; } - ui_print("Opening update package...\n"); + ui->Print("Opening update package...\n"); int numKeys; RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); @@ -261,10 +272,9 @@ really_install_package(const char *path, int* wipe_cache) LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); // Give verification half the progress bar... - ui_print("Verifying update package...\n"); - ui_show_progress( - VERIFICATION_PROGRESS_FRACTION, - VERIFICATION_PROGRESS_TIME); + ui->Print("Verifying update package...\n"); + ui->SetProgressType(RecoveryUI::DETERMINATE); + ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); int err; err = verify_file(path, loadedKeys, numKeys); @@ -286,7 +296,7 @@ really_install_package(const char *path, int* wipe_cache) /* Verify and install the contents of the package. */ - ui_print("Installing update...\n"); + ui->Print("Installing update...\n"); return try_update_binary(path, &zip, wipe_cache); } @@ -19,6 +19,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT }; // Install the package specified by root_path. If INSTALL_SUCCESS is // returned and *wipe_cache is true on exit, caller should wipe the @@ -26,4 +30,8 @@ enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT }; int install_package(const char *root_path, int* wipe_cache, const char* install_file); +#ifdef __cplusplus +} +#endif + #endif // RECOVERY_INSTALL_H_ diff --git a/minadbd/Android.mk b/minadbd/Android.mk new file mode 100644 index 000000000..5a4de6828 --- /dev/null +++ b/minadbd/Android.mk @@ -0,0 +1,32 @@ +# Copyright 2005 The Android Open Source Project +# +# Android.mk for adb +# + +LOCAL_PATH:= $(call my-dir) + +# minadbd library +# ========================================================= + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + adb.c \ + fdevent.c \ + transport.c \ + transport_usb.c \ + sockets.c \ + services.c \ + usb_linux_client.c \ + utils.c + +LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE + +LOCAL_MODULE := libminadbd + +LOCAL_STATIC_LIBRARIES := libcutils libc +include $(BUILD_STATIC_LIBRARY) + + + diff --git a/minadbd/README.txt b/minadbd/README.txt new file mode 100644 index 000000000..c9df484c3 --- /dev/null +++ b/minadbd/README.txt @@ -0,0 +1,39 @@ +The contents of this directory are copied from system/core/adb, with +the following changes: + +adb.c + - much support for host mode and non-linux OS's stripped out; this + version only runs as adbd on the device. + - always setuid/setgid's itself to the shell user + - only uses USB transport + - references to JDWP removed + - main() removed + - all ADB_HOST and win32 code removed + - removed listeners, logging code, background server (for host) + +adb.h + - minor changes to match adb.c changes + +sockets.c + - references to JDWP removed + - ADB_HOST code removed + +services.c + - all services except echo_service (which is commented out) removed + - all host mode support removed + - sideload_service() added; this is the only service supported. It + receives a single blob of data, writes it to a fixed filename, and + makes the process exit. + +Android.mk + - only builds in adbd mode; builds as static library instead of a + standalone executable. + +sysdeps.h + - changes adb_creat() to use O_NOFOLLOW + +transport.c + - removed ADB_HOST code + +transport_usb.c + - removed ADB_HOST code diff --git a/minadbd/adb.c b/minadbd/adb.c new file mode 100644 index 000000000..0e8fd2a7e --- /dev/null +++ b/minadbd/adb.c @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define TRACE_TAG TRACE_ADB + +#include <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> +#include <linux/capability.h> +#include <linux/prctl.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("/dev/android_adb", F_OK) == 0) { + // listen on USB + usb_init(); + } + + if (setgid(AID_SHELL) != 0) { + fprintf(stderr, "failed to setgid to shell\n"); + exit(1); + } + if (setuid(AID_SHELL) != 0) { + fprintf(stderr, "failed to setuid to shell\n"); + exit(1); + } + fprintf(stderr, "userid is %d\n", getuid()); + + D("Event loop starting\n"); + + fdevent_loop(); + + usb_cleanup(); + + return 0; +} diff --git a/minadbd/adb.h b/minadbd/adb.h new file mode 100644 index 000000000..98fa5972e --- /dev/null +++ b/minadbd/adb.h @@ -0,0 +1,418 @@ +/* + * 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 +int init_socket_transport(atransport *t, int s, int port, int local); +void init_usb_transport(atransport *t, usb_handle *usb, int state); + +/* for MacOS X cleanup */ +void close_usb_devices(); + +/* cause new transports to be init'd and added to the list */ +void register_socket_transport(int s, const char *serial, int port, int local); + +/* these should only be used for the "adb disconnect" command */ +void unregister_transport(atransport *t); +void unregister_all_tcp_transports(); + +void register_usb_transport(usb_handle *h, const char *serial, unsigned writeable); + +/* this should only be used for transports with connection_state == CS_NOPERM */ +void unregister_usb_transport(usb_handle *usb); + +atransport *find_transport(const char *serial); +#if ADB_HOST +atransport* find_emulator_transport_by_adb_port(int adb_port); +#endif + +int service_to_fd(const char *name); +#if ADB_HOST +asocket *host_service_to_socket(const char* name, const char *serial); +#endif + +#if !ADB_HOST +typedef enum { + BACKUP, + RESTORE +} BackupOperation; +int backup_service(BackupOperation operation, char* args); +void framebuffer_service(int fd, void *cookie); +void log_service(int fd, void *cookie); +void remount_service(int fd, void *cookie); +char * get_log_file_path(const char * log_name); +#endif + +/* packet allocator */ +apacket *get_apacket(void); +void put_apacket(apacket *p); + +int check_header(apacket *p); +int check_data(apacket *p); + +/* define ADB_TRACE to 1 to enable tracing support, or 0 to disable it */ + +#define ADB_TRACE 1 + +/* IMPORTANT: if you change the following list, don't + * forget to update the corresponding 'tags' table in + * the adb_trace_init() function implemented in adb.c + */ +typedef enum { + TRACE_ADB = 0, /* 0x001 */ + TRACE_SOCKETS, + TRACE_PACKETS, + TRACE_TRANSPORT, + TRACE_RWX, /* 0x010 */ + TRACE_USB, + TRACE_SYNC, + TRACE_SYSDEPS, + TRACE_JDWP, /* 0x100 */ + TRACE_SERVICES, +} AdbTrace; + +#if ADB_TRACE + + extern int adb_trace_mask; + extern unsigned char adb_trace_output_count; + void adb_trace_init(void); + +# define ADB_TRACING ((adb_trace_mask & (1 << TRACE_TAG)) != 0) + + /* you must define TRACE_TAG before using this macro */ +# define D(...) \ + do { \ + if (ADB_TRACING) { \ + int save_errno = errno; \ + adb_mutex_lock(&D_lock); \ + fprintf(stderr, "%s::%s():", \ + __FILE__, __FUNCTION__); \ + errno = save_errno; \ + fprintf(stderr, __VA_ARGS__ ); \ + fflush(stderr); \ + adb_mutex_unlock(&D_lock); \ + errno = save_errno; \ + } \ + } while (0) +# define DR(...) \ + do { \ + if (ADB_TRACING) { \ + int save_errno = errno; \ + adb_mutex_lock(&D_lock); \ + errno = save_errno; \ + fprintf(stderr, __VA_ARGS__ ); \ + fflush(stderr); \ + adb_mutex_unlock(&D_lock); \ + errno = save_errno; \ + } \ + } while (0) +#else +# define D(...) ((void)0) +# define DR(...) ((void)0) +# define ADB_TRACING 0 +#endif + + +#if !TRACE_PACKETS +#define print_packet(tag,p) do {} while (0) +#endif + +#if ADB_HOST_ON_TARGET +/* adb and adbd are coexisting on the target, so use 5038 for adb + * to avoid conflicting with adbd's usage of 5037 + */ +# define DEFAULT_ADB_PORT 5038 +#else +# define DEFAULT_ADB_PORT 5037 +#endif + +#define DEFAULT_ADB_LOCAL_TRANSPORT_PORT 5555 + +#define ADB_CLASS 0xff +#define ADB_SUBCLASS 0x42 +#define ADB_PROTOCOL 0x1 + + +void local_init(int port); +int local_connect(int port); +int local_connect_arbitrary_ports(int console_port, int adb_port); + +/* usb host/client interface */ +void usb_init(); +void usb_cleanup(); +int usb_write(usb_handle *h, const void *data, int len); +int usb_read(usb_handle *h, void *data, int len); +int usb_close(usb_handle *h); +void usb_kick(usb_handle *h); + +/* used for USB device detection */ +#if ADB_HOST +int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol); +#endif + +unsigned host_to_le32(unsigned n); +int adb_commandline(int argc, char **argv); + +int connection_state(atransport *t); + +#define CS_ANY -1 +#define CS_OFFLINE 0 +#define CS_BOOTLOADER 1 +#define CS_DEVICE 2 +#define CS_HOST 3 +#define CS_RECOVERY 4 +#define CS_NOPERM 5 /* Insufficient permissions to communicate with the device */ +#define CS_SIDELOAD 6 + +extern int HOST; +extern int SHELL_EXIT_NOTIFY_FD; + +#define CHUNK_SIZE (64*1024) + +int sendfailmsg(int fd, const char *reason); +int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s); + +#define ADB_SIDELOAD_FILENAME "/tmp/update.zip" + +#endif diff --git a/minadbd/fdevent.c b/minadbd/fdevent.c new file mode 100644 index 000000000..5c374a71b --- /dev/null +++ b/minadbd/fdevent.c @@ -0,0 +1,695 @@ +/* http://frotznet.googlecode.com/svn/trunk/utils/fdevent.c +** +** Copyright 2006, Brian Swetland <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 new file mode 100644 index 000000000..a0ebe2a7e --- /dev/null +++ b/minadbd/fdevent.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FDEVENT_H +#define __FDEVENT_H + +#include <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/mutex_list.h b/minadbd/mutex_list.h new file mode 100644 index 000000000..652dd7341 --- /dev/null +++ b/minadbd/mutex_list.h @@ -0,0 +1,26 @@ +/* the list of mutexes used by adb */ +/* #ifndef __MUTEX_LIST_H + * Do not use an include-guard. This file is included once to declare the locks + * and once in win32 to actually do the runtime initialization. + */ +#ifndef ADB_MUTEX +#error ADB_MUTEX not defined when including this file +#endif +ADB_MUTEX(dns_lock) +ADB_MUTEX(socket_list_lock) +ADB_MUTEX(transport_lock) +#if ADB_HOST +ADB_MUTEX(local_transports_lock) +#endif +ADB_MUTEX(usb_lock) + +// Sadly logging to /data/adb/adb-... is not thread safe. +// After modifying adb.h::D() to count invocations: +// DEBUG(jpa):0:Handling main() +// DEBUG(jpa):1:[ usb_init - starting thread ] +// (Oopsies, no :2:, and matching message is also gone.) +// DEBUG(jpa):3:[ usb_thread - opening device ] +// DEBUG(jpa):4:jdwp control socket started (10) +ADB_MUTEX(D_lock) + +#undef ADB_MUTEX diff --git a/minadbd/services.c b/minadbd/services.c new file mode 100644 index 000000000..aef37f7e4 --- /dev/null +++ b/minadbd/services.c @@ -0,0 +1,162 @@ +/* + * 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "sysdeps.h" +#include "fdevent.h" + +#define TRACE_TAG TRACE_SERVICES +#include "adb.h" + +typedef struct stinfo stinfo; + +struct stinfo { + void (*func)(int fd, void *cookie); + int fd; + void *cookie; +}; + + +void *service_bootstrap_func(void *x) +{ + stinfo *sti = x; + sti->func(sti->fd, sti->cookie); + free(sti); + return 0; +} + +static void sideload_service(int s, void *cookie) +{ + unsigned char buf[4096]; + unsigned count = (unsigned) cookie; + int fd; + + fprintf(stderr, "sideload_service invoked\n"); + + fd = adb_creat(ADB_SIDELOAD_FILENAME, 0644); + if(fd < 0) { + fprintf(stderr, "failed to create %s\n", ADB_SIDELOAD_FILENAME); + adb_close(s); + return; + } + + while(count > 0) { + unsigned xfer = (count > 4096) ? 4096 : count; + if(readx(s, buf, xfer)) break; + if(writex(fd, buf, xfer)) break; + count -= xfer; + } + + if(count == 0) { + writex(s, "OKAY", 4); + } else { + writex(s, "FAIL", 4); + } + adb_close(fd); + adb_close(s); + + if (count == 0) { + fprintf(stderr, "adbd exiting after successful sideload\n"); + sleep(1); + exit(0); + } +} + + +#if 0 +static void echo_service(int fd, void *cookie) +{ + char buf[4096]; + int r; + char *p; + int c; + + for(;;) { + r = read(fd, buf, 4096); + if(r == 0) goto done; + if(r < 0) { + if(errno == EINTR) continue; + else goto done; + } + + c = r; + p = buf; + while(c > 0) { + r = write(fd, p, c); + if(r > 0) { + c -= r; + p += r; + continue; + } + if((r < 0) && (errno == EINTR)) continue; + goto done; + } + } +done: + close(fd); +} +#endif + +static int create_service_thread(void (*func)(int, void *), void *cookie) +{ + stinfo *sti; + adb_thread_t t; + int s[2]; + + if(adb_socketpair(s)) { + printf("cannot create service socket pair\n"); + return -1; + } + + sti = malloc(sizeof(stinfo)); + if(sti == 0) fatal("cannot allocate stinfo"); + sti->func = func; + sti->cookie = cookie; + sti->fd = s[1]; + + if(adb_thread_create( &t, service_bootstrap_func, sti)){ + free(sti); + adb_close(s[0]); + adb_close(s[1]); + printf("cannot create service thread\n"); + return -1; + } + + D("service thread started, %d:%d\n",s[0], s[1]); + return s[0]; +} + +int service_to_fd(const char *name) +{ + int ret = -1; + + if (!strncmp(name, "sideload:", 9)) { + ret = create_service_thread(sideload_service, (void*) atoi(name + 9)); +#if 0 + } else if(!strncmp(name, "echo:", 5)){ + ret = create_service_thread(echo_service, 0); +#endif + } + if (ret >= 0) { + close_on_exec(ret); + } + return ret; +} diff --git a/minadbd/sockets.c b/minadbd/sockets.c new file mode 100644 index 000000000..2dd646159 --- /dev/null +++ b/minadbd/sockets.c @@ -0,0 +1,730 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <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=%d\n", s->id, s->fd, r, r<0?errno:0, avail); + if(r > 0) { + avail -= r; + x += r; + continue; + } + if(r < 0) { + if(errno == EAGAIN) break; + if(errno == EINTR) continue; + } + + /* r = 0 or unhandled error */ + is_eof = 1; + break; + } + D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d\n", + s->id, s->fd, r, is_eof, s->fde.force_eof); + if((avail == MAX_PAYLOAD) || (s->peer == 0)) { + put_apacket(p); + } else { + p->len = MAX_PAYLOAD - avail; + + r = s->peer->enqueue(s->peer, p); + D("LS(%d): fd=%d post peer->enqueue(). r=%d\n", s->id, s->fd, r); + + if(r < 0) { + /* error return means they closed us as a side-effect + ** and we must return immediately. + ** + ** note that if we still have buffered packets, the + ** socket will be placed on the closing socket list. + ** this handler function will be called again + ** to process FDE_WRITE events. + */ + return; + } + + if(r > 0) { + /* if the remote cannot accept further events, + ** we disable notification of READs. They'll + ** be enabled again when we get a call to ready() + */ + fdevent_del(&s->fde, FDE_READ); + } + } + /* Don't allow a forced eof if data is still there */ + if((s->fde.force_eof && !r) || is_eof) { + D(" closing because is_eof=%d r=%d s->fde.force_eof=%d\n", is_eof, r, s->fde.force_eof); + s->close(s); + } + } + + if(ev & FDE_ERROR){ + /* this should be caught be the next read or write + ** catching it here means we may skip the last few + ** bytes of readable data. + */ +// s->close(s); + D("LS(%d): FDE_ERROR (fd=%d)\n", s->id, s->fd); + + return; + } +} + +asocket *create_local_socket(int fd) +{ + asocket *s = calloc(1, sizeof(asocket)); + if (s == NULL) fatal("cannot allocate socket"); + s->fd = fd; + s->enqueue = local_socket_enqueue; + s->ready = local_socket_ready; + s->close = local_socket_close; + install_local_socket(s); + + fdevent_install(&s->fde, fd, local_socket_event_func, s); +/* fdevent_add(&s->fde, FDE_ERROR); */ + //fprintf(stderr, "Created local socket in create_local_socket \n"); + D("LS(%d): created (fd=%d)\n", s->id, s->fd); + return s; +} + +asocket *create_local_service_socket(const char *name) +{ + asocket *s; + int fd; + + fd = service_to_fd(name); + if(fd < 0) return 0; + + s = create_local_socket(fd); + D("LS(%d): bound to '%s' via %d\n", s->id, name, fd); + return s; +} + +/* a Remote socket is used to send/receive data to/from a given transport object +** it needs to be closed when the transport is forcibly destroyed by the user +*/ +typedef struct aremotesocket { + asocket socket; + adisconnect disconnect; +} aremotesocket; + +static int remote_socket_enqueue(asocket *s, apacket *p) +{ + D("entered remote_socket_enqueue RS(%d) WRITE fd=%d peer.fd=%d\n", + s->id, s->fd, s->peer->fd); + p->msg.command = A_WRTE; + p->msg.arg0 = s->peer->id; + p->msg.arg1 = s->id; + p->msg.data_length = p->len; + send_packet(p, s->transport); + return 1; +} + +static void remote_socket_ready(asocket *s) +{ + D("entered remote_socket_ready RS(%d) OKAY fd=%d peer.fd=%d\n", + s->id, s->fd, s->peer->fd); + apacket *p = get_apacket(); + p->msg.command = A_OKAY; + p->msg.arg0 = s->peer->id; + p->msg.arg1 = s->id; + send_packet(p, s->transport); +} + +static void remote_socket_close(asocket *s) +{ + D("entered remote_socket_close RS(%d) CLOSE fd=%d peer->fd=%d\n", + s->id, s->fd, s->peer?s->peer->fd:-1); + apacket *p = get_apacket(); + p->msg.command = A_CLSE; + if(s->peer) { + p->msg.arg0 = s->peer->id; + s->peer->peer = 0; + D("RS(%d) peer->close()ing peer->id=%d peer->fd=%d\n", + s->id, s->peer->id, s->peer->fd); + s->peer->close(s->peer); + } + p->msg.arg1 = s->id; + send_packet(p, s->transport); + D("RS(%d): closed\n", s->id); + remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect ); + free(s); +} + +static void remote_socket_disconnect(void* _s, atransport* t) +{ + asocket* s = _s; + asocket* peer = s->peer; + + D("remote_socket_disconnect RS(%d)\n", s->id); + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect ); + free(s); +} + +asocket *create_remote_socket(unsigned id, atransport *t) +{ + asocket *s = calloc(1, sizeof(aremotesocket)); + adisconnect* dis = &((aremotesocket*)s)->disconnect; + + if (s == NULL) fatal("cannot allocate socket"); + s->id = id; + s->enqueue = remote_socket_enqueue; + s->ready = remote_socket_ready; + s->close = remote_socket_close; + s->transport = t; + + dis->func = remote_socket_disconnect; + dis->opaque = s; + add_transport_disconnect( t, dis ); + D("RS(%d): created\n", s->id); + return s; +} + +void connect_to_remote(asocket *s, const char *destination) +{ + D("Connect_to_remote call RS(%d) fd=%d\n", s->id, s->fd); + apacket *p = get_apacket(); + int len = strlen(destination) + 1; + + if(len > (MAX_PAYLOAD-1)) { + fatal("destination oversized"); + } + + D("LS(%d): connect('%s')\n", s->id, destination); + p->msg.command = A_OPEN; + p->msg.arg0 = s->id; + p->msg.data_length = len; + strcpy((char*) p->data, destination); + send_packet(p, s->transport); +} + + +/* this is used by magic sockets to rig local sockets to + send the go-ahead message when they connect */ +static void local_socket_ready_notify(asocket *s) +{ + s->ready = local_socket_ready; + s->close = local_socket_close; + adb_write(s->fd, "OKAY", 4); + s->ready(s); +} + +/* this is used by magic sockets to rig local sockets to + send the failure message if they are closed before + connected (to avoid closing them without a status message) */ +static void local_socket_close_notify(asocket *s) +{ + s->ready = local_socket_ready; + s->close = local_socket_close; + sendfailmsg(s->fd, "closed"); + s->close(s); +} + +unsigned unhex(unsigned char *s, int len) +{ + unsigned n = 0, c; + + while(len-- > 0) { + switch((c = *s++)) { + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': + case '9': + c -= '0'; + break; + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + c = c - 'a' + 10; + break; + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + c = c - 'A' + 10; + break; + default: + return 0xffffffff; + } + + n = (n << 4) | c; + } + + return n; +} + +/* skip_host_serial return the position in a string + skipping over the 'serial' parameter in the ADB protocol, + where parameter string may be a host:port string containing + the protocol delimiter (colon). */ +char *skip_host_serial(char *service) { + char *first_colon, *serial_end; + + first_colon = strchr(service, ':'); + if (!first_colon) { + /* No colon in service string. */ + return NULL; + } + serial_end = first_colon; + if (isdigit(serial_end[1])) { + serial_end++; + while ((*serial_end) && isdigit(*serial_end)) { + serial_end++; + } + if ((*serial_end) != ':') { + // Something other than numbers was found, reset the end. + serial_end = first_colon; + } + } + return serial_end; +} + +static int smart_socket_enqueue(asocket *s, apacket *p) +{ + unsigned len; + + D("SS(%d): enqueue %d\n", s->id, p->len); + + if(s->pkt_first == 0) { + s->pkt_first = p; + s->pkt_last = p; + } else { + if((s->pkt_first->len + p->len) > MAX_PAYLOAD) { + D("SS(%d): overflow\n", s->id); + put_apacket(p); + goto fail; + } + + memcpy(s->pkt_first->data + s->pkt_first->len, + p->data, p->len); + s->pkt_first->len += p->len; + put_apacket(p); + + p = s->pkt_first; + } + + /* don't bother if we can't decode the length */ + if(p->len < 4) return 0; + + len = unhex(p->data, 4); + if((len < 1) || (len > 1024)) { + D("SS(%d): bad size (%d)\n", s->id, len); + goto fail; + } + + D("SS(%d): len is %d\n", s->id, len ); + /* can't do anything until we have the full header */ + if((len + 4) > p->len) { + D("SS(%d): waiting for %d more bytes\n", s->id, len+4 - p->len); + return 0; + } + + p->data[len + 4] = 0; + + D("SS(%d): '%s'\n", s->id, (char*) (p->data + 4)); + + if (s->transport == NULL) { + char* error_string = "unknown failure"; + s->transport = acquire_one_transport (CS_ANY, + kTransportAny, NULL, &error_string); + + if (s->transport == NULL) { + sendfailmsg(s->peer->fd, error_string); + goto fail; + } + } + + if(!(s->transport) || (s->transport->connection_state == CS_OFFLINE)) { + /* if there's no remote we fail the connection + ** right here and terminate it + */ + sendfailmsg(s->peer->fd, "device offline (x)"); + goto fail; + } + + + /* instrument our peer to pass the success or fail + ** message back once it connects or closes, then + ** detach from it, request the connection, and + ** tear down + */ + s->peer->ready = local_socket_ready_notify; + s->peer->close = local_socket_close_notify; + s->peer->peer = 0; + /* give him our transport and upref it */ + s->peer->transport = s->transport; + + connect_to_remote(s->peer, (char*) (p->data + 4)); + s->peer = 0; + s->close(s); + return 1; + +fail: + /* we're going to close our peer as a side-effect, so + ** return -1 to signal that state to the local socket + ** who is enqueueing against us + */ + s->close(s); + return -1; +} + +static void smart_socket_ready(asocket *s) +{ + D("SS(%d): ready\n", s->id); +} + +static void smart_socket_close(asocket *s) +{ + D("SS(%d): closed\n", s->id); + if(s->pkt_first){ + put_apacket(s->pkt_first); + } + if(s->peer) { + s->peer->peer = 0; + s->peer->close(s->peer); + s->peer = 0; + } + free(s); +} + +asocket *create_smart_socket(void (*action_cb)(asocket *s, const char *act)) +{ + D("Creating smart socket \n"); + asocket *s = calloc(1, sizeof(asocket)); + if (s == NULL) fatal("cannot allocate socket"); + s->enqueue = smart_socket_enqueue; + s->ready = smart_socket_ready; + s->close = smart_socket_close; + s->extra = action_cb; + + D("SS(%d): created %p\n", s->id, action_cb); + return s; +} + +void smart_socket_action(asocket *s, const char *act) +{ + +} + +void connect_to_smartsocket(asocket *s) +{ + D("Connecting to smart socket \n"); + asocket *ss = create_smart_socket(smart_socket_action); + s->peer = ss; + ss->peer = s; + s->ready(s); +} diff --git a/minadbd/sysdeps.h b/minadbd/sysdeps.h new file mode 100644 index 000000000..800ddb753 --- /dev/null +++ b/minadbd/sysdeps.h @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* this file contains system-dependent definitions used by ADB + * they're related to threads, sockets and file descriptors + */ +#ifndef _ADB_SYSDEPS_H +#define _ADB_SYSDEPS_H + +#ifdef __CYGWIN__ +# undef _WIN32 +#endif + +#ifdef _WIN32 + +#include <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 new file mode 100644 index 000000000..ff2004932 --- /dev/null +++ b/minadbd/transport.c @@ -0,0 +1,824 @@ +/* + * 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_socket_transport(int s, const char *serial, int port, int local) +{ + atransport *t = calloc(1, sizeof(atransport)); + char buff[32]; + + if (!serial) { + snprintf(buff, sizeof buff, "T-%p", t); + serial = buff; + } + D("transport: %s init'ing for socket %d, on port %d\n", serial, s, port); + if ( init_socket_transport(t, s, port, local) < 0 ) { + adb_close(s); + free(t); + return; + } + if(serial) { + t->serial = strdup(serial); + } + register_transport(t); +} + +void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable) +{ + atransport *t = calloc(1, sizeof(atransport)); + D("transport: %p init'ing for usb_handle %p (sn='%s')\n", t, usb, + serial ? serial : ""); + init_usb_transport(t, usb, (writeable ? CS_OFFLINE : CS_NOPERM)); + if(serial) { + t->serial = strdup(serial); + } + register_transport(t); +} + +/* this should only be used for transports with connection_state == CS_NOPERM */ +void unregister_usb_transport(usb_handle *usb) +{ + atransport *t; + adb_mutex_lock(&transport_lock); + for(t = transport_list.next; t != &transport_list; t = t->next) { + if (t->usb == usb && t->connection_state == CS_NOPERM) { + t->next->prev = t->prev; + t->prev->next = t->next; + break; + } + } + adb_mutex_unlock(&transport_lock); +} + +#undef TRACE_TAG +#define TRACE_TAG TRACE_RWX + +int readx(int fd, void *ptr, size_t len) +{ + char *p = ptr; + int r; +#if ADB_TRACE + int len0 = len; +#endif + D("readx: fd=%d wanted=%d\n", fd, (int)len); + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if (r < 0) { + D("readx: fd=%d error %d: %s\n", fd, errno, strerror(errno)); + if (errno == EINTR) + continue; + } else { + D("readx: fd=%d disconnected\n", fd); + } + return -1; + } + } + +#if ADB_TRACE + D("readx: fd=%d wanted=%d got=%d\n", fd, len0, len0 - len); + dump_hex( ptr, len0 ); +#endif + return 0; +} + +int writex(int fd, const void *ptr, size_t len) +{ + char *p = (char*) ptr; + int r; + +#if ADB_TRACE + D("writex: fd=%d len=%d: ", fd, (int)len); + dump_hex( ptr, len ); +#endif + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if (r < 0) { + D("writex: fd=%d error %d: %s\n", fd, errno, strerror(errno)); + if (errno == EINTR) + continue; + } else { + D("writex: fd=%d disconnected\n", fd); + } + return -1; + } + } + return 0; +} + +int check_header(apacket *p) +{ + if(p->msg.magic != (p->msg.command ^ 0xffffffff)) { + D("check_header(): invalid magic\n"); + return -1; + } + + if(p->msg.data_length > MAX_PAYLOAD) { + D("check_header(): %d > MAX_PAYLOAD\n", p->msg.data_length); + return -1; + } + + return 0; +} + +int check_data(apacket *p) +{ + unsigned count, sum; + unsigned char *x; + + count = p->msg.data_length; + x = p->data; + sum = 0; + while(count-- > 0) { + sum += *x++; + } + + if(sum != p->msg.data_check) { + return -1; + } else { + return 0; + } +} diff --git a/minadbd/transport.h b/minadbd/transport.h new file mode 100644 index 000000000..992e05285 --- /dev/null +++ b/minadbd/transport.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TRANSPORT_H +#define __TRANSPORT_H + +/* convenience wrappers around read/write that will retry on +** EINTR and/or short read/write. Returns 0 on success, -1 +** on error or EOF. +*/ +int readx(int fd, void *ptr, size_t len); +int writex(int fd, const void *ptr, size_t len); +#endif /* __TRANSPORT_H */ diff --git a/minadbd/transport_usb.c b/minadbd/transport_usb.c new file mode 100644 index 000000000..91cbf6151 --- /dev/null +++ b/minadbd/transport_usb.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <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 new file mode 100644 index 000000000..635fa4bbb --- /dev/null +++ b/minadbd/usb_linux_client.c @@ -0,0 +1,157 @@ +/* + * 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 <sys/ioctl.h> +#include <sys/types.h> +#include <dirent.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + + +struct usb_handle +{ + int fd; + adb_cond_t notify; + adb_mutex_t lock; +}; + +void usb_cleanup() +{ + // nothing to do here +} + +static void *usb_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); + } + 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; +} + +int usb_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; +} + +int usb_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; +} + +void usb_init() +{ + usb_handle *h; + adb_thread_t tid; + int fd; + + h = calloc(1, sizeof(usb_handle)); + h->fd = -1; + adb_cond_init(&h->notify, 0); + adb_mutex_init(&h->lock, 0); + + // 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); + 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_open_thread, h)){ + fatal_errno("cannot create usb thread"); + } +} + +void usb_kick(usb_handle *h) +{ + D("usb_kick\n"); + adb_mutex_lock(&h->lock); + adb_close(h->fd); + h->fd = -1; + + // notify usb_open_thread that we are disconnected + adb_cond_signal(&h->notify); + adb_mutex_unlock(&h->lock); +} + +int usb_close(usb_handle *h) +{ + // nothing to do here + return 0; +} diff --git a/minadbd/utils.c b/minadbd/utils.c new file mode 100644 index 000000000..91518bab6 --- /dev/null +++ b/minadbd/utils.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "utils.h" +#include <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 new file mode 100644 index 000000000..f70ecd24d --- /dev/null +++ b/minadbd/utils.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _ADB_UTILS_H +#define _ADB_UTILS_H + +/* bounded buffer functions */ + +/* all these functions are used to append data to a bounded buffer. + * + * after each operation, the buffer is guaranteed to be zero-terminated, + * even in the case of an overflow. they all return the new buffer position + * which allows one to use them in succession, only checking for overflows + * at the end. For example: + * + * BUFF_DECL(temp,p,end,1024); + * char* p; + * + * p = buff_addc(temp, end, '"'); + * p = buff_adds(temp, end, string); + * p = buff_addc(temp, end, '"'); + * + * if (p >= end) { + * overflow detected. note that 'temp' is + * zero-terminated for safety. + * } + * return strdup(temp); + */ + +/* tries to add a character to the buffer, in case of overflow + * this will only write a terminating zero and return buffEnd. + */ +char* buff_addc (char* buff, char* buffEnd, int c); + +/* tries to add a string to the buffer */ +char* buff_adds (char* buff, char* buffEnd, const char* s); + +/* tries to add a bytes to the buffer. the input can contain zero bytes, + * but a terminating zero will always be appended at the end anyway + */ +char* buff_addb (char* buff, char* buffEnd, const void* data, int len); + +/* tries to add a formatted string to a bounded buffer */ +char* buff_add (char* buff, char* buffEnd, const char* format, ... ); + +/* convenience macro used to define a bounded buffer, as well as + * a 'cursor' and 'end' variables all in one go. + * + * note: this doesn't place an initial terminating zero in the buffer, + * you need to use one of the buff_ functions for this. or simply + * do _cursor[0] = 0 manually. + */ +#define BUFF_DECL(_buff,_cursor,_end,_size) \ + char _buff[_size], *_cursor=_buff, *_end = _cursor + (_size) + +#endif /* _ADB_UTILS_H */ diff --git a/minelf/Retouch.c b/minelf/Retouch.c index 33809cd6d..d75eec1e8 100644 --- a/minelf/Retouch.c +++ b/minelf/Retouch.c @@ -194,213 +194,3 @@ int retouch_mask_data(uint8_t *binary_object, if (retouch_offset != NULL) *retouch_offset = offset_candidate; return RETOUCH_DATA_MATCHED; } - -// On success, _override is set to the offset that was actually applied. -// This implies that once we randomize to an offset we stick with it. -// This in turn is necessary in order to guarantee recovery after crash. -bool retouch_one_library(const char *binary_name, - const char *binary_sha1, - int32_t retouch_offset, - int32_t *retouch_offset_override) { - bool success = true; - int result; - - FileContents file; - file.data = NULL; - - char binary_name_atomic[strlen(binary_name)+10]; - strcpy(binary_name_atomic, binary_name); - strcat(binary_name_atomic, ".atomic"); - - // We need a path that exists for calling statfs() later. - // - // Assume that binary_name (eg "/system/app/Foo.apk") is located - // on the same filesystem as its top-level directory ("/system"). - char target_fs[strlen(binary_name)+1]; - char* slash = strchr(binary_name+1, '/'); - if (slash != NULL) { - int count = slash - binary_name; - strncpy(target_fs, binary_name, count); - target_fs[count] = '\0'; - } else { - strcpy(target_fs, binary_name); - } - - result = LoadFileContents(binary_name, &file, RETOUCH_DONT_MASK); - - if (result == 0) { - // Figure out the *apparent* offset to which this file has been - // retouched. If it looks good, we will skip processing (we might - // have crashed and during this recovery pass we don't want to - // overwrite a valuable saved file in /cache---which would happen - // if we blindly retouch everything again). NOTE: This implies - // that we might have to override the supplied retouch offset. We - // can do the override only once though: everything should match - // afterward. - - int32_t inferred_offset; - int retouch_probe_result = retouch_mask_data(file.data, - file.size, - NULL, - &inferred_offset); - - if (retouch_probe_result == RETOUCH_DATA_MATCHED) { - if ((retouch_offset == inferred_offset) || - ((retouch_offset != 0 && inferred_offset != 0) && - (retouch_offset_override != NULL))) { - // This file is OK already and we are allowed to override. - // Let's just return the offset override value. It is critical - // to skip regardless of override: a broken file might need - // recovery down the list and we should not mess up the saved - // copy by doing unnecessary retouching. - // - // NOTE: If retouching was already started with a different - // value, we will not be allowed to override. This happens - // if on the retouch list there is a patched binary (which is - // masked in apply_patch()) before there is a non-patched - // binary. - if (retouch_offset_override != NULL) - *retouch_offset_override = inferred_offset; - success = true; - goto out; - } else { - // Retouch to zero (mask the retouching), to make sure that - // the SHA-1 check will pass below. - int32_t zero = 0; - retouch_mask_data(file.data, file.size, &zero, NULL); - SHA(file.data, file.size, file.sha1); - } - } - - if (retouch_probe_result == RETOUCH_DATA_NOTAPPLICABLE) { - // In the case of not retouchable, fake it. We do not want - // to do the normal processing and overwrite the backup file: - // we might be recovering! - // - // We return a zero override, which tells the caller that we - // simply skipped the file. - if (retouch_offset_override != NULL) - *retouch_offset_override = 0; - success = true; - goto out; - } - - // If we get here, either there was a mismatch in the offset, or - // the file has not been processed yet. Continue with normal - // processing. - } - - if (result != 0 || FindMatchingPatch(file.sha1, &binary_sha1, 1) < 0) { - free(file.data); - printf("Attempting to recover source from '%s' ...\n", - CACHE_TEMP_SOURCE); - result = LoadFileContents(CACHE_TEMP_SOURCE, &file, RETOUCH_DO_MASK); - if (result != 0 || FindMatchingPatch(file.sha1, &binary_sha1, 1) < 0) { - printf(" failed.\n"); - success = false; - goto out; - } - printf(" succeeded.\n"); - } - - // Retouch in-memory before worrying about backing up the original. - // - // Recovery steps will be oblivious to the actual retouch offset used, - // so might as well write out the already-retouched copy. Then, in the - // usual case, we will just swap the file locally, with no more writes - // needed. In the no-free-space case, we will then write the same to the - // original location. - - result = retouch_mask_data(file.data, file.size, &retouch_offset, NULL); - if (result != RETOUCH_DATA_MATCHED) { - success = false; - goto out; - } - if (retouch_offset_override != NULL) - *retouch_offset_override = retouch_offset; - - // How much free space do we need? - bool enough_space = false; - size_t free_space = FreeSpaceForFile(target_fs); - // 50% margin when estimating the space needed. - enough_space = (free_space > (file.size * 3 / 2)); - - // The experts say we have to allow for a retry of the - // whole process to avoid filesystem weirdness. - int retry = 1; - bool made_copy = false; - do { - // First figure out where to store a copy of the original. - // Ideally leave the original itself intact until the - // atomic swap. If no room on the same partition, fall back - // to the cache partition and remove the original. - - if (!enough_space) { - printf("Target is %ldB; free space is %ldB: not enough.\n", - (long)file.size, (long)free_space); - - retry = 0; - if (MakeFreeSpaceOnCache(file.size) < 0) { - printf("Not enough free space on '/cache'.\n"); - success = false; - goto out; - } - if (SaveFileContents(CACHE_TEMP_SOURCE, file) < 0) { - printf("Failed to back up source file.\n"); - success = false; - goto out; - } - made_copy = true; - unlink(binary_name); - - size_t free_space = FreeSpaceForFile(target_fs); - printf("(now %ld bytes free for target)\n", (long)free_space); - } - - result = SaveFileContents(binary_name_atomic, file); - if (result != 0) { - // Maybe the filesystem was optimistic: retry. - enough_space = false; - unlink(binary_name_atomic); - printf("Saving the retouched contents failed; retrying.\n"); - continue; - } - - // Succeeded; no need to retry. - break; - } while (retry-- > 0); - - // Give the .atomic file the same owner, group, and mode of the - // original source file. - if (chmod(binary_name_atomic, file.st.st_mode) != 0) { - printf("chmod of \"%s\" failed: %s\n", - binary_name_atomic, strerror(errno)); - success = false; - goto out; - } - if (chown(binary_name_atomic, file.st.st_uid, file.st.st_gid) != 0) { - printf("chown of \"%s\" failed: %s\n", - binary_name_atomic, - strerror(errno)); - success = false; - goto out; - } - - // Finally, rename the .atomic file to replace the target file. - if (rename(binary_name_atomic, binary_name) != 0) { - printf("rename of .atomic to \"%s\" failed: %s\n", - binary_name, strerror(errno)); - success = false; - goto out; - } - - // If this run created a copy, and we're here, we can delete it. - if (made_copy) unlink(CACHE_TEMP_SOURCE); - - out: - // clean up - free(file.data); - unlink(binary_name_atomic); - - return success; -} diff --git a/minelf/Retouch.h b/minelf/Retouch.h index 048d78e44..13bacd5ad 100644 --- a/minelf/Retouch.h +++ b/minelf/Retouch.h @@ -25,12 +25,6 @@ typedef struct { uint32_t blob_size; /* in bytes, located right before this struct */ } retouch_info_t __attribute__((packed)); -// Retouch a file. Use CACHED_SOURCE_TEMP to store a copy. -bool retouch_one_library(const char *binary_name, - const char *binary_sha1, - int32_t retouch_offset, - int32_t *retouch_offset_override); - #define RETOUCH_DONT_MASK 0 #define RETOUCH_DO_MASK 1 diff --git a/minui/minui.h b/minui/minui.h index 2e2f1f477..74da4e9c0 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -19,6 +19,10 @@ #include <stdbool.h> +#ifdef __cplusplus +extern "C" { +#endif + typedef void* gr_surface; typedef unsigned short gr_pixel; @@ -69,4 +73,8 @@ void ev_dispatch(void); int res_create_surface(const char* name, gr_surface* pSurface); void res_free_surface(gr_surface surface); +#ifdef __cplusplus +} +#endif + #endif diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h index 1f378b75c..f8be64026 100644 --- a/minzip/DirUtil.h +++ b/minzip/DirUtil.h @@ -20,6 +20,10 @@ #include <stdbool.h> #include <utime.h> +#ifdef __cplusplus +extern "C" { +#endif + #ifdef HAVE_SELINUX #include <selinux/selinux.h> #include <selinux/label.h> @@ -56,4 +60,8 @@ int dirUnlinkHierarchy(const char *path); int dirSetHierarchyPermissions(const char *path, int uid, int gid, int dirMode, int fileMode); +#ifdef __cplusplus +} +#endif + #endif // MINZIP_DIRUTIL_H_ diff --git a/minzip/Zip.h b/minzip/Zip.h index 0b1c9d532..4bb9ef6a4 100644 --- a/minzip/Zip.h +++ b/minzip/Zip.h @@ -14,6 +14,10 @@ #include "Hash.h" #include "SysUtil.h" +#ifdef __cplusplus +extern "C" { +#endif + #ifdef HAVE_SELINUX #include <selinux/selinux.h> #include <selinux/label.h> @@ -218,4 +222,8 @@ bool mzExtractRecursive(const ZipArchive *pArchive, void (*callback)(const char *fn, void*), void *cookie, struct selabel_handle *sehnd); +#ifdef __cplusplus +} +#endif + #endif /*_MINZIP_ZIP*/ diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c index c77687602..a39d60001 100644 --- a/mtdutils/flash_image.c +++ b/mtdutils/flash_image.c @@ -42,7 +42,7 @@ void die(const char *msg, ...) { } fprintf(stderr, "%s\n", buf); - LOGE("%s\n", buf); + ALOGE("%s\n", buf); exit(1); } @@ -74,23 +74,23 @@ int main(int argc, char **argv) { MtdReadContext *in = mtd_read_partition(partition); if (in == NULL) { - LOGW("error opening %s: %s\n", argv[1], strerror(errno)); + ALOGW("error opening %s: %s\n", argv[1], strerror(errno)); // just assume it needs re-writing } else { char check[HEADER_SIZE]; int checklen = mtd_read_data(in, check, sizeof(check)); if (checklen <= 0) { - LOGW("error reading %s: %s\n", argv[1], strerror(errno)); + ALOGW("error reading %s: %s\n", argv[1], strerror(errno)); // just assume it needs re-writing } else if (checklen == headerlen && !memcmp(header, check, headerlen)) { - LOGI("header is the same, not flashing %s\n", argv[1]); + ALOGI("header is the same, not flashing %s\n", argv[1]); return 0; } mtd_read_close(in); } // Skip the header (we'll come back to it), write everything else - LOGI("flashing %s from %s\n", argv[1], argv[2]); + ALOGI("flashing %s from %s\n", argv[1], argv[2]); MtdWriteContext *out = mtd_write_partition(partition); if (out == NULL) die("error writing %s", argv[1]); diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h index 30b2927c2..d721355b8 100644 --- a/mtdutils/mounts.h +++ b/mtdutils/mounts.h @@ -17,6 +17,10 @@ #ifndef MTDUTILS_MOUNTS_H_ #define MTDUTILS_MOUNTS_H_ +#ifdef __cplusplus +extern "C" { +#endif + typedef struct MountedVolume MountedVolume; int scan_mounted_volumes(void); @@ -30,4 +34,8 @@ int unmount_mounted_volume(const MountedVolume *volume); int remount_read_only(const MountedVolume* volume); +#ifdef __cplusplus +} +#endif + #endif // MTDUTILS_MOUNTS_H_ diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h index 45d3ebc91..2708c4318 100644 --- a/mtdutils/mtdutils.h +++ b/mtdutils/mtdutils.h @@ -19,6 +19,10 @@ #include <sys/types.h> // for size_t, etc. +#ifdef __cplusplus +extern "C" { +#endif + typedef struct MtdPartition MtdPartition; int mtd_scan_partitions(void); @@ -53,4 +57,8 @@ off_t mtd_erase_blocks(MtdWriteContext *, int blocks); /* 0 ok, -1 for all */ off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos); int mtd_write_close(MtdWriteContext *); +#ifdef __cplusplus +} +#endif + #endif // MTDUTILS_H_ diff --git a/recovery.c b/recovery.cpp index 2cb482d02..baafadca0 100644 --- a/recovery.c +++ b/recovery.cpp @@ -37,7 +37,13 @@ #include "minui/minui.h" #include "minzip/DirUtil.h" #include "roots.h" -#include "recovery_ui.h" +#include "ui.h" +#include "screen_ui.h" +#include "device.h" +#include "adb_install.h" +extern "C" { +#include "minadbd/adb.h" +} struct selabel_handle *sehandle; @@ -61,7 +67,7 @@ static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload"; -extern UIParameters ui_parameters; // from ui.c +RecoveryUI* ui = NULL; /* * The recovery tool communicates with the main system through /cache files. @@ -292,9 +298,9 @@ finish_recovery(const char *send_intent) { static int erase_volume(const char *volume) { - ui_set_background(BACKGROUND_ICON_INSTALLING); - ui_show_indeterminate_progress(); - ui_print("Formatting %s...\n", volume); + ui->SetBackground(RecoveryUI::INSTALLING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); + ui->Print("Formatting %s...\n", volume); ensure_path_unmounted(volume); @@ -351,7 +357,7 @@ copy_sideloaded_package(const char* original_path) { strcpy(copy_path, SIDELOAD_TEMP_DIR); strcat(copy_path, "/package.zip"); - char* buffer = malloc(BUFSIZ); + char* buffer = (char*)malloc(BUFSIZ); if (buffer == NULL) { LOGE("Failed to allocate buffer\n"); return NULL; @@ -398,22 +404,22 @@ copy_sideloaded_package(const char* original_path) { return strdup(copy_path); } -static char** -prepend_title(const char** headers) { - char* title[] = { "Android system recovery <" - EXPAND(RECOVERY_API_VERSION) "e>", - "", - NULL }; +static const char** +prepend_title(const char* const* headers) { + const char* title[] = { "Android system recovery <" + EXPAND(RECOVERY_API_VERSION) "e>", + "", + NULL }; // count the number of lines in our title, plus the // caller-provided headers. int count = 0; - char** p; + const char* const* p; for (p = title; *p; ++p, ++count); for (p = headers; *p; ++p, ++count); - char** new_headers = malloc((count+1) * sizeof(char*)); - char** h = new_headers; + const char** new_headers = (const char**)malloc((count+1) * sizeof(char*)); + const char** h = new_headers; for (p = title; *p; ++p, ++h) *h = *p; for (p = headers; *p; ++p, ++h) *h = *p; *h = NULL; @@ -422,46 +428,46 @@ prepend_title(const char** headers) { } static int -get_menu_selection(char** headers, char** items, int menu_only, - int initial_selection) { +get_menu_selection(const char* const * headers, const char* const * items, + int menu_only, int initial_selection, Device* device) { // throw away keys pressed previously, so user doesn't // accidentally trigger menu items. - ui_clear_key_queue(); + ui->FlushKeys(); - ui_start_menu(headers, items, initial_selection); + ui->StartMenu(headers, items, initial_selection); int selected = initial_selection; int chosen_item = -1; while (chosen_item < 0) { - int key = ui_wait_key(); - int visible = ui_text_visible(); + int key = ui->WaitKey(); + int visible = ui->IsTextVisible(); if (key == -1) { // ui_wait_key() timed out - if (ui_text_ever_visible()) { + if (ui->WasTextEverVisible()) { continue; } else { LOGI("timed out waiting for key input; rebooting.\n"); - ui_end_menu(); - return ITEM_REBOOT; + ui->EndMenu(); + return 0; // XXX fixme } } - int action = device_handle_key(key, visible); + int action = device->HandleMenuKey(key, visible); if (action < 0) { switch (action) { - case HIGHLIGHT_UP: + case Device::kHighlightUp: --selected; - selected = ui_menu_select(selected); + selected = ui->SelectMenu(selected); break; - case HIGHLIGHT_DOWN: + case Device::kHighlightDown: ++selected; - selected = ui_menu_select(selected); + selected = ui->SelectMenu(selected); break; - case SELECT_ITEM: + case Device::kInvokeItem: chosen_item = selected; break; - case NO_ACTION: + case Device::kNoAction: break; } } else if (!menu_only) { @@ -469,7 +475,7 @@ get_menu_selection(char** headers, char** items, int menu_only, } } - ui_end_menu(); + ui->EndMenu(); return chosen_item; } @@ -479,7 +485,7 @@ static int compare_string(const void* a, const void* b) { static int update_directory(const char* path, const char* unmount_when_done, - int* wipe_cache) { + int* wipe_cache, Device* device) { ensure_path_mounted(path); const char* MENU_HEADERS[] = { "Choose a package to install:", @@ -497,14 +503,14 @@ update_directory(const char* path, const char* unmount_when_done, return 0; } - char** headers = prepend_title(MENU_HEADERS); + const char** headers = prepend_title(MENU_HEADERS); int d_size = 0; int d_alloc = 10; - char** dirs = malloc(d_alloc * sizeof(char*)); + char** dirs = (char**)malloc(d_alloc * sizeof(char*)); int z_size = 1; int z_alloc = 10; - char** zips = malloc(z_alloc * sizeof(char*)); + char** zips = (char**)malloc(z_alloc * sizeof(char*)); zips[0] = strdup("../"); while ((de = readdir(d)) != NULL) { @@ -518,9 +524,9 @@ update_directory(const char* path, const char* unmount_when_done, if (d_size >= d_alloc) { d_alloc *= 2; - dirs = realloc(dirs, d_alloc * sizeof(char*)); + dirs = (char**)realloc(dirs, d_alloc * sizeof(char*)); } - dirs[d_size] = malloc(name_len + 2); + dirs[d_size] = (char*)malloc(name_len + 2); strcpy(dirs[d_size], de->d_name); dirs[d_size][name_len] = '/'; dirs[d_size][name_len+1] = '\0'; @@ -530,7 +536,7 @@ update_directory(const char* path, const char* unmount_when_done, strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) { if (z_size >= z_alloc) { z_alloc *= 2; - zips = realloc(zips, z_alloc * sizeof(char*)); + zips = (char**)realloc(zips, z_alloc * sizeof(char*)); } zips[z_size++] = strdup(de->d_name); } @@ -543,7 +549,7 @@ update_directory(const char* path, const char* unmount_when_done, // append dirs to the zips list if (d_size + z_size + 1 > z_alloc) { z_alloc = d_size + z_size + 1; - zips = realloc(zips, z_alloc * sizeof(char*)); + zips = (char**)realloc(zips, z_alloc * sizeof(char*)); } memcpy(zips + z_size, dirs, d_size * sizeof(char*)); free(dirs); @@ -553,7 +559,7 @@ update_directory(const char* path, const char* unmount_when_done, int result; int chosen_item = 0; do { - chosen_item = get_menu_selection(headers, zips, 1, chosen_item); + chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device); char* item = zips[chosen_item]; int item_len = strlen(item); @@ -568,7 +574,7 @@ update_directory(const char* path, const char* unmount_when_done, strlcat(new_path, "/", PATH_MAX); strlcat(new_path, item, PATH_MAX); new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/' - result = update_directory(new_path, unmount_when_done, wipe_cache); + result = update_directory(new_path, unmount_when_done, wipe_cache, device); if (result >= 0) break; } else { // selected a zip file: attempt to install it, and return @@ -578,7 +584,7 @@ update_directory(const char* path, const char* unmount_when_done, strlcat(new_path, "/", PATH_MAX); strlcat(new_path, item, PATH_MAX); - ui_print("\n-- Install %s ...\n", path); + ui->Print("\n-- Install %s ...\n", path); set_sdcard_update_bootloader_message(); char* copy = copy_sideloaded_package(new_path); if (unmount_when_done != NULL) { @@ -606,121 +612,140 @@ update_directory(const char* path, const char* unmount_when_done, } static void -wipe_data(int confirm) { +wipe_data(int confirm, Device* device) { if (confirm) { - static char** title_headers = NULL; + static const char** title_headers = NULL; if (title_headers == NULL) { - char* headers[] = { "Confirm wipe of all user data?", - " THIS CAN NOT BE UNDONE.", - "", - NULL }; + const char* headers[] = { "Confirm wipe of all user data?", + " THIS CAN NOT BE UNDONE.", + "", + NULL }; title_headers = prepend_title((const char**)headers); } - char* items[] = { " No", - " No", - " No", - " No", - " No", - " No", - " No", - " Yes -- delete all user data", // [7] - " No", - " No", - " No", - NULL }; - - int chosen_item = get_menu_selection(title_headers, items, 1, 0); + 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; } } - ui_print("\n-- Wiping data...\n"); - device_wipe_data(); + ui->Print("\n-- Wiping data...\n"); + device->WipeData(); erase_volume("/data"); erase_volume("/cache"); - ui_print("Data wipe complete.\n"); + ui->Print("Data wipe complete.\n"); } static void -prompt_and_wait() { - char** headers = prepend_title((const char**)MENU_HEADERS); +prompt_and_wait(Device* device) { + const char* const* headers = prepend_title(device->GetMenuHeaders()); for (;;) { finish_recovery(NULL); - ui_reset_progress(); + ui->SetProgressType(RecoveryUI::EMPTY); - int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0); + int chosen_item = get_menu_selection(headers, 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. - chosen_item = device_perform_action(chosen_item); + chosen_item = device->InvokeMenuItem(chosen_item); int status; int wipe_cache; switch (chosen_item) { - case ITEM_REBOOT: + case Device::REBOOT: return; - case ITEM_WIPE_DATA: - wipe_data(ui_text_visible()); - if (!ui_text_visible()) return; + case Device::WIPE_DATA: + wipe_data(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return; break; - case ITEM_WIPE_CACHE: - ui_print("\n-- Wiping cache...\n"); + case Device::WIPE_CACHE: + ui->Print("\n-- Wiping cache...\n"); erase_volume("/cache"); - ui_print("Cache wipe complete.\n"); - if (!ui_text_visible()) return; + ui->Print("Cache wipe complete.\n"); + if (!ui->IsTextVisible()) return; break; - case ITEM_APPLY_SDCARD: - status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache); + case Device::APPLY_EXT: + // Some packages expect /cache to be mounted (eg, + // standard incremental packages expect to use /cache + // as scratch space). + ensure_path_mounted(CACHE_ROOT); + status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache, device); if (status == INSTALL_SUCCESS && wipe_cache) { - ui_print("\n-- Wiping cache (at package request)...\n"); + ui->Print("\n-- Wiping cache (at package request)...\n"); if (erase_volume("/cache")) { - ui_print("Cache wipe failed.\n"); + ui->Print("Cache wipe failed.\n"); } else { - ui_print("Cache wipe complete.\n"); + ui->Print("Cache wipe complete.\n"); } } if (status >= 0) { if (status != INSTALL_SUCCESS) { - ui_set_background(BACKGROUND_ICON_ERROR); - ui_print("Installation aborted.\n"); - } else if (!ui_text_visible()) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + } else if (!ui->IsTextVisible()) { return; // reboot if logs aren't visible } else { - ui_print("\nInstall from sdcard complete.\n"); + ui->Print("\nInstall from sdcard complete.\n"); } } break; - case ITEM_APPLY_CACHE: + + case Device::APPLY_CACHE: // Don't unmount cache at the end of this. - status = update_directory(CACHE_ROOT, NULL, &wipe_cache); + status = update_directory(CACHE_ROOT, NULL, &wipe_cache, device); if (status == INSTALL_SUCCESS && wipe_cache) { - ui_print("\n-- Wiping cache (at package request)...\n"); + ui->Print("\n-- Wiping cache (at package request)...\n"); if (erase_volume("/cache")) { - ui_print("Cache wipe failed.\n"); + ui->Print("Cache wipe failed.\n"); } else { - ui_print("Cache wipe complete.\n"); + ui->Print("Cache wipe complete.\n"); } } if (status >= 0) { if (status != INSTALL_SUCCESS) { - ui_set_background(BACKGROUND_ICON_ERROR); - ui_print("Installation aborted.\n"); - } else if (!ui_text_visible()) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + } else if (!ui->IsTextVisible()) { return; // reboot if logs aren't visible } else { - ui_print("\nInstall from cache complete.\n"); + ui->Print("\nInstall from cache complete.\n"); } } break; + case Device::APPLY_ADB_SIDELOAD: + ensure_path_mounted(CACHE_ROOT); + 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"); + } else if (!ui->IsTextVisible()) { + return; // reboot if logs aren't visible + } else { + ui->Print("\nInstall from ADB complete.\n"); + } + } + break; } } } @@ -737,11 +762,26 @@ main(int argc, char **argv) { // If these fail, there's not really anywhere to complain... freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL); freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL); + + // If this binary is started with the single argument "--adbd", + // instead of being the normal recovery binary, it turns into kind + // of a stripped-down version of adbd that only supports the + // 'sideload' command. Note this must be a real argument, not + // anything in the command file or bootloader control block; the + // 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(); + return 0; + } + printf("Starting recovery on %s", ctime(&start)); - device_ui_init(&ui_parameters); - ui_init(); - ui_set_background(BACKGROUND_ICON_INSTALLING); + Device* device = make_device(); + ui = device->GetUI(); + + ui->Init(); + ui->SetBackground(RecoveryUI::NONE); load_volume_table(); get_args(&argc, &argv); @@ -758,7 +798,7 @@ main(int argc, char **argv) { case 'u': update_package = optarg; break; case 'w': wipe_data = wipe_cache = 1; break; case 'c': wipe_cache = 1; break; - case 't': ui_show_text(1); break; + case 't': ui->ShowText(true); break; case '?': LOGE("Invalid command argument\n"); continue; @@ -778,7 +818,7 @@ main(int argc, char **argv) { } #endif - device_recovery_start(); + device->StartRecovery(); printf("Command:"); for (arg = 0; arg < argc; arg++) { @@ -792,7 +832,7 @@ main(int argc, char **argv) { // "/cache/foo". if (strncmp(update_package, "CACHE:", 6) == 0) { int len = strlen(update_package) + 10; - char* modified_path = malloc(len); + char* modified_path = (char*)malloc(len); strlcpy(modified_path, "/cache/", len); strlcat(modified_path, update_package+6, len); printf("(replacing path \"%s\" with \"%s\")\n", @@ -814,27 +854,27 @@ main(int argc, char **argv) { LOGE("Cache wipe (requested by package) failed."); } } - if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n"); + if (status != INSTALL_SUCCESS) ui->Print("Installation aborted.\n"); } else if (wipe_data) { - if (device_wipe_data()) status = INSTALL_ERROR; + 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"); + 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"); + if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n"); } else { status = INSTALL_ERROR; // No command specified } - if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR); - if (status != INSTALL_SUCCESS || ui_text_visible()) { - prompt_and_wait(); + if (status != INSTALL_SUCCESS) ui->SetBackground(RecoveryUI::ERROR); + if (status != INSTALL_SUCCESS || ui->IsTextVisible()) { + prompt_and_wait(device); } // Otherwise, get ready to boot the main system... finish_recovery(send_intent); - ui_print("Rebooting...\n"); + ui->Print("Rebooting...\n"); android_reboot(ANDROID_RB_RESTART, 0, 0); return EXIT_SUCCESS; } diff --git a/recovery_ui.h b/recovery_ui.h deleted file mode 100644 index 5f0177045..000000000 --- a/recovery_ui.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _RECOVERY_UI_H -#define _RECOVERY_UI_H - -#include "common.h" - -// Called before UI library is initialized. Can change things like -// how many frames are included in various animations, etc. -extern void device_ui_init(UIParameters* ui_parameters); - -// Called when recovery starts up. Returns 0. -extern int device_recovery_start(); - -// Called in the input thread when a new key (key_code) is pressed. -// *key_pressed is an array of KEY_MAX+1 bytes indicating which other -// keys are already pressed. Return true if the text display should -// be toggled. -extern int device_toggle_display(volatile char* key_pressed, int key_code); - -// Called in the input thread when a new key (key_code) is pressed. -// *key_pressed is an array of KEY_MAX+1 bytes indicating which other -// keys are already pressed. Return true if the device should reboot -// immediately. -extern int device_reboot_now(volatile char* key_pressed, int key_code); - -// Called from the main thread when recovery is waiting for input and -// a key is pressed. key is the code of the key pressed; visible is -// true if the recovery menu is being shown. Implementations can call -// ui_key_pressed() to discover if other keys are being held down. -// Return one of the defined constants below in order to: -// -// - move the menu highlight (HIGHLIGHT_*) -// - invoke the highlighted item (SELECT_ITEM) -// - do nothing (NO_ACTION) -// - invoke a specific action (a menu position: any non-negative number) -extern int device_handle_key(int key, int visible); - -// Perform a recovery action selected from the menu. 'which' will be -// the item number of the selected menu item, or a non-negative number -// returned from device_handle_key(). The menu will be hidden when -// this is called; implementations can call ui_print() to print -// information to the screen. -extern int device_perform_action(int which); - -// Called when we do a wipe data/factory reset operation (either via a -// reboot from the main system with the --wipe_data flag, or when the -// user boots into recovery manually and selects the option from the -// menu.) Can perform whatever device-specific wiping actions are -// needed. Return 0 on success. The userdata and cache partitions -// are erased after this returns (whether it returns success or not). -int device_wipe_data(); - -#define NO_ACTION -1 - -#define HIGHLIGHT_UP -2 -#define HIGHLIGHT_DOWN -3 -#define SELECT_ITEM -4 - -#define ITEM_REBOOT 0 -#define ITEM_APPLY_EXT 1 -#define ITEM_APPLY_SDCARD 1 // historical synonym for ITEM_APPLY_EXT -#define ITEM_WIPE_DATA 2 -#define ITEM_WIPE_CACHE 3 -#define ITEM_APPLY_CACHE 4 - -// Header text to display above the main menu. -extern char* MENU_HEADERS[]; - -// Text of menu items. -extern char* MENU_ITEMS[]; - -#endif @@ -31,11 +31,11 @@ static int num_volumes = 0; static Volume* device_volumes = NULL; -struct selabel_handle *sehandle; +extern struct selabel_handle *sehandle; static int parse_options(char* options, Volume* volume) { char* option; - while (option = strtok(options, ",")) { + while ((option = strtok(options, ","))) { options = NULL; if (strncmp(option, "length=", 7) == 0) { @@ -50,7 +50,7 @@ static int parse_options(char* options, Volume* volume) { void load_volume_table() { int alloc = 2; - device_volumes = malloc(alloc * sizeof(Volume)); + device_volumes = (Volume*)malloc(alloc * sizeof(Volume)); // Insert an entry for /tmp, which is the ramdisk and is always mounted. device_volumes[0].mount_point = "/tmp"; @@ -93,7 +93,7 @@ void load_volume_table() { if (mount_point && fs_type && device) { while (num_volumes >= alloc) { alloc *= 2; - device_volumes = realloc(device_volumes, alloc*sizeof(Volume)); + device_volumes = (Volume*)realloc(device_volumes, alloc*sizeof(Volume)); } device_volumes[num_volumes].mount_point = strdup(mount_point); device_volumes[num_volumes].fs_type = strdup(fs_type); @@ -19,6 +19,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); @@ -38,4 +42,8 @@ int ensure_path_unmounted(const char* path); // it is mounted. int format_volume(const char* volume); +#ifdef __cplusplus +} +#endif + #endif // RECOVERY_ROOTS_H_ diff --git a/screen_ui.cpp b/screen_ui.cpp new file mode 100644 index 000000000..2a8652ecf --- /dev/null +++ b/screen_ui.cpp @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "common.h" +#include "device.h" +#include "minui/minui.h" +#include "screen_ui.h" +#include "ui.h" + +#define CHAR_WIDTH 10 +#define CHAR_HEIGHT 18 + +// 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); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +ScreenRecoveryUI::ScreenRecoveryUI() : + currentIcon(NONE), + installingFrame(0), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + pagesIdentical(false), + text_cols(0), + text_rows(0), + text_col(0), + text_row(0), + text_top(0), + show_text(false), + show_text_ever(false), + show_menu(false), + menu_top(0), + menu_items(0), + menu_sel(0), + + // These values are correct for the default image resources + // provided with the android platform. Devices which use + // different resources should have a subclass of ScreenRecoveryUI + // that overrides Init() to set these values appropriately and + // then call the superclass Init(). + animation_fps(20), + indeterminate_frames(6), + installing_frames(7), + install_overlay_offset_x(13), + install_overlay_offset_y(190) { + pthread_mutex_init(&updateMutex, NULL); + self = this; +} + +// Draw the given frame over the installation overlay animation. The +// background is not cleared or draw with the base icon first; we +// assume that the frame already contains some other frame of the +// animation. Does nothing if no overlay animation is defined. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_install_overlay_locked(int frame) { + if (installationOverlay == NULL) return; + gr_surface surface = installationOverlay[frame]; + int iconWidth = gr_get_width(surface); + int iconHeight = gr_get_height(surface); + gr_blit(surface, 0, 0, iconWidth, iconHeight, + install_overlay_offset_x, install_overlay_offset_y); +} + +// 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) +{ + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (icon) { + gr_surface surface = backgroundIcon[icon]; + int iconWidth = gr_get_width(surface); + int iconHeight = gr_get_height(surface); + int iconX = (gr_fb_width() - iconWidth) / 2; + int iconY = (gr_fb_height() - iconHeight) / 2; + gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); + if (icon == INSTALLING) { + draw_install_overlay_locked(installingFrame); + } + } +} + +// Draw the progress bar (if any) on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_progress_locked() +{ + if (currentIcon == INSTALLING) { + draw_install_overlay_locked(installingFrame); + } + + if (progressBarType != EMPTY) { + int iconHeight = gr_get_height(backgroundIcon[INSTALLING]); + int width = gr_get_width(progressBarEmpty); + int height = gr_get_height(progressBarEmpty); + + int dx = (gr_fb_width() - width)/2; + int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; + + // Erase behind the progress bar (in case this was a progress-only update) + gr_color(0, 0, 0, 255); + gr_fill(dx, dy, width, height); + + if (progressBarType == DETERMINATE) { + float p = progressScopeStart + progress * progressScopeSize; + int pos = (int) (p * width); + + if (pos > 0) { + gr_blit(progressBarFill, 0, 0, pos, height, dx, dy); + } + if (pos < width-1) { + gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); + } + } + + if (progressBarType == INDETERMINATE) { + static int frame = 0; + gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy); + frame = (frame + 1) % indeterminate_frames; + } + } +} + +void ScreenRecoveryUI::draw_text_line(int row, const char* t) { + if (t[0] != '\0') { + gr_text(0, (row+1)*CHAR_HEIGHT-1, t); + } +} + +// Redraw everything on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_screen_locked() +{ + draw_background_locked(currentIcon); + draw_progress_locked(); + + if (show_text) { + gr_color(0, 0, 0, 160); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + int i = 0; + if (show_menu) { + gr_color(64, 96, 255, 255); + gr_fill(0, (menu_top+menu_sel) * CHAR_HEIGHT, + gr_fb_width(), (menu_top+menu_sel+1)*CHAR_HEIGHT+1); + + for (; i < menu_top + menu_items; ++i) { + if (i == menu_top + menu_sel) { + gr_color(255, 255, 255, 255); + draw_text_line(i, menu[i]); + gr_color(64, 96, 255, 255); + } else { + draw_text_line(i, menu[i]); + } + } + gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1, + gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1); + ++i; + } + + gr_color(255, 255, 0, 255); + + for (; i < text_rows; ++i) { + draw_text_line(i, text[(i+text_top) % text_rows]); + } + } +} + +// Redraw everything on the screen and flip the screen (make it visible). +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::update_screen_locked() +{ + draw_screen_locked(); + gr_flip(); +} + +// Updates only the progress bar, if possible, otherwise redraws the screen. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::update_progress_locked() +{ + if (show_text || !pagesIdentical) { + draw_screen_locked(); // Must redraw the whole screen + pagesIdentical = true; + } else { + draw_progress_locked(); // Draw only the progress bar and overlays + } + gr_flip(); +} + +// 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::progress_loop() { + double interval = 1.0 / animation_fps; + for (;;) { + double start = now(); + pthread_mutex_lock(&updateMutex); + + int redraw = 0; + + // update the installation animation, if active + // skip this if we have a text overlay (too expensive to update) + if (currentIcon == INSTALLING && installing_frames > 0 && !show_text) { + installingFrame = (installingFrame + 1) % installing_frames; + redraw = 1; + } + + // update the progress bar animation, if active + // skip this if we have a text overlay (too expensive to update) + if (progressBarType == INDETERMINATE && !show_text) { + redraw = 1; + } + + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float progress = 1.0 * elapsed / duration; + if (progress > 1.0) progress = 1.0; + if (progress > progress) { + progress = progress; + redraw = 1; + } + } + + if (redraw) update_progress_locked(); + + pthread_mutex_unlock(&updateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end-start); + if (delay < 0.02) delay = 0.02; + usleep((long)(delay * 1000000)); + } +} + +void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) { + int result = res_create_surface(filename, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + +void ScreenRecoveryUI::Init() +{ + gr_init(); + + 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; + + LoadBitmap("icon_installing", &backgroundIcon[INSTALLING]); + LoadBitmap("icon_error", &backgroundIcon[ERROR]); + LoadBitmap("progress_empty", &progressBarEmpty); + LoadBitmap("progress_fill", &progressBarFill); + + int i; + + progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames * + sizeof(gr_surface)); + for (i = 0; i < indeterminate_frames; ++i) { + char filename[40]; + // "indeterminate01.png", "indeterminate02.png", ... + sprintf(filename, "indeterminate%02d", i+1); + LoadBitmap(filename, progressBarIndeterminate+i); + } + + if (installing_frames > 0) { + installationOverlay = (gr_surface*)malloc(installing_frames * + sizeof(gr_surface)); + for (i = 0; i < installing_frames; ++i) { + char filename[40]; + // "icon_installing_overlay01.png", + // "icon_installing_overlay02.png", ... + sprintf(filename, "icon_installing_overlay%02d", i+1); + LoadBitmap(filename, installationOverlay+i); + } + + // Adjust the offset to account for the positioning of the + // base image on the screen. + if (backgroundIcon[INSTALLING] != NULL) { + gr_surface bg = backgroundIcon[INSTALLING]; + install_overlay_offset_x += (gr_fb_width() - gr_get_width(bg)) / 2; + install_overlay_offset_y += (gr_fb_height() - gr_get_height(bg)) / 2; + } + } else { + installationOverlay = NULL; + } + + pthread_create(&progress_t, NULL, progress_thread, NULL); + + RecoveryUI::Init(); +} + +void ScreenRecoveryUI::SetBackground(Icon icon) +{ + pthread_mutex_lock(&updateMutex); + currentIcon = icon; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::SetProgressType(ProgressType type) +{ + pthread_mutex_lock(&updateMutex); + if (progressBarType != type) { + progressBarType = type; + update_progress_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::ShowProgress(float portion, float seconds) +{ + pthread_mutex_lock(&updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_progress_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::SetProgress(float fraction) +{ + pthread_mutex_lock(&updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = gr_get_width(progressBarIndeterminate[0]); + float scale = width * progressScopeSize; + if ((int) (progress * scale) != (int) (fraction * scale)) { + progress = fraction; + update_progress_locked(); + } + } + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::Print(const char *fmt, ...) +{ + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + fputs(buf, stdout); + + // This can get called before ui_init(), so be careful. + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + char *ptr; + for (ptr = buf; *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col >= text_cols) { + text[text_row][text_col] = '\0'; + text_col = 0; + text_row = (text_row + 1) % text_rows; + if (text_row == text_top) text_top = (text_top + 1) % text_rows; + } + if (*ptr != '\n') text[text_row][text_col++] = *ptr; + } + text[text_row][text_col] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +void 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[i][text_cols-1] = '\0'; + } + menu_items = i - menu_top; + show_menu = 1; + menu_sel = initial_selection; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +int ScreenRecoveryUI::SelectMenu(int sel) { + int old_sel; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items-1; + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; +} + +void ScreenRecoveryUI::EndMenu() { + int i; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0 && text_rows > 0 && text_cols > 0) { + show_menu = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +bool ScreenRecoveryUI::IsTextVisible() +{ + pthread_mutex_lock(&updateMutex); + int visible = show_text; + pthread_mutex_unlock(&updateMutex); + return visible; +} + +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) +{ + pthread_mutex_lock(&updateMutex); + show_text = visible; + if (show_text) show_text_ever = 1; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} diff --git a/screen_ui.h b/screen_ui.h new file mode 100644 index 000000000..34929ee1a --- /dev/null +++ b/screen_ui.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_SCREEN_UI_H +#define RECOVERY_SCREEN_UI_H + +#include <pthread.h> + +#include "ui.h" +#include "minui/minui.h" + +// Implementation of RecoveryUI appropriate for devices with a screen +// (shows an icon + a progress bar, text logging, menu, etc.) +class ScreenRecoveryUI : public RecoveryUI { + public: + ScreenRecoveryUI(); + + void Init(); + + // overall recovery state ("background image") + void SetBackground(Icon icon); + + // progress indicator + void SetProgressType(ProgressType type); + void ShowProgress(float portion, float seconds); + void SetProgress(float fraction); + + // text log + void ShowText(bool visible); + bool IsTextVisible(); + bool WasTextEverVisible(); + + // printing messages + void Print(const char* fmt, ...); // __attribute__((format(printf, 1, 2))); + + // menu display + void StartMenu(const char* const * headers, const char* const * items, + int initial_selection); + int SelectMenu(int sel); + void EndMenu(); + + private: + Icon currentIcon; + int installingFrame; + + pthread_mutex_t updateMutex; + gr_surface backgroundIcon[3]; + gr_surface *installationOverlay; + gr_surface *progressBarIndeterminate; + gr_surface progressBarEmpty; + gr_surface progressBarFill; + + ProgressType progressBarType; + + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; + + // true when both graphics pages are the same (except for the + // progress bar) + bool pagesIdentical; + + static const int kMaxCols = 96; + static const int kMaxRows = 32; + + // 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; + bool show_text; + bool show_text_ever; // has show_text ever been true? + + char menu[kMaxRows][kMaxCols]; + bool show_menu; + int menu_top, menu_items, menu_sel; + + pthread_t progress_t; + + int animation_fps; + int indeterminate_frames; + int installing_frames; + int install_overlay_offset_x, install_overlay_offset_y; + + void draw_install_overlay_locked(int frame); + void draw_background_locked(Icon icon); + void draw_progress_locked(); + void draw_text_line(int row, const char* t); + 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); + +}; + +#endif // RECOVERY_UI_H @@ -1,664 +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 <errno.h> -#include <fcntl.h> -#include <linux/input.h> -#include <pthread.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <sys/types.h> -#include <time.h> -#include <unistd.h> - -#include "common.h" -#include <cutils/android_reboot.h> -#include "minui/minui.h" -#include "recovery_ui.h" - -#define MAX_COLS 96 -#define MAX_ROWS 32 - -#define CHAR_WIDTH 10 -#define CHAR_HEIGHT 18 - -#define UI_WAIT_KEY_TIMEOUT_SEC 120 - -UIParameters ui_parameters = { - 6, // indeterminate progress bar frames - 20, // fps - 7, // installation icon frames (0 == static image) - 13, 190, // installation icon overlay offset -}; - -static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER; -static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS]; -static gr_surface *gInstallationOverlay; -static gr_surface *gProgressBarIndeterminate; -static gr_surface gProgressBarEmpty; -static gr_surface gProgressBarFill; - -static const struct { gr_surface* surface; const char *name; } BITMAPS[] = { - { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" }, - { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" }, - { &gProgressBarEmpty, "progress_empty" }, - { &gProgressBarFill, "progress_fill" }, - { NULL, NULL }, -}; - -static int gCurrentIcon = 0; -static int gInstallingFrame = 0; - -static enum ProgressBarType { - PROGRESSBAR_TYPE_NONE, - PROGRESSBAR_TYPE_INDETERMINATE, - PROGRESSBAR_TYPE_NORMAL, -} gProgressBarType = PROGRESSBAR_TYPE_NONE; - -// Progress bar scope of current operation -static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0; -static double gProgressScopeTime, gProgressScopeDuration; - -// Set to 1 when both graphics pages are the same (except for the progress bar) -static int gPagesIdentical = 0; - -// Log text overlay, displayed when a magic key is pressed -static char text[MAX_ROWS][MAX_COLS]; -static int text_cols = 0, text_rows = 0; -static int text_col = 0, text_row = 0, text_top = 0; -static int show_text = 0; -static int show_text_ever = 0; // has show_text ever been 1? - -static char menu[MAX_ROWS][MAX_COLS]; -static int show_menu = 0; -static int menu_top = 0, menu_items = 0, menu_sel = 0; - -// Key event input queue -static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER; -static int key_queue[256], key_queue_len = 0; -static volatile char key_pressed[KEY_MAX + 1]; - -// Return the current time as a double (including fractions of a second). -static double now() { - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec + tv.tv_usec / 1000000.0; -} - -// Draw the given frame over the installation overlay animation. The -// background is not cleared or draw with the base icon first; we -// assume that the frame already contains some other frame of the -// animation. Does nothing if no overlay animation is defined. -// Should only be called with gUpdateMutex locked. -static void draw_install_overlay_locked(int frame) { - if (gInstallationOverlay == NULL) return; - gr_surface surface = gInstallationOverlay[frame]; - int iconWidth = gr_get_width(surface); - int iconHeight = gr_get_height(surface); - gr_blit(surface, 0, 0, iconWidth, iconHeight, - ui_parameters.install_overlay_offset_x, - ui_parameters.install_overlay_offset_y); -} - -// Clear the screen and draw the currently selected background icon (if any). -// Should only be called with gUpdateMutex locked. -static void draw_background_locked(int icon) -{ - gPagesIdentical = 0; - gr_color(0, 0, 0, 255); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - if (icon) { - gr_surface surface = gBackgroundIcon[icon]; - int iconWidth = gr_get_width(surface); - int iconHeight = gr_get_height(surface); - int iconX = (gr_fb_width() - iconWidth) / 2; - int iconY = (gr_fb_height() - iconHeight) / 2; - gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); - if (icon == BACKGROUND_ICON_INSTALLING) { - draw_install_overlay_locked(gInstallingFrame); - } - } -} - -// Draw the progress bar (if any) on the screen. Does not flip pages. -// Should only be called with gUpdateMutex locked. -static void draw_progress_locked() -{ - if (gCurrentIcon == BACKGROUND_ICON_INSTALLING) { - draw_install_overlay_locked(gInstallingFrame); - } - - if (gProgressBarType != PROGRESSBAR_TYPE_NONE) { - int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]); - int width = gr_get_width(gProgressBarEmpty); - int height = gr_get_height(gProgressBarEmpty); - - int dx = (gr_fb_width() - width)/2; - int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; - - // Erase behind the progress bar (in case this was a progress-only update) - gr_color(0, 0, 0, 255); - gr_fill(dx, dy, width, height); - - if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) { - float progress = gProgressScopeStart + gProgress * gProgressScopeSize; - int pos = (int) (progress * width); - - if (pos > 0) { - gr_blit(gProgressBarFill, 0, 0, pos, height, dx, dy); - } - if (pos < width-1) { - gr_blit(gProgressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); - } - } - - if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) { - static int frame = 0; - gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy); - frame = (frame + 1) % ui_parameters.indeterminate_frames; - } - } -} - -static void draw_text_line(int row, const char* t) { - if (t[0] != '\0') { - gr_text(0, (row+1)*CHAR_HEIGHT-1, t); - } -} - -// Redraw everything on the screen. Does not flip pages. -// Should only be called with gUpdateMutex locked. -static void draw_screen_locked(void) -{ - draw_background_locked(gCurrentIcon); - draw_progress_locked(); - - if (show_text) { - gr_color(0, 0, 0, 160); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - int i = 0; - if (show_menu) { - gr_color(64, 96, 255, 255); - gr_fill(0, (menu_top+menu_sel) * CHAR_HEIGHT, - gr_fb_width(), (menu_top+menu_sel+1)*CHAR_HEIGHT+1); - - for (; i < menu_top + menu_items; ++i) { - if (i == menu_top + menu_sel) { - gr_color(255, 255, 255, 255); - draw_text_line(i, menu[i]); - gr_color(64, 96, 255, 255); - } else { - draw_text_line(i, menu[i]); - } - } - gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1, - gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1); - ++i; - } - - gr_color(255, 255, 0, 255); - - for (; i < text_rows; ++i) { - draw_text_line(i, text[(i+text_top) % text_rows]); - } - } -} - -// Redraw everything on the screen and flip the screen (make it visible). -// Should only be called with gUpdateMutex locked. -static void update_screen_locked(void) -{ - draw_screen_locked(); - gr_flip(); -} - -// Updates only the progress bar, if possible, otherwise redraws the screen. -// Should only be called with gUpdateMutex locked. -static void update_progress_locked(void) -{ - if (show_text || !gPagesIdentical) { - draw_screen_locked(); // Must redraw the whole screen - gPagesIdentical = 1; - } else { - draw_progress_locked(); // Draw only the progress bar and overlays - } - gr_flip(); -} - -// Keeps the progress bar updated, even when the process is otherwise busy. -static void *progress_thread(void *cookie) -{ - double interval = 1.0 / ui_parameters.update_fps; - for (;;) { - double start = now(); - pthread_mutex_lock(&gUpdateMutex); - - int redraw = 0; - - // update the installation animation, if active - // skip this if we have a text overlay (too expensive to update) - if (gCurrentIcon == BACKGROUND_ICON_INSTALLING && - ui_parameters.installing_frames > 0 && - !show_text) { - gInstallingFrame = - (gInstallingFrame + 1) % ui_parameters.installing_frames; - redraw = 1; - } - - // update the progress bar animation, if active - // skip this if we have a text overlay (too expensive to update) - if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) { - redraw = 1; - } - - // move the progress bar forward on timed intervals, if configured - int duration = gProgressScopeDuration; - if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) { - double elapsed = now() - gProgressScopeTime; - float progress = 1.0 * elapsed / duration; - if (progress > 1.0) progress = 1.0; - if (progress > gProgress) { - gProgress = progress; - redraw = 1; - } - } - - if (redraw) update_progress_locked(); - - pthread_mutex_unlock(&gUpdateMutex); - double end = now(); - // minimum of 20ms delay between frames - double delay = interval - (end-start); - if (delay < 0.02) delay = 0.02; - usleep((long)(delay * 1000000)); - } - return NULL; -} - -static int rel_sum = 0; - -static int input_callback(int fd, short revents, void *data) -{ - struct input_event ev; - int ret; - int fake_key = 0; - - ret = ev_get_input(fd, revents, &ev); - if (ret) - return -1; - - if (ev.type == EV_SYN) { - return 0; - } else if (ev.type == EV_REL) { - if (ev.code == REL_Y) { - // accumulate the up or down motion reported by - // the trackball. When it exceeds a threshold - // (positive or negative), fake an up/down - // key event. - rel_sum += ev.value; - if (rel_sum > 3) { - fake_key = 1; - ev.type = EV_KEY; - ev.code = KEY_DOWN; - ev.value = 1; - rel_sum = 0; - } else if (rel_sum < -3) { - fake_key = 1; - ev.type = EV_KEY; - ev.code = KEY_UP; - ev.value = 1; - rel_sum = 0; - } - } - } else { - rel_sum = 0; - } - - if (ev.type != EV_KEY || ev.code > KEY_MAX) - return 0; - - pthread_mutex_lock(&key_queue_mutex); - if (!fake_key) { - // our "fake" keys only report a key-down event (no - // key-up), so don't record them in the key_pressed - // table. - key_pressed[ev.code] = ev.value; - } - const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); - if (ev.value > 0 && key_queue_len < queue_max) { - key_queue[key_queue_len++] = ev.code; - pthread_cond_signal(&key_queue_cond); - } - pthread_mutex_unlock(&key_queue_mutex); - - if (ev.value > 0 && device_toggle_display(key_pressed, ev.code)) { - pthread_mutex_lock(&gUpdateMutex); - show_text = !show_text; - if (show_text) show_text_ever = 1; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); - } - - if (ev.value > 0 && device_reboot_now(key_pressed, ev.code)) { - android_reboot(ANDROID_RB_RESTART, 0, 0); - } - - return 0; -} - -// Reads input events, handles special hot keys, and adds to the key queue. -static void *input_thread(void *cookie) -{ - for (;;) { - if (!ev_wait(-1)) - ev_dispatch(); - } - return NULL; -} - -void ui_init(void) -{ - gr_init(); - ev_init(input_callback, NULL); - - text_col = text_row = 0; - text_rows = gr_fb_height() / CHAR_HEIGHT; - if (text_rows > MAX_ROWS) text_rows = MAX_ROWS; - text_top = 1; - - text_cols = gr_fb_width() / CHAR_WIDTH; - if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1; - - int i; - for (i = 0; BITMAPS[i].name != NULL; ++i) { - int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface); - if (result < 0) { - LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result); - } - } - - gProgressBarIndeterminate = malloc(ui_parameters.indeterminate_frames * - sizeof(gr_surface)); - for (i = 0; i < ui_parameters.indeterminate_frames; ++i) { - char filename[40]; - // "indeterminate01.png", "indeterminate02.png", ... - sprintf(filename, "indeterminate%02d", i+1); - int result = res_create_surface(filename, gProgressBarIndeterminate+i); - if (result < 0) { - LOGE("Missing bitmap %s\n(Code %d)\n", filename, result); - } - } - - if (ui_parameters.installing_frames > 0) { - gInstallationOverlay = malloc(ui_parameters.installing_frames * - sizeof(gr_surface)); - for (i = 0; i < ui_parameters.installing_frames; ++i) { - char filename[40]; - // "icon_installing_overlay01.png", - // "icon_installing_overlay02.png", ... - sprintf(filename, "icon_installing_overlay%02d", i+1); - int result = res_create_surface(filename, gInstallationOverlay+i); - if (result < 0) { - LOGE("Missing bitmap %s\n(Code %d)\n", filename, result); - } - } - - // Adjust the offset to account for the positioning of the - // base image on the screen. - if (gBackgroundIcon[BACKGROUND_ICON_INSTALLING] != NULL) { - gr_surface bg = gBackgroundIcon[BACKGROUND_ICON_INSTALLING]; - ui_parameters.install_overlay_offset_x += - (gr_fb_width() - gr_get_width(bg)) / 2; - ui_parameters.install_overlay_offset_y += - (gr_fb_height() - gr_get_height(bg)) / 2; - } - } else { - gInstallationOverlay = NULL; - } - - pthread_t t; - pthread_create(&t, NULL, progress_thread, NULL); - pthread_create(&t, NULL, input_thread, NULL); -} - -void ui_set_background(int icon) -{ - pthread_mutex_lock(&gUpdateMutex); - gCurrentIcon = icon; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_show_indeterminate_progress() -{ - pthread_mutex_lock(&gUpdateMutex); - if (gProgressBarType != PROGRESSBAR_TYPE_INDETERMINATE) { - gProgressBarType = PROGRESSBAR_TYPE_INDETERMINATE; - update_progress_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_show_progress(float portion, int seconds) -{ - pthread_mutex_lock(&gUpdateMutex); - gProgressBarType = PROGRESSBAR_TYPE_NORMAL; - gProgressScopeStart += gProgressScopeSize; - gProgressScopeSize = portion; - gProgressScopeTime = now(); - gProgressScopeDuration = seconds; - gProgress = 0; - update_progress_locked(); - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_set_progress(float fraction) -{ - pthread_mutex_lock(&gUpdateMutex); - if (fraction < 0.0) fraction = 0.0; - if (fraction > 1.0) fraction = 1.0; - if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && fraction > gProgress) { - // Skip updates that aren't visibly different. - int width = gr_get_width(gProgressBarIndeterminate[0]); - float scale = width * gProgressScopeSize; - if ((int) (gProgress * scale) != (int) (fraction * scale)) { - gProgress = fraction; - update_progress_locked(); - } - } - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_reset_progress() -{ - pthread_mutex_lock(&gUpdateMutex); - gProgressBarType = PROGRESSBAR_TYPE_NONE; - gProgressScopeStart = gProgressScopeSize = 0; - gProgressScopeTime = gProgressScopeDuration = 0; - gProgress = 0; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_print(const char *fmt, ...) -{ - char buf[256]; - va_list ap; - va_start(ap, fmt); - vsnprintf(buf, 256, fmt, ap); - va_end(ap); - - fputs(buf, stdout); - - // This can get called before ui_init(), so be careful. - pthread_mutex_lock(&gUpdateMutex); - if (text_rows > 0 && text_cols > 0) { - char *ptr; - for (ptr = buf; *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col >= text_cols) { - text[text_row][text_col] = '\0'; - text_col = 0; - text_row = (text_row + 1) % text_rows; - if (text_row == text_top) text_top = (text_top + 1) % text_rows; - } - if (*ptr != '\n') text[text_row][text_col++] = *ptr; - } - text[text_row][text_col] = '\0'; - update_screen_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_start_menu(char** headers, char** items, int initial_selection) { - int i; - pthread_mutex_lock(&gUpdateMutex); - 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[i][text_cols-1] = '\0'; - } - menu_items = i - menu_top; - show_menu = 1; - menu_sel = initial_selection; - update_screen_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); -} - -int ui_menu_select(int sel) { - int old_sel; - pthread_mutex_lock(&gUpdateMutex); - if (show_menu > 0) { - old_sel = menu_sel; - menu_sel = sel; - if (menu_sel < 0) menu_sel = 0; - if (menu_sel >= menu_items) menu_sel = menu_items-1; - sel = menu_sel; - if (menu_sel != old_sel) update_screen_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); - return sel; -} - -void ui_end_menu() { - int i; - pthread_mutex_lock(&gUpdateMutex); - if (show_menu > 0 && text_rows > 0 && text_cols > 0) { - show_menu = 0; - update_screen_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); -} - -int ui_text_visible() -{ - pthread_mutex_lock(&gUpdateMutex); - int visible = show_text; - pthread_mutex_unlock(&gUpdateMutex); - return visible; -} - -int ui_text_ever_visible() -{ - pthread_mutex_lock(&gUpdateMutex); - int ever_visible = show_text_ever; - pthread_mutex_unlock(&gUpdateMutex); - return ever_visible; -} - -void ui_show_text(int visible) -{ - pthread_mutex_lock(&gUpdateMutex); - show_text = visible; - if (show_text) show_text_ever = 1; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); -} - -// Return true if USB is connected. -static int usb_connected() { - int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); - if (fd < 0) { - printf("failed to open /sys/class/android_usb/android0/state: %s\n", - strerror(errno)); - return 0; - } - - char buf; - /* 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", - strerror(errno)); - } - return connected; -} - -int ui_wait_key() -{ - pthread_mutex_lock(&key_queue_mutex); - - // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is - // plugged in. - do { - struct timeval now; - struct timespec timeout; - gettimeofday(&now, NULL); - timeout.tv_sec = now.tv_sec; - timeout.tv_nsec = now.tv_usec * 1000; - timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; - - int rc = 0; - while (key_queue_len == 0 && rc != ETIMEDOUT) { - rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, - &timeout); - } - } while (usb_connected() && key_queue_len == 0); - - int key = -1; - if (key_queue_len > 0) { - key = key_queue[0]; - memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); - } - pthread_mutex_unlock(&key_queue_mutex); - return key; -} - -int ui_key_pressed(int key) -{ - // This is a volatile static array, don't bother locking - return key_pressed[key]; -} - -void ui_clear_key_queue() { - pthread_mutex_lock(&key_queue_mutex); - key_queue_len = 0; - pthread_mutex_unlock(&key_queue_mutex); -} @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include <cutils/android_reboot.h> + +#include "common.h" +#include "device.h" +#include "minui/minui.h" +#include "screen_ui.h" +#include "ui.h" + +#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) { + pthread_mutex_init(&key_queue_mutex, NULL); + pthread_cond_init(&key_queue_cond, NULL); + self = this; +} + +void RecoveryUI::Init() { + ev_init(input_callback, NULL); + pthread_create(&input_t, NULL, input_thread, NULL); +} + + +int RecoveryUI::input_callback(int fd, short revents, void* data) +{ + struct input_event ev; + int ret; + + ret = ev_get_input(fd, revents, &ev); + if (ret) + return -1; + + if (ev.type == EV_SYN) { + return 0; + } else if (ev.type == EV_REL) { + if (ev.code == REL_Y) { + // accumulate the up or down motion reported by + // the trackball. When it exceeds a threshold + // (positive or negative), fake an up/down + // key event. + self->rel_sum += ev.value; + if (self->rel_sum > 3) { + self->process_key(KEY_DOWN, 1); // press down key + self->process_key(KEY_DOWN, 0); // and release it + self->rel_sum = 0; + } else if (self->rel_sum < -3) { + self->process_key(KEY_UP, 1); // press up key + self->process_key(KEY_UP, 0); // and release it + self->rel_sum = 0; + } + } + } else { + self->rel_sum = 0; + } + + if (ev.type == EV_KEY && ev.code <= KEY_MAX) + self->process_key(ev.code, ev.value); + + return 0; +} + +// Process a key-up or -down event. A key is "registered" when it is +// pressed and then released, with no other keypresses or releases in +// between. Registered keys are passed to CheckKey() to see if it +// should trigger a visibility toggle, an immediate reboot, or be +// queued to be processed next time the foreground thread wants a key +// (eg, for the menu). +// +// We also keep track of which keys are currently down so that +// CheckKey can call IsKeyPressed to see what other keys are held when +// a key is registered. +// +// updown == 1 for key down events; 0 for key up events +void RecoveryUI::process_key(int key_code, int updown) { + bool register_key = false; + + pthread_mutex_lock(&key_queue_mutex); + key_pressed[key_code] = updown; + if (updown) { + key_last_down = key_code; + } else { + if (key_last_down == key_code) + register_key = true; + key_last_down = -1; + } + pthread_mutex_unlock(&key_queue_mutex); + + if (register_key) { + switch (CheckKey(key_code)) { + case RecoveryUI::IGNORE: + break; + + case RecoveryUI::TOGGLE: + ShowText(!IsTextVisible()); + break; + + case RecoveryUI::REBOOT: + android_reboot(ANDROID_RB_RESTART, 0, 0); + break; + + case RecoveryUI::ENQUEUE: + pthread_mutex_lock(&key_queue_mutex); + const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); + if (key_queue_len < queue_max) { + key_queue[key_queue_len++] = key_code; + pthread_cond_signal(&key_queue_cond); + } + pthread_mutex_unlock(&key_queue_mutex); + break; + } + } +} + +// 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() +{ + pthread_mutex_lock(&key_queue_mutex); + + // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is + // plugged in. + do { + struct timeval now; + struct timespec timeout; + gettimeofday(&now, NULL); + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = now.tv_usec * 1000; + timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; + + int rc = 0; + while (key_queue_len == 0 && rc != ETIMEDOUT) { + rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, + &timeout); + } + } while (usb_connected() && key_queue_len == 0); + + int key = -1; + if (key_queue_len > 0) { + key = key_queue[0]; + memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + } + pthread_mutex_unlock(&key_queue_mutex); + return key; +} + +// Return true if USB is connected. +bool RecoveryUI::usb_connected() { + 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", + strerror(errno)); + return 0; + } + + char buf; + /* 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", + strerror(errno)); + } + return connected; +} + +bool RecoveryUI::IsKeyPressed(int key) +{ + pthread_mutex_lock(&key_queue_mutex); + int pressed = key_pressed[key]; + pthread_mutex_unlock(&key_queue_mutex); + return pressed; +} + +void RecoveryUI::FlushKeys() { + pthread_mutex_lock(&key_queue_mutex); + key_queue_len = 0; + pthread_mutex_unlock(&key_queue_mutex); +} + +RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) { + return RecoveryUI::ENQUEUE; +} @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_UI_H +#define RECOVERY_UI_H + +#include <linux/input.h> +#include <pthread.h> + +// Abstract class for controlling the user interface during recovery. +class RecoveryUI { + public: + RecoveryUI(); + + virtual ~RecoveryUI() { } + + // Initialize the object; called before anything else. + virtual void Init(); + + // Set the overall recovery state ("background image"). + enum Icon { NONE, INSTALLING, ERROR }; + virtual void SetBackground(Icon icon) = 0; + + // --- progress indicator --- + enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE }; + virtual void SetProgressType(ProgressType determinate) = 0; + + // Show a progress bar and define the scope of the next operation: + // portion - fraction of the progress bar the next operation will use + // seconds - expected time interval (progress bar moves at this minimum rate) + virtual void ShowProgress(float portion, float seconds) = 0; + + // Set progress bar position (0.0 - 1.0 within the scope defined + // by the last call to ShowProgress). + virtual void SetProgress(float fraction) = 0; + + // --- text log --- + + virtual void ShowText(bool visible) = 0; + + virtual bool IsTextVisible() = 0; + + virtual bool WasTextEverVisible() = 0; + + // 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; + + // --- key handling --- + + // Wait for keypress and return it. May return -1 after timeout. + virtual int WaitKey(); + + virtual bool IsKeyPressed(int key); + + // Erase any queued-up keys. + virtual void FlushKeys(); + + // Called on each keypress, 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 }; + virtual KeyAction CheckKey(int key); + + // --- menu display --- + + // Display some header text followed by a menu of items, which appears + // at the top of the screen (in place of any scrolling ui_print() + // output, if necessary). + 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). + virtual int SelectMenu(int sel) = 0; + + // End menu mode, resetting the text overlay so that ui_print() + // statements will be displayed. + virtual void EndMenu() = 0; + +private: + // Key event input queue + pthread_mutex_t key_queue_mutex; + pthread_cond_t key_queue_cond; + int key_queue[256], key_queue_len; + char key_pressed[KEY_MAX + 1]; // under key_queue_mutex + int key_last_down; // under key_queue_mutex + int rel_sum; + + pthread_t input_t; + + static void* input_thread(void* cookie); + static int input_callback(int fd, short revents, void* data); + void process_key(int key_code, int updown); + bool usb_connected(); +}; + +#endif // RECOVERY_UI_H diff --git a/updater/install.c b/updater/install.c index 64da2efd6..f981017bf 100644 --- a/updater/install.c +++ b/updater/install.c @@ -33,7 +33,6 @@ #include "edify/expr.h" #include "mincrypt/sha.h" #include "minzip/DirUtil.h" -#include "minelf/Retouch.h" #include "mtdutils/mounts.h" #include "mtdutils/mtdutils.h" #include "updater.h" @@ -458,121 +457,6 @@ Value* PackageExtractFileFn(const char* name, State* state, } -// retouch_binaries(lib1, lib2, ...) -Value* RetouchBinariesFn(const char* name, State* state, - int argc, Expr* argv[]) { - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - - char **retouch_entries = ReadVarArgs(state, argc, argv); - if (retouch_entries == NULL) { - return StringValue(strdup("t")); - } - - // some randomness from the clock - int32_t override_base; - bool override_set = false; - int32_t random_base = time(NULL) % 1024; - // some more randomness from /dev/random - FILE *f_random = fopen("/dev/random", "rb"); - uint16_t random_bits = 0; - if (f_random != NULL) { - fread(&random_bits, 2, 1, f_random); - random_bits = random_bits % 1024; - fclose(f_random); - } - random_base = (random_base + random_bits) % 1024; - fprintf(ui->cmd_pipe, "ui_print Random offset: 0x%x\n", random_base); - fprintf(ui->cmd_pipe, "ui_print\n"); - - // make sure we never randomize to zero; this let's us look at a file - // and know for sure whether it has been processed; important in the - // crash recovery process - if (random_base == 0) random_base = 1; - // make sure our randomization is page-aligned - random_base *= -0x1000; - override_base = random_base; - - int i = 0; - bool success = true; - while (i < (argc - 1)) { - success = success && retouch_one_library(retouch_entries[i], - retouch_entries[i+1], - random_base, - override_set ? - NULL : - &override_base); - if (!success) - ErrorAbort(state, "Failed to retouch '%s'.", retouch_entries[i]); - - free(retouch_entries[i]); - free(retouch_entries[i+1]); - i += 2; - - if (success && override_base != 0) { - random_base = override_base; - override_set = true; - } - } - if (i < argc) { - free(retouch_entries[i]); - success = false; - } - free(retouch_entries); - - if (!success) { - Value* v = malloc(sizeof(Value)); - v->type = VAL_STRING; - v->data = NULL; - v->size = -1; - return v; - } - return StringValue(strdup("t")); -} - - -// undo_retouch_binaries(lib1, lib2, ...) -Value* UndoRetouchBinariesFn(const char* name, State* state, - int argc, Expr* argv[]) { - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - - char **retouch_entries = ReadVarArgs(state, argc, argv); - if (retouch_entries == NULL) { - return StringValue(strdup("t")); - } - - int i = 0; - bool success = true; - int32_t override_base; - while (i < (argc-1)) { - success = success && retouch_one_library(retouch_entries[i], - retouch_entries[i+1], - 0 /* undo => offset==0 */, - NULL); - if (!success) - ErrorAbort(state, "Failed to unretouch '%s'.", - retouch_entries[i]); - - free(retouch_entries[i]); - free(retouch_entries[i+1]); - i += 2; - } - if (i < argc) { - free(retouch_entries[i]); - success = false; - } - free(retouch_entries); - - if (!success) { - Value* v = malloc(sizeof(Value)); - v->type = VAL_STRING; - v->data = NULL; - v->size = -1; - return v; - } - return StringValue(strdup("t")); -} - - // symlink target src1 src2 ... // unlinks any previously existing src1, src2, etc before creating symlinks. Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { @@ -589,21 +473,27 @@ Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { return NULL; } + int bad = 0; int i; for (i = 0; i < argc-1; ++i) { if (unlink(srcs[i]) < 0) { if (errno != ENOENT) { fprintf(stderr, "%s: failed to remove %s: %s\n", name, srcs[i], strerror(errno)); + ++bad; } } if (symlink(target, srcs[i]) < 0) { fprintf(stderr, "%s: failed to symlink %s to %s: %s\n", name, srcs[i], target, strerror(errno)); + ++bad; } free(srcs[i]); } free(srcs); + if (bad) { + return ErrorAbort(state, "%s: some symlinks failed", name); + } return StringValue(strdup("")); } @@ -622,6 +512,7 @@ Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { char* end; int i; + int bad = 0; int uid = strtoul(args[0], &end, 0); if (*end != '\0' || args[0][0] == 0) { @@ -663,10 +554,12 @@ Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { if (chown(args[i], uid, gid) < 0) { fprintf(stderr, "%s: chown of %s to %d %d failed: %s\n", name, args[i], uid, gid, strerror(errno)); + ++bad; } if (chmod(args[i], mode) < 0) { fprintf(stderr, "%s: chmod of %s to %o failed: %s\n", name, args[i], mode, strerror(errno)); + ++bad; } } } @@ -678,6 +571,10 @@ done: } free(args); + if (bad) { + free(result); + return ErrorAbort(state, "%s: some changes failed", name); + } return StringValue(result); } @@ -815,11 +712,12 @@ Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) { return NULL; } + char* partition = NULL; if (partition_value->type != VAL_STRING) { ErrorAbort(state, "partition argument to %s must be string", name); goto done; } - char* partition = partition_value->data; + partition = partition_value->data; if (strlen(partition) == 0) { ErrorAbort(state, "partition argument to %s can't be empty", name); goto done; @@ -1212,8 +1110,6 @@ void RegisterInstallFunctions() { RegisterFunction("delete_recursive", DeleteFn); RegisterFunction("package_extract_dir", PackageExtractDirFn); RegisterFunction("package_extract_file", PackageExtractFileFn); - RegisterFunction("retouch_binaries", RetouchBinariesFn); - RegisterFunction("undo_retouch_binaries", UndoRetouchBinariesFn); RegisterFunction("symlink", SymlinkFn); RegisterFunction("set_perm", SetPermFn); RegisterFunction("set_perm_recursive", SetPermFn); diff --git a/verifier.c b/verifier.cpp index 729e085cf..1c5a41d1b 100644 --- a/verifier.c +++ b/verifier.cpp @@ -16,6 +16,7 @@ #include "common.h" #include "verifier.h" +#include "ui.h" #include "mincrypt/rsa.h" #include "mincrypt/sha.h" @@ -24,6 +25,8 @@ #include <stdio.h> #include <errno.h> +extern RecoveryUI* ui; + // Look for an RSA signature embedded in the .ZIP file comment given // the path to the zip. Verify it matches one of the given public // keys. @@ -32,7 +35,7 @@ // or no key matches the signature). int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) { - ui_set_progress(0.0); + ui->SetProgress(0.0); FILE* f = fopen(path, "rb"); if (f == NULL) { @@ -69,8 +72,8 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey return VERIFY_FAILURE; } - int comment_size = footer[4] + (footer[5] << 8); - int signature_start = footer[0] + (footer[1] << 8); + size_t comment_size = footer[4] + (footer[5] << 8); + size_t signature_start = footer[0] + (footer[1] << 8); LOGI("comment is %d bytes; signature %d bytes from end\n", comment_size, signature_start); @@ -99,7 +102,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey // bytes) and the comment data. size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2; - unsigned char* eocd = malloc(eocd_size); + unsigned char* eocd = (unsigned char*)malloc(eocd_size); if (eocd == NULL) { LOGE("malloc for EOCD record failed\n"); fclose(f); @@ -120,7 +123,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey return VERIFY_FAILURE; } - int i; + size_t i; for (i = 4; i < eocd_size-3; ++i) { if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { @@ -138,7 +141,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey SHA_CTX ctx; SHA_init(&ctx); - unsigned char* buffer = malloc(BUFFER_SIZE); + unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE); if (buffer == NULL) { LOGE("failed to alloc memory for sha1 buffer\n"); fclose(f); @@ -149,7 +152,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey size_t so_far = 0; fseek(f, 0, SEEK_SET); while (so_far < signed_len) { - int size = BUFFER_SIZE; + size_t size = BUFFER_SIZE; if (signed_len - so_far < size) size = signed_len - so_far; if (fread(buffer, 1, size, f) != size) { LOGE("failed to read data from %s (%s)\n", path, strerror(errno)); @@ -160,7 +163,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey so_far += size; double f = so_far / (double)signed_len; if (f > frac + 0.02 || size == so_far) { - ui_set_progress(f); + ui->SetProgress(f); frac = f; } } diff --git a/verifier_test.c b/verifier_test.cpp index 5b6c1f451..fe5519d79 100644 --- a/verifier_test.c +++ b/verifier_test.cpp @@ -19,6 +19,7 @@ #include <stdarg.h> #include "verifier.h" +#include "ui.h" // This is build/target/product/security/testkey.x509.pem after being // dumped out by dumpkey.jar. @@ -58,18 +59,36 @@ RSAPublicKey test_key = 367251975, 810756730, -1941182952, 1175080310 } }; -void ui_print(const char* fmt, ...) { - char buf[256]; - va_list ap; - va_start(ap, fmt); - vsnprintf(buf, 256, fmt, ap); - va_end(ap); +RecoveryUI* ui = NULL; - fputs(buf, stderr); -} +// verifier expects to find a UI object; we provide one that does +// nothing but print. +class FakeUI : public RecoveryUI { + void Init() { } + void SetBackground(Icon icon) { } -void ui_set_progress(float fraction) { -} + void SetProgressType(ProgressType determinate) { } + void ShowProgress(float portion, float seconds) { } + void SetProgress(float fraction) { } + + void ShowText(bool visible) { } + bool IsTextVisible() { return false; } + bool WasTextEverVisible() { return false; } + void Print(const char* fmt, ...) { + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + fputs(buf, stderr); + } + + void StartMenu(const char* const * headers, const char* const * items, + int initial_selection) { } + int SelectMenu(int sel) { return 0; } + void EndMenu() { } +}; int main(int argc, char **argv) { if (argc != 2) { @@ -77,6 +96,8 @@ int main(int argc, char **argv) { return 2; } + ui = new FakeUI(); + int result = verify_file(argv[1], &test_key, 1); if (result == VERIFY_SUCCESS) { printf("SUCCESS\n"); diff --git a/verifier_test.sh b/verifier_test.sh index 6350e80d3..a1de5c57b 100755 --- a/verifier_test.sh +++ b/verifier_test.sh @@ -1,11 +1,7 @@ #!/bin/bash # -# A test suite for applypatch. Run in a client where you have done -# envsetup, choosecombo, etc. -# -# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your -# system partition. -# +# A test suite for recovery's package signature verifier. Run in a +# client where you have done envsetup, lunch, etc. # # TODO: find some way to get this run regularly along with the rest of # the tests. |