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