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