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