PackageInstallerSession.java revision f06009542390472872da986486d385001e91a2a7
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_ALREADY_EXISTS; 21import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 22import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; 23import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; 24import static android.system.OsConstants.O_CREAT; 25import static android.system.OsConstants.O_RDONLY; 26import static android.system.OsConstants.O_WRONLY; 27 28import android.content.Intent; 29import android.content.pm.ApplicationInfo; 30import android.content.pm.IPackageInstallObserver2; 31import android.content.pm.IPackageInstallerSession; 32import android.content.pm.InstallSessionInfo; 33import android.content.pm.InstallSessionParams; 34import android.content.pm.PackageInstaller; 35import android.content.pm.PackageManager; 36import android.content.pm.PackageParser; 37import android.content.pm.PackageParser.ApkLite; 38import android.content.pm.PackageParser.PackageParserException; 39import android.content.pm.Signature; 40import android.os.Bundle; 41import android.os.FileBridge; 42import android.os.FileUtils; 43import android.os.Handler; 44import android.os.Looper; 45import android.os.Message; 46import android.os.ParcelFileDescriptor; 47import android.os.RemoteException; 48import android.os.UserHandle; 49import android.system.ErrnoException; 50import android.system.Os; 51import android.system.OsConstants; 52import android.system.StructStat; 53import android.util.ArraySet; 54import android.util.ExceptionUtils; 55import android.util.MathUtils; 56import android.util.Slog; 57 58import com.android.internal.annotations.GuardedBy; 59import com.android.internal.util.ArrayUtils; 60import com.android.internal.util.IndentingPrintWriter; 61import com.android.internal.util.Preconditions; 62 63import libcore.io.Libcore; 64 65import java.io.File; 66import java.io.FileDescriptor; 67import java.io.IOException; 68import java.util.ArrayList; 69import java.util.concurrent.atomic.AtomicInteger; 70 71public class PackageInstallerSession extends IPackageInstallerSession.Stub { 72 private static final String TAG = "PackageInstaller"; 73 private static final boolean LOGD = true; 74 75 private static final int MSG_COMMIT = 0; 76 77 // TODO: enforce INSTALL_ALLOW_TEST 78 // TODO: enforce INSTALL_ALLOW_DOWNGRADE 79 // TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL 80 81 // TODO: treat INHERIT_EXISTING as installExistingPackage() 82 83 private final PackageInstallerService.InternalCallback mCallback; 84 private final PackageManagerService mPm; 85 private final Handler mHandler; 86 87 final int sessionId; 88 final int userId; 89 final String installerPackageName; 90 final InstallSessionParams params; 91 final long createdMillis; 92 final File sessionStageDir; 93 94 /** Note that UID is not persisted; it's always derived at runtime. */ 95 final int installerUid; 96 97 AtomicInteger openCount = new AtomicInteger(); 98 99 private final Object mLock = new Object(); 100 101 @GuardedBy("mLock") 102 private float mClientProgress = 0; 103 @GuardedBy("mLock") 104 private float mProgress = 0; 105 @GuardedBy("mLock") 106 private float mReportedProgress = -1; 107 108 @GuardedBy("mLock") 109 private boolean mSealed = false; 110 @GuardedBy("mLock") 111 private boolean mPermissionsAccepted = false; 112 @GuardedBy("mLock") 113 private boolean mDestroyed = false; 114 115 private int mFinalStatus; 116 private String mFinalMessage; 117 118 /** 119 * Path to the resolved base APK for this session, which may point at an APK 120 * inside the session (when the session defines the base), or it may point 121 * at the existing base APK (when adding splits to an existing app). 122 * <p> 123 * This is used when confirming permissions, since we can't fully stage the 124 * session inside an ASEC before confirming with user. 125 */ 126 @GuardedBy("mLock") 127 private String mResolvedBaseCodePath; 128 129 @GuardedBy("mLock") 130 private ArrayList<FileBridge> mBridges = new ArrayList<>(); 131 132 @GuardedBy("mLock") 133 private IPackageInstallObserver2 mRemoteObserver; 134 135 /** Fields derived from commit parsing */ 136 private String mPackageName; 137 private int mVersionCode; 138 private Signature[] mSignatures; 139 140 private final Handler.Callback mHandlerCallback = new Handler.Callback() { 141 @Override 142 public boolean handleMessage(Message msg) { 143 synchronized (mLock) { 144 if (msg.obj != null) { 145 mRemoteObserver = (IPackageInstallObserver2) msg.obj; 146 } 147 148 try { 149 commitLocked(); 150 } catch (PackageManagerException e) { 151 Slog.e(TAG, "Install failed: " + e); 152 destroyInternal(); 153 dispatchSessionFinished(e.error, e.getMessage(), null); 154 } 155 156 return true; 157 } 158 } 159 }; 160 161 public PackageInstallerSession(PackageInstallerService.InternalCallback callback, 162 PackageManagerService pm, Looper looper, int sessionId, int userId, 163 String installerPackageName, InstallSessionParams params, long createdMillis, 164 File sessionStageDir, boolean sealed) { 165 mCallback = callback; 166 mPm = pm; 167 mHandler = new Handler(looper, mHandlerCallback); 168 169 this.sessionId = sessionId; 170 this.userId = userId; 171 this.installerPackageName = installerPackageName; 172 this.params = params; 173 this.createdMillis = createdMillis; 174 this.sessionStageDir = sessionStageDir; 175 176 mSealed = sealed; 177 178 // Always derived at runtime 179 installerUid = mPm.getPackageUid(installerPackageName, userId); 180 181 if (mPm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, 182 installerPackageName) == PackageManager.PERMISSION_GRANTED) { 183 mPermissionsAccepted = true; 184 } else { 185 mPermissionsAccepted = false; 186 } 187 188 computeProgressLocked(); 189 } 190 191 public InstallSessionInfo generateInfo() { 192 final InstallSessionInfo info = new InstallSessionInfo(); 193 194 info.sessionId = sessionId; 195 info.installerPackageName = installerPackageName; 196 info.resolvedBaseCodePath = mResolvedBaseCodePath; 197 info.progress = mProgress; 198 info.sealed = mSealed; 199 info.open = openCount.get() > 0; 200 201 info.mode = params.mode; 202 info.sizeBytes = params.sizeBytes; 203 info.appPackageName = params.appPackageName; 204 info.appIcon = params.appIcon; 205 info.appLabel = params.appLabel; 206 207 return info; 208 } 209 210 private void assertNotSealed(String cookie) { 211 synchronized (mLock) { 212 if (mSealed) { 213 throw new SecurityException(cookie + " not allowed after commit"); 214 } 215 } 216 } 217 218 @Override 219 public void setClientProgress(float progress) { 220 synchronized (mLock) { 221 mClientProgress = progress; 222 computeProgressLocked(); 223 } 224 maybePublishProgress(); 225 } 226 227 @Override 228 public void addClientProgress(float progress) { 229 synchronized (mLock) { 230 mClientProgress += progress; 231 computeProgressLocked(); 232 } 233 maybePublishProgress(); 234 } 235 236 private void computeProgressLocked() { 237 mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f); 238 } 239 240 private void maybePublishProgress() { 241 // Only publish when meaningful change 242 if (Math.abs(mProgress - mReportedProgress) > 0.01) { 243 mReportedProgress = mProgress; 244 mCallback.onSessionProgressChanged(this, mProgress); 245 } 246 } 247 248 @Override 249 public String[] list() { 250 assertNotSealed("list"); 251 return sessionStageDir.list(); 252 } 253 254 @Override 255 public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) { 256 try { 257 return openWriteInternal(name, offsetBytes, lengthBytes); 258 } catch (IOException e) { 259 throw ExceptionUtils.wrap(e); 260 } 261 } 262 263 private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes) 264 throws IOException { 265 // TODO: relay over to DCS when installing to ASEC 266 267 // Quick sanity check of state, and allocate a pipe for ourselves. We 268 // then do heavy disk allocation outside the lock, but this open pipe 269 // will block any attempted install transitions. 270 final FileBridge bridge; 271 synchronized (mLock) { 272 assertNotSealed("openWrite"); 273 274 bridge = new FileBridge(); 275 mBridges.add(bridge); 276 } 277 278 try { 279 // Use installer provided name for now; we always rename later 280 if (!FileUtils.isValidExtFilename(name)) { 281 throw new IllegalArgumentException("Invalid name: " + name); 282 } 283 final File target = new File(sessionStageDir, name); 284 285 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), 286 O_CREAT | O_WRONLY, 0644); 287 Os.chmod(target.getAbsolutePath(), 0644); 288 289 // If caller specified a total length, allocate it for them. Free up 290 // cache space to grow, if needed. 291 if (lengthBytes > 0) { 292 final StructStat stat = Libcore.os.fstat(targetFd); 293 final long deltaBytes = lengthBytes - stat.st_size; 294 if (deltaBytes > 0) { 295 mPm.freeStorage(deltaBytes); 296 } 297 Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); 298 } 299 300 if (offsetBytes > 0) { 301 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); 302 } 303 304 bridge.setTargetFile(targetFd); 305 bridge.start(); 306 return new ParcelFileDescriptor(bridge.getClientSocket()); 307 308 } catch (ErrnoException e) { 309 throw e.rethrowAsIOException(); 310 } 311 } 312 313 @Override 314 public ParcelFileDescriptor openRead(String name) { 315 try { 316 return openReadInternal(name); 317 } catch (IOException e) { 318 throw ExceptionUtils.wrap(e); 319 } 320 } 321 322 private ParcelFileDescriptor openReadInternal(String name) throws IOException { 323 assertNotSealed("openRead"); 324 325 try { 326 if (!FileUtils.isValidExtFilename(name)) { 327 throw new IllegalArgumentException("Invalid name: " + name); 328 } 329 final File target = new File(sessionStageDir, name); 330 331 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0); 332 return new ParcelFileDescriptor(targetFd); 333 334 } catch (ErrnoException e) { 335 throw e.rethrowAsIOException(); 336 } 337 } 338 339 @Override 340 public void commit(IPackageInstallObserver2 observer) { 341 Preconditions.checkNotNull(observer); 342 mHandler.obtainMessage(MSG_COMMIT, observer).sendToTarget(); 343 } 344 345 private void commitLocked() throws PackageManagerException { 346 if (mDestroyed) { 347 throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session"); 348 } 349 350 // Verify that all writers are hands-off 351 if (!mSealed) { 352 for (FileBridge bridge : mBridges) { 353 if (!bridge.isClosed()) { 354 throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED, 355 "Files still open"); 356 } 357 } 358 mSealed = true; 359 360 // TODO: persist disabled mutations before going forward, since 361 // beyond this point we may have hardlinks to the valid install 362 } 363 364 // Verify that stage looks sane with respect to existing application. 365 // This currently only ensures packageName, versionCode, and certificate 366 // consistency. 367 validateInstallLocked(); 368 369 Preconditions.checkNotNull(mPackageName); 370 Preconditions.checkNotNull(mSignatures); 371 Preconditions.checkNotNull(mResolvedBaseCodePath); 372 373 if (!mPermissionsAccepted) { 374 // User needs to accept permissions; give installer an intent they 375 // can use to involve user. 376 final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS); 377 intent.setPackage("com.android.packageinstaller"); 378 intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); 379 try { 380 mRemoteObserver.onUserActionRequired(intent); 381 } catch (RemoteException ignored) { 382 } 383 return; 384 } 385 386 // Inherit any packages and native libraries from existing install that 387 // haven't been overridden. 388 if (params.mode == InstallSessionParams.MODE_INHERIT_EXISTING) { 389 spliceExistingFilesIntoStage(); 390 } 391 392 // TODO: surface more granular state from dexopt 393 mCallback.onSessionProgressChanged(this, 0.9f); 394 395 // TODO: for ASEC based applications, grow and stream in packages 396 397 // We've reached point of no return; call into PMS to install the stage. 398 // Regardless of success or failure we always destroy session. 399 final IPackageInstallObserver2 remoteObserver = mRemoteObserver; 400 final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { 401 @Override 402 public void onUserActionRequired(Intent intent) { 403 throw new IllegalStateException(); 404 } 405 406 @Override 407 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 408 Bundle extras) { 409 destroyInternal(); 410 dispatchSessionFinished(returnCode, msg, extras); 411 } 412 }; 413 414 mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params, 415 installerPackageName, installerUid, new UserHandle(userId)); 416 } 417 418 /** 419 * Validate install by confirming that all application packages are have 420 * consistent package name, version code, and signing certificates. 421 * <p> 422 * Renames package files in stage to match split names defined inside. 423 */ 424 private void validateInstallLocked() throws PackageManagerException { 425 mPackageName = null; 426 mVersionCode = -1; 427 mSignatures = null; 428 mResolvedBaseCodePath = null; 429 430 final File[] files = sessionStageDir.listFiles(); 431 if (ArrayUtils.isEmpty(files)) { 432 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); 433 } 434 435 final ArraySet<String> seenSplits = new ArraySet<>(); 436 437 // Verify that all staged packages are internally consistent 438 for (File file : files) { 439 final ApkLite info; 440 try { 441 info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES); 442 } catch (PackageParserException e) { 443 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 444 "Failed to parse " + file + ": " + e); 445 } 446 447 if (!seenSplits.add(info.splitName)) { 448 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 449 "Split " + info.splitName + " was defined multiple times"); 450 } 451 452 // Use first package to define unknown values 453 if (mPackageName == null) { 454 mPackageName = info.packageName; 455 mVersionCode = info.versionCode; 456 } 457 if (mSignatures == null) { 458 mSignatures = info.signatures; 459 } 460 461 assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode, 462 info.signatures); 463 464 // Take this opportunity to enforce uniform naming 465 final String targetName; 466 if (info.splitName == null) { 467 targetName = "base.apk"; 468 } else { 469 targetName = "split_" + info.splitName + ".apk"; 470 } 471 if (!FileUtils.isValidExtFilename(targetName)) { 472 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 473 "Invalid filename: " + targetName); 474 } 475 476 final File targetFile = new File(sessionStageDir, targetName); 477 if (!file.equals(targetFile)) { 478 file.renameTo(targetFile); 479 } 480 481 // Base is coming from session 482 if (info.splitName == null) { 483 mResolvedBaseCodePath = targetFile.getAbsolutePath(); 484 } 485 } 486 487 // TODO: shift package signature verification to installer; we're 488 // currently relying on PMS to do this. 489 // TODO: teach about compatible upgrade keysets. 490 491 if (params.mode == InstallSessionParams.MODE_FULL_INSTALL) { 492 // Full installs must include a base package 493 if (!seenSplits.contains(null)) { 494 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 495 "Full install must include a base package"); 496 } 497 498 } else { 499 // Partial installs must be consistent with existing install 500 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); 501 if (app == null) { 502 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 503 "Missing existing base package for " + mPackageName); 504 } 505 506 // Base might be inherited from existing install 507 if (mResolvedBaseCodePath == null) { 508 mResolvedBaseCodePath = app.getBaseCodePath(); 509 } 510 511 final ApkLite info; 512 try { 513 info = PackageParser.parseApkLite(new File(app.getBaseCodePath()), 514 PackageParser.PARSE_GET_SIGNATURES); 515 } catch (PackageParserException e) { 516 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 517 "Failed to parse existing base " + app.getBaseCodePath() + ": " + e); 518 } 519 520 assertPackageConsistent("Existing base", info.packageName, info.versionCode, 521 info.signatures); 522 } 523 } 524 525 private void assertPackageConsistent(String tag, String packageName, int versionCode, 526 Signature[] signatures) throws PackageManagerException { 527 if (!mPackageName.equals(packageName)) { 528 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " 529 + packageName + " inconsistent with " + mPackageName); 530 } 531 if (mVersionCode != versionCode) { 532 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag 533 + " version code " + versionCode + " inconsistent with " 534 + mVersionCode); 535 } 536 if (!Signature.areExactMatch(mSignatures, signatures)) { 537 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 538 tag + " signatures are inconsistent"); 539 } 540 } 541 542 /** 543 * Application is already installed; splice existing files that haven't been 544 * overridden into our stage. 545 */ 546 private void spliceExistingFilesIntoStage() throws PackageManagerException { 547 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); 548 549 int n = 0; 550 final File[] oldFiles = new File(app.getCodePath()).listFiles(); 551 if (!ArrayUtils.isEmpty(oldFiles)) { 552 for (File oldFile : oldFiles) { 553 if (!PackageParser.isApkFile(oldFile)) continue; 554 555 final File newFile = new File(sessionStageDir, oldFile.getName()); 556 try { 557 Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath()); 558 n++; 559 } catch (ErrnoException e) { 560 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, 561 "Failed to splice into stage", e); 562 } 563 } 564 } 565 566 if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage"); 567 } 568 569 void setPermissionsResult(boolean accepted) { 570 if (!mSealed) { 571 throw new SecurityException("Must be sealed to accept permissions"); 572 } 573 574 if (accepted) { 575 // Mark and kick off another install pass 576 mPermissionsAccepted = true; 577 mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); 578 } else { 579 destroyInternal(); 580 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null); 581 } 582 } 583 584 @Override 585 public void close() { 586 if (openCount.decrementAndGet() == 0) { 587 mCallback.onSessionClosed(this); 588 } 589 } 590 591 @Override 592 public void abandon() { 593 destroyInternal(); 594 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); 595 } 596 597 private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { 598 mFinalStatus = returnCode; 599 mFinalMessage = msg; 600 601 if (mRemoteObserver != null) { 602 try { 603 mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras); 604 } catch (RemoteException ignored) { 605 } 606 } 607 608 final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED); 609 mCallback.onSessionFinished(this, success); 610 } 611 612 private void destroyInternal() { 613 synchronized (mLock) { 614 mSealed = true; 615 mDestroyed = true; 616 } 617 FileUtils.deleteContents(sessionStageDir); 618 sessionStageDir.delete(); 619 } 620 621 void dump(IndentingPrintWriter pw) { 622 pw.println("Session " + sessionId + ":"); 623 pw.increaseIndent(); 624 625 pw.printPair("userId", userId); 626 pw.printPair("installerPackageName", installerPackageName); 627 pw.printPair("installerUid", installerUid); 628 pw.printPair("createdMillis", createdMillis); 629 pw.printPair("sessionStageDir", sessionStageDir); 630 pw.println(); 631 632 params.dump(pw); 633 634 pw.printPair("mClientProgress", mClientProgress); 635 pw.printPair("mProgress", mProgress); 636 pw.printPair("mSealed", mSealed); 637 pw.printPair("mPermissionsAccepted", mPermissionsAccepted); 638 pw.printPair("mDestroyed", mDestroyed); 639 pw.printPair("mBridges", mBridges.size()); 640 pw.printPair("mFinalStatus", mFinalStatus); 641 pw.printPair("mFinalMessage", mFinalMessage); 642 pw.println(); 643 644 pw.decreaseIndent(); 645 } 646 647 Snapshot snapshot() { 648 return new Snapshot(this); 649 } 650 651 static class Snapshot { 652 final float clientProgress; 653 final boolean sealed; 654 655 public Snapshot(PackageInstallerSession session) { 656 clientProgress = session.mClientProgress; 657 sealed = session.mSealed; 658 } 659 } 660} 661