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