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