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