PackageInstallerSession.java revision bc09755e193c2802d2d88871ac3d1f182b260c30
1/* 2 * Copyright (C) 2014 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 17package com.android.server.pm; 18 19import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; 20import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; 21import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 22import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 23import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; 24import static android.system.OsConstants.O_CREAT; 25import static android.system.OsConstants.O_RDONLY; 26import static android.system.OsConstants.O_WRONLY; 27import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid; 28import static com.android.server.pm.PackageInstallerService.prepareInternalStageDir; 29 30import android.content.Context; 31import android.content.Intent; 32import android.content.IntentSender; 33import android.content.pm.ApplicationInfo; 34import android.content.pm.IPackageInstallObserver2; 35import android.content.pm.IPackageInstallerSession; 36import android.content.pm.PackageInstaller; 37import android.content.pm.PackageInstaller.SessionInfo; 38import android.content.pm.PackageInstaller.SessionParams; 39import android.content.pm.PackageManager; 40import android.content.pm.PackageParser; 41import android.content.pm.PackageParser.ApkLite; 42import android.content.pm.PackageParser.PackageLite; 43import android.content.pm.PackageParser.PackageParserException; 44import android.content.pm.Signature; 45import android.os.Bundle; 46import android.os.FileBridge; 47import android.os.FileUtils; 48import android.os.Handler; 49import android.os.Looper; 50import android.os.Message; 51import android.os.ParcelFileDescriptor; 52import android.os.Process; 53import android.os.RemoteException; 54import android.os.UserHandle; 55import android.system.ErrnoException; 56import android.system.Os; 57import android.system.OsConstants; 58import android.system.StructStat; 59import android.util.ArraySet; 60import android.util.ExceptionUtils; 61import android.util.MathUtils; 62import android.util.Slog; 63 64import com.android.internal.annotations.GuardedBy; 65import com.android.internal.content.NativeLibraryHelper; 66import com.android.internal.content.PackageHelper; 67import com.android.internal.util.ArrayUtils; 68import com.android.internal.util.IndentingPrintWriter; 69import com.android.internal.util.Preconditions; 70import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter; 71 72import libcore.io.IoUtils; 73import libcore.io.Libcore; 74 75import java.io.File; 76import java.io.FileDescriptor; 77import java.io.IOException; 78import java.util.ArrayList; 79import java.util.List; 80import java.util.concurrent.atomic.AtomicInteger; 81 82public class PackageInstallerSession extends IPackageInstallerSession.Stub { 83 private static final String TAG = "PackageInstaller"; 84 private static final boolean LOGD = true; 85 86 private static final int MSG_COMMIT = 0; 87 88 // TODO: enforce INSTALL_ALLOW_TEST 89 // TODO: enforce INSTALL_ALLOW_DOWNGRADE 90 91 private final PackageInstallerService.InternalCallback mCallback; 92 private final Context mContext; 93 private final PackageManagerService mPm; 94 private final Handler mHandler; 95 96 final int sessionId; 97 final int userId; 98 final String installerPackageName; 99 final SessionParams params; 100 final long createdMillis; 101 102 /** Staging location where client data is written. */ 103 final File stageDir; 104 final String stageCid; 105 106 /** Note that UID is not persisted; it's always derived at runtime. */ 107 final int installerUid; 108 109 private final AtomicInteger mActiveCount = new AtomicInteger(); 110 111 private final Object mLock = new Object(); 112 113 @GuardedBy("mLock") 114 private float mClientProgress = 0; 115 @GuardedBy("mLock") 116 private float mInternalProgress = 0; 117 118 @GuardedBy("mLock") 119 private float mProgress = 0; 120 @GuardedBy("mLock") 121 private float mReportedProgress = -1; 122 123 @GuardedBy("mLock") 124 private boolean mPrepared = false; 125 @GuardedBy("mLock") 126 private boolean mSealed = false; 127 @GuardedBy("mLock") 128 private boolean mPermissionsAccepted = false; 129 @GuardedBy("mLock") 130 private boolean mDestroyed = false; 131 132 private int mFinalStatus; 133 private String mFinalMessage; 134 135 @GuardedBy("mLock") 136 private ArrayList<FileBridge> mBridges = new ArrayList<>(); 137 138 @GuardedBy("mLock") 139 private IPackageInstallObserver2 mRemoteObserver; 140 141 /** Fields derived from commit parsing */ 142 private String mPackageName; 143 private int mVersionCode; 144 private Signature[] mSignatures; 145 146 /** 147 * Path to the validated base APK for this session, which may point at an 148 * APK inside the session (when the session defines the base), or it may 149 * point at the existing base APK (when adding splits to an existing app). 150 * <p> 151 * This is used when confirming permissions, since we can't fully stage the 152 * session inside an ASEC before confirming with user. 153 */ 154 @GuardedBy("mLock") 155 private File mResolvedBaseFile; 156 157 @GuardedBy("mLock") 158 private File mResolvedStageDir; 159 160 @GuardedBy("mLock") 161 private final List<File> mResolvedStagedFiles = new ArrayList<>(); 162 @GuardedBy("mLock") 163 private final List<File> mResolvedInheritedFiles = new ArrayList<>(); 164 165 private final Handler.Callback mHandlerCallback = new Handler.Callback() { 166 @Override 167 public boolean handleMessage(Message msg) { 168 synchronized (mLock) { 169 if (msg.obj != null) { 170 mRemoteObserver = (IPackageInstallObserver2) msg.obj; 171 } 172 173 try { 174 commitLocked(); 175 } catch (PackageManagerException e) { 176 final String completeMsg = ExceptionUtils.getCompleteMessage(e); 177 Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); 178 destroyInternal(); 179 dispatchSessionFinished(e.error, completeMsg, null); 180 } 181 182 return true; 183 } 184 } 185 }; 186 187 public PackageInstallerSession(PackageInstallerService.InternalCallback callback, 188 Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, 189 String installerPackageName, SessionParams params, long createdMillis, 190 File stageDir, String stageCid, boolean prepared, boolean sealed) { 191 mCallback = callback; 192 mContext = context; 193 mPm = pm; 194 mHandler = new Handler(looper, mHandlerCallback); 195 196 this.sessionId = sessionId; 197 this.userId = userId; 198 this.installerPackageName = installerPackageName; 199 this.params = params; 200 this.createdMillis = createdMillis; 201 this.stageDir = stageDir; 202 this.stageCid = stageCid; 203 204 if ((stageDir == null) == (stageCid == null)) { 205 throw new IllegalArgumentException( 206 "Exactly one of stageDir or stageCid stage must be set"); 207 } 208 209 mPrepared = prepared; 210 mSealed = sealed; 211 212 // Always derived at runtime 213 installerUid = mPm.getPackageUid(installerPackageName, userId); 214 215 if (mPm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, 216 installerPackageName) == PackageManager.PERMISSION_GRANTED) { 217 mPermissionsAccepted = true; 218 } else { 219 mPermissionsAccepted = false; 220 } 221 } 222 223 public SessionInfo generateInfo() { 224 final SessionInfo info = new SessionInfo(); 225 synchronized (mLock) { 226 info.sessionId = sessionId; 227 info.installerPackageName = installerPackageName; 228 info.resolvedBaseCodePath = (mResolvedBaseFile != null) ? 229 mResolvedBaseFile.getAbsolutePath() : null; 230 info.progress = mProgress; 231 info.sealed = mSealed; 232 info.active = mActiveCount.get() > 0; 233 234 info.mode = params.mode; 235 info.sizeBytes = params.sizeBytes; 236 info.appPackageName = params.appPackageName; 237 info.appIcon = params.appIcon; 238 info.appLabel = params.appLabel; 239 } 240 return info; 241 } 242 243 public boolean isPrepared() { 244 synchronized (mLock) { 245 return mPrepared; 246 } 247 } 248 249 public boolean isSealed() { 250 synchronized (mLock) { 251 return mSealed; 252 } 253 } 254 255 private void assertPreparedAndNotSealed(String cookie) { 256 synchronized (mLock) { 257 if (!mPrepared) { 258 throw new IllegalStateException(cookie + " before prepared"); 259 } 260 if (mSealed) { 261 throw new SecurityException(cookie + " not allowed after commit"); 262 } 263 } 264 } 265 266 /** 267 * Resolve the actual location where staged data should be written. This 268 * might point at an ASEC mount point, which is why we delay path resolution 269 * until someone actively works with the session. 270 */ 271 private File resolveStageDir() throws IOException { 272 synchronized (mLock) { 273 if (mResolvedStageDir == null) { 274 if (stageDir != null) { 275 mResolvedStageDir = stageDir; 276 } else { 277 final String path = PackageHelper.getSdDir(stageCid); 278 if (path != null) { 279 mResolvedStageDir = new File(path); 280 } else { 281 throw new IOException("Failed to resolve path to container " + stageCid); 282 } 283 } 284 } 285 return mResolvedStageDir; 286 } 287 } 288 289 @Override 290 public void setClientProgress(float progress) { 291 synchronized (mLock) { 292 // Always publish first staging movement 293 final boolean forcePublish = (mClientProgress == 0); 294 mClientProgress = progress; 295 computeProgressLocked(forcePublish); 296 } 297 } 298 299 @Override 300 public void addClientProgress(float progress) { 301 synchronized (mLock) { 302 setClientProgress(mClientProgress + progress); 303 } 304 } 305 306 private void computeProgressLocked(boolean forcePublish) { 307 mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) 308 + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); 309 310 // Only publish when meaningful change 311 if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) { 312 mReportedProgress = mProgress; 313 mCallback.onSessionProgressChanged(this, mProgress); 314 } 315 } 316 317 @Override 318 public String[] getNames() { 319 assertPreparedAndNotSealed("getNames"); 320 try { 321 return resolveStageDir().list(); 322 } catch (IOException e) { 323 throw ExceptionUtils.wrap(e); 324 } 325 } 326 327 @Override 328 public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) { 329 try { 330 return openWriteInternal(name, offsetBytes, lengthBytes); 331 } catch (IOException e) { 332 throw ExceptionUtils.wrap(e); 333 } 334 } 335 336 private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes) 337 throws IOException { 338 // Quick sanity check of state, and allocate a pipe for ourselves. We 339 // then do heavy disk allocation outside the lock, but this open pipe 340 // will block any attempted install transitions. 341 final FileBridge bridge; 342 synchronized (mLock) { 343 assertPreparedAndNotSealed("openWrite"); 344 345 bridge = new FileBridge(); 346 mBridges.add(bridge); 347 } 348 349 try { 350 // Use installer provided name for now; we always rename later 351 if (!FileUtils.isValidExtFilename(name)) { 352 throw new IllegalArgumentException("Invalid name: " + name); 353 } 354 final File target = new File(resolveStageDir(), name); 355 356 // TODO: this should delegate to DCS so the system process avoids 357 // holding open FDs into containers. 358 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), 359 O_CREAT | O_WRONLY, 0644); 360 Os.chmod(target.getAbsolutePath(), 0644); 361 362 // If caller specified a total length, allocate it for them. Free up 363 // cache space to grow, if needed. 364 if (lengthBytes > 0) { 365 final StructStat stat = Libcore.os.fstat(targetFd); 366 final long deltaBytes = lengthBytes - stat.st_size; 367 // Only need to free up space when writing to internal stage 368 if (stageDir != null && deltaBytes > 0) { 369 mPm.freeStorage(deltaBytes); 370 } 371 Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); 372 } 373 374 if (offsetBytes > 0) { 375 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); 376 } 377 378 bridge.setTargetFile(targetFd); 379 bridge.start(); 380 return new ParcelFileDescriptor(bridge.getClientSocket()); 381 382 } catch (ErrnoException e) { 383 throw e.rethrowAsIOException(); 384 } 385 } 386 387 @Override 388 public ParcelFileDescriptor openRead(String name) { 389 try { 390 return openReadInternal(name); 391 } catch (IOException e) { 392 throw ExceptionUtils.wrap(e); 393 } 394 } 395 396 private ParcelFileDescriptor openReadInternal(String name) throws IOException { 397 assertPreparedAndNotSealed("openRead"); 398 399 try { 400 if (!FileUtils.isValidExtFilename(name)) { 401 throw new IllegalArgumentException("Invalid name: " + name); 402 } 403 final File target = new File(resolveStageDir(), name); 404 405 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0); 406 return new ParcelFileDescriptor(targetFd); 407 408 } catch (ErrnoException e) { 409 throw e.rethrowAsIOException(); 410 } 411 } 412 413 @Override 414 public void commit(IntentSender statusReceiver) { 415 Preconditions.checkNotNull(statusReceiver); 416 417 synchronized (mLock) { 418 if (!mSealed) { 419 // Verify that all writers are hands-off 420 for (FileBridge bridge : mBridges) { 421 if (!bridge.isClosed()) { 422 throw new SecurityException("Files still open"); 423 } 424 } 425 426 // Persist the fact that we've sealed ourselves to prevent 427 // mutations of any hard links we create. 428 mSealed = true; 429 mCallback.onSessionSealed(this); 430 } 431 } 432 433 // Client staging is fully done at this point 434 mClientProgress = 1f; 435 computeProgressLocked(true); 436 437 // This ongoing commit should keep session active, even though client 438 // will probably close their end. 439 mActiveCount.incrementAndGet(); 440 441 final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext, 442 statusReceiver, sessionId); 443 mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); 444 } 445 446 private void commitLocked() throws PackageManagerException { 447 if (mDestroyed) { 448 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); 449 } 450 if (!mSealed) { 451 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); 452 } 453 454 try { 455 resolveStageDir(); 456 } catch (IOException e) { 457 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 458 "Failed to resolve stage location", e); 459 } 460 461 // Verify that stage looks sane with respect to existing application. 462 // This currently only ensures packageName, versionCode, and certificate 463 // consistency. 464 validateInstallLocked(); 465 466 Preconditions.checkNotNull(mPackageName); 467 Preconditions.checkNotNull(mSignatures); 468 Preconditions.checkNotNull(mResolvedBaseFile); 469 470 if (!mPermissionsAccepted) { 471 // User needs to accept permissions; give installer an intent they 472 // can use to involve user. 473 final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS); 474 intent.setPackage("com.android.packageinstaller"); 475 intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); 476 try { 477 mRemoteObserver.onUserActionRequired(intent); 478 } catch (RemoteException ignored) { 479 } 480 481 // Commit was keeping session marked as active until now; release 482 // that extra refcount so session appears idle. 483 close(); 484 return; 485 } 486 487 if (stageCid != null) { 488 // Figure out the final installed size and resize the container once 489 // and for all. Internally the parser handles straddling between two 490 // locations when inheriting. 491 final long finalSize = calculateInstalledSize(); 492 resizeContainer(stageCid, finalSize); 493 } 494 495 // Inherit any packages and native libraries from existing install that 496 // haven't been overridden. 497 if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { 498 try { 499 if (stageCid != null) { 500 // TODO: this should delegate to DCS so the system process 501 // avoids holding open FDs into containers. 502 copyFiles(mResolvedInheritedFiles, resolveStageDir()); 503 } else { 504 linkFiles(mResolvedInheritedFiles, resolveStageDir()); 505 } 506 } catch (IOException e) { 507 throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, 508 "Failed to inherit existing install", e); 509 } 510 } 511 512 // TODO: surface more granular state from dexopt 513 mInternalProgress = 0.5f; 514 computeProgressLocked(true); 515 516 // Unpack native libraries 517 extractNativeLibraries(mResolvedStageDir, params.abiOverride); 518 519 // Container is ready to go, let's seal it up! 520 if (stageCid != null) { 521 finalizeAndFixContainer(stageCid); 522 } 523 524 // We've reached point of no return; call into PMS to install the stage. 525 // Regardless of success or failure we always destroy session. 526 final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { 527 @Override 528 public void onUserActionRequired(Intent intent) { 529 throw new IllegalStateException(); 530 } 531 532 @Override 533 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 534 Bundle extras) { 535 destroyInternal(); 536 dispatchSessionFinished(returnCode, msg, extras); 537 } 538 }; 539 540 mPm.installStage(mPackageName, stageDir, stageCid, localObserver, 541 params, installerPackageName, installerUid, new UserHandle(userId)); 542 } 543 544 /** 545 * Validate install by confirming that all application packages are have 546 * consistent package name, version code, and signing certificates. 547 * <p> 548 * Clears and populates {@link #mResolvedBaseFile}, 549 * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}. 550 * <p> 551 * Renames package files in stage to match split names defined inside. 552 * <p> 553 * Note that upgrade compatibility is still performed by 554 * {@link PackageManagerService}. 555 */ 556 private void validateInstallLocked() throws PackageManagerException { 557 mPackageName = null; 558 mVersionCode = -1; 559 mSignatures = null; 560 561 mResolvedBaseFile = null; 562 mResolvedStagedFiles.clear(); 563 mResolvedInheritedFiles.clear(); 564 565 final File[] files = mResolvedStageDir.listFiles(); 566 if (ArrayUtils.isEmpty(files)) { 567 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); 568 } 569 570 // Verify that all staged packages are internally consistent 571 final ArraySet<String> stagedSplits = new ArraySet<>(); 572 for (File file : files) { 573 574 // Installers can't stage directories, so it's fine to ignore 575 // entries like "lost+found". 576 if (file.isDirectory()) continue; 577 578 final ApkLite apk; 579 try { 580 apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES); 581 } catch (PackageParserException e) { 582 throw PackageManagerException.from(e); 583 } 584 585 if (!stagedSplits.add(apk.splitName)) { 586 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 587 "Split " + apk.splitName + " was defined multiple times"); 588 } 589 590 // Use first package to define unknown values 591 if (mPackageName == null) { 592 mPackageName = apk.packageName; 593 mVersionCode = apk.versionCode; 594 } 595 if (mSignatures == null) { 596 mSignatures = apk.signatures; 597 } 598 599 assertApkConsistent(String.valueOf(file), apk); 600 601 // Take this opportunity to enforce uniform naming 602 final String targetName; 603 if (apk.splitName == null) { 604 targetName = "base.apk"; 605 } else { 606 targetName = "split_" + apk.splitName + ".apk"; 607 } 608 if (!FileUtils.isValidExtFilename(targetName)) { 609 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 610 "Invalid filename: " + targetName); 611 } 612 613 final File targetFile = new File(mResolvedStageDir, targetName); 614 if (!file.equals(targetFile)) { 615 file.renameTo(targetFile); 616 } 617 618 // Base is coming from session 619 if (apk.splitName == null) { 620 mResolvedBaseFile = targetFile; 621 } 622 623 mResolvedStagedFiles.add(targetFile); 624 } 625 626 if (params.mode == SessionParams.MODE_FULL_INSTALL) { 627 // Full installs must include a base package 628 if (!stagedSplits.contains(null)) { 629 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 630 "Full install must include a base package"); 631 } 632 633 } else { 634 // Partial installs must be consistent with existing install 635 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); 636 if (app == null) { 637 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 638 "Missing existing base package for " + mPackageName); 639 } 640 641 final PackageLite existing; 642 final ApkLite existingBase; 643 try { 644 existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0); 645 existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()), 646 PackageParser.PARSE_COLLECT_CERTIFICATES); 647 } catch (PackageParserException e) { 648 throw PackageManagerException.from(e); 649 } 650 651 assertApkConsistent("Existing base", existingBase); 652 653 // Inherit base if not overridden 654 if (mResolvedBaseFile == null) { 655 mResolvedBaseFile = new File(app.getBaseCodePath()); 656 mResolvedInheritedFiles.add(mResolvedBaseFile); 657 } 658 659 // Inherit splits if not overridden 660 if (!ArrayUtils.isEmpty(existing.splitNames)) { 661 for (int i = 0; i < existing.splitNames.length; i++) { 662 final String splitName = existing.splitNames[i]; 663 final File splitFile = new File(existing.splitCodePaths[i]); 664 665 if (!stagedSplits.contains(splitName)) { 666 mResolvedInheritedFiles.add(splitFile); 667 } 668 } 669 } 670 } 671 } 672 673 private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException { 674 if (!mPackageName.equals(apk.packageName)) { 675 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " 676 + apk.packageName + " inconsistent with " + mPackageName); 677 } 678 if (mVersionCode != apk.versionCode) { 679 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag 680 + " version code " + apk.versionCode + " inconsistent with " 681 + mVersionCode); 682 } 683 if (!Signature.areExactMatch(mSignatures, apk.signatures)) { 684 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 685 tag + " signatures are inconsistent"); 686 } 687 } 688 689 /** 690 * Calculate the final install footprint size, combining both staged and 691 * existing APKs together and including unpacked native code from both. 692 */ 693 private long calculateInstalledSize() throws PackageManagerException { 694 Preconditions.checkNotNull(mResolvedBaseFile); 695 696 final ApkLite baseApk; 697 try { 698 baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0); 699 } catch (PackageParserException e) { 700 throw PackageManagerException.from(e); 701 } 702 703 final List<String> splitPaths = new ArrayList<>(); 704 for (File file : mResolvedStagedFiles) { 705 if (mResolvedBaseFile.equals(file)) continue; 706 splitPaths.add(file.getAbsolutePath()); 707 } 708 for (File file : mResolvedInheritedFiles) { 709 if (mResolvedBaseFile.equals(file)) continue; 710 splitPaths.add(file.getAbsolutePath()); 711 } 712 713 // This is kind of hacky; we're creating a half-parsed package that is 714 // straddled between the inherited and staged APKs. 715 final PackageLite pkg = new PackageLite(null, baseApk, null, 716 splitPaths.toArray(new String[splitPaths.size()])); 717 final boolean isForwardLocked = 718 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0; 719 720 try { 721 return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride); 722 } catch (IOException e) { 723 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 724 "Failed to calculate install size", e); 725 } 726 } 727 728 private static void linkFiles(List<File> fromFiles, File toDir) throws IOException { 729 for (File fromFile : fromFiles) { 730 final File toFile = new File(toDir, fromFile.getName()); 731 try { 732 if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile); 733 Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath()); 734 } catch (ErrnoException e) { 735 throw new IOException("Failed to link " + fromFile + " to " + toFile, e); 736 } 737 } 738 Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); 739 } 740 741 private static void copyFiles(List<File> fromFiles, File toDir) throws IOException { 742 // Remove any partial files from previous attempt 743 for (File file : toDir.listFiles()) { 744 if (file.getName().endsWith(".tmp")) { 745 file.delete(); 746 } 747 } 748 749 for (File fromFile : fromFiles) { 750 final File tmpFile = File.createTempFile("inherit", ".tmp", toDir); 751 if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile); 752 if (!FileUtils.copyFile(fromFile, tmpFile)) { 753 throw new IOException("Failed to copy " + fromFile + " to " + tmpFile); 754 } 755 756 final File toFile = new File(toDir, fromFile.getName()); 757 if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile); 758 if (!tmpFile.renameTo(toFile)) { 759 throw new IOException("Failed to rename " + tmpFile + " to " + toFile); 760 } 761 } 762 Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir); 763 } 764 765 private static void extractNativeLibraries(File packageDir, String abiOverride) 766 throws PackageManagerException { 767 if (LOGD) Slog.v(TAG, "extractNativeLibraries()"); 768 769 // Always start from a clean slate 770 final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME); 771 NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true); 772 773 NativeLibraryHelper.Handle handle = null; 774 try { 775 handle = NativeLibraryHelper.Handle.create(packageDir); 776 final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir, 777 abiOverride); 778 if (res != PackageManager.INSTALL_SUCCEEDED) { 779 throw new PackageManagerException(res, 780 "Failed to extract native libraries, res=" + res); 781 } 782 } catch (IOException e) { 783 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, 784 "Failed to extract native libraries", e); 785 } finally { 786 IoUtils.closeQuietly(handle); 787 } 788 } 789 790 private static void resizeContainer(String cid, long targetSize) 791 throws PackageManagerException { 792 String path = PackageHelper.getSdDir(cid); 793 if (path == null) { 794 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 795 "Failed to find mounted " + cid); 796 } 797 798 final long currentSize = new File(path).getTotalSpace(); 799 if (currentSize > targetSize) { 800 Slog.w(TAG, "Current size " + currentSize + " is larger than target size " 801 + targetSize + "; skipping resize"); 802 return; 803 } 804 805 if (!PackageHelper.unMountSdDir(cid)) { 806 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 807 "Failed to unmount " + cid + " before resize"); 808 } 809 810 if (!PackageHelper.resizeSdDir(targetSize, cid, 811 PackageManagerService.getEncryptKey())) { 812 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 813 "Failed to resize " + cid + " to " + targetSize + " bytes"); 814 } 815 816 path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), 817 Process.SYSTEM_UID, false); 818 if (path == null) { 819 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 820 "Failed to mount " + cid + " after resize"); 821 } 822 } 823 824 private void finalizeAndFixContainer(String cid) throws PackageManagerException { 825 if (!PackageHelper.finalizeSdDir(cid)) { 826 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 827 "Failed to finalize container " + cid); 828 } 829 830 final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE, 831 UserHandle.USER_OWNER); 832 final int gid = UserHandle.getSharedAppGid(uid); 833 if (!PackageHelper.fixSdPermissions(cid, gid, null)) { 834 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 835 "Failed to fix permissions on container " + cid); 836 } 837 } 838 839 void setPermissionsResult(boolean accepted) { 840 if (!mSealed) { 841 throw new SecurityException("Must be sealed to accept permissions"); 842 } 843 844 if (accepted) { 845 // Mark and kick off another install pass 846 mPermissionsAccepted = true; 847 mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); 848 } else { 849 destroyInternal(); 850 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null); 851 } 852 } 853 854 public void open() throws IOException { 855 if (mActiveCount.getAndIncrement() == 0) { 856 mCallback.onSessionActiveChanged(this, true); 857 } 858 859 synchronized (mLock) { 860 if (!mPrepared) { 861 if (stageDir != null) { 862 prepareInternalStageDir(stageDir); 863 } else if (stageCid != null) { 864 prepareExternalStageCid(stageCid, params.sizeBytes); 865 866 // TODO: deliver more granular progress for ASEC allocation 867 mInternalProgress = 0.25f; 868 computeProgressLocked(true); 869 } else { 870 throw new IllegalArgumentException( 871 "Exactly one of stageDir or stageCid stage must be set"); 872 } 873 874 mPrepared = true; 875 mCallback.onSessionPrepared(this); 876 } 877 } 878 } 879 880 @Override 881 public void close() { 882 if (mActiveCount.decrementAndGet() == 0) { 883 mCallback.onSessionActiveChanged(this, false); 884 } 885 } 886 887 @Override 888 public void abandon() { 889 destroyInternal(); 890 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); 891 } 892 893 private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { 894 mFinalStatus = returnCode; 895 mFinalMessage = msg; 896 897 if (mRemoteObserver != null) { 898 try { 899 mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras); 900 } catch (RemoteException ignored) { 901 } 902 } 903 904 final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED); 905 mCallback.onSessionFinished(this, success); 906 } 907 908 private void destroyInternal() { 909 synchronized (mLock) { 910 mSealed = true; 911 mDestroyed = true; 912 913 // Force shut down all bridges 914 for (FileBridge bridge : mBridges) { 915 bridge.forceClose(); 916 } 917 } 918 if (stageDir != null) { 919 FileUtils.deleteContents(stageDir); 920 stageDir.delete(); 921 } 922 if (stageCid != null) { 923 PackageHelper.destroySdDir(stageCid); 924 } 925 } 926 927 void dump(IndentingPrintWriter pw) { 928 synchronized (mLock) { 929 dumpLocked(pw); 930 } 931 } 932 933 private void dumpLocked(IndentingPrintWriter pw) { 934 pw.println("Session " + sessionId + ":"); 935 pw.increaseIndent(); 936 937 pw.printPair("userId", userId); 938 pw.printPair("installerPackageName", installerPackageName); 939 pw.printPair("installerUid", installerUid); 940 pw.printPair("createdMillis", createdMillis); 941 pw.printPair("stageDir", stageDir); 942 pw.printPair("stageCid", stageCid); 943 pw.println(); 944 945 params.dump(pw); 946 947 pw.printPair("mClientProgress", mClientProgress); 948 pw.printPair("mProgress", mProgress); 949 pw.printPair("mSealed", mSealed); 950 pw.printPair("mPermissionsAccepted", mPermissionsAccepted); 951 pw.printPair("mDestroyed", mDestroyed); 952 pw.printPair("mBridges", mBridges.size()); 953 pw.printPair("mFinalStatus", mFinalStatus); 954 pw.printPair("mFinalMessage", mFinalMessage); 955 pw.println(); 956 957 pw.decreaseIndent(); 958 } 959} 960