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