| /* |
| * 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 <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <limits.h> |
| #include <linux/input.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/reboot.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "bootloader.h" |
| #include "common.h" |
| #include "cutils/properties.h" |
| #include "firmware.h" |
| #include "install.h" |
| #include "minui/minui.h" |
| #include "minzip/DirUtil.h" |
| #include "roots.h" |
| #include "recovery_ui.h" |
| |
| 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' }, |
| }; |
| |
| 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 *SDCARD_PACKAGE_FILE = "SDCARD:update.zip"; |
| static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; |
| |
| /* |
| * 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=root: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 |
| * |
| * 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_root() reformats /data |
| * 6. erase_root() 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_root() 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 file given in root:path format, mounting partitions as necessary |
| static FILE* |
| fopen_root_path(const char *root_path, const char *mode) { |
| if (ensure_root_path_mounted(root_path) != 0) { |
| LOGE("Can't mount %s\n", root_path); |
| return NULL; |
| } |
| |
| char path[PATH_MAX] = ""; |
| if (translate_root_path(root_path, path, sizeof(path)) == NULL) { |
| LOGE("Bad path %s\n", root_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); |
| if (fp == NULL) LOGE("Can't open %s\n", path); |
| 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_root_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); |
| } |
| |
| // 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_root_path(INTENT_FILE, "w"); |
| if (fp != NULL) { |
| fputs(send_intent, fp); |
| check_and_fclose(fp, INTENT_FILE); |
| } |
| } |
| |
| // Copy logs to cache so the system can find out what happened. |
| FILE *log = fopen_root_path(LOG_FILE, "a"); |
| if (log != NULL) { |
| FILE *tmplog = fopen(TEMPORARY_LOG_FILE, "r"); |
| if (tmplog == NULL) { |
| LOGE("Can't open %s\n", TEMPORARY_LOG_FILE); |
| } else { |
| static long tmplog_offset = 0; |
| fseek(tmplog, tmplog_offset, SEEK_SET); // Since last write |
| char buf[4096]; |
| while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log); |
| tmplog_offset = ftell(tmplog); |
| check_and_fclose(tmplog, TEMPORARY_LOG_FILE); |
| } |
| check_and_fclose(log, LOG_FILE); |
| } |
| |
| // Reset the bootloader message to revert to a normal main system boot. |
| struct bootloader_message boot; |
| memset(&boot, 0, sizeof(boot)); |
| set_bootloader_message(&boot); |
| |
| // Remove the command file, so recovery won't repeat indefinitely. |
| char path[PATH_MAX] = ""; |
| if (ensure_root_path_mounted(COMMAND_FILE) != 0 || |
| translate_root_path(COMMAND_FILE, path, sizeof(path)) == NULL || |
| (unlink(path) && errno != ENOENT)) { |
| LOGW("Can't unlink %s\n", COMMAND_FILE); |
| } |
| |
| sync(); // For good measure. |
| } |
| |
| static int |
| erase_root(const char *root) |
| { |
| ui_set_background(BACKGROUND_ICON_INSTALLING); |
| ui_show_indeterminate_progress(); |
| ui_print("Formatting %s...\n", root); |
| return format_root_device(root); |
| } |
| |
| static void |
| prompt_and_wait() |
| { |
| char* title[] = { "Android system recovery <" |
| EXPAND(RECOVERY_API_VERSION) "e>", |
| "", |
| NULL }; |
| |
| // count the number of lines in our title, plus the |
| // product-provided headers. |
| int count = 0; |
| char** p; |
| for (p = title; *p; ++p, ++count); |
| for (p = MENU_HEADERS; *p; ++p, ++count); |
| |
| char** headers = malloc((count+1) * sizeof(char*)); |
| char** h = headers; |
| for (p = title; *p; ++p, ++h) *h = *p; |
| for (p = MENU_HEADERS; *p; ++p, ++h) *h = *p; |
| *h = NULL; |
| |
| ui_start_menu(headers, MENU_ITEMS); |
| int selected = 0; |
| int chosen_item = -1; |
| |
| finish_recovery(NULL); |
| ui_reset_progress(); |
| for (;;) { |
| int key = ui_wait_key(); |
| int visible = ui_text_visible(); |
| |
| 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 { |
| chosen_item = action; |
| } |
| |
| if (chosen_item >= 0) { |
| // turn off the menu, letting ui_print() to scroll output |
| // on the screen. |
| ui_end_menu(); |
| |
| // 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); |
| |
| switch (chosen_item) { |
| case ITEM_REBOOT: |
| return; |
| |
| case ITEM_WIPE_DATA: |
| ui_print("\n-- Wiping data...\n"); |
| device_wipe_data(); |
| erase_root("DATA:"); |
| erase_root("CACHE:"); |
| ui_print("Data wipe complete.\n"); |
| if (!ui_text_visible()) return; |
| break; |
| |
| case ITEM_WIPE_CACHE: |
| ui_print("\n-- Wiping cache...\n"); |
| erase_root("CACHE:"); |
| ui_print("Cache wipe complete.\n"); |
| if (!ui_text_visible()) return; |
| break; |
| |
| case ITEM_APPLY_SDCARD: |
| ui_print("\n-- Install from sdcard...\n"); |
| set_sdcard_update_bootloader_message(); |
| int status = install_package(SDCARD_PACKAGE_FILE); |
| 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 { |
| if (firmware_update_pending()) { |
| ui_print("\nReboot via menu to complete\n" |
| "installation.\n"); |
| } else { |
| ui_print("\nInstall from sdcard complete.\n"); |
| } |
| } |
| break; |
| } |
| |
| // if we didn't return from this function to reboot, show |
| // the menu again. |
| ui_start_menu(headers, MENU_ITEMS); |
| selected = 0; |
| chosen_item = -1; |
| |
| finish_recovery(NULL); |
| ui_reset_progress(); |
| |
| // throw away keys pressed while the command was running, |
| // so user doesn't accidentally trigger menu items. |
| ui_clear_key_queue(); |
| } |
| } |
| } |
| |
| static void |
| print_property(const char *key, const char *name, void *cookie) |
| { |
| fprintf(stderr, "%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); |
| fprintf(stderr, "Starting recovery on %s", ctime(&start)); |
| |
| ui_init(); |
| 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 '?': |
| LOGE("Invalid command argument\n"); |
| continue; |
| } |
| } |
| |
| fprintf(stderr, "Command:"); |
| for (arg = 0; arg < argc; arg++) { |
| fprintf(stderr, " \"%s\"", argv[arg]); |
| } |
| fprintf(stderr, "\n\n"); |
| |
| property_list(print_property, NULL); |
| fprintf(stderr, "\n"); |
| |
| int status = INSTALL_SUCCESS; |
| |
| if (update_package != NULL) { |
| status = install_package(update_package); |
| if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n"); |
| } else if (wipe_data) { |
| if (device_wipe_data()) status = INSTALL_ERROR; |
| if (erase_root("DATA:")) status = INSTALL_ERROR; |
| if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR; |
| if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n"); |
| } else if (wipe_cache) { |
| if (wipe_cache && erase_root("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(); |
| |
| // If there is a radio image pending, reboot now to install it. |
| maybe_install_firmware_update(send_intent); |
| |
| // Otherwise, get ready to boot the main system... |
| finish_recovery(send_intent); |
| ui_print("Rebooting...\n"); |
| sync(); |
| reboot(RB_AUTOBOOT); |
| return EXIT_SUCCESS; |
| } |