summaryrefslogtreecommitdiffstats
path: root/recovery.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--recovery.cpp621
1 files changed, 543 insertions, 78 deletions
diff --git a/recovery.cpp b/recovery.cpp
index b7a545898..10f84140d 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -28,33 +28,44 @@
#include <sys/klog.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
-#include <base/file.h>
-#include <base/stringprintf.h>
+#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 <log/logger.h> /* Android Log packet format */
+#include <private/android_logger.h> /* private pmsg functions */
+
+#include <healthd/BatteryMonitor.h>
+
+#include "adb_install.h"
#include "bootloader.h"
#include "common.h"
-#include "cutils/properties.h"
-#include "cutils/android_reboot.h"
+#include "device.h"
+#include "error_code.h"
+#include "fuse_sdcard_provider.h"
+#include "fuse_sideload.h"
#include "install.h"
#include "minui/minui.h"
#include "minzip/DirUtil.h"
#include "roots.h"
#include "ui.h"
#include "screen_ui.h"
-#include "device.h"
-#include "adb_install.h"
-#include "adb.h"
-#include "fuse_sideload.h"
-#include "fuse_sdcard_provider.h"
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' },
@@ -65,6 +76,7 @@ static const struct option OPTIONS[] = {
{ "stages", required_argument, NULL, 'g' },
{ "shutdown_after", no_argument, NULL, 'p' },
{ "reason", required_argument, NULL, 'r' },
+ { "security", no_argument, NULL, 'e'},
{ NULL, 0, NULL, 0 },
};
@@ -74,19 +86,31 @@ 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";
+static const char *CONVERT_FBE_DIR = "/tmp/convert_fbe";
+static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe";
static const char *CACHE_ROOT = "/cache";
+static const char *DATA_ROOT = "/data";
static const char *SDCARD_ROOT = "/sdcard";
static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
static const char *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;
+// 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;
+static const char* locale = "en_US";
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.
@@ -151,8 +175,7 @@ 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) {
+FILE* fopen_path(const char *path, const char *mode) {
if (ensure_path_mounted(path) != 0) {
LOGE("Can't mount %s\n", path);
return NULL;
@@ -166,23 +189,102 @@ fopen_path(const char *path, const char *mode) {
return fp;
}
+// close a file, log an error if the error indicator is set
+static void check_and_fclose(FILE *fp, const char *name) {
+ fflush(fp);
+ if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno));
+ fclose(fp);
+}
+
bool is_ro_debuggable() {
char value[PROPERTY_VALUE_MAX+1];
return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1');
}
static void redirect_stdio(const char* filename) {
- // If these fail, there's not really anywhere to complain...
- freopen(filename, "a", stdout); setbuf(stdout, NULL);
- freopen(filename, "a", stderr); setbuf(stderr, NULL);
-}
+ int pipefd[2];
+ if (pipe(pipefd) == -1) {
+ LOGE("pipe failed: %s\n", strerror(errno));
-// close a file, log an error if the error indicator is set
-static void
-check_and_fclose(FILE *fp, const char *name) {
- fflush(fp);
- if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno));
- fclose(fp);
+ // Fall back to traditional logging mode without timestamps.
+ // If these fail, there's not really anywhere to complain...
+ freopen(filename, "a", stdout); setbuf(stdout, NULL);
+ freopen(filename, "a", stderr); setbuf(stderr, NULL);
+
+ return;
+ }
+
+ pid_t pid = fork();
+ if (pid == -1) {
+ LOGE("fork failed: %s\n", strerror(errno));
+
+ // Fall back to traditional logging mode without timestamps.
+ // If these fail, there's not really anywhere to complain...
+ freopen(filename, "a", stdout); setbuf(stdout, NULL);
+ freopen(filename, "a", stderr); setbuf(stderr, NULL);
+
+ return;
+ }
+
+ if (pid == 0) {
+ /// Close the unused write end.
+ close(pipefd[1]);
+
+ auto start = std::chrono::steady_clock::now();
+
+ // 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));
+ close(pipefd[0]);
+ _exit(1);
+ }
+
+ FILE* pipe_fp = fdopen(pipefd[0], "r");
+ if (pipe_fp == nullptr) {
+ LOGE("fdopen failed: %s\n", strerror(errno));
+ check_and_fclose(log_fp, filename);
+ close(pipefd[0]);
+ _exit(1);
+ }
+
+ char* line = nullptr;
+ size_t len = 0;
+ while (getline(&line, &len, pipe_fp) != -1) {
+ auto now = std::chrono::steady_clock::now();
+ double duration = std::chrono::duration_cast<std::chrono::duration<double>>(
+ now - start).count();
+ if (line[0] == '\n') {
+ fprintf(log_fp, "[%12.6lf]\n", duration);
+ } else {
+ fprintf(log_fp, "[%12.6lf] %s", duration, line);
+ }
+ fflush(log_fp);
+ }
+
+ LOGE("getline failed: %s\n", strerror(errno));
+
+ free(line);
+ check_and_fclose(log_fp, filename);
+ close(pipefd[0]);
+ _exit(1);
+ } else {
+ // Redirect stdout/stderr to the logger process.
+ // Close the unused read end.
+ close(pipefd[0]);
+
+ setbuf(stdout, nullptr);
+ setbuf(stderr, nullptr);
+
+ if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
+ LOGE("dup2 stdout failed: %s\n", strerror(errno));
+ }
+ if (dup2(pipefd[1], STDERR_FILENO) == -1) {
+ LOGE("dup2 stderr failed: %s\n", strerror(errno));
+ }
+
+ close(pipefd[1]);
+ }
}
// command line args come from, in decreasing precedence:
@@ -221,8 +323,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;
@@ -285,6 +387,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;
@@ -326,14 +440,18 @@ static void rotate_logs(int max) {
ensure_path_mounted(LAST_KMSG_FILE);
for (int i = max-1; i >= 0; --i) {
- std::string old_log = android::base::StringPrintf((i == 0) ? "%s" : "%s.%d",
- LAST_LOG_FILE, 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((i == 0) ? "%s" : "%s.%d",
- LAST_KMSG_FILE, i);
+ 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());
}
@@ -348,6 +466,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.
@@ -371,7 +498,7 @@ static void copy_logs() {
static void
finish_recovery(const char *send_intent) {
// By this point, we're ready to return to the main system...
- if (send_intent != NULL) {
+ if (send_intent != NULL && has_cache) {
FILE *fp = fopen_path(INTENT_FILE, "w");
if (fp == NULL) {
LOGE("Can't open %s\n", INTENT_FILE);
@@ -385,12 +512,16 @@ finish_recovery(const char *send_intent) {
// 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();
@@ -401,12 +532,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.
}
@@ -419,6 +551,7 @@ typedef struct _saved_log_file {
static bool erase_volume(const char* volume) {
bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+ bool is_data = (strcmp(volume, DATA_ROOT) == 0);
ui->SetBackground(RecoveryUI::ERASING);
ui->SetProgressType(RecoveryUI::INDETERMINATE);
@@ -473,7 +606,28 @@ static bool erase_volume(const char* volume) {
ui->Print("Formatting %s...\n", volume);
ensure_path_unmounted(volume);
- int result = format_volume(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) {
while (head) {
@@ -675,7 +829,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;
@@ -683,6 +837,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;
}
@@ -696,6 +855,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));
@@ -706,7 +870,10 @@ static void choose_recovery_file(Device* device) {
// Add LAST_KMSG_FILE + LAST_KMSG_FILE.x
for (int i = 0; i < KEEP_LOG_COUNT; i++) {
char* log_file;
- if (asprintf(&log_file, (i == 0) ? "%s" : "%s.%d", LAST_LOG_FILE, i) == -1) {
+ 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;
}
@@ -717,7 +884,9 @@ static void choose_recovery_file(Device* device) {
}
char* kmsg_file;
- if (asprintf(&kmsg_file, (i == 0) ? "%s" : "%s.%d", LAST_KMSG_FILE, i) == -1) {
+ 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;
}
@@ -736,10 +905,7 @@ static void choose_recovery_file(Device* device) {
int chosen_item = get_menu_selection(headers, entries, 1, 0, device);
if (strcmp(entries[chosen_item], "Back") == 0) break;
- // TODO: do we need to redirect? ShowFile could just avoid writing to stdio.
- redirect_stdio("/dev/null");
ui->ShowFile(entries[chosen_item]);
- redirect_stdio(TEMPORARY_LOG_FILE);
}
for (size_t i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) {
@@ -747,6 +913,41 @@ static void choose_recovery_file(Device* device) {
}
}
+static void run_graphics_test(Device* device) {
+ // 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);
+
+ 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
+// appear, before timing out.
+#define SDCARD_INSTALL_TIMEOUT 10
+
static int apply_from_sdcard(Device* device, bool* wipe_cache) {
modified_flash = true;
@@ -758,19 +959,68 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) {
char* path = browse_directory(SDCARD_ROOT, device);
if (path == NULL) {
ui->Print("\n-- No package file selected.\n");
+ ensure_path_unmounted(SDCARD_ROOT);
return INSTALL_ERROR;
}
ui->Print("\n-- Install %s ...\n", path);
set_sdcard_update_bootloader_message();
- void* token = start_sdcard_fuse(path);
- int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache,
- TEMPORARY_INSTALL_FILE, false);
+ // We used to use fuse in a thread as opposed to a process. Since accessing
+ // through fuse involves going from kernel to userspace to kernel, it leads
+ // to deadlock when a page fault occurs. (Bug: 26313124)
+ pid_t child;
+ if ((child = fork()) == 0) {
+ bool status = start_sdcard_fuse(path);
+
+ _exit(status ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child
+ // process is ready.
+ int result = INSTALL_ERROR;
+ int status;
+ bool waited = false;
+ for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) {
+ if (waitpid(child, &status, WNOHANG) == -1) {
+ result = INSTALL_ERROR;
+ waited = true;
+ break;
+ }
+
+ struct stat sb;
+ if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &sb) == -1) {
+ if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT-1) {
+ sleep(1);
+ continue;
+ } else {
+ LOGE("Timed out waiting for the fuse-provided package.\n");
+ result = INSTALL_ERROR;
+ kill(child, SIGKILL);
+ break;
+ }
+ }
+
+ result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache,
+ TEMPORARY_INSTALL_FILE, false, 0/*retry_count*/);
+ break;
+ }
+
+ if (!waited) {
+ // Calling stat() on this magic filename signals the fuse
+ // filesystem to shut down.
+ struct stat sb;
+ stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &sb);
+
+ waitpid(child, &status, 0);
+ }
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ LOGE("Error exit from the fuse process: %d\n", WEXITSTATUS(status));
+ }
- finish_sdcard_fuse(token);
ensure_path_unmounted(SDCARD_ROOT);
- return status;
+ return result;
}
// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION
@@ -852,10 +1102,29 @@ prompt_and_wait(Device* device, int status) {
choose_recovery_file(device);
break;
+ case Device::RUN_GRAPHICS_TEST:
+ run_graphics_test(device);
+ break;
+
case Device::MOUNT_SYSTEM:
- if (ensure_path_mounted("/system") != -1) {
- ui->Print("Mounted /system.\n");
+ 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");
+ }
}
+
break;
}
}
@@ -903,11 +1172,145 @@ ui_print(const char* format, ...) {
}
}
-int
-main(int argc, char **argv) {
- time_t start = time(NULL);
+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);
- redirect_stdio(TEMPORARY_LOG_FILE);
+ 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
@@ -917,13 +1320,21 @@ 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_main(0, DEFAULT_ADB_PORT);
+ adb_server_main(0, DEFAULT_ADB_PORT, -1);
return 0;
}
+ time_t start = time(NULL);
+
+ // redirect_stdio should be called only in non-sideload mode. Otherwise
+ // we may have two logger instances with different timestamps.
+ redirect_stdio(TEMPORARY_LOG_FILE);
+
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;
@@ -935,11 +1346,14 @@ main(int argc, char **argv) {
bool sideload_auto_reboot = false;
bool just_exit = false;
bool shutdown_after = false;
+ int retry_count = 0;
+ bool security_update = false;
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;
@@ -958,13 +1372,14 @@ main(int argc, char **argv) {
}
case 'p': shutdown_after = true; break;
case 'r': reason = optarg; break;
+ case 'e': security_update = true; break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
}
- if (locale == NULL) {
+ if (locale == nullptr && has_cache) {
load_locale_from_cache();
}
printf("locale is [%s]\n", locale);
@@ -977,6 +1392,9 @@ main(int argc, char **argv) {
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) {
@@ -1011,11 +1429,15 @@ main(int argc, char **argv) {
if (strncmp(update_package, "CACHE:", 6) == 0) {
int len = strlen(update_package) + 10;
char* modified_path = (char*)malloc(len);
- strlcpy(modified_path, "/cache/", len);
- strlcat(modified_path, update_package+6, len);
- printf("(replacing path \"%s\" with \"%s\")\n",
- update_package, modified_path);
- update_package = modified_path;
+ 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");
}
}
printf("\n");
@@ -1028,18 +1450,57 @@ 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);
+ // It's not entirely true that we will modify the flash. But we want
+ // to log the update attempt since update_package is non-NULL.
+ modified_flash = true;
+
+ if (!is_battery_ok()) {
+ ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
+ BATTERY_OK_PERCENTAGE);
+ // Log the error code to last_install when installation skips due to
+ // low battery.
+ FILE* install_log = fopen_path(LAST_INSTALL_FILE, "w");
+ if (install_log != nullptr) {
+ fprintf(install_log, "%s\n", update_package);
+ fprintf(install_log, "0\n");
+ fprintf(install_log, "error: %d\n", kLowBattery);
+ fclose(install_log);
+ } else {
+ LOGE("failed to open last_install: %s\n", strerror(errno));
+ }
+ status = INSTALL_SKIPPED;
+ } else {
+ status = install_package(update_package, &should_wipe_cache,
+ TEMPORARY_INSTALL_FILE, true, retry_count);
+ 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) {
@@ -1088,7 +1549,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;
@@ -1114,6 +1576,9 @@ main(int argc, char **argv) {
property_set(ANDROID_RB_PROPERTY, "reboot,");
break;
}
- sleep(5); // should reboot before this finishes
+ while (true) {
+ pause();
+ }
+ // Should be unreachable.
return EXIT_SUCCESS;
}