1 /* 2 * Copyright (C) 2010 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 package android.os; 18 19 import android.annotation.SystemApi; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.os.UserManager; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 import java.io.ByteArrayInputStream; 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.FileNotFoundException; 31 import java.io.FileReader; 32 import java.io.FileWriter; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.RandomAccessFile; 36 import java.security.GeneralSecurityException; 37 import java.security.PublicKey; 38 import java.security.Signature; 39 import java.security.SignatureException; 40 import java.security.cert.CertificateFactory; 41 import java.security.cert.X509Certificate; 42 import java.util.Enumeration; 43 import java.util.HashSet; 44 import java.util.Iterator; 45 import java.util.List; 46 import java.util.Locale; 47 import java.util.zip.ZipEntry; 48 import java.util.zip.ZipFile; 49 50 import com.android.internal.logging.MetricsLogger; 51 52 import sun.security.pkcs.PKCS7; 53 import sun.security.pkcs.SignerInfo; 54 55 /** 56 * RecoverySystem contains methods for interacting with the Android 57 * recovery system (the separate partition that can be used to install 58 * system updates, wipe user data, etc.) 59 */ 60 public class RecoverySystem { 61 private static final String TAG = "RecoverySystem"; 62 63 /** 64 * Default location of zip file containing public keys (X509 65 * certs) authorized to sign OTA updates. 66 */ 67 private static final File DEFAULT_KEYSTORE = 68 new File("/system/etc/security/otacerts.zip"); 69 70 /** Send progress to listeners no more often than this (in ms). */ 71 private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500; 72 73 /** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */ 74 private static final File RECOVERY_DIR = new File("/cache/recovery"); 75 private static final File LOG_FILE = new File(RECOVERY_DIR, "log"); 76 private static final File LAST_INSTALL_FILE = new File(RECOVERY_DIR, "last_install"); 77 private static final String LAST_PREFIX = "last_"; 78 79 /** 80 * The recovery image uses this file to identify the location (i.e. blocks) 81 * of an OTA package on the /data partition. The block map file is 82 * generated by uncrypt. 83 * 84 * @hide 85 */ 86 public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map"); 87 88 /** 89 * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be 90 * read by uncrypt. 91 * 92 * @hide 93 */ 94 public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file"); 95 96 /** 97 * UNCRYPT_STATUS_FILE stores the time cost (and error code in the case of a failure) 98 * of uncrypt. 99 * 100 * @hide 101 */ 102 public static final File UNCRYPT_STATUS_FILE = new File(RECOVERY_DIR, "uncrypt_status"); 103 104 // Length limits for reading files. 105 private static final int LOG_FILE_MAX_LENGTH = 64 * 1024; 106 107 // Prevent concurrent execution of requests. 108 private static final Object sRequestLock = new Object(); 109 110 private final IRecoverySystem mService; 111 112 /** 113 * Interface definition for a callback to be invoked regularly as 114 * verification proceeds. 115 */ 116 public interface ProgressListener { 117 /** 118 * Called periodically as the verification progresses. 119 * 120 * @param progress the approximate percentage of the 121 * verification that has been completed, ranging from 0 122 * to 100 (inclusive). 123 */ 124 public void onProgress(int progress); 125 } 126 127 /** @return the set of certs that can be used to sign an OTA package. */ 128 private static HashSet<X509Certificate> getTrustedCerts(File keystore) 129 throws IOException, GeneralSecurityException { 130 HashSet<X509Certificate> trusted = new HashSet<X509Certificate>(); 131 if (keystore == null) { 132 keystore = DEFAULT_KEYSTORE; 133 } 134 ZipFile zip = new ZipFile(keystore); 135 try { 136 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 137 Enumeration<? extends ZipEntry> entries = zip.entries(); 138 while (entries.hasMoreElements()) { 139 ZipEntry entry = entries.nextElement(); 140 InputStream is = zip.getInputStream(entry); 141 try { 142 trusted.add((X509Certificate) cf.generateCertificate(is)); 143 } finally { 144 is.close(); 145 } 146 } 147 } finally { 148 zip.close(); 149 } 150 return trusted; 151 } 152 153 /** 154 * Verify the cryptographic signature of a system update package 155 * before installing it. Note that the package is also verified 156 * separately by the installer once the device is rebooted into 157 * the recovery system. This function will return only if the 158 * package was successfully verified; otherwise it will throw an 159 * exception. 160 * 161 * Verification of a package can take significant time, so this 162 * function should not be called from a UI thread. Interrupting 163 * the thread while this function is in progress will result in a 164 * SecurityException being thrown (and the thread's interrupt flag 165 * will be cleared). 166 * 167 * @param packageFile the package to be verified 168 * @param listener an object to receive periodic progress 169 * updates as verification proceeds. May be null. 170 * @param deviceCertsZipFile the zip file of certificates whose 171 * public keys we will accept. Verification succeeds if the 172 * package is signed by the private key corresponding to any 173 * public key in this file. May be null to use the system default 174 * file (currently "/system/etc/security/otacerts.zip"). 175 * 176 * @throws IOException if there were any errors reading the 177 * package or certs files. 178 * @throws GeneralSecurityException if verification failed 179 */ 180 public static void verifyPackage(File packageFile, 181 ProgressListener listener, 182 File deviceCertsZipFile) 183 throws IOException, GeneralSecurityException { 184 final long fileLen = packageFile.length(); 185 186 final RandomAccessFile raf = new RandomAccessFile(packageFile, "r"); 187 try { 188 final long startTimeMillis = System.currentTimeMillis(); 189 if (listener != null) { 190 listener.onProgress(0); 191 } 192 193 raf.seek(fileLen - 6); 194 byte[] footer = new byte[6]; 195 raf.readFully(footer); 196 197 if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) { 198 throw new SignatureException("no signature in file (no footer)"); 199 } 200 201 final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8); 202 final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8); 203 204 byte[] eocd = new byte[commentSize + 22]; 205 raf.seek(fileLen - (commentSize + 22)); 206 raf.readFully(eocd); 207 208 // Check that we have found the start of the 209 // end-of-central-directory record. 210 if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b || 211 eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) { 212 throw new SignatureException("no signature in file (bad footer)"); 213 } 214 215 for (int i = 4; i < eocd.length-3; ++i) { 216 if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b && 217 eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) { 218 throw new SignatureException("EOCD marker found after start of EOCD"); 219 } 220 } 221 222 // Parse the signature 223 PKCS7 block = 224 new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart)); 225 226 // Take the first certificate from the signature (packages 227 // should contain only one). 228 X509Certificate[] certificates = block.getCertificates(); 229 if (certificates == null || certificates.length == 0) { 230 throw new SignatureException("signature contains no certificates"); 231 } 232 X509Certificate cert = certificates[0]; 233 PublicKey signatureKey = cert.getPublicKey(); 234 235 SignerInfo[] signerInfos = block.getSignerInfos(); 236 if (signerInfos == null || signerInfos.length == 0) { 237 throw new SignatureException("signature contains no signedData"); 238 } 239 SignerInfo signerInfo = signerInfos[0]; 240 241 // Check that the public key of the certificate contained 242 // in the package equals one of our trusted public keys. 243 boolean verified = false; 244 HashSet<X509Certificate> trusted = getTrustedCerts( 245 deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile); 246 for (X509Certificate c : trusted) { 247 if (c.getPublicKey().equals(signatureKey)) { 248 verified = true; 249 break; 250 } 251 } 252 if (!verified) { 253 throw new SignatureException("signature doesn't match any trusted key"); 254 } 255 256 // The signature cert matches a trusted key. Now verify that 257 // the digest in the cert matches the actual file data. 258 raf.seek(0); 259 final ProgressListener listenerForInner = listener; 260 SignerInfo verifyResult = block.verify(signerInfo, new InputStream() { 261 // The signature covers all of the OTA package except the 262 // archive comment and its 2-byte length. 263 long toRead = fileLen - commentSize - 2; 264 long soFar = 0; 265 266 int lastPercent = 0; 267 long lastPublishTime = startTimeMillis; 268 269 @Override 270 public int read() throws IOException { 271 throw new UnsupportedOperationException(); 272 } 273 274 @Override 275 public int read(byte[] b, int off, int len) throws IOException { 276 if (soFar >= toRead) { 277 return -1; 278 } 279 if (Thread.currentThread().isInterrupted()) { 280 return -1; 281 } 282 283 int size = len; 284 if (soFar + size > toRead) { 285 size = (int)(toRead - soFar); 286 } 287 int read = raf.read(b, off, size); 288 soFar += read; 289 290 if (listenerForInner != null) { 291 long now = System.currentTimeMillis(); 292 int p = (int)(soFar * 100 / toRead); 293 if (p > lastPercent && 294 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { 295 lastPercent = p; 296 lastPublishTime = now; 297 listenerForInner.onProgress(lastPercent); 298 } 299 } 300 301 return read; 302 } 303 }); 304 305 final boolean interrupted = Thread.interrupted(); 306 if (listener != null) { 307 listener.onProgress(100); 308 } 309 310 if (interrupted) { 311 throw new SignatureException("verification was interrupted"); 312 } 313 314 if (verifyResult == null) { 315 throw new SignatureException("signature digest verification failed"); 316 } 317 } finally { 318 raf.close(); 319 } 320 } 321 322 /** 323 * Process a given package with uncrypt. No-op if the package is not on the 324 * /data partition. 325 * 326 * @param Context the Context to use 327 * @param packageFile the package to be processed 328 * @param listener an object to receive periodic progress updates as 329 * processing proceeds. May be null. 330 * @param handler the Handler upon which the callbacks will be 331 * executed. 332 * 333 * @throws IOException if there were any errors processing the package file. 334 * 335 * @hide 336 */ 337 @SystemApi 338 public static void processPackage(Context context, 339 File packageFile, 340 final ProgressListener listener, 341 final Handler handler) 342 throws IOException { 343 String filename = packageFile.getCanonicalPath(); 344 if (!filename.startsWith("/data/")) { 345 return; 346 } 347 348 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 349 IRecoverySystemProgressListener progressListener = null; 350 if (listener != null) { 351 final Handler progressHandler; 352 if (handler != null) { 353 progressHandler = handler; 354 } else { 355 progressHandler = new Handler(context.getMainLooper()); 356 } 357 progressListener = new IRecoverySystemProgressListener.Stub() { 358 int lastProgress = 0; 359 long lastPublishTime = System.currentTimeMillis(); 360 361 @Override 362 public void onProgress(final int progress) { 363 final long now = System.currentTimeMillis(); 364 progressHandler.post(new Runnable() { 365 @Override 366 public void run() { 367 if (progress > lastProgress && 368 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { 369 lastProgress = progress; 370 lastPublishTime = now; 371 listener.onProgress(progress); 372 } 373 } 374 }); 375 } 376 }; 377 } 378 379 if (!rs.uncrypt(filename, progressListener)) { 380 throw new IOException("process package failed"); 381 } 382 } 383 384 /** 385 * Process a given package with uncrypt. No-op if the package is not on the 386 * /data partition. 387 * 388 * @param Context the Context to use 389 * @param packageFile the package to be processed 390 * @param listener an object to receive periodic progress updates as 391 * processing proceeds. May be null. 392 * 393 * @throws IOException if there were any errors processing the package file. 394 * 395 * @hide 396 */ 397 @SystemApi 398 public static void processPackage(Context context, 399 File packageFile, 400 final ProgressListener listener) 401 throws IOException { 402 processPackage(context, packageFile, listener, null); 403 } 404 405 /** 406 * Reboots the device in order to install the given update 407 * package. 408 * Requires the {@link android.Manifest.permission#REBOOT} permission. 409 * 410 * @param context the Context to use 411 * @param packageFile the update package to install. Must be on 412 * a partition mountable by recovery. (The set of partitions 413 * known to recovery may vary from device to device. Generally, 414 * /cache and /data are safe.) 415 * 416 * @throws IOException if writing the recovery command file 417 * fails, or if the reboot itself fails. 418 */ 419 public static void installPackage(Context context, File packageFile) 420 throws IOException { 421 installPackage(context, packageFile, false); 422 } 423 424 /** 425 * If the package hasn't been processed (i.e. uncrypt'd), set up 426 * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the 427 * reboot. 428 * 429 * @param context the Context to use 430 * @param packageFile the update package to install. Must be on a 431 * partition mountable by recovery. 432 * @param processed if the package has been processed (uncrypt'd). 433 * 434 * @throws IOException if writing the recovery command file fails, or if 435 * the reboot itself fails. 436 * 437 * @hide 438 */ 439 @SystemApi 440 public static void installPackage(Context context, File packageFile, boolean processed) 441 throws IOException { 442 synchronized (sRequestLock) { 443 LOG_FILE.delete(); 444 // Must delete the file in case it was created by system server. 445 UNCRYPT_PACKAGE_FILE.delete(); 446 447 String filename = packageFile.getCanonicalPath(); 448 Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); 449 450 // If the package name ends with "_s.zip", it's a security update. 451 boolean securityUpdate = filename.endsWith("_s.zip"); 452 453 // If the package is on the /data partition, the package needs to 454 // be processed (i.e. uncrypt'd). The caller specifies if that has 455 // been done in 'processed' parameter. 456 if (filename.startsWith("/data/")) { 457 if (processed) { 458 if (!BLOCK_MAP_FILE.exists()) { 459 Log.e(TAG, "Package claimed to have been processed but failed to find " 460 + "the block map file."); 461 throw new IOException("Failed to find block map file"); 462 } 463 } else { 464 FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE); 465 try { 466 uncryptFile.write(filename + "\n"); 467 } finally { 468 uncryptFile.close(); 469 } 470 // UNCRYPT_PACKAGE_FILE needs to be readable and writable 471 // by system server. 472 if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false) 473 || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) { 474 Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE); 475 } 476 477 BLOCK_MAP_FILE.delete(); 478 } 479 480 // If the package is on the /data partition, use the block map 481 // file as the package name instead. 482 filename = "@/cache/recovery/block.map"; 483 } 484 485 final String filenameArg = "--update_package=" + filename + "\n"; 486 final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n"; 487 final String securityArg = "--security\n"; 488 489 String command = filenameArg + localeArg; 490 if (securityUpdate) { 491 command += securityArg; 492 } 493 494 RecoverySystem rs = (RecoverySystem) context.getSystemService( 495 Context.RECOVERY_SERVICE); 496 if (!rs.setupBcb(command)) { 497 throw new IOException("Setup BCB failed"); 498 } 499 500 // Having set up the BCB (bootloader control block), go ahead and reboot 501 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 502 pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE); 503 504 throw new IOException("Reboot failed (no permissions?)"); 505 } 506 } 507 508 /** 509 * Schedule to install the given package on next boot. The caller needs to 510 * ensure that the package must have been processed (uncrypt'd) if needed. 511 * It sets up the command in BCB (bootloader control block), which will 512 * be read by the bootloader and the recovery image. 513 * 514 * @param Context the Context to use. 515 * @param packageFile the package to be installed. 516 * 517 * @throws IOException if there were any errors setting up the BCB. 518 * 519 * @hide 520 */ 521 @SystemApi 522 public static void scheduleUpdateOnBoot(Context context, File packageFile) 523 throws IOException { 524 String filename = packageFile.getCanonicalPath(); 525 boolean securityUpdate = filename.endsWith("_s.zip"); 526 527 // If the package is on the /data partition, use the block map file as 528 // the package name instead. 529 if (filename.startsWith("/data/")) { 530 filename = "@/cache/recovery/block.map"; 531 } 532 533 final String filenameArg = "--update_package=" + filename + "\n"; 534 final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n"; 535 final String securityArg = "--security\n"; 536 537 String command = filenameArg + localeArg; 538 if (securityUpdate) { 539 command += securityArg; 540 } 541 542 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 543 if (!rs.setupBcb(command)) { 544 throw new IOException("schedule update on boot failed"); 545 } 546 } 547 548 /** 549 * Cancel any scheduled update by clearing up the BCB (bootloader control 550 * block). 551 * 552 * @param Context the Context to use. 553 * 554 * @throws IOException if there were any errors clearing up the BCB. 555 * 556 * @hide 557 */ 558 @SystemApi 559 public static void cancelScheduledUpdate(Context context) 560 throws IOException { 561 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 562 if (!rs.clearBcb()) { 563 throw new IOException("cancel scheduled update failed"); 564 } 565 } 566 567 /** 568 * Reboots the device and wipes the user data and cache 569 * partitions. This is sometimes called a "factory reset", which 570 * is something of a misnomer because the system partition is not 571 * restored to its factory state. Requires the 572 * {@link android.Manifest.permission#REBOOT} permission. 573 * 574 * @param context the Context to use 575 * 576 * @throws IOException if writing the recovery command file 577 * fails, or if the reboot itself fails. 578 * @throws SecurityException if the current user is not allowed to wipe data. 579 */ 580 public static void rebootWipeUserData(Context context) throws IOException { 581 rebootWipeUserData(context, false /* shutdown */, context.getPackageName(), 582 false /* force */); 583 } 584 585 /** {@hide} */ 586 public static void rebootWipeUserData(Context context, String reason) throws IOException { 587 rebootWipeUserData(context, false /* shutdown */, reason, false /* force */); 588 } 589 590 /** {@hide} */ 591 public static void rebootWipeUserData(Context context, boolean shutdown) 592 throws IOException { 593 rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */); 594 } 595 596 /** 597 * Reboots the device and wipes the user data and cache 598 * partitions. This is sometimes called a "factory reset", which 599 * is something of a misnomer because the system partition is not 600 * restored to its factory state. Requires the 601 * {@link android.Manifest.permission#REBOOT} permission. 602 * 603 * @param context the Context to use 604 * @param shutdown if true, the device will be powered down after 605 * the wipe completes, rather than being rebooted 606 * back to the regular system. 607 * @param reason the reason for the wipe that is visible in the logs 608 * @param force whether the {@link UserManager.DISALLOW_FACTORY_RESET} user restriction 609 * should be ignored 610 * 611 * @throws IOException if writing the recovery command file 612 * fails, or if the reboot itself fails. 613 * @throws SecurityException if the current user is not allowed to wipe data. 614 * 615 * @hide 616 */ 617 public static void rebootWipeUserData(Context context, boolean shutdown, String reason, 618 boolean force) throws IOException { 619 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 620 if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) { 621 throw new SecurityException("Wiping data is not allowed for this user."); 622 } 623 final ConditionVariable condition = new ConditionVariable(); 624 625 Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION"); 626 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 627 context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM, 628 android.Manifest.permission.MASTER_CLEAR, 629 new BroadcastReceiver() { 630 @Override 631 public void onReceive(Context context, Intent intent) { 632 condition.open(); 633 } 634 }, null, 0, null, null); 635 636 // Block until the ordered broadcast has completed. 637 condition.block(); 638 639 String shutdownArg = null; 640 if (shutdown) { 641 shutdownArg = "--shutdown_after"; 642 } 643 644 String reasonArg = null; 645 if (!TextUtils.isEmpty(reason)) { 646 reasonArg = "--reason=" + sanitizeArg(reason); 647 } 648 649 final String localeArg = "--locale=" + Locale.getDefault().toString(); 650 bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg); 651 } 652 653 /** 654 * Reboot into the recovery system to wipe the /cache partition. 655 * @throws IOException if something goes wrong. 656 */ 657 public static void rebootWipeCache(Context context) throws IOException { 658 rebootWipeCache(context, context.getPackageName()); 659 } 660 661 /** {@hide} */ 662 public static void rebootWipeCache(Context context, String reason) throws IOException { 663 String reasonArg = null; 664 if (!TextUtils.isEmpty(reason)) { 665 reasonArg = "--reason=" + sanitizeArg(reason); 666 } 667 668 final String localeArg = "--locale=" + Locale.getDefault().toString(); 669 bootCommand(context, "--wipe_cache", reasonArg, localeArg); 670 } 671 672 /** 673 * Reboot into recovery and wipe the A/B device. 674 * 675 * @param Context the Context to use. 676 * @param packageFile the wipe package to be applied. 677 * @param reason the reason to wipe. 678 * 679 * @throws IOException if something goes wrong. 680 * 681 * @hide 682 */ 683 @SystemApi 684 public static void rebootWipeAb(Context context, File packageFile, String reason) 685 throws IOException { 686 String reasonArg = null; 687 if (!TextUtils.isEmpty(reason)) { 688 reasonArg = "--reason=" + sanitizeArg(reason); 689 } 690 691 final String filename = packageFile.getCanonicalPath(); 692 final String filenameArg = "--wipe_package=" + filename; 693 final String localeArg = "--locale=" + Locale.getDefault().toString(); 694 bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg); 695 } 696 697 /** 698 * Reboot into the recovery system with the supplied argument. 699 * @param args to pass to the recovery utility. 700 * @throws IOException if something goes wrong. 701 */ 702 private static void bootCommand(Context context, String... args) throws IOException { 703 LOG_FILE.delete(); 704 705 StringBuilder command = new StringBuilder(); 706 for (String arg : args) { 707 if (!TextUtils.isEmpty(arg)) { 708 command.append(arg); 709 command.append("\n"); 710 } 711 } 712 713 // Write the command into BCB (bootloader control block) and boot from 714 // there. Will not return unless failed. 715 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 716 rs.rebootRecoveryWithCommand(command.toString()); 717 718 throw new IOException("Reboot failed (no permissions?)"); 719 } 720 721 // Read last_install; then report time (in seconds) and I/O (in MiB) for 722 // this update to tron. 723 // Only report on the reboots immediately after an OTA update. 724 private static void parseLastInstallLog(Context context) { 725 try (BufferedReader in = new BufferedReader(new FileReader(LAST_INSTALL_FILE))) { 726 String line = null; 727 int bytesWrittenInMiB = -1, bytesStashedInMiB = -1; 728 int timeTotal = -1; 729 int uncryptTime = -1; 730 int sourceVersion = -1; 731 while ((line = in.readLine()) != null) { 732 // Here is an example of lines in last_install: 733 // ... 734 // time_total: 101 735 // bytes_written_vendor: 51074 736 // bytes_stashed_vendor: 200 737 int numIndex = line.indexOf(':'); 738 if (numIndex == -1 || numIndex + 1 >= line.length()) { 739 continue; 740 } 741 String numString = line.substring(numIndex + 1).trim(); 742 long parsedNum; 743 try { 744 parsedNum = Long.parseLong(numString); 745 } catch (NumberFormatException ignored) { 746 Log.e(TAG, "Failed to parse numbers in " + line); 747 continue; 748 } 749 750 final int MiB = 1024 * 1024; 751 int scaled; 752 try { 753 if (line.startsWith("bytes")) { 754 scaled = Math.toIntExact(parsedNum / MiB); 755 } else { 756 scaled = Math.toIntExact(parsedNum); 757 } 758 } catch (ArithmeticException ignored) { 759 Log.e(TAG, "Number overflows in " + line); 760 continue; 761 } 762 763 if (line.startsWith("time")) { 764 timeTotal = scaled; 765 } else if (line.startsWith("uncrypt_time")) { 766 uncryptTime = scaled; 767 } else if (line.startsWith("source_build")) { 768 sourceVersion = scaled; 769 } else if (line.startsWith("bytes_written")) { 770 bytesWrittenInMiB = (bytesWrittenInMiB == -1) ? scaled : 771 bytesWrittenInMiB + scaled; 772 } else if (line.startsWith("bytes_stashed")) { 773 bytesStashedInMiB = (bytesStashedInMiB == -1) ? scaled : 774 bytesStashedInMiB + scaled; 775 } 776 } 777 778 // Don't report data to tron if corresponding entry isn't found in last_install. 779 if (timeTotal != -1) { 780 MetricsLogger.histogram(context, "ota_time_total", timeTotal); 781 } 782 if (uncryptTime != -1) { 783 MetricsLogger.histogram(context, "ota_uncrypt_time", uncryptTime); 784 } 785 if (sourceVersion != -1) { 786 MetricsLogger.histogram(context, "ota_source_version", sourceVersion); 787 } 788 if (bytesWrittenInMiB != -1) { 789 MetricsLogger.histogram(context, "ota_written_in_MiBs", bytesWrittenInMiB); 790 } 791 if (bytesStashedInMiB != -1) { 792 MetricsLogger.histogram(context, "ota_stashed_in_MiBs", bytesStashedInMiB); 793 } 794 795 } catch (IOException e) { 796 Log.e(TAG, "Failed to read lines in last_install", e); 797 } 798 } 799 800 /** 801 * Called after booting to process and remove recovery-related files. 802 * @return the log file from recovery, or null if none was found. 803 * 804 * @hide 805 */ 806 public static String handleAftermath(Context context) { 807 // Record the tail of the LOG_FILE 808 String log = null; 809 try { 810 log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n"); 811 } catch (FileNotFoundException e) { 812 Log.i(TAG, "No recovery log file"); 813 } catch (IOException e) { 814 Log.e(TAG, "Error reading recovery log", e); 815 } 816 817 if (log != null) { 818 parseLastInstallLog(context); 819 } 820 821 // Only remove the OTA package if it's partially processed (uncrypt'd). 822 boolean reservePackage = BLOCK_MAP_FILE.exists(); 823 if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) { 824 String filename = null; 825 try { 826 filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null); 827 } catch (IOException e) { 828 Log.e(TAG, "Error reading uncrypt file", e); 829 } 830 831 // Remove the OTA package on /data that has been (possibly 832 // partially) processed. (Bug: 24973532) 833 if (filename != null && filename.startsWith("/data")) { 834 if (UNCRYPT_PACKAGE_FILE.delete()) { 835 Log.i(TAG, "Deleted: " + filename); 836 } else { 837 Log.e(TAG, "Can't delete: " + filename); 838 } 839 } 840 } 841 842 // We keep the update logs (beginning with LAST_PREFIX), and optionally 843 // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE 844 // will be created at the end of a successful uncrypt. If seeing this 845 // file, we keep the block map file and the file that contains the 846 // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for 847 // GmsCore to avoid re-downloading everything again. 848 String[] names = RECOVERY_DIR.list(); 849 for (int i = 0; names != null && i < names.length; i++) { 850 if (names[i].startsWith(LAST_PREFIX)) continue; 851 if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue; 852 if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue; 853 854 recursiveDelete(new File(RECOVERY_DIR, names[i])); 855 } 856 857 return log; 858 } 859 860 /** 861 * Internally, delete a given file or directory recursively. 862 */ 863 private static void recursiveDelete(File name) { 864 if (name.isDirectory()) { 865 String[] files = name.list(); 866 for (int i = 0; files != null && i < files.length; i++) { 867 File f = new File(name, files[i]); 868 recursiveDelete(f); 869 } 870 } 871 872 if (!name.delete()) { 873 Log.e(TAG, "Can't delete: " + name); 874 } else { 875 Log.i(TAG, "Deleted: " + name); 876 } 877 } 878 879 /** 880 * Talks to RecoverySystemService via Binder to trigger uncrypt. 881 */ 882 private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) { 883 try { 884 return mService.uncrypt(packageFile, listener); 885 } catch (RemoteException unused) { 886 } 887 return false; 888 } 889 890 /** 891 * Talks to RecoverySystemService via Binder to set up the BCB. 892 */ 893 private boolean setupBcb(String command) { 894 try { 895 return mService.setupBcb(command); 896 } catch (RemoteException unused) { 897 } 898 return false; 899 } 900 901 /** 902 * Talks to RecoverySystemService via Binder to clear up the BCB. 903 */ 904 private boolean clearBcb() { 905 try { 906 return mService.clearBcb(); 907 } catch (RemoteException unused) { 908 } 909 return false; 910 } 911 912 /** 913 * Talks to RecoverySystemService via Binder to set up the BCB command and 914 * reboot into recovery accordingly. 915 */ 916 private void rebootRecoveryWithCommand(String command) { 917 try { 918 mService.rebootRecoveryWithCommand(command); 919 } catch (RemoteException ignored) { 920 } 921 } 922 923 /** 924 * Internally, recovery treats each line of the command file as a separate 925 * argv, so we only need to protect against newlines and nulls. 926 */ 927 private static String sanitizeArg(String arg) { 928 arg = arg.replace('\0', '?'); 929 arg = arg.replace('\n', '?'); 930 return arg; 931 } 932 933 934 /** 935 * @removed Was previously made visible by accident. 936 */ 937 public RecoverySystem() { 938 mService = null; 939 } 940 941 /** 942 * @hide 943 */ 944 public RecoverySystem(IRecoverySystem service) { 945 mService = service; 946 } 947 } 948