diff --git a/Android.mk b/Android.mk index 816d143..b596941 100644 --- a/Android.mk +++ b/Android.mk @@ -1,15 +1,18 @@ +ifneq ($(TARGET_SIMULATOR),true) +ifeq ($(TARGET_ARCH),arm) + LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) commands_recovery_local_path := $(LOCAL_PATH) - -ifneq ($(TARGET_SIMULATOR),true) -ifeq ($(TARGET_ARCH),arm) +# LOCAL_CPP_EXTENSION := .c LOCAL_SRC_FILES := \ + extendedcommands.c \ + legacy.c \ + commands.c \ recovery.c \ bootloader.c \ - commands.c \ firmware.c \ install.c \ roots.c \ @@ -22,6 +25,9 @@ LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true +RECOVERY_API_VERSION := 1.6.3 +LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) + # This binary is in the recovery ramdisk, which is otherwise a copy of root. # It gets copied there in config/Makefile. LOCAL_MODULE_TAGS suppresses # a (redundant) copy of the binary in /system/bin for user builds. @@ -29,32 +35,30 @@ LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := eng -LOCAL_STATIC_LIBRARIES := libminzip libunz libamend libmtdutils libmincrypt -LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libcutils +LOCAL_STATIC_LIBRARIES := +ifeq ($(TARGET_RECOVERY_UI_LIB),) + LOCAL_SRC_FILES += default_recovery_ui.c +else + LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) +endif +LOCAL_STATIC_LIBRARIES += libbusybox libclearsilverregex libmkyaffs2image libunyaffs libdump_image libflash_image libmtdutils +LOCAL_STATIC_LIBRARIES += libamend +LOCAL_STATIC_LIBRARIES += libminzip libunz libmtdutils libmincrypt +LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils LOCAL_STATIC_LIBRARIES += libstdc++ libc -# Specify a C-includable file containing the OTA public keys. -# This is built in config/Makefile. -# *** THIS IS A TOTAL HACK; EXECUTABLES MUST NOT CHANGE BETWEEN DIFFERENT -# PRODUCTS/BUILD TYPES. *** -# TODO: make recovery read the keys from an external file. -RECOVERY_INSTALL_OTA_KEYS_INC := \ - $(call intermediates-dir-for,PACKAGING,ota_keys_inc)/keys.inc -# Let install.c say #include "keys.inc" -LOCAL_C_INCLUDES += $(dir $(RECOVERY_INSTALL_OTA_KEYS_INC)) - include $(BUILD_EXECUTABLE) -# Depend on the generated keys.inc containing the OTA public keys. -$(intermediates)/install.o: $(RECOVERY_INSTALL_OTA_KEYS_INC) - -include $(commands_recovery_local_path)/minui/Android.mk - -endif # TARGET_ARCH == arm -endif # !TARGET_SIMULATOR - include $(commands_recovery_local_path)/amend/Android.mk +include $(commands_recovery_local_path)/nandroid/Android.mk +include $(commands_recovery_local_path)/minui/Android.mk include $(commands_recovery_local_path)/minzip/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 +include $(commands_recovery_local_path)/updater/Android.mk commands_recovery_local_path := + +endif # TARGET_ARCH == arm +endif # !TARGET_SIMULATOR + diff --git a/amend/register.c b/amend/register.c index 167dd32..e45a973 100644 --- a/amend/register.c +++ b/amend/register.c @@ -125,6 +125,19 @@ cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[], return -1; } +/* delete + */ +static int +cmd_delete(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_WORDS(); +//xxx + return -1; +} + /* mark dirty|clean */ static int @@ -165,6 +178,9 @@ registerUpdateCommands() ret = registerCommand("copy_dir", CMD_ARGS_WORDS, cmd_copy_dir, NULL); if (ret < 0) return ret; + ret = registerCommand("delete", CMD_ARGS_WORDS, cmd_delete, NULL); + if (ret < 0) return ret; + ret = registerCommand("format", CMD_ARGS_WORDS, cmd_format, NULL); if (ret < 0) return ret; diff --git a/bootloader.c b/bootloader.c index de441e1..bc79bee 100644 --- a/bootloader.c +++ b/bootloader.c @@ -198,7 +198,7 @@ int write_update_for_bootloader( header.version = UPDATE_VERSION; header.size = header_size; - header.image_offset = mtd_erase_blocks(write, 0); + off_t image_start_pos = mtd_erase_blocks(write, 0); header.image_length = update_length; if ((int) header.image_offset == -1 || mtd_write_data(write, update, update_length) != update_length) { @@ -206,6 +206,8 @@ int write_update_for_bootloader( mtd_write_close(write); return -1; } + off_t busy_start_pos = mtd_erase_blocks(write, 0); + header.image_offset = mtd_find_write_start(write, image_start_pos); header.bitmap_width = bitmap_width; header.bitmap_height = bitmap_height; @@ -213,7 +215,6 @@ int write_update_for_bootloader( int bitmap_length = (bitmap_bpp + 7) / 8 * bitmap_width * bitmap_height; - header.busy_bitmap_offset = mtd_erase_blocks(write, 0); header.busy_bitmap_length = busy_bitmap != NULL ? bitmap_length : 0; if ((int) header.busy_bitmap_offset == -1 || mtd_write_data(write, busy_bitmap, bitmap_length) != bitmap_length) { @@ -221,8 +222,9 @@ int write_update_for_bootloader( mtd_write_close(write); return -1; } + off_t fail_start_pos = mtd_erase_blocks(write, 0); + header.busy_bitmap_offset = mtd_find_write_start(write, busy_start_pos); - header.fail_bitmap_offset = mtd_erase_blocks(write, 0); header.fail_bitmap_length = fail_bitmap != NULL ? bitmap_length : 0; if ((int) header.fail_bitmap_offset == -1 || mtd_write_data(write, fail_bitmap, bitmap_length) != bitmap_length) { @@ -230,6 +232,8 @@ int write_update_for_bootloader( mtd_write_close(write); return -1; } + mtd_erase_blocks(write, 0); + header.fail_bitmap_offset = mtd_find_write_start(write, fail_start_pos); /* Write the header last, after all the blocks it refers to, so that * when the magic number is installed everything is valid. @@ -252,7 +256,7 @@ int write_update_for_bootloader( return -1; } - if (mtd_erase_blocks(write, 0) != (off_t) header.image_offset) { + if (mtd_erase_blocks(write, 0) != image_start_pos) { LOGE("Misalignment rewriting %s\n(%s)\n", CACHE_NAME, strerror(errno)); mtd_write_close(write); return -1; diff --git a/commands.c b/commands.c index 23ad91c..7b2c2c1 100644 --- a/commands.c +++ b/commands.c @@ -39,6 +39,8 @@ #include "minzip/Zip.h" #include "roots.h" +#include "extendedcommands.h" + static int gDidShowProgress = 0; #define UNUSED(p) ((void)(p)) @@ -107,6 +109,10 @@ cmd_assert(const char *name, void *cookie, int argc, const char *argv[], CHECK_BOOL(); NO_PERMS(permissions); + if (!script_assert_enabled) { + return 0; + } + /* If our argument is false, return non-zero (failure) * If our argument is true, return zero (success) */ @@ -391,7 +397,7 @@ cmd_run_program(const char *name, void *cookie, int argc, const char *argv[], int status; waitpid(pid, &status, 0); - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + if (WIFEXITED(status) && WEXITSTATUS(status) == 0 || !script_assert_enabled) { return 0; } else { LOGE("Error in %s\n(Status %d)\n", path, status); @@ -753,6 +759,98 @@ cmd_done(const char *name, void *cookie, int argc, const char *argv[], } +static int +cmd_backup_rom(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + + char* backup_name = NULL; + switch(argc) + { + case 0: + break; + case 1: + backup_name = argv[0]; + break; + default: + LOGE("Command %s requires zero or one argument\n", name); + return 1; + } + + return do_nandroid_backup(backup_name); +} + +static int +cmd_restore_rom(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + + if (argc != 1) { + LOGE("Command %s requires exactly one argument\n", name); + return 1; + } + + return do_nandroid_restore(argv[0]); +} + +static int +cmd_sleep(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + + if (argc != 1) { + LOGE("Command %s requires exactly one argument\n", name); + return 1; + } + + int seconds = atoi(argv[0]); + sleep(seconds); + + return 0; +} + +static int +cmd_print(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + + char message[1024]; + message[0] = NULL; + int i; + for (i = 0; i < argc; i++) + { + strcat(message, argv[i]); + strcat(message, " "); + } + strcat(message, "\n"); + + ui_print(message); + return 0; +} + +static int +cmd_install_zip(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + + if (argc != 1) { + LOGE("Command %s requires exactly one argument\n", name); + return 1; + } + + return install_zip(argv[0]); +} + /* * Function definitions */ @@ -1116,6 +1214,21 @@ register_update_commands(RecoveryCommandContext *ctx) ret = registerCommand("done", CMD_ARGS_WORDS, cmd_done, (void *)ctx); if (ret < 0) return ret; + ret = registerCommand("backup_rom", CMD_ARGS_WORDS, cmd_backup_rom, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("restore_rom", CMD_ARGS_WORDS, cmd_restore_rom, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("sleep", CMD_ARGS_WORDS, cmd_sleep, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("print", CMD_ARGS_WORDS, cmd_print, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("install_zip", CMD_ARGS_WORDS, cmd_install_zip, (void *)ctx); + if (ret < 0) return ret; + /* * Functions */ diff --git a/common.h b/common.h index e17f76a..d962a0a 100644 --- a/common.h +++ b/common.h @@ -47,7 +47,6 @@ void ui_end_menu(); // Set the icon (normally the only thing visible besides the progress bar). enum { BACKGROUND_ICON_NONE, - BACKGROUND_ICON_UNPACKING, BACKGROUND_ICON_INSTALLING, BACKGROUND_ICON_ERROR, BACKGROUND_ICON_FIRMWARE_INSTALLING, @@ -69,7 +68,7 @@ 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.5; +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; @@ -91,4 +90,7 @@ void ui_reset_progress(); #define LOGD(...) do {} while (0) #endif +#define STRINGIFY(x) #x +#define EXPAND(x) STRINGIFY(x) + #endif // RECOVERY_COMMON_H diff --git a/default_recovery_ui.c b/default_recovery_ui.c new file mode 100644 index 0000000..458c817 --- /dev/null +++ b/default_recovery_ui.c @@ -0,0 +1,80 @@ +/* + * 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 + +#include "recovery_ui.h" +#include "common.h" +#include "extendedcommands.h" + +char* MENU_HEADERS[] = { NULL }; + +char* MENU_ITEMS[] = { "reboot system now", + "apply sdcard:update.zip", + "wipe data/factory reset", + "wipe cache partition", + "install zip from sdcard", + "backup", + "restore", + "mount /sdcard", + "mount USB storage", + NULL }; + +int device_toggle_display(volatile char* key_pressed, int key_code) { + int alt = key_pressed[KEY_LEFTALT] || key_pressed[KEY_RIGHTALT]; + if (alt && key_code == KEY_L) + return 1; + // allow toggling of the display if the correct key is pressed, and the display toggle is allowed or the display is currently off + return get_allow_toggle_display() && (key_code == KEY_HOME || key_code == KEY_MENU || key_code == KEY_POWER || key_code == KEY_END); +} + +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: + case BTN_MOUSE: + return SELECT_ITEM; + + case KEY_POWER: + case KEY_END: + case KEY_BACKSPACE: + if (!get_allow_toggle_display()) + return GO_BACK; + } + } + + return NO_ACTION; +} + +int device_perform_action(int which) { + return which; +} + +int device_wipe_data() { + return 0; +} diff --git a/edify/Android.mk b/edify/Android.mk new file mode 100644 index 0000000..fac0ba7 --- /dev/null +++ b/edify/Android.mk @@ -0,0 +1,39 @@ +# Copyright 2009 The Android Open Source Project + +LOCAL_PATH := $(call my-dir) + +edify_src_files := \ + lexer.l \ + parser.y \ + expr.c + +# "-x c" forces the lex/yacc files to be compiled as c; +# the build system otherwise forces them to be c++. +edify_cflags := -x c + +# +# Build the host-side command line tool +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + $(edify_src_files) \ + main.c + +LOCAL_CFLAGS := $(edify_cflags) -g -O0 +LOCAL_MODULE := edify +LOCAL_YACCFLAGS := -v + +include $(BUILD_HOST_EXECUTABLE) + +# +# Build the device-side library +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(edify_src_files) + +LOCAL_CFLAGS := $(edify_cflags) +LOCAL_MODULE := libedify + +include $(BUILD_STATIC_LIBRARY) diff --git a/edify/README b/edify/README new file mode 100644 index 0000000..810455c --- /dev/null +++ b/edify/README @@ -0,0 +1,108 @@ +Update scripts (from donut onwards) are written in a new little +scripting language ("edify") that is superficially somewhat similar to +the old one ("amend"). This is a brief overview of the new language. + +- The entire script is a single expression. + +- All expressions are string-valued. + +- String literals appear in double quotes. \n, \t, \", and \\ are + understood, as are hexadecimal escapes like \x4a. + +- String literals consisting of only letters, numbers, colons, + underscores, slashes, and periods don't need to be in double quotes. + +- The following words are reserved: + + if then else endif + + They have special meaning when unquoted. (In quotes, they are just + string literals.) + +- When used as a boolean, the empty string is "false" and all other + strings are "true". + +- All functions are actually macros (in the Lisp sense); the body of + the function can control which (if any) of the arguments are + evaluated. This means that functions can act as control + structures. + +- Operators (like "&&" and "||") are just syntactic sugar for builtin + functions, so they can act as control structures as well. + +- ";" is a binary operator; evaluating it just means to first evaluate + the left side, then the right. It can also appear after any + expression. + +- Comments start with "#" and run to the end of the line. + + + +Some examples: + +- There's no distinction between quoted and unquoted strings; the + quotes are only needed if you want characters like whitespace to + appear in the string. The following expressions all evaluate to the + same string. + + "a b" + a + " " + b + "a" + " " + "b" + "a\x20b" + a + "\x20b" + concat(a, " ", "b") + "concat"(a, " ", "b") + + As shown in the last example, function names are just strings, + too. They must be string *literals*, however. This is not legal: + + ("con" + "cat")(a, " ", b) # syntax error! + + +- The ifelse() builtin takes three arguments: it evaluates exactly + one of the second and third, depending on whether the first one is + true. There is also some syntactic sugar to make expressions that + look like if/else statements: + + # these are all equivalent + ifelse(something(), "yes", "no") + if something() then yes else no endif + if something() then "yes" else "no" endif + + The else part is optional. + + if something() then "yes" endif # if something() is false, + # evaluates to false + + ifelse(condition(), "", abort()) # abort() only called if + # condition() is false + + The last example is equivalent to: + + assert(condition()) + + +- The && and || operators can be used similarly; they evaluate their + second argument only if it's needed to determine the truth of the + expression. Their value is the value of the last-evaluated + argument: + + file_exists("/data/system/bad") && delete("/data/system/bad") + + file_exists("/data/system/missing") || create("/data/system/missing") + + get_it() || "xxx" # returns value of get_it() if that value is + # true, otherwise returns "xxx" + + +- The purpose of ";" is to simulate imperative statements, of course, + but the operator can be used anywhere. Its value is the value of + its right side: + + concat(a;b;c, d, e;f) # evaluates to "cdf" + + A more useful example might be something like: + + ifelse(condition(), + (first_step(); second_step();), # second ; is optional + alternative_procedure()) diff --git a/edify/expr.c b/edify/expr.c new file mode 100644 index 0000000..72e5100 --- /dev/null +++ b/edify/expr.c @@ -0,0 +1,432 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include "expr.h" + +// Functions should: +// +// - return a malloc()'d string +// - if Evaluate() on any argument returns NULL, return NULL. + +int BooleanString(const char* s) { + return s[0] != '\0'; +} + +char* Evaluate(State* state, Expr* expr) { + return expr->fn(expr->name, state, expr->argc, expr->argv); +} + +char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc == 0) { + return strdup(""); + } + char** strings = malloc(argc * sizeof(char*)); + int i; + for (i = 0; i < argc; ++i) { + strings[i] = NULL; + } + char* result = NULL; + int length = 0; + for (i = 0; i < argc; ++i) { + strings[i] = Evaluate(state, argv[i]); + if (strings[i] == NULL) { + goto done; + } + length += strlen(strings[i]); + } + + result = malloc(length+1); + int p = 0; + for (i = 0; i < argc; ++i) { + strcpy(result+p, strings[i]); + p += strlen(strings[i]); + } + result[p] = '\0'; + + done: + for (i = 0; i < argc; ++i) { + free(strings[i]); + } + return result; +} + +char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2 && argc != 3) { + free(state->errmsg); + state->errmsg = strdup("ifelse expects 2 or 3 arguments"); + return NULL; + } + char* cond = Evaluate(state, argv[0]); + if (cond == NULL) { + return NULL; + } + + if (BooleanString(cond) == true) { + free(cond); + return Evaluate(state, argv[1]); + } else { + if (argc == 3) { + free(cond); + return Evaluate(state, argv[2]); + } else { + return cond; + } + } +} + +char* AbortFn(const char* name, State* state, int argc, Expr* argv[]) { + char* msg = NULL; + if (argc > 0) { + msg = Evaluate(state, argv[0]); + } + free(state->errmsg); + if (msg) { + state->errmsg = msg; + } else { + state->errmsg = strdup("called abort()"); + } + return NULL; +} + +char* AssertFn(const char* name, State* state, int argc, Expr* argv[]) { + int i; + for (i = 0; i < argc; ++i) { + char* v = Evaluate(state, argv[i]); + if (v == NULL) { + return NULL; + } + int b = BooleanString(v); + free(v); + if (!b) { + int prefix_len; + int len = argv[i]->end - argv[i]->start; + char* err_src = malloc(len + 20); + strcpy(err_src, "assert failed: "); + prefix_len = strlen(err_src); + memcpy(err_src + prefix_len, state->script + argv[i]->start, len); + err_src[prefix_len + len] = '\0'; + free(state->errmsg); + state->errmsg = err_src; + return NULL; + } + } + return strdup(""); +} + +char* SleepFn(const char* name, State* state, int argc, Expr* argv[]) { + char* val = Evaluate(state, argv[0]); + if (val == NULL) { + return NULL; + } + int v = strtol(val, NULL, 10); + sleep(v); + return val; +} + +char* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) { + int i; + for (i = 0; i < argc; ++i) { + char* v = Evaluate(state, argv[i]); + if (v == NULL) { + return NULL; + } + fputs(v, stdout); + free(v); + } + return strdup(""); +} + +char* LogicalAndFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + if (BooleanString(left) == true) { + free(left); + return Evaluate(state, argv[1]); + } else { + return left; + } +} + +char* LogicalOrFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + if (BooleanString(left) == false) { + free(left); + return Evaluate(state, argv[1]); + } else { + return left; + } +} + +char* LogicalNotFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* val = Evaluate(state, argv[0]); + if (val == NULL) return NULL; + bool bv = BooleanString(val); + free(val); + if (bv) { + return strdup(""); + } else { + return strdup("t"); + } +} + +char* SubstringFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* needle = Evaluate(state, argv[0]); + if (needle == NULL) return NULL; + char* haystack = Evaluate(state, argv[1]); + if (haystack == NULL) { + free(needle); + return NULL; + } + + char* result = strdup(strstr(haystack, needle) ? "t" : ""); + free(needle); + free(haystack); + return result; +} + +char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + char* right = Evaluate(state, argv[1]); + if (right == NULL) { + free(left); + return NULL; + } + + char* result = strdup(strcmp(left, right) == 0 ? "t" : ""); + free(left); + free(right); + return result; +} + +char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + char* right = Evaluate(state, argv[1]); + if (right == NULL) { + free(left); + return NULL; + } + + char* result = strdup(strcmp(left, right) != 0 ? "t" : ""); + free(left); + free(right); + return result; +} + +char* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + free(left); + return Evaluate(state, argv[1]); +} + +char* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + free(state->errmsg); + state->errmsg = strdup("less_than_int expects 2 arguments"); + return NULL; + } + + char* left; + char* right; + if (ReadArgs(state, argv, 2, &left, &right) < 0) return NULL; + + bool result = false; + char* end; + + long l_int = strtol(left, &end, 10); + if (left[0] == '\0' || *end != '\0') { + fprintf(stderr, "[%s] is not an int\n", left); + goto done; + } + + long r_int = strtol(right, &end, 10); + if (right[0] == '\0' || *end != '\0') { + fprintf(stderr, "[%s] is not an int\n", right); + goto done; + } + + result = l_int < r_int; + + done: + free(left); + free(right); + return strdup(result ? "t" : ""); +} + +char* GreaterThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + free(state->errmsg); + state->errmsg = strdup("greater_than_int expects 2 arguments"); + return NULL; + } + + Expr* temp[2]; + temp[0] = argv[1]; + temp[1] = argv[0]; + + return LessThanIntFn(name, state, 2, temp); +} + +char* Literal(const char* name, State* state, int argc, Expr* argv[]) { + return strdup(name); +} + +Expr* Build(Function fn, YYLTYPE loc, int count, ...) { + va_list v; + va_start(v, count); + Expr* e = malloc(sizeof(Expr)); + e->fn = fn; + e->name = "(operator)"; + e->argc = count; + e->argv = malloc(count * sizeof(Expr*)); + int i; + for (i = 0; i < count; ++i) { + e->argv[i] = va_arg(v, Expr*); + } + va_end(v); + e->start = loc.start; + e->end = loc.end; + return e; +} + +// ----------------------------------------------------------------- +// the function table +// ----------------------------------------------------------------- + +static int fn_entries = 0; +static int fn_size = 0; +NamedFunction* fn_table = NULL; + +void RegisterFunction(const char* name, Function fn) { + if (fn_entries >= fn_size) { + fn_size = fn_size*2 + 1; + fn_table = realloc(fn_table, fn_size * sizeof(NamedFunction)); + } + fn_table[fn_entries].name = name; + fn_table[fn_entries].fn = fn; + ++fn_entries; +} + +static int fn_entry_compare(const void* a, const void* b) { + const char* na = ((const NamedFunction*)a)->name; + const char* nb = ((const NamedFunction*)b)->name; + return strcmp(na, nb); +} + +void FinishRegistration() { + qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare); +} + +Function FindFunction(const char* name) { + NamedFunction key; + key.name = name; + NamedFunction* nf = bsearch(&key, fn_table, fn_entries, + sizeof(NamedFunction), fn_entry_compare); + if (nf == NULL) { + return NULL; + } + return nf->fn; +} + +void RegisterBuiltins() { + RegisterFunction("ifelse", IfElseFn); + RegisterFunction("abort", AbortFn); + RegisterFunction("assert", AssertFn); + RegisterFunction("concat", ConcatFn); + RegisterFunction("is_substring", SubstringFn); + RegisterFunction("stdout", StdoutFn); + RegisterFunction("sleep", SleepFn); + + RegisterFunction("less_than_int", LessThanIntFn); + RegisterFunction("greater_than_int", GreaterThanIntFn); +} + + +// ----------------------------------------------------------------- +// convenience methods for functions +// ----------------------------------------------------------------- + +// Evaluate the expressions in argv, giving 'count' char* (the ... is +// zero or more char** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadArgs(State* state, Expr* argv[], int count, ...) { + char** args = malloc(count * sizeof(char*)); + va_list v; + va_start(v, count); + int i; + for (i = 0; i < count; ++i) { + args[i] = Evaluate(state, argv[i]); + if (args[i] == NULL) { + va_end(v); + int j; + for (j = 0; j < i; ++j) { + free(args[j]); + } + return -1; + } + *(va_arg(v, char**)) = args[i]; + } + va_end(v); + return 0; +} + +// Evaluate the expressions in argv, returning an array of char* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// strings it contains. +char** ReadVarArgs(State* state, int argc, Expr* argv[]) { + char** args = (char**)malloc(argc * sizeof(char*)); + int i = 0; + for (i = 0; i < argc; ++i) { + args[i] = Evaluate(state, argv[i]); + if (args[i] == NULL) { + int j; + for (j = 0; j < i; ++j) { + free(args[j]); + } + free(args); + return NULL; + } + } + return args; +} + +// Use printf-style arguments to compose an error message to put into +// *state. Returns NULL. +char* ErrorAbort(State* state, char* format, ...) { + char* buffer = malloc(4096); + va_list v; + va_start(v, format); + vsnprintf(buffer, 4096, format, v); + va_end(v); + free(state->errmsg); + state->errmsg = buffer; + return NULL; +} diff --git a/edify/expr.h b/edify/expr.h new file mode 100644 index 0000000..d2e7392 --- /dev/null +++ b/edify/expr.h @@ -0,0 +1,126 @@ +/* + * 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 _EXPRESSION_H +#define _EXPRESSION_H + +#include "yydefs.h" + +#define MAX_STRING_LEN 1024 + +typedef struct Expr Expr; + +typedef struct { + // Optional pointer to app-specific data; the core of edify never + // uses this value. + void* cookie; + + // The source of the original script. Must be NULL-terminated, + // and in writable memory (Evaluate may make temporary changes to + // it but will restore it when done). + char* script; + + // The error message (if any) returned if the evaluation aborts. + // Should be NULL initially, will be either NULL or a malloc'd + // pointer after Evaluate() returns. + char* errmsg; +} State; + +typedef char* (*Function)(const char* name, State* state, + int argc, Expr* argv[]); + +struct Expr { + Function fn; + char* name; + int argc; + Expr** argv; + int start, end; +}; + +char* Evaluate(State* state, Expr* expr); + +// Glue to make an Expr out of a literal. +char* Literal(const char* name, State* state, int argc, Expr* argv[]); + +// Functions corresponding to various syntactic sugar operators. +// ("concat" is also available as a builtin function, to concatenate +// more than two strings.) +char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]); +char* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]); +char* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]); +char* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]); +char* SubstringFn(const char* name, State* state, int argc, Expr* argv[]); +char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]); +char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]); +char* SequenceFn(const char* name, State* state, int argc, Expr* argv[]); + +// Convenience function for building expressions with a fixed number +// of arguments. +Expr* Build(Function fn, YYLTYPE loc, int count, ...); + +// Global builtins, registered by RegisterBuiltins(). +char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]); +char* AssertFn(const char* name, State* state, int argc, Expr* argv[]); +char* AbortFn(const char* name, State* state, int argc, Expr* argv[]); + + +// For setting and getting the global error string (when returning +// NULL from a function). +void SetError(const char* message); // makes a copy +const char* GetError(); // retains ownership +void ClearError(); + + +typedef struct { + const char* name; + Function fn; +} NamedFunction; + +// Register a new function. The same Function may be registered under +// multiple names, but a given name should only be used once. +void RegisterFunction(const char* name, Function fn); + +// Register all the builtins. +void RegisterBuiltins(); + +// Call this after all calls to RegisterFunction() but before parsing +// any scripts to finish building the function table. +void FinishRegistration(); + +// Find the Function for a given name; return NULL if no such function +// exists. +Function FindFunction(const char* name); + + +// --- convenience functions for use in functions --- + +// Evaluate the expressions in argv, giving 'count' char* (the ... is +// zero or more char** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadArgs(State* state, Expr* argv[], int count, ...); + +// Evaluate the expressions in argv, returning an array of char* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// strings it contains. +char** ReadVarArgs(State* state, int argc, Expr* argv[]); + +// Use printf-style arguments to compose an error message to put into +// *state. Returns NULL. +char* ErrorAbort(State* state, char* format, ...); + + +#endif // _EXPRESSION_H diff --git a/edify/lexer.l b/edify/lexer.l new file mode 100644 index 0000000..2c4489c --- /dev/null +++ b/edify/lexer.l @@ -0,0 +1,110 @@ +%{ +/* + * 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 "expr.h" +#include "yydefs.h" +#include "parser.h" + +int gLine = 1; +int gColumn = 1; +int gPos = 0; + +// TODO: enforce MAX_STRING_LEN during lexing +char string_buffer[MAX_STRING_LEN]; +char* string_pos; + +#define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \ + gColumn+=yyleng; gPos+=yyleng;} while(0) + +%} + +%x STR + +%option noyywrap + +%% + + +\" { + BEGIN(STR); + string_pos = string_buffer; + yylloc.start = gPos; + ++gColumn; + ++gPos; +} + +{ + \" { + ++gColumn; + ++gPos; + BEGIN(INITIAL); + *string_pos = '\0'; + yylval.str = strdup(string_buffer); + yylloc.end = gPos; + return STRING; + } + + \\n { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\n'; } + \\t { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\t'; } + \\\" { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\"'; } + \\\\ { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\\'; } + + \\x[0-9a-fA-F]{2} { + gColumn += yyleng; + gPos += yyleng; + int val; + sscanf(yytext+2, "%x", &val); + *string_pos++ = val; + } + + \n { + ++gLine; + ++gPos; + gColumn = 1; + *string_pos++ = yytext[0]; + } + + . { + ++gColumn; + ++gPos; + *string_pos++ = yytext[0]; + } +} + +if ADVANCE; return IF; +then ADVANCE; return THEN; +else ADVANCE; return ELSE; +endif ADVANCE; return ENDIF; + +[a-zA-Z0-9_:/.]+ { + ADVANCE; + yylval.str = strdup(yytext); + return STRING; +} + +\&\& ADVANCE; return AND; +\|\| ADVANCE; return OR; +== ADVANCE; return EQ; +!= ADVANCE; return NE; + +[+(),!;] ADVANCE; return yytext[0]; + +[ \t]+ ADVANCE; + +(#.*)?\n gPos += yyleng; ++gLine; gColumn = 1; + +. return BAD; diff --git a/edify/main.c b/edify/main.c new file mode 100644 index 0000000..0e36108 --- /dev/null +++ b/edify/main.c @@ -0,0 +1,213 @@ +/* + * 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 +#include +#include + +#include "expr.h" +#include "parser.h" + +extern int yyparse(Expr** root, int* error_count); + +int expect(const char* expr_str, const char* expected, int* errors) { + Expr* e; + int error; + char* result; + + printf("."); + + yy_scan_string(expr_str); + int error_count = 0; + error = yyparse(&e, &error_count); + if (error > 0 || error_count > 0) { + fprintf(stderr, "error parsing \"%s\" (%d errors)\n", + expr_str, error_count); + ++*errors; + return 0; + } + + State state; + state.cookie = NULL; + state.script = expr_str; + state.errmsg = NULL; + + result = Evaluate(&state, e); + free(state.errmsg); + if (result == NULL && expected != NULL) { + fprintf(stderr, "error evaluating \"%s\"\n", expr_str); + ++*errors; + return 0; + } + + if (result == NULL && expected == NULL) { + return 1; + } + + if (strcmp(result, expected) != 0) { + fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n", + expr_str, expected, result); + ++*errors; + free(result); + return 0; + } + + free(result); + return 1; +} + +int test() { + int errors = 0; + + expect("a", "a", &errors); + expect("\"a\"", "a", &errors); + expect("\"\\x61\"", "a", &errors); + expect("# this is a comment\n" + " a\n" + " \n", + "a", &errors); + + + // sequence operator + expect("a; b; c", "c", &errors); + + // string concat operator + expect("a + b", "ab", &errors); + expect("a + \n \"b\"", "ab", &errors); + expect("a + b +\nc\n", "abc", &errors); + + // string concat function + expect("concat(a, b)", "ab", &errors); + expect("concat(a,\n \"b\")", "ab", &errors); + expect("concat(a + b,\nc,\"d\")", "abcd", &errors); + expect("\"concat\"(a + b,\nc,\"d\")", "abcd", &errors); + + // logical and + expect("a && b", "b", &errors); + expect("a && \"\"", "", &errors); + expect("\"\" && b", "", &errors); + expect("\"\" && \"\"", "", &errors); + expect("\"\" && abort()", "", &errors); // test short-circuiting + expect("t && abort()", NULL, &errors); + + // logical or + expect("a || b", "a", &errors); + expect("a || \"\"", "a", &errors); + expect("\"\" || b", "b", &errors); + expect("\"\" || \"\"", "", &errors); + expect("a || abort()", "a", &errors); // test short-circuiting + expect("\"\" || abort()", NULL, &errors); + + // logical not + expect("!a", "", &errors); + expect("! \"\"", "t", &errors); + expect("!!a", "t", &errors); + + // precedence + expect("\"\" == \"\" && b", "b", &errors); + expect("a + b == ab", "t", &errors); + expect("ab == a + b", "t", &errors); + expect("a + (b == ab)", "a", &errors); + expect("(ab == a) + b", "b", &errors); + + // substring function + expect("is_substring(cad, abracadabra)", "t", &errors); + expect("is_substring(abrac, abracadabra)", "t", &errors); + expect("is_substring(dabra, abracadabra)", "t", &errors); + expect("is_substring(cad, abracxadabra)", "", &errors); + expect("is_substring(abrac, axbracadabra)", "", &errors); + expect("is_substring(dabra, abracadabrxa)", "", &errors); + + // ifelse function + expect("ifelse(t, yes, no)", "yes", &errors); + expect("ifelse(!t, yes, no)", "no", &errors); + expect("ifelse(t, yes, abort())", "yes", &errors); + expect("ifelse(!t, abort(), no)", "no", &errors); + + // if "statements" + expect("if t then yes else no endif", "yes", &errors); + expect("if \"\" then yes else no endif", "no", &errors); + expect("if \"\" then yes endif", "", &errors); + expect("if \"\"; t then yes endif", "yes", &errors); + + // numeric comparisons + expect("less_than_int(3, 14)", "t", &errors); + expect("less_than_int(14, 3)", "", &errors); + expect("less_than_int(x, 3)", "", &errors); + expect("less_than_int(3, x)", "", &errors); + expect("greater_than_int(3, 14)", "", &errors); + expect("greater_than_int(14, 3)", "t", &errors); + expect("greater_than_int(x, 3)", "", &errors); + expect("greater_than_int(3, x)", "", &errors); + + printf("\n"); + + return errors; +} + +void ExprDump(int depth, Expr* n, char* script) { + printf("%*s", depth*2, ""); + char temp = script[n->end]; + script[n->end] = '\0'; + printf("%s %p (%d-%d) \"%s\"\n", + n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end, + script+n->start); + script[n->end] = temp; + int i; + for (i = 0; i < n->argc; ++i) { + ExprDump(depth+1, n->argv[i], script); + } +} + +int main(int argc, char** argv) { + RegisterBuiltins(); + FinishRegistration(); + + if (argc == 1) { + return test() != 0; + } + + FILE* f = fopen(argv[1], "r"); + char buffer[8192]; + int size = fread(buffer, 1, 8191, f); + fclose(f); + buffer[size] = '\0'; + + Expr* root; + int error_count = 0; + yy_scan_bytes(buffer, size); + int error = yyparse(&root, &error_count); + printf("parse returned %d; %d errors encountered\n", error, error_count); + if (error == 0 || error_count > 0) { + + ExprDump(0, root, buffer); + + State state; + state.cookie = NULL; + state.script = buffer; + state.errmsg = NULL; + + char* result = Evaluate(&state, root); + if (result == NULL) { + printf("result was NULL, message is: %s\n", + (state.errmsg == NULL ? "(NULL)" : state.errmsg)); + free(state.errmsg); + } else { + printf("result is [%s]\n", result); + } + } + return 0; +} diff --git a/edify/parser.y b/edify/parser.y new file mode 100644 index 0000000..3f9ade1 --- /dev/null +++ b/edify/parser.y @@ -0,0 +1,130 @@ +%{ +/* + * 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 +#include +#include + +#include "expr.h" +#include "yydefs.h" +#include "parser.h" + +extern int gLine; +extern int gColumn; + +void yyerror(Expr** root, int* error_count, const char* s); +int yyparse(Expr** root, int* error_count); + +%} + +%locations + +%union { + char* str; + Expr* expr; + struct { + int argc; + Expr** argv; + } args; +} + +%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF +%token STRING BAD +%type expr +%type arglist + +%parse-param {Expr** root} +%parse-param {int* error_count} +%error-verbose + +/* declarations in increasing order of precedence */ +%left ';' +%left ',' +%left OR +%left AND +%left EQ NE +%left '+' +%right '!' + +%% + +input: expr { *root = $1; } +; + +expr: STRING { + $$ = malloc(sizeof(Expr)); + $$->fn = Literal; + $$->name = $1; + $$->argc = 0; + $$->argv = NULL; + $$->start = @$.start; + $$->end = @$.end; +} +| '(' expr ')' { $$ = $2; $$->start=@$.start; $$->end=@$.end; } +| expr ';' { $$ = $1; $$->start=@1.start; $$->end=@1.end; } +| expr ';' expr { $$ = Build(SequenceFn, @$, 2, $1, $3); } +| error ';' expr { $$ = $3; $$->start=@$.start; $$->end=@$.end; } +| expr '+' expr { $$ = Build(ConcatFn, @$, 2, $1, $3); } +| expr EQ expr { $$ = Build(EqualityFn, @$, 2, $1, $3); } +| expr NE expr { $$ = Build(InequalityFn, @$, 2, $1, $3); } +| expr AND expr { $$ = Build(LogicalAndFn, @$, 2, $1, $3); } +| expr OR expr { $$ = Build(LogicalOrFn, @$, 2, $1, $3); } +| '!' expr { $$ = Build(LogicalNotFn, @$, 1, $2); } +| IF expr THEN expr ENDIF { $$ = Build(IfElseFn, @$, 2, $2, $4); } +| IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); } +| STRING '(' arglist ')' { + $$ = malloc(sizeof(Expr)); + $$->fn = FindFunction($1); + if ($$->fn == NULL) { + char buffer[256]; + snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1); + yyerror(root, error_count, buffer); + YYERROR; + } + $$->name = $1; + $$->argc = $3.argc; + $$->argv = $3.argv; + $$->start = @$.start; + $$->end = @$.end; +} +; + +arglist: /* empty */ { + $$.argc = 0; + $$.argv = NULL; +} +| expr { + $$.argc = 1; + $$.argv = malloc(sizeof(Expr*)); + $$.argv[0] = $1; +} +| arglist ',' expr { + $$.argc = $1.argc + 1; + $$.argv = realloc($$.argv, $$.argc * sizeof(Expr*)); + $$.argv[$$.argc-1] = $3; +} +; + +%% + +void yyerror(Expr** root, int* error_count, const char* s) { + if (strlen(s) == 0) { + s = "syntax error"; + } + printf("line %d col %d: %s\n", gLine, gColumn, s); + ++*error_count; +} diff --git a/edify/yydefs.h b/edify/yydefs.h new file mode 100644 index 0000000..6257862 --- /dev/null +++ b/edify/yydefs.h @@ -0,0 +1,36 @@ +/* + * 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 _YYDEFS_H_ +#define _YYDEFS_H_ + +#define YYLTYPE YYLTYPE +typedef struct { + int start, end; +} YYLTYPE; + +#define YYLLOC_DEFAULT(Current, Rhs, N) \ + do { \ + if (N) { \ + (Current).start = YYRHSLOC(Rhs, 1).start; \ + (Current).end = YYRHSLOC(Rhs, N).end; \ + } else { \ + (Current).start = YYRHSLOC(Rhs, 0).start; \ + (Current).end = YYRHSLOC(Rhs, 0).end; \ + } \ + } while (0) + +#endif diff --git a/extendedcommands.c b/extendedcommands.c new file mode 100644 index 0000000..f38ffd0 --- /dev/null +++ b/extendedcommands.c @@ -0,0 +1,492 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "bootloader.h" +#include "common.h" +#include "cutils/properties.h" +#include "firmware.h" +#include "install.h" +#include "minui/minui.h" +#include "minzip/DirUtil.h" +#include "roots.h" +#include "recovery_ui.h" + +#include "commands.h" +#include "amend/amend.h" + +int signature_check_enabled = 1; +int script_assert_enabled = 1; +static const char *SDCARD_PACKAGE_FILE = "SDCARD:update.zip"; + +void +toggle_signature_check() +{ + signature_check_enabled = !signature_check_enabled; + ui_print("Signature Check: %s\n", signature_check_enabled ? "Enabled" : "Disabled"); +} + +void toggle_script_asserts() +{ + script_assert_enabled = !script_assert_enabled; + ui_print("Script Asserts: %s\n", script_assert_enabled ? "Enabled" : "Disabled"); +} + +int install_zip(const char* packagefilepath) +{ + ui_print("\n-- Installing: %s\n", packagefilepath); + set_sdcard_update_bootloader_message(); + int status = install_package(packagefilepath); + ui_reset_progress(); + if (status != INSTALL_SUCCESS) { + ui_set_background(BACKGROUND_ICON_ERROR); + ui_print("Installation aborted.\n"); + return 1; + } + if (firmware_update_pending()) { + ui_print("\nReboot via menu to complete\ninstallation.\n"); + } + ui_set_background(BACKGROUND_ICON_NONE); + ui_print("\nInstall from sdcard complete.\n"); + return 0; +} + +char* INSTALL_MENU_ITEMS[] = { "apply sdcard:update.zip", + "choose zip from sdcard", + "toggle signature verification", + "toggle script asserts", + NULL }; +#define ITEM_APPLY_SDCARD 0 +#define ITEM_CHOOSE_ZIP 1 +#define ITEM_SIG_CHECK 2 +#define ITEM_ASSERTS 3 + +void show_install_update_menu() +{ + static char* headers[] = { "Apply update from .zip file on SD card", + "", + NULL + }; + for (;;) + { + int chosen_item = get_menu_selection(headers, INSTALL_MENU_ITEMS, 0); + switch (chosen_item) + { + case ITEM_ASSERTS: + toggle_script_asserts(); + break; + case ITEM_SIG_CHECK: + toggle_signature_check(); + break; + case ITEM_APPLY_SDCARD: + install_zip(SDCARD_PACKAGE_FILE); + break; + case ITEM_CHOOSE_ZIP: + show_choose_zip_menu(); + break; + default: + return; + } + + } +} + +char** gather_files(const char* directory, const char* fileExtensionOrDirectory, int* numFiles) +{ + char path[PATH_MAX] = ""; + DIR *dir; + struct dirent *de; + int total = 0; + int i; + char** files; + int pass; + *numFiles = 0; + int dirLen = strlen(directory); + + dir = opendir(directory); + if (dir == NULL) { + ui_print("Couldn't open directory.\n"); + return NULL; + } + + int extension_length; + if (fileExtensionOrDirectory != NULL) + extension_length = strlen(fileExtensionOrDirectory); + + int isCounting = 1; + i = 0; + for (pass = 0; pass < 2; pass++) { + while ((de=readdir(dir)) != NULL) { + // skip hidden files + if (de->d_name[0] == '.') + continue; + + // NULL means that we are gathering directories, so skip this + if (fileExtensionOrDirectory != NULL) + { + // make sure that we can have the desired extension (prevent seg fault) + if (strlen(de->d_name) < extension_length) + continue; + // compare the extension + if (strcmp(de->d_name + strlen(de->d_name) - extension_length, fileExtensionOrDirectory) != 0) + continue; + } + else + { + struct stat info; + char* fullFileName = (char*)malloc(strlen(de->d_name) + dirLen + 1); + strcpy(fullFileName, directory); + strcat(fullFileName, de->d_name); + stat(fullFileName, &info); + free(fullFileName); + // make sure it is a directory + if (!(S_ISDIR(info.st_mode))) + continue; + } + + if (pass == 0) + { + total++; + continue; + } + + files[i] = (char*) malloc(dirLen + strlen(de->d_name) + 2); + strcpy(files[i], directory); + strcat(files[i], de->d_name); + if (fileExtensionOrDirectory == NULL) + strcat(files[i], "/"); + i++; + } + if (pass == 1) + break; + if (total == 0) + break; + rewinddir(dir); + *numFiles = total; + files = (char**) malloc((total+1)*sizeof(char*)); + files[total]=NULL; + } + + if(closedir(dir) < 0) { + LOGE("Failed to close directory."); + } + + if (total==0) { + return NULL; + } + + return files; +} + +void free_string_array(char** array) +{ + char* cursor = array[0]; + int i = 0; + while (cursor != NULL) + { + free(cursor); + cursor = array[++i]; + } + free(array); +} + +// pass in NULL for fileExtensionOrDirectory and you will get a directory chooser +char* choose_file_menu(const char* directory, const char* fileExtensionOrDirectory, const char* headers[]) +{ + char path[PATH_MAX] = ""; + DIR *dir; + struct dirent *de; + int numFiles = 0; + int numDirs = 0; + int i; + + int dir_len = strlen(directory); + + char** files = gather_files(directory, fileExtensionOrDirectory, &numFiles); + char** dirs; + if (fileExtensionOrDirectory != NULL) + dirs = gather_files(directory, NULL, &numDirs); + int total = numDirs + numFiles; + if (total == 0) + { + ui_print("No files found.\n"); + return NULL; + } + char** list = (char**) malloc((total + 1) * sizeof(char*)); + list[total] = NULL; + + + for (i = 0 ; i < numDirs; i++) + { + list[i] = strdup(dirs[i] + dir_len); + } + + for (i = 0 ; i < numFiles; i++) + { + list[numDirs + i] = strdup(files[i] + dir_len); + } + + for (;;) + { + int chosen_item = get_menu_selection(headers, list, 0); + if (chosen_item == GO_BACK) + break; + if (chosen_item < numDirs) + { + char* subret = choose_file_menu(dirs[chosen_item], fileExtensionOrDirectory, headers); + if (subret != NULL) + return subret; + continue; + } + static char ret[PATH_MAX]; + strcpy(ret, files[chosen_item - numDirs]); + ui_print("File chosen: %s\n", ret); + return ret; + } + return NULL; +} + +void show_choose_zip_menu() +{ + if (ensure_root_path_mounted("SDCARD:") != 0) { + LOGE ("Can't mount /sdcard\n"); + return; + } + + static char* headers[] = { "Choose a zip to apply", + "", + NULL + }; + + char* file = choose_file_menu("/sdcard/", ".zip", headers); + if (file == NULL) + return; + char sdcard_package_file[1024]; + strcpy(sdcard_package_file, "SDCARD:"); + strcat(sdcard_package_file, file + strlen("/sdcard/")); + install_zip(sdcard_package_file); +} + +// This was pulled from bionic: The default system command always looks +// for shell in /system/bin/sh. This is bad. +#define _PATH_BSHELL "/sbin/sh" +#define system recovery_system +extern char **environ; +int +system(const char *command) +{ + pid_t pid; + sig_t intsave, quitsave; + sigset_t mask, omask; + int pstat; + char *argp[] = {"sh", "-c", NULL, NULL}; + + if (!command) /* just checking... */ + return(1); + + argp[2] = (char *)command; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &omask); + switch (pid = vfork()) { + case -1: /* error */ + sigprocmask(SIG_SETMASK, &omask, NULL); + return(-1); + case 0: /* child */ + sigprocmask(SIG_SETMASK, &omask, NULL); + execve(_PATH_BSHELL, argp, environ); + _exit(127); + } + + intsave = (sig_t) bsd_signal(SIGINT, SIG_IGN); + quitsave = (sig_t) bsd_signal(SIGQUIT, SIG_IGN); + pid = waitpid(pid, (int *)&pstat, 0); + sigprocmask(SIG_SETMASK, &omask, NULL); + (void)bsd_signal(SIGINT, intsave); + (void)bsd_signal(SIGQUIT, quitsave); + return (pid == -1 ? -1 : pstat); +} + +int do_nandroid_backup(char* backup_name) +{ + if (ensure_root_path_mounted("SDCARD:") != 0) { + LOGE ("Can't mount /sdcard\n"); + return 1; + } + + char cmd[PATH_MAX]; + if (NULL != backup_name) + sprintf(cmd, "/sbin/nandroid-mobile.sh backup /sdcard/clockworkmod/backup/ %s", backup_name); + else + sprintf(cmd, "/sbin/nandroid-mobile.sh backup /sdcard/clockworkmod/backup/"); + ui_print("Performing backup...\n"); + int ret = system(cmd); + if (ret != 0) + { + ui_print("Error while backing up! Error code: %d\n", ret); + return ret; + } + ui_print("Backup complete.\n"); + return ret; +} + +int do_nandroid_restore(char* backup_path) +{ + if (ensure_root_path_mounted("SDCARD:") != 0) { + LOGE ("Can't mount /sdcard\n"); + return 1; + } + + char* command[PATH_MAX]; + sprintf(command, "nandroid-mobile.sh restore %s", backup_path); + ui_print("Performing restore...\n"); + int ret = system(command); + if (ret != 0) + { + ui_print("Error while restoring!\n"); + return ret; + } + ui_print("Restore complete.\n"); + return ret; +} + +void show_nandroid_restore_menu() +{ + static char* headers[] = { "Choose an image to restore", + "", + NULL + }; + + char* file = choose_file_menu("/sdcard/clockworkmod/backup/", NULL, headers); + if (file == NULL) + return; + do_nandroid_restore(file); +} + +void do_mount_usb_storage() +{ + system("echo /dev/block/mmcblk0 > /sys/devices/platform/usb_mass_storage/lun0/file"); + static char* headers[] = { "USB Mass Storage device", + "Leaving this menu unmount", + "your SD card from your PC.", + "", + NULL + }; + + static char* list[] = { "Unmount", NULL }; + + for (;;) + { + int chosen_item = get_menu_selection(headers, list, 0); + if (chosen_item == GO_BACK || chosen_item == 0) + break; + } + + system("echo '' > /sys/devices/platform/usb_mass_storage/lun0/file"); + system("echo 0 > /sys/devices/platform/usb_mass_storage/lun0/enable"); +} + +#define EXTENDEDCOMMAND_SCRIPT "/cache/recovery/extendedcommand" + +int extendedcommand_file_exists() +{ + struct stat file_info; + return 0 == stat(EXTENDEDCOMMAND_SCRIPT, &file_info); +} + +int run_script(char* filename) +{ + struct stat file_info; + if (0 != stat(filename, &file_info)) { + printf("Error executing stat on file: %s\n", filename); + return 1; + } + + int script_len = file_info.st_size; + char* script_data = (char*)malloc(script_len); + FILE *file = fopen(filename, "rb"); + fread(script_data, script_len, 1, file); + fclose(file); + + /* Parse the script. Note that the script and parse tree are never freed. + */ + const AmCommandList *commands = parseAmendScript(script_data, script_len); + if (commands == NULL) { + printf("Syntax error in update script\n"); + return 1; + } else { + printf("Parsed %.*s\n", script_len, filename); + } + + /* Execute the script. + */ + int ret = execCommandList((ExecContext *)1, commands); + if (ret != 0) { + int num = ret; + char *line, *next = script_data; + while (next != NULL && ret-- > 0) { + line = next; + next = memchr(line, '\n', script_data + script_len - line); + if (next != NULL) *next++ = '\0'; + } + printf("Failure at line %d:\n%s\n", num, next ? line : "(not found)"); + return 1; + } + + return 0; +} + +int run_and_remove_extendedcommand() +{ + int i = 0; + for (i = 20; i > 0; i--) { + ui_print("Waiting for SD Card to mount (%ds)\n", i); + if (ensure_root_path_mounted("SDCARD:") == 0) { + ui_print("SD Card mounted...\n"); + break; + } + sleep(1); + } + if (i == 0) { + ui_print("Timed out waiting for SD card... continuing anyways."); + } + + + int ret = run_script(EXTENDEDCOMMAND_SCRIPT); + remove(EXTENDEDCOMMAND_SCRIPT); + return ret; +} + +int amend_main(int argc, char** argv) +{ + if (argc != 2) + { + printf("Usage: amend