summaryrefslogtreecommitdiffstats
path: root/recovery.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'recovery.cpp')
-rw-r--r--recovery.cpp397
1 files changed, 179 insertions, 218 deletions
diff --git a/recovery.cpp b/recovery.cpp
index 575e287aa..4dd827919 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -43,20 +43,20 @@
#include "screen_ui.h"
#include "device.h"
#include "adb_install.h"
-extern "C" {
-#include "minadbd/adb.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, 's' },
+ { "send_intent", required_argument, NULL, 'i' },
{ "update_package", required_argument, NULL, 'u' },
{ "wipe_data", no_argument, NULL, 'w' },
{ "wipe_cache", no_argument, NULL, 'c' },
{ "show_text", no_argument, NULL, 't' },
+ { "sideload", no_argument, NULL, 's' },
+ { "sideload_auto_reboot", no_argument, NULL, 'a' },
{ "just_exit", no_argument, NULL, 'x' },
{ "locale", required_argument, NULL, 'l' },
{ "stages", required_argument, NULL, 'g' },
@@ -82,14 +82,11 @@ static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
#define KEEP_LOG_COUNT 10
-// Number of lines per page when displaying a file on screen
-#define LINES_PER_PAGE 30
-
RecoveryUI* ui = NULL;
char* locale = NULL;
-char recovery_version[PROPERTY_VALUE_MAX+1];
char* stage = NULL;
char* reason = NULL;
+bool modified_flash = false;
/*
* The recovery tool communicates with the main system through /cache files.
@@ -169,6 +166,11 @@ fopen_path(const char *path, const char *mode) {
return 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);
@@ -330,13 +332,18 @@ copy_log_file(const char* source, const char* destination, int append) {
// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max
// Overwrites any existing last_log.$max.
-static void
-rotate_last_logs(int max) {
+static void rotate_last_logs(int max) {
+ // Logs should only be rotated once.
+ static bool rotated = false;
+ if (rotated) {
+ return;
+ }
+ rotated = true;
+ ensure_path_mounted(LAST_LOG_FILE);
+
char oldfn[256];
char newfn[256];
-
- int i;
- for (i = max-1; i >= 0; --i) {
+ for (int i = max-1; i >= 0; --i) {
snprintf(oldfn, sizeof(oldfn), (i==0) ? LAST_LOG_FILE : (LAST_LOG_FILE ".%d"), i);
snprintf(newfn, sizeof(newfn), LAST_LOG_FILE ".%d", i+1);
// ignore errors
@@ -344,8 +351,17 @@ rotate_last_logs(int max) {
}
}
-static void
-copy_logs() {
+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
+ // or menu selections. This is to avoid unnecessary rotation (and
+ // possible deletion) of log files, if it does not do anything loggable.
+ if (!modified_flash) {
+ return;
+ }
+
+ rotate_last_logs(KEEP_LOG_COUNT);
+
// Copy logs to cache so the system can find out what happened.
copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
@@ -497,25 +513,6 @@ erase_volume(const char *volume) {
return result;
}
-static const char**
-prepend_title(const char* const* headers) {
- // count the number of lines in our title, plus the
- // caller-provided headers.
- int count = 3; // our title has 3 lines
- const char* const* p;
- for (p = headers; *p; ++p, ++count);
-
- const char** new_headers = (const char**)malloc((count+1) * sizeof(char*));
- const char** h = new_headers;
- *(h++) = "Android system recovery <" EXPAND(RECOVERY_API_VERSION) "e>";
- *(h++) = recovery_version;
- *(h++) = "";
- for (p = headers; *p; ++p, ++h) *h = *p;
- *h = NULL;
-
- return new_headers;
-}
-
static int
get_menu_selection(const char* const * headers, const char* const * items,
int menu_only, int initial_selection, Device* device) {
@@ -546,12 +543,10 @@ get_menu_selection(const char* const * headers, const char* const * items,
if (action < 0) {
switch (action) {
case Device::kHighlightUp:
- --selected;
- selected = ui->SelectMenu(selected);
+ selected = ui->SelectMenu(--selected);
break;
case Device::kHighlightDown:
- ++selected;
- selected = ui->SelectMenu(selected);
+ selected = ui->SelectMenu(++selected);
break;
case Device::kInvokeItem:
chosen_item = selected;
@@ -573,24 +568,15 @@ static int compare_string(const void* a, const void* b) {
}
// Returns a malloc'd path, or NULL.
-static char*
-browse_directory(const char* path, Device* device) {
+static char* browse_directory(const char* path, Device* device) {
ensure_path_mounted(path);
- const char* MENU_HEADERS[] = { "Choose a package to install:",
- path,
- "",
- NULL };
- DIR* d;
- struct dirent* de;
- d = opendir(path);
+ DIR* d = opendir(path);
if (d == NULL) {
LOGE("error opening %s: %s\n", path, strerror(errno));
return NULL;
}
- const char** headers = prepend_title(MENU_HEADERS);
-
int d_size = 0;
int d_alloc = 10;
char** dirs = (char**)malloc(d_alloc * sizeof(char*));
@@ -599,6 +585,7 @@ browse_directory(const char* path, Device* device) {
char** zips = (char**)malloc(z_alloc * sizeof(char*));
zips[0] = strdup("../");
+ struct dirent* de;
while ((de = readdir(d)) != NULL) {
int name_len = strlen(de->d_name);
@@ -642,6 +629,8 @@ browse_directory(const char* path, Device* device) {
z_size += d_size;
zips[z_size] = NULL;
+ const char* headers[] = { "Choose a package to install:", path, NULL };
+
char* result;
int chosen_item = 0;
while (true) {
@@ -672,103 +661,54 @@ browse_directory(const char* path, Device* device) {
}
}
- int i;
- for (i = 0; i < z_size; ++i) free(zips[i]);
+ for (int i = 0; i < z_size; ++i) free(zips[i]);
free(zips);
- free(headers);
return result;
}
-static void
-wipe_data(int confirm, Device* device) {
- if (confirm) {
- static const char** title_headers = NULL;
-
- if (title_headers == NULL) {
- const char* headers[] = { "Confirm wipe of all user data?",
- " THIS CAN NOT BE UNDONE.",
- "",
- NULL };
- title_headers = prepend_title((const char**)headers);
- }
-
- const char* items[] = { " No",
- " No",
- " No",
- " No",
- " No",
- " No",
- " No",
- " Yes -- delete all user data", // [7]
- " No",
- " No",
- " No",
- NULL };
-
- int chosen_item = get_menu_selection(title_headers, items, 1, 0, device);
- if (chosen_item != 7) {
- return;
- }
- }
+static bool yes_no(Device* device, const char* question1, const char* question2) {
+ const char* headers[] = { question1, question2, NULL };
+ const char* items[] = { " No", " Yes", NULL };
- ui->Print("\n-- Wiping data...\n");
- device->WipeData();
- erase_volume("/data");
- erase_volume("/cache");
- ui->Print("Data wipe complete.\n");
+ int chosen_item = get_menu_selection(headers, items, 1, 0, device);
+ return (chosen_item == 1);
}
-static void file_to_ui(const char* fn) {
- FILE *fp = fopen_path(fn, "re");
- if (fp == NULL) {
- ui->Print(" Unable to open %s: %s\n", fn, strerror(errno));
- return;
+// 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;
}
- char line[1024];
- int ct = 0;
- int key = 0;
- redirect_stdio("/dev/null");
- while(fgets(line, sizeof(line), fp) != NULL) {
- ui->Print("%s", line);
- ct++;
- if (ct % LINES_PER_PAGE == 0) {
- // give the user time to glance at the entries
- key = ui->WaitKey();
-
- if (key == KEY_POWER) {
- break;
- }
- if (key == KEY_VOLUMEUP) {
- // Go back by seeking to the beginning and dumping ct - n
- // lines. It's ugly, but this way we don't need to store
- // the previous offsets. The files we're dumping here aren't
- // expected to be very large.
- int i;
+ modified_flash = true;
- ct -= 2 * LINES_PER_PAGE;
- if (ct < 0) {
- ct = 0;
- }
- fseek(fp, 0, SEEK_SET);
- for (i = 0; i < ct; i++) {
- fgets(line, sizeof(line), fp);
- }
- ui->Print("^^^^^^^^^^\n");
- }
- }
+ ui->Print("\n-- Wiping data...\n");
+ if (device->WipeData() == 0 && erase_volume("/data") == 0 && erase_volume("/cache") == 0) {
+ ui->Print("Data wipe complete.\n");
+ return true;
+ } else {
+ ui->Print("Data wipe failed.\n");
+ return false;
}
+}
- // If the user didn't abort, then give the user time to glance at
- // the end of the log, sorry, no rewind here
- if (key != KEY_POWER) {
- ui->Print("\n--END-- (press any key)\n");
- ui->WaitKey();
+// Return true on success.
+static bool wipe_cache(bool should_confirm, Device* device) {
+ if (should_confirm && !yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!")) {
+ return false;
}
- redirect_stdio(TEMPORARY_LOG_FILE);
- fclose(fp);
+ modified_flash = true;
+
+ ui->Print("\n-- Wiping cache...\n");
+ if (erase_volume("/cache") == 0) {
+ ui->Print("Cache wipe complete.\n");
+ return true;
+ } else {
+ ui->Print("Cache wipe failed.\n");
+ return false;
+ }
}
static void choose_recovery_file(Device* device) {
@@ -776,9 +716,6 @@ static void choose_recovery_file(Device* device) {
unsigned int n;
static const char** title_headers = NULL;
char *filename;
- const char* headers[] = { "Select file to view",
- "",
- NULL };
// "Go back" + LAST_KMSG_FILE + KEEP_LOG_COUNT + terminating NULL entry
char* entries[KEEP_LOG_COUNT + 3];
memset(entries, 0, sizeof(entries));
@@ -806,12 +743,16 @@ static void choose_recovery_file(Device* device) {
entries[n++] = filename;
}
- title_headers = prepend_title((const char**)headers);
+ const char* headers[] = { "Select file to view", NULL };
- while(1) {
- int chosen_item = get_menu_selection(title_headers, entries, 1, 0, device);
+ while (true) {
+ int chosen_item = get_menu_selection(headers, entries, 1, 0, device);
if (chosen_item == 0) break;
- file_to_ui(entries[chosen_item]);
+
+ // 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 (i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) {
@@ -819,13 +760,37 @@ static void choose_recovery_file(Device* device) {
}
}
+static int apply_from_sdcard(Device* device, bool* wipe_cache) {
+ modified_flash = true;
+
+ if (ensure_path_mounted(SDCARD_ROOT) != 0) {
+ ui->Print("\n-- Couldn't mount %s.\n", SDCARD_ROOT);
+ return INSTALL_ERROR;
+ }
+
+ char* path = browse_directory(SDCARD_ROOT, device);
+ if (path == NULL) {
+ ui->Print("\n-- No package file selected.\n");
+ 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);
+
+ finish_sdcard_fuse(token);
+ ensure_path_unmounted(SDCARD_ROOT);
+ return status;
+}
+
// 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) {
- const char* const* headers = prepend_title(device->GetMenuHeaders());
-
for (;;) {
finish_recovery(NULL);
switch (status) {
@@ -841,14 +806,14 @@ prompt_and_wait(Device* device, int status) {
}
ui->SetProgressType(RecoveryUI::EMPTY);
- int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);
+ 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);
- int wipe_cache = 0;
+ bool should_wipe_cache = false;
switch (chosen_action) {
case Device::NO_ACTION:
break;
@@ -864,72 +829,45 @@ prompt_and_wait(Device* device, int status) {
break;
case Device::WIPE_CACHE:
- ui->Print("\n-- Wiping cache...\n");
- erase_volume("/cache");
- ui->Print("Cache wipe complete.\n");
+ wipe_cache(ui->IsTextVisible(), device);
if (!ui->IsTextVisible()) return Device::NO_ACTION;
break;
- case Device::APPLY_EXT: {
- ensure_path_mounted(SDCARD_ROOT);
- char* path = browse_directory(SDCARD_ROOT, device);
- if (path == NULL) {
- ui->Print("\n-- No package file selected.\n", path);
- break;
- }
-
- 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);
-
- finish_sdcard_fuse(token);
- ensure_path_unmounted(SDCARD_ROOT);
-
- if (status == INSTALL_SUCCESS && wipe_cache) {
- ui->Print("\n-- Wiping cache (at package request)...\n");
- if (erase_volume("/cache")) {
- ui->Print("Cache wipe failed.\n");
+ 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 {
- ui->Print("Cache wipe complete.\n");
+ 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 >= 0) {
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 sdcard complete.\n");
+ ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card");
}
}
break;
- }
- case Device::APPLY_CACHE:
- ui->Print("\nAPPLY_CACHE is deprecated.\n");
- break;
-
- case Device::READ_RECOVERY_LASTLOG:
+ case Device::VIEW_RECOVERY_LOGS:
choose_recovery_file(device);
break;
- case Device::APPLY_ADB_SIDELOAD:
- status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE);
- if (status >= 0) {
- 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 ADB complete.\n");
- }
+ case Device::MOUNT_SYSTEM:
+ if (ensure_path_mounted("/system") != -1) {
+ ui->Print("Mounted /system.\n");
}
break;
}
@@ -992,31 +930,35 @@ 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();
+ adb_main(0, DEFAULT_ADB_PORT);
return 0;
}
printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
load_volume_table();
- ensure_path_mounted(LAST_LOG_FILE);
- rotate_last_logs(KEEP_LOG_COUNT);
get_args(&argc, &argv);
const char *send_intent = NULL;
const char *update_package = NULL;
- int wipe_data = 0, wipe_cache = 0, show_text = 0;
+ bool should_wipe_data = false;
+ bool should_wipe_cache = false;
+ bool show_text = false;
+ bool sideload = false;
+ bool sideload_auto_reboot = false;
bool just_exit = false;
bool shutdown_after = false;
int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
- case 's': send_intent = optarg; break;
+ case 'i': send_intent = optarg; break;
case 'u': update_package = optarg; break;
- case 'w': wipe_data = wipe_cache = 1; break;
- case 'c': wipe_cache = 1; break;
- case 't': show_text = 1; break;
+ case 'w': should_wipe_data = true; break;
+ case 'c': should_wipe_cache = true; break;
+ case 't': show_text = true; break;
+ case 's': sideload = true; break;
+ case 'a': sideload = true; sideload_auto_reboot = true; break;
case 'x': just_exit = true; break;
case 'l': locale = optarg; break;
case 'g': {
@@ -1092,52 +1034,71 @@ main(int argc, char **argv) {
printf("\n");
property_list(print_property, NULL);
- property_get("ro.build.display.id", recovery_version, "");
printf("\n");
+ ui->Print("Supported API: %d\n", RECOVERY_API_VERSION);
+
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
- status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);
- if (status == INSTALL_SUCCESS && wipe_cache) {
- if (erase_volume("/cache")) {
- LOGE("Cache wipe (requested by package) failed.");
- }
+ 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");
- ui->Print("OTA failed! Please power off the device to keep it in this state and file a bug report!\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.
- char buffer[PROPERTY_VALUE_MAX+1];
- property_get("ro.build.fingerprint", buffer, "");
- if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) {
+ if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
- } else if (wipe_data) {
- if (device->WipeData()) status = INSTALL_ERROR;
- if (erase_volume("/data")) status = INSTALL_ERROR;
- if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
- if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
- } else if (wipe_cache) {
- if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
- if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
+ } else if (should_wipe_data) {
+ if (!wipe_data(false, device)) {
+ status = INSTALL_ERROR;
+ }
+ } else if (should_wipe_cache) {
+ if (!wipe_cache(false, device)) {
+ status = INSTALL_ERROR;
+ }
+ } else if (sideload) {
+ // 'adb reboot sideload' acts the same as user presses key combinations
+ // to enter the sideload mode. When 'sideload-auto-reboot' is used, text
+ // display will NOT be turned on by default. And it will reboot after
+ // sideload finishes even if there are errors. Unless one turns on the
+ // text display during the installation. This is to enable automated
+ // testing.
+ if (!sideload_auto_reboot) {
+ ui->ShowText(true);
+ }
+ status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
+ if (status == INSTALL_SUCCESS && should_wipe_cache) {
+ if (!wipe_cache(false, device)) {
+ status = INSTALL_ERROR;
+ }
+ }
+ ui->Print("\nInstall from ADB complete (status: %d).\n", status);
+ if (sideload_auto_reboot) {
+ ui->Print("Rebooting automatically.\n");
+ }
} else if (!just_exit) {
status = INSTALL_NONE; // No command specified
ui->SetBackground(RecoveryUI::NO_COMMAND);
}
- if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+ if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {
copy_logs();
ui->SetBackground(RecoveryUI::ERROR);
}
+
Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
- if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
+ if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) {
Device::BuiltinAction temp = prompt_and_wait(device, status);
- if (temp != Device::NO_ACTION) after = temp;
+ if (temp != Device::NO_ACTION) {
+ after = temp;
+ }
}
// Save logs and clean up before rebooting or shutting down.