1 /* 2 * Copyright (C) 2017 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 com.android.server.backup.restore; 18 19 import static com.android.server.backup.BackupManagerService.BACKUP_FILE_HEADER_MAGIC; 20 import static com.android.server.backup.BackupManagerService.BACKUP_FILE_VERSION; 21 import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_FILENAME; 22 import static com.android.server.backup.BackupManagerService.BACKUP_METADATA_FILENAME; 23 import static com.android.server.backup.BackupManagerService.DEBUG; 24 import static com.android.server.backup.BackupManagerService.MORE_DEBUG; 25 import static com.android.server.backup.BackupManagerService.OP_TYPE_RESTORE_WAIT; 26 import static com.android.server.backup.BackupManagerService.SETTINGS_PACKAGE; 27 import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; 28 import static com.android.server.backup.BackupManagerService.TAG; 29 import static com.android.server.backup.BackupPasswordManager.PBKDF_CURRENT; 30 import static com.android.server.backup.BackupPasswordManager.PBKDF_FALLBACK; 31 import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT; 32 33 import android.app.ApplicationThreadConstants; 34 import android.app.IBackupAgent; 35 import android.app.backup.FullBackup; 36 import android.app.backup.IBackupManagerMonitor; 37 import android.app.backup.IFullBackupRestoreObserver; 38 import android.content.pm.ApplicationInfo; 39 import android.content.pm.PackageInfo; 40 import android.content.pm.PackageManager.NameNotFoundException; 41 import android.content.pm.PackageManagerInternal; 42 import android.content.pm.Signature; 43 import android.os.Environment; 44 import android.os.ParcelFileDescriptor; 45 import android.os.RemoteException; 46 import android.util.Slog; 47 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.internal.util.Preconditions; 50 import com.android.server.LocalServices; 51 import com.android.server.backup.BackupAgentTimeoutParameters; 52 import com.android.server.backup.BackupManagerService; 53 import com.android.server.backup.FileMetadata; 54 import com.android.server.backup.KeyValueAdbRestoreEngine; 55 import com.android.server.backup.PackageManagerBackupAgent; 56 import com.android.server.backup.fullbackup.FullBackupObbConnection; 57 import com.android.server.backup.utils.BytesReadListener; 58 import com.android.server.backup.utils.FullBackupRestoreObserverUtils; 59 import com.android.server.backup.utils.PasswordUtils; 60 import com.android.server.backup.utils.RestoreUtils; 61 import com.android.server.backup.utils.TarBackupReader; 62 63 import java.io.FileInputStream; 64 import java.io.FileOutputStream; 65 import java.io.IOException; 66 import java.io.InputStream; 67 import java.security.InvalidAlgorithmParameterException; 68 import java.security.InvalidKeyException; 69 import java.security.NoSuchAlgorithmException; 70 import java.util.Arrays; 71 import java.util.HashMap; 72 import java.util.HashSet; 73 import java.util.concurrent.atomic.AtomicBoolean; 74 import java.util.zip.InflaterInputStream; 75 76 import javax.crypto.BadPaddingException; 77 import javax.crypto.Cipher; 78 import javax.crypto.CipherInputStream; 79 import javax.crypto.IllegalBlockSizeException; 80 import javax.crypto.NoSuchPaddingException; 81 import javax.crypto.SecretKey; 82 import javax.crypto.spec.IvParameterSpec; 83 import javax.crypto.spec.SecretKeySpec; 84 85 public class PerformAdbRestoreTask implements Runnable { 86 87 private final BackupManagerService mBackupManagerService; 88 private final ParcelFileDescriptor mInputFile; 89 private final String mCurrentPassword; 90 private final String mDecryptPassword; 91 private final AtomicBoolean mLatchObject; 92 private final PackageManagerBackupAgent mPackageManagerBackupAgent; 93 private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver(); 94 95 private IFullBackupRestoreObserver mObserver; 96 private IBackupAgent mAgent; 97 private String mAgentPackage; 98 private ApplicationInfo mTargetApp; 99 private FullBackupObbConnection mObbConnection = null; 100 private ParcelFileDescriptor[] mPipes = null; 101 private byte[] mWidgetData = null; 102 private long mAppVersion; 103 104 private long mBytes; 105 private final BackupAgentTimeoutParameters mAgentTimeoutParameters; 106 107 // Runner that can be placed on a separate thread to do in-process invocation 108 // of the "restore finished" API asynchronously. Used by adb restore. 109 private static class RestoreFinishedRunnable implements Runnable { 110 111 private final IBackupAgent mAgent; 112 private final int mToken; 113 private final BackupManagerService mBackupManagerService; 114 115 RestoreFinishedRunnable(IBackupAgent agent, int token, 116 BackupManagerService backupManagerService) { 117 mAgent = agent; 118 mToken = token; 119 mBackupManagerService = backupManagerService; 120 } 121 122 @Override 123 public void run() { 124 try { 125 mAgent.doRestoreFinished(mToken, mBackupManagerService.getBackupManagerBinder()); 126 } catch (RemoteException e) { 127 // never happens; this is used only for local binder calls 128 } 129 } 130 } 131 132 // possible handling states for a given package in the restore dataset 133 private final HashMap<String, RestorePolicy> mPackagePolicies 134 = new HashMap<>(); 135 136 // installer package names for each encountered app, derived from the manifests 137 private final HashMap<String, String> mPackageInstallers = new HashMap<>(); 138 139 // Signatures for a given package found in its manifest file 140 private final HashMap<String, Signature[]> mManifestSignatures 141 = new HashMap<>(); 142 143 // Packages we've already wiped data on when restoring their first file 144 private final HashSet<String> mClearedPackages = new HashSet<>(); 145 146 public PerformAdbRestoreTask(BackupManagerService backupManagerService, 147 ParcelFileDescriptor fd, String curPassword, String decryptPassword, 148 IFullBackupRestoreObserver observer, AtomicBoolean latch) { 149 this.mBackupManagerService = backupManagerService; 150 mInputFile = fd; 151 mCurrentPassword = curPassword; 152 mDecryptPassword = decryptPassword; 153 mObserver = observer; 154 mLatchObject = latch; 155 mAgent = null; 156 mPackageManagerBackupAgent = backupManagerService.makeMetadataAgent(); 157 mAgentPackage = null; 158 mTargetApp = null; 159 mObbConnection = new FullBackupObbConnection(backupManagerService); 160 mAgentTimeoutParameters = Preconditions.checkNotNull( 161 backupManagerService.getAgentTimeoutParameters(), 162 "Timeout parameters cannot be null"); 163 164 // Which packages we've already wiped data on. We prepopulate this 165 // with a whitelist of packages known to be unclearable. 166 mClearedPackages.add("android"); 167 mClearedPackages.add(SETTINGS_PACKAGE); 168 } 169 170 @Override 171 public void run() { 172 Slog.i(TAG, "--- Performing full-dataset restore ---"); 173 mObbConnection.establish(); 174 mObserver = FullBackupRestoreObserverUtils.sendStartRestore(mObserver); 175 176 // Are we able to restore shared-storage data? 177 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 178 mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT); 179 } 180 181 FileInputStream rawInStream = null; 182 try { 183 if (!mBackupManagerService.backupPasswordMatches(mCurrentPassword)) { 184 if (DEBUG) { 185 Slog.w(TAG, "Backup password mismatch; aborting"); 186 } 187 return; 188 } 189 190 mBytes = 0; 191 192 rawInStream = new FileInputStream(mInputFile.getFileDescriptor()); 193 194 InputStream tarInputStream = parseBackupFileHeaderAndReturnTarStream(rawInStream, 195 mDecryptPassword); 196 if (tarInputStream == null) { 197 // There was an error reading the backup file, which is already handled and logged. 198 // Just abort. 199 return; 200 } 201 202 byte[] buffer = new byte[32 * 1024]; 203 boolean didRestore; 204 do { 205 didRestore = restoreOneFile(tarInputStream, false /* mustKillAgent */, buffer, 206 null /* onlyPackage */, true /* allowApks */, 207 mBackupManagerService.generateRandomIntegerToken(), null /* monitor */); 208 } while (didRestore); 209 210 if (MORE_DEBUG) { 211 Slog.v(TAG, "Done consuming input tarfile, total bytes=" + mBytes); 212 } 213 } catch (IOException e) { 214 Slog.e(TAG, "Unable to read restore input"); 215 } finally { 216 tearDownPipes(); 217 tearDownAgent(mTargetApp, true); 218 219 try { 220 if (rawInStream != null) { 221 rawInStream.close(); 222 } 223 mInputFile.close(); 224 } catch (IOException e) { 225 Slog.w(TAG, "Close of restore data pipe threw", e); 226 /* nothing we can do about this */ 227 } 228 synchronized (mLatchObject) { 229 mLatchObject.set(true); 230 mLatchObject.notifyAll(); 231 } 232 mObbConnection.tearDown(); 233 mObserver = FullBackupRestoreObserverUtils.sendEndRestore(mObserver); 234 Slog.d(TAG, "Full restore pass complete."); 235 mBackupManagerService.getWakelock().release(); 236 } 237 } 238 239 private static void readFullyOrThrow(InputStream in, byte[] buffer) throws IOException { 240 int offset = 0; 241 while (offset < buffer.length) { 242 int bytesRead = in.read(buffer, offset, buffer.length - offset); 243 if (bytesRead <= 0) { 244 throw new IOException("Couldn't fully read data"); 245 } 246 offset += bytesRead; 247 } 248 } 249 250 @VisibleForTesting 251 public static InputStream parseBackupFileHeaderAndReturnTarStream( 252 InputStream rawInputStream, 253 String decryptPassword) 254 throws IOException { 255 // First, parse out the unencrypted/uncompressed header 256 boolean compressed = false; 257 InputStream preCompressStream = rawInputStream; 258 259 boolean okay = false; 260 final int headerLen = BACKUP_FILE_HEADER_MAGIC.length(); 261 byte[] streamHeader = new byte[headerLen]; 262 readFullyOrThrow(rawInputStream, streamHeader); 263 byte[] magicBytes = BACKUP_FILE_HEADER_MAGIC.getBytes( 264 "UTF-8"); 265 if (Arrays.equals(magicBytes, streamHeader)) { 266 // okay, header looks good. now parse out the rest of the fields. 267 String s = readHeaderLine(rawInputStream); 268 final int archiveVersion = Integer.parseInt(s); 269 if (archiveVersion <= BACKUP_FILE_VERSION) { 270 // okay, it's a version we recognize. if it's version 1, we may need 271 // to try two different PBKDF2 regimes to compare checksums. 272 final boolean pbkdf2Fallback = (archiveVersion == 1); 273 274 s = readHeaderLine(rawInputStream); 275 compressed = (Integer.parseInt(s) != 0); 276 s = readHeaderLine(rawInputStream); 277 if (s.equals("none")) { 278 // no more header to parse; we're good to go 279 okay = true; 280 } else if (decryptPassword != null && decryptPassword.length() > 0) { 281 preCompressStream = decodeAesHeaderAndInitialize( 282 decryptPassword, s, pbkdf2Fallback, 283 rawInputStream); 284 if (preCompressStream != null) { 285 okay = true; 286 } 287 } else { 288 Slog.w(TAG, "Archive is encrypted but no password given"); 289 } 290 } else { 291 Slog.w(TAG, "Wrong header version: " + s); 292 } 293 } else { 294 Slog.w(TAG, "Didn't read the right header magic"); 295 } 296 297 if (!okay) { 298 Slog.w(TAG, "Invalid restore data; aborting."); 299 return null; 300 } 301 302 // okay, use the right stream layer based on compression 303 return compressed ? new InflaterInputStream(preCompressStream) : preCompressStream; 304 } 305 306 private static String readHeaderLine(InputStream in) throws IOException { 307 int c; 308 StringBuilder buffer = new StringBuilder(80); 309 while ((c = in.read()) >= 0) { 310 if (c == '\n') { 311 break; // consume and discard the newlines 312 } 313 buffer.append((char) c); 314 } 315 return buffer.toString(); 316 } 317 318 private static InputStream attemptMasterKeyDecryption(String decryptPassword, String algorithm, 319 byte[] userSalt, byte[] ckSalt, 320 int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream, 321 boolean doLog) { 322 InputStream result = null; 323 324 try { 325 Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); 326 SecretKey userKey = PasswordUtils 327 .buildPasswordKey(algorithm, decryptPassword, userSalt, 328 rounds); 329 byte[] IV = PasswordUtils.hexToByteArray(userIvHex); 330 IvParameterSpec ivSpec = new IvParameterSpec(IV); 331 c.init(Cipher.DECRYPT_MODE, 332 new SecretKeySpec(userKey.getEncoded(), "AES"), 333 ivSpec); 334 byte[] mkCipher = PasswordUtils.hexToByteArray(masterKeyBlobHex); 335 byte[] mkBlob = c.doFinal(mkCipher); 336 337 // first, the master key IV 338 int offset = 0; 339 int len = mkBlob[offset++]; 340 IV = Arrays.copyOfRange(mkBlob, offset, offset + len); 341 offset += len; 342 // then the master key itself 343 len = mkBlob[offset++]; 344 byte[] mk = Arrays.copyOfRange(mkBlob, 345 offset, offset + len); 346 offset += len; 347 // and finally the master key checksum hash 348 len = mkBlob[offset++]; 349 byte[] mkChecksum = Arrays.copyOfRange(mkBlob, 350 offset, offset + len); 351 352 // now validate the decrypted master key against the checksum 353 byte[] calculatedCk = PasswordUtils.makeKeyChecksum(algorithm, mk, ckSalt, 354 rounds); 355 if (Arrays.equals(calculatedCk, mkChecksum)) { 356 ivSpec = new IvParameterSpec(IV); 357 c.init(Cipher.DECRYPT_MODE, 358 new SecretKeySpec(mk, "AES"), 359 ivSpec); 360 // Only if all of the above worked properly will 'result' be assigned 361 result = new CipherInputStream(rawInStream, c); 362 } else if (doLog) { 363 Slog.w(TAG, "Incorrect password"); 364 } 365 } catch (InvalidAlgorithmParameterException e) { 366 if (doLog) { 367 Slog.e(TAG, "Needed parameter spec unavailable!", e); 368 } 369 } catch (BadPaddingException e) { 370 // This case frequently occurs when the wrong password is used to decrypt 371 // the master key. Use the identical "incorrect password" log text as is 372 // used in the checksum failure log in order to avoid providing additional 373 // information to an attacker. 374 if (doLog) { 375 Slog.w(TAG, "Incorrect password"); 376 } 377 } catch (IllegalBlockSizeException e) { 378 if (doLog) { 379 Slog.w(TAG, "Invalid block size in master key"); 380 } 381 } catch (NoSuchAlgorithmException e) { 382 if (doLog) { 383 Slog.e(TAG, "Needed decryption algorithm unavailable!"); 384 } 385 } catch (NoSuchPaddingException e) { 386 if (doLog) { 387 Slog.e(TAG, "Needed padding mechanism unavailable!"); 388 } 389 } catch (InvalidKeyException e) { 390 if (doLog) { 391 Slog.w(TAG, "Illegal password; aborting"); 392 } 393 } 394 395 return result; 396 } 397 398 private static InputStream decodeAesHeaderAndInitialize(String decryptPassword, 399 String encryptionName, 400 boolean pbkdf2Fallback, 401 InputStream rawInStream) { 402 InputStream result = null; 403 try { 404 if (encryptionName.equals(PasswordUtils.ENCRYPTION_ALGORITHM_NAME)) { 405 406 String userSaltHex = readHeaderLine(rawInStream); // 5 407 byte[] userSalt = PasswordUtils.hexToByteArray(userSaltHex); 408 409 String ckSaltHex = readHeaderLine(rawInStream); // 6 410 byte[] ckSalt = PasswordUtils.hexToByteArray(ckSaltHex); 411 412 int rounds = Integer.parseInt(readHeaderLine(rawInStream)); // 7 413 String userIvHex = readHeaderLine(rawInStream); // 8 414 415 String masterKeyBlobHex = readHeaderLine(rawInStream); // 9 416 417 // decrypt the master key blob 418 result = attemptMasterKeyDecryption(decryptPassword, PBKDF_CURRENT, 419 userSalt, ckSalt, rounds, userIvHex, masterKeyBlobHex, rawInStream, false); 420 if (result == null && pbkdf2Fallback) { 421 result = attemptMasterKeyDecryption( 422 decryptPassword, PBKDF_FALLBACK, userSalt, ckSalt, 423 rounds, userIvHex, masterKeyBlobHex, rawInStream, true); 424 } 425 } else { 426 Slog.w(TAG, "Unsupported encryption method: " + encryptionName); 427 } 428 } catch (NumberFormatException e) { 429 Slog.w(TAG, "Can't parse restore data header"); 430 } catch (IOException e) { 431 Slog.w(TAG, "Can't read input header"); 432 } 433 434 return result; 435 } 436 437 boolean restoreOneFile(InputStream instream, boolean mustKillAgent, byte[] buffer, 438 PackageInfo onlyPackage, boolean allowApks, int token, IBackupManagerMonitor monitor) { 439 BytesReadListener bytesReadListener = new BytesReadListener() { 440 @Override 441 public void onBytesRead(long bytesRead) { 442 mBytes += bytesRead; 443 } 444 }; 445 TarBackupReader tarBackupReader = new TarBackupReader(instream, 446 bytesReadListener, monitor); 447 FileMetadata info; 448 try { 449 info = tarBackupReader.readTarHeaders(); 450 if (info != null) { 451 if (MORE_DEBUG) { 452 info.dump(); 453 } 454 455 final String pkg = info.packageName; 456 if (!pkg.equals(mAgentPackage)) { 457 // okay, change in package; set up our various 458 // bookkeeping if we haven't seen it yet 459 if (!mPackagePolicies.containsKey(pkg)) { 460 mPackagePolicies.put(pkg, RestorePolicy.IGNORE); 461 } 462 463 // Clean up the previous agent relationship if necessary, 464 // and let the observer know we're considering a new app. 465 if (mAgent != null) { 466 if (DEBUG) { 467 Slog.d(TAG, "Saw new package; finalizing old one"); 468 } 469 // Now we're really done 470 tearDownPipes(); 471 tearDownAgent(mTargetApp, true); 472 mTargetApp = null; 473 mAgentPackage = null; 474 } 475 } 476 477 if (info.path.equals(BACKUP_MANIFEST_FILENAME)) { 478 Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures( 479 info); 480 // readAppManifestAndReturnSignatures() will have extracted the version from 481 // the manifest, so we save it to use in key-value restore later. 482 mAppVersion = info.version; 483 PackageManagerInternal pmi = LocalServices.getService( 484 PackageManagerInternal.class); 485 RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy( 486 mBackupManagerService.getPackageManager(), allowApks, 487 info, signatures, pmi); 488 mManifestSignatures.put(info.packageName, signatures); 489 mPackagePolicies.put(pkg, restorePolicy); 490 mPackageInstallers.put(pkg, info.installerPackageName); 491 // We've read only the manifest content itself at this point, 492 // so consume the footer before looping around to the next 493 // input file 494 tarBackupReader.skipTarPadding(info.size); 495 mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg); 496 } else if (info.path.equals(BACKUP_METADATA_FILENAME)) { 497 // Metadata blobs! 498 tarBackupReader.readMetadata(info); 499 500 // The following only exist because we want to keep refactoring as safe as 501 // possible, without changing too much. 502 // TODO: Refactor, so that there are no funny things like this. 503 // This is read during TarBackupReader.readMetadata(). 504 mWidgetData = tarBackupReader.getWidgetData(); 505 // This can be nulled during TarBackupReader.readMetadata(). 506 monitor = tarBackupReader.getMonitor(); 507 508 tarBackupReader.skipTarPadding(info.size); 509 } else { 510 // Non-manifest, so it's actual file data. Is this a package 511 // we're ignoring? 512 boolean okay = true; 513 RestorePolicy policy = mPackagePolicies.get(pkg); 514 switch (policy) { 515 case IGNORE: 516 okay = false; 517 break; 518 519 case ACCEPT_IF_APK: 520 // If we're in accept-if-apk state, then the first file we 521 // see MUST be the apk. 522 if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) { 523 if (DEBUG) { 524 Slog.d(TAG, "APK file; installing"); 525 } 526 // Try to install the app. 527 String installerPackageName = mPackageInstallers.get(pkg); 528 boolean isSuccessfullyInstalled = RestoreUtils.installApk(instream, 529 mBackupManagerService.getContext(), 530 mDeleteObserver, mManifestSignatures, mPackagePolicies, 531 info, installerPackageName, bytesReadListener); 532 // good to go; promote to ACCEPT 533 mPackagePolicies.put(pkg, isSuccessfullyInstalled 534 ? RestorePolicy.ACCEPT 535 : RestorePolicy.IGNORE); 536 // At this point we've consumed this file entry 537 // ourselves, so just strip the tar footer and 538 // go on to the next file in the input stream 539 tarBackupReader.skipTarPadding(info.size); 540 return true; 541 } else { 542 // File data before (or without) the apk. We can't 543 // handle it coherently in this case so ignore it. 544 mPackagePolicies.put(pkg, RestorePolicy.IGNORE); 545 okay = false; 546 } 547 break; 548 549 case ACCEPT: 550 if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) { 551 if (DEBUG) { 552 Slog.d(TAG, "apk present but ACCEPT"); 553 } 554 // we can take the data without the apk, so we 555 // *want* to do so. skip the apk by declaring this 556 // one file not-okay without changing the restore 557 // policy for the package. 558 okay = false; 559 } 560 break; 561 562 default: 563 // Something has gone dreadfully wrong when determining 564 // the restore policy from the manifest. Ignore the 565 // rest of this package's data. 566 Slog.e(TAG, "Invalid policy from manifest"); 567 okay = false; 568 mPackagePolicies.put(pkg, RestorePolicy.IGNORE); 569 break; 570 } 571 572 // The path needs to be canonical 573 if (!isCanonicalFilePath(info.path)) { 574 okay = false; 575 } 576 577 // If the policy is satisfied, go ahead and set up to pipe the 578 // data to the agent. 579 if (DEBUG && okay && mAgent != null) { 580 Slog.i(TAG, "Reusing existing agent instance"); 581 } 582 if (okay && mAgent == null) { 583 if (DEBUG) { 584 Slog.d(TAG, "Need to launch agent for " + pkg); 585 } 586 587 try { 588 mTargetApp = 589 mBackupManagerService.getPackageManager().getApplicationInfo( 590 pkg, 0); 591 592 // If we haven't sent any data to this app yet, we probably 593 // need to clear it first. Check that. 594 if (!mClearedPackages.contains(pkg)) { 595 // apps with their own backup agents are 596 // responsible for coherently managing a full 597 // restore. 598 if (mTargetApp.backupAgentName == null) { 599 if (DEBUG) { 600 Slog.d(TAG, 601 "Clearing app data preparatory to full restore"); 602 } 603 mBackupManagerService.clearApplicationDataSynchronous(pkg, true); 604 } else { 605 if (DEBUG) { 606 Slog.d(TAG, "backup agent (" 607 + mTargetApp.backupAgentName + ") => no clear"); 608 } 609 } 610 mClearedPackages.add(pkg); 611 } else { 612 if (DEBUG) { 613 Slog.d(TAG, "We've initialized this app already; no clear " 614 + "required"); 615 } 616 } 617 618 // All set; now set up the IPC and launch the agent 619 setUpPipes(); 620 mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp, 621 FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain) 622 ? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL 623 : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL); 624 mAgentPackage = pkg; 625 } catch (IOException e) { 626 // fall through to error handling 627 } catch (NameNotFoundException e) { 628 // fall through to error handling 629 } 630 631 if (mAgent == null) { 632 Slog.e(TAG, "Unable to create agent for " + pkg); 633 okay = false; 634 tearDownPipes(); 635 mPackagePolicies.put(pkg, RestorePolicy.IGNORE); 636 } 637 } 638 639 // Sanity check: make sure we never give data to the wrong app. This 640 // should never happen but a little paranoia here won't go amiss. 641 if (okay && !pkg.equals(mAgentPackage)) { 642 Slog.e(TAG, "Restoring data for " + pkg 643 + " but agent is for " + mAgentPackage); 644 okay = false; 645 } 646 647 // At this point we have an agent ready to handle the full 648 // restore data as well as a pipe for sending data to 649 // that agent. Tell the agent to start reading from the 650 // pipe. 651 if (okay) { 652 boolean agentSuccess = true; 653 long toCopy = info.size; 654 long restoreAgentTimeoutMillis = 655 mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(); 656 try { 657 mBackupManagerService.prepareOperationTimeout( 658 token, restoreAgentTimeoutMillis, null, OP_TYPE_RESTORE_WAIT); 659 660 if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) { 661 if (DEBUG) { 662 Slog.d(TAG, "Restoring OBB file for " + pkg 663 + " : " + info.path); 664 } 665 mObbConnection.restoreObbFile(pkg, mPipes[0], 666 info.size, info.type, info.path, info.mode, 667 info.mtime, token, 668 mBackupManagerService.getBackupManagerBinder()); 669 } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) { 670 if (DEBUG) { 671 Slog.d(TAG, "Restoring key-value file for " + pkg 672 + " : " + info.path); 673 } 674 // Set the version saved from manifest entry. 675 info.version = mAppVersion; 676 KeyValueAdbRestoreEngine restoreEngine = 677 new KeyValueAdbRestoreEngine( 678 mBackupManagerService, 679 mBackupManagerService.getDataDir(), info, mPipes[0], 680 mAgent, token); 681 new Thread(restoreEngine, "restore-key-value-runner").start(); 682 } else { 683 if (DEBUG) { 684 Slog.d(TAG, "Invoking agent to restore file " + info.path); 685 } 686 // fire up the app's agent listening on the socket. If 687 // the agent is running in the system process we can't 688 // just invoke it asynchronously, so we provide a thread 689 // for it here. 690 if (mTargetApp.processName.equals("system")) { 691 Slog.d(TAG, "system process agent - spinning a thread"); 692 RestoreFileRunnable runner = new RestoreFileRunnable( 693 mBackupManagerService, mAgent, info, mPipes[0], token); 694 new Thread(runner, "restore-sys-runner").start(); 695 } else { 696 mAgent.doRestoreFile(mPipes[0], info.size, info.type, 697 info.domain, info.path, info.mode, info.mtime, 698 token, mBackupManagerService.getBackupManagerBinder()); 699 } 700 } 701 } catch (IOException e) { 702 // couldn't dup the socket for a process-local restore 703 Slog.d(TAG, "Couldn't establish restore"); 704 agentSuccess = false; 705 okay = false; 706 } catch (RemoteException e) { 707 // whoops, remote entity went away. We'll eat the content 708 // ourselves, then, and not copy it over. 709 Slog.e(TAG, "Agent crashed during full restore"); 710 agentSuccess = false; 711 okay = false; 712 } 713 714 // Copy over the data if the agent is still good 715 if (okay) { 716 boolean pipeOkay = true; 717 FileOutputStream pipe = new FileOutputStream( 718 mPipes[1].getFileDescriptor()); 719 while (toCopy > 0) { 720 int toRead = (toCopy > buffer.length) 721 ? buffer.length : (int) toCopy; 722 int nRead = instream.read(buffer, 0, toRead); 723 if (nRead >= 0) { 724 mBytes += nRead; 725 } 726 if (nRead <= 0) { 727 break; 728 } 729 toCopy -= nRead; 730 731 // send it to the output pipe as long as things 732 // are still good 733 if (pipeOkay) { 734 try { 735 pipe.write(buffer, 0, nRead); 736 } catch (IOException e) { 737 Slog.e(TAG, "Failed to write to restore pipe", e); 738 pipeOkay = false; 739 } 740 } 741 } 742 743 // done sending that file! Now we just need to consume 744 // the delta from info.size to the end of block. 745 tarBackupReader.skipTarPadding(info.size); 746 747 // and now that we've sent it all, wait for the remote 748 // side to acknowledge receipt 749 agentSuccess = mBackupManagerService.waitUntilOperationComplete(token); 750 } 751 752 // okay, if the remote end failed at any point, deal with 753 // it by ignoring the rest of the restore on it 754 if (!agentSuccess) { 755 if (DEBUG) { 756 Slog.d(TAG, "Agent failure restoring " + pkg + "; now ignoring"); 757 } 758 mBackupManagerService.getBackupHandler().removeMessages( 759 MSG_RESTORE_OPERATION_TIMEOUT); 760 tearDownPipes(); 761 tearDownAgent(mTargetApp, false); 762 mPackagePolicies.put(pkg, RestorePolicy.IGNORE); 763 } 764 } 765 766 // Problems setting up the agent communication, or an already- 767 // ignored package: skip to the next tar stream entry by 768 // reading and discarding this file. 769 if (!okay) { 770 if (DEBUG) { 771 Slog.d(TAG, "[discarding file content]"); 772 } 773 long bytesToConsume = (info.size + 511) & ~511; 774 while (bytesToConsume > 0) { 775 int toRead = (bytesToConsume > buffer.length) 776 ? buffer.length : (int) bytesToConsume; 777 long nRead = instream.read(buffer, 0, toRead); 778 if (nRead >= 0) { 779 mBytes += nRead; 780 } 781 if (nRead <= 0) { 782 break; 783 } 784 bytesToConsume -= nRead; 785 } 786 } 787 } 788 } 789 } catch (IOException e) { 790 if (DEBUG) { 791 Slog.w(TAG, "io exception on restore socket read", e); 792 } 793 // treat as EOF 794 info = null; 795 } 796 797 return (info != null); 798 } 799 800 private static boolean isCanonicalFilePath(String path) { 801 if (path.contains("..") || path.contains("//")) { 802 if (MORE_DEBUG) { 803 Slog.w(TAG, "Dropping invalid path " + path); 804 } 805 return false; 806 } 807 808 return true; 809 } 810 811 private void setUpPipes() throws IOException { 812 mPipes = ParcelFileDescriptor.createPipe(); 813 } 814 815 private void tearDownPipes() { 816 if (mPipes != null) { 817 try { 818 mPipes[0].close(); 819 mPipes[0] = null; 820 mPipes[1].close(); 821 mPipes[1] = null; 822 } catch (IOException e) { 823 Slog.w(TAG, "Couldn't close agent pipes", e); 824 } 825 mPipes = null; 826 } 827 } 828 829 private void tearDownAgent(ApplicationInfo app, boolean doRestoreFinished) { 830 if (mAgent != null) { 831 try { 832 // In the adb restore case, we do restore-finished here 833 if (doRestoreFinished) { 834 final int token = mBackupManagerService.generateRandomIntegerToken(); 835 long fullBackupAgentTimeoutMillis = 836 mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); 837 final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch( 838 mBackupManagerService, token); 839 mBackupManagerService.prepareOperationTimeout( 840 token, fullBackupAgentTimeoutMillis, latch, OP_TYPE_RESTORE_WAIT); 841 if (mTargetApp.processName.equals("system")) { 842 if (MORE_DEBUG) { 843 Slog.d(TAG, "system agent - restoreFinished on thread"); 844 } 845 Runnable runner = new RestoreFinishedRunnable(mAgent, token, 846 mBackupManagerService); 847 new Thread(runner, "restore-sys-finished-runner").start(); 848 } else { 849 mAgent.doRestoreFinished(token, 850 mBackupManagerService.getBackupManagerBinder()); 851 } 852 853 latch.await(); 854 } 855 856 mBackupManagerService.tearDownAgentAndKill(app); 857 } catch (RemoteException e) { 858 Slog.d(TAG, "Lost app trying to shut down"); 859 } 860 mAgent = null; 861 } 862 } 863 864 } 865