DexManager.java revision 61fd6eab463d5b86ef177537c149ee45a0a40dcc
1/* 2 * Copyright (C) 2016 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.dex; 18 19import android.content.pm.ApplicationInfo; 20import android.content.pm.IPackageManager; 21import android.content.pm.PackageInfo; 22import android.content.pm.PackageParser; 23import android.os.RemoteException; 24import android.os.storage.StorageManager; 25 26import android.util.Slog; 27 28import com.android.internal.annotations.GuardedBy; 29import com.android.server.pm.Installer; 30import com.android.server.pm.Installer.InstallerException; 31import com.android.server.pm.PackageDexOptimizer; 32import com.android.server.pm.PackageManagerServiceUtils; 33import com.android.server.pm.PackageManagerServiceCompilerMapping; 34 35import java.io.File; 36import java.io.IOException; 37import java.util.List; 38import java.util.HashMap; 39import java.util.HashSet; 40import java.util.Map; 41import java.util.Set; 42 43import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; 44import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; 45 46/** 47 * This class keeps track of how dex files are used. 48 * Every time it gets a notification about a dex file being loaded it tracks 49 * its owning package and records it in PackageDexUsage (package-dex-usage.list). 50 * 51 * TODO(calin): Extract related dexopt functionality from PackageManagerService 52 * into this class. 53 */ 54public class DexManager { 55 private static final String TAG = "DexManager"; 56 57 private static final boolean DEBUG = false; 58 59 // Maps package name to code locations. 60 // It caches the code locations for the installed packages. This allows for 61 // faster lookups (no locks) when finding what package owns the dex file. 62 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; 63 64 // PackageDexUsage handles the actual I/O operations. It is responsible to 65 // encode and save the dex usage data. 66 private final PackageDexUsage mPackageDexUsage; 67 68 private final IPackageManager mPackageManager; 69 private final PackageDexOptimizer mPackageDexOptimizer; 70 private final Object mInstallLock; 71 @GuardedBy("mInstallLock") 72 private final Installer mInstaller; 73 74 // Possible outcomes of a dex search. 75 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found 76 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk 77 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 78 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex 79 80 public DexManager(IPackageManager pms, PackageDexOptimizer pdo, 81 Installer installer, Object installLock) { 82 mPackageCodeLocationsCache = new HashMap<>(); 83 mPackageDexUsage = new PackageDexUsage(); 84 mPackageManager = pms; 85 mPackageDexOptimizer = pdo; 86 mInstaller = installer; 87 mInstallLock = installLock; 88 } 89 90 /** 91 * Notify about dex files loads. 92 * Note that this method is invoked when apps load dex files and it should 93 * return as fast as possible. 94 * 95 * @param loadingPackage the package performing the load 96 * @param dexPaths the list of dex files being loaded 97 * @param loaderIsa the ISA of the app loading the dex files 98 * @param loaderUserId the user id which runs the code loading the dex files 99 */ 100 public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths, 101 String loaderIsa, int loaderUserId) { 102 try { 103 notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId); 104 } catch (Exception e) { 105 Slog.w(TAG, "Exception while notifying dex load for package " + 106 loadingAppInfo.packageName, e); 107 } 108 } 109 110 private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths, 111 String loaderIsa, int loaderUserId) { 112 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 113 Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " + 114 loaderIsa + "?"); 115 return; 116 } 117 118 for (String dexPath : dexPaths) { 119 // Find the owning package name. 120 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); 121 122 if (DEBUG) { 123 Slog.i(TAG, loadingAppInfo.packageName 124 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); 125 } 126 127 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { 128 // TODO(calin): extend isUsedByOtherApps check to detect the cases where 129 // different apps share the same runtime. In that case we should not mark the dex 130 // file as isUsedByOtherApps. Currently this is a safe approximation. 131 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals( 132 searchResult.mOwningPackageName); 133 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 134 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; 135 136 if (primaryOrSplit && !isUsedByOtherApps) { 137 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps 138 // do not record it. This case does not bring any new usable information 139 // and can be safely skipped. 140 continue; 141 } 142 143 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, 144 // or UsedBytOtherApps), record will return true and we trigger an async write 145 // to disk to make sure we don't loose the data in case of a reboot. 146 if (mPackageDexUsage.record(searchResult.mOwningPackageName, 147 dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) { 148 mPackageDexUsage.maybeWriteAsync(); 149 } 150 } else { 151 // This can happen in a few situations: 152 // - bogus dex loads 153 // - recent installs/uninstalls that we didn't detect. 154 // - new installed splits 155 // If we can't find the owner of the dex we simply do not track it. The impact is 156 // that the dex file will not be considered for offline optimizations. 157 // TODO(calin): add hooks for move/uninstall notifications to 158 // capture package moves or obsolete packages. 159 if (DEBUG) { 160 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); 161 } 162 } 163 } 164 } 165 166 /** 167 * Read the dex usage from disk and populate the code cache locations. 168 * @param existingPackages a map containing information about what packages 169 * are available to what users. Only packages in this list will be 170 * recognized during notifyDexLoad(). 171 */ 172 public void load(Map<Integer, List<PackageInfo>> existingPackages) { 173 try { 174 loadInternal(existingPackages); 175 } catch (Exception e) { 176 mPackageDexUsage.clear(); 177 Slog.w(TAG, "Exception while loading package dex usage. " + 178 "Starting with a fresh state.", e); 179 } 180 } 181 182 public void notifyPackageInstalled(PackageInfo info, int userId) { 183 cachePackageCodeLocation(info, userId); 184 } 185 186 private void cachePackageCodeLocation(PackageInfo info, int userId) { 187 PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName); 188 if (pcl != null) { 189 pcl.mergeAppDataDirs(info.applicationInfo, userId); 190 } else { 191 mPackageCodeLocationsCache.put(info.packageName, 192 new PackageCodeLocations(info.applicationInfo, userId)); 193 } 194 } 195 196 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { 197 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 198 // Cache the code locations for the installed packages. This allows for 199 // faster lookups (no locks) when finding what package owns the dex file. 200 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { 201 List<PackageInfo> packageInfoList = entry.getValue(); 202 int userId = entry.getKey(); 203 for (PackageInfo pi : packageInfoList) { 204 // Cache the code locations. 205 cachePackageCodeLocation(pi, userId); 206 207 // Cache a map from package name to the set of user ids who installed the package. 208 // We will use it to sync the data and remove obsolete entries from 209 // mPackageDexUsage. 210 Set<Integer> users = putIfAbsent( 211 packageToUsersMap, pi.packageName, new HashSet<>()); 212 users.add(userId); 213 } 214 } 215 216 mPackageDexUsage.read(); 217 mPackageDexUsage.syncData(packageToUsersMap); 218 } 219 220 /** 221 * Get the package dex usage for the given package name. 222 * @return the package data or null if there is no data available for this package. 223 */ 224 public PackageUseInfo getPackageUseInfo(String packageName) { 225 return mPackageDexUsage.getPackageUseInfo(packageName); 226 } 227 228 /** 229 * Perform dexopt on the package {@code packageName} secondary dex files. 230 * @return true if all secondary dex files were processed successfully (compiled or skipped 231 * because they don't need to be compiled).. 232 */ 233 public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) { 234 // Select the dex optimizer based on the force parameter. 235 // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust 236 // the necessary dexopt flags to make sure that compilation is not skipped. This avoid 237 // passing the force flag through the multitude of layers. 238 // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to 239 // allocate an object here. 240 PackageDexOptimizer pdo = force 241 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) 242 : mPackageDexOptimizer; 243 PackageUseInfo useInfo = getPackageUseInfo(packageName); 244 if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { 245 if (DEBUG) { 246 Slog.d(TAG, "No secondary dex use for package:" + packageName); 247 } 248 // Nothing to compile, return true. 249 return true; 250 } 251 boolean success = true; 252 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 253 String dexPath = entry.getKey(); 254 DexUseInfo dexUseInfo = entry.getValue(); 255 PackageInfo pkg = null; 256 try { 257 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 258 dexUseInfo.getOwnerUserId()); 259 } catch (RemoteException e) { 260 throw new AssertionError(e); 261 } 262 // It may be that the package gets uninstalled while we try to compile its 263 // secondary dex files. If that's the case, just ignore. 264 // Note that we don't break the entire loop because the package might still be 265 // installed for other users. 266 if (pkg == null) { 267 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 268 + " for user " + dexUseInfo.getOwnerUserId()); 269 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); 270 continue; 271 } 272 int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, 273 dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps()); 274 success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); 275 } 276 return success; 277 } 278 279 /** 280 * Reconcile the information we have about the secondary dex files belonging to 281 * {@code packagName} and the actual dex files. For all dex files that were 282 * deleted, update the internal records and delete any generated oat files. 283 */ 284 public void reconcileSecondaryDexFiles(String packageName) { 285 PackageUseInfo useInfo = getPackageUseInfo(packageName); 286 if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { 287 if (DEBUG) { 288 Slog.d(TAG, "No secondary dex use for package:" + packageName); 289 } 290 // Nothing to reconcile. 291 return; 292 } 293 Set<String> dexFilesToRemove = new HashSet<>(); 294 boolean updated = false; 295 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 296 String dexPath = entry.getKey(); 297 DexUseInfo dexUseInfo = entry.getValue(); 298 PackageInfo pkg = null; 299 try { 300 // Note that we look for the package in the PackageManager just to be able 301 // to get back the real app uid and its storage kind. These are only used 302 // to perform extra validation in installd. 303 // TODO(calin): maybe a bit overkill. 304 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 305 dexUseInfo.getOwnerUserId()); 306 } catch (RemoteException ignore) { 307 // Can't happen, DexManager is local. 308 } 309 if (pkg == null) { 310 // It may be that the package was uninstalled while we process the secondary 311 // dex files. 312 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 313 + " for user " + dexUseInfo.getOwnerUserId()); 314 // Update the usage and continue, another user might still have the package. 315 updated = mPackageDexUsage.removeUserPackage( 316 packageName, dexUseInfo.getOwnerUserId()) || updated; 317 continue; 318 } 319 ApplicationInfo info = pkg.applicationInfo; 320 int flags = 0; 321 if (info.dataDir.equals(info.deviceProtectedDataDir)) { 322 flags |= StorageManager.FLAG_STORAGE_DE; 323 } else if (info.dataDir.equals(info.credentialProtectedDataDir)) { 324 flags |= StorageManager.FLAG_STORAGE_CE; 325 } else { 326 Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName); 327 updated = mPackageDexUsage.removeUserPackage( 328 packageName, dexUseInfo.getOwnerUserId()) || updated; 329 continue; 330 } 331 332 boolean dexStillExists = true; 333 synchronized(mInstallLock) { 334 try { 335 String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]); 336 dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName, 337 pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags); 338 } catch (InstallerException e) { 339 Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath + 340 " : " + e.getMessage()); 341 } 342 } 343 if (!dexStillExists) { 344 updated = mPackageDexUsage.removeDexFile( 345 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 346 } 347 348 } 349 if (updated) { 350 mPackageDexUsage.maybeWriteAsync(); 351 } 352 } 353 354 /** 355 * Return all packages that contain records of secondary dex files. 356 */ 357 public Set<String> getAllPackagesWithSecondaryDexFiles() { 358 return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); 359 } 360 361 /** 362 * Retrieves the package which owns the given dexPath. 363 */ 364 private DexSearchResult getDexPackage( 365 ApplicationInfo loadingAppInfo, String dexPath, int userId) { 366 // Ignore framework code. 367 // TODO(calin): is there a better way to detect it? 368 if (dexPath.startsWith("/system/framework/")) { 369 new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); 370 } 371 372 // First, check if the package which loads the dex file actually owns it. 373 // Most of the time this will be true and we can return early. 374 PackageCodeLocations loadingPackageCodeLocations = 375 new PackageCodeLocations(loadingAppInfo, userId); 376 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); 377 if (outcome != DEX_SEARCH_NOT_FOUND) { 378 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. 379 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); 380 } 381 382 // The loadingPackage does not own the dex file. 383 // Perform a reverse look-up in the cache to detect if any package has ownership. 384 // Note that we can have false negatives if the cache falls out of date. 385 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { 386 outcome = pcl.searchDex(dexPath, userId); 387 if (outcome != DEX_SEARCH_NOT_FOUND) { 388 return new DexSearchResult(pcl.mPackageName, outcome); 389 } 390 } 391 392 // Cache miss. Return not found for the moment. 393 // 394 // TODO(calin): this may be because of a newly installed package, an update 395 // or a new added user. We can either perform a full look up again or register 396 // observers to be notified of package/user updates. 397 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); 398 } 399 400 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { 401 V existingValue = map.putIfAbsent(key, newValue); 402 return existingValue == null ? newValue : existingValue; 403 } 404 405 /** 406 * Convenience class to store the different locations where a package might 407 * own code. 408 */ 409 private static class PackageCodeLocations { 410 private final String mPackageName; 411 private final String mBaseCodePath; 412 private final Set<String> mSplitCodePaths; 413 // Maps user id to the application private directory. 414 private final Map<Integer, Set<String>> mAppDataDirs; 415 416 public PackageCodeLocations(ApplicationInfo ai, int userId) { 417 mPackageName = ai.packageName; 418 mBaseCodePath = ai.sourceDir; 419 mSplitCodePaths = new HashSet<>(); 420 if (ai.splitSourceDirs != null) { 421 for (String split : ai.splitSourceDirs) { 422 mSplitCodePaths.add(split); 423 } 424 } 425 mAppDataDirs = new HashMap<>(); 426 mergeAppDataDirs(ai, userId); 427 } 428 429 public void mergeAppDataDirs(ApplicationInfo ai, int userId) { 430 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); 431 dataDirs.add(ai.dataDir); 432 } 433 434 public int searchDex(String dexPath, int userId) { 435 // First check that this package is installed or active for the given user. 436 // If we don't have a data dir it means this user is trying to load something 437 // unavailable for them. 438 Set<String> userDataDirs = mAppDataDirs.get(userId); 439 if (userDataDirs == null) { 440 Slog.w(TAG, "Trying to load a dex path which does not exist for the current " + 441 "user. dexPath=" + dexPath + ", userId=" + userId); 442 return DEX_SEARCH_NOT_FOUND; 443 } 444 445 if (mBaseCodePath.equals(dexPath)) { 446 return DEX_SEARCH_FOUND_PRIMARY; 447 } 448 if (mSplitCodePaths.contains(dexPath)) { 449 return DEX_SEARCH_FOUND_SPLIT; 450 } 451 for (String dataDir : userDataDirs) { 452 if (dexPath.startsWith(dataDir)) { 453 return DEX_SEARCH_FOUND_SECONDARY; 454 } 455 } 456 457 // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink, 458 // /data/data/ -> /data/user/0/. 459 if (DEBUG) { 460 try { 461 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); 462 if (dexPathReal != dexPath) { 463 Slog.d(TAG, "Dex loaded with symlink. dexPath=" + 464 dexPath + " dexPathReal=" + dexPathReal); 465 } 466 } catch (IOException e) { 467 // Ignore 468 } 469 } 470 return DEX_SEARCH_NOT_FOUND; 471 } 472 } 473 474 /** 475 * Convenience class to store ownership search results. 476 */ 477 private class DexSearchResult { 478 private String mOwningPackageName; 479 private int mOutcome; 480 481 public DexSearchResult(String owningPackageName, int outcome) { 482 this.mOwningPackageName = owningPackageName; 483 this.mOutcome = outcome; 484 } 485 486 @Override 487 public String toString() { 488 return mOwningPackageName + "-" + mOutcome; 489 } 490 } 491 492 493} 494