diff options
Diffstat (limited to 'recovery.cpp')
-rw-r--r-- | recovery.cpp | 1551 |
1 files changed, 702 insertions, 849 deletions
diff --git a/recovery.cpp b/recovery.cpp index 0f0b978e7..c1a31b6a8 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -34,23 +34,30 @@ #include <time.h> #include <unistd.h> +#include <algorithm> #include <chrono> +#include <memory> #include <string> #include <vector> #include <adb.h> -#include <android/log.h> /* Android Log Priority Tags */ #include <android-base/file.h> +#include <android-base/logging.h> #include <android-base/parseint.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> +#include <android-base/unique_fd.h> #include <bootloader_message/bootloader_message.h> #include <cutils/android_reboot.h> -#include <cutils/properties.h> -#include <log/logger.h> /* Android Log packet format */ -#include <private/android_logger.h> /* private pmsg functions */ - +#include <cutils/properties.h> /* for property_list */ #include <healthd/BatteryMonitor.h> +#include <private/android_logger.h> /* private pmsg functions */ +#include <private/android_filesystem_config.h> /* for AID_SYSTEM */ +#include <selinux/android.h> +#include <selinux/label.h> +#include <selinux/selinux.h> +#include <ziparchive/zip_archive.h> #include "adb_install.h" #include "common.h" @@ -59,18 +66,16 @@ #include "fuse_sdcard_provider.h" #include "fuse_sideload.h" #include "install.h" +#include "minadbd/minadbd.h" #include "minui/minui.h" -#include "minzip/DirUtil.h" -#include "minzip/Zip.h" +#include "otautil/DirUtil.h" #include "roots.h" -#include "ui.h" -#include "unique_fd.h" +#include "rotate_logs.h" #include "screen_ui.h" - -struct selabel_handle *sehandle; +#include "stub_ui.h" +#include "ui.h" static const struct option OPTIONS[] = { - { "send_intent", required_argument, NULL, 'i' }, { "update_package", required_argument, NULL, 'u' }, { "retry_count", required_argument, NULL, 'n' }, { "wipe_data", no_argument, NULL, 'w' }, @@ -80,12 +85,12 @@ static const struct option OPTIONS[] = { { "sideload_auto_reboot", no_argument, NULL, 'a' }, { "just_exit", no_argument, NULL, 'x' }, { "locale", required_argument, NULL, 'l' }, - { "stages", required_argument, NULL, 'g' }, { "shutdown_after", no_argument, NULL, 'p' }, { "reason", required_argument, NULL, 'r' }, { "security", no_argument, NULL, 'e'}, { "wipe_ab", no_argument, NULL, 0 }, { "wipe_package_size", required_argument, NULL, 0 }, + { "prompt_and_wipe_data", no_argument, NULL, 0 }, { NULL, 0, NULL, 0 }, }; @@ -97,7 +102,6 @@ static const std::vector<std::string> bootreason_blacklist { static const char *CACHE_LOG_DIR = "/cache/recovery"; 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_INSTALL_FILE = "/cache/recovery/last_install"; static const char *LOCALE_FILE = "/cache/recovery/last_locale"; @@ -110,7 +114,6 @@ static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; -static const int KEEP_LOG_COUNT = 10; // We will try to apply the update package 5 times at most in case of an I/O error. static const int EIO_RETRY_COUNT = 4; static const int BATTERY_READ_TIMEOUT_IN_SEC = 10; @@ -119,25 +122,28 @@ static const int BATTERY_READ_TIMEOUT_IN_SEC = 10; // So we should check battery with a slightly lower limitation. static const int BATTERY_OK_PERCENTAGE = 20; static const int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15; -constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe"; +static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe"; +static constexpr const char* DEFAULT_LOCALE = "en-US"; -RecoveryUI* ui = NULL; -static const char* locale = "en_US"; -char* stage = NULL; -char* reason = NULL; -bool modified_flash = false; +static std::string locale; static bool has_cache = false; +RecoveryUI* ui = nullptr; +bool modified_flash = false; +std::string stage; +const char* reason = nullptr; +struct selabel_handle* sehandle; + /* * 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 + * --prompt_and_wipe_data - prompt the user that data is corrupt, + * with their consent 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 * --just_exit - do nothing; exit and reboot @@ -170,30 +176,13 @@ static bool has_cache = false; * -- 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 + * 7b. the user reboots (pulling the battery, etc) into the 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); + LOG(ERROR) << "Can't mount " << path; return NULL; } @@ -208,19 +197,31 @@ FILE* fopen_path(const char *path, const char *mode) { // 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)); + if (fsync(fileno(fp)) == -1) { + PLOG(ERROR) << "Failed to fsync " << name; + } + if (ferror(fp)) { + PLOG(ERROR) << "Error in " << name; + } fclose(fp); } bool is_ro_debuggable() { - char value[PROPERTY_VALUE_MAX+1]; - return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); + return android::base::GetBoolProperty("ro.debuggable", false); +} + +bool reboot(const std::string& command) { + std::string cmd = command; + if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { + cmd += ",quiescent"; + } + return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd); } static void redirect_stdio(const char* filename) { int pipefd[2]; if (pipe(pipefd) == -1) { - LOGE("pipe failed: %s\n", strerror(errno)); + PLOG(ERROR) << "pipe failed"; // Fall back to traditional logging mode without timestamps. // If these fail, there's not really anywhere to complain... @@ -232,7 +233,7 @@ static void redirect_stdio(const char* filename) { pid_t pid = fork(); if (pid == -1) { - LOGE("fork failed: %s\n", strerror(errno)); + PLOG(ERROR) << "fork failed"; // Fall back to traditional logging mode without timestamps. // If these fail, there's not really anywhere to complain... @@ -251,17 +252,17 @@ static void redirect_stdio(const char* filename) { // Child logger to actually write to the log file. FILE* log_fp = fopen(filename, "a"); if (log_fp == nullptr) { - LOGE("fopen \"%s\" failed: %s\n", filename, strerror(errno)); + PLOG(ERROR) << "fopen \"" << filename << "\" failed"; close(pipefd[0]); - _exit(1); + _exit(EXIT_FAILURE); } FILE* pipe_fp = fdopen(pipefd[0], "r"); if (pipe_fp == nullptr) { - LOGE("fdopen failed: %s\n", strerror(errno)); + PLOG(ERROR) << "fdopen failed"; check_and_fclose(log_fp, filename); close(pipefd[0]); - _exit(1); + _exit(EXIT_FAILURE); } char* line = nullptr; @@ -278,12 +279,12 @@ static void redirect_stdio(const char* filename) { fflush(log_fp); } - LOGE("getline failed: %s\n", strerror(errno)); + PLOG(ERROR) << "getline failed"; free(line); check_and_fclose(log_fp, filename); close(pipefd[0]); - _exit(1); + _exit(EXIT_FAILURE); } else { // Redirect stdout/stderr to the logger process. // Close the unused read end. @@ -293,10 +294,10 @@ static void redirect_stdio(const char* filename) { setbuf(stderr, nullptr); if (dup2(pipefd[1], STDOUT_FILENO) == -1) { - LOGE("dup2 stdout failed: %s\n", strerror(errno)); + PLOG(ERROR) << "dup2 stdout failed"; } if (dup2(pipefd[1], STDERR_FILENO) == -1) { - LOGE("dup2 stderr failed: %s\n", strerror(errno)); + PLOG(ERROR) << "dup2 stderr failed"; } close(pipefd[1]); @@ -307,104 +308,95 @@ static void redirect_stdio(const char* filename) { // - 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) { - bootloader_message boot = {}; - std::string err; - if (!read_bootloader_message(&boot, &err)) { - LOGE("%s\n", err.c_str()); - // If fails, leave a zeroed bootloader_message. - memset(&boot, 0, sizeof(boot)); - } - stage = strndup(boot.stage, sizeof(boot.stage)); - - if (boot.command[0] != 0 && boot.command[0] != 255) { - LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command); - } - - if (boot.status[0] != 0 && boot.status[0] != 255) { - LOGI("Boot status: %.*s\n", (int)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 we have /cache). - if (*argc <= 1 && has_cache) { - FILE *fp = fopen_path(COMMAND_FILE, "r"); - if (fp != NULL) { - char *token; - 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; - token = strtok(buf, "\r\n"); - if (token != NULL) { - (*argv)[*argc] = strdup(token); // Strip newline. - } else { - --*argc; - } - } - - 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)); - } - if (!write_bootloader_message(boot, &err)) { - LOGE("%s\n", err.c_str()); - } +static std::vector<std::string> get_args(const int argc, char** const argv) { + CHECK_GT(argc, 0); + + bootloader_message boot = {}; + std::string err; + if (!read_bootloader_message(&boot, &err)) { + LOG(ERROR) << err; + // If fails, leave a zeroed bootloader_message. + boot = {}; + } + stage = std::string(boot.stage); + + if (boot.command[0] != 0) { + std::string boot_command = std::string(boot.command, sizeof(boot.command)); + LOG(INFO) << "Boot command: " << boot_command; + } + + if (boot.status[0] != 0) { + std::string boot_status = std::string(boot.status, sizeof(boot.status)); + LOG(INFO) << "Boot status: " << boot_status; + } + + std::vector<std::string> args(argv, argv + argc); + + // --- if arguments weren't supplied, look in the bootloader control block + if (args.size() == 1) { + boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination + std::string boot_recovery(boot.recovery); + std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n"); + if (!tokens.empty() && tokens[0] == "recovery") { + for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from boot message"; + } else if (boot.recovery[0] != 0) { + LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\""; + } + } + + // --- if that doesn't work, try the command file (if we have /cache). + if (args.size() == 1 && has_cache) { + std::string content; + if (ensure_path_mounted(COMMAND_FILE) == 0 && + android::base::ReadFileToString(COMMAND_FILE, &content)) { + std::vector<std::string> tokens = android::base::Split(content, "\n"); + // All the arguments in COMMAND_FILE are needed (unlike the BCB message, + // COMMAND_FILE doesn't use filename as the first argument). + for (auto it = tokens.begin(); it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE; + } + } + + // Write the arguments (excluding the filename in args[0]) back into the + // bootloader control block. So the device will always boot into recovery to + // finish the pending work, until finish_recovery() is called. + std::vector<std::string> options(args.cbegin() + 1, args.cend()); + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << "Failed to set BCB message: " << err; + } + + return args; } -static void -set_sdcard_update_bootloader_message() { - bootloader_message boot = {}; - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - std::string err; - if (!write_bootloader_message(boot, &err)) { - LOGE("%s\n", err.c_str()); - } +// Set the BCB to reboot back into recovery (it won't resume the install from +// sdcard though). +static void set_sdcard_update_bootloader_message() { + std::vector<std::string> options; + std::string err; + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << "Failed to set BCB message: " << err; + } } // Read from kernel log into buffer and write out to file. static void save_kernel_log(const char* destination) { int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); if (klog_buf_len <= 0) { - LOGE("Error getting klog size: %s\n", strerror(errno)); + PLOG(ERROR) << "Error getting klog size"; return; } std::string buffer(klog_buf_len, 0); int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); if (n == -1) { - LOGE("Error in reading klog: %s\n", strerror(errno)); + PLOG(ERROR) << "Error in reading klog"; return; } buffer.resize(n); @@ -424,17 +416,17 @@ static void copy_log_file_to_pmsg(const char* source, const char* destination) { } // How much of the temp log we have copied to the copy in cache. -static long tmplog_offset = 0; +static off_t tmplog_offset = 0; static void copy_log_file(const char* source, const char* destination, bool append) { FILE* dest_fp = fopen_path(destination, append ? "a" : "w"); if (dest_fp == nullptr) { - LOGE("Can't open %s\n", destination); + PLOG(ERROR) << "Can't open " << destination; } else { FILE* source_fp = fopen(source, "r"); if (source_fp != nullptr) { if (append) { - fseek(source_fp, tmplog_offset, SEEK_SET); // Since last write + fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write } char buf[4096]; size_t bytes; @@ -442,7 +434,7 @@ static void copy_log_file(const char* source, const char* destination, bool appe fwrite(buf, 1, bytes, dest_fp); } if (append) { - tmplog_offset = ftell(source_fp); + tmplog_offset = ftello(source_fp); } check_and_fclose(source_fp, source); } @@ -450,37 +442,6 @@ static void copy_log_file(const char* source, const char* destination, bool appe } } -// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. -// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. -// Overwrite any existing last_log.$max and last_kmsg.$max. -static void rotate_logs(int max) { - // Logs should only be rotated once. - static bool rotated = false; - if (rotated) { - return; - } - rotated = true; - ensure_path_mounted(LAST_LOG_FILE); - ensure_path_mounted(LAST_KMSG_FILE); - - for (int i = max-1; i >= 0; --i) { - std::string old_log = android::base::StringPrintf("%s", LAST_LOG_FILE); - if (i > 0) { - old_log += "." + std::to_string(i); - } - std::string new_log = android::base::StringPrintf("%s.%d", LAST_LOG_FILE, i+1); - // Ignore errors if old_log doesn't exist. - rename(old_log.c_str(), new_log.c_str()); - - std::string old_kmsg = android::base::StringPrintf("%s", LAST_KMSG_FILE); - if (i > 0) { - old_kmsg += "." + std::to_string(i); - } - std::string new_kmsg = android::base::StringPrintf("%s.%d", LAST_KMSG_FILE, i+1); - rename(old_kmsg.c_str(), new_kmsg.c_str()); - } -} - static void copy_logs() { // We only rotate and record the log of the current session if there are // actual attempts to modify the flash, such as wipes, installs from BCB @@ -499,7 +460,9 @@ static void copy_logs() { return; } - rotate_logs(KEEP_LOG_COUNT); + ensure_path_mounted(LAST_LOG_FILE); + ensure_path_mounted(LAST_KMSG_FILE); + rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); // Copy logs to cache so the system can find out what happened. copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true); @@ -507,60 +470,43 @@ static void copy_logs() { copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false); save_kernel_log(LAST_KMSG_FILE); chmod(LOG_FILE, 0600); - chown(LOG_FILE, 1000, 1000); // system user + chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); chmod(LAST_KMSG_FILE, 0600); - chown(LAST_KMSG_FILE, 1000, 1000); // system user + chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM); chmod(LAST_LOG_FILE, 0640); chmod(LAST_INSTALL_FILE, 0644); sync(); } // 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 && has_cache) { - 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 our log file to cache as well (for the system to read). This function is +// idempotent: call it as many times as you like. +static void finish_recovery() { // Save the locale to cache, so if recovery is next started up // without a --locale argument (eg, directly from the bootloader) // it will use the last-known locale. - if (locale != NULL) { - size_t len = strlen(locale); - __pmsg_write(LOCALE_FILE, locale, len); - if (has_cache) { - LOGI("Saving locale \"%s\"\n", locale); - FILE* fp = fopen_path(LOCALE_FILE, "w"); - fwrite(locale, 1, len, fp); - fflush(fp); - fsync(fileno(fp)); - check_and_fclose(fp, LOCALE_FILE); + if (!locale.empty() && has_cache) { + LOG(INFO) << "Saving locale \"" << locale << "\""; + + FILE* fp = fopen_path(LOCALE_FILE, "w"); + if (!android::base::WriteStringToFd(locale, fileno(fp))) { + PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE; } + check_and_fclose(fp, LOCALE_FILE); } copy_logs(); // Reset to normal system boot so recovery won't cycle indefinitely. - bootloader_message boot = {}; std::string err; - if (!write_bootloader_message(boot, &err)) { - LOGE("%s\n", err.c_str()); + if (!clear_bootloader_message(&err)) { + LOG(ERROR) << "Failed to clear BCB message: " << err; } // Remove the command file, so recovery won't repeat indefinitely. if (has_cache) { if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { - LOGW("Can't unlink %s\n", COMMAND_FILE); + LOG(WARNING) << "Can't unlink " << COMMAND_FILE; } ensure_path_unmounted(CACHE_ROOT); } @@ -568,287 +514,239 @@ finish_recovery(const char *send_intent) { sync(); // For good measure. } -typedef struct _saved_log_file { - char* name; - struct stat st; - unsigned char* data; - struct _saved_log_file* next; -} saved_log_file; +struct saved_log_file { + std::string name; + struct stat sb; + std::string data; +}; static bool erase_volume(const char* volume) { - bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); - bool is_data = (strcmp(volume, DATA_ROOT) == 0); + bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); + bool is_data = (strcmp(volume, DATA_ROOT) == 0); - ui->SetBackground(RecoveryUI::ERASING); - ui->SetProgressType(RecoveryUI::INDETERMINATE); + ui->SetBackground(RecoveryUI::ERASING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); - saved_log_file* head = NULL; - - if (is_cache) { - // If we're reformatting /cache, we load any past logs - // (i.e. "/cache/recovery/last_*") and the current log - // ("/cache/recovery/log") into memory, so we can restore them after - // the reformat. - - ensure_path_mounted(volume); - - DIR* d; - struct dirent* de; - d = opendir(CACHE_LOG_DIR); - if (d) { - char path[PATH_MAX]; - strcpy(path, CACHE_LOG_DIR); - strcat(path, "/"); - int path_len = strlen(path); - while ((de = readdir(d)) != NULL) { - if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { - saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file)); - strcpy(path+path_len, de->d_name); - p->name = strdup(path); - if (stat(path, &(p->st)) == 0) { - // truncate files to 512kb - if (p->st.st_size > (1 << 19)) { - p->st.st_size = 1 << 19; - } - p->data = (unsigned char*) malloc(p->st.st_size); - FILE* f = fopen(path, "rb"); - fread(p->data, 1, p->st.st_size, f); - fclose(f); - p->next = head; - head = p; - } else { - free(p); - } - } - } - closedir(d); - } else { - if (errno != ENOENT) { - printf("opendir failed: %s\n", strerror(errno)); - } - } - } + std::vector<saved_log_file> log_files; + + if (is_cache) { + // If we're reformatting /cache, we load any past logs + // (i.e. "/cache/recovery/last_*") and the current log + // ("/cache/recovery/log") into memory, so we can restore them after + // the reformat. - ui->Print("Formatting %s...\n", volume); + ensure_path_mounted(volume); - ensure_path_unmounted(volume); + struct dirent* de; + std::unique_ptr<DIR, decltype(&closedir)> d(opendir(CACHE_LOG_DIR), closedir); + if (d) { + while ((de = readdir(d.get())) != nullptr) { + if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { + std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name); + + struct stat sb; + if (stat(path.c_str(), &sb) == 0) { + // truncate files to 512kb + if (sb.st_size > (1 << 19)) { + sb.st_size = 1 << 19; + } - int result; + std::string data(sb.st_size, '\0'); + FILE* f = fopen(path.c_str(), "rb"); + fread(&data[0], 1, data.size(), f); + fclose(f); - if (is_data && reason && strcmp(reason, "convert_fbe") == 0) { - // Create convert_fbe breadcrumb file to signal to init - // to convert to file based encryption, not full disk encryption - if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { - ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno)); - return true; + log_files.emplace_back(saved_log_file{ path, sb, data }); + } } - FILE* f = fopen(CONVERT_FBE_FILE, "wb"); - if (!f) { - ui->Print("Failed to convert to file encryption %s\n", strerror(errno)); - return true; + } + } else { + if (errno != ENOENT) { + PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR; + } + } + } + + ui->Print("Formatting %s...\n", volume); + + ensure_path_unmounted(volume); + + int result; + + if (is_data && reason && strcmp(reason, "convert_fbe") == 0) { + // Create convert_fbe breadcrumb file to signal to init + // to convert to file based encryption, not full disk encryption + if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { + ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno)); + return true; + } + FILE* f = fopen(CONVERT_FBE_FILE, "wb"); + if (!f) { + ui->Print("Failed to convert to file encryption %s\n", strerror(errno)); + return true; + } + fclose(f); + result = format_volume(volume, CONVERT_FBE_DIR); + remove(CONVERT_FBE_FILE); + rmdir(CONVERT_FBE_DIR); + } else { + result = format_volume(volume); + } + + if (is_cache) { + // Re-create the log dir and write back the log entries. + if (ensure_path_mounted(CACHE_LOG_DIR) == 0 && + dirCreateHierarchy(CACHE_LOG_DIR, 0777, nullptr, false, sehandle) == 0) { + for (const auto& log : log_files) { + if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid, + log.sb.st_gid)) { + PLOG(ERROR) << "Failed to write to " << log.name; } - fclose(f); - result = format_volume(volume, CONVERT_FBE_DIR); - remove(CONVERT_FBE_FILE); - rmdir(CONVERT_FBE_DIR); + } } else { - result = format_volume(volume); + PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR; } - if (is_cache) { - while (head) { - FILE* f = fopen_path(head->name, "wb"); - if (f) { - fwrite(head->data, 1, head->st.st_size, f); - fclose(f); - chmod(head->name, head->st.st_mode); - chown(head->name, head->st.st_uid, head->st.st_gid); - } - free(head->name); - free(head->data); - saved_log_file* temp = head->next; - free(head); - head = temp; - } - - // 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; - copy_logs(); - } + // 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; + copy_logs(); + } - return (result == 0); + return (result == 0); } -static int -get_menu_selection(const char* const * headers, const char* const * items, - int menu_only, int initial_selection, Device* device) { - // throw away keys pressed previously, so user doesn't - // accidentally trigger menu items. - ui->FlushKeys(); - - ui->StartMenu(headers, items, initial_selection); - int selected = initial_selection; - int chosen_item = -1; - - while (chosen_item < 0) { - int key = ui->WaitKey(); - int visible = ui->IsTextVisible(); - - if (key == -1) { // ui_wait_key() timed out - if (ui->WasTextEverVisible()) { - continue; - } else { - LOGI("timed out waiting for key input; rebooting.\n"); - ui->EndMenu(); - return 0; // XXX fixme - } - } - - int action = device->HandleMenuKey(key, visible); - - if (action < 0) { - switch (action) { - case Device::kHighlightUp: - selected = ui->SelectMenu(--selected); - break; - case Device::kHighlightDown: - selected = ui->SelectMenu(++selected); - break; - case Device::kInvokeItem: - chosen_item = selected; - break; - case Device::kNoAction: - break; - } - } else if (!menu_only) { - chosen_item = action; - } - } - - ui->EndMenu(); - return chosen_item; +// Display a menu with the specified 'headers' and 'items'. Device specific HandleMenuKey() may +// return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only +// a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the +// (non-negative) chosen item number, or -1 if timed out waiting for input. +static int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only, + int initial_selection, Device* device) { + // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. + ui->FlushKeys(); + + ui->StartMenu(headers, items, initial_selection); + + int selected = initial_selection; + int chosen_item = -1; + while (chosen_item < 0) { + int key = ui->WaitKey(); + if (key == -1) { // WaitKey() timed out. + if (ui->WasTextEverVisible()) { + continue; + } else { + LOG(INFO) << "Timed out waiting for key input; rebooting."; + ui->EndMenu(); + return -1; + } + } + + bool visible = ui->IsTextVisible(); + int action = device->HandleMenuKey(key, visible); + + if (action < 0) { + switch (action) { + case Device::kHighlightUp: + selected = ui->SelectMenu(--selected); + break; + case Device::kHighlightDown: + selected = ui->SelectMenu(++selected); + break; + case Device::kInvokeItem: + chosen_item = selected; + break; + case Device::kNoAction: + break; + } + } else if (!menu_only) { + chosen_item = action; + } + } + + ui->EndMenu(); + return chosen_item; } -static int compare_string(const void* a, const void* b) { - return strcmp(*(const char**)a, *(const char**)b); -} +// Returns the selected filename, or an empty string. +static std::string browse_directory(const std::string& path, Device* device) { + ensure_path_mounted(path.c_str()); -// Returns a malloc'd path, or NULL. -static char* browse_directory(const char* path, Device* device) { - ensure_path_mounted(path); + std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir); + if (!d) { + PLOG(ERROR) << "error opening " << path; + return ""; + } - DIR* d = opendir(path); - if (d == NULL) { - LOGE("error opening %s: %s\n", path, strerror(errno)); - return NULL; - } + std::vector<std::string> dirs; + std::vector<std::string> zips = { "../" }; // "../" is always the first entry. - 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("../"); + dirent* de; + while ((de = readdir(d.get())) != nullptr) { + std::string name(de->d_name); - struct dirent* de; - 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); - } + if (de->d_type == DT_DIR) { + // Skip "." and ".." entries. + if (name == "." || name == "..") continue; + dirs.push_back(name + "/"); + } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) { + zips.push_back(name); } - closedir(d); + } - qsort(dirs, d_size, sizeof(char*), compare_string); - qsort(zips, z_size, sizeof(char*), compare_string); + std::sort(dirs.begin(), dirs.end()); + std::sort(zips.begin(), zips.end()); - // 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; + // Append dirs to the zips list. + zips.insert(zips.end(), dirs.begin(), dirs.end()); - const char* headers[] = { "Choose a package to install:", path, NULL }; + const char* entries[zips.size() + 1]; + entries[zips.size()] = nullptr; + for (size_t i = 0; i < zips.size(); i++) { + entries[i] = zips[i].c_str(); + } - char* result; - int chosen_item = 0; - while (true) { - chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device); + const char* headers[] = { "Choose a package to install:", path.c_str(), nullptr }; - 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 = NULL; - break; - } + int chosen_item = 0; + while (true) { + chosen_item = get_menu_selection(headers, entries, true, chosen_item, device); - char new_path[PATH_MAX]; - strlcpy(new_path, path, PATH_MAX); - strlcat(new_path, "/", PATH_MAX); - strlcat(new_path, item, PATH_MAX); - - if (item[item_len-1] == '/') { - // recurse down into a subdirectory - new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/' - result = browse_directory(new_path, device); - if (result) break; - } else { - // selected a zip file: return the malloc'd path to the caller. - result = strdup(new_path); - break; - } + const std::string& item = zips[chosen_item]; + if (chosen_item == 0) { + // Go up but continue browsing (if the caller is browse_directory). + return ""; } - for (int i = 0; i < z_size; ++i) free(zips[i]); - free(zips); + std::string new_path = path + "/" + item; + if (new_path.back() == '/') { + // Recurse down into a subdirectory. + new_path.pop_back(); + std::string result = browse_directory(new_path, device); + if (!result.empty()) return result; + } else { + // Selected a zip file: return the path to the caller. + return new_path; + } + } - return result; + // Unreachable. } static bool yes_no(Device* device, const char* question1, const char* question2) { const char* headers[] = { question1, question2, NULL }; const char* items[] = { " No", " Yes", NULL }; - int chosen_item = get_menu_selection(headers, items, 1, 0, device); + int chosen_item = get_menu_selection(headers, items, true, 0, device); return (chosen_item == 1); } -// Return true on success. -static bool wipe_data(int should_confirm, Device* device) { - if (should_confirm && !yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!")) { - return false; - } +static bool ask_to_wipe_data(Device* device) { + return yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!"); +} +// Return true on success. +static bool wipe_data(Device* device) { modified_flash = true; ui->Print("\n-- Wiping data...\n"); @@ -861,6 +759,30 @@ static bool wipe_data(int should_confirm, Device* device) { return success; } +static bool prompt_and_wipe_data(Device* device) { + const char* const headers[] = { + "Can't load Android system. Your data may be corrupt.", + "If you continue to get this message, you may need to", + "perform a factory data reset and erase all user data", + "stored on this device.", + NULL + }; + const char* const items[] = { + "Try again", + "Factory data reset", + NULL + }; + for (;;) { + int chosen_item = get_menu_selection(headers, items, true, 0, device); + if (chosen_item != 1) { + return true; // Just reboot, no wipe; not a failure, user asked for it + } + if (ask_to_wipe_data(device)) { + return wipe_data(device); + } + } +} + // Return true on success. static bool wipe_cache(bool should_confirm, Device* device) { if (!has_cache) { @@ -880,47 +802,45 @@ static bool wipe_cache(bool should_confirm, Device* device) { return success; } -// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. -// Otherwise, it goes with BLKDISCARD (if device supports BLKDISCARDZEROES) or -// BLKZEROOUT. +// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with +// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT. static bool secure_wipe_partition(const std::string& partition) { - unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); - if (fd.get() == -1) { - LOGE("failed to open \"%s\": %s\n", partition.c_str(), strerror(errno)); - return false; - } + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open \"" << partition << "\""; + return false; + } - uint64_t range[2] = {0, 0}; - if (ioctl(fd.get(), BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { - LOGE("failed to get partition size: %s\n", strerror(errno)); + uint64_t range[2] = { 0, 0 }; + if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { + PLOG(ERROR) << "Failed to get partition size"; + return false; + } + LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1]; + + LOG(INFO) << " Trying BLKSECDISCARD..."; + if (ioctl(fd, BLKSECDISCARD, &range) == -1) { + PLOG(WARNING) << " Failed"; + + // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. + unsigned int zeroes; + if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { + LOG(INFO) << " Trying BLKDISCARD..."; + if (ioctl(fd, BLKDISCARD, &range) == -1) { + PLOG(ERROR) << " Failed"; return false; + } + } else { + LOG(INFO) << " Trying BLKZEROOUT..."; + if (ioctl(fd, BLKZEROOUT, &range) == -1) { + PLOG(ERROR) << " Failed"; + return false; + } } - printf("Secure-wiping \"%s\" from %" PRIu64 " to %" PRIu64 ".\n", - partition.c_str(), range[0], range[1]); - - printf("Trying BLKSECDISCARD...\t"); - if (ioctl(fd.get(), BLKSECDISCARD, &range) == -1) { - printf("failed: %s\n", strerror(errno)); - - // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. - unsigned int zeroes; - if (ioctl(fd.get(), BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { - printf("Trying BLKDISCARD...\t"); - if (ioctl(fd.get(), BLKDISCARD, &range) == -1) { - printf("failed: %s\n", strerror(errno)); - return false; - } - } else { - printf("Trying BLKZEROOUT...\t"); - if (ioctl(fd.get(), BLKZEROOUT, &range) == -1) { - printf("failed: %s\n", strerror(errno)); - return false; - } - } - } + } - printf("done\n"); - return true; + LOG(INFO) << " Done"; + return true; } // Check if the wipe package matches expectation: @@ -928,35 +848,35 @@ static bool secure_wipe_partition(const std::string& partition) { // 2. check metadata (ota-type, pre-device and serial number if having one). static bool check_wipe_package(size_t wipe_package_size) { if (wipe_package_size == 0) { - LOGE("wipe_package_size is zero.\n"); + LOG(ERROR) << "wipe_package_size is zero"; return false; } std::string wipe_package; std::string err_str; if (!read_wipe_package(&wipe_package, wipe_package_size, &err_str)) { - LOGE("Failed to read wipe package: %s\n", err_str.c_str()); + PLOG(ERROR) << "Failed to read wipe package"; return false; } if (!verify_package(reinterpret_cast<const unsigned char*>(wipe_package.data()), wipe_package.size())) { - LOGE("Failed to verify package.\n"); + LOG(ERROR) << "Failed to verify package"; return false; } // Extract metadata - ZipArchive zip; - int err = mzOpenZipArchive(reinterpret_cast<unsigned char*>(&wipe_package[0]), - wipe_package.size(), &zip); + ZipArchiveHandle zip; + int err = OpenArchiveFromMemory(static_cast<void*>(&wipe_package[0]), wipe_package.size(), + "wipe_package", &zip); if (err != 0) { - LOGE("Can't open wipe package: %s\n", err != -1 ? strerror(err) : "bad"); + LOG(ERROR) << "Can't open wipe package : " << ErrorCodeString(err); return false; } std::string metadata; - if (!read_metadata_from_package(&zip, &metadata)) { - mzCloseZipArchive(&zip); + if (!read_metadata_from_package(zip, &metadata)) { + CloseArchive(zip); return false; } - mzCloseZipArchive(&zip); + CloseArchive(zip); // Check metadata std::vector<std::string> lines = android::base::Split(metadata, "\n"); @@ -969,13 +889,11 @@ static bool check_wipe_package(size_t wipe_package_size) { ota_type_matched = true; } else if (android::base::StartsWith(line, "pre-device=")) { std::string device_type = line.substr(strlen("pre-device=")); - char real_device_type[PROPERTY_VALUE_MAX]; - property_get("ro.build.product", real_device_type, ""); + std::string real_device_type = android::base::GetProperty("ro.build.product", ""); device_type_matched = (device_type == real_device_type); } else if (android::base::StartsWith(line, "serialno=")) { std::string serial_no = line.substr(strlen("serialno=")); - char real_serial_no[PROPERTY_VALUE_MAX]; - property_get("ro.serialno", real_serial_no, ""); + std::string real_serial_no = android::base::GetProperty("ro.serialno", ""); has_serial_number = true; serial_number_matched = (serial_no == real_serial_no); } @@ -990,12 +908,12 @@ static bool wipe_ab_device(size_t wipe_package_size) { ui->SetProgressType(RecoveryUI::INDETERMINATE); if (!check_wipe_package(wipe_package_size)) { - LOGE("Failed to verify wipe package\n"); + LOG(ERROR) << "Failed to verify wipe package"; return false; } std::string partition_list; if (!android::base::ReadFileToString(RECOVERY_WIPE, &partition_list)) { - LOGE("failed to read \"%s\".\n", RECOVERY_WIPE); + LOG(ERROR) << "failed to read \"" << RECOVERY_WIPE << "\""; return false; } @@ -1014,98 +932,94 @@ static bool wipe_ab_device(size_t wipe_package_size) { } static void choose_recovery_file(Device* device) { - // "Back" + KEEP_LOG_COUNT * 2 + terminating nullptr entry - char* entries[1 + KEEP_LOG_COUNT * 2 + 1]; - memset(entries, 0, sizeof(entries)); - - unsigned int n = 0; - - if (has_cache) { - // Add LAST_LOG_FILE + LAST_LOG_FILE.x - // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x - for (int i = 0; i < KEEP_LOG_COUNT; i++) { - char* log_file; - int ret; - ret = (i == 0) ? asprintf(&log_file, "%s", LAST_LOG_FILE) : - asprintf(&log_file, "%s.%d", LAST_LOG_FILE, i); - if (ret == -1) { - // memory allocation failure - return early. Should never happen. - return; - } - if ((ensure_path_mounted(log_file) != 0) || (access(log_file, R_OK) == -1)) { - free(log_file); - } else { - entries[n++] = log_file; - } - - char* kmsg_file; - ret = (i == 0) ? asprintf(&kmsg_file, "%s", LAST_KMSG_FILE) : - asprintf(&kmsg_file, "%s.%d", LAST_KMSG_FILE, i); - if (ret == -1) { - // memory allocation failure - return early. Should never happen. - return; - } - if ((ensure_path_mounted(kmsg_file) != 0) || (access(kmsg_file, R_OK) == -1)) { - free(kmsg_file); - } else { - entries[n++] = kmsg_file; - } - } - } else { - // If cache partition is not found, view /tmp/recovery.log instead. - ui->Print("No /cache partition found.\n"); - if (access(TEMPORARY_LOG_FILE, R_OK) == -1) { - return; - } else{ - entries[n++] = strdup(TEMPORARY_LOG_FILE); + std::vector<std::string> entries; + if (has_cache) { + for (int i = 0; i < KEEP_LOG_COUNT; i++) { + auto add_to_entries = [&](const char* filename) { + std::string log_file(filename); + if (i > 0) { + log_file += "." + std::to_string(i); } - } - entries[n++] = strdup("Back"); - - const char* headers[] = { "Select file to view", nullptr }; + if (ensure_path_mounted(log_file.c_str()) == 0 && access(log_file.c_str(), R_OK) == 0) { + entries.push_back(std::move(log_file)); + } + }; - while (true) { - int chosen_item = get_menu_selection(headers, entries, 1, 0, device); - if (strcmp(entries[chosen_item], "Back") == 0) break; + // Add LAST_LOG_FILE + LAST_LOG_FILE.x + add_to_entries(LAST_LOG_FILE); - ui->ShowFile(entries[chosen_item]); + // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x + add_to_entries(LAST_KMSG_FILE); } - - for (size_t i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) { - free(entries[i]); + } else { + // If cache partition is not found, view /tmp/recovery.log instead. + if (access(TEMPORARY_LOG_FILE, R_OK) == -1) { + return; + } else { + entries.push_back(TEMPORARY_LOG_FILE); } -} + } -static void run_graphics_test(Device* device) { - // Switch to graphics screen. - ui->ShowText(false); + entries.push_back("Back"); - ui->SetProgressType(RecoveryUI::INDETERMINATE); - ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); - sleep(1); + std::vector<const char*> menu_entries(entries.size()); + std::transform(entries.cbegin(), entries.cend(), menu_entries.begin(), + [](const std::string& entry) { return entry.c_str(); }); + menu_entries.push_back(nullptr); - ui->SetBackground(RecoveryUI::ERROR); - sleep(1); + const char* headers[] = { "Select file to view", nullptr }; - ui->SetBackground(RecoveryUI::NO_COMMAND); - sleep(1); + int chosen_item = 0; + while (true) { + chosen_item = get_menu_selection(headers, menu_entries.data(), true, chosen_item, device); + if (entries[chosen_item] == "Back") break; - ui->SetBackground(RecoveryUI::ERASING); - sleep(1); - - ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); - - ui->SetProgressType(RecoveryUI::DETERMINATE); - ui->ShowProgress(1.0, 10.0); - float fraction = 0.0; - for (size_t i = 0; i < 100; ++i) { - fraction += .01; - ui->SetProgress(fraction); - usleep(100000); - } + ui->ShowFile(entries[chosen_item].c_str()); + } +} - ui->ShowText(true); +static void run_graphics_test() { + // Switch to graphics screen. + ui->ShowText(false); + + ui->SetProgressType(RecoveryUI::INDETERMINATE); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + sleep(1); + + ui->SetBackground(RecoveryUI::ERROR); + sleep(1); + + ui->SetBackground(RecoveryUI::NO_COMMAND); + sleep(1); + + ui->SetBackground(RecoveryUI::ERASING); + sleep(1); + + // Calling SetBackground() after SetStage() to trigger a redraw. + ui->SetStage(1, 3); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + sleep(1); + ui->SetStage(2, 3); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + sleep(1); + ui->SetStage(3, 3); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + sleep(1); + + ui->SetStage(-1, -1); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + + ui->SetProgressType(RecoveryUI::DETERMINATE); + ui->ShowProgress(1.0, 10.0); + float fraction = 0.0; + for (size_t i = 0; i < 100; ++i) { + fraction += .01; + ui->SetProgress(fraction); + usleep(100000); + } + + ui->ShowText(true); } // How long (in seconds) we wait for the fuse-provided package file to @@ -1120,14 +1034,14 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { return INSTALL_ERROR; } - char* path = browse_directory(SDCARD_ROOT, device); - if (path == NULL) { + std::string path = browse_directory(SDCARD_ROOT, device); + if (path.empty()) { ui->Print("\n-- No package file selected.\n"); ensure_path_unmounted(SDCARD_ROOT); return INSTALL_ERROR; } - ui->Print("\n-- Install %s ...\n", path); + ui->Print("\n-- Install %s ...\n", path.c_str()); set_sdcard_update_bootloader_message(); // We used to use fuse in a thread as opposed to a process. Since accessing @@ -1135,7 +1049,7 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { // to deadlock when a page fault occurs. (Bug: 26313124) pid_t child; if ((child = fork()) == 0) { - bool status = start_sdcard_fuse(path); + bool status = start_sdcard_fuse(path.c_str()); _exit(status ? EXIT_SUCCESS : EXIT_FAILURE); } @@ -1158,7 +1072,7 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { sleep(1); continue; } else { - LOGE("Timed out waiting for the fuse-provided package.\n"); + LOG(ERROR) << "Timed out waiting for the fuse-provided package."; result = INSTALL_ERROR; kill(child, SIGKILL); break; @@ -1180,118 +1094,116 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOGE("Error exit from the fuse process: %d\n", WEXITSTATUS(status)); + LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status); } ensure_path_unmounted(SDCARD_ROOT); return result; } -// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION -// means to take the default, which is to reboot or shutdown depending -// on if the --shutdown_after flag was passed to recovery. -static Device::BuiltinAction -prompt_and_wait(Device* device, int status) { - for (;;) { - finish_recovery(NULL); - switch (status) { - case INSTALL_SUCCESS: - case INSTALL_NONE: - ui->SetBackground(RecoveryUI::NO_COMMAND); - break; - - case INSTALL_ERROR: - case INSTALL_CORRUPT: - ui->SetBackground(RecoveryUI::ERROR); - break; - } - ui->SetProgressType(RecoveryUI::EMPTY); - - int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), 0, 0, device); - - // device-specific code may take some action here. It may - // return one of the core actions handled in the switch - // statement below. - Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item); +// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, +// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. +static Device::BuiltinAction prompt_and_wait(Device* device, int status) { + for (;;) { + finish_recovery(); + switch (status) { + case INSTALL_SUCCESS: + case INSTALL_NONE: + ui->SetBackground(RecoveryUI::NO_COMMAND); + break; - bool should_wipe_cache = false; - switch (chosen_action) { - case Device::NO_ACTION: - break; + case INSTALL_ERROR: + case INSTALL_CORRUPT: + ui->SetBackground(RecoveryUI::ERROR); + break; + } + ui->SetProgressType(RecoveryUI::EMPTY); - case Device::REBOOT: - case Device::SHUTDOWN: - case Device::REBOOT_BOOTLOADER: - return chosen_action; + int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device); - case Device::WIPE_DATA: - wipe_data(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; + // Device-specific code may take some action here. It may return one of the core actions + // handled in the switch statement below. + Device::BuiltinAction chosen_action = + (chosen_item == -1) ? Device::REBOOT : device->InvokeMenuItem(chosen_item); - case Device::WIPE_CACHE: - wipe_cache(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; + bool should_wipe_cache = false; + switch (chosen_action) { + case Device::NO_ACTION: + break; - case Device::APPLY_ADB_SIDELOAD: - case Device::APPLY_SDCARD: - { - bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); - if (adb) { - status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); - } else { - status = apply_from_sdcard(device, &should_wipe_cache); - } + case Device::REBOOT: + case Device::SHUTDOWN: + case Device::REBOOT_BOOTLOADER: + return chosen_action; - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; - } - } + case Device::WIPE_DATA: + if (ui->IsTextVisible()) { + if (ask_to_wipe_data(device)) { + wipe_data(device); + } + } else { + wipe_data(device); + return Device::NO_ACTION; + } + break; - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - copy_logs(); - } else if (!ui->IsTextVisible()) { - return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); - } - } - break; + case Device::WIPE_CACHE: + wipe_cache(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - case Device::VIEW_RECOVERY_LOGS: - choose_recovery_file(device); - break; + case Device::APPLY_ADB_SIDELOAD: + case Device::APPLY_SDCARD: + { + bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); + if (adb) { + status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); + } else { + status = apply_from_sdcard(device, &should_wipe_cache); + } + + if (status == INSTALL_SUCCESS && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } + } + + if (status != INSTALL_SUCCESS) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + copy_logs(); + } else if (!ui->IsTextVisible()) { + return Device::NO_ACTION; // reboot if logs aren't visible + } else { + ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); + } + } + break; - case Device::RUN_GRAPHICS_TEST: - run_graphics_test(device); - break; + case Device::VIEW_RECOVERY_LOGS: + choose_recovery_file(device); + break; - case Device::MOUNT_SYSTEM: - char system_root_image[PROPERTY_VALUE_MAX]; - property_get("ro.build.system_root_image", system_root_image, ""); - - // For a system image built with the root directory (i.e. - // system_root_image == "true"), we mount it to /system_root, and symlink /system - // to /system_root/system to make adb shell work (the symlink is created through - // the build system). - // Bug: 22855115 - if (strcmp(system_root_image, "true") == 0) { - if (ensure_path_mounted_at("/", "/system_root") != -1) { - ui->Print("Mounted /system.\n"); - } - } else { - if (ensure_path_mounted("/system") != -1) { - ui->Print("Mounted /system.\n"); - } - } + case Device::RUN_GRAPHICS_TEST: + run_graphics_test(); + break; - break; + case Device::MOUNT_SYSTEM: + // For a system image built with the root directory (i.e. system_root_image == "true"), we + // mount it to /system_root, and symlink /system to /system_root/system to make adb shell + // work (the symlink is created through the build system). (Bug: 22855115) + if (android::base::GetBoolProperty("ro.build.system_root_image", false)) { + if (ensure_path_mounted_at("/", "/system_root") != -1) { + ui->Print("Mounted /system.\n"); + } + } else { + if (ensure_path_mounted("/system") != -1) { + ui->Print("Mounted /system.\n"); + } } + break; } + } } static void @@ -1299,40 +1211,44 @@ print_property(const char *key, const char *name, void *cookie) { printf("%s=%s\n", key, name); } -static void -load_locale_from_cache() { - FILE* fp = fopen_path(LOCALE_FILE, "r"); - char buffer[80]; - if (fp != NULL) { - fgets(buffer, sizeof(buffer), fp); - int j = 0; - unsigned int i; - for (i = 0; i < sizeof(buffer) && buffer[i]; ++i) { - if (!isspace(buffer[i])) { - buffer[j++] = buffer[i]; - } - } - buffer[j] = 0; - locale = strdup(buffer); - check_and_fclose(fp, LOCALE_FILE); +static std::string load_locale_from_cache() { + if (ensure_path_mounted(LOCALE_FILE) != 0) { + LOG(ERROR) << "Can't mount " << LOCALE_FILE; + return ""; } -} -static RecoveryUI* gCurrentUI = NULL; + std::string content; + if (!android::base::ReadFileToString(LOCALE_FILE, &content)) { + PLOG(ERROR) << "Can't read " << LOCALE_FILE; + return ""; + } -void -ui_print(const char* format, ...) { - char buffer[256]; + return android::base::Trim(content); +} +void ui_print(const char* format, ...) { + std::string buffer; va_list ap; va_start(ap, format); - vsnprintf(buffer, sizeof(buffer), format, ap); + android::base::StringAppendV(&buffer, format, ap); va_end(ap); - if (gCurrentUI != NULL) { - gCurrentUI->Print("%s", buffer); + if (ui != nullptr) { + ui->Print("%s", buffer.c_str()); + } else { + fputs(buffer.c_str(), stdout); + } +} + +static constexpr char log_characters[] = "VDIWEF"; + +void UiLogger(android::base::LogId id, android::base::LogSeverity severity, + const char* tag, const char* file, unsigned int line, + const char* message) { + if (severity >= android::base::ERROR && ui != nullptr) { + ui->Print("E:%s\n", message); } else { - fputs(buffer, stdout); + fprintf(stdout, "%c:%s\n", log_characters[severity], message); } } @@ -1390,42 +1306,32 @@ static bool is_battery_ok() { } } -static void set_retry_bootloader_message(int retry_count, int argc, char** argv) { - bootloader_message boot = {}; - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - - for (int i = 1; i < argc; ++i) { - if (strstr(argv[i], "retry_count") == nullptr) { - strlcat(boot.recovery, argv[i], sizeof(boot.recovery)); - strlcat(boot.recovery, "\n", sizeof(boot.recovery)); - } +static void set_retry_bootloader_message(int retry_count, const std::vector<std::string>& args) { + std::vector<std::string> options; + for (const auto& arg : args) { + if (!android::base::StartsWith(arg, "--retry_count")) { + options.push_back(arg); } + } - // Initialize counter to 1 if it's not in BCB, otherwise increment it by 1. - if (retry_count == 0) { - strlcat(boot.recovery, "--retry_count=1\n", sizeof(boot.recovery)); - } else { - char buffer[20]; - snprintf(buffer, sizeof(buffer), "--retry_count=%d\n", retry_count+1); - strlcat(boot.recovery, buffer, sizeof(boot.recovery)); - } - std::string err; - if (!write_bootloader_message(boot, &err)) { - LOGE("%s\n", err.c_str()); - } + // Increment the retry counter by 1. + options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count + 1)); + std::string err; + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << err; + } } static bool bootreason_in_blacklist() { - char bootreason[PROPERTY_VALUE_MAX]; - if (property_get("ro.boot.bootreason", bootreason, nullptr) > 0) { - for (const auto& str : bootreason_blacklist) { - if (strcasecmp(str.c_str(), bootreason) == 0) { - return true; - } - } - } - return false; + std::string bootreason = android::base::GetProperty("ro.boot.bootreason", ""); + if (!bootreason.empty()) { + for (const auto& str : bootreason_blacklist) { + if (strcasecmp(str.c_str(), bootreason.c_str()) == 0) { + return true; + } + } + } + return false; } static void log_failure_code(ErrorCode code, const char *update_package) { @@ -1436,68 +1342,23 @@ static void log_failure_code(ErrorCode code, const char *update_package) { }; std::string log_content = android::base::Join(log_buffer, "\n"); if (!android::base::WriteStringToFile(log_content, TEMPORARY_INSTALL_FILE)) { - LOGE("failed to write %s: %s\n", TEMPORARY_INSTALL_FILE, strerror(errno)); + PLOG(ERROR) << "failed to write " << TEMPORARY_INSTALL_FILE; } // Also write the info into last_log. - LOGI("%s\n", log_content.c_str()); -} - -static ssize_t logbasename( - log_id_t /* logId */, - char /* prio */, - const char *filename, - const char * /* buf */, size_t len, - void *arg) { - if (strstr(LAST_KMSG_FILE, filename) || - strstr(LAST_LOG_FILE, filename)) { - bool *doRotate = reinterpret_cast<bool *>(arg); - *doRotate = true; - } - return len; -} - -static ssize_t logrotate( - log_id_t logId, - char prio, - const char *filename, - const char *buf, size_t len, - void *arg) { - bool *doRotate = reinterpret_cast<bool *>(arg); - if (!*doRotate) { - return __android_log_pmsg_file_write(logId, prio, filename, buf, len); - } - - std::string name(filename); - size_t dot = name.find_last_of("."); - std::string sub = name.substr(0, dot); - - if (!strstr(LAST_KMSG_FILE, sub.c_str()) && - !strstr(LAST_LOG_FILE, sub.c_str())) { - return __android_log_pmsg_file_write(logId, prio, filename, buf, len); - } - - // filename rotation - if (dot == std::string::npos) { - name += ".1"; - } else { - std::string number = name.substr(dot + 1); - if (!isdigit(number.data()[0])) { - name += ".1"; - } else { - unsigned long long i = std::stoull(number); - name = sub + "." + std::to_string(i + 1); - } - } - - return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len); + LOG(INFO) << log_content; } int main(int argc, char **argv) { + // We don't have logcat yet under recovery; so we'll print error on screen and + // log to stdout (which is redirected to recovery.log) as we used to do. + android::base::InitLogging(argv, &UiLogger); + // Take last pmsg contents and rewrite it to the current pmsg session. static const char filter[] = "recovery/"; // Do we need to rotate? bool doRotate = false; + __android_log_pmsg_file_read( LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate); @@ -1514,7 +1375,7 @@ int main(int argc, char **argv) { // only way recovery should be run with this argument is when it // starts a copy of itself from the apply_from_adb() function. if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { - adb_server_main(0, DEFAULT_ADB_PORT, -1); + minadbd_main(); return 0; } @@ -1529,11 +1390,14 @@ int main(int argc, char **argv) { load_volume_table(); has_cache = volume_for_path(CACHE_ROOT) != nullptr; - get_args(&argc, &argv); + std::vector<std::string> args = get_args(argc, argv); + std::vector<char*> args_to_parse(args.size()); + std::transform(args.cbegin(), args.cend(), args_to_parse.begin(), + [](const std::string& arg) { return const_cast<char*>(arg.c_str()); }); - const char *send_intent = NULL; const char *update_package = NULL; bool should_wipe_data = false; + bool should_prompt_and_wipe_data = false; bool should_wipe_cache = false; bool should_wipe_ab = false; size_t wipe_package_size = 0; @@ -1547,9 +1411,9 @@ int main(int argc, char **argv) { int arg; int option_index; - while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { + while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS, + &option_index)) != -1) { switch (arg) { - case 'i': send_intent = optarg; break; case 'n': android::base::ParseInt(optarg, &retry_count, 0); break; case 'u': update_package = optarg; break; case 'w': should_wipe_data = true; break; @@ -1559,64 +1423,67 @@ int main(int argc, char **argv) { case 'a': sideload = true; sideload_auto_reboot = true; break; case 'x': just_exit = true; break; case 'l': locale = optarg; break; - case 'g': { - if (stage == NULL || *stage == '\0') { - char buffer[20] = "1/"; - strncat(buffer, optarg, sizeof(buffer)-3); - stage = strdup(buffer); - } - break; - } case 'p': shutdown_after = true; break; case 'r': reason = optarg; break; case 'e': security_update = true; break; case 0: { - if (strcmp(OPTIONS[option_index].name, "wipe_ab") == 0) { + std::string option = OPTIONS[option_index].name; + if (option == "wipe_ab") { should_wipe_ab = true; - break; - } else if (strcmp(OPTIONS[option_index].name, "wipe_package_size") == 0) { + } else if (option == "wipe_package_size") { android::base::ParseUint(optarg, &wipe_package_size); - break; + } else if (option == "prompt_and_wipe_data") { + should_prompt_and_wipe_data = true; } break; } case '?': - LOGE("Invalid command argument\n"); + LOG(ERROR) << "Invalid command argument"; continue; } } - if (locale == nullptr && has_cache) { - load_locale_from_cache(); + if (locale.empty()) { + if (has_cache) { + locale = load_locale_from_cache(); + } + + if (locale.empty()) { + locale = DEFAULT_LOCALE; + } } - printf("locale is [%s]\n", locale); - printf("stage is [%s]\n", stage); + + printf("locale is [%s]\n", locale.c_str()); + printf("stage is [%s]\n", stage.c_str()); printf("reason is [%s]\n", reason); Device* device = make_device(); - ui = device->GetUI(); - gCurrentUI = ui; + if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { + printf("Quiescent recovery mode.\n"); + ui = new StubRecoveryUI(); + } else { + ui = device->GetUI(); + + if (!ui->Init(locale)) { + printf("Failed to initialize UI, use stub UI instead.\n"); + ui = new StubRecoveryUI(); + } + } - ui->SetLocale(locale); - ui->Init(); // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". ui->SetSystemUpdateText(security_update); int st_cur, st_max; - if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) { + if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) { ui->SetStage(st_cur, st_max); } ui->SetBackground(RecoveryUI::NONE); if (show_text) ui->ShowText(true); - struct selinux_opt seopts[] = { - { SELABEL_OPT_PATH, "/file_contexts" } - }; - - sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); - + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); if (!sehandle) { ui->Print("Warning: No file_contexts\n"); } @@ -1624,30 +1491,10 @@ int main(int argc, char **argv) { device->StartRecovery(); 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); - if (modified_path) { - 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; - } - else - printf("modified_path allocation failed\n"); - } + for (const auto& arg : args) { + printf(" \"%s\"", arg.c_str()); } - printf("\n"); + printf("\n\n"); property_list(print_property, NULL); printf("\n"); @@ -1685,13 +1532,12 @@ int main(int argc, char **argv) { // times before we abandon this OTA update. if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) { copy_logs(); - set_retry_bootloader_message(retry_count, argc, argv); + set_retry_bootloader_message(retry_count, args); // Print retry count on screen. ui->Print("Retry attempt %d\n", retry_count); // Reboot and retry the update - int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery"); - if (ret < 0) { + if (!reboot("reboot,recovery")) { ui->Print("Reboot failed\n"); } else { while (true) { @@ -1708,9 +1554,16 @@ int main(int argc, char **argv) { } } } else if (should_wipe_data) { - if (!wipe_data(false, device)) { + if (!wipe_data(device)) { + status = INSTALL_ERROR; + } + } else if (should_prompt_and_wipe_data) { + ui->ShowText(true); + ui->SetBackground(RecoveryUI::ERROR); + if (!prompt_and_wipe_data(device)) { status = INSTALL_ERROR; } + ui->ShowText(false); } else if (should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; @@ -1766,26 +1619,26 @@ int main(int argc, char **argv) { } // Save logs and clean up before rebooting or shutting down. - finish_recovery(send_intent); + finish_recovery(); switch (after) { case Device::SHUTDOWN: ui->Print("Shutting down...\n"); - property_set(ANDROID_RB_PROPERTY, "shutdown,"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); break; case Device::REBOOT_BOOTLOADER: ui->Print("Rebooting to bootloader...\n"); - property_set(ANDROID_RB_PROPERTY, "reboot,bootloader"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); break; default: ui->Print("Rebooting...\n"); - property_set(ANDROID_RB_PROPERTY, "reboot,"); + reboot("reboot,"); break; } while (true) { - pause(); + pause(); } // Should be unreachable. return EXIT_SUCCESS; |