blob: d1af3ac05b8553e33e61fb980966cecf28b2113c [file] [log] [blame]
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <ctype.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <getopt.h>
21#include <limits.h>
22#include <linux/input.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
Doug Zongker23ceeea2010-07-08 17:27:55 -070026#include <sys/stat.h>
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080027#include <sys/types.h>
28#include <time.h>
29#include <unistd.h>
Doug Zongker8674a722010-09-15 11:08:23 -070030#include <dirent.h>
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080031
32#include "bootloader.h"
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080033#include "common.h"
34#include "cutils/properties.h"
Ken Sumrall6e4472a2011-03-07 23:37:27 -080035#include "cutils/android_reboot.h"
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080036#include "install.h"
37#include "minui/minui.h"
38#include "minzip/DirUtil.h"
39#include "roots.h"
Doug Zongkerddd6a282009-06-09 12:22:33 -070040#include "recovery_ui.h"
Doug Zongker28ce47c2011-10-28 10:33:05 -070041#include "ui.h"
Doug Zongker211aebc2011-10-28 15:13:10 -070042#include "screen_ui.h"
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080043
44static const struct option OPTIONS[] = {
45 { "send_intent", required_argument, NULL, 's' },
46 { "update_package", required_argument, NULL, 'u' },
47 { "wipe_data", no_argument, NULL, 'w' },
48 { "wipe_cache", no_argument, NULL, 'c' },
Doug Zongker4bc98062010-09-03 11:00:13 -070049 { "show_text", no_argument, NULL, 't' },
Doug Zongker988500b2009-10-06 14:41:38 -070050 { NULL, 0, NULL, 0 },
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080051};
52
Doug Zongkerd4208f92010-09-20 12:16:13 -070053static const char *COMMAND_FILE = "/cache/recovery/command";
54static const char *INTENT_FILE = "/cache/recovery/intent";
55static const char *LOG_FILE = "/cache/recovery/log";
Doug Zongker2c3539e2010-09-29 13:21:30 -070056static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
Doug Zongkerd0181b82011-10-19 10:51:12 -070057static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
Michael Ward9d2629c2011-06-23 22:14:24 -070058static const char *CACHE_ROOT = "/cache";
Doug Zongkerd4208f92010-09-20 12:16:13 -070059static const char *SDCARD_ROOT = "/sdcard";
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080060static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
Doug Zongkerd0181b82011-10-19 10:51:12 -070061static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
Doug Zongkerd4208f92010-09-20 12:16:13 -070062static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload";
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080063
Doug Zongker6809c512011-03-01 14:04:34 -080064extern UIParameters ui_parameters; // from ui.c
65
Doug Zongker211aebc2011-10-28 15:13:10 -070066RecoveryUI* ui = NULL;
67
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080068/*
69 * The recovery tool communicates with the main system through /cache files.
70 * /cache/recovery/command - INPUT - command line for tool, one arg per line
71 * /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
72 * /cache/recovery/intent - OUTPUT - intent that was passed in
73 *
74 * The arguments which may be supplied in the recovery.command file:
75 * --send_intent=anystring - write the text out to recovery.intent
Doug Zongkerd4208f92010-09-20 12:16:13 -070076 * --update_package=path - verify install an OTA package file
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080077 * --wipe_data - erase user data (and cache), then reboot
78 * --wipe_cache - wipe cache (but not user data), then reboot
Oscar Montemayor05231562009-11-30 08:40:57 -080079 * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080080 *
81 * After completing, we remove /cache/recovery/command and reboot.
82 * Arguments may also be supplied in the bootloader control block (BCB).
83 * These important scenarios must be safely restartable at any point:
84 *
85 * FACTORY RESET
86 * 1. user selects "factory reset"
87 * 2. main system writes "--wipe_data" to /cache/recovery/command
88 * 3. main system reboots into recovery
89 * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"
90 * -- after this, rebooting will restart the erase --
Doug Zongkerd4208f92010-09-20 12:16:13 -070091 * 5. erase_volume() reformats /data
92 * 6. erase_volume() reformats /cache
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080093 * 7. finish_recovery() erases BCB
94 * -- after this, rebooting will restart the main system --
95 * 8. main() calls reboot() to boot main system
96 *
97 * OTA INSTALL
98 * 1. main system downloads OTA package to /cache/some-filename.zip
Doug Zongker9b125b02010-09-22 12:01:37 -070099 * 2. main system writes "--update_package=/cache/some-filename.zip"
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800100 * 3. main system reboots into recovery
101 * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
102 * -- after this, rebooting will attempt to reinstall the update --
103 * 5. install_package() attempts to install the update
104 * NOTE: the package install must itself be restartable from any point
105 * 6. finish_recovery() erases BCB
106 * -- after this, rebooting will (try to) restart the main system --
107 * 7. ** if install failed **
108 * 7a. prompt_and_wait() shows an error icon and waits for the user
109 * 7b; the user reboots (pulling the battery, etc) into the main system
110 * 8. main() calls maybe_install_firmware_update()
111 * ** if the update contained radio/hboot firmware **:
112 * 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
113 * -- after this, rebooting will reformat cache & restart main system --
114 * 8b. m_i_f_u() writes firmware image into raw cache partition
115 * 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
116 * -- after this, rebooting will attempt to reinstall firmware --
117 * 8d. bootloader tries to flash firmware
118 * 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
119 * -- after this, rebooting will reformat cache & restart main system --
Doug Zongkerd4208f92010-09-20 12:16:13 -0700120 * 8f. erase_volume() reformats /cache
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800121 * 8g. finish_recovery() erases BCB
122 * -- after this, rebooting will (try to) restart the main system --
123 * 9. main() calls reboot() to boot main system
124 */
125
126static const int MAX_ARG_LENGTH = 4096;
127static const int MAX_ARGS = 100;
128
Doug Zongkerd4208f92010-09-20 12:16:13 -0700129// open a given path, mounting partitions as necessary
Doug Zongker469243e2011-04-12 09:28:10 -0700130FILE*
Doug Zongkerd4208f92010-09-20 12:16:13 -0700131fopen_path(const char *path, const char *mode) {
132 if (ensure_path_mounted(path) != 0) {
133 LOGE("Can't mount %s\n", path);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800134 return NULL;
135 }
136
137 // When writing, try to create the containing directory, if necessary.
138 // Use generous permissions, the system (init.rc) will reset them.
139 if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1);
140
141 FILE *fp = fopen(path, mode);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800142 return fp;
143}
144
145// close a file, log an error if the error indicator is set
146static void
147check_and_fclose(FILE *fp, const char *name) {
148 fflush(fp);
149 if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno));
150 fclose(fp);
151}
152
153// command line args come from, in decreasing precedence:
154// - the actual command line
155// - the bootloader control block (one per line, after "recovery")
156// - the contents of COMMAND_FILE (one per line)
157static void
158get_args(int *argc, char ***argv) {
159 struct bootloader_message boot;
160 memset(&boot, 0, sizeof(boot));
161 get_bootloader_message(&boot); // this may fail, leaving a zeroed structure
162
163 if (boot.command[0] != 0 && boot.command[0] != 255) {
164 LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
165 }
166
167 if (boot.status[0] != 0 && boot.status[0] != 255) {
168 LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
169 }
170
171 // --- if arguments weren't supplied, look in the bootloader control block
172 if (*argc <= 1) {
173 boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination
174 const char *arg = strtok(boot.recovery, "\n");
175 if (arg != NULL && !strcmp(arg, "recovery")) {
176 *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
177 (*argv)[0] = strdup(arg);
178 for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
179 if ((arg = strtok(NULL, "\n")) == NULL) break;
180 (*argv)[*argc] = strdup(arg);
181 }
182 LOGI("Got arguments from boot message\n");
183 } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {
184 LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
185 }
186 }
187
188 // --- if that doesn't work, try the command file
189 if (*argc <= 1) {
Doug Zongkerd4208f92010-09-20 12:16:13 -0700190 FILE *fp = fopen_path(COMMAND_FILE, "r");
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800191 if (fp != NULL) {
192 char *argv0 = (*argv)[0];
193 *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
194 (*argv)[0] = argv0; // use the same program name
195
196 char buf[MAX_ARG_LENGTH];
197 for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
198 if (!fgets(buf, sizeof(buf), fp)) break;
199 (*argv)[*argc] = strdup(strtok(buf, "\r\n")); // Strip newline.
200 }
201
202 check_and_fclose(fp, COMMAND_FILE);
203 LOGI("Got arguments from %s\n", COMMAND_FILE);
204 }
205 }
206
207 // --> write the arguments we have back into the bootloader control block
208 // always boot into recovery after this (until finish_recovery() is called)
209 strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
210 strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
211 int i;
212 for (i = 1; i < *argc; ++i) {
213 strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
214 strlcat(boot.recovery, "\n", sizeof(boot.recovery));
215 }
216 set_bootloader_message(&boot);
217}
218
Doug Zongker34c98df2009-08-18 12:05:45 -0700219static void
Oscar Montemayor05231562009-11-30 08:40:57 -0800220set_sdcard_update_bootloader_message() {
Doug Zongker34c98df2009-08-18 12:05:45 -0700221 struct bootloader_message boot;
222 memset(&boot, 0, sizeof(boot));
223 strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
224 strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
225 set_bootloader_message(&boot);
226}
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800227
Doug Zongker2c3539e2010-09-29 13:21:30 -0700228// How much of the temp log we have copied to the copy in cache.
229static long tmplog_offset = 0;
230
231static void
Doug Zongkerd0181b82011-10-19 10:51:12 -0700232copy_log_file(const char* source, const char* destination, int append) {
Doug Zongker2c3539e2010-09-29 13:21:30 -0700233 FILE *log = fopen_path(destination, append ? "a" : "w");
234 if (log == NULL) {
235 LOGE("Can't open %s\n", destination);
236 } else {
Doug Zongkerd0181b82011-10-19 10:51:12 -0700237 FILE *tmplog = fopen(source, "r");
238 if (tmplog != NULL) {
Doug Zongker2c3539e2010-09-29 13:21:30 -0700239 if (append) {
240 fseek(tmplog, tmplog_offset, SEEK_SET); // Since last write
241 }
242 char buf[4096];
243 while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log);
244 if (append) {
245 tmplog_offset = ftell(tmplog);
246 }
Doug Zongkerd0181b82011-10-19 10:51:12 -0700247 check_and_fclose(tmplog, source);
Doug Zongker2c3539e2010-09-29 13:21:30 -0700248 }
249 check_and_fclose(log, destination);
250 }
251}
252
253
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800254// clear the recovery command and prepare to boot a (hopefully working) system,
255// copy our log file to cache as well (for the system to read), and
256// record any intent we were asked to communicate back to the system.
257// this function is idempotent: call it as many times as you like.
258static void
Oscar Montemayor05231562009-11-30 08:40:57 -0800259finish_recovery(const char *send_intent) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800260 // By this point, we're ready to return to the main system...
261 if (send_intent != NULL) {
Doug Zongkerd4208f92010-09-20 12:16:13 -0700262 FILE *fp = fopen_path(INTENT_FILE, "w");
Jay Freeman (saurik)619ec2f2008-11-17 01:56:05 +0000263 if (fp == NULL) {
264 LOGE("Can't open %s\n", INTENT_FILE);
265 } else {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800266 fputs(send_intent, fp);
267 check_and_fclose(fp, INTENT_FILE);
268 }
269 }
270
271 // Copy logs to cache so the system can find out what happened.
Doug Zongkerd0181b82011-10-19 10:51:12 -0700272 copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
273 copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
274 copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
275 chmod(LOG_FILE, 0600);
276 chown(LOG_FILE, 1000, 1000); // system user
Doug Zongker2c3539e2010-09-29 13:21:30 -0700277 chmod(LAST_LOG_FILE, 0640);
Doug Zongkerd0181b82011-10-19 10:51:12 -0700278 chmod(LAST_INSTALL_FILE, 0644);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800279
Oscar Montemayor05231562009-11-30 08:40:57 -0800280 // Reset to mormal system boot so recovery won't cycle indefinitely.
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800281 struct bootloader_message boot;
282 memset(&boot, 0, sizeof(boot));
283 set_bootloader_message(&boot);
284
285 // Remove the command file, so recovery won't repeat indefinitely.
Doug Zongkerd4208f92010-09-20 12:16:13 -0700286 if (ensure_path_mounted(COMMAND_FILE) != 0 ||
287 (unlink(COMMAND_FILE) && errno != ENOENT)) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800288 LOGW("Can't unlink %s\n", COMMAND_FILE);
289 }
290
Doug Zongkerd0181b82011-10-19 10:51:12 -0700291 ensure_path_unmounted(CACHE_ROOT);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800292 sync(); // For good measure.
293}
294
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800295static int
Doug Zongkerd4208f92010-09-20 12:16:13 -0700296erase_volume(const char *volume) {
Doug Zongker211aebc2011-10-28 15:13:10 -0700297 ui->SetBackground(RecoveryUI::INSTALLING);
298 ui->SetProgressType(RecoveryUI::INDETERMINATE);
299 ui->Print("Formatting %s...\n", volume);
Doug Zongker2c3539e2010-09-29 13:21:30 -0700300
Doug Zongkerd0181b82011-10-19 10:51:12 -0700301 ensure_path_unmounted(volume);
302
Doug Zongker2c3539e2010-09-29 13:21:30 -0700303 if (strcmp(volume, "/cache") == 0) {
304 // Any part of the log we'd copied to cache is now gone.
305 // Reset the pointer so we copy from the beginning of the temp
306 // log.
307 tmplog_offset = 0;
308 }
309
Doug Zongkerd4208f92010-09-20 12:16:13 -0700310 return format_volume(volume);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800311}
312
Doug Zongker23ceeea2010-07-08 17:27:55 -0700313static char*
Doug Zongkerd4208f92010-09-20 12:16:13 -0700314copy_sideloaded_package(const char* original_path) {
315 if (ensure_path_mounted(original_path) != 0) {
316 LOGE("Can't mount %s\n", original_path);
Doug Zongker23ceeea2010-07-08 17:27:55 -0700317 return NULL;
318 }
319
Doug Zongkerd4208f92010-09-20 12:16:13 -0700320 if (ensure_path_mounted(SIDELOAD_TEMP_DIR) != 0) {
Doug Zongker23ceeea2010-07-08 17:27:55 -0700321 LOGE("Can't mount %s\n", SIDELOAD_TEMP_DIR);
322 return NULL;
323 }
324
Doug Zongkerd4208f92010-09-20 12:16:13 -0700325 if (mkdir(SIDELOAD_TEMP_DIR, 0700) != 0) {
Doug Zongker23ceeea2010-07-08 17:27:55 -0700326 if (errno != EEXIST) {
327 LOGE("Can't mkdir %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno));
328 return NULL;
329 }
330 }
331
Doug Zongkerd4208f92010-09-20 12:16:13 -0700332 // verify that SIDELOAD_TEMP_DIR is exactly what we expect: a
333 // directory, owned by root, readable and writable only by root.
Doug Zongker23ceeea2010-07-08 17:27:55 -0700334 struct stat st;
Doug Zongkerd4208f92010-09-20 12:16:13 -0700335 if (stat(SIDELOAD_TEMP_DIR, &st) != 0) {
336 LOGE("failed to stat %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno));
Doug Zongker23ceeea2010-07-08 17:27:55 -0700337 return NULL;
338 }
339 if (!S_ISDIR(st.st_mode)) {
Doug Zongkerd4208f92010-09-20 12:16:13 -0700340 LOGE("%s isn't a directory\n", SIDELOAD_TEMP_DIR);
Doug Zongker23ceeea2010-07-08 17:27:55 -0700341 return NULL;
342 }
343 if ((st.st_mode & 0777) != 0700) {
Doug Zongkerd4208f92010-09-20 12:16:13 -0700344 LOGE("%s has perms %o\n", SIDELOAD_TEMP_DIR, st.st_mode);
Doug Zongker23ceeea2010-07-08 17:27:55 -0700345 return NULL;
346 }
347 if (st.st_uid != 0) {
Doug Zongkerd4208f92010-09-20 12:16:13 -0700348 LOGE("%s owned by %lu; not root\n", SIDELOAD_TEMP_DIR, st.st_uid);
Doug Zongker23ceeea2010-07-08 17:27:55 -0700349 return NULL;
350 }
351
Doug Zongkerd4208f92010-09-20 12:16:13 -0700352 char copy_path[PATH_MAX];
353 strcpy(copy_path, SIDELOAD_TEMP_DIR);
Doug Zongker23ceeea2010-07-08 17:27:55 -0700354 strcat(copy_path, "/package.zip");
355
Doug Zongker28ce47c2011-10-28 10:33:05 -0700356 char* buffer = (char*)malloc(BUFSIZ);
Doug Zongker23ceeea2010-07-08 17:27:55 -0700357 if (buffer == NULL) {
358 LOGE("Failed to allocate buffer\n");
359 return NULL;
360 }
361
362 size_t read;
363 FILE* fin = fopen(original_path, "rb");
364 if (fin == NULL) {
365 LOGE("Failed to open %s (%s)\n", original_path, strerror(errno));
366 return NULL;
367 }
368 FILE* fout = fopen(copy_path, "wb");
369 if (fout == NULL) {
370 LOGE("Failed to open %s (%s)\n", copy_path, strerror(errno));
371 return NULL;
372 }
373
374 while ((read = fread(buffer, 1, BUFSIZ, fin)) > 0) {
375 if (fwrite(buffer, 1, read, fout) != read) {
376 LOGE("Short write of %s (%s)\n", copy_path, strerror(errno));
377 return NULL;
378 }
379 }
380
381 free(buffer);
382
383 if (fclose(fout) != 0) {
384 LOGE("Failed to close %s (%s)\n", copy_path, strerror(errno));
385 return NULL;
386 }
387
388 if (fclose(fin) != 0) {
389 LOGE("Failed to close %s (%s)\n", original_path, strerror(errno));
390 return NULL;
391 }
392
393 // "adb push" is happy to overwrite read-only files when it's
394 // running as root, but we'll try anyway.
395 if (chmod(copy_path, 0400) != 0) {
396 LOGE("Failed to chmod %s (%s)\n", copy_path, strerror(errno));
397 return NULL;
398 }
399
Doug Zongkerd4208f92010-09-20 12:16:13 -0700400 return strdup(copy_path);
Doug Zongker23ceeea2010-07-08 17:27:55 -0700401}
402
Doug Zongker28ce47c2011-10-28 10:33:05 -0700403static const char**
Doug Zongker8674a722010-09-15 11:08:23 -0700404prepend_title(const char** headers) {
Doug Zongker28ce47c2011-10-28 10:33:05 -0700405 const char* title[] = { "Android system recovery <"
406 EXPAND(RECOVERY_API_VERSION) "e>",
407 "",
408 NULL };
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800409
Doug Zongkerd6837852009-06-17 22:07:13 -0700410 // count the number of lines in our title, plus the
Doug Zongkerf93d8162009-09-22 15:16:02 -0700411 // caller-provided headers.
Doug Zongkerd6837852009-06-17 22:07:13 -0700412 int count = 0;
Doug Zongker28ce47c2011-10-28 10:33:05 -0700413 const char** p;
Doug Zongkerd6837852009-06-17 22:07:13 -0700414 for (p = title; *p; ++p, ++count);
Doug Zongkerf93d8162009-09-22 15:16:02 -0700415 for (p = headers; *p; ++p, ++count);
Doug Zongkerd6837852009-06-17 22:07:13 -0700416
Doug Zongker28ce47c2011-10-28 10:33:05 -0700417 const char** new_headers = (const char**)malloc((count+1) * sizeof(char*));
418 const char** h = new_headers;
Doug Zongkerd6837852009-06-17 22:07:13 -0700419 for (p = title; *p; ++p, ++h) *h = *p;
Doug Zongkerf93d8162009-09-22 15:16:02 -0700420 for (p = headers; *p; ++p, ++h) *h = *p;
Doug Zongkerd6837852009-06-17 22:07:13 -0700421 *h = NULL;
422
Doug Zongkerf93d8162009-09-22 15:16:02 -0700423 return new_headers;
424}
425
426static int
Doug Zongker28ce47c2011-10-28 10:33:05 -0700427get_menu_selection(const char* const * headers, const char* const * items,
428 int menu_only, int initial_selection) {
Doug Zongkerf93d8162009-09-22 15:16:02 -0700429 // throw away keys pressed previously, so user doesn't
430 // accidentally trigger menu items.
Doug Zongker211aebc2011-10-28 15:13:10 -0700431 ui->FlushKeys();
Doug Zongkerf93d8162009-09-22 15:16:02 -0700432
Doug Zongker211aebc2011-10-28 15:13:10 -0700433 ui->StartMenu(headers, items, initial_selection);
Doug Zongker8674a722010-09-15 11:08:23 -0700434 int selected = initial_selection;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800435 int chosen_item = -1;
436
Doug Zongkerf93d8162009-09-22 15:16:02 -0700437 while (chosen_item < 0) {
Doug Zongker211aebc2011-10-28 15:13:10 -0700438 int key = ui->WaitKey();
439 int visible = ui->IsTextVisible();
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800440
Doug Zongker5cae4452011-01-25 13:15:30 -0800441 if (key == -1) { // ui_wait_key() timed out
Doug Zongker211aebc2011-10-28 15:13:10 -0700442 if (ui->WasTextEverVisible()) {
Doug Zongker5cae4452011-01-25 13:15:30 -0800443 continue;
444 } else {
445 LOGI("timed out waiting for key input; rebooting.\n");
Doug Zongker211aebc2011-10-28 15:13:10 -0700446 ui->EndMenu();
Doug Zongker5cae4452011-01-25 13:15:30 -0800447 return ITEM_REBOOT;
448 }
449 }
450
Doug Zongkerddd6a282009-06-09 12:22:33 -0700451 int action = device_handle_key(key, visible);
452
453 if (action < 0) {
454 switch (action) {
455 case HIGHLIGHT_UP:
456 --selected;
Doug Zongker211aebc2011-10-28 15:13:10 -0700457 selected = ui->SelectMenu(selected);
Doug Zongkerddd6a282009-06-09 12:22:33 -0700458 break;
459 case HIGHLIGHT_DOWN:
460 ++selected;
Doug Zongker211aebc2011-10-28 15:13:10 -0700461 selected = ui->SelectMenu(selected);
Doug Zongkerddd6a282009-06-09 12:22:33 -0700462 break;
463 case SELECT_ITEM:
464 chosen_item = selected;
465 break;
466 case NO_ACTION:
467 break;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800468 }
Doug Zongkerf93d8162009-09-22 15:16:02 -0700469 } else if (!menu_only) {
Doug Zongkerddd6a282009-06-09 12:22:33 -0700470 chosen_item = action;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800471 }
Doug Zongkerf93d8162009-09-22 15:16:02 -0700472 }
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800473
Doug Zongker211aebc2011-10-28 15:13:10 -0700474 ui->EndMenu();
Doug Zongkerf93d8162009-09-22 15:16:02 -0700475 return chosen_item;
476}
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800477
Doug Zongker8674a722010-09-15 11:08:23 -0700478static int compare_string(const void* a, const void* b) {
479 return strcmp(*(const char**)a, *(const char**)b);
480}
481
482static int
Doug Zongkerd0181b82011-10-19 10:51:12 -0700483update_directory(const char* path, const char* unmount_when_done,
484 int* wipe_cache) {
Michael Ward9d2629c2011-06-23 22:14:24 -0700485 ensure_path_mounted(path);
Doug Zongkerc18eeb82010-09-21 16:49:26 -0700486
Doug Zongker8674a722010-09-15 11:08:23 -0700487 const char* MENU_HEADERS[] = { "Choose a package to install:",
Doug Zongkerd4208f92010-09-20 12:16:13 -0700488 path,
Doug Zongker8674a722010-09-15 11:08:23 -0700489 "",
490 NULL };
491 DIR* d;
492 struct dirent* de;
Doug Zongkerd4208f92010-09-20 12:16:13 -0700493 d = opendir(path);
Doug Zongker8674a722010-09-15 11:08:23 -0700494 if (d == NULL) {
495 LOGE("error opening %s: %s\n", path, strerror(errno));
Michael Ward9d2629c2011-06-23 22:14:24 -0700496 if (unmount_when_done != NULL) {
497 ensure_path_unmounted(unmount_when_done);
498 }
Doug Zongker8674a722010-09-15 11:08:23 -0700499 return 0;
500 }
501
Doug Zongker28ce47c2011-10-28 10:33:05 -0700502 const char** headers = prepend_title(MENU_HEADERS);
Doug Zongker8674a722010-09-15 11:08:23 -0700503
504 int d_size = 0;
505 int d_alloc = 10;
Doug Zongker28ce47c2011-10-28 10:33:05 -0700506 char** dirs = (char**)malloc(d_alloc * sizeof(char*));
Doug Zongker8674a722010-09-15 11:08:23 -0700507 int z_size = 1;
508 int z_alloc = 10;
Doug Zongker28ce47c2011-10-28 10:33:05 -0700509 char** zips = (char**)malloc(z_alloc * sizeof(char*));
Doug Zongker8674a722010-09-15 11:08:23 -0700510 zips[0] = strdup("../");
511
512 while ((de = readdir(d)) != NULL) {
513 int name_len = strlen(de->d_name);
514
515 if (de->d_type == DT_DIR) {
516 // skip "." and ".." entries
517 if (name_len == 1 && de->d_name[0] == '.') continue;
518 if (name_len == 2 && de->d_name[0] == '.' &&
519 de->d_name[1] == '.') continue;
520
521 if (d_size >= d_alloc) {
522 d_alloc *= 2;
Doug Zongker28ce47c2011-10-28 10:33:05 -0700523 dirs = (char**)realloc(dirs, d_alloc * sizeof(char*));
Doug Zongker8674a722010-09-15 11:08:23 -0700524 }
Doug Zongker28ce47c2011-10-28 10:33:05 -0700525 dirs[d_size] = (char*)malloc(name_len + 2);
Doug Zongker8674a722010-09-15 11:08:23 -0700526 strcpy(dirs[d_size], de->d_name);
527 dirs[d_size][name_len] = '/';
528 dirs[d_size][name_len+1] = '\0';
529 ++d_size;
530 } else if (de->d_type == DT_REG &&
531 name_len >= 4 &&
532 strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) {
533 if (z_size >= z_alloc) {
534 z_alloc *= 2;
Doug Zongker28ce47c2011-10-28 10:33:05 -0700535 zips = (char**)realloc(zips, z_alloc * sizeof(char*));
Doug Zongker8674a722010-09-15 11:08:23 -0700536 }
537 zips[z_size++] = strdup(de->d_name);
538 }
539 }
540 closedir(d);
541
542 qsort(dirs, d_size, sizeof(char*), compare_string);
543 qsort(zips, z_size, sizeof(char*), compare_string);
544
545 // append dirs to the zips list
546 if (d_size + z_size + 1 > z_alloc) {
547 z_alloc = d_size + z_size + 1;
Doug Zongker28ce47c2011-10-28 10:33:05 -0700548 zips = (char**)realloc(zips, z_alloc * sizeof(char*));
Doug Zongker8674a722010-09-15 11:08:23 -0700549 }
550 memcpy(zips + z_size, dirs, d_size * sizeof(char*));
551 free(dirs);
552 z_size += d_size;
553 zips[z_size] = NULL;
554
555 int result;
556 int chosen_item = 0;
557 do {
558 chosen_item = get_menu_selection(headers, zips, 1, chosen_item);
559
560 char* item = zips[chosen_item];
561 int item_len = strlen(item);
562 if (chosen_item == 0) { // item 0 is always "../"
Michael Ward9d2629c2011-06-23 22:14:24 -0700563 // go up but continue browsing (if the caller is update_directory)
Doug Zongker8674a722010-09-15 11:08:23 -0700564 result = -1;
565 break;
566 } else if (item[item_len-1] == '/') {
567 // recurse down into a subdirectory
568 char new_path[PATH_MAX];
Doug Zongkerd4208f92010-09-20 12:16:13 -0700569 strlcpy(new_path, path, PATH_MAX);
570 strlcat(new_path, "/", PATH_MAX);
Doug Zongker8674a722010-09-15 11:08:23 -0700571 strlcat(new_path, item, PATH_MAX);
Doug Zongkerd4208f92010-09-20 12:16:13 -0700572 new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/'
Doug Zongkerd0181b82011-10-19 10:51:12 -0700573 result = update_directory(new_path, unmount_when_done, wipe_cache);
Doug Zongker8674a722010-09-15 11:08:23 -0700574 if (result >= 0) break;
575 } else {
576 // selected a zip file: attempt to install it, and return
577 // the status to the caller.
578 char new_path[PATH_MAX];
Doug Zongkerd4208f92010-09-20 12:16:13 -0700579 strlcpy(new_path, path, PATH_MAX);
Doug Zongkerc18eeb82010-09-21 16:49:26 -0700580 strlcat(new_path, "/", PATH_MAX);
Doug Zongker8674a722010-09-15 11:08:23 -0700581 strlcat(new_path, item, PATH_MAX);
582
Doug Zongker211aebc2011-10-28 15:13:10 -0700583 ui->Print("\n-- Install %s ...\n", path);
Doug Zongker8674a722010-09-15 11:08:23 -0700584 set_sdcard_update_bootloader_message();
Doug Zongkerd4208f92010-09-20 12:16:13 -0700585 char* copy = copy_sideloaded_package(new_path);
Michael Ward9d2629c2011-06-23 22:14:24 -0700586 if (unmount_when_done != NULL) {
587 ensure_path_unmounted(unmount_when_done);
588 }
Doug Zongkerd4208f92010-09-20 12:16:13 -0700589 if (copy) {
Doug Zongkerd0181b82011-10-19 10:51:12 -0700590 result = install_package(copy, wipe_cache, TEMPORARY_INSTALL_FILE);
Doug Zongkerd4208f92010-09-20 12:16:13 -0700591 free(copy);
592 } else {
593 result = INSTALL_ERROR;
594 }
Doug Zongker8674a722010-09-15 11:08:23 -0700595 break;
596 }
597 } while (true);
598
599 int i;
600 for (i = 0; i < z_size; ++i) free(zips[i]);
601 free(zips);
602 free(headers);
603
Michael Ward9d2629c2011-06-23 22:14:24 -0700604 if (unmount_when_done != NULL) {
605 ensure_path_unmounted(unmount_when_done);
606 }
Doug Zongker8674a722010-09-15 11:08:23 -0700607 return result;
608}
609
Doug Zongkerf93d8162009-09-22 15:16:02 -0700610static void
611wipe_data(int confirm) {
612 if (confirm) {
Doug Zongker28ce47c2011-10-28 10:33:05 -0700613 static const char** title_headers = NULL;
Doug Zongkerddd6a282009-06-09 12:22:33 -0700614
Doug Zongkerf93d8162009-09-22 15:16:02 -0700615 if (title_headers == NULL) {
Doug Zongker28ce47c2011-10-28 10:33:05 -0700616 const char* headers[] = { "Confirm wipe of all user data?",
617 " THIS CAN NOT BE UNDONE.",
618 "",
619 NULL };
Doug Zongker8674a722010-09-15 11:08:23 -0700620 title_headers = prepend_title((const char**)headers);
Doug Zongkerf93d8162009-09-22 15:16:02 -0700621 }
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800622
Doug Zongker28ce47c2011-10-28 10:33:05 -0700623 const char* items[] = { " No",
624 " No",
625 " No",
626 " No",
627 " No",
628 " No",
629 " No",
630 " Yes -- delete all user data", // [7]
631 " No",
632 " No",
633 " No",
634 NULL };
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800635
Doug Zongker8674a722010-09-15 11:08:23 -0700636 int chosen_item = get_menu_selection(title_headers, items, 1, 0);
Doug Zongkerf93d8162009-09-22 15:16:02 -0700637 if (chosen_item != 7) {
638 return;
639 }
640 }
Doug Zongker1066d2c2009-04-01 13:57:40 -0700641
Doug Zongker211aebc2011-10-28 15:13:10 -0700642 ui->Print("\n-- Wiping data...\n");
Doug Zongkerf93d8162009-09-22 15:16:02 -0700643 device_wipe_data();
Doug Zongkerd4208f92010-09-20 12:16:13 -0700644 erase_volume("/data");
645 erase_volume("/cache");
Doug Zongker211aebc2011-10-28 15:13:10 -0700646 ui->Print("Data wipe complete.\n");
Doug Zongkerf93d8162009-09-22 15:16:02 -0700647}
648
649static void
Oscar Montemayor05231562009-11-30 08:40:57 -0800650prompt_and_wait() {
Doug Zongker28ce47c2011-10-28 10:33:05 -0700651 const char** headers = prepend_title((const char**)MENU_HEADERS);
Doug Zongkerf93d8162009-09-22 15:16:02 -0700652
653 for (;;) {
654 finish_recovery(NULL);
Doug Zongker211aebc2011-10-28 15:13:10 -0700655 ui->SetProgressType(RecoveryUI::EMPTY);
Doug Zongkerf93d8162009-09-22 15:16:02 -0700656
Doug Zongker8674a722010-09-15 11:08:23 -0700657 int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0);
Doug Zongkerf93d8162009-09-22 15:16:02 -0700658
659 // device-specific code may take some action here. It may
660 // return one of the core actions handled in the switch
661 // statement below.
662 chosen_item = device_perform_action(chosen_item);
663
Michael Ward9d2629c2011-06-23 22:14:24 -0700664 int status;
Doug Zongkerd0181b82011-10-19 10:51:12 -0700665 int wipe_cache;
Doug Zongkerf93d8162009-09-22 15:16:02 -0700666 switch (chosen_item) {
667 case ITEM_REBOOT:
668 return;
669
670 case ITEM_WIPE_DATA:
Doug Zongker211aebc2011-10-28 15:13:10 -0700671 wipe_data(ui->IsTextVisible());
672 if (!ui->IsTextVisible()) return;
Doug Zongkerf93d8162009-09-22 15:16:02 -0700673 break;
674
675 case ITEM_WIPE_CACHE:
Doug Zongker211aebc2011-10-28 15:13:10 -0700676 ui->Print("\n-- Wiping cache...\n");
Doug Zongkerd4208f92010-09-20 12:16:13 -0700677 erase_volume("/cache");
Doug Zongker211aebc2011-10-28 15:13:10 -0700678 ui->Print("Cache wipe complete.\n");
679 if (!ui->IsTextVisible()) return;
Doug Zongkerf93d8162009-09-22 15:16:02 -0700680 break;
681
682 case ITEM_APPLY_SDCARD:
Doug Zongkerd0181b82011-10-19 10:51:12 -0700683 status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache);
684 if (status == INSTALL_SUCCESS && wipe_cache) {
Doug Zongker211aebc2011-10-28 15:13:10 -0700685 ui->Print("\n-- Wiping cache (at package request)...\n");
Doug Zongkerd0181b82011-10-19 10:51:12 -0700686 if (erase_volume("/cache")) {
Doug Zongker211aebc2011-10-28 15:13:10 -0700687 ui->Print("Cache wipe failed.\n");
Doug Zongkerd0181b82011-10-19 10:51:12 -0700688 } else {
Doug Zongker211aebc2011-10-28 15:13:10 -0700689 ui->Print("Cache wipe complete.\n");
Doug Zongkerd0181b82011-10-19 10:51:12 -0700690 }
691 }
Doug Zongker8674a722010-09-15 11:08:23 -0700692 if (status >= 0) {
693 if (status != INSTALL_SUCCESS) {
Doug Zongker211aebc2011-10-28 15:13:10 -0700694 ui->SetBackground(RecoveryUI::ERROR);
695 ui->Print("Installation aborted.\n");
696 } else if (!ui->IsTextVisible()) {
Doug Zongker8674a722010-09-15 11:08:23 -0700697 return; // reboot if logs aren't visible
698 } else {
Doug Zongker211aebc2011-10-28 15:13:10 -0700699 ui->Print("\nInstall from sdcard complete.\n");
Doug Zongker8674a722010-09-15 11:08:23 -0700700 }
Doug Zongkerf93d8162009-09-22 15:16:02 -0700701 }
702 break;
Michael Ward9d2629c2011-06-23 22:14:24 -0700703 case ITEM_APPLY_CACHE:
704 // Don't unmount cache at the end of this.
Doug Zongkerd0181b82011-10-19 10:51:12 -0700705 status = update_directory(CACHE_ROOT, NULL, &wipe_cache);
706 if (status == INSTALL_SUCCESS && wipe_cache) {
Doug Zongker211aebc2011-10-28 15:13:10 -0700707 ui->Print("\n-- Wiping cache (at package request)...\n");
Doug Zongkerd0181b82011-10-19 10:51:12 -0700708 if (erase_volume("/cache")) {
Doug Zongker211aebc2011-10-28 15:13:10 -0700709 ui->Print("Cache wipe failed.\n");
Doug Zongkerd0181b82011-10-19 10:51:12 -0700710 } else {
Doug Zongker211aebc2011-10-28 15:13:10 -0700711 ui->Print("Cache wipe complete.\n");
Doug Zongkerd0181b82011-10-19 10:51:12 -0700712 }
713 }
Michael Ward9d2629c2011-06-23 22:14:24 -0700714 if (status >= 0) {
715 if (status != INSTALL_SUCCESS) {
Doug Zongker211aebc2011-10-28 15:13:10 -0700716 ui->SetBackground(RecoveryUI::ERROR);
717 ui->Print("Installation aborted.\n");
718 } else if (!ui->IsTextVisible()) {
Michael Ward9d2629c2011-06-23 22:14:24 -0700719 return; // reboot if logs aren't visible
720 } else {
Doug Zongker211aebc2011-10-28 15:13:10 -0700721 ui->Print("\nInstall from cache complete.\n");
Michael Ward9d2629c2011-06-23 22:14:24 -0700722 }
723 }
724 break;
725
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800726 }
727 }
728}
729
730static void
Oscar Montemayor05231562009-11-30 08:40:57 -0800731print_property(const char *key, const char *name, void *cookie) {
Doug Zongker56c51052010-07-01 09:18:44 -0700732 printf("%s=%s\n", key, name);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800733}
734
735int
Oscar Montemayor05231562009-11-30 08:40:57 -0800736main(int argc, char **argv) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800737 time_t start = time(NULL);
738
739 // If these fail, there's not really anywhere to complain...
740 freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
741 freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
Doug Zongker56c51052010-07-01 09:18:44 -0700742 printf("Starting recovery on %s", ctime(&start));
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800743
Doug Zongker211aebc2011-10-28 15:13:10 -0700744 // TODO: device_* should be a C++ class; init should return the
745 // appropriate UI for the device.
Doug Zongker6809c512011-03-01 14:04:34 -0800746 device_ui_init(&ui_parameters);
Doug Zongker211aebc2011-10-28 15:13:10 -0700747 ui = new ScreenRecoveryUI();
748
749 ui->Init();
750 ui->SetBackground(RecoveryUI::INSTALLING);
Doug Zongkerd4208f92010-09-20 12:16:13 -0700751 load_volume_table();
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800752 get_args(&argc, &argv);
753
754 int previous_runs = 0;
755 const char *send_intent = NULL;
756 const char *update_package = NULL;
757 int wipe_data = 0, wipe_cache = 0;
758
759 int arg;
760 while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
761 switch (arg) {
762 case 'p': previous_runs = atoi(optarg); break;
763 case 's': send_intent = optarg; break;
764 case 'u': update_package = optarg; break;
765 case 'w': wipe_data = wipe_cache = 1; break;
766 case 'c': wipe_cache = 1; break;
Doug Zongker211aebc2011-10-28 15:13:10 -0700767 case 't': ui->ShowText(true); break;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800768 case '?':
769 LOGE("Invalid command argument\n");
770 continue;
771 }
772 }
773
Doug Zongkerefa1bab2010-02-01 15:59:12 -0800774 device_recovery_start();
775
Doug Zongker56c51052010-07-01 09:18:44 -0700776 printf("Command:");
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800777 for (arg = 0; arg < argc; arg++) {
Doug Zongker56c51052010-07-01 09:18:44 -0700778 printf(" \"%s\"", argv[arg]);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800779 }
Doug Zongker9b125b02010-09-22 12:01:37 -0700780 printf("\n");
781
782 if (update_package) {
783 // For backwards compatibility on the cache partition only, if
784 // we're given an old 'root' path "CACHE:foo", change it to
785 // "/cache/foo".
786 if (strncmp(update_package, "CACHE:", 6) == 0) {
787 int len = strlen(update_package) + 10;
Doug Zongker28ce47c2011-10-28 10:33:05 -0700788 char* modified_path = (char*)malloc(len);
Doug Zongker9b125b02010-09-22 12:01:37 -0700789 strlcpy(modified_path, "/cache/", len);
790 strlcat(modified_path, update_package+6, len);
791 printf("(replacing path \"%s\" with \"%s\")\n",
792 update_package, modified_path);
793 update_package = modified_path;
794 }
795 }
796 printf("\n");
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800797
798 property_list(print_property, NULL);
Doug Zongker56c51052010-07-01 09:18:44 -0700799 printf("\n");
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800800
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800801 int status = INSTALL_SUCCESS;
802
Doug Zongker540d57f2011-01-18 13:36:58 -0800803 if (update_package != NULL) {
Doug Zongkerd0181b82011-10-19 10:51:12 -0700804 status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);
805 if (status == INSTALL_SUCCESS && wipe_cache) {
806 if (erase_volume("/cache")) {
807 LOGE("Cache wipe (requested by package) failed.");
808 }
809 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700810 if (status != INSTALL_SUCCESS) ui->Print("Installation aborted.\n");
Doug Zongkerb128f542009-06-18 15:07:14 -0700811 } else if (wipe_data) {
812 if (device_wipe_data()) status = INSTALL_ERROR;
Doug Zongkerd4208f92010-09-20 12:16:13 -0700813 if (erase_volume("/data")) status = INSTALL_ERROR;
814 if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
Doug Zongker211aebc2011-10-28 15:13:10 -0700815 if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
Doug Zongkerb128f542009-06-18 15:07:14 -0700816 } else if (wipe_cache) {
Doug Zongkerd4208f92010-09-20 12:16:13 -0700817 if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
Doug Zongker211aebc2011-10-28 15:13:10 -0700818 if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800819 } else {
820 status = INSTALL_ERROR; // No command specified
821 }
822
Doug Zongker211aebc2011-10-28 15:13:10 -0700823 if (status != INSTALL_SUCCESS) ui->SetBackground(RecoveryUI::ERROR);
824 if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
Doug Zongker8674a722010-09-15 11:08:23 -0700825 prompt_and_wait();
826 }
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800827
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800828 // Otherwise, get ready to boot the main system...
829 finish_recovery(send_intent);
Doug Zongker211aebc2011-10-28 15:13:10 -0700830 ui->Print("Rebooting...\n");
Ken Sumrall6e4472a2011-03-07 23:37:27 -0800831 android_reboot(ANDROID_RB_RESTART, 0, 0);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800832 return EXIT_SUCCESS;
833}