summaryrefslogtreecommitdiffstats
path: root/recovery.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'recovery.cpp')
-rw-r--r--recovery.cpp305
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: