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