diff options
Diffstat (limited to '')
-rw-r--r-- | recovery.cpp | 305 |
1 files changed, 253 insertions, 52 deletions
diff --git a/recovery.cpp b/recovery.cpp index 17e9eb66f..169413ad5 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -35,10 +35,17 @@ #include <chrono> #include <adb.h> +#include <android/log.h> /* Android Log Priority Tags */ #include <android-base/file.h> +#include <android-base/parseint.h> #include <android-base/stringprintf.h> #include <cutils/android_reboot.h> #include <cutils/properties.h> +#include <healthd/BatteryMonitor.h> +#include <log/logger.h> /* Android Log packet format */ +#include <private/android_logger.h> /* private pmsg functions */ +#include <selinux/label.h> +#include <selinux/selinux.h> #include "adb_install.h" #include "bootloader.h" @@ -56,8 +63,8 @@ struct selabel_handle *sehandle; 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' }, { "wipe_cache", no_argument, NULL, 'c' }, { "show_text", no_argument, NULL, 't' }, @@ -73,7 +80,6 @@ static const struct option OPTIONS[] = { 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"; @@ -84,21 +90,27 @@ 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; +static const int EIO_RETRY_COUNT = 2; +static const int BATTERY_READ_TIMEOUT_IN_SEC = 10; +// GmsCore enters recovery mode to install package when having enough battery +// percentage. Normally, the threshold is 40% without charger and 20% with charger. +// 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; RecoveryUI* ui = NULL; char* locale = NULL; char* stage = NULL; char* reason = NULL; bool modified_flash = false; +static bool has_cache = false; /* * 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 @@ -302,8 +314,8 @@ get_args(int *argc, char ***argv) { } } - // --- if that doesn't work, try the command file - if (*argc <= 1) { + // --- 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; @@ -366,6 +378,18 @@ static void save_kernel_log(const char* destination) { android::base::WriteStringToFile(buffer, destination); } +// write content to the current pmsg session. +static ssize_t __pmsg_write(const char *filename, const char *buf, size_t len) { + return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, + filename, buf, len); +} + +static void copy_log_file_to_pmsg(const char* source, const char* destination) { + std::string content; + android::base::ReadFileToString(source, &content); + __pmsg_write(destination, content.c_str(), content.length()); +} + // How much of the temp log we have copied to the copy in cache. static long tmplog_offset = 0; @@ -433,6 +457,15 @@ static void copy_logs() { return; } + // Always write to pmsg, this allows the OTA logs to be caught in logcat -L + copy_log_file_to_pmsg(TEMPORARY_LOG_FILE, LAST_LOG_FILE); + copy_log_file_to_pmsg(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE); + + // We can do nothing for now if there's no /cache partition. + if (!has_cache) { + return; + } + rotate_logs(KEEP_LOG_COUNT); // Copy logs to cache so the system can find out what happened. @@ -450,32 +483,24 @@ static void copy_logs() { } // 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. +// 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(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); - } - } - +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) { - LOGI("Saving locale \"%s\"\n", locale); - FILE* fp = fopen_path(LOCALE_FILE, "w"); - fwrite(locale, 1, strlen(locale), fp); - fflush(fp); - fsync(fileno(fp)); - check_and_fclose(fp, LOCALE_FILE); + 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); + } } copy_logs(); @@ -486,12 +511,13 @@ finish_recovery(const char *send_intent) { 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); + if (has_cache) { + 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); } - ensure_path_unmounted(CACHE_ROOT); sync(); // For good measure. } @@ -760,7 +786,7 @@ static bool wipe_data(int should_confirm, Device* device) { bool success = device->PreWipeData() && erase_volume("/data") && - erase_volume("/cache") && + (has_cache ? erase_volume("/cache") : true) && device->PostWipeData(); ui->Print("Data wipe %s.\n", success ? "complete" : "failed"); return success; @@ -768,6 +794,11 @@ static bool wipe_data(int should_confirm, Device* device) { // Return true on success. static bool wipe_cache(bool should_confirm, Device* device) { + if (!has_cache) { + ui->Print("No /cache partition found.\n"); + return false; + } + if (should_confirm && !yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!")) { return false; } @@ -781,6 +812,11 @@ static bool wipe_cache(bool should_confirm, Device* device) { } static void choose_recovery_file(Device* device) { + if (!has_cache) { + ui->Print("No /cache partition found.\n"); + return; + } + // "Back" + KEEP_LOG_COUNT * 2 + terminating nullptr entry char* entries[1 + KEEP_LOG_COUNT * 2 + 1]; memset(entries, 0, sizeof(entries)); @@ -919,7 +955,7 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { static Device::BuiltinAction prompt_and_wait(Device* device, int status) { for (;;) { - finish_recovery(NULL); + finish_recovery(); switch (status) { case INSTALL_SUCCESS: case INSTALL_NONE: @@ -1058,8 +1094,146 @@ ui_print(const char* format, ...) { } } -int -main(int argc, char **argv) { +static bool is_battery_ok() { + struct healthd_config healthd_config = { + .batteryStatusPath = android::String8(android::String8::kEmptyString), + .batteryHealthPath = android::String8(android::String8::kEmptyString), + .batteryPresentPath = android::String8(android::String8::kEmptyString), + .batteryCapacityPath = android::String8(android::String8::kEmptyString), + .batteryVoltagePath = android::String8(android::String8::kEmptyString), + .batteryTemperaturePath = android::String8(android::String8::kEmptyString), + .batteryTechnologyPath = android::String8(android::String8::kEmptyString), + .batteryCurrentNowPath = android::String8(android::String8::kEmptyString), + .batteryCurrentAvgPath = android::String8(android::String8::kEmptyString), + .batteryChargeCounterPath = android::String8(android::String8::kEmptyString), + .batteryFullChargePath = android::String8(android::String8::kEmptyString), + .batteryCycleCountPath = android::String8(android::String8::kEmptyString), + .energyCounter = NULL, + .boot_min_cap = 0, + .screen_on = NULL + }; + healthd_board_init(&healthd_config); + + android::BatteryMonitor monitor; + monitor.init(&healthd_config); + + int wait_second = 0; + while (true) { + int charge_status = monitor.getChargeStatus(); + // Treat unknown status as charged. + bool charged = (charge_status != android::BATTERY_STATUS_DISCHARGING && + charge_status != android::BATTERY_STATUS_NOT_CHARGING); + android::BatteryProperty capacity; + android::status_t status = monitor.getProperty(android::BATTERY_PROP_CAPACITY, &capacity); + ui_print("charge_status %d, charged %d, status %d, capacity %lld\n", charge_status, + charged, status, capacity.valueInt64); + // At startup, the battery drivers in devices like N5X/N6P take some time to load + // the battery profile. Before the load finishes, it reports value 50 as a fake + // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected + // to finish loading the battery profile earlier than 10 seconds after kernel startup. + if (status == 0 && capacity.valueInt64 == 50) { + if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) { + sleep(1); + wait_second++; + continue; + } + } + // If we can't read battery percentage, it may be a device without battery. In this + // situation, use 100 as a fake battery percentage. + if (status != 0) { + capacity.valueInt64 = 100; + } + return (charged && capacity.valueInt64 >= BATTERY_WITH_CHARGER_OK_PERCENTAGE) || + (!charged && capacity.valueInt64 >= BATTERY_OK_PERCENTAGE); + } +} + +static void set_retry_bootloader_message(int retry_count, int argc, char** argv) { + struct 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)); + } + } + + // 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)); + } + set_bootloader_message(&boot); +} + +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); +} + +int main(int argc, char **argv) { + // 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); + // Take action to refresh pmsg contents + __android_log_pmsg_file_read( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, + logrotate, &doRotate); + // If this binary is started with the single argument "--adbd", // instead of being the normal recovery binary, it turns into kind // of a stripped-down version of adbd that only supports the @@ -1081,9 +1255,10 @@ main(int argc, char **argv) { printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table(); + has_cache = volume_for_path(CACHE_ROOT) != nullptr; + get_args(&argc, &argv); - const char *send_intent = NULL; const char *update_package = NULL; bool should_wipe_data = false; bool should_wipe_cache = false; @@ -1092,11 +1267,12 @@ main(int argc, char **argv) { bool sideload_auto_reboot = false; bool just_exit = false; bool shutdown_after = false; + int retry_count = 0; int arg; while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -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; case 'c': should_wipe_cache = true; break; @@ -1121,7 +1297,7 @@ main(int argc, char **argv) { } } - if (locale == NULL) { + if (locale == nullptr && has_cache) { load_locale_from_cache(); } printf("locale is [%s]\n", locale); @@ -1189,18 +1365,42 @@ main(int argc, char **argv) { int status = INSTALL_SUCCESS; if (update_package != NULL) { - status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true); - if (status == INSTALL_SUCCESS && should_wipe_cache) { - wipe_cache(false, device); - } - if (status != INSTALL_SUCCESS) { - ui->Print("Installation aborted.\n"); - - // If this is an eng or userdebug build, then automatically - // turn the text display on if the script fails so the error - // message is visible. - if (is_ro_debuggable()) { - ui->ShowText(true); + if (!is_battery_ok()) { + ui->Print("battery capacity is not enough for installing package, needed is %d%%\n", + BATTERY_OK_PERCENTAGE); + status = INSTALL_SKIPPED; + } else { + status = install_package(update_package, &should_wipe_cache, + TEMPORARY_INSTALL_FILE, true); + if (status == INSTALL_SUCCESS && should_wipe_cache) { + wipe_cache(false, device); + } + if (status != INSTALL_SUCCESS) { + ui->Print("Installation aborted.\n"); + // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT + // 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); + // 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) { + ui->Print("Reboot failed\n"); + } else { + while (true) { + pause(); + } + } + } + // If this is an eng or userdebug build, then automatically + // turn the text display on if the script fails so the error + // message is visible. + if (is_ro_debuggable()) { + ui->ShowText(true); + } } } } else if (should_wipe_data) { @@ -1249,7 +1449,8 @@ main(int argc, char **argv) { } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; - if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) { + if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) || + ui->IsTextVisible()) { Device::BuiltinAction temp = prompt_and_wait(device, status); if (temp != Device::NO_ACTION) { after = temp; @@ -1257,7 +1458,7 @@ 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: |