PackageInstallerSession.java revision 3a44f3f1b446315ef894e01d2ab9b5388c2bd8c4
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.content.pm.PackageManager.INSTALL_SUCCEEDED; 24 25import android.content.pm.ApplicationInfo; 26import android.content.pm.IPackageInstallObserver2; 27import android.content.pm.IPackageInstallerSession; 28import android.content.pm.PackageInstallerParams; 29import android.content.pm.PackageManager; 30import android.content.pm.PackageParser; 31import android.content.pm.PackageParser.PackageLite; 32import android.content.pm.Signature; 33import android.os.Build; 34import android.os.Bundle; 35import android.os.FileUtils; 36import android.os.Handler; 37import android.os.Looper; 38import android.os.Message; 39import android.os.ParcelFileDescriptor; 40import android.os.RemoteException; 41import android.os.SELinux; 42import android.system.ErrnoException; 43import android.system.OsConstants; 44import android.system.StructStat; 45import android.util.ArraySet; 46import android.util.Slog; 47 48import com.android.internal.content.NativeLibraryHelper; 49import com.android.internal.util.ArrayUtils; 50import com.android.internal.util.Preconditions; 51 52import libcore.io.IoUtils; 53import libcore.io.Libcore; 54import libcore.io.Streams; 55 56import java.io.File; 57import java.io.FileDescriptor; 58import java.io.FileInputStream; 59import java.io.FileOutputStream; 60import java.io.IOException; 61import java.util.ArrayList; 62 63public class PackageInstallerSession extends IPackageInstallerSession.Stub { 64 private static final String TAG = "PackageInstaller"; 65 66 private final PackageInstallerService.Callback mCallback; 67 private final PackageManagerService mPm; 68 private final Handler mHandler; 69 70 public final int sessionId; 71 public final int userId; 72 public final String installerPackageName; 73 /** UID not persisted */ 74 public final int installerUid; 75 public final PackageInstallerParams params; 76 public final long createdMillis; 77 public final File sessionDir; 78 79 private static final int MSG_INSTALL = 0; 80 81 private Handler.Callback mHandlerCallback = new Handler.Callback() { 82 @Override 83 public boolean handleMessage(Message msg) { 84 synchronized (mLock) { 85 if (msg.obj != null) { 86 mRemoteObserver = (IPackageInstallObserver2) msg.obj; 87 } 88 89 try { 90 installLocked(); 91 } catch (InstallFailedException e) { 92 Slog.e(TAG, "Install failed: " + e); 93 try { 94 mRemoteObserver.packageInstalled(mPackageName, null, e.error); 95 } catch (RemoteException ignored) { 96 } 97 } 98 99 return true; 100 } 101 } 102 }; 103 104 private final Object mLock = new Object(); 105 106 private int mProgress; 107 108 private String mPackageName; 109 private int mVersionCode; 110 private Signature[] mSignatures; 111 112 private boolean mMutationsAllowed; 113 private boolean mVerifierConfirmed; 114 private boolean mPermissionsConfirmed; 115 private boolean mInvalid; 116 117 private ArrayList<WritePipe> mPipes = new ArrayList<>(); 118 119 private IPackageInstallObserver2 mRemoteObserver; 120 121 public PackageInstallerSession(PackageInstallerService.Callback callback, 122 PackageManagerService pm, int sessionId, int userId, String installerPackageName, 123 int installerUid, PackageInstallerParams params, long createdMillis, File sessionDir, 124 Looper looper) { 125 mCallback = callback; 126 mPm = pm; 127 mHandler = new Handler(looper, mHandlerCallback); 128 129 this.sessionId = sessionId; 130 this.userId = userId; 131 this.installerPackageName = installerPackageName; 132 this.installerUid = installerUid; 133 this.params = params; 134 this.createdMillis = createdMillis; 135 this.sessionDir = sessionDir; 136 137 // Check against any explicitly provided signatures 138 mSignatures = params.signatures; 139 140 // TODO: splice in flag when restoring persisted session 141 mMutationsAllowed = true; 142 143 if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName) 144 == PackageManager.PERMISSION_GRANTED) { 145 mPermissionsConfirmed = true; 146 } 147 } 148 149 @Override 150 public void updateProgress(int progress) { 151 mProgress = progress; 152 mCallback.onProgressChanged(this); 153 } 154 155 @Override 156 public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) { 157 // TODO: relay over to DCS when installing to ASEC 158 159 // Quick sanity check of state, and allocate a pipe for ourselves. We 160 // then do heavy disk allocation outside the lock, but this open pipe 161 // will block any attempted install transitions. 162 final WritePipe pipe; 163 synchronized (mLock) { 164 if (!mMutationsAllowed) { 165 throw new IllegalStateException("Mutations not allowed"); 166 } 167 168 pipe = new WritePipe(); 169 mPipes.add(pipe); 170 } 171 172 try { 173 // Use installer provided name for now; we always rename later 174 if (!FileUtils.isValidExtFilename(name)) { 175 throw new IllegalArgumentException("Invalid name: " + name); 176 } 177 final File target = new File(sessionDir, name); 178 179 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), 180 OsConstants.O_CREAT | OsConstants.O_WRONLY, 00700); 181 182 // If caller specified a total length, allocate it for them. Free up 183 // cache space to grow, if needed. 184 if (lengthBytes > 0) { 185 final StructStat stat = Libcore.os.fstat(targetFd); 186 final long deltaBytes = lengthBytes - stat.st_size; 187 if (deltaBytes > 0) { 188 mPm.freeStorage(deltaBytes); 189 } 190 Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); 191 } 192 193 if (offsetBytes > 0) { 194 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); 195 } 196 197 pipe.setTargetFd(targetFd); 198 pipe.start(); 199 return pipe.getWriteFd(); 200 201 } catch (ErrnoException e) { 202 throw new IllegalStateException("Failed to write", e); 203 } catch (IOException e) { 204 throw new IllegalStateException("Failed to write", e); 205 } 206 } 207 208 @Override 209 public void install(IPackageInstallObserver2 observer) { 210 Preconditions.checkNotNull(observer); 211 mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget(); 212 } 213 214 private void installLocked() throws InstallFailedException { 215 if (mInvalid) { 216 throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session"); 217 } 218 219 // Verify that all writers are hands-off 220 if (mMutationsAllowed) { 221 for (WritePipe pipe : mPipes) { 222 if (!pipe.isClosed()) { 223 throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED, 224 "Files still open"); 225 } 226 } 227 mMutationsAllowed = false; 228 229 // TODO: persist disabled mutations before going forward, since 230 // beyond this point we may have hardlinks to the valid install 231 } 232 233 // Verify that stage looks sane with respect to existing application. 234 // This currently only ensures packageName, versionCode, and certificate 235 // consistency. 236 validateInstallLocked(); 237 238 Preconditions.checkNotNull(mPackageName); 239 Preconditions.checkNotNull(mSignatures); 240 241 if (!mVerifierConfirmed) { 242 // TODO: async communication with verifier 243 // when they confirm, we'll kick off another install() pass 244 mVerifierConfirmed = true; 245 } 246 247 if (!mPermissionsConfirmed) { 248 // TODO: async confirm permissions with user 249 // when they confirm, we'll kick off another install() pass 250 mPermissionsConfirmed = true; 251 } 252 253 // Unpack any native libraries contained in this session 254 unpackNativeLibraries(); 255 256 // Inherit any packages and native libraries from existing install that 257 // haven't been overridden. 258 if (!params.fullInstall) { 259 spliceExistingFilesIntoStage(); 260 } 261 262 // TODO: for ASEC based applications, grow and stream in packages 263 264 // We've reached point of no return; call into PMS to install the stage. 265 // Regardless of success or failure we always destroy session. 266 final IPackageInstallObserver2 remoteObserver = mRemoteObserver; 267 final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { 268 @Override 269 public void packageInstalled(String basePackageName, Bundle extras, int returnCode) 270 throws RemoteException { 271 destroy(); 272 remoteObserver.packageInstalled(basePackageName, extras, returnCode); 273 } 274 }; 275 276 mPm.installStage(mPackageName, this.sessionDir, localObserver, params.installFlags); 277 } 278 279 /** 280 * Validate install by confirming that all application packages are have 281 * consistent package name, version code, and signing certificates. 282 * <p> 283 * Renames package files in stage to match split names defined inside. 284 */ 285 private void validateInstallLocked() throws InstallFailedException { 286 mPackageName = null; 287 mVersionCode = -1; 288 mSignatures = null; 289 290 final File[] files = sessionDir.listFiles(); 291 if (ArrayUtils.isEmpty(files)) { 292 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged"); 293 } 294 295 final ArraySet<String> seenSplits = new ArraySet<>(); 296 297 // Verify that all staged packages are internally consistent 298 for (File file : files) { 299 final PackageLite info = PackageParser.parsePackageLite(file.getAbsolutePath(), 300 PackageParser.PARSE_GET_SIGNATURES); 301 if (info == null) { 302 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 303 "Failed to parse " + file); 304 } 305 306 if (!seenSplits.add(info.splitName)) { 307 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 308 "Split " + info.splitName + " was defined multiple times"); 309 } 310 311 // Use first package to define unknown values 312 if (mPackageName != null) { 313 mPackageName = info.packageName; 314 mVersionCode = info.versionCode; 315 } 316 if (mSignatures != null) { 317 mSignatures = info.signatures; 318 } 319 320 assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode, 321 info.signatures); 322 323 // Take this opportunity to enforce uniform naming 324 final String name; 325 if (info.splitName == null) { 326 name = info.packageName + ".apk"; 327 } else { 328 name = info.packageName + "-" + info.splitName + ".apk"; 329 } 330 if (!FileUtils.isValidExtFilename(name)) { 331 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 332 "Invalid filename: " + name); 333 } 334 if (!file.getName().equals(name)) { 335 file.renameTo(new File(file.getParentFile(), name)); 336 } 337 } 338 339 // TODO: shift package signature verification to installer; we're 340 // currently relying on PMS to do this. 341 // TODO: teach about compatible upgrade keysets. 342 343 if (params.fullInstall) { 344 // Full installs must include a base package 345 if (!seenSplits.contains(null)) { 346 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 347 "Full install must include a base package"); 348 } 349 350 } else { 351 // Partial installs must be consistent with existing install. 352 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); 353 if (app == null) { 354 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 355 "Missing existing base package for " + mPackageName); 356 } 357 358 final PackageLite info = PackageParser.parsePackageLite(app.sourceDir, 359 PackageParser.PARSE_GET_SIGNATURES); 360 if (info == null) { 361 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 362 "Failed to parse existing base " + app.sourceDir); 363 } 364 365 assertPackageConsistent("Existing base", info.packageName, info.versionCode, 366 info.signatures); 367 } 368 } 369 370 private void assertPackageConsistent(String tag, String packageName, int versionCode, 371 Signature[] signatures) throws InstallFailedException { 372 if (!mPackageName.equals(packageName)) { 373 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package " 374 + packageName + " inconsistent with " + mPackageName); 375 } 376 if (mVersionCode != versionCode) { 377 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag 378 + " version code " + versionCode + " inconsistent with " 379 + mVersionCode); 380 } 381 if (!Signature.areExactMatch(mSignatures, signatures)) { 382 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 383 tag + " signatures are inconsistent"); 384 } 385 } 386 387 /** 388 * Application is already installed; splice existing files that haven't been 389 * overridden into our stage. 390 */ 391 private void spliceExistingFilesIntoStage() throws InstallFailedException { 392 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); 393 final File existingDir = new File(app.sourceDir).getParentFile(); 394 395 try { 396 linkTreeIgnoringExisting(existingDir, sessionDir); 397 } catch (ErrnoException e) { 398 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 399 "Failed to splice into stage"); 400 } 401 } 402 403 /** 404 * Recursively hard link all files from source directory tree to target. 405 * When a file already exists in the target tree, it leaves that file 406 * intact. 407 */ 408 private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException { 409 final File[] sourceContents = sourceDir.listFiles(); 410 if (ArrayUtils.isEmpty(sourceContents)) return; 411 412 for (File sourceFile : sourceContents) { 413 final File targetFile = new File(targetDir, sourceFile.getName()); 414 415 if (sourceFile.isDirectory()) { 416 targetFile.mkdir(); 417 linkTreeIgnoringExisting(sourceFile, targetFile); 418 } else { 419 Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath()); 420 } 421 } 422 } 423 424 private void unpackNativeLibraries() throws InstallFailedException { 425 final File libDir = new File(sessionDir, "lib"); 426 427 if (!libDir.mkdir()) { 428 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 429 "Failed to create " + libDir); 430 } 431 432 try { 433 Libcore.os.chmod(libDir.getAbsolutePath(), 0755); 434 } catch (ErrnoException e) { 435 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 436 "Failed to prepare " + libDir + ": " + e); 437 } 438 439 if (!SELinux.restorecon(libDir)) { 440 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 441 "Failed to set context on " + libDir); 442 } 443 444 // Unpack all native libraries under stage 445 final File[] files = sessionDir.listFiles(); 446 if (ArrayUtils.isEmpty(files)) { 447 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged"); 448 } 449 450 for (File file : files) { 451 final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(file); 452 try { 453 final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle, 454 Build.SUPPORTED_ABIS); 455 if (abiIndex >= 0) { 456 int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libDir, 457 Build.SUPPORTED_ABIS[abiIndex]); 458 if (copyRet != INSTALL_SUCCEEDED) { 459 throw new InstallFailedException(copyRet, 460 "Failed to copy native libraries for " + file); 461 } 462 } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) { 463 throw new InstallFailedException(abiIndex, 464 "Failed to copy native libraries for " + file); 465 } 466 } finally { 467 handle.close(); 468 } 469 } 470 } 471 472 @Override 473 public void destroy() { 474 try { 475 synchronized (mLock) { 476 mInvalid = true; 477 } 478 FileUtils.deleteContents(sessionDir); 479 sessionDir.delete(); 480 } finally { 481 mCallback.onSessionInvalid(this); 482 } 483 } 484 485 private static class WritePipe extends Thread { 486 private final ParcelFileDescriptor[] mPipe; 487 488 private FileDescriptor mTargetFd; 489 490 private volatile boolean mClosed; 491 492 public WritePipe() { 493 try { 494 mPipe = ParcelFileDescriptor.createPipe(); 495 } catch (IOException e) { 496 throw new IllegalStateException("Failed to create pipe"); 497 } 498 } 499 500 public boolean isClosed() { 501 return mClosed; 502 } 503 504 public void setTargetFd(FileDescriptor targetFd) { 505 mTargetFd = targetFd; 506 } 507 508 public ParcelFileDescriptor getWriteFd() { 509 return mPipe[1]; 510 } 511 512 @Override 513 public void run() { 514 FileInputStream in = null; 515 FileOutputStream out = null; 516 try { 517 // TODO: look at switching to sendfile(2) to speed up 518 in = new FileInputStream(mPipe[0].getFileDescriptor()); 519 out = new FileOutputStream(mTargetFd); 520 Streams.copy(in, out); 521 } catch (IOException e) { 522 Slog.w(TAG, "Failed to stream data: " + e); 523 } finally { 524 IoUtils.closeQuietly(mPipe[0]); 525 IoUtils.closeQuietly(mTargetFd); 526 mClosed = true; 527 } 528 } 529 } 530 531 private class InstallFailedException extends Exception { 532 private final int error; 533 534 public InstallFailedException(int error, String detailMessage) { 535 super(detailMessage); 536 this.error = error; 537 } 538 } 539} 540