From 6060e5c6df717a8070ceeb9c72c3c5dafa29cdd7 Mon Sep 17 00:00:00 2001 From: "Koushik K. Dutta" Date: Thu, 11 Feb 2010 22:27:06 -0800 Subject: [PATCH] update.zip somewhat working now... --- Android.mk | 3 + amend/register.c | 16 + commands.c | 1154 +++++++++++++++++++++++++++++++++++++++++ commands.h | 28 + default_recovery_ui.c | 2 + extendedcommands.c | 39 ++ extendedcommands.h | 5 + install.c | 45 +- legacy.c | 35 ++ recovery.c | 14 + recovery_ui.h | 2 + 11 files changed, 1323 insertions(+), 20 deletions(-) create mode 100644 commands.c create mode 100644 commands.h create mode 100644 extendedcommands.c create mode 100644 extendedcommands.h diff --git a/Android.mk b/Android.mk index 6a64d86..66c30cc 100644 --- a/Android.mk +++ b/Android.mk @@ -5,9 +5,12 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) commands_recovery_local_path := $(LOCAL_PATH) +# LOCAL_CPP_EXTENSION := .c LOCAL_SRC_FILES := \ + extendedcommands.c \ legacy.c \ + commands.c \ recovery.c \ bootloader.c \ firmware.c \ 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/commands.c b/commands.c new file mode 100644 index 0000000..e6fb9be --- /dev/null +++ b/commands.c @@ -0,0 +1,1154 @@ +/* + * 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. + */ + +#undef NDEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "amend/commands.h" +#include "commands.h" +#include "common.h" +#include "cutils/misc.h" +#include "cutils/properties.h" +#include "firmware.h" +#include "minzip/DirUtil.h" +#include "minzip/Zip.h" +#include "roots.h" + +#include "extendedcommands.h" + +static int gDidShowProgress = 0; + +#define UNUSED(p) ((void)(p)) + +#define CHECK_BOOL() \ + do { \ + assert(argv == NULL); \ + if (argv != NULL) return -1; \ + assert(argc == true || argc == false); \ + if (argc != true && argc != false) return -1; \ + } while (false) + +#define CHECK_WORDS() \ + do { \ + assert(argc >= 0); \ + if (argc < 0) return -1; \ + assert(argc == 0 || argv != NULL); \ + if (argc != 0 && argv == NULL) return -1; \ + if (permissions != NULL) { \ + int CW_I_; \ + for (CW_I_ = 0; CW_I_ < argc; CW_I_++) { \ + assert(argv[CW_I_] != NULL); \ + if (argv[CW_I_] == NULL) return -1; \ + } \ + } \ + } while (false) + +#define CHECK_FN() \ + do { \ + CHECK_WORDS(); \ + if (permissions != NULL) { \ + assert(result == NULL); \ + if (result != NULL) return -1; \ + } else { \ + assert(result != NULL); \ + if (result == NULL) return -1; \ + } \ + } while (false) + +#define NO_PERMS(perms) \ + do { \ + PermissionRequestList *NP_PRL_ = (perms); \ + if (NP_PRL_ != NULL) { \ + int NP_RET_ = addPermissionRequestToList(NP_PRL_, \ + "", false, PERM_NONE); \ + if (NP_RET_ < 0) { \ + /* Returns from the calling function. \ + */ \ + return NP_RET_; \ + } \ + } \ + } while (false) + +/* + * Command definitions + */ + +/* assert + */ +static int +cmd_assert(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + 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) + */ + if (argc) { + return 0; + } else { + return 1; + } +} + +/* format + */ +static int +cmd_format(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_WORDS(); + + if (argc != 1) { + LOGE("Command %s requires exactly one argument\n", name); + return 1; + } + const char *root = argv[0]; + ui_print("Formatting %s...\n", root); + + int ret = format_root_device(root); + if (ret != 0) { + LOGE("Can't format %s\n", root); + return 1; + } + + return 0; +} + +/* delete [ ...] + * delete_recursive [ ...] + * + * Like "rm -f", will try to delete every named file/dir, even if + * earlier ones fail. Recursive deletes that fail halfway through + * give up early. + */ +static int +cmd_delete(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + int nerr = 0; + bool recurse; + + if (argc < 1) { + LOGE("Command %s requires at least one argument\n", name); + return 1; + } + + recurse = (strcmp(name, "delete_recursive") == 0); + ui_print("Deleting files...\n"); +//xxx permissions + + int i; + for (i = 0; i < argc; i++) { + const char *root_path = argv[i]; + char pathbuf[PATH_MAX]; + const char *path; + + /* This guarantees that all paths use "SYSTEM:"-style roots; + * plain paths won't make it through translate_root_path(). + */ + path = translate_root_path(root_path, pathbuf, sizeof(pathbuf)); + if (path != NULL) { + int ret = ensure_root_path_mounted(root_path); + if (ret < 0) { + LOGW("Can't mount volume to delete \"%s\"\n", root_path); + nerr++; + continue; + } + if (recurse) { + ret = dirUnlinkHierarchy(path); + } else { + ret = unlink(path); + } + if (ret != 0 && errno != ENOENT) { + LOGW("Can't delete %s\n(%s)\n", path, strerror(errno)); + nerr++; + } + } else { + nerr++; + } + } +//TODO: add a way to fail if a delete didn't work + + return 0; +} + +typedef struct { + int num_done; + int num_total; +} ExtractContext; + +static void extract_count_cb(const char *fn, void *cookie) +{ + ++((ExtractContext*) cookie)->num_total; +} + +static void extract_cb(const char *fn, void *cookie) +{ + // minzip writes the filename to the log, so we don't need to + ExtractContext *ctx = (ExtractContext*) cookie; + ui_set_progress((float) ++ctx->num_done / ctx->num_total); +} + +/* copy_dir [] + * + * The contents of will become the contents of . + * The original contents of are preserved unless something + * in overwrote them. + * + * e.g., for "copy_dir PKG:system SYSTEM:", the file "PKG:system/a" + * would be copied to "SYSTEM:a". + * + * The specified timestamp (in decimal seconds since 1970) will be used, + * or a fixed default timestamp will be supplied otherwise. + */ +static int +cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_WORDS(); +//xxx permissions + + // To create a consistent system image, never use the clock for timestamps. + struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default + if (argc == 3) { + char *end; + time_t value = strtoul(argv[2], &end, 0); + if (value == 0 || end[0] != '\0') { + LOGE("Command %s: invalid timestamp \"%s\"\n", name, argv[2]); + return 1; + } else if (value < timestamp.modtime) { + LOGE("Command %s: timestamp \"%s\" too early\n", name, argv[2]); + return 1; + } + timestamp.modtime = timestamp.actime = value; + } else if (argc != 2) { + LOGE("Command %s requires exactly two arguments\n", name); + return 1; + } + + // Use 40% of the progress bar (80% post-verification) by default + ui_print("Copying files...\n"); + if (!gDidShowProgress) ui_show_progress(DEFAULT_FILES_PROGRESS_FRACTION, 0); + + /* Mount the destination volume if it isn't already. + */ + const char *dst_root_path = argv[1]; + int ret = ensure_root_path_mounted(dst_root_path); + if (ret < 0) { + LOGE("Can't mount %s\n", dst_root_path); + return 1; + } + + /* Get the real target path. + */ + char dstpathbuf[PATH_MAX]; + const char *dst_path; + dst_path = translate_root_path(dst_root_path, + dstpathbuf, sizeof(dstpathbuf)); + if (dst_path == NULL) { + LOGE("Command %s: bad destination path \"%s\"\n", name, dst_root_path); + return 1; + } + + /* Try to copy the directory. The source may be inside a package. + */ + const char *src_root_path = argv[0]; + char srcpathbuf[PATH_MAX]; + const char *src_path; + if (is_package_root_path(src_root_path)) { + const ZipArchive *package; + src_path = translate_package_root_path(src_root_path, + srcpathbuf, sizeof(srcpathbuf), &package); + if (src_path == NULL) { + LOGE("Command %s: bad source path \"%s\"\n", name, src_root_path); + return 1; + } + + /* Extract the files. Set MZ_EXTRACT_FILES_ONLY, because only files + * are validated by the signature. Do a dry run first to count how + * many there are (and find some errors early). + */ + ExtractContext ctx; + ctx.num_done = 0; + ctx.num_total = 0; + + if (!mzExtractRecursive(package, src_path, dst_path, + MZ_EXTRACT_FILES_ONLY | MZ_EXTRACT_DRY_RUN, + ×tamp, extract_count_cb, (void *) &ctx) || + !mzExtractRecursive(package, src_path, dst_path, + MZ_EXTRACT_FILES_ONLY, + ×tamp, extract_cb, (void *) &ctx)) { + LOGW("Command %s: couldn't extract \"%s\" to \"%s\"\n", + name, src_root_path, dst_root_path); + return 1; + } + } else { + LOGE("Command %s: non-package source path \"%s\" not yet supported\n", + name, src_root_path); +//xxx mount the src volume +//xxx + return 255; + } + + return 0; +} + +/* run_program [ ...] + * + * Run an external program included in the update package. + */ +static int +cmd_run_program(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + + if (argc < 1) { + LOGE("Command %s requires at least one argument\n", name); + return 1; + } + + // Copy the program file to temporary storage. + if (!is_package_root_path(argv[0])) { + LOGE("Command %s: non-package program file \"%s\" not supported\n", + name, argv[0]); + return 1; + } + + char path[PATH_MAX]; + const ZipArchive *package; + if (!translate_package_root_path(argv[0], path, sizeof(path), &package)) { + LOGE("Command %s: bad source path \"%s\"\n", name, argv[0]); + return 1; + } + + const ZipEntry *entry = mzFindZipEntry(package, path); + if (entry == NULL) { + LOGE("Can't find %s\n", path); + return 1; + } + + static const char *binary = "/tmp/run_program_binary"; + unlink(binary); // just to be sure + int fd = creat(binary, 0755); + if (fd < 0) { + LOGE("Can't make %s\n", binary); + return 1; + } + bool ok = mzExtractZipEntryToFile(package, entry, fd); + close(fd); + + if (!ok) { + LOGE("Can't copy %s\n", path); + return 1; + } + + // Create a copy of argv to NULL-terminate it, as execv requires + char **args = (char **) malloc(sizeof(char*) * (argc + 1)); + memcpy(args, argv, sizeof(char*) * argc); + args[argc] = NULL; + + pid_t pid = fork(); + if (pid == 0) { + execv(binary, args); + fprintf(stderr, "E:Can't run %s\n(%s)\n", binary, strerror(errno)); + _exit(-1); + } + + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + return 0; + } else { + LOGE("Error in %s\n(Status %d)\n", path, status); + return 1; + } +} + +/* set_perm [... ] + * set_perm_recursive [... ] + * + * Like "chmod", "chown" and "chgrp" all in one, set ownership and permissions + * of single files or entire directory trees. Any error causes failure. + * User, group, and modes must all be integer values (hex or octal OK). + */ +static int +cmd_set_perm(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + bool recurse = !strcmp(name, "set_perm_recursive"); + + int min_args = 4 + (recurse ? 1 : 0); + if (argc < min_args) { + LOGE("Command %s requires at least %d args\n", name, min_args); + return 1; + } + + // All the arguments except the path(s) are numeric. + int i, n[min_args - 1]; + for (i = 0; i < min_args - 1; ++i) { + char *end; + n[i] = strtoul(argv[i], &end, 0); + if (end[0] != '\0' || argv[i][0] == '\0') { + LOGE("Command %s: invalid argument \"%s\"\n", name, argv[i]); + return 1; + } + } + + for (i = min_args - 1; i < min_args; ++i) { + char path[PATH_MAX]; + if (translate_root_path(argv[i], path, sizeof(path)) == NULL) { + LOGE("Command %s: bad path \"%s\"\n", name, argv[i]); + return 1; + } + + if (ensure_root_path_mounted(argv[i])) { + LOGE("Can't mount %s\n", argv[i]); + return 1; + } + + if (recurse + ? dirSetHierarchyPermissions(path, n[0], n[1], n[2], n[3]) + : (chown(path, n[0], n[1]) || chmod(path, n[2]))) { + LOGE("Can't chown/mod %s\n(%s)\n", path, strerror(errno)); + return 1; + } + } + + return 0; +} + +/* show_progress + * + * Use of the on-screen progress meter for the next operation, + * automatically advancing the meter over seconds (or more rapidly + * if the actual rate of progress can be determined). + */ +static int +cmd_show_progress(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + + if (argc != 2) { + LOGE("Command %s requires exactly two arguments\n", name); + return 1; + } + + char *end; + double fraction = strtod(argv[0], &end); + if (end[0] != '\0' || argv[0][0] == '\0' || fraction < 0 || fraction > 1) { + LOGE("Command %s: invalid fraction \"%s\"\n", name, argv[0]); + return 1; + } + + int duration = strtoul(argv[1], &end, 0); + if (end[0] != '\0' || argv[0][0] == '\0') { + LOGE("Command %s: invalid duration \"%s\"\n", name, argv[1]); + return 1; + } + + // Half of the progress bar is taken by verification, + // so everything that happens during installation is scaled. + ui_show_progress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), duration); + gDidShowProgress = 1; + return 0; +} + +/* symlink + * + * Create a symlink, like "ln -s". The link path must not exist already. + * Note that is in root:path format, but is + * for the target filesystem (and may be relative). + */ +static int +cmd_symlink(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); + + if (argc != 2) { + LOGE("Command %s requires exactly two arguments\n", name); + return 1; + } + + char path[PATH_MAX]; + if (translate_root_path(argv[1], path, sizeof(path)) == NULL) { + LOGE("Command %s: bad path \"%s\"\n", name, argv[1]); + return 1; + } + + if (ensure_root_path_mounted(argv[1])) { + LOGE("Can't mount %s\n", argv[1]); + return 1; + } + + if (symlink(argv[0], path)) { + LOGE("Can't symlink %s\n", path); + return 1; + } + + return 0; +} + +struct FirmwareContext { + size_t total_bytes, done_bytes; + char *data; +}; + +static bool firmware_fn(const unsigned char *data, int data_len, void *cookie) +{ + struct FirmwareContext *context = (struct FirmwareContext*) cookie; + if (context->done_bytes + data_len > context->total_bytes) { + LOGE("Data overrun in firmware\n"); + return false; // Should not happen, but let's be safe. + } + + memcpy(context->data + context->done_bytes, data, data_len); + context->done_bytes += data_len; + ui_set_progress(context->done_bytes * 1.0 / context->total_bytes); + return true; +} + +/* write_radio_image + * write_hboot_image + * Doesn't actually take effect until the rest of installation finishes. + */ +static int +cmd_write_firmware_image(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; + } + + const char *type; + if (!strcmp(name, "write_radio_image")) { + type = "radio"; + } else if (!strcmp(name, "write_hboot_image")) { + type = "hboot"; + } else { + LOGE("Unknown firmware update command %s\n", name); + return 1; + } + + if (!is_package_root_path(argv[0])) { + LOGE("Command %s: non-package image file \"%s\" not supported\n", + name, argv[0]); + return 1; + } + + ui_print("Extracting %s image...\n", type); + char path[PATH_MAX]; + const ZipArchive *package; + if (!translate_package_root_path(argv[0], path, sizeof(path), &package)) { + LOGE("Command %s: bad source path \"%s\"\n", name, argv[0]); + return 1; + } + + const ZipEntry *entry = mzFindZipEntry(package, path); + if (entry == NULL) { + LOGE("Can't find %s\n", path); + return 1; + } + + // Load the update image into RAM. + struct FirmwareContext context; + context.total_bytes = mzGetZipEntryUncompLen(entry); + context.done_bytes = 0; + context.data = malloc(context.total_bytes); + if (context.data == NULL) { + LOGE("Can't allocate %d bytes for %s\n", context.total_bytes, argv[0]); + return 1; + } + + if (!mzProcessZipEntryContents(package, entry, firmware_fn, &context) || + context.done_bytes != context.total_bytes) { + LOGE("Can't read %s\n", argv[0]); + free(context.data); + return 1; + } + + if (remember_firmware_update(type, context.data, context.total_bytes)) { + LOGE("Can't store %s image\n", type); + free(context.data); + return 1; + } + + return 0; +} + +static bool write_raw_image_process_fn( + const unsigned char *data, + int data_len, void *ctx) +{ + int r = mtd_write_data((MtdWriteContext*)ctx, (const char *)data, data_len); + if (r == data_len) return true; + LOGE("%s\n", strerror(errno)); + return false; +} + +/* write_raw_image + */ +static int +cmd_write_raw_image(const char *name, void *cookie, + int argc, const char *argv[], PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_WORDS(); +//xxx permissions + + if (argc != 2) { + LOGE("Command %s requires exactly two arguments\n", name); + return 1; + } + + // Use 10% of the progress bar (20% post-verification) by default + const char *src_root_path = argv[0]; + const char *dst_root_path = argv[1]; + ui_print("Writing %s...\n", dst_root_path); + if (!gDidShowProgress) ui_show_progress(DEFAULT_IMAGE_PROGRESS_FRACTION, 0); + + /* Find the source image, which is probably in a package. + */ + if (!is_package_root_path(src_root_path)) { + LOGE("Command %s: non-package source path \"%s\" not yet supported\n", + name, src_root_path); + return 255; + } + + /* Get the package. + */ + char srcpathbuf[PATH_MAX]; + const char *src_path; + const ZipArchive *package; + src_path = translate_package_root_path(src_root_path, + srcpathbuf, sizeof(srcpathbuf), &package); + if (src_path == NULL) { + LOGE("Command %s: bad source path \"%s\"\n", name, src_root_path); + return 1; + } + + /* Get the entry. + */ + const ZipEntry *entry = mzFindZipEntry(package, src_path); + if (entry == NULL) { + LOGE("Missing file %s\n", src_path); + return 1; + } + + /* Unmount the destination root if it isn't already. + */ + int ret = ensure_root_path_unmounted(dst_root_path); + if (ret < 0) { + LOGE("Can't unmount %s\n", dst_root_path); + return 1; + } + + /* Open the partition for writing. + */ + const MtdPartition *partition = get_root_mtd_partition(dst_root_path); + if (partition == NULL) { + LOGE("Can't find %s\n", dst_root_path); + return 1; + } + MtdWriteContext *context = mtd_write_partition(partition); + if (context == NULL) { + LOGE("Can't open %s\n", dst_root_path); + return 1; + } + + /* Extract and write the image. + */ + bool ok = mzProcessZipEntryContents(package, entry, + write_raw_image_process_fn, context); + if (!ok) { + LOGE("Error writing %s\n", dst_root_path); + mtd_write_close(context); + return 1; + } + + if (mtd_erase_blocks(context, -1) == (off_t) -1) { + LOGE("Error finishing %s\n", dst_root_path); + mtd_write_close(context); + return -1; + } + + if (mtd_write_close(context)) { + LOGE("Error closing %s\n", dst_root_path); + return -1; + } + return 0; +} + +/* mark dirty|clean + */ +static int +cmd_mark(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_WORDS(); +//xxx when marking, save the top-level hash at the mark point +// so we can retry on failure. Otherwise the hashes won't match, +// or someone could intentionally dirty the FS to force a downgrade +//xxx + return -1; +} + +/* done + */ +static int +cmd_done(const char *name, void *cookie, int argc, const char *argv[], + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_WORDS(); +//xxx + return -1; +} + + +/* + * Function definitions + */ + +/* compatible_with() + * + * Returns "true" if this version of the script parser and command + * set supports the named version. + */ +static int +fn_compatible_with(const char *name, void *cookie, int argc, const char *argv[], + char **result, size_t *resultLen, + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_FN(); + NO_PERMS(permissions); + + if (argc != 1) { + fprintf(stderr, "%s: wrong number of arguments (%d)\n", + name, argc); + return 1; + } + + if (!strcmp(argv[0], "0.1") || !strcmp(argv[0], "0.2")) { + *result = strdup("true"); + } else { + *result = strdup(""); + } + if (resultLen != NULL) { + *resultLen = strlen(*result); + } + return 0; +} + +/* update_forced() + * + * Returns "true" if some system setting has determined that + * the update should happen no matter what. + */ +static int +fn_update_forced(const char *name, void *cookie, int argc, const char *argv[], + char **result, size_t *resultLen, + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_FN(); + NO_PERMS(permissions); + + if (argc != 0) { + fprintf(stderr, "%s: wrong number of arguments (%d)\n", + name, argc); + return 1; + } + + //xxx check some global or property + bool force = true; + if (force) { + *result = strdup("true"); + } else { + *result = strdup(""); + } + if (resultLen != NULL) { + *resultLen = strlen(*result); + } + + return 0; +} + +/* get_mark() + * + * Returns the current mark associated with the provided resource. + */ +static int +fn_get_mark(const char *name, void *cookie, int argc, const char *argv[], + char **result, size_t *resultLen, + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_FN(); + NO_PERMS(permissions); + + if (argc != 1) { + fprintf(stderr, "%s: wrong number of arguments (%d)\n", + name, argc); + return 1; + } + + //xxx look up the value + *result = strdup(""); + if (resultLen != NULL) { + *resultLen = strlen(*result); + } + + return 0; +} + +/* hash_dir() + */ +static int +fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[], + char **result, size_t *resultLen, + PermissionRequestList *permissions) +{ + int ret = -1; + + UNUSED(name); + UNUSED(cookie); + CHECK_FN(); + + const char *dir; + if (argc != 1) { + fprintf(stderr, "%s: wrong number of arguments (%d)\n", + name, argc); + return 1; + } else { + dir = argv[0]; + } + + if (permissions != NULL) { + if (dir == NULL) { + /* The argument is the result of another function. + * Assume the worst case, where the function returns + * the root. + */ + dir = "/"; + } + ret = addPermissionRequestToList(permissions, dir, true, PERM_READ); + } else { +//xxx build and return the string + *result = strdup("hashvalue"); + if (resultLen != NULL) { + *resultLen = strlen(*result); + } + ret = 0; + } + + return ret; +} + +/* matches(, [, ...]) + * If matches (strcmp) any of ..., returns , + * otherwise returns "". + * + * E.g., assert matches(hash_dir("/path"), "hash1", "hash2") + */ +static int +fn_matches(const char *name, void *cookie, int argc, const char *argv[], + char **result, size_t *resultLen, + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_FN(); + NO_PERMS(permissions); + + if (argc < 2) { + fprintf(stderr, "%s: not enough arguments (%d < 2)\n", + name, argc); + return 1; + } + + int i; + for (i = 1; i < argc; i++) { + if (strcmp(argv[0], argv[i]) == 0) { + *result = strdup(argv[0]); + if (resultLen != NULL) { + *resultLen = strlen(*result); + } + return 0; + } + } + + *result = strdup(""); + if (resultLen != NULL) { + *resultLen = 1; + } + return 0; +} + +/* concat(, [, ...]) + * Returns the concatenation of all strings. + */ +static int +fn_concat(const char *name, void *cookie, int argc, const char *argv[], + char **result, size_t *resultLen, + PermissionRequestList *permissions) +{ + UNUSED(name); + UNUSED(cookie); + CHECK_FN(); + NO_PERMS(permissions); + + size_t totalLen = 0; + int i; + for (i = 0; i < argc; i++) { + totalLen += strlen(argv[i]); + } + + char *s = (char *)malloc(totalLen + 1); + if (s == NULL) { + return -1; + } + s[totalLen] = '\0'; + for (i = 0; i < argc; i++) { + //TODO: keep track of the end to avoid walking the string each time + strcat(s, argv[i]); + } + *result = s; + if (resultLen != NULL) { + *resultLen = strlen(s); + } + + return 0; +} + +/* getprop() + * Returns the named Android system property value, or "" if not set. + */ +static int +fn_getprop(const char *name, void *cookie, int argc, const char *argv[], + char **result, size_t *resultLen, + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_FN(); + NO_PERMS(permissions); + + if (argc != 1) { + LOGE("Command %s requires exactly one argument\n", name); + return 1; + } + + char value[PROPERTY_VALUE_MAX]; + property_get(argv[0], value, ""); + + *result = strdup(value); + if (resultLen != NULL) { + *resultLen = strlen(*result); + } + + return 0; +} + +/* file_contains(, ) + * Returns "true" if the file exists and contains the specified substring. + */ +static int +fn_file_contains(const char *name, void *cookie, int argc, const char *argv[], + char **result, size_t *resultLen, + PermissionRequestList *permissions) +{ + UNUSED(cookie); + CHECK_FN(); + NO_PERMS(permissions); + + if (argc != 2) { + LOGE("Command %s requires exactly two arguments\n", name); + return 1; + } + + char pathbuf[PATH_MAX]; + const char *root_path = argv[0]; + const char *path = translate_root_path(root_path, pathbuf, sizeof(pathbuf)); + if (path == NULL) { + LOGE("Command %s: bad path \"%s\"\n", name, root_path); + return 1; + } + + if (ensure_root_path_mounted(root_path)) { + LOGE("Can't mount %s\n", root_path); + return 1; + } + + const char *needle = argv[1]; + char *haystack = (char*) load_file(path, NULL); + if (haystack == NULL) { + LOGI("%s: Can't read \"%s\" (%s)\n", name, path, strerror(errno)); + *result = ""; /* File not found is not an error. */ + } else if (strstr(haystack, needle) == NULL) { + LOGI("%s: Can't find \"%s\" in \"%s\"\n", name, needle, path); + *result = strdup(""); + free(haystack); + } else { + *result = strdup("true"); + free(haystack); + } + + if (resultLen != NULL) { + *resultLen = strlen(*result); + } + return 0; +} + +int +register_update_commands(RecoveryCommandContext *ctx) +{ + int ret; + + ret = commandInit(); + if (ret < 0) return ret; + + /* + * Commands + */ + + ret = registerCommand("assert", CMD_ARGS_BOOLEAN, cmd_assert, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("delete", CMD_ARGS_WORDS, cmd_delete, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("delete_recursive", CMD_ARGS_WORDS, cmd_delete, + (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("copy_dir", CMD_ARGS_WORDS, + cmd_copy_dir, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("run_program", CMD_ARGS_WORDS, + cmd_run_program, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("set_perm", CMD_ARGS_WORDS, + cmd_set_perm, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("set_perm_recursive", CMD_ARGS_WORDS, + cmd_set_perm, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("show_progress", CMD_ARGS_WORDS, + cmd_show_progress, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("symlink", CMD_ARGS_WORDS, cmd_symlink, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("format", CMD_ARGS_WORDS, cmd_format, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("write_radio_image", CMD_ARGS_WORDS, + cmd_write_firmware_image, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("write_hboot_image", CMD_ARGS_WORDS, + cmd_write_firmware_image, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("write_raw_image", CMD_ARGS_WORDS, + cmd_write_raw_image, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("mark", CMD_ARGS_WORDS, cmd_mark, (void *)ctx); + if (ret < 0) return ret; + + ret = registerCommand("done", CMD_ARGS_WORDS, cmd_done, (void *)ctx); + if (ret < 0) return ret; + + /* + * Functions + */ + + ret = registerFunction("compatible_with", fn_compatible_with, (void *)ctx); + if (ret < 0) return ret; + + ret = registerFunction("update_forced", fn_update_forced, (void *)ctx); + if (ret < 0) return ret; + + ret = registerFunction("get_mark", fn_get_mark, (void *)ctx); + if (ret < 0) return ret; + + ret = registerFunction("hash_dir", fn_hash_dir, (void *)ctx); + if (ret < 0) return ret; + + ret = registerFunction("matches", fn_matches, (void *)ctx); + if (ret < 0) return ret; + + ret = registerFunction("concat", fn_concat, (void *)ctx); + if (ret < 0) return ret; + + ret = registerFunction("getprop", fn_getprop, (void *)ctx); + if (ret < 0) return ret; + + ret = registerFunction("file_contains", fn_file_contains, (void *)ctx); + if (ret < 0) return ret; + + return 0; +} diff --git a/commands.h b/commands.h new file mode 100644 index 0000000..e9acea2 --- /dev/null +++ b/commands.h @@ -0,0 +1,28 @@ +/* + * 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 RECOVERY_COMMANDS_H_ +#define RECOVERY_COMMANDS_H_ + +#include "minzip/Zip.h" + +typedef struct { + ZipArchive *package; +} RecoveryCommandContext; + +int register_update_commands(RecoveryCommandContext *ctx); + +#endif // RECOVERY_COMMANDS_H_ diff --git a/default_recovery_ui.c b/default_recovery_ui.c index cb76b9a..025ee98 100644 --- a/default_recovery_ui.c +++ b/default_recovery_ui.c @@ -27,6 +27,8 @@ char* MENU_ITEMS[] = { "reboot system now", "apply sdcard:update.zip", "wipe data/factory reset", "wipe cache partition", + "toggle signature verification", + "toggle script asserts", NULL }; int device_toggle_display(volatile char* key_pressed, int key_code) { diff --git a/extendedcommands.c b/extendedcommands.c new file mode 100644 index 0000000..b2aacc9 --- /dev/null +++ b/extendedcommands.c @@ -0,0 +1,39 @@ +#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" + +int signature_check_enabled = 1; +int script_assert_enabled = 1; + +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", signature_check_enabled ? "Enabled" : "Disabled"); +} \ No newline at end of file diff --git a/extendedcommands.h b/extendedcommands.h new file mode 100644 index 0000000..5b82960 --- /dev/null +++ b/extendedcommands.h @@ -0,0 +1,5 @@ +extern int signature_check_enabled; +extern int script_assert_enabled; + +void +toggle_signature_check(); \ No newline at end of file diff --git a/install.c b/install.c index d0d9038..5a829d1 100644 --- a/install.c +++ b/install.c @@ -35,6 +35,8 @@ #include "firmware.h" #include "legacy.h" +#include "extendedcommands.h" + #define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" #define PUBLIC_KEYS_FILE "/res/keys" @@ -347,27 +349,30 @@ install_package(const char *root_path) ui_print("Opening update package...\n"); LOGI("Update file path: %s\n", path); - int numKeys; - RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); - if (loadedKeys == NULL) { - LOGE("Failed to load keys\n"); - return INSTALL_CORRUPT; - } - LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); - - // Give verification half the progress bar... - ui_print("Verifying update package...\n"); - ui_show_progress( - VERIFICATION_PROGRESS_FRACTION, - VERIFICATION_PROGRESS_TIME); - int err; - err = verify_file(path, loadedKeys, numKeys); - free(loadedKeys); - LOGI("verify_file returned %d\n", err); - if (err != VERIFY_SUCCESS) { - LOGE("signature verification failed\n"); - return INSTALL_CORRUPT; + + if (signature_check_enabled) { + int numKeys; + RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); + if (loadedKeys == NULL) { + LOGE("Failed to load keys\n"); + return INSTALL_CORRUPT; + } + LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); + + // Give verification half the progress bar... + ui_print("Verifying update package...\n"); + ui_show_progress( + VERIFICATION_PROGRESS_FRACTION, + VERIFICATION_PROGRESS_TIME); + + err = verify_file(path, loadedKeys, numKeys); + free(loadedKeys); + LOGI("verify_file returned %d\n", err); + if (err != VERIFY_SUCCESS) { + LOGE("signature verification failed\n"); + return INSTALL_CORRUPT; + } } /* Try to open the package. diff --git a/legacy.c b/legacy.c index b50ba3e..dab06ad 100644 --- a/legacy.c +++ b/legacy.c @@ -46,9 +46,44 @@ #include "roots.h" #include "verifier.h" +static int read_data(ZipArchive *zip, const ZipEntry *entry, + char** ppData, int* pLength) { + int len = (int)mzGetZipEntryUncompLen(entry); + if (len <= 0) { + LOGE("Bad data length %d\n", len); + return -1; + } + char *data = malloc(len + 1); + if (data == NULL) { + LOGE("Can't allocate %d bytes for data\n", len + 1); + return -2; + } + bool ok = mzReadZipEntry(zip, entry, data, len); + if (!ok) { + LOGE("Error while reading data\n"); + free(data); + return -3; + } + data[len] = '\0'; // not necessary, but just to be safe + *ppData = data; + if (pLength) { + *pLength = len; + } + return 0; +} + int handle_update_script(ZipArchive *zip, const ZipEntry *update_script_entry) { + // This is bizarre. The build fails with "undefined reference" + // unless the following two functions are referenced from somewhere to + // force them to be linked. This seems to be a problem with yacc/lex. + if (zip == 1) + { + fwrite(NULL, 0, 0, NULL); + fileno(NULL); + } + /* Read the entire script into a buffer. */ int script_len; diff --git a/recovery.c b/recovery.c index 5b3f6e5..b857610 100644 --- a/recovery.c +++ b/recovery.c @@ -38,6 +38,9 @@ #include "roots.h" #include "recovery_ui.h" +#include "extendedcommands.h" +#include "commands.h" + static const struct option OPTIONS[] = { { "send_intent", required_argument, NULL, 's' }, { "update_package", required_argument, NULL, 'u' }, @@ -430,6 +433,12 @@ prompt_and_wait() } } break; + case ITEM_SIG_CHECK: + toggle_signature_check(); + break; + case ITEM_ASSERTS: + toggle_script_asserts(); + break; } } } @@ -482,6 +491,11 @@ main(int argc, char **argv) fprintf(stderr, "\n"); int status = INSTALL_SUCCESS; + + RecoveryCommandContext ctx = { NULL }; + if (register_update_commands(&ctx)) { + LOGE("Can't install update commands\n"); + } if (update_package != NULL) { status = install_package(update_package); diff --git a/recovery_ui.h b/recovery_ui.h index 8818ef3..1aabbdf 100644 --- a/recovery_ui.h +++ b/recovery_ui.h @@ -66,6 +66,8 @@ int device_wipe_data(); #define ITEM_APPLY_SDCARD 1 #define ITEM_WIPE_DATA 2 #define ITEM_WIPE_CACHE 3 +#define ITEM_SIG_CHECK 4 +#define ITEM_ASSERTS 5 // Header text to display above the main menu. extern char* MENU_HEADERS[];