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