From 10e418d3c89ec404fbf959c1ef77a720a42a66ed Mon Sep 17 00:00:00 2001 From: Doug Zongker Date: Fri, 28 Oct 2011 10:33:05 -0700 Subject: turn recovery into a C++ binary Change-Id: I68a67a4c8edec9a74463b3d4766005ce27b51316 --- Android.mk | 14 +- bootloader.c | 202 ------------- bootloader.cpp | 202 +++++++++++++ bootloader.h | 8 + common.h | 65 +---- install.c | 310 -------------------- install.cpp | 314 ++++++++++++++++++++ install.h | 8 + minui/minui.h | 8 + minzip/DirUtil.h | 8 + minzip/Zip.h | 8 + mtdutils/mounts.h | 8 + mtdutils/mtdutils.h | 8 + recovery.c | 825 --------------------------------------------------- recovery.cpp | 826 ++++++++++++++++++++++++++++++++++++++++++++++++++++ recovery_ui.h | 8 + roots.c | 282 ------------------ roots.cpp | 282 ++++++++++++++++++ roots.h | 8 + ui.c | 664 ----------------------------------------- ui.cpp | 666 ++++++++++++++++++++++++++++++++++++++++++ ui.h | 75 +++++ verifier.c | 184 ------------ verifier.cpp | 185 ++++++++++++ verifier_test.c | 91 ------ verifier_test.cpp | 91 ++++++ verifier_test.sh | 8 +- 27 files changed, 2732 insertions(+), 2626 deletions(-) delete mode 100644 bootloader.c create mode 100644 bootloader.cpp delete mode 100644 install.c create mode 100644 install.cpp delete mode 100644 recovery.c create mode 100644 recovery.cpp delete mode 100644 roots.c create mode 100644 roots.cpp delete mode 100644 ui.c create mode 100644 ui.cpp create mode 100644 ui.h delete mode 100644 verifier.c create mode 100644 verifier.cpp delete mode 100644 verifier_test.c create mode 100644 verifier_test.cpp diff --git a/Android.mk b/Android.mk index 282862fc9..2c81be676 100644 --- a/Android.mk +++ b/Android.mk @@ -4,12 +4,12 @@ include $(CLEAR_VARS) commands_recovery_local_path := $(LOCAL_PATH) LOCAL_SRC_FILES := \ - recovery.c \ - bootloader.c \ - install.c \ - roots.c \ - ui.c \ - verifier.c + recovery.cpp \ + bootloader.cpp \ + install.cpp \ + roots.cpp \ + ui.cpp \ + verifier.cpp LOCAL_MODULE := recovery @@ -50,7 +50,7 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) -LOCAL_SRC_FILES := verifier_test.c verifier.c +LOCAL_SRC_FILES := verifier_test.cpp verifier.cpp LOCAL_MODULE := verifier_test diff --git a/bootloader.c b/bootloader.c deleted file mode 100644 index baaddc55f..000000000 --- a/bootloader.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "bootloader.h" -#include "common.h" -#include "mtdutils/mtdutils.h" -#include "roots.h" - -#include -#include -#include -#include -#include - -static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v); -static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v); -static int get_bootloader_message_block(struct bootloader_message *out, const Volume* v); -static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v); - -int get_bootloader_message(struct bootloader_message *out) { - Volume* v = volume_for_path("/misc"); - if (v == NULL) { - LOGE("Cannot load volume /misc!\n"); - return -1; - } - if (strcmp(v->fs_type, "mtd") == 0) { - return get_bootloader_message_mtd(out, v); - } else if (strcmp(v->fs_type, "emmc") == 0) { - return get_bootloader_message_block(out, v); - } - LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); - return -1; -} - -int set_bootloader_message(const struct bootloader_message *in) { - Volume* v = volume_for_path("/misc"); - if (v == NULL) { - LOGE("Cannot load volume /misc!\n"); - return -1; - } - if (strcmp(v->fs_type, "mtd") == 0) { - return set_bootloader_message_mtd(in, v); - } else if (strcmp(v->fs_type, "emmc") == 0) { - return set_bootloader_message_block(in, v); - } - LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); - return -1; -} - -// ------------------------------ -// for misc partitions on MTD -// ------------------------------ - -static const int MISC_PAGES = 3; // number of pages to save -static const int MISC_COMMAND_PAGE = 1; // bootloader command is this page - -static int get_bootloader_message_mtd(struct bootloader_message *out, - const Volume* v) { - size_t write_size; - mtd_scan_partitions(); - const MtdPartition *part = mtd_find_partition_by_name(v->device); - if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) { - LOGE("Can't find %s\n", v->device); - return -1; - } - - MtdReadContext *read = mtd_read_partition(part); - if (read == NULL) { - LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - - const ssize_t size = write_size * MISC_PAGES; - char data[size]; - ssize_t r = mtd_read_data(read, data, size); - if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno)); - mtd_read_close(read); - if (r != size) return -1; - - memcpy(out, &data[write_size * MISC_COMMAND_PAGE], sizeof(*out)); - return 0; -} -static int set_bootloader_message_mtd(const struct bootloader_message *in, - const Volume* v) { - size_t write_size; - mtd_scan_partitions(); - const MtdPartition *part = mtd_find_partition_by_name(v->device); - if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) { - LOGE("Can't find %s\n", v->device); - return -1; - } - - MtdReadContext *read = mtd_read_partition(part); - if (read == NULL) { - LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - - ssize_t size = write_size * MISC_PAGES; - char data[size]; - ssize_t r = mtd_read_data(read, data, size); - if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno)); - mtd_read_close(read); - if (r != size) return -1; - - memcpy(&data[write_size * MISC_COMMAND_PAGE], in, sizeof(*in)); - - MtdWriteContext *write = mtd_write_partition(part); - if (write == NULL) { - LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - if (mtd_write_data(write, data, size) != size) { - LOGE("Can't write %s\n(%s)\n", v->device, strerror(errno)); - mtd_write_close(write); - return -1; - } - if (mtd_write_close(write)) { - LOGE("Can't finish %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - - LOGI("Set boot command \"%s\"\n", in->command[0] != 255 ? in->command : ""); - return 0; -} - - -// ------------------------------------ -// for misc partitions on block devices -// ------------------------------------ - -static void wait_for_device(const char* fn) { - int tries = 0; - int ret; - struct stat buf; - do { - ++tries; - ret = stat(fn, &buf); - if (ret) { - printf("stat %s try %d: %s\n", fn, tries, strerror(errno)); - sleep(1); - } - } while (ret && tries < 10); - if (ret) { - printf("failed to stat %s\n", fn); - } -} - -static int get_bootloader_message_block(struct bootloader_message *out, - const Volume* v) { - wait_for_device(v->device); - FILE* f = fopen(v->device, "rb"); - if (f == NULL) { - LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - struct bootloader_message temp; - int count = fread(&temp, sizeof(temp), 1, f); - if (count != 1) { - LOGE("Failed reading %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - if (fclose(f) != 0) { - LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - memcpy(out, &temp, sizeof(temp)); - return 0; -} - -static int set_bootloader_message_block(const struct bootloader_message *in, - const Volume* v) { - wait_for_device(v->device); - FILE* f = fopen(v->device, "wb"); - if (f == NULL) { - LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - int count = fwrite(in, sizeof(*in), 1, f); - if (count != 1) { - LOGE("Failed writing %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - if (fclose(f) != 0) { - LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno)); - return -1; - } - return 0; -} diff --git a/bootloader.cpp b/bootloader.cpp new file mode 100644 index 000000000..baaddc55f --- /dev/null +++ b/bootloader.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootloader.h" +#include "common.h" +#include "mtdutils/mtdutils.h" +#include "roots.h" + +#include +#include +#include +#include +#include + +static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v); +static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v); +static int get_bootloader_message_block(struct bootloader_message *out, const Volume* v); +static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v); + +int get_bootloader_message(struct bootloader_message *out) { + Volume* v = volume_for_path("/misc"); + if (v == NULL) { + LOGE("Cannot load volume /misc!\n"); + return -1; + } + if (strcmp(v->fs_type, "mtd") == 0) { + return get_bootloader_message_mtd(out, v); + } else if (strcmp(v->fs_type, "emmc") == 0) { + return get_bootloader_message_block(out, v); + } + LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); + return -1; +} + +int set_bootloader_message(const struct bootloader_message *in) { + Volume* v = volume_for_path("/misc"); + if (v == NULL) { + LOGE("Cannot load volume /misc!\n"); + return -1; + } + if (strcmp(v->fs_type, "mtd") == 0) { + return set_bootloader_message_mtd(in, v); + } else if (strcmp(v->fs_type, "emmc") == 0) { + return set_bootloader_message_block(in, v); + } + LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); + return -1; +} + +// ------------------------------ +// for misc partitions on MTD +// ------------------------------ + +static const int MISC_PAGES = 3; // number of pages to save +static const int MISC_COMMAND_PAGE = 1; // bootloader command is this page + +static int get_bootloader_message_mtd(struct bootloader_message *out, + const Volume* v) { + size_t write_size; + mtd_scan_partitions(); + const MtdPartition *part = mtd_find_partition_by_name(v->device); + if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) { + LOGE("Can't find %s\n", v->device); + return -1; + } + + MtdReadContext *read = mtd_read_partition(part); + if (read == NULL) { + LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + + const ssize_t size = write_size * MISC_PAGES; + char data[size]; + ssize_t r = mtd_read_data(read, data, size); + if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno)); + mtd_read_close(read); + if (r != size) return -1; + + memcpy(out, &data[write_size * MISC_COMMAND_PAGE], sizeof(*out)); + return 0; +} +static int set_bootloader_message_mtd(const struct bootloader_message *in, + const Volume* v) { + size_t write_size; + mtd_scan_partitions(); + const MtdPartition *part = mtd_find_partition_by_name(v->device); + if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) { + LOGE("Can't find %s\n", v->device); + return -1; + } + + MtdReadContext *read = mtd_read_partition(part); + if (read == NULL) { + LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + + ssize_t size = write_size * MISC_PAGES; + char data[size]; + ssize_t r = mtd_read_data(read, data, size); + if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno)); + mtd_read_close(read); + if (r != size) return -1; + + memcpy(&data[write_size * MISC_COMMAND_PAGE], in, sizeof(*in)); + + MtdWriteContext *write = mtd_write_partition(part); + if (write == NULL) { + LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + if (mtd_write_data(write, data, size) != size) { + LOGE("Can't write %s\n(%s)\n", v->device, strerror(errno)); + mtd_write_close(write); + return -1; + } + if (mtd_write_close(write)) { + LOGE("Can't finish %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + + LOGI("Set boot command \"%s\"\n", in->command[0] != 255 ? in->command : ""); + return 0; +} + + +// ------------------------------------ +// for misc partitions on block devices +// ------------------------------------ + +static void wait_for_device(const char* fn) { + int tries = 0; + int ret; + struct stat buf; + do { + ++tries; + ret = stat(fn, &buf); + if (ret) { + printf("stat %s try %d: %s\n", fn, tries, strerror(errno)); + sleep(1); + } + } while (ret && tries < 10); + if (ret) { + printf("failed to stat %s\n", fn); + } +} + +static int get_bootloader_message_block(struct bootloader_message *out, + const Volume* v) { + wait_for_device(v->device); + FILE* f = fopen(v->device, "rb"); + if (f == NULL) { + LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + struct bootloader_message temp; + int count = fread(&temp, sizeof(temp), 1, f); + if (count != 1) { + LOGE("Failed reading %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + if (fclose(f) != 0) { + LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + memcpy(out, &temp, sizeof(temp)); + return 0; +} + +static int set_bootloader_message_block(const struct bootloader_message *in, + const Volume* v) { + wait_for_device(v->device); + FILE* f = fopen(v->device, "wb"); + if (f == NULL) { + LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + int count = fwrite(in, sizeof(*in), 1, f); + if (count != 1) { + LOGE("Failed writing %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + if (fclose(f) != 0) { + LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno)); + return -1; + } + return 0; +} diff --git a/bootloader.h b/bootloader.h index 2e749aa12..712aa1a2d 100644 --- a/bootloader.h +++ b/bootloader.h @@ -17,6 +17,10 @@ #ifndef _RECOVERY_BOOTLOADER_H #define _RECOVERY_BOOTLOADER_H +#ifdef __cplusplus +extern "C" { +#endif + /* Bootloader Message * * This structure describes the content of a block in flash @@ -47,4 +51,8 @@ struct bootloader_message { int get_bootloader_message(struct bootloader_message *out); int set_bootloader_message(const struct bootloader_message *in); +#ifdef __cplusplus +} +#endif + #endif diff --git a/common.h b/common.h index ef2fe9f62..88807b880 100644 --- a/common.h +++ b/common.h @@ -19,61 +19,12 @@ #include -// Initialize the graphics system. -void ui_init(); - -// Use KEY_* codes from or KEY_DREAM_* from "minui/minui.h". -int ui_wait_key(); // waits for a key/button press, returns the code -int ui_key_pressed(int key); // returns >0 if the code is currently pressed -int ui_text_visible(); // returns >0 if text log is currently visible -int ui_text_ever_visible(); // returns >0 if text log was ever visible -void ui_show_text(int visible); -void ui_clear_key_queue(); - -// Write a message to the on-screen log shown with Alt-L (also to stderr). -// The screen is small, and users may need to report these messages to support, -// so keep the output short and not too cryptic. -void ui_print(const char *fmt, ...) __attribute__((format(printf, 1, 2))); - -// Display some header text followed by a menu of items, which appears -// at the top of the screen (in place of any scrolling ui_print() -// output, if necessary). -void ui_start_menu(char** headers, char** items, int initial_selection); -// Set the menu highlight to the given index, and return it (capped to -// the range [0..numitems). -int ui_menu_select(int sel); -// End menu mode, resetting the text overlay so that ui_print() -// statements will be displayed. -void ui_end_menu(); - -// Set the icon (normally the only thing visible besides the progress bar). -enum { - BACKGROUND_ICON_NONE, - BACKGROUND_ICON_INSTALLING, - BACKGROUND_ICON_ERROR, - NUM_BACKGROUND_ICONS -}; -void ui_set_background(int icon); - -// Show a progress bar and define the scope of the next operation: -// portion - fraction of the progress bar the next operation will use -// seconds - expected time interval (progress bar moves at this minimum rate) -void ui_show_progress(float portion, int seconds); -void ui_set_progress(float fraction); // 0.0 - 1.0 within the defined scope - -// Default allocation of progress bar segments to operations -static const int VERIFICATION_PROGRESS_TIME = 60; -static const float VERIFICATION_PROGRESS_FRACTION = 0.25; -static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; -static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; - -// Show a rotating "barberpole" for ongoing operations. Updates automatically. -void ui_show_indeterminate_progress(); - -// Hide and reset the progress bar. -void ui_reset_progress(); - -#define LOGE(...) ui_print("E:" __VA_ARGS__) +#ifdef __cplusplus +extern "C" { +#endif + +// TODO: restore ui_print for LOGE +#define LOGE(...) fprintf(stdout, "E:" __VA_ARGS__) #define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__) #define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__) @@ -129,4 +80,8 @@ typedef struct { // fopen a file, mounting volumes and making parent dirs as necessary. FILE* fopen_path(const char *path, const char *mode); +#ifdef __cplusplus +} +#endif + #endif // RECOVERY_COMMON_H diff --git a/install.c b/install.c deleted file mode 100644 index 9d7595e1a..000000000 --- a/install.c +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "common.h" -#include "install.h" -#include "mincrypt/rsa.h" -#include "minui/minui.h" -#include "minzip/SysUtil.h" -#include "minzip/Zip.h" -#include "mtdutils/mounts.h" -#include "mtdutils/mtdutils.h" -#include "roots.h" -#include "verifier.h" - -#define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" -#define PUBLIC_KEYS_FILE "/res/keys" - -// If the package contains an update binary, extract it and run it. -static int -try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { - const ZipEntry* binary_entry = - mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); - if (binary_entry == NULL) { - mzCloseZipArchive(zip); - return INSTALL_CORRUPT; - } - - char* binary = "/tmp/update_binary"; - unlink(binary); - int fd = creat(binary, 0755); - if (fd < 0) { - mzCloseZipArchive(zip); - LOGE("Can't make %s\n", binary); - return INSTALL_ERROR; - } - bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); - close(fd); - mzCloseZipArchive(zip); - - if (!ok) { - LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME); - return INSTALL_ERROR; - } - - int pipefd[2]; - pipe(pipefd); - - // When executing the update binary contained in the package, the - // arguments passed are: - // - // - the version number for this interface - // - // - an fd to which the program can write in order to update the - // progress bar. The program can write single-line commands: - // - // progress - // fill up the next part of of the progress bar - // over seconds. If is zero, use - // set_progress commands to manually control the - // progress of this segment of the bar - // - // set_progress - // should be between 0.0 and 1.0; sets the - // progress bar within the segment defined by the most - // recent progress command. - // - // firmware <"hboot"|"radio"> - // arrange to install the contents of in the - // given partition on reboot. - // - // (API v2: may start with "PACKAGE:" to - // indicate taking a file from the OTA package.) - // - // (API v3: this command no longer exists.) - // - // ui_print - // display on the screen. - // - // - the name of the package zip file. - // - - char** args = malloc(sizeof(char*) * 5); - args[0] = binary; - args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk - args[2] = malloc(10); - sprintf(args[2], "%d", pipefd[1]); - args[3] = (char*)path; - args[4] = NULL; - - pid_t pid = fork(); - if (pid == 0) { - close(pipefd[0]); - execv(binary, args); - fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); - _exit(-1); - } - close(pipefd[1]); - - *wipe_cache = 0; - - char buffer[1024]; - FILE* from_child = fdopen(pipefd[0], "r"); - while (fgets(buffer, sizeof(buffer), from_child) != NULL) { - char* command = strtok(buffer, " \n"); - if (command == NULL) { - continue; - } else if (strcmp(command, "progress") == 0) { - char* fraction_s = strtok(NULL, " \n"); - char* seconds_s = strtok(NULL, " \n"); - - float fraction = strtof(fraction_s, NULL); - int seconds = strtol(seconds_s, NULL, 10); - - ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), - seconds); - } else if (strcmp(command, "set_progress") == 0) { - char* fraction_s = strtok(NULL, " \n"); - float fraction = strtof(fraction_s, NULL); - ui_set_progress(fraction); - } else if (strcmp(command, "ui_print") == 0) { - char* str = strtok(NULL, "\n"); - if (str) { - ui_print("%s", str); - } else { - ui_print("\n"); - } - } else if (strcmp(command, "wipe_cache") == 0) { - *wipe_cache = 1; - } else { - LOGE("unknown command [%s]\n", command); - } - } - fclose(from_child); - - int status; - waitpid(pid, &status, 0); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); - return INSTALL_ERROR; - } - - return INSTALL_SUCCESS; -} - -// Reads a file containing one or more public keys as produced by -// DumpPublicKey: this is an RSAPublicKey struct as it would appear -// as a C source literal, eg: -// -// "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" -// -// (Note that the braces and commas in this example are actual -// characters the parser expects to find in the file; the ellipses -// indicate more numbers omitted from this example.) -// -// The file may contain multiple keys in this format, separated by -// commas. The last key must not be followed by a comma. -// -// Returns NULL if the file failed to parse, or if it contain zero keys. -static RSAPublicKey* -load_keys(const char* filename, int* numKeys) { - RSAPublicKey* out = NULL; - *numKeys = 0; - - FILE* f = fopen(filename, "r"); - if (f == NULL) { - LOGE("opening %s: %s\n", filename, strerror(errno)); - goto exit; - } - - int i; - bool done = false; - while (!done) { - ++*numKeys; - out = realloc(out, *numKeys * sizeof(RSAPublicKey)); - RSAPublicKey* key = out + (*numKeys - 1); - if (fscanf(f, " { %i , 0x%x , { %u", - &(key->len), &(key->n0inv), &(key->n[0])) != 3) { - goto exit; - } - if (key->len != RSANUMWORDS) { - LOGE("key length (%d) does not match expected size\n", key->len); - goto exit; - } - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; - } - if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; - } - fscanf(f, " } } "); - - // if the line ends in a comma, this file has more keys. - switch (fgetc(f)) { - case ',': - // more keys to come. - break; - - case EOF: - done = true; - break; - - default: - LOGE("unexpected character between keys\n"); - goto exit; - } - } - - fclose(f); - return out; - -exit: - if (f) fclose(f); - free(out); - *numKeys = 0; - return NULL; -} - -static int -really_install_package(const char *path, int* wipe_cache) -{ - ui_set_background(BACKGROUND_ICON_INSTALLING); - ui_print("Finding update package...\n"); - ui_show_indeterminate_progress(); - LOGI("Update location: %s\n", path); - - if (ensure_path_mounted(path) != 0) { - LOGE("Can't mount %s\n", path); - return INSTALL_CORRUPT; - } - - ui_print("Opening update package...\n"); - - int numKeys; - RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); - if (loadedKeys == NULL) { - LOGE("Failed to load keys\n"); - return INSTALL_CORRUPT; - } - LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); - - // Give verification half the progress bar... - ui_print("Verifying update package...\n"); - ui_show_progress( - VERIFICATION_PROGRESS_FRACTION, - VERIFICATION_PROGRESS_TIME); - - int err; - err = verify_file(path, loadedKeys, numKeys); - free(loadedKeys); - LOGI("verify_file returned %d\n", err); - if (err != VERIFY_SUCCESS) { - LOGE("signature verification failed\n"); - return INSTALL_CORRUPT; - } - - /* Try to open the package. - */ - ZipArchive zip; - err = mzOpenZipArchive(path, &zip); - if (err != 0) { - LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad"); - return INSTALL_CORRUPT; - } - - /* Verify and install the contents of the package. - */ - ui_print("Installing update...\n"); - return try_update_binary(path, &zip, wipe_cache); -} - -int -install_package(const char* path, int* wipe_cache, const char* install_file) -{ - FILE* install_log = fopen_path(install_file, "w"); - if (install_log) { - fputs(path, install_log); - fputc('\n', install_log); - } else { - LOGE("failed to open last_install: %s\n", strerror(errno)); - } - int result = really_install_package(path, wipe_cache); - if (install_log) { - fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log); - fputc('\n', install_log); - fclose(install_log); - } - return result; -} diff --git a/install.cpp b/install.cpp new file mode 100644 index 000000000..482e0d755 --- /dev/null +++ b/install.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "install.h" +#include "mincrypt/rsa.h" +#include "minui/minui.h" +#include "minzip/SysUtil.h" +#include "minzip/Zip.h" +#include "mtdutils/mounts.h" +#include "mtdutils/mtdutils.h" +#include "roots.h" +#include "verifier.h" +#include "ui.h" + +#define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" +#define PUBLIC_KEYS_FILE "/res/keys" + +// If the package contains an update binary, extract it and run it. +static int +try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { + const ZipEntry* binary_entry = + mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); + if (binary_entry == NULL) { + mzCloseZipArchive(zip); + return INSTALL_CORRUPT; + } + + const char* binary = "/tmp/update_binary"; + unlink(binary); + int fd = creat(binary, 0755); + if (fd < 0) { + mzCloseZipArchive(zip); + LOGE("Can't make %s\n", binary); + return INSTALL_ERROR; + } + bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); + close(fd); + mzCloseZipArchive(zip); + + if (!ok) { + LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME); + return INSTALL_ERROR; + } + + int pipefd[2]; + pipe(pipefd); + + // When executing the update binary contained in the package, the + // arguments passed are: + // + // - the version number for this interface + // + // - an fd to which the program can write in order to update the + // progress bar. The program can write single-line commands: + // + // progress + // fill up the next part of of the progress bar + // over seconds. If is zero, use + // set_progress commands to manually control the + // progress of this segment of the bar + // + // set_progress + // should be between 0.0 and 1.0; sets the + // progress bar within the segment defined by the most + // recent progress command. + // + // firmware <"hboot"|"radio"> + // arrange to install the contents of in the + // given partition on reboot. + // + // (API v2: may start with "PACKAGE:" to + // indicate taking a file from the OTA package.) + // + // (API v3: this command no longer exists.) + // + // ui_print + // display on the screen. + // + // - the name of the package zip file. + // + + const char** args = (const char**)malloc(sizeof(char*) * 5); + args[0] = binary; + args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk + char* temp = (char*)malloc(10); + sprintf(temp, "%d", pipefd[1]); + args[2] = temp; + args[3] = (char*)path; + args[4] = NULL; + + pid_t pid = fork(); + if (pid == 0) { + close(pipefd[0]); + execv(binary, (char* const*)args); + fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); + _exit(-1); + } + close(pipefd[1]); + + *wipe_cache = 0; + + char buffer[1024]; + FILE* from_child = fdopen(pipefd[0], "r"); + while (fgets(buffer, sizeof(buffer), from_child) != NULL) { + char* command = strtok(buffer, " \n"); + if (command == NULL) { + continue; + } else if (strcmp(command, "progress") == 0) { + char* fraction_s = strtok(NULL, " \n"); + char* seconds_s = strtok(NULL, " \n"); + + float fraction = strtof(fraction_s, NULL); + int seconds = strtol(seconds_s, NULL, 10); + + ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), + seconds); + } else if (strcmp(command, "set_progress") == 0) { + char* fraction_s = strtok(NULL, " \n"); + float fraction = strtof(fraction_s, NULL); + ui_set_progress(fraction); + } else if (strcmp(command, "ui_print") == 0) { + char* str = strtok(NULL, "\n"); + if (str) { + ui_print("%s", str); + } else { + ui_print("\n"); + } + } else if (strcmp(command, "wipe_cache") == 0) { + *wipe_cache = 1; + } else { + LOGE("unknown command [%s]\n", command); + } + } + fclose(from_child); + + int status; + waitpid(pid, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); + return INSTALL_ERROR; + } + + return INSTALL_SUCCESS; +} + +// Reads a file containing one or more public keys as produced by +// DumpPublicKey: this is an RSAPublicKey struct as it would appear +// as a C source literal, eg: +// +// "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" +// +// (Note that the braces and commas in this example are actual +// characters the parser expects to find in the file; the ellipses +// indicate more numbers omitted from this example.) +// +// The file may contain multiple keys in this format, separated by +// commas. The last key must not be followed by a comma. +// +// Returns NULL if the file failed to parse, or if it contain zero keys. +static RSAPublicKey* +load_keys(const char* filename, int* numKeys) { + RSAPublicKey* out = NULL; + *numKeys = 0; + + FILE* f = fopen(filename, "r"); + if (f == NULL) { + LOGE("opening %s: %s\n", filename, strerror(errno)); + goto exit; + } + + { + int i; + bool done = false; + while (!done) { + ++*numKeys; + out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey)); + RSAPublicKey* key = out + (*numKeys - 1); + if (fscanf(f, " { %i , 0x%x , { %u", + &(key->len), &(key->n0inv), &(key->n[0])) != 3) { + goto exit; + } + if (key->len != RSANUMWORDS) { + LOGE("key length (%d) does not match expected size\n", key->len); + goto exit; + } + for (i = 1; i < key->len; ++i) { + if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; + } + if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; + for (i = 1; i < key->len; ++i) { + if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; + } + fscanf(f, " } } "); + + // if the line ends in a comma, this file has more keys. + switch (fgetc(f)) { + case ',': + // more keys to come. + break; + + case EOF: + done = true; + break; + + default: + LOGE("unexpected character between keys\n"); + goto exit; + } + } + } + + fclose(f); + return out; + +exit: + if (f) fclose(f); + free(out); + *numKeys = 0; + return NULL; +} + +static int +really_install_package(const char *path, int* wipe_cache) +{ + ui_set_background(BACKGROUND_ICON_INSTALLING); + ui_print("Finding update package...\n"); + ui_show_indeterminate_progress(); + LOGI("Update location: %s\n", path); + + if (ensure_path_mounted(path) != 0) { + LOGE("Can't mount %s\n", path); + return INSTALL_CORRUPT; + } + + ui_print("Opening update package...\n"); + + int numKeys; + RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); + if (loadedKeys == NULL) { + LOGE("Failed to load keys\n"); + return INSTALL_CORRUPT; + } + LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); + + // Give verification half the progress bar... + ui_print("Verifying update package...\n"); + ui_show_progress( + VERIFICATION_PROGRESS_FRACTION, + VERIFICATION_PROGRESS_TIME); + + int err; + err = verify_file(path, loadedKeys, numKeys); + free(loadedKeys); + LOGI("verify_file returned %d\n", err); + if (err != VERIFY_SUCCESS) { + LOGE("signature verification failed\n"); + return INSTALL_CORRUPT; + } + + /* Try to open the package. + */ + ZipArchive zip; + err = mzOpenZipArchive(path, &zip); + if (err != 0) { + LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad"); + return INSTALL_CORRUPT; + } + + /* Verify and install the contents of the package. + */ + ui_print("Installing update...\n"); + return try_update_binary(path, &zip, wipe_cache); +} + +int +install_package(const char* path, int* wipe_cache, const char* install_file) +{ + FILE* install_log = fopen_path(install_file, "w"); + if (install_log) { + fputs(path, install_log); + fputc('\n', install_log); + } else { + LOGE("failed to open last_install: %s\n", strerror(errno)); + } + int result = really_install_package(path, wipe_cache); + if (install_log) { + fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log); + fputc('\n', install_log); + fclose(install_log); + } + return result; +} diff --git a/install.h b/install.h index 5ebe16047..1943f02d3 100644 --- a/install.h +++ b/install.h @@ -19,6 +19,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT }; // Install the package specified by root_path. If INSTALL_SUCCESS is // returned and *wipe_cache is true on exit, caller should wipe the @@ -26,4 +30,8 @@ enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT }; int install_package(const char *root_path, int* wipe_cache, const char* install_file); +#ifdef __cplusplus +} +#endif + #endif // RECOVERY_INSTALL_H_ diff --git a/minui/minui.h b/minui/minui.h index 2e2f1f477..74da4e9c0 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -19,6 +19,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef void* gr_surface; typedef unsigned short gr_pixel; @@ -69,4 +73,8 @@ void ev_dispatch(void); int res_create_surface(const char* name, gr_surface* pSurface); void res_free_surface(gr_surface surface); +#ifdef __cplusplus +} +#endif + #endif diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h index 5d881f562..0d5ea7ccb 100644 --- a/minzip/DirUtil.h +++ b/minzip/DirUtil.h @@ -20,6 +20,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* Like "mkdir -p", try to guarantee that all directories * specified in path are present, creating as many directories * as necessary. The specified mode is passed to all mkdir @@ -48,4 +52,8 @@ int dirUnlinkHierarchy(const char *path); int dirSetHierarchyPermissions(const char *path, int uid, int gid, int dirMode, int fileMode); +#ifdef __cplusplus +} +#endif + #endif // MINZIP_DIRUTIL_H_ diff --git a/minzip/Zip.h b/minzip/Zip.h index 9f99fba5b..739dbf5f2 100644 --- a/minzip/Zip.h +++ b/minzip/Zip.h @@ -14,6 +14,10 @@ #include "Hash.h" #include "SysUtil.h" +#ifdef __cplusplus +extern "C" { +#endif + /* * One entry in the Zip archive. Treat this as opaque -- use accessors below. * @@ -210,4 +214,8 @@ bool mzExtractRecursive(const ZipArchive *pArchive, int flags, const struct utimbuf *timestamp, void (*callback)(const char *fn, void*), void *cookie); +#ifdef __cplusplus +} +#endif + #endif /*_MINZIP_ZIP*/ diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h index 30b2927c2..d721355b8 100644 --- a/mtdutils/mounts.h +++ b/mtdutils/mounts.h @@ -17,6 +17,10 @@ #ifndef MTDUTILS_MOUNTS_H_ #define MTDUTILS_MOUNTS_H_ +#ifdef __cplusplus +extern "C" { +#endif + typedef struct MountedVolume MountedVolume; int scan_mounted_volumes(void); @@ -30,4 +34,8 @@ int unmount_mounted_volume(const MountedVolume *volume); int remount_read_only(const MountedVolume* volume); +#ifdef __cplusplus +} +#endif + #endif // MTDUTILS_MOUNTS_H_ diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h index 45d3ebc91..2708c4318 100644 --- a/mtdutils/mtdutils.h +++ b/mtdutils/mtdutils.h @@ -19,6 +19,10 @@ #include // for size_t, etc. +#ifdef __cplusplus +extern "C" { +#endif + typedef struct MtdPartition MtdPartition; int mtd_scan_partitions(void); @@ -53,4 +57,8 @@ off_t mtd_erase_blocks(MtdWriteContext *, int blocks); /* 0 ok, -1 for all */ off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos); int mtd_write_close(MtdWriteContext *); +#ifdef __cplusplus +} +#endif + #endif // MTDUTILS_H_ diff --git a/recovery.c b/recovery.c deleted file mode 100644 index 06d649809..000000000 --- a/recovery.c +++ /dev/null @@ -1,825 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "bootloader.h" -#include "common.h" -#include "cutils/properties.h" -#include "cutils/android_reboot.h" -#include "install.h" -#include "minui/minui.h" -#include "minzip/DirUtil.h" -#include "roots.h" -#include "recovery_ui.h" - -static const struct option OPTIONS[] = { - { "send_intent", required_argument, NULL, 's' }, - { "update_package", required_argument, NULL, 'u' }, - { "wipe_data", no_argument, NULL, 'w' }, - { "wipe_cache", no_argument, NULL, 'c' }, - { "show_text", no_argument, NULL, 't' }, - { NULL, 0, NULL, 0 }, -}; - -static const char *COMMAND_FILE = "/cache/recovery/command"; -static const char *INTENT_FILE = "/cache/recovery/intent"; -static const char *LOG_FILE = "/cache/recovery/log"; -static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; -static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; -static const char *CACHE_ROOT = "/cache"; -static const char *SDCARD_ROOT = "/sdcard"; -static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; -static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; -static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload"; - -extern UIParameters ui_parameters; // from ui.c - -/* - * The recovery tool communicates with the main system through /cache files. - * /cache/recovery/command - INPUT - command line for tool, one arg per line - * /cache/recovery/log - OUTPUT - combined log file from recovery run(s) - * /cache/recovery/intent - OUTPUT - intent that was passed in - * - * The arguments which may be supplied in the recovery.command file: - * --send_intent=anystring - write the text out to recovery.intent - * --update_package=path - verify install an OTA package file - * --wipe_data - erase user data (and cache), then reboot - * --wipe_cache - wipe cache (but not user data), then reboot - * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs - * - * After completing, we remove /cache/recovery/command and reboot. - * Arguments may also be supplied in the bootloader control block (BCB). - * These important scenarios must be safely restartable at any point: - * - * FACTORY RESET - * 1. user selects "factory reset" - * 2. main system writes "--wipe_data" to /cache/recovery/command - * 3. main system reboots into recovery - * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data" - * -- after this, rebooting will restart the erase -- - * 5. erase_volume() reformats /data - * 6. erase_volume() reformats /cache - * 7. finish_recovery() erases BCB - * -- after this, rebooting will restart the main system -- - * 8. main() calls reboot() to boot main system - * - * OTA INSTALL - * 1. main system downloads OTA package to /cache/some-filename.zip - * 2. main system writes "--update_package=/cache/some-filename.zip" - * 3. main system reboots into recovery - * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..." - * -- after this, rebooting will attempt to reinstall the update -- - * 5. install_package() attempts to install the update - * NOTE: the package install must itself be restartable from any point - * 6. finish_recovery() erases BCB - * -- after this, rebooting will (try to) restart the main system -- - * 7. ** if install failed ** - * 7a. prompt_and_wait() shows an error icon and waits for the user - * 7b; the user reboots (pulling the battery, etc) into the main system - * 8. main() calls maybe_install_firmware_update() - * ** if the update contained radio/hboot firmware **: - * 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache" - * -- after this, rebooting will reformat cache & restart main system -- - * 8b. m_i_f_u() writes firmware image into raw cache partition - * 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache" - * -- after this, rebooting will attempt to reinstall firmware -- - * 8d. bootloader tries to flash firmware - * 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache") - * -- after this, rebooting will reformat cache & restart main system -- - * 8f. erase_volume() reformats /cache - * 8g. finish_recovery() erases BCB - * -- after this, rebooting will (try to) restart the main system -- - * 9. main() calls reboot() to boot main system - */ - -static const int MAX_ARG_LENGTH = 4096; -static const int MAX_ARGS = 100; - -// open a given path, mounting partitions as necessary -FILE* -fopen_path(const char *path, const char *mode) { - if (ensure_path_mounted(path) != 0) { - LOGE("Can't mount %s\n", path); - return NULL; - } - - // When writing, try to create the containing directory, if necessary. - // Use generous permissions, the system (init.rc) will reset them. - if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1); - - FILE *fp = fopen(path, mode); - return fp; -} - -// close a file, log an error if the error indicator is set -static void -check_and_fclose(FILE *fp, const char *name) { - fflush(fp); - if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno)); - fclose(fp); -} - -// command line args come from, in decreasing precedence: -// - the actual command line -// - the bootloader control block (one per line, after "recovery") -// - the contents of COMMAND_FILE (one per line) -static void -get_args(int *argc, char ***argv) { - struct bootloader_message boot; - memset(&boot, 0, sizeof(boot)); - get_bootloader_message(&boot); // this may fail, leaving a zeroed structure - - if (boot.command[0] != 0 && boot.command[0] != 255) { - LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command); - } - - if (boot.status[0] != 0 && boot.status[0] != 255) { - LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status); - } - - // --- if arguments weren't supplied, look in the bootloader control block - if (*argc <= 1) { - boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination - const char *arg = strtok(boot.recovery, "\n"); - if (arg != NULL && !strcmp(arg, "recovery")) { - *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); - (*argv)[0] = strdup(arg); - for (*argc = 1; *argc < MAX_ARGS; ++*argc) { - if ((arg = strtok(NULL, "\n")) == NULL) break; - (*argv)[*argc] = strdup(arg); - } - LOGI("Got arguments from boot message\n"); - } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) { - LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery); - } - } - - // --- if that doesn't work, try the command file - if (*argc <= 1) { - FILE *fp = fopen_path(COMMAND_FILE, "r"); - if (fp != NULL) { - char *argv0 = (*argv)[0]; - *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); - (*argv)[0] = argv0; // use the same program name - - char buf[MAX_ARG_LENGTH]; - for (*argc = 1; *argc < MAX_ARGS; ++*argc) { - if (!fgets(buf, sizeof(buf), fp)) break; - (*argv)[*argc] = strdup(strtok(buf, "\r\n")); // Strip newline. - } - - check_and_fclose(fp, COMMAND_FILE); - LOGI("Got arguments from %s\n", COMMAND_FILE); - } - } - - // --> write the arguments we have back into the bootloader control block - // always boot into recovery after this (until finish_recovery() is called) - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - int i; - for (i = 1; i < *argc; ++i) { - strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery)); - strlcat(boot.recovery, "\n", sizeof(boot.recovery)); - } - set_bootloader_message(&boot); -} - -static void -set_sdcard_update_bootloader_message() { - struct bootloader_message boot; - memset(&boot, 0, sizeof(boot)); - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - set_bootloader_message(&boot); -} - -// How much of the temp log we have copied to the copy in cache. -static long tmplog_offset = 0; - -static void -copy_log_file(const char* source, const char* destination, int append) { - FILE *log = fopen_path(destination, append ? "a" : "w"); - if (log == NULL) { - LOGE("Can't open %s\n", destination); - } else { - FILE *tmplog = fopen(source, "r"); - if (tmplog != NULL) { - if (append) { - fseek(tmplog, tmplog_offset, SEEK_SET); // Since last write - } - char buf[4096]; - while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log); - if (append) { - tmplog_offset = ftell(tmplog); - } - check_and_fclose(tmplog, source); - } - check_and_fclose(log, destination); - } -} - - -// clear the recovery command and prepare to boot a (hopefully working) system, -// copy our log file to cache as well (for the system to read), and -// record any intent we were asked to communicate back to the system. -// this function is idempotent: call it as many times as you like. -static void -finish_recovery(const char *send_intent) { - // By this point, we're ready to return to the main system... - if (send_intent != NULL) { - FILE *fp = fopen_path(INTENT_FILE, "w"); - if (fp == NULL) { - LOGE("Can't open %s\n", INTENT_FILE); - } else { - fputs(send_intent, fp); - check_and_fclose(fp, INTENT_FILE); - } - } - - // Copy logs to cache so the system can find out what happened. - copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true); - copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false); - copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false); - chmod(LOG_FILE, 0600); - chown(LOG_FILE, 1000, 1000); // system user - chmod(LAST_LOG_FILE, 0640); - chmod(LAST_INSTALL_FILE, 0644); - - // Reset to mormal system boot so recovery won't cycle indefinitely. - struct bootloader_message boot; - memset(&boot, 0, sizeof(boot)); - set_bootloader_message(&boot); - - // Remove the command file, so recovery won't repeat indefinitely. - if (ensure_path_mounted(COMMAND_FILE) != 0 || - (unlink(COMMAND_FILE) && errno != ENOENT)) { - LOGW("Can't unlink %s\n", COMMAND_FILE); - } - - ensure_path_unmounted(CACHE_ROOT); - sync(); // For good measure. -} - -static int -erase_volume(const char *volume) { - ui_set_background(BACKGROUND_ICON_INSTALLING); - ui_show_indeterminate_progress(); - ui_print("Formatting %s...\n", volume); - - ensure_path_unmounted(volume); - - if (strcmp(volume, "/cache") == 0) { - // Any part of the log we'd copied to cache is now gone. - // Reset the pointer so we copy from the beginning of the temp - // log. - tmplog_offset = 0; - } - - return format_volume(volume); -} - -static char* -copy_sideloaded_package(const char* original_path) { - if (ensure_path_mounted(original_path) != 0) { - LOGE("Can't mount %s\n", original_path); - return NULL; - } - - if (ensure_path_mounted(SIDELOAD_TEMP_DIR) != 0) { - LOGE("Can't mount %s\n", SIDELOAD_TEMP_DIR); - return NULL; - } - - if (mkdir(SIDELOAD_TEMP_DIR, 0700) != 0) { - if (errno != EEXIST) { - LOGE("Can't mkdir %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno)); - return NULL; - } - } - - // verify that SIDELOAD_TEMP_DIR is exactly what we expect: a - // directory, owned by root, readable and writable only by root. - struct stat st; - if (stat(SIDELOAD_TEMP_DIR, &st) != 0) { - LOGE("failed to stat %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno)); - return NULL; - } - if (!S_ISDIR(st.st_mode)) { - LOGE("%s isn't a directory\n", SIDELOAD_TEMP_DIR); - return NULL; - } - if ((st.st_mode & 0777) != 0700) { - LOGE("%s has perms %o\n", SIDELOAD_TEMP_DIR, st.st_mode); - return NULL; - } - if (st.st_uid != 0) { - LOGE("%s owned by %lu; not root\n", SIDELOAD_TEMP_DIR, st.st_uid); - return NULL; - } - - char copy_path[PATH_MAX]; - strcpy(copy_path, SIDELOAD_TEMP_DIR); - strcat(copy_path, "/package.zip"); - - char* buffer = malloc(BUFSIZ); - if (buffer == NULL) { - LOGE("Failed to allocate buffer\n"); - return NULL; - } - - size_t read; - FILE* fin = fopen(original_path, "rb"); - if (fin == NULL) { - LOGE("Failed to open %s (%s)\n", original_path, strerror(errno)); - return NULL; - } - FILE* fout = fopen(copy_path, "wb"); - if (fout == NULL) { - LOGE("Failed to open %s (%s)\n", copy_path, strerror(errno)); - return NULL; - } - - while ((read = fread(buffer, 1, BUFSIZ, fin)) > 0) { - if (fwrite(buffer, 1, read, fout) != read) { - LOGE("Short write of %s (%s)\n", copy_path, strerror(errno)); - return NULL; - } - } - - free(buffer); - - if (fclose(fout) != 0) { - LOGE("Failed to close %s (%s)\n", copy_path, strerror(errno)); - return NULL; - } - - if (fclose(fin) != 0) { - LOGE("Failed to close %s (%s)\n", original_path, strerror(errno)); - return NULL; - } - - // "adb push" is happy to overwrite read-only files when it's - // running as root, but we'll try anyway. - if (chmod(copy_path, 0400) != 0) { - LOGE("Failed to chmod %s (%s)\n", copy_path, strerror(errno)); - return NULL; - } - - return strdup(copy_path); -} - -static char** -prepend_title(const char** headers) { - char* title[] = { "Android system recovery <" - EXPAND(RECOVERY_API_VERSION) "e>", - "", - NULL }; - - // count the number of lines in our title, plus the - // caller-provided headers. - int count = 0; - char** p; - for (p = title; *p; ++p, ++count); - for (p = headers; *p; ++p, ++count); - - char** new_headers = malloc((count+1) * sizeof(char*)); - char** h = new_headers; - for (p = title; *p; ++p, ++h) *h = *p; - for (p = headers; *p; ++p, ++h) *h = *p; - *h = NULL; - - return new_headers; -} - -static int -get_menu_selection(char** headers, char** items, int menu_only, - int initial_selection) { - // throw away keys pressed previously, so user doesn't - // accidentally trigger menu items. - ui_clear_key_queue(); - - ui_start_menu(headers, items, initial_selection); - int selected = initial_selection; - int chosen_item = -1; - - while (chosen_item < 0) { - int key = ui_wait_key(); - int visible = ui_text_visible(); - - if (key == -1) { // ui_wait_key() timed out - if (ui_text_ever_visible()) { - continue; - } else { - LOGI("timed out waiting for key input; rebooting.\n"); - ui_end_menu(); - return ITEM_REBOOT; - } - } - - int action = device_handle_key(key, visible); - - if (action < 0) { - switch (action) { - case HIGHLIGHT_UP: - --selected; - selected = ui_menu_select(selected); - break; - case HIGHLIGHT_DOWN: - ++selected; - selected = ui_menu_select(selected); - break; - case SELECT_ITEM: - chosen_item = selected; - break; - case NO_ACTION: - break; - } - } else if (!menu_only) { - chosen_item = action; - } - } - - ui_end_menu(); - return chosen_item; -} - -static int compare_string(const void* a, const void* b) { - return strcmp(*(const char**)a, *(const char**)b); -} - -static int -update_directory(const char* path, const char* unmount_when_done, - int* wipe_cache) { - ensure_path_mounted(path); - - const char* MENU_HEADERS[] = { "Choose a package to install:", - path, - "", - NULL }; - DIR* d; - struct dirent* de; - d = opendir(path); - if (d == NULL) { - LOGE("error opening %s: %s\n", path, strerror(errno)); - if (unmount_when_done != NULL) { - ensure_path_unmounted(unmount_when_done); - } - return 0; - } - - char** headers = prepend_title(MENU_HEADERS); - - int d_size = 0; - int d_alloc = 10; - char** dirs = malloc(d_alloc * sizeof(char*)); - int z_size = 1; - int z_alloc = 10; - char** zips = malloc(z_alloc * sizeof(char*)); - zips[0] = strdup("../"); - - while ((de = readdir(d)) != NULL) { - int name_len = strlen(de->d_name); - - if (de->d_type == DT_DIR) { - // skip "." and ".." entries - if (name_len == 1 && de->d_name[0] == '.') continue; - if (name_len == 2 && de->d_name[0] == '.' && - de->d_name[1] == '.') continue; - - if (d_size >= d_alloc) { - d_alloc *= 2; - dirs = realloc(dirs, d_alloc * sizeof(char*)); - } - dirs[d_size] = malloc(name_len + 2); - strcpy(dirs[d_size], de->d_name); - dirs[d_size][name_len] = '/'; - dirs[d_size][name_len+1] = '\0'; - ++d_size; - } else if (de->d_type == DT_REG && - name_len >= 4 && - strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) { - if (z_size >= z_alloc) { - z_alloc *= 2; - zips = realloc(zips, z_alloc * sizeof(char*)); - } - zips[z_size++] = strdup(de->d_name); - } - } - closedir(d); - - qsort(dirs, d_size, sizeof(char*), compare_string); - qsort(zips, z_size, sizeof(char*), compare_string); - - // append dirs to the zips list - if (d_size + z_size + 1 > z_alloc) { - z_alloc = d_size + z_size + 1; - zips = realloc(zips, z_alloc * sizeof(char*)); - } - memcpy(zips + z_size, dirs, d_size * sizeof(char*)); - free(dirs); - z_size += d_size; - zips[z_size] = NULL; - - int result; - int chosen_item = 0; - do { - chosen_item = get_menu_selection(headers, zips, 1, chosen_item); - - char* item = zips[chosen_item]; - int item_len = strlen(item); - if (chosen_item == 0) { // item 0 is always "../" - // go up but continue browsing (if the caller is update_directory) - result = -1; - break; - } else if (item[item_len-1] == '/') { - // recurse down into a subdirectory - char new_path[PATH_MAX]; - strlcpy(new_path, path, PATH_MAX); - strlcat(new_path, "/", PATH_MAX); - strlcat(new_path, item, PATH_MAX); - new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/' - result = update_directory(new_path, unmount_when_done, wipe_cache); - if (result >= 0) break; - } else { - // selected a zip file: attempt to install it, and return - // the status to the caller. - char new_path[PATH_MAX]; - strlcpy(new_path, path, PATH_MAX); - strlcat(new_path, "/", PATH_MAX); - strlcat(new_path, item, PATH_MAX); - - ui_print("\n-- Install %s ...\n", path); - set_sdcard_update_bootloader_message(); - char* copy = copy_sideloaded_package(new_path); - if (unmount_when_done != NULL) { - ensure_path_unmounted(unmount_when_done); - } - if (copy) { - result = install_package(copy, wipe_cache, TEMPORARY_INSTALL_FILE); - free(copy); - } else { - result = INSTALL_ERROR; - } - break; - } - } while (true); - - int i; - for (i = 0; i < z_size; ++i) free(zips[i]); - free(zips); - free(headers); - - if (unmount_when_done != NULL) { - ensure_path_unmounted(unmount_when_done); - } - return result; -} - -static void -wipe_data(int confirm) { - if (confirm) { - static char** title_headers = NULL; - - if (title_headers == NULL) { - char* headers[] = { "Confirm wipe of all user data?", - " THIS CAN NOT BE UNDONE.", - "", - NULL }; - title_headers = prepend_title((const char**)headers); - } - - char* items[] = { " No", - " No", - " No", - " No", - " No", - " No", - " No", - " Yes -- delete all user data", // [7] - " No", - " No", - " No", - NULL }; - - int chosen_item = get_menu_selection(title_headers, items, 1, 0); - if (chosen_item != 7) { - return; - } - } - - ui_print("\n-- Wiping data...\n"); - device_wipe_data(); - erase_volume("/data"); - erase_volume("/cache"); - ui_print("Data wipe complete.\n"); -} - -static void -prompt_and_wait() { - char** headers = prepend_title((const char**)MENU_HEADERS); - - for (;;) { - finish_recovery(NULL); - ui_reset_progress(); - - int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0); - - // device-specific code may take some action here. It may - // return one of the core actions handled in the switch - // statement below. - chosen_item = device_perform_action(chosen_item); - - int status; - int wipe_cache; - switch (chosen_item) { - case ITEM_REBOOT: - return; - - case ITEM_WIPE_DATA: - wipe_data(ui_text_visible()); - if (!ui_text_visible()) return; - break; - - case ITEM_WIPE_CACHE: - ui_print("\n-- Wiping cache...\n"); - erase_volume("/cache"); - ui_print("Cache wipe complete.\n"); - if (!ui_text_visible()) return; - break; - - case ITEM_APPLY_SDCARD: - status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache); - if (status == INSTALL_SUCCESS && wipe_cache) { - ui_print("\n-- Wiping cache (at package request)...\n"); - if (erase_volume("/cache")) { - ui_print("Cache wipe failed.\n"); - } else { - ui_print("Cache wipe complete.\n"); - } - } - if (status >= 0) { - if (status != INSTALL_SUCCESS) { - ui_set_background(BACKGROUND_ICON_ERROR); - ui_print("Installation aborted.\n"); - } else if (!ui_text_visible()) { - return; // reboot if logs aren't visible - } else { - ui_print("\nInstall from sdcard complete.\n"); - } - } - break; - case ITEM_APPLY_CACHE: - // Don't unmount cache at the end of this. - status = update_directory(CACHE_ROOT, NULL, &wipe_cache); - if (status == INSTALL_SUCCESS && wipe_cache) { - ui_print("\n-- Wiping cache (at package request)...\n"); - if (erase_volume("/cache")) { - ui_print("Cache wipe failed.\n"); - } else { - ui_print("Cache wipe complete.\n"); - } - } - if (status >= 0) { - if (status != INSTALL_SUCCESS) { - ui_set_background(BACKGROUND_ICON_ERROR); - ui_print("Installation aborted.\n"); - } else if (!ui_text_visible()) { - return; // reboot if logs aren't visible - } else { - ui_print("\nInstall from cache complete.\n"); - } - } - break; - - } - } -} - -static void -print_property(const char *key, const char *name, void *cookie) { - printf("%s=%s\n", key, name); -} - -int -main(int argc, char **argv) { - time_t start = time(NULL); - - // If these fail, there's not really anywhere to complain... - freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL); - freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL); - printf("Starting recovery on %s", ctime(&start)); - - device_ui_init(&ui_parameters); - ui_init(); - ui_set_background(BACKGROUND_ICON_INSTALLING); - load_volume_table(); - get_args(&argc, &argv); - - int previous_runs = 0; - const char *send_intent = NULL; - const char *update_package = NULL; - int wipe_data = 0, wipe_cache = 0; - - int arg; - while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) { - switch (arg) { - case 'p': previous_runs = atoi(optarg); break; - case 's': send_intent = optarg; break; - case 'u': update_package = optarg; break; - case 'w': wipe_data = wipe_cache = 1; break; - case 'c': wipe_cache = 1; break; - case 't': ui_show_text(1); break; - case '?': - LOGE("Invalid command argument\n"); - continue; - } - } - - device_recovery_start(); - - printf("Command:"); - for (arg = 0; arg < argc; arg++) { - printf(" \"%s\"", argv[arg]); - } - printf("\n"); - - if (update_package) { - // For backwards compatibility on the cache partition only, if - // we're given an old 'root' path "CACHE:foo", change it to - // "/cache/foo". - if (strncmp(update_package, "CACHE:", 6) == 0) { - int len = strlen(update_package) + 10; - char* modified_path = malloc(len); - strlcpy(modified_path, "/cache/", len); - strlcat(modified_path, update_package+6, len); - printf("(replacing path \"%s\" with \"%s\")\n", - update_package, modified_path); - update_package = modified_path; - } - } - printf("\n"); - - property_list(print_property, NULL); - printf("\n"); - - int status = INSTALL_SUCCESS; - - if (update_package != NULL) { - status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE); - if (status == INSTALL_SUCCESS && wipe_cache) { - if (erase_volume("/cache")) { - LOGE("Cache wipe (requested by package) failed."); - } - } - if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n"); - } else if (wipe_data) { - if (device_wipe_data()) status = INSTALL_ERROR; - if (erase_volume("/data")) status = INSTALL_ERROR; - if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; - if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n"); - } else if (wipe_cache) { - if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; - if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed.\n"); - } else { - status = INSTALL_ERROR; // No command specified - } - - if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR); - if (status != INSTALL_SUCCESS || ui_text_visible()) { - prompt_and_wait(); - } - - // Otherwise, get ready to boot the main system... - finish_recovery(send_intent); - ui_print("Rebooting...\n"); - android_reboot(ANDROID_RB_RESTART, 0, 0); - return EXIT_SUCCESS; -} diff --git a/recovery.cpp b/recovery.cpp new file mode 100644 index 000000000..7c1d7fb0f --- /dev/null +++ b/recovery.cpp @@ -0,0 +1,826 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bootloader.h" +#include "common.h" +#include "cutils/properties.h" +#include "cutils/android_reboot.h" +#include "install.h" +#include "minui/minui.h" +#include "minzip/DirUtil.h" +#include "roots.h" +#include "recovery_ui.h" +#include "ui.h" + +static const struct option OPTIONS[] = { + { "send_intent", required_argument, NULL, 's' }, + { "update_package", required_argument, NULL, 'u' }, + { "wipe_data", no_argument, NULL, 'w' }, + { "wipe_cache", no_argument, NULL, 'c' }, + { "show_text", no_argument, NULL, 't' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *COMMAND_FILE = "/cache/recovery/command"; +static const char *INTENT_FILE = "/cache/recovery/intent"; +static const char *LOG_FILE = "/cache/recovery/log"; +static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; +static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; +static const char *CACHE_ROOT = "/cache"; +static const char *SDCARD_ROOT = "/sdcard"; +static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; +static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; +static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload"; + +extern UIParameters ui_parameters; // from ui.c + +/* + * The recovery tool communicates with the main system through /cache files. + * /cache/recovery/command - INPUT - command line for tool, one arg per line + * /cache/recovery/log - OUTPUT - combined log file from recovery run(s) + * /cache/recovery/intent - OUTPUT - intent that was passed in + * + * The arguments which may be supplied in the recovery.command file: + * --send_intent=anystring - write the text out to recovery.intent + * --update_package=path - verify install an OTA package file + * --wipe_data - erase user data (and cache), then reboot + * --wipe_cache - wipe cache (but not user data), then reboot + * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs + * + * After completing, we remove /cache/recovery/command and reboot. + * Arguments may also be supplied in the bootloader control block (BCB). + * These important scenarios must be safely restartable at any point: + * + * FACTORY RESET + * 1. user selects "factory reset" + * 2. main system writes "--wipe_data" to /cache/recovery/command + * 3. main system reboots into recovery + * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data" + * -- after this, rebooting will restart the erase -- + * 5. erase_volume() reformats /data + * 6. erase_volume() reformats /cache + * 7. finish_recovery() erases BCB + * -- after this, rebooting will restart the main system -- + * 8. main() calls reboot() to boot main system + * + * OTA INSTALL + * 1. main system downloads OTA package to /cache/some-filename.zip + * 2. main system writes "--update_package=/cache/some-filename.zip" + * 3. main system reboots into recovery + * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..." + * -- after this, rebooting will attempt to reinstall the update -- + * 5. install_package() attempts to install the update + * NOTE: the package install must itself be restartable from any point + * 6. finish_recovery() erases BCB + * -- after this, rebooting will (try to) restart the main system -- + * 7. ** if install failed ** + * 7a. prompt_and_wait() shows an error icon and waits for the user + * 7b; the user reboots (pulling the battery, etc) into the main system + * 8. main() calls maybe_install_firmware_update() + * ** if the update contained radio/hboot firmware **: + * 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache" + * -- after this, rebooting will reformat cache & restart main system -- + * 8b. m_i_f_u() writes firmware image into raw cache partition + * 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache" + * -- after this, rebooting will attempt to reinstall firmware -- + * 8d. bootloader tries to flash firmware + * 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache") + * -- after this, rebooting will reformat cache & restart main system -- + * 8f. erase_volume() reformats /cache + * 8g. finish_recovery() erases BCB + * -- after this, rebooting will (try to) restart the main system -- + * 9. main() calls reboot() to boot main system + */ + +static const int MAX_ARG_LENGTH = 4096; +static const int MAX_ARGS = 100; + +// open a given path, mounting partitions as necessary +FILE* +fopen_path(const char *path, const char *mode) { + if (ensure_path_mounted(path) != 0) { + LOGE("Can't mount %s\n", path); + return NULL; + } + + // When writing, try to create the containing directory, if necessary. + // Use generous permissions, the system (init.rc) will reset them. + if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1); + + FILE *fp = fopen(path, mode); + return fp; +} + +// close a file, log an error if the error indicator is set +static void +check_and_fclose(FILE *fp, const char *name) { + fflush(fp); + if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno)); + fclose(fp); +} + +// command line args come from, in decreasing precedence: +// - the actual command line +// - the bootloader control block (one per line, after "recovery") +// - the contents of COMMAND_FILE (one per line) +static void +get_args(int *argc, char ***argv) { + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + get_bootloader_message(&boot); // this may fail, leaving a zeroed structure + + if (boot.command[0] != 0 && boot.command[0] != 255) { + LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command); + } + + if (boot.status[0] != 0 && boot.status[0] != 255) { + LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status); + } + + // --- if arguments weren't supplied, look in the bootloader control block + if (*argc <= 1) { + boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination + const char *arg = strtok(boot.recovery, "\n"); + if (arg != NULL && !strcmp(arg, "recovery")) { + *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); + (*argv)[0] = strdup(arg); + for (*argc = 1; *argc < MAX_ARGS; ++*argc) { + if ((arg = strtok(NULL, "\n")) == NULL) break; + (*argv)[*argc] = strdup(arg); + } + LOGI("Got arguments from boot message\n"); + } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) { + LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery); + } + } + + // --- if that doesn't work, try the command file + if (*argc <= 1) { + FILE *fp = fopen_path(COMMAND_FILE, "r"); + if (fp != NULL) { + char *argv0 = (*argv)[0]; + *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); + (*argv)[0] = argv0; // use the same program name + + char buf[MAX_ARG_LENGTH]; + for (*argc = 1; *argc < MAX_ARGS; ++*argc) { + if (!fgets(buf, sizeof(buf), fp)) break; + (*argv)[*argc] = strdup(strtok(buf, "\r\n")); // Strip newline. + } + + check_and_fclose(fp, COMMAND_FILE); + LOGI("Got arguments from %s\n", COMMAND_FILE); + } + } + + // --> write the arguments we have back into the bootloader control block + // always boot into recovery after this (until finish_recovery() is called) + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + int i; + for (i = 1; i < *argc; ++i) { + strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery)); + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + set_bootloader_message(&boot); +} + +static void +set_sdcard_update_bootloader_message() { + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + set_bootloader_message(&boot); +} + +// How much of the temp log we have copied to the copy in cache. +static long tmplog_offset = 0; + +static void +copy_log_file(const char* source, const char* destination, int append) { + FILE *log = fopen_path(destination, append ? "a" : "w"); + if (log == NULL) { + LOGE("Can't open %s\n", destination); + } else { + FILE *tmplog = fopen(source, "r"); + if (tmplog != NULL) { + if (append) { + fseek(tmplog, tmplog_offset, SEEK_SET); // Since last write + } + char buf[4096]; + while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log); + if (append) { + tmplog_offset = ftell(tmplog); + } + check_and_fclose(tmplog, source); + } + check_and_fclose(log, destination); + } +} + + +// clear the recovery command and prepare to boot a (hopefully working) system, +// copy our log file to cache as well (for the system to read), and +// record any intent we were asked to communicate back to the system. +// this function is idempotent: call it as many times as you like. +static void +finish_recovery(const char *send_intent) { + // By this point, we're ready to return to the main system... + if (send_intent != NULL) { + FILE *fp = fopen_path(INTENT_FILE, "w"); + if (fp == NULL) { + LOGE("Can't open %s\n", INTENT_FILE); + } else { + fputs(send_intent, fp); + check_and_fclose(fp, INTENT_FILE); + } + } + + // Copy logs to cache so the system can find out what happened. + copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true); + copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false); + copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false); + chmod(LOG_FILE, 0600); + chown(LOG_FILE, 1000, 1000); // system user + chmod(LAST_LOG_FILE, 0640); + chmod(LAST_INSTALL_FILE, 0644); + + // Reset to mormal system boot so recovery won't cycle indefinitely. + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + set_bootloader_message(&boot); + + // Remove the command file, so recovery won't repeat indefinitely. + if (ensure_path_mounted(COMMAND_FILE) != 0 || + (unlink(COMMAND_FILE) && errno != ENOENT)) { + LOGW("Can't unlink %s\n", COMMAND_FILE); + } + + ensure_path_unmounted(CACHE_ROOT); + sync(); // For good measure. +} + +static int +erase_volume(const char *volume) { + ui_set_background(BACKGROUND_ICON_INSTALLING); + ui_show_indeterminate_progress(); + ui_print("Formatting %s...\n", volume); + + ensure_path_unmounted(volume); + + if (strcmp(volume, "/cache") == 0) { + // Any part of the log we'd copied to cache is now gone. + // Reset the pointer so we copy from the beginning of the temp + // log. + tmplog_offset = 0; + } + + return format_volume(volume); +} + +static char* +copy_sideloaded_package(const char* original_path) { + if (ensure_path_mounted(original_path) != 0) { + LOGE("Can't mount %s\n", original_path); + return NULL; + } + + if (ensure_path_mounted(SIDELOAD_TEMP_DIR) != 0) { + LOGE("Can't mount %s\n", SIDELOAD_TEMP_DIR); + return NULL; + } + + if (mkdir(SIDELOAD_TEMP_DIR, 0700) != 0) { + if (errno != EEXIST) { + LOGE("Can't mkdir %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno)); + return NULL; + } + } + + // verify that SIDELOAD_TEMP_DIR is exactly what we expect: a + // directory, owned by root, readable and writable only by root. + struct stat st; + if (stat(SIDELOAD_TEMP_DIR, &st) != 0) { + LOGE("failed to stat %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno)); + return NULL; + } + if (!S_ISDIR(st.st_mode)) { + LOGE("%s isn't a directory\n", SIDELOAD_TEMP_DIR); + return NULL; + } + if ((st.st_mode & 0777) != 0700) { + LOGE("%s has perms %o\n", SIDELOAD_TEMP_DIR, st.st_mode); + return NULL; + } + if (st.st_uid != 0) { + LOGE("%s owned by %lu; not root\n", SIDELOAD_TEMP_DIR, st.st_uid); + return NULL; + } + + char copy_path[PATH_MAX]; + strcpy(copy_path, SIDELOAD_TEMP_DIR); + strcat(copy_path, "/package.zip"); + + char* buffer = (char*)malloc(BUFSIZ); + if (buffer == NULL) { + LOGE("Failed to allocate buffer\n"); + return NULL; + } + + size_t read; + FILE* fin = fopen(original_path, "rb"); + if (fin == NULL) { + LOGE("Failed to open %s (%s)\n", original_path, strerror(errno)); + return NULL; + } + FILE* fout = fopen(copy_path, "wb"); + if (fout == NULL) { + LOGE("Failed to open %s (%s)\n", copy_path, strerror(errno)); + return NULL; + } + + while ((read = fread(buffer, 1, BUFSIZ, fin)) > 0) { + if (fwrite(buffer, 1, read, fout) != read) { + LOGE("Short write of %s (%s)\n", copy_path, strerror(errno)); + return NULL; + } + } + + free(buffer); + + if (fclose(fout) != 0) { + LOGE("Failed to close %s (%s)\n", copy_path, strerror(errno)); + return NULL; + } + + if (fclose(fin) != 0) { + LOGE("Failed to close %s (%s)\n", original_path, strerror(errno)); + return NULL; + } + + // "adb push" is happy to overwrite read-only files when it's + // running as root, but we'll try anyway. + if (chmod(copy_path, 0400) != 0) { + LOGE("Failed to chmod %s (%s)\n", copy_path, strerror(errno)); + return NULL; + } + + return strdup(copy_path); +} + +static const char** +prepend_title(const char** headers) { + const char* title[] = { "Android system recovery <" + EXPAND(RECOVERY_API_VERSION) "e>", + "", + NULL }; + + // count the number of lines in our title, plus the + // caller-provided headers. + int count = 0; + const char** p; + for (p = title; *p; ++p, ++count); + for (p = headers; *p; ++p, ++count); + + const char** new_headers = (const char**)malloc((count+1) * sizeof(char*)); + const char** h = new_headers; + for (p = title; *p; ++p, ++h) *h = *p; + for (p = headers; *p; ++p, ++h) *h = *p; + *h = NULL; + + return new_headers; +} + +static int +get_menu_selection(const char* const * headers, const char* const * items, + int menu_only, int initial_selection) { + // throw away keys pressed previously, so user doesn't + // accidentally trigger menu items. + ui_clear_key_queue(); + + ui_start_menu(headers, items, initial_selection); + int selected = initial_selection; + int chosen_item = -1; + + while (chosen_item < 0) { + int key = ui_wait_key(); + int visible = ui_text_visible(); + + if (key == -1) { // ui_wait_key() timed out + if (ui_text_ever_visible()) { + continue; + } else { + LOGI("timed out waiting for key input; rebooting.\n"); + ui_end_menu(); + return ITEM_REBOOT; + } + } + + int action = device_handle_key(key, visible); + + if (action < 0) { + switch (action) { + case HIGHLIGHT_UP: + --selected; + selected = ui_menu_select(selected); + break; + case HIGHLIGHT_DOWN: + ++selected; + selected = ui_menu_select(selected); + break; + case SELECT_ITEM: + chosen_item = selected; + break; + case NO_ACTION: + break; + } + } else if (!menu_only) { + chosen_item = action; + } + } + + ui_end_menu(); + return chosen_item; +} + +static int compare_string(const void* a, const void* b) { + return strcmp(*(const char**)a, *(const char**)b); +} + +static int +update_directory(const char* path, const char* unmount_when_done, + int* wipe_cache) { + ensure_path_mounted(path); + + const char* MENU_HEADERS[] = { "Choose a package to install:", + path, + "", + NULL }; + DIR* d; + struct dirent* de; + d = opendir(path); + if (d == NULL) { + LOGE("error opening %s: %s\n", path, strerror(errno)); + if (unmount_when_done != NULL) { + ensure_path_unmounted(unmount_when_done); + } + return 0; + } + + const char** headers = prepend_title(MENU_HEADERS); + + int d_size = 0; + int d_alloc = 10; + char** dirs = (char**)malloc(d_alloc * sizeof(char*)); + int z_size = 1; + int z_alloc = 10; + char** zips = (char**)malloc(z_alloc * sizeof(char*)); + zips[0] = strdup("../"); + + while ((de = readdir(d)) != NULL) { + int name_len = strlen(de->d_name); + + if (de->d_type == DT_DIR) { + // skip "." and ".." entries + if (name_len == 1 && de->d_name[0] == '.') continue; + if (name_len == 2 && de->d_name[0] == '.' && + de->d_name[1] == '.') continue; + + if (d_size >= d_alloc) { + d_alloc *= 2; + dirs = (char**)realloc(dirs, d_alloc * sizeof(char*)); + } + dirs[d_size] = (char*)malloc(name_len + 2); + strcpy(dirs[d_size], de->d_name); + dirs[d_size][name_len] = '/'; + dirs[d_size][name_len+1] = '\0'; + ++d_size; + } else if (de->d_type == DT_REG && + name_len >= 4 && + strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) { + if (z_size >= z_alloc) { + z_alloc *= 2; + zips = (char**)realloc(zips, z_alloc * sizeof(char*)); + } + zips[z_size++] = strdup(de->d_name); + } + } + closedir(d); + + qsort(dirs, d_size, sizeof(char*), compare_string); + qsort(zips, z_size, sizeof(char*), compare_string); + + // append dirs to the zips list + if (d_size + z_size + 1 > z_alloc) { + z_alloc = d_size + z_size + 1; + zips = (char**)realloc(zips, z_alloc * sizeof(char*)); + } + memcpy(zips + z_size, dirs, d_size * sizeof(char*)); + free(dirs); + z_size += d_size; + zips[z_size] = NULL; + + int result; + int chosen_item = 0; + do { + chosen_item = get_menu_selection(headers, zips, 1, chosen_item); + + char* item = zips[chosen_item]; + int item_len = strlen(item); + if (chosen_item == 0) { // item 0 is always "../" + // go up but continue browsing (if the caller is update_directory) + result = -1; + break; + } else if (item[item_len-1] == '/') { + // recurse down into a subdirectory + char new_path[PATH_MAX]; + strlcpy(new_path, path, PATH_MAX); + strlcat(new_path, "/", PATH_MAX); + strlcat(new_path, item, PATH_MAX); + new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/' + result = update_directory(new_path, unmount_when_done, wipe_cache); + if (result >= 0) break; + } else { + // selected a zip file: attempt to install it, and return + // the status to the caller. + char new_path[PATH_MAX]; + strlcpy(new_path, path, PATH_MAX); + strlcat(new_path, "/", PATH_MAX); + strlcat(new_path, item, PATH_MAX); + + ui_print("\n-- Install %s ...\n", path); + set_sdcard_update_bootloader_message(); + char* copy = copy_sideloaded_package(new_path); + if (unmount_when_done != NULL) { + ensure_path_unmounted(unmount_when_done); + } + if (copy) { + result = install_package(copy, wipe_cache, TEMPORARY_INSTALL_FILE); + free(copy); + } else { + result = INSTALL_ERROR; + } + break; + } + } while (true); + + int i; + for (i = 0; i < z_size; ++i) free(zips[i]); + free(zips); + free(headers); + + if (unmount_when_done != NULL) { + ensure_path_unmounted(unmount_when_done); + } + return result; +} + +static void +wipe_data(int confirm) { + if (confirm) { + static const char** title_headers = NULL; + + if (title_headers == NULL) { + const char* headers[] = { "Confirm wipe of all user data?", + " THIS CAN NOT BE UNDONE.", + "", + NULL }; + title_headers = prepend_title((const char**)headers); + } + + const char* items[] = { " No", + " No", + " No", + " No", + " No", + " No", + " No", + " Yes -- delete all user data", // [7] + " No", + " No", + " No", + NULL }; + + int chosen_item = get_menu_selection(title_headers, items, 1, 0); + if (chosen_item != 7) { + return; + } + } + + ui_print("\n-- Wiping data...\n"); + device_wipe_data(); + erase_volume("/data"); + erase_volume("/cache"); + ui_print("Data wipe complete.\n"); +} + +static void +prompt_and_wait() { + const char** headers = prepend_title((const char**)MENU_HEADERS); + + for (;;) { + finish_recovery(NULL); + ui_reset_progress(); + + int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0); + + // device-specific code may take some action here. It may + // return one of the core actions handled in the switch + // statement below. + chosen_item = device_perform_action(chosen_item); + + int status; + int wipe_cache; + switch (chosen_item) { + case ITEM_REBOOT: + return; + + case ITEM_WIPE_DATA: + wipe_data(ui_text_visible()); + if (!ui_text_visible()) return; + break; + + case ITEM_WIPE_CACHE: + ui_print("\n-- Wiping cache...\n"); + erase_volume("/cache"); + ui_print("Cache wipe complete.\n"); + if (!ui_text_visible()) return; + break; + + case ITEM_APPLY_SDCARD: + status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache); + if (status == INSTALL_SUCCESS && wipe_cache) { + ui_print("\n-- Wiping cache (at package request)...\n"); + if (erase_volume("/cache")) { + ui_print("Cache wipe failed.\n"); + } else { + ui_print("Cache wipe complete.\n"); + } + } + if (status >= 0) { + if (status != INSTALL_SUCCESS) { + ui_set_background(BACKGROUND_ICON_ERROR); + ui_print("Installation aborted.\n"); + } else if (!ui_text_visible()) { + return; // reboot if logs aren't visible + } else { + ui_print("\nInstall from sdcard complete.\n"); + } + } + break; + case ITEM_APPLY_CACHE: + // Don't unmount cache at the end of this. + status = update_directory(CACHE_ROOT, NULL, &wipe_cache); + if (status == INSTALL_SUCCESS && wipe_cache) { + ui_print("\n-- Wiping cache (at package request)...\n"); + if (erase_volume("/cache")) { + ui_print("Cache wipe failed.\n"); + } else { + ui_print("Cache wipe complete.\n"); + } + } + if (status >= 0) { + if (status != INSTALL_SUCCESS) { + ui_set_background(BACKGROUND_ICON_ERROR); + ui_print("Installation aborted.\n"); + } else if (!ui_text_visible()) { + return; // reboot if logs aren't visible + } else { + ui_print("\nInstall from cache complete.\n"); + } + } + break; + + } + } +} + +static void +print_property(const char *key, const char *name, void *cookie) { + printf("%s=%s\n", key, name); +} + +int +main(int argc, char **argv) { + time_t start = time(NULL); + + // If these fail, there's not really anywhere to complain... + freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL); + freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL); + printf("Starting recovery on %s", ctime(&start)); + + device_ui_init(&ui_parameters); + ui_init(); + ui_set_background(BACKGROUND_ICON_INSTALLING); + load_volume_table(); + get_args(&argc, &argv); + + int previous_runs = 0; + const char *send_intent = NULL; + const char *update_package = NULL; + int wipe_data = 0, wipe_cache = 0; + + int arg; + while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) { + switch (arg) { + case 'p': previous_runs = atoi(optarg); break; + case 's': send_intent = optarg; break; + case 'u': update_package = optarg; break; + case 'w': wipe_data = wipe_cache = 1; break; + case 'c': wipe_cache = 1; break; + case 't': ui_show_text(1); break; + case '?': + LOGE("Invalid command argument\n"); + continue; + } + } + + device_recovery_start(); + + printf("Command:"); + for (arg = 0; arg < argc; arg++) { + printf(" \"%s\"", argv[arg]); + } + printf("\n"); + + if (update_package) { + // For backwards compatibility on the cache partition only, if + // we're given an old 'root' path "CACHE:foo", change it to + // "/cache/foo". + if (strncmp(update_package, "CACHE:", 6) == 0) { + int len = strlen(update_package) + 10; + char* modified_path = (char*)malloc(len); + strlcpy(modified_path, "/cache/", len); + strlcat(modified_path, update_package+6, len); + printf("(replacing path \"%s\" with \"%s\")\n", + update_package, modified_path); + update_package = modified_path; + } + } + printf("\n"); + + property_list(print_property, NULL); + printf("\n"); + + int status = INSTALL_SUCCESS; + + if (update_package != NULL) { + status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE); + if (status == INSTALL_SUCCESS && wipe_cache) { + if (erase_volume("/cache")) { + LOGE("Cache wipe (requested by package) failed."); + } + } + if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n"); + } else if (wipe_data) { + if (device_wipe_data()) status = INSTALL_ERROR; + if (erase_volume("/data")) status = INSTALL_ERROR; + if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; + if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n"); + } else if (wipe_cache) { + if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; + if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed.\n"); + } else { + status = INSTALL_ERROR; // No command specified + } + + if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR); + if (status != INSTALL_SUCCESS || ui_text_visible()) { + prompt_and_wait(); + } + + // Otherwise, get ready to boot the main system... + finish_recovery(send_intent); + ui_print("Rebooting...\n"); + android_reboot(ANDROID_RB_RESTART, 0, 0); + return EXIT_SUCCESS; +} diff --git a/recovery_ui.h b/recovery_ui.h index 5f0177045..4c4baf542 100644 --- a/recovery_ui.h +++ b/recovery_ui.h @@ -19,6 +19,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + // Called before UI library is initialized. Can change things like // how many frames are included in various animations, etc. extern void device_ui_init(UIParameters* ui_parameters); @@ -84,4 +88,8 @@ extern char* MENU_HEADERS[]; // Text of menu items. extern char* MENU_ITEMS[]; +#ifdef __cplusplus +} +#endif + #endif diff --git a/roots.c b/roots.c deleted file mode 100644 index cb7e067a1..000000000 --- a/roots.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdutils/mtdutils.h" -#include "mtdutils/mounts.h" -#include "roots.h" -#include "common.h" -#include "make_ext4fs.h" - -static int num_volumes = 0; -static Volume* device_volumes = NULL; - -static int parse_options(char* options, Volume* volume) { - char* option; - while (option = strtok(options, ",")) { - options = NULL; - - if (strncmp(option, "length=", 7) == 0) { - volume->length = strtoll(option+7, NULL, 10); - } else { - LOGE("bad option \"%s\"\n", option); - return -1; - } - } - return 0; -} - -void load_volume_table() { - int alloc = 2; - device_volumes = malloc(alloc * sizeof(Volume)); - - // Insert an entry for /tmp, which is the ramdisk and is always mounted. - device_volumes[0].mount_point = "/tmp"; - device_volumes[0].fs_type = "ramdisk"; - device_volumes[0].device = NULL; - device_volumes[0].device2 = NULL; - device_volumes[0].length = 0; - num_volumes = 1; - - FILE* fstab = fopen("/etc/recovery.fstab", "r"); - if (fstab == NULL) { - LOGE("failed to open /etc/recovery.fstab (%s)\n", strerror(errno)); - return; - } - - char buffer[1024]; - int i; - while (fgets(buffer, sizeof(buffer)-1, fstab)) { - for (i = 0; buffer[i] && isspace(buffer[i]); ++i); - if (buffer[i] == '\0' || buffer[i] == '#') continue; - - char* original = strdup(buffer); - - char* mount_point = strtok(buffer+i, " \t\n"); - char* fs_type = strtok(NULL, " \t\n"); - char* device = strtok(NULL, " \t\n"); - // lines may optionally have a second device, to use if - // mounting the first one fails. - char* options = NULL; - char* device2 = strtok(NULL, " \t\n"); - if (device2) { - if (device2[0] == '/') { - options = strtok(NULL, " \t\n"); - } else { - options = device2; - device2 = NULL; - } - } - - if (mount_point && fs_type && device) { - while (num_volumes >= alloc) { - alloc *= 2; - device_volumes = realloc(device_volumes, alloc*sizeof(Volume)); - } - device_volumes[num_volumes].mount_point = strdup(mount_point); - device_volumes[num_volumes].fs_type = strdup(fs_type); - device_volumes[num_volumes].device = strdup(device); - device_volumes[num_volumes].device2 = - device2 ? strdup(device2) : NULL; - - device_volumes[num_volumes].length = 0; - if (parse_options(options, device_volumes + num_volumes) != 0) { - LOGE("skipping malformed recovery.fstab line: %s\n", original); - } else { - ++num_volumes; - } - } else { - LOGE("skipping malformed recovery.fstab line: %s\n", original); - } - free(original); - } - - fclose(fstab); - - printf("recovery filesystem table\n"); - printf("=========================\n"); - for (i = 0; i < num_volumes; ++i) { - Volume* v = &device_volumes[i]; - printf(" %d %s %s %s %s %lld\n", i, v->mount_point, v->fs_type, - v->device, v->device2, v->length); - } - printf("\n"); -} - -Volume* volume_for_path(const char* path) { - int i; - for (i = 0; i < num_volumes; ++i) { - Volume* v = device_volumes+i; - int len = strlen(v->mount_point); - if (strncmp(path, v->mount_point, len) == 0 && - (path[len] == '\0' || path[len] == '/')) { - return v; - } - } - return NULL; -} - -int ensure_path_mounted(const char* path) { - Volume* v = volume_for_path(path); - if (v == NULL) { - LOGE("unknown volume for path [%s]\n", path); - return -1; - } - if (strcmp(v->fs_type, "ramdisk") == 0) { - // the ramdisk is always mounted. - return 0; - } - - int result; - result = scan_mounted_volumes(); - if (result < 0) { - LOGE("failed to scan mounted volumes\n"); - return -1; - } - - const MountedVolume* mv = - find_mounted_volume_by_mount_point(v->mount_point); - if (mv) { - // volume is already mounted - return 0; - } - - mkdir(v->mount_point, 0755); // in case it doesn't already exist - - if (strcmp(v->fs_type, "yaffs2") == 0) { - // mount an MTD partition as a YAFFS2 filesystem. - mtd_scan_partitions(); - const MtdPartition* partition; - partition = mtd_find_partition_by_name(v->device); - if (partition == NULL) { - LOGE("failed to find \"%s\" partition to mount at \"%s\"\n", - v->device, v->mount_point); - return -1; - } - return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0); - } else if (strcmp(v->fs_type, "ext4") == 0 || - strcmp(v->fs_type, "vfat") == 0) { - result = mount(v->device, v->mount_point, v->fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); - if (result == 0) return 0; - - if (v->device2) { - LOGW("failed to mount %s (%s); trying %s\n", - v->device, strerror(errno), v->device2); - result = mount(v->device2, v->mount_point, v->fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); - if (result == 0) return 0; - } - - LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno)); - return -1; - } - - LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point); - return -1; -} - -int ensure_path_unmounted(const char* path) { - Volume* v = volume_for_path(path); - if (v == NULL) { - LOGE("unknown volume for path [%s]\n", path); - return -1; - } - if (strcmp(v->fs_type, "ramdisk") == 0) { - // the ramdisk is always mounted; you can't unmount it. - return -1; - } - - int result; - result = scan_mounted_volumes(); - if (result < 0) { - LOGE("failed to scan mounted volumes\n"); - return -1; - } - - const MountedVolume* mv = - find_mounted_volume_by_mount_point(v->mount_point); - if (mv == NULL) { - // volume is already unmounted - return 0; - } - - return unmount_mounted_volume(mv); -} - -int format_volume(const char* volume) { - Volume* v = volume_for_path(volume); - if (v == NULL) { - LOGE("unknown volume \"%s\"\n", volume); - return -1; - } - if (strcmp(v->fs_type, "ramdisk") == 0) { - // you can't format the ramdisk. - LOGE("can't format_volume \"%s\"", volume); - return -1; - } - if (strcmp(v->mount_point, volume) != 0) { - LOGE("can't give path \"%s\" to format_volume\n", volume); - return -1; - } - - if (ensure_path_unmounted(volume) != 0) { - LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point); - return -1; - } - - if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) { - mtd_scan_partitions(); - const MtdPartition* partition = mtd_find_partition_by_name(v->device); - if (partition == NULL) { - LOGE("format_volume: no MTD partition \"%s\"\n", v->device); - return -1; - } - - MtdWriteContext *write = mtd_write_partition(partition); - if (write == NULL) { - LOGW("format_volume: can't open MTD \"%s\"\n", v->device); - return -1; - } else if (mtd_erase_blocks(write, -1) == (off_t) -1) { - LOGW("format_volume: can't erase MTD \"%s\"\n", v->device); - mtd_write_close(write); - return -1; - } else if (mtd_write_close(write)) { - LOGW("format_volume: can't close MTD \"%s\"\n", v->device); - return -1; - } - return 0; - } - - if (strcmp(v->fs_type, "ext4") == 0) { - int result = make_ext4fs(v->device, v->length); - if (result != 0) { - LOGE("format_volume: make_extf4fs failed on %s\n", v->device); - return -1; - } - return 0; - } - - LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type); - return -1; -} diff --git a/roots.cpp b/roots.cpp new file mode 100644 index 000000000..9345cb0a2 --- /dev/null +++ b/roots.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdutils/mtdutils.h" +#include "mtdutils/mounts.h" +#include "roots.h" +#include "common.h" +#include "make_ext4fs.h" + +static int num_volumes = 0; +static Volume* device_volumes = NULL; + +static int parse_options(char* options, Volume* volume) { + char* option; + while ((option = strtok(options, ","))) { + options = NULL; + + if (strncmp(option, "length=", 7) == 0) { + volume->length = strtoll(option+7, NULL, 10); + } else { + LOGE("bad option \"%s\"\n", option); + return -1; + } + } + return 0; +} + +void load_volume_table() { + int alloc = 2; + device_volumes = (Volume*)malloc(alloc * sizeof(Volume)); + + // Insert an entry for /tmp, which is the ramdisk and is always mounted. + device_volumes[0].mount_point = "/tmp"; + device_volumes[0].fs_type = "ramdisk"; + device_volumes[0].device = NULL; + device_volumes[0].device2 = NULL; + device_volumes[0].length = 0; + num_volumes = 1; + + FILE* fstab = fopen("/etc/recovery.fstab", "r"); + if (fstab == NULL) { + LOGE("failed to open /etc/recovery.fstab (%s)\n", strerror(errno)); + return; + } + + char buffer[1024]; + int i; + while (fgets(buffer, sizeof(buffer)-1, fstab)) { + for (i = 0; buffer[i] && isspace(buffer[i]); ++i); + if (buffer[i] == '\0' || buffer[i] == '#') continue; + + char* original = strdup(buffer); + + char* mount_point = strtok(buffer+i, " \t\n"); + char* fs_type = strtok(NULL, " \t\n"); + char* device = strtok(NULL, " \t\n"); + // lines may optionally have a second device, to use if + // mounting the first one fails. + char* options = NULL; + char* device2 = strtok(NULL, " \t\n"); + if (device2) { + if (device2[0] == '/') { + options = strtok(NULL, " \t\n"); + } else { + options = device2; + device2 = NULL; + } + } + + if (mount_point && fs_type && device) { + while (num_volumes >= alloc) { + alloc *= 2; + device_volumes = (Volume*)realloc(device_volumes, alloc*sizeof(Volume)); + } + device_volumes[num_volumes].mount_point = strdup(mount_point); + device_volumes[num_volumes].fs_type = strdup(fs_type); + device_volumes[num_volumes].device = strdup(device); + device_volumes[num_volumes].device2 = + device2 ? strdup(device2) : NULL; + + device_volumes[num_volumes].length = 0; + if (parse_options(options, device_volumes + num_volumes) != 0) { + LOGE("skipping malformed recovery.fstab line: %s\n", original); + } else { + ++num_volumes; + } + } else { + LOGE("skipping malformed recovery.fstab line: %s\n", original); + } + free(original); + } + + fclose(fstab); + + printf("recovery filesystem table\n"); + printf("=========================\n"); + for (i = 0; i < num_volumes; ++i) { + Volume* v = &device_volumes[i]; + printf(" %d %s %s %s %s %lld\n", i, v->mount_point, v->fs_type, + v->device, v->device2, v->length); + } + printf("\n"); +} + +Volume* volume_for_path(const char* path) { + int i; + for (i = 0; i < num_volumes; ++i) { + Volume* v = device_volumes+i; + int len = strlen(v->mount_point); + if (strncmp(path, v->mount_point, len) == 0 && + (path[len] == '\0' || path[len] == '/')) { + return v; + } + } + return NULL; +} + +int ensure_path_mounted(const char* path) { + Volume* v = volume_for_path(path); + if (v == NULL) { + LOGE("unknown volume for path [%s]\n", path); + return -1; + } + if (strcmp(v->fs_type, "ramdisk") == 0) { + // the ramdisk is always mounted. + return 0; + } + + int result; + result = scan_mounted_volumes(); + if (result < 0) { + LOGE("failed to scan mounted volumes\n"); + return -1; + } + + const MountedVolume* mv = + find_mounted_volume_by_mount_point(v->mount_point); + if (mv) { + // volume is already mounted + return 0; + } + + mkdir(v->mount_point, 0755); // in case it doesn't already exist + + if (strcmp(v->fs_type, "yaffs2") == 0) { + // mount an MTD partition as a YAFFS2 filesystem. + mtd_scan_partitions(); + const MtdPartition* partition; + partition = mtd_find_partition_by_name(v->device); + if (partition == NULL) { + LOGE("failed to find \"%s\" partition to mount at \"%s\"\n", + v->device, v->mount_point); + return -1; + } + return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0); + } else if (strcmp(v->fs_type, "ext4") == 0 || + strcmp(v->fs_type, "vfat") == 0) { + result = mount(v->device, v->mount_point, v->fs_type, + MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); + if (result == 0) return 0; + + if (v->device2) { + LOGW("failed to mount %s (%s); trying %s\n", + v->device, strerror(errno), v->device2); + result = mount(v->device2, v->mount_point, v->fs_type, + MS_NOATIME | MS_NODEV | MS_NODIRATIME, ""); + if (result == 0) return 0; + } + + LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno)); + return -1; + } + + LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point); + return -1; +} + +int ensure_path_unmounted(const char* path) { + Volume* v = volume_for_path(path); + if (v == NULL) { + LOGE("unknown volume for path [%s]\n", path); + return -1; + } + if (strcmp(v->fs_type, "ramdisk") == 0) { + // the ramdisk is always mounted; you can't unmount it. + return -1; + } + + int result; + result = scan_mounted_volumes(); + if (result < 0) { + LOGE("failed to scan mounted volumes\n"); + return -1; + } + + const MountedVolume* mv = + find_mounted_volume_by_mount_point(v->mount_point); + if (mv == NULL) { + // volume is already unmounted + return 0; + } + + return unmount_mounted_volume(mv); +} + +int format_volume(const char* volume) { + Volume* v = volume_for_path(volume); + if (v == NULL) { + LOGE("unknown volume \"%s\"\n", volume); + return -1; + } + if (strcmp(v->fs_type, "ramdisk") == 0) { + // you can't format the ramdisk. + LOGE("can't format_volume \"%s\"", volume); + return -1; + } + if (strcmp(v->mount_point, volume) != 0) { + LOGE("can't give path \"%s\" to format_volume\n", volume); + return -1; + } + + if (ensure_path_unmounted(volume) != 0) { + LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point); + return -1; + } + + if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) { + mtd_scan_partitions(); + const MtdPartition* partition = mtd_find_partition_by_name(v->device); + if (partition == NULL) { + LOGE("format_volume: no MTD partition \"%s\"\n", v->device); + return -1; + } + + MtdWriteContext *write = mtd_write_partition(partition); + if (write == NULL) { + LOGW("format_volume: can't open MTD \"%s\"\n", v->device); + return -1; + } else if (mtd_erase_blocks(write, -1) == (off_t) -1) { + LOGW("format_volume: can't erase MTD \"%s\"\n", v->device); + mtd_write_close(write); + return -1; + } else if (mtd_write_close(write)) { + LOGW("format_volume: can't close MTD \"%s\"\n", v->device); + return -1; + } + return 0; + } + + if (strcmp(v->fs_type, "ext4") == 0) { + int result = make_ext4fs(v->device, v->length); + if (result != 0) { + LOGE("format_volume: make_extf4fs failed on %s\n", v->device); + return -1; + } + return 0; + } + + LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type); + return -1; +} diff --git a/roots.h b/roots.h index cf59bfdf3..8abe18fb7 100644 --- a/roots.h +++ b/roots.h @@ -19,6 +19,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); @@ -38,4 +42,8 @@ int ensure_path_unmounted(const char* path); // it is mounted. int format_volume(const char* volume); +#ifdef __cplusplus +} +#endif + #endif // RECOVERY_ROOTS_H_ diff --git a/ui.c b/ui.c deleted file mode 100644 index 25df3d043..000000000 --- a/ui.c +++ /dev/null @@ -1,664 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common.h" -#include -#include "minui/minui.h" -#include "recovery_ui.h" - -#define MAX_COLS 96 -#define MAX_ROWS 32 - -#define CHAR_WIDTH 10 -#define CHAR_HEIGHT 18 - -#define UI_WAIT_KEY_TIMEOUT_SEC 120 - -UIParameters ui_parameters = { - 6, // indeterminate progress bar frames - 20, // fps - 7, // installation icon frames (0 == static image) - 13, 190, // installation icon overlay offset -}; - -static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER; -static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS]; -static gr_surface *gInstallationOverlay; -static gr_surface *gProgressBarIndeterminate; -static gr_surface gProgressBarEmpty; -static gr_surface gProgressBarFill; - -static const struct { gr_surface* surface; const char *name; } BITMAPS[] = { - { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" }, - { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" }, - { &gProgressBarEmpty, "progress_empty" }, - { &gProgressBarFill, "progress_fill" }, - { NULL, NULL }, -}; - -static int gCurrentIcon = 0; -static int gInstallingFrame = 0; - -static enum ProgressBarType { - PROGRESSBAR_TYPE_NONE, - PROGRESSBAR_TYPE_INDETERMINATE, - PROGRESSBAR_TYPE_NORMAL, -} gProgressBarType = PROGRESSBAR_TYPE_NONE; - -// Progress bar scope of current operation -static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0; -static double gProgressScopeTime, gProgressScopeDuration; - -// Set to 1 when both graphics pages are the same (except for the progress bar) -static int gPagesIdentical = 0; - -// Log text overlay, displayed when a magic key is pressed -static char text[MAX_ROWS][MAX_COLS]; -static int text_cols = 0, text_rows = 0; -static int text_col = 0, text_row = 0, text_top = 0; -static int show_text = 0; -static int show_text_ever = 0; // has show_text ever been 1? - -static char menu[MAX_ROWS][MAX_COLS]; -static int show_menu = 0; -static int menu_top = 0, menu_items = 0, menu_sel = 0; - -// Key event input queue -static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER; -static int key_queue[256], key_queue_len = 0; -static volatile char key_pressed[KEY_MAX + 1]; - -// Return the current time as a double (including fractions of a second). -static double now() { - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec + tv.tv_usec / 1000000.0; -} - -// Draw the given frame over the installation overlay animation. The -// background is not cleared or draw with the base icon first; we -// assume that the frame already contains some other frame of the -// animation. Does nothing if no overlay animation is defined. -// Should only be called with gUpdateMutex locked. -static void draw_install_overlay_locked(int frame) { - if (gInstallationOverlay == NULL) return; - gr_surface surface = gInstallationOverlay[frame]; - int iconWidth = gr_get_width(surface); - int iconHeight = gr_get_height(surface); - gr_blit(surface, 0, 0, iconWidth, iconHeight, - ui_parameters.install_overlay_offset_x, - ui_parameters.install_overlay_offset_y); -} - -// Clear the screen and draw the currently selected background icon (if any). -// Should only be called with gUpdateMutex locked. -static void draw_background_locked(int icon) -{ - gPagesIdentical = 0; - gr_color(0, 0, 0, 255); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - if (icon) { - gr_surface surface = gBackgroundIcon[icon]; - int iconWidth = gr_get_width(surface); - int iconHeight = gr_get_height(surface); - int iconX = (gr_fb_width() - iconWidth) / 2; - int iconY = (gr_fb_height() - iconHeight) / 2; - gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); - if (icon == BACKGROUND_ICON_INSTALLING) { - draw_install_overlay_locked(gInstallingFrame); - } - } -} - -// Draw the progress bar (if any) on the screen. Does not flip pages. -// Should only be called with gUpdateMutex locked. -static void draw_progress_locked() -{ - if (gCurrentIcon == BACKGROUND_ICON_INSTALLING) { - draw_install_overlay_locked(gInstallingFrame); - } - - if (gProgressBarType != PROGRESSBAR_TYPE_NONE) { - int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]); - int width = gr_get_width(gProgressBarEmpty); - int height = gr_get_height(gProgressBarEmpty); - - int dx = (gr_fb_width() - width)/2; - int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; - - // Erase behind the progress bar (in case this was a progress-only update) - gr_color(0, 0, 0, 255); - gr_fill(dx, dy, width, height); - - if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) { - float progress = gProgressScopeStart + gProgress * gProgressScopeSize; - int pos = (int) (progress * width); - - if (pos > 0) { - gr_blit(gProgressBarFill, 0, 0, pos, height, dx, dy); - } - if (pos < width-1) { - gr_blit(gProgressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); - } - } - - if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) { - static int frame = 0; - gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy); - frame = (frame + 1) % ui_parameters.indeterminate_frames; - } - } -} - -static void draw_text_line(int row, const char* t) { - if (t[0] != '\0') { - gr_text(0, (row+1)*CHAR_HEIGHT-1, t); - } -} - -// Redraw everything on the screen. Does not flip pages. -// Should only be called with gUpdateMutex locked. -static void draw_screen_locked(void) -{ - draw_background_locked(gCurrentIcon); - draw_progress_locked(); - - if (show_text) { - gr_color(0, 0, 0, 160); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - int i = 0; - if (show_menu) { - gr_color(64, 96, 255, 255); - gr_fill(0, (menu_top+menu_sel) * CHAR_HEIGHT, - gr_fb_width(), (menu_top+menu_sel+1)*CHAR_HEIGHT+1); - - for (; i < menu_top + menu_items; ++i) { - if (i == menu_top + menu_sel) { - gr_color(255, 255, 255, 255); - draw_text_line(i, menu[i]); - gr_color(64, 96, 255, 255); - } else { - draw_text_line(i, menu[i]); - } - } - gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1, - gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1); - ++i; - } - - gr_color(255, 255, 0, 255); - - for (; i < text_rows; ++i) { - draw_text_line(i, text[(i+text_top) % text_rows]); - } - } -} - -// Redraw everything on the screen and flip the screen (make it visible). -// Should only be called with gUpdateMutex locked. -static void update_screen_locked(void) -{ - draw_screen_locked(); - gr_flip(); -} - -// Updates only the progress bar, if possible, otherwise redraws the screen. -// Should only be called with gUpdateMutex locked. -static void update_progress_locked(void) -{ - if (show_text || !gPagesIdentical) { - draw_screen_locked(); // Must redraw the whole screen - gPagesIdentical = 1; - } else { - draw_progress_locked(); // Draw only the progress bar and overlays - } - gr_flip(); -} - -// Keeps the progress bar updated, even when the process is otherwise busy. -static void *progress_thread(void *cookie) -{ - double interval = 1.0 / ui_parameters.update_fps; - for (;;) { - double start = now(); - pthread_mutex_lock(&gUpdateMutex); - - int redraw = 0; - - // update the installation animation, if active - // skip this if we have a text overlay (too expensive to update) - if (gCurrentIcon == BACKGROUND_ICON_INSTALLING && - ui_parameters.installing_frames > 0 && - !show_text) { - gInstallingFrame = - (gInstallingFrame + 1) % ui_parameters.installing_frames; - redraw = 1; - } - - // update the progress bar animation, if active - // skip this if we have a text overlay (too expensive to update) - if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) { - redraw = 1; - } - - // move the progress bar forward on timed intervals, if configured - int duration = gProgressScopeDuration; - if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) { - double elapsed = now() - gProgressScopeTime; - float progress = 1.0 * elapsed / duration; - if (progress > 1.0) progress = 1.0; - if (progress > gProgress) { - gProgress = progress; - redraw = 1; - } - } - - if (redraw) update_progress_locked(); - - pthread_mutex_unlock(&gUpdateMutex); - double end = now(); - // minimum of 20ms delay between frames - double delay = interval - (end-start); - if (delay < 0.02) delay = 0.02; - usleep((long)(delay * 1000000)); - } - return NULL; -} - -static int rel_sum = 0; - -static int input_callback(int fd, short revents, void *data) -{ - struct input_event ev; - int ret; - int fake_key = 0; - - ret = ev_get_input(fd, revents, &ev); - if (ret) - return -1; - - if (ev.type == EV_SYN) { - return 0; - } else if (ev.type == EV_REL) { - if (ev.code == REL_Y) { - // accumulate the up or down motion reported by - // the trackball. When it exceeds a threshold - // (positive or negative), fake an up/down - // key event. - rel_sum += ev.value; - if (rel_sum > 3) { - fake_key = 1; - ev.type = EV_KEY; - ev.code = KEY_DOWN; - ev.value = 1; - rel_sum = 0; - } else if (rel_sum < -3) { - fake_key = 1; - ev.type = EV_KEY; - ev.code = KEY_UP; - ev.value = 1; - rel_sum = 0; - } - } - } else { - rel_sum = 0; - } - - if (ev.type != EV_KEY || ev.code > KEY_MAX) - return 0; - - pthread_mutex_lock(&key_queue_mutex); - if (!fake_key) { - // our "fake" keys only report a key-down event (no - // key-up), so don't record them in the key_pressed - // table. - key_pressed[ev.code] = ev.value; - } - const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); - if (ev.value > 0 && key_queue_len < queue_max) { - key_queue[key_queue_len++] = ev.code; - pthread_cond_signal(&key_queue_cond); - } - pthread_mutex_unlock(&key_queue_mutex); - - if (ev.value > 0 && device_toggle_display(key_pressed, ev.code)) { - pthread_mutex_lock(&gUpdateMutex); - show_text = !show_text; - if (show_text) show_text_ever = 1; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); - } - - if (ev.value > 0 && device_reboot_now(key_pressed, ev.code)) { - android_reboot(ANDROID_RB_RESTART, 0, 0); - } - - return 0; -} - -// Reads input events, handles special hot keys, and adds to the key queue. -static void *input_thread(void *cookie) -{ - for (;;) { - if (!ev_wait(-1)) - ev_dispatch(); - } - return NULL; -} - -void ui_init(void) -{ - gr_init(); - ev_init(input_callback, NULL); - - text_col = text_row = 0; - text_rows = gr_fb_height() / CHAR_HEIGHT; - if (text_rows > MAX_ROWS) text_rows = MAX_ROWS; - text_top = 1; - - text_cols = gr_fb_width() / CHAR_WIDTH; - if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1; - - int i; - for (i = 0; BITMAPS[i].name != NULL; ++i) { - int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface); - if (result < 0) { - LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result); - } - } - - gProgressBarIndeterminate = malloc(ui_parameters.indeterminate_frames * - sizeof(gr_surface)); - for (i = 0; i < ui_parameters.indeterminate_frames; ++i) { - char filename[40]; - // "indeterminate01.png", "indeterminate02.png", ... - sprintf(filename, "indeterminate%02d", i+1); - int result = res_create_surface(filename, gProgressBarIndeterminate+i); - if (result < 0) { - LOGE("Missing bitmap %s\n(Code %d)\n", filename, result); - } - } - - if (ui_parameters.installing_frames > 0) { - gInstallationOverlay = malloc(ui_parameters.installing_frames * - sizeof(gr_surface)); - for (i = 0; i < ui_parameters.installing_frames; ++i) { - char filename[40]; - // "icon_installing_overlay01.png", - // "icon_installing_overlay02.png", ... - sprintf(filename, "icon_installing_overlay%02d", i+1); - int result = res_create_surface(filename, gInstallationOverlay+i); - if (result < 0) { - LOGE("Missing bitmap %s\n(Code %d)\n", filename, result); - } - } - - // Adjust the offset to account for the positioning of the - // base image on the screen. - if (gBackgroundIcon[BACKGROUND_ICON_INSTALLING] != NULL) { - gr_surface bg = gBackgroundIcon[BACKGROUND_ICON_INSTALLING]; - ui_parameters.install_overlay_offset_x += - (gr_fb_width() - gr_get_width(bg)) / 2; - ui_parameters.install_overlay_offset_y += - (gr_fb_height() - gr_get_height(bg)) / 2; - } - } else { - gInstallationOverlay = NULL; - } - - pthread_t t; - pthread_create(&t, NULL, progress_thread, NULL); - pthread_create(&t, NULL, input_thread, NULL); -} - -void ui_set_background(int icon) -{ - pthread_mutex_lock(&gUpdateMutex); - gCurrentIcon = icon; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_show_indeterminate_progress() -{ - pthread_mutex_lock(&gUpdateMutex); - if (gProgressBarType != PROGRESSBAR_TYPE_INDETERMINATE) { - gProgressBarType = PROGRESSBAR_TYPE_INDETERMINATE; - update_progress_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_show_progress(float portion, int seconds) -{ - pthread_mutex_lock(&gUpdateMutex); - gProgressBarType = PROGRESSBAR_TYPE_NORMAL; - gProgressScopeStart += gProgressScopeSize; - gProgressScopeSize = portion; - gProgressScopeTime = now(); - gProgressScopeDuration = seconds; - gProgress = 0; - update_progress_locked(); - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_set_progress(float fraction) -{ - pthread_mutex_lock(&gUpdateMutex); - if (fraction < 0.0) fraction = 0.0; - if (fraction > 1.0) fraction = 1.0; - if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && fraction > gProgress) { - // Skip updates that aren't visibly different. - int width = gr_get_width(gProgressBarIndeterminate[0]); - float scale = width * gProgressScopeSize; - if ((int) (gProgress * scale) != (int) (fraction * scale)) { - gProgress = fraction; - update_progress_locked(); - } - } - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_reset_progress() -{ - pthread_mutex_lock(&gUpdateMutex); - gProgressBarType = PROGRESSBAR_TYPE_NONE; - gProgressScopeStart = gProgressScopeSize = 0; - gProgressScopeTime = gProgressScopeDuration = 0; - gProgress = 0; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_print(const char *fmt, ...) -{ - char buf[256]; - va_list ap; - va_start(ap, fmt); - vsnprintf(buf, 256, fmt, ap); - va_end(ap); - - fputs(buf, stdout); - - // This can get called before ui_init(), so be careful. - pthread_mutex_lock(&gUpdateMutex); - if (text_rows > 0 && text_cols > 0) { - char *ptr; - for (ptr = buf; *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col >= text_cols) { - text[text_row][text_col] = '\0'; - text_col = 0; - text_row = (text_row + 1) % text_rows; - if (text_row == text_top) text_top = (text_top + 1) % text_rows; - } - if (*ptr != '\n') text[text_row][text_col++] = *ptr; - } - text[text_row][text_col] = '\0'; - update_screen_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); -} - -void ui_start_menu(char** headers, char** items, int initial_selection) { - int i; - pthread_mutex_lock(&gUpdateMutex); - if (text_rows > 0 && text_cols > 0) { - for (i = 0; i < text_rows; ++i) { - if (headers[i] == NULL) break; - strncpy(menu[i], headers[i], text_cols-1); - menu[i][text_cols-1] = '\0'; - } - menu_top = i; - for (; i < text_rows; ++i) { - if (items[i-menu_top] == NULL) break; - strncpy(menu[i], items[i-menu_top], text_cols-1); - menu[i][text_cols-1] = '\0'; - } - menu_items = i - menu_top; - show_menu = 1; - menu_sel = initial_selection; - update_screen_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); -} - -int ui_menu_select(int sel) { - int old_sel; - pthread_mutex_lock(&gUpdateMutex); - if (show_menu > 0) { - old_sel = menu_sel; - menu_sel = sel; - if (menu_sel < 0) menu_sel = 0; - if (menu_sel >= menu_items) menu_sel = menu_items-1; - sel = menu_sel; - if (menu_sel != old_sel) update_screen_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); - return sel; -} - -void ui_end_menu() { - int i; - pthread_mutex_lock(&gUpdateMutex); - if (show_menu > 0 && text_rows > 0 && text_cols > 0) { - show_menu = 0; - update_screen_locked(); - } - pthread_mutex_unlock(&gUpdateMutex); -} - -int ui_text_visible() -{ - pthread_mutex_lock(&gUpdateMutex); - int visible = show_text; - pthread_mutex_unlock(&gUpdateMutex); - return visible; -} - -int ui_text_ever_visible() -{ - pthread_mutex_lock(&gUpdateMutex); - int ever_visible = show_text_ever; - pthread_mutex_unlock(&gUpdateMutex); - return ever_visible; -} - -void ui_show_text(int visible) -{ - pthread_mutex_lock(&gUpdateMutex); - show_text = visible; - if (show_text) show_text_ever = 1; - update_screen_locked(); - pthread_mutex_unlock(&gUpdateMutex); -} - -// Return true if USB is connected. -static int usb_connected() { - int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); - if (fd < 0) { - printf("failed to open /sys/class/android_usb/android0/state: %s\n", - strerror(errno)); - return 0; - } - - char buf; - /* USB is connected if android_usb state is CONNECTED or CONFIGURED */ - int connected = (read(fd, &buf, 1) == 1) && (buf == 'C'); - if (close(fd) < 0) { - printf("failed to close /sys/class/android_usb/android0/state: %s\n", - strerror(errno)); - } - return connected; -} - -int ui_wait_key() -{ - pthread_mutex_lock(&key_queue_mutex); - - // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is - // plugged in. - do { - struct timeval now; - struct timespec timeout; - gettimeofday(&now, NULL); - timeout.tv_sec = now.tv_sec; - timeout.tv_nsec = now.tv_usec * 1000; - timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; - - int rc = 0; - while (key_queue_len == 0 && rc != ETIMEDOUT) { - rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, - &timeout); - } - } while (usb_connected() && key_queue_len == 0); - - int key = -1; - if (key_queue_len > 0) { - key = key_queue[0]; - memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); - } - pthread_mutex_unlock(&key_queue_mutex); - return key; -} - -int ui_key_pressed(int key) -{ - // This is a volatile static array, don't bother locking - return key_pressed[key]; -} - -void ui_clear_key_queue() { - pthread_mutex_lock(&key_queue_mutex); - key_queue_len = 0; - pthread_mutex_unlock(&key_queue_mutex); -} diff --git a/ui.cpp b/ui.cpp new file mode 100644 index 000000000..657a01ece --- /dev/null +++ b/ui.cpp @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include +#include "minui/minui.h" +#include "recovery_ui.h" +#include "ui.h" + +#define MAX_COLS 96 +#define MAX_ROWS 32 + +#define CHAR_WIDTH 10 +#define CHAR_HEIGHT 18 + +#define UI_WAIT_KEY_TIMEOUT_SEC 120 + +UIParameters ui_parameters = { + 6, // indeterminate progress bar frames + 20, // fps + 7, // installation icon frames (0 == static image) + 13, 190, // installation icon overlay offset +}; + +static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER; +static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS]; +static gr_surface *gInstallationOverlay; +static gr_surface *gProgressBarIndeterminate; +static gr_surface gProgressBarEmpty; +static gr_surface gProgressBarFill; + +static const struct { gr_surface* surface; const char *name; } BITMAPS[] = { + { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" }, + { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" }, + { &gProgressBarEmpty, "progress_empty" }, + { &gProgressBarFill, "progress_fill" }, + { NULL, NULL }, +}; + +static int gCurrentIcon = 0; +static int gInstallingFrame = 0; + +static enum ProgressBarType { + PROGRESSBAR_TYPE_NONE, + PROGRESSBAR_TYPE_INDETERMINATE, + PROGRESSBAR_TYPE_NORMAL, +} gProgressBarType = PROGRESSBAR_TYPE_NONE; + +// Progress bar scope of current operation +static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0; +static double gProgressScopeTime, gProgressScopeDuration; + +// Set to 1 when both graphics pages are the same (except for the progress bar) +static int gPagesIdentical = 0; + +// Log text overlay, displayed when a magic key is pressed +static char text[MAX_ROWS][MAX_COLS]; +static int text_cols = 0, text_rows = 0; +static int text_col = 0, text_row = 0, text_top = 0; +static int show_text = 0; +static int show_text_ever = 0; // has show_text ever been 1? + +static char menu[MAX_ROWS][MAX_COLS]; +static int show_menu = 0; +static int menu_top = 0, menu_items = 0, menu_sel = 0; + +// Key event input queue +static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER; +static int key_queue[256], key_queue_len = 0; +static volatile char key_pressed[KEY_MAX + 1]; + +// Return the current time as a double (including fractions of a second). +static double now() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +// Draw the given frame over the installation overlay animation. The +// background is not cleared or draw with the base icon first; we +// assume that the frame already contains some other frame of the +// animation. Does nothing if no overlay animation is defined. +// Should only be called with gUpdateMutex locked. +static void draw_install_overlay_locked(int frame) { + if (gInstallationOverlay == NULL) return; + gr_surface surface = gInstallationOverlay[frame]; + int iconWidth = gr_get_width(surface); + int iconHeight = gr_get_height(surface); + gr_blit(surface, 0, 0, iconWidth, iconHeight, + ui_parameters.install_overlay_offset_x, + ui_parameters.install_overlay_offset_y); +} + +// Clear the screen and draw the currently selected background icon (if any). +// Should only be called with gUpdateMutex locked. +static void draw_background_locked(int icon) +{ + gPagesIdentical = 0; + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (icon) { + gr_surface surface = gBackgroundIcon[icon]; + int iconWidth = gr_get_width(surface); + int iconHeight = gr_get_height(surface); + int iconX = (gr_fb_width() - iconWidth) / 2; + int iconY = (gr_fb_height() - iconHeight) / 2; + gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); + if (icon == BACKGROUND_ICON_INSTALLING) { + draw_install_overlay_locked(gInstallingFrame); + } + } +} + +// Draw the progress bar (if any) on the screen. Does not flip pages. +// Should only be called with gUpdateMutex locked. +static void draw_progress_locked() +{ + if (gCurrentIcon == BACKGROUND_ICON_INSTALLING) { + draw_install_overlay_locked(gInstallingFrame); + } + + if (gProgressBarType != PROGRESSBAR_TYPE_NONE) { + int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]); + int width = gr_get_width(gProgressBarEmpty); + int height = gr_get_height(gProgressBarEmpty); + + int dx = (gr_fb_width() - width)/2; + int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; + + // Erase behind the progress bar (in case this was a progress-only update) + gr_color(0, 0, 0, 255); + gr_fill(dx, dy, width, height); + + if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) { + float progress = gProgressScopeStart + gProgress * gProgressScopeSize; + int pos = (int) (progress * width); + + if (pos > 0) { + gr_blit(gProgressBarFill, 0, 0, pos, height, dx, dy); + } + if (pos < width-1) { + gr_blit(gProgressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); + } + } + + if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) { + static int frame = 0; + gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy); + frame = (frame + 1) % ui_parameters.indeterminate_frames; + } + } +} + +static void draw_text_line(int row, const char* t) { + if (t[0] != '\0') { + gr_text(0, (row+1)*CHAR_HEIGHT-1, t); + } +} + +// Redraw everything on the screen. Does not flip pages. +// Should only be called with gUpdateMutex locked. +static void draw_screen_locked(void) +{ + draw_background_locked(gCurrentIcon); + draw_progress_locked(); + + if (show_text) { + gr_color(0, 0, 0, 160); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + int i = 0; + if (show_menu) { + gr_color(64, 96, 255, 255); + gr_fill(0, (menu_top+menu_sel) * CHAR_HEIGHT, + gr_fb_width(), (menu_top+menu_sel+1)*CHAR_HEIGHT+1); + + for (; i < menu_top + menu_items; ++i) { + if (i == menu_top + menu_sel) { + gr_color(255, 255, 255, 255); + draw_text_line(i, menu[i]); + gr_color(64, 96, 255, 255); + } else { + draw_text_line(i, menu[i]); + } + } + gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1, + gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1); + ++i; + } + + gr_color(255, 255, 0, 255); + + for (; i < text_rows; ++i) { + draw_text_line(i, text[(i+text_top) % text_rows]); + } + } +} + +// Redraw everything on the screen and flip the screen (make it visible). +// Should only be called with gUpdateMutex locked. +static void update_screen_locked(void) +{ + draw_screen_locked(); + gr_flip(); +} + +// Updates only the progress bar, if possible, otherwise redraws the screen. +// Should only be called with gUpdateMutex locked. +static void update_progress_locked(void) +{ + if (show_text || !gPagesIdentical) { + draw_screen_locked(); // Must redraw the whole screen + gPagesIdentical = 1; + } else { + draw_progress_locked(); // Draw only the progress bar and overlays + } + gr_flip(); +} + +// Keeps the progress bar updated, even when the process is otherwise busy. +static void *progress_thread(void *cookie) +{ + double interval = 1.0 / ui_parameters.update_fps; + for (;;) { + double start = now(); + pthread_mutex_lock(&gUpdateMutex); + + int redraw = 0; + + // update the installation animation, if active + // skip this if we have a text overlay (too expensive to update) + if (gCurrentIcon == BACKGROUND_ICON_INSTALLING && + ui_parameters.installing_frames > 0 && + !show_text) { + gInstallingFrame = + (gInstallingFrame + 1) % ui_parameters.installing_frames; + redraw = 1; + } + + // update the progress bar animation, if active + // skip this if we have a text overlay (too expensive to update) + if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) { + redraw = 1; + } + + // move the progress bar forward on timed intervals, if configured + int duration = gProgressScopeDuration; + if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) { + double elapsed = now() - gProgressScopeTime; + float progress = 1.0 * elapsed / duration; + if (progress > 1.0) progress = 1.0; + if (progress > gProgress) { + gProgress = progress; + redraw = 1; + } + } + + if (redraw) update_progress_locked(); + + pthread_mutex_unlock(&gUpdateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end-start); + if (delay < 0.02) delay = 0.02; + usleep((long)(delay * 1000000)); + } + return NULL; +} + +static int rel_sum = 0; + +static int input_callback(int fd, short revents, void *data) +{ + struct input_event ev; + int ret; + int fake_key = 0; + + ret = ev_get_input(fd, revents, &ev); + if (ret) + return -1; + + if (ev.type == EV_SYN) { + return 0; + } else if (ev.type == EV_REL) { + if (ev.code == REL_Y) { + // accumulate the up or down motion reported by + // the trackball. When it exceeds a threshold + // (positive or negative), fake an up/down + // key event. + rel_sum += ev.value; + if (rel_sum > 3) { + fake_key = 1; + ev.type = EV_KEY; + ev.code = KEY_DOWN; + ev.value = 1; + rel_sum = 0; + } else if (rel_sum < -3) { + fake_key = 1; + ev.type = EV_KEY; + ev.code = KEY_UP; + ev.value = 1; + rel_sum = 0; + } + } + } else { + rel_sum = 0; + } + + if (ev.type != EV_KEY || ev.code > KEY_MAX) + return 0; + + pthread_mutex_lock(&key_queue_mutex); + if (!fake_key) { + // our "fake" keys only report a key-down event (no + // key-up), so don't record them in the key_pressed + // table. + key_pressed[ev.code] = ev.value; + } + const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); + if (ev.value > 0 && key_queue_len < queue_max) { + key_queue[key_queue_len++] = ev.code; + pthread_cond_signal(&key_queue_cond); + } + pthread_mutex_unlock(&key_queue_mutex); + + if (ev.value > 0 && device_toggle_display(key_pressed, ev.code)) { + pthread_mutex_lock(&gUpdateMutex); + show_text = !show_text; + if (show_text) show_text_ever = 1; + update_screen_locked(); + pthread_mutex_unlock(&gUpdateMutex); + } + + if (ev.value > 0 && device_reboot_now(key_pressed, ev.code)) { + android_reboot(ANDROID_RB_RESTART, 0, 0); + } + + return 0; +} + +// Reads input events, handles special hot keys, and adds to the key queue. +static void *input_thread(void *cookie) +{ + for (;;) { + if (!ev_wait(-1)) + ev_dispatch(); + } + return NULL; +} + +void ui_init(void) +{ + gr_init(); + ev_init(input_callback, NULL); + + text_col = text_row = 0; + text_rows = gr_fb_height() / CHAR_HEIGHT; + if (text_rows > MAX_ROWS) text_rows = MAX_ROWS; + text_top = 1; + + text_cols = gr_fb_width() / CHAR_WIDTH; + if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1; + + int i; + for (i = 0; BITMAPS[i].name != NULL; ++i) { + int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface); + if (result < 0) { + LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result); + } + } + + gProgressBarIndeterminate = (gr_surface*)malloc(ui_parameters.indeterminate_frames * + sizeof(gr_surface)); + for (i = 0; i < ui_parameters.indeterminate_frames; ++i) { + char filename[40]; + // "indeterminate01.png", "indeterminate02.png", ... + sprintf(filename, "indeterminate%02d", i+1); + int result = res_create_surface(filename, gProgressBarIndeterminate+i); + if (result < 0) { + LOGE("Missing bitmap %s\n(Code %d)\n", filename, result); + } + } + + if (ui_parameters.installing_frames > 0) { + gInstallationOverlay = (gr_surface*)malloc(ui_parameters.installing_frames * + sizeof(gr_surface)); + for (i = 0; i < ui_parameters.installing_frames; ++i) { + char filename[40]; + // "icon_installing_overlay01.png", + // "icon_installing_overlay02.png", ... + sprintf(filename, "icon_installing_overlay%02d", i+1); + int result = res_create_surface(filename, gInstallationOverlay+i); + if (result < 0) { + LOGE("Missing bitmap %s\n(Code %d)\n", filename, result); + } + } + + // Adjust the offset to account for the positioning of the + // base image on the screen. + if (gBackgroundIcon[BACKGROUND_ICON_INSTALLING] != NULL) { + gr_surface bg = gBackgroundIcon[BACKGROUND_ICON_INSTALLING]; + ui_parameters.install_overlay_offset_x += + (gr_fb_width() - gr_get_width(bg)) / 2; + ui_parameters.install_overlay_offset_y += + (gr_fb_height() - gr_get_height(bg)) / 2; + } + } else { + gInstallationOverlay = NULL; + } + + pthread_t t; + pthread_create(&t, NULL, progress_thread, NULL); + pthread_create(&t, NULL, input_thread, NULL); +} + +void ui_set_background(int icon) +{ + pthread_mutex_lock(&gUpdateMutex); + gCurrentIcon = icon; + update_screen_locked(); + pthread_mutex_unlock(&gUpdateMutex); +} + +void ui_show_indeterminate_progress() +{ + pthread_mutex_lock(&gUpdateMutex); + if (gProgressBarType != PROGRESSBAR_TYPE_INDETERMINATE) { + gProgressBarType = PROGRESSBAR_TYPE_INDETERMINATE; + update_progress_locked(); + } + pthread_mutex_unlock(&gUpdateMutex); +} + +void ui_show_progress(float portion, int seconds) +{ + pthread_mutex_lock(&gUpdateMutex); + gProgressBarType = PROGRESSBAR_TYPE_NORMAL; + gProgressScopeStart += gProgressScopeSize; + gProgressScopeSize = portion; + gProgressScopeTime = now(); + gProgressScopeDuration = seconds; + gProgress = 0; + update_progress_locked(); + pthread_mutex_unlock(&gUpdateMutex); +} + +void ui_set_progress(float fraction) +{ + pthread_mutex_lock(&gUpdateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && fraction > gProgress) { + // Skip updates that aren't visibly different. + int width = gr_get_width(gProgressBarIndeterminate[0]); + float scale = width * gProgressScopeSize; + if ((int) (gProgress * scale) != (int) (fraction * scale)) { + gProgress = fraction; + update_progress_locked(); + } + } + pthread_mutex_unlock(&gUpdateMutex); +} + +void ui_reset_progress() +{ + pthread_mutex_lock(&gUpdateMutex); + gProgressBarType = PROGRESSBAR_TYPE_NONE; + gProgressScopeStart = gProgressScopeSize = 0; + gProgressScopeTime = gProgressScopeDuration = 0; + gProgress = 0; + update_screen_locked(); + pthread_mutex_unlock(&gUpdateMutex); +} + +void ui_print(const char *fmt, ...) +{ + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + fputs(buf, stdout); + + // This can get called before ui_init(), so be careful. + pthread_mutex_lock(&gUpdateMutex); + if (text_rows > 0 && text_cols > 0) { + char *ptr; + for (ptr = buf; *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col >= text_cols) { + text[text_row][text_col] = '\0'; + text_col = 0; + text_row = (text_row + 1) % text_rows; + if (text_row == text_top) text_top = (text_top + 1) % text_rows; + } + if (*ptr != '\n') text[text_row][text_col++] = *ptr; + } + text[text_row][text_col] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&gUpdateMutex); +} + +void ui_start_menu(const char* const * headers, const char* const * items, + int initial_selection) { + int i; + pthread_mutex_lock(&gUpdateMutex); + if (text_rows > 0 && text_cols > 0) { + for (i = 0; i < text_rows; ++i) { + if (headers[i] == NULL) break; + strncpy(menu[i], headers[i], text_cols-1); + menu[i][text_cols-1] = '\0'; + } + menu_top = i; + for (; i < text_rows; ++i) { + if (items[i-menu_top] == NULL) break; + strncpy(menu[i], items[i-menu_top], text_cols-1); + menu[i][text_cols-1] = '\0'; + } + menu_items = i - menu_top; + show_menu = 1; + menu_sel = initial_selection; + update_screen_locked(); + } + pthread_mutex_unlock(&gUpdateMutex); +} + +int ui_menu_select(int sel) { + int old_sel; + pthread_mutex_lock(&gUpdateMutex); + if (show_menu > 0) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items-1; + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&gUpdateMutex); + return sel; +} + +void ui_end_menu() { + int i; + pthread_mutex_lock(&gUpdateMutex); + if (show_menu > 0 && text_rows > 0 && text_cols > 0) { + show_menu = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&gUpdateMutex); +} + +int ui_text_visible() +{ + pthread_mutex_lock(&gUpdateMutex); + int visible = show_text; + pthread_mutex_unlock(&gUpdateMutex); + return visible; +} + +int ui_text_ever_visible() +{ + pthread_mutex_lock(&gUpdateMutex); + int ever_visible = show_text_ever; + pthread_mutex_unlock(&gUpdateMutex); + return ever_visible; +} + +void ui_show_text(int visible) +{ + pthread_mutex_lock(&gUpdateMutex); + show_text = visible; + if (show_text) show_text_ever = 1; + update_screen_locked(); + pthread_mutex_unlock(&gUpdateMutex); +} + +// Return true if USB is connected. +static int usb_connected() { + int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); + if (fd < 0) { + printf("failed to open /sys/class/android_usb/android0/state: %s\n", + strerror(errno)); + return 0; + } + + char buf; + /* USB is connected if android_usb state is CONNECTED or CONFIGURED */ + int connected = (read(fd, &buf, 1) == 1) && (buf == 'C'); + if (close(fd) < 0) { + printf("failed to close /sys/class/android_usb/android0/state: %s\n", + strerror(errno)); + } + return connected; +} + +int ui_wait_key() +{ + pthread_mutex_lock(&key_queue_mutex); + + // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is + // plugged in. + do { + struct timeval now; + struct timespec timeout; + gettimeofday(&now, NULL); + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = now.tv_usec * 1000; + timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; + + int rc = 0; + while (key_queue_len == 0 && rc != ETIMEDOUT) { + rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, + &timeout); + } + } while (usb_connected() && key_queue_len == 0); + + int key = -1; + if (key_queue_len > 0) { + key = key_queue[0]; + memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + } + pthread_mutex_unlock(&key_queue_mutex); + return key; +} + +int ui_key_pressed(int key) +{ + // This is a volatile static array, don't bother locking + return key_pressed[key]; +} + +void ui_clear_key_queue() { + pthread_mutex_lock(&key_queue_mutex); + key_queue_len = 0; + pthread_mutex_unlock(&key_queue_mutex); +} diff --git a/ui.h b/ui.h new file mode 100644 index 000000000..fa7a53c19 --- /dev/null +++ b/ui.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_UI_H +#define RECOVERY_UI_H + +// Initialize the graphics system. +void ui_init(); + +// Use KEY_* codes from or KEY_DREAM_* from "minui/minui.h". +int ui_wait_key(); // waits for a key/button press, returns the code +int ui_key_pressed(int key); // returns >0 if the code is currently pressed +int ui_text_visible(); // returns >0 if text log is currently visible +int ui_text_ever_visible(); // returns >0 if text log was ever visible +void ui_show_text(int visible); +void ui_clear_key_queue(); + +// Write a message to the on-screen log shown with Alt-L (also to stderr). +// The screen is small, and users may need to report these messages to support, +// so keep the output short and not too cryptic. +void ui_print(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +// Display some header text followed by a menu of items, which appears +// at the top of the screen (in place of any scrolling ui_print() +// output, if necessary). +void ui_start_menu(const char* const * headers, const char* const * items, + int initial_selection); +// Set the menu highlight to the given index, and return it (capped to +// the range [0..numitems). +int ui_menu_select(int sel); +// End menu mode, resetting the text overlay so that ui_print() +// statements will be displayed. +void ui_end_menu(); + +// Set the icon (normally the only thing visible besides the progress bar). +enum { + BACKGROUND_ICON_NONE, + BACKGROUND_ICON_INSTALLING, + BACKGROUND_ICON_ERROR, + NUM_BACKGROUND_ICONS +}; +void ui_set_background(int icon); + +// Show a progress bar and define the scope of the next operation: +// portion - fraction of the progress bar the next operation will use +// seconds - expected time interval (progress bar moves at this minimum rate) +void ui_show_progress(float portion, int seconds); +void ui_set_progress(float fraction); // 0.0 - 1.0 within the defined scope + +// Default allocation of progress bar segments to operations +static const int VERIFICATION_PROGRESS_TIME = 60; +static const float VERIFICATION_PROGRESS_FRACTION = 0.25; +static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; +static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; + +// Show a rotating "barberpole" for ongoing operations. Updates automatically. +void ui_show_indeterminate_progress(); + +// Hide and reset the progress bar. +void ui_reset_progress(); + +#endif // RECOVERY_UI_H diff --git a/verifier.c b/verifier.c deleted file mode 100644 index 729e085cf..000000000 --- a/verifier.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "common.h" -#include "verifier.h" - -#include "mincrypt/rsa.h" -#include "mincrypt/sha.h" - -#include -#include -#include - -// Look for an RSA signature embedded in the .ZIP file comment given -// the path to the zip. Verify it matches one of the given public -// keys. -// -// Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered -// or no key matches the signature). - -int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) { - ui_set_progress(0.0); - - FILE* f = fopen(path, "rb"); - if (f == NULL) { - LOGE("failed to open %s (%s)\n", path, strerror(errno)); - return VERIFY_FAILURE; - } - - // An archive with a whole-file signature will end in six bytes: - // - // (2-byte signature start) $ff $ff (2-byte comment size) - // - // (As far as the ZIP format is concerned, these are part of the - // archive comment.) We start by reading this footer, this tells - // us how far back from the end we have to start reading to find - // the whole comment. - -#define FOOTER_SIZE 6 - - if (fseek(f, -FOOTER_SIZE, SEEK_END) != 0) { - LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); - fclose(f); - return VERIFY_FAILURE; - } - - unsigned char footer[FOOTER_SIZE]; - if (fread(footer, 1, FOOTER_SIZE, f) != FOOTER_SIZE) { - LOGE("failed to read footer from %s (%s)\n", path, strerror(errno)); - fclose(f); - return VERIFY_FAILURE; - } - - if (footer[2] != 0xff || footer[3] != 0xff) { - fclose(f); - return VERIFY_FAILURE; - } - - int comment_size = footer[4] + (footer[5] << 8); - int signature_start = footer[0] + (footer[1] << 8); - LOGI("comment is %d bytes; signature %d bytes from end\n", - comment_size, signature_start); - - if (signature_start - FOOTER_SIZE < RSANUMBYTES) { - // "signature" block isn't big enough to contain an RSA block. - LOGE("signature is too short\n"); - fclose(f); - return VERIFY_FAILURE; - } - -#define EOCD_HEADER_SIZE 22 - - // The end-of-central-directory record is 22 bytes plus any - // comment length. - size_t eocd_size = comment_size + EOCD_HEADER_SIZE; - - if (fseek(f, -eocd_size, SEEK_END) != 0) { - LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); - fclose(f); - return VERIFY_FAILURE; - } - - // Determine how much of the file is covered by the signature. - // This is everything except the signature data and length, which - // includes all of the EOCD except for the comment length field (2 - // bytes) and the comment data. - size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2; - - unsigned char* eocd = malloc(eocd_size); - if (eocd == NULL) { - LOGE("malloc for EOCD record failed\n"); - fclose(f); - return VERIFY_FAILURE; - } - if (fread(eocd, 1, eocd_size, f) != eocd_size) { - LOGE("failed to read eocd from %s (%s)\n", path, strerror(errno)); - fclose(f); - return VERIFY_FAILURE; - } - - // If this is really is the EOCD record, it will begin with the - // magic number $50 $4b $05 $06. - if (eocd[0] != 0x50 || eocd[1] != 0x4b || - eocd[2] != 0x05 || eocd[3] != 0x06) { - LOGE("signature length doesn't match EOCD marker\n"); - fclose(f); - return VERIFY_FAILURE; - } - - int i; - for (i = 4; i < eocd_size-3; ++i) { - if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && - eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { - // if the sequence $50 $4b $05 $06 appears anywhere after - // the real one, minzip will find the later (wrong) one, - // which could be exploitable. Fail verification if - // this sequence occurs anywhere after the real one. - LOGE("EOCD marker occurs after start of EOCD\n"); - fclose(f); - return VERIFY_FAILURE; - } - } - -#define BUFFER_SIZE 4096 - - SHA_CTX ctx; - SHA_init(&ctx); - unsigned char* buffer = malloc(BUFFER_SIZE); - if (buffer == NULL) { - LOGE("failed to alloc memory for sha1 buffer\n"); - fclose(f); - return VERIFY_FAILURE; - } - - double frac = -1.0; - size_t so_far = 0; - fseek(f, 0, SEEK_SET); - while (so_far < signed_len) { - int size = BUFFER_SIZE; - if (signed_len - so_far < size) size = signed_len - so_far; - if (fread(buffer, 1, size, f) != size) { - LOGE("failed to read data from %s (%s)\n", path, strerror(errno)); - fclose(f); - return VERIFY_FAILURE; - } - SHA_update(&ctx, buffer, size); - so_far += size; - double f = so_far / (double)signed_len; - if (f > frac + 0.02 || size == so_far) { - ui_set_progress(f); - frac = f; - } - } - fclose(f); - free(buffer); - - const uint8_t* sha1 = SHA_final(&ctx); - for (i = 0; i < numKeys; ++i) { - // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that - // the signing tool appends after the signature itself. - if (RSA_verify(pKeys+i, eocd + eocd_size - 6 - RSANUMBYTES, - RSANUMBYTES, sha1)) { - LOGI("whole-file signature verified against key %d\n", i); - free(eocd); - return VERIFY_SUCCESS; - } - } - free(eocd); - LOGE("failed to verify whole-file signature\n"); - return VERIFY_FAILURE; -} diff --git a/verifier.cpp b/verifier.cpp new file mode 100644 index 000000000..58ca72393 --- /dev/null +++ b/verifier.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common.h" +#include "verifier.h" +#include "ui.h" + +#include "mincrypt/rsa.h" +#include "mincrypt/sha.h" + +#include +#include +#include + +// Look for an RSA signature embedded in the .ZIP file comment given +// the path to the zip. Verify it matches one of the given public +// keys. +// +// Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered +// or no key matches the signature). + +int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) { + ui_set_progress(0.0); + + FILE* f = fopen(path, "rb"); + if (f == NULL) { + LOGE("failed to open %s (%s)\n", path, strerror(errno)); + return VERIFY_FAILURE; + } + + // An archive with a whole-file signature will end in six bytes: + // + // (2-byte signature start) $ff $ff (2-byte comment size) + // + // (As far as the ZIP format is concerned, these are part of the + // archive comment.) We start by reading this footer, this tells + // us how far back from the end we have to start reading to find + // the whole comment. + +#define FOOTER_SIZE 6 + + if (fseek(f, -FOOTER_SIZE, SEEK_END) != 0) { + LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); + fclose(f); + return VERIFY_FAILURE; + } + + unsigned char footer[FOOTER_SIZE]; + if (fread(footer, 1, FOOTER_SIZE, f) != FOOTER_SIZE) { + LOGE("failed to read footer from %s (%s)\n", path, strerror(errno)); + fclose(f); + return VERIFY_FAILURE; + } + + if (footer[2] != 0xff || footer[3] != 0xff) { + fclose(f); + return VERIFY_FAILURE; + } + + size_t comment_size = footer[4] + (footer[5] << 8); + size_t signature_start = footer[0] + (footer[1] << 8); + LOGI("comment is %d bytes; signature %d bytes from end\n", + comment_size, signature_start); + + if (signature_start - FOOTER_SIZE < RSANUMBYTES) { + // "signature" block isn't big enough to contain an RSA block. + LOGE("signature is too short\n"); + fclose(f); + return VERIFY_FAILURE; + } + +#define EOCD_HEADER_SIZE 22 + + // The end-of-central-directory record is 22 bytes plus any + // comment length. + size_t eocd_size = comment_size + EOCD_HEADER_SIZE; + + if (fseek(f, -eocd_size, SEEK_END) != 0) { + LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); + fclose(f); + return VERIFY_FAILURE; + } + + // Determine how much of the file is covered by the signature. + // This is everything except the signature data and length, which + // includes all of the EOCD except for the comment length field (2 + // bytes) and the comment data. + size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2; + + unsigned char* eocd = (unsigned char*)malloc(eocd_size); + if (eocd == NULL) { + LOGE("malloc for EOCD record failed\n"); + fclose(f); + return VERIFY_FAILURE; + } + if (fread(eocd, 1, eocd_size, f) != eocd_size) { + LOGE("failed to read eocd from %s (%s)\n", path, strerror(errno)); + fclose(f); + return VERIFY_FAILURE; + } + + // If this is really is the EOCD record, it will begin with the + // magic number $50 $4b $05 $06. + if (eocd[0] != 0x50 || eocd[1] != 0x4b || + eocd[2] != 0x05 || eocd[3] != 0x06) { + LOGE("signature length doesn't match EOCD marker\n"); + fclose(f); + return VERIFY_FAILURE; + } + + size_t i; + for (i = 4; i < eocd_size-3; ++i) { + if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && + eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { + // if the sequence $50 $4b $05 $06 appears anywhere after + // the real one, minzip will find the later (wrong) one, + // which could be exploitable. Fail verification if + // this sequence occurs anywhere after the real one. + LOGE("EOCD marker occurs after start of EOCD\n"); + fclose(f); + return VERIFY_FAILURE; + } + } + +#define BUFFER_SIZE 4096 + + SHA_CTX ctx; + SHA_init(&ctx); + unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE); + if (buffer == NULL) { + LOGE("failed to alloc memory for sha1 buffer\n"); + fclose(f); + return VERIFY_FAILURE; + } + + double frac = -1.0; + size_t so_far = 0; + fseek(f, 0, SEEK_SET); + while (so_far < signed_len) { + size_t size = BUFFER_SIZE; + if (signed_len - so_far < size) size = signed_len - so_far; + if (fread(buffer, 1, size, f) != size) { + LOGE("failed to read data from %s (%s)\n", path, strerror(errno)); + fclose(f); + return VERIFY_FAILURE; + } + SHA_update(&ctx, buffer, size); + so_far += size; + double f = so_far / (double)signed_len; + if (f > frac + 0.02 || size == so_far) { + ui_set_progress(f); + frac = f; + } + } + fclose(f); + free(buffer); + + const uint8_t* sha1 = SHA_final(&ctx); + for (i = 0; i < numKeys; ++i) { + // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that + // the signing tool appends after the signature itself. + if (RSA_verify(pKeys+i, eocd + eocd_size - 6 - RSANUMBYTES, + RSANUMBYTES, sha1)) { + LOGI("whole-file signature verified against key %d\n", i); + free(eocd); + return VERIFY_SUCCESS; + } + } + free(eocd); + LOGE("failed to verify whole-file signature\n"); + return VERIFY_FAILURE; +} diff --git a/verifier_test.c b/verifier_test.c deleted file mode 100644 index 5b6c1f451..000000000 --- a/verifier_test.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include "verifier.h" - -// This is build/target/product/security/testkey.x509.pem after being -// dumped out by dumpkey.jar. -RSAPublicKey test_key = - { 64, 0xc926ad21, - { 1795090719, 2141396315, 950055447, -1713398866, - -26044131, 1920809988, 546586521, -795969498, - 1776797858, -554906482, 1805317999, 1429410244, - 129622599, 1422441418, 1783893377, 1222374759, - -1731647369, 323993566, 28517732, 609753416, - 1826472888, 215237850, -33324596, -245884705, - -1066504894, 774857746, 154822455, -1797768399, - -1536767878, -1275951968, -1500189652, 87251430, - -1760039318, 120774784, 571297800, -599067824, - -1815042109, -483341846, -893134306, -1900097649, - -1027721089, 950095497, 555058928, 414729973, - 1136544882, -1250377212, 465547824, -236820568, - -1563171242, 1689838846, -404210357, 1048029507, - 895090649, 247140249, 178744550, -747082073, - -1129788053, 109881576, -350362881, 1044303212, - -522594267, -1309816990, -557446364, -695002876}, - { -857949815, -510492167, -1494742324, -1208744608, - 251333580, 2131931323, 512774938, 325948880, - -1637480859, 2102694287, -474399070, 792812816, - 1026422502, 2053275343, -1494078096, -1181380486, - 165549746, -21447327, -229719404, 1902789247, - 772932719, -353118870, -642223187, 216871947, - -1130566647, 1942378755, -298201445, 1055777370, - 964047799, 629391717, -2062222979, -384408304, - 191868569, -1536083459, -612150544, -1297252564, - -1592438046, -724266841, -518093464, -370899750, - -739277751, -1536141862, 1323144535, 61311905, - 1997411085, 376844204, 213777604, -217643712, - 9135381, 1625809335, -1490225159, -1342673351, - 1117190829, -57654514, 1825108855, -1281819325, - 1111251351, -1726129724, 1684324211, -1773988491, - 367251975, 810756730, -1941182952, 1175080310 } - }; - -void ui_print(const char* fmt, ...) { - char buf[256]; - va_list ap; - va_start(ap, fmt); - vsnprintf(buf, 256, fmt, ap); - va_end(ap); - - fputs(buf, stderr); -} - -void ui_set_progress(float fraction) { -} - -int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 2; - } - - int result = verify_file(argv[1], &test_key, 1); - if (result == VERIFY_SUCCESS) { - printf("SUCCESS\n"); - return 0; - } else if (result == VERIFY_FAILURE) { - printf("FAILURE\n"); - return 1; - } else { - printf("bad return value\n"); - return 3; - } -} diff --git a/verifier_test.cpp b/verifier_test.cpp new file mode 100644 index 000000000..5b6c1f451 --- /dev/null +++ b/verifier_test.cpp @@ -0,0 +1,91 @@ +/* + * 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 "verifier.h" + +// This is build/target/product/security/testkey.x509.pem after being +// dumped out by dumpkey.jar. +RSAPublicKey test_key = + { 64, 0xc926ad21, + { 1795090719, 2141396315, 950055447, -1713398866, + -26044131, 1920809988, 546586521, -795969498, + 1776797858, -554906482, 1805317999, 1429410244, + 129622599, 1422441418, 1783893377, 1222374759, + -1731647369, 323993566, 28517732, 609753416, + 1826472888, 215237850, -33324596, -245884705, + -1066504894, 774857746, 154822455, -1797768399, + -1536767878, -1275951968, -1500189652, 87251430, + -1760039318, 120774784, 571297800, -599067824, + -1815042109, -483341846, -893134306, -1900097649, + -1027721089, 950095497, 555058928, 414729973, + 1136544882, -1250377212, 465547824, -236820568, + -1563171242, 1689838846, -404210357, 1048029507, + 895090649, 247140249, 178744550, -747082073, + -1129788053, 109881576, -350362881, 1044303212, + -522594267, -1309816990, -557446364, -695002876}, + { -857949815, -510492167, -1494742324, -1208744608, + 251333580, 2131931323, 512774938, 325948880, + -1637480859, 2102694287, -474399070, 792812816, + 1026422502, 2053275343, -1494078096, -1181380486, + 165549746, -21447327, -229719404, 1902789247, + 772932719, -353118870, -642223187, 216871947, + -1130566647, 1942378755, -298201445, 1055777370, + 964047799, 629391717, -2062222979, -384408304, + 191868569, -1536083459, -612150544, -1297252564, + -1592438046, -724266841, -518093464, -370899750, + -739277751, -1536141862, 1323144535, 61311905, + 1997411085, 376844204, 213777604, -217643712, + 9135381, 1625809335, -1490225159, -1342673351, + 1117190829, -57654514, 1825108855, -1281819325, + 1111251351, -1726129724, 1684324211, -1773988491, + 367251975, 810756730, -1941182952, 1175080310 } + }; + +void ui_print(const char* fmt, ...) { + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + fputs(buf, stderr); +} + +void ui_set_progress(float fraction) { +} + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 2; + } + + int result = verify_file(argv[1], &test_key, 1); + if (result == VERIFY_SUCCESS) { + printf("SUCCESS\n"); + return 0; + } else if (result == VERIFY_FAILURE) { + printf("FAILURE\n"); + return 1; + } else { + printf("bad return value\n"); + return 3; + } +} diff --git a/verifier_test.sh b/verifier_test.sh index 6350e80d3..a1de5c57b 100755 --- a/verifier_test.sh +++ b/verifier_test.sh @@ -1,11 +1,7 @@ #!/bin/bash # -# A test suite for applypatch. Run in a client where you have done -# envsetup, choosecombo, etc. -# -# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your -# system partition. -# +# A test suite for recovery's package signature verifier. Run in a +# client where you have done envsetup, lunch, etc. # # TODO: find some way to get this run regularly along with the rest of # the tests. -- cgit v1.2.3