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