DexManager.java revision 943d1e76542bac686bb1164cb792f59f844eaf56
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.os.FileUtils; 23import android.os.RemoteException; 24import android.os.storage.StorageManager; 25import android.os.UserHandle; 26 27import android.util.Slog; 28 29import com.android.internal.annotations.GuardedBy; 30import com.android.server.pm.Installer; 31import com.android.server.pm.Installer.InstallerException; 32import com.android.server.pm.PackageDexOptimizer; 33import com.android.server.pm.PackageManagerService; 34import com.android.server.pm.PackageManagerServiceUtils; 35import com.android.server.pm.PackageManagerServiceCompilerMapping; 36 37import java.io.File; 38import java.io.IOException; 39import java.util.Collection; 40import java.util.Collections; 41import java.util.List; 42import java.util.HashMap; 43import java.util.HashSet; 44import java.util.Map; 45import java.util.Set; 46 47import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; 48import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; 49import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; 50 51/** 52 * This class keeps track of how dex files are used. 53 * Every time it gets a notification about a dex file being loaded it tracks 54 * its owning package and records it in PackageDexUsage (package-dex-usage.list). 55 * 56 * TODO(calin): Extract related dexopt functionality from PackageManagerService 57 * into this class. 58 */ 59public class DexManager { 60 private static final String TAG = "DexManager"; 61 62 private static final boolean DEBUG = false; 63 64 // Maps package name to code locations. 65 // It caches the code locations for the installed packages. This allows for 66 // faster lookups (no locks) when finding what package owns the dex file. 67 @GuardedBy("mPackageCodeLocationsCache") 68 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; 69 70 // PackageDexUsage handles the actual I/O operations. It is responsible to 71 // encode and save the dex usage data. 72 private final PackageDexUsage mPackageDexUsage; 73 74 private final IPackageManager mPackageManager; 75 private final PackageDexOptimizer mPackageDexOptimizer; 76 private final Object mInstallLock; 77 @GuardedBy("mInstallLock") 78 private final Installer mInstaller; 79 80 // Possible outcomes of a dex search. 81 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found 82 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk 83 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 84 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex 85 86 /** 87 * We do not record packages that have no secondary dex files or that are not used by other 88 * apps. This is an optimization to reduce the amount of data that needs to be written to 89 * disk (apps will not usually be shared so this trims quite a bit the number we record). 90 * 91 * To make this behaviour transparent to the callers which need use information on packages, 92 * DexManager will return this DEFAULT instance from 93 * {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and 94 * is marked as not being used by other apps. This reflects the intended behaviour when we don't 95 * find the package in the underlying data file. 96 */ 97 private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo(); 98 99 public DexManager(IPackageManager pms, PackageDexOptimizer pdo, 100 Installer installer, Object installLock) { 101 mPackageCodeLocationsCache = new HashMap<>(); 102 mPackageDexUsage = new PackageDexUsage(); 103 mPackageManager = pms; 104 mPackageDexOptimizer = pdo; 105 mInstaller = installer; 106 mInstallLock = installLock; 107 } 108 109 /** 110 * Notify about dex files loads. 111 * Note that this method is invoked when apps load dex files and it should 112 * return as fast as possible. 113 * 114 * @param loadingAppInfo the package performing the load 115 * @param classLoadersNames the names of the class loaders present in the loading chain. The 116 * list encodes the class loader chain in the natural order. The first class loader has 117 * the second one as its parent and so on. The dex files present in the class path of the 118 * first class loader will be recorded in the usage file. 119 * @param classPaths the class paths corresponding to the class loaders names from 120 * {@param classLoadersNames}. The the first element corresponds to the first class loader 121 * and so on. A classpath is represented as a list of dex files separated by 122 * {@code File.pathSeparator}. 123 * The dex files found in the first class path will be recorded in the usage file. 124 * @param loaderIsa the ISA of the app loading the dex files 125 * @param loaderUserId the user id which runs the code loading the dex files 126 */ 127 public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames, 128 List<String> classPaths, String loaderIsa, int loaderUserId) { 129 try { 130 notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa, 131 loaderUserId); 132 } catch (Exception e) { 133 Slog.w(TAG, "Exception while notifying dex load for package " + 134 loadingAppInfo.packageName, e); 135 } 136 } 137 138 private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, 139 List<String> classLoaderNames, List<String> classPaths, String loaderIsa, 140 int loaderUserId) { 141 if (classLoaderNames.size() != classPaths.size()) { 142 Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size"); 143 return; 144 } 145 if (classLoaderNames.isEmpty()) { 146 Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty"); 147 return; 148 } 149 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 150 Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " + 151 loaderIsa + "?"); 152 return; 153 } 154 155 // The classpath is represented as a list of dex files separated by File.pathSeparator. 156 String[] dexPathsToRegister = classPaths.get(0).split(File.pathSeparator); 157 158 // Encode the class loader contexts for the dexPathsToRegister. 159 String[] classLoaderContexts = DexoptUtils.processContextForDexLoad( 160 classLoaderNames, classPaths); 161 162 int dexPathIndex = 0; 163 for (String dexPath : dexPathsToRegister) { 164 // Find the owning package name. 165 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); 166 167 if (DEBUG) { 168 Slog.i(TAG, loadingAppInfo.packageName 169 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); 170 } 171 172 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { 173 // TODO(calin): extend isUsedByOtherApps check to detect the cases where 174 // different apps share the same runtime. In that case we should not mark the dex 175 // file as isUsedByOtherApps. Currently this is a safe approximation. 176 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals( 177 searchResult.mOwningPackageName); 178 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 179 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; 180 181 if (primaryOrSplit && !isUsedByOtherApps) { 182 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps 183 // do not record it. This case does not bring any new usable information 184 // and can be safely skipped. 185 continue; 186 } 187 188 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, 189 // or UsedBytOtherApps), record will return true and we trigger an async write 190 // to disk to make sure we don't loose the data in case of a reboot. 191 192 // A null classLoaderContexts means that there are unsupported class loaders in the 193 // chain. 194 String classLoaderContext = classLoaderContexts == null 195 ? PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT 196 : classLoaderContexts[dexPathIndex]; 197 if (mPackageDexUsage.record(searchResult.mOwningPackageName, 198 dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, 199 loadingAppInfo.packageName, classLoaderContext)) { 200 mPackageDexUsage.maybeWriteAsync(); 201 } 202 } else { 203 // If we can't find the owner of the dex we simply do not track it. The impact is 204 // that the dex file will not be considered for offline optimizations. 205 if (DEBUG) { 206 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); 207 } 208 } 209 dexPathIndex++; 210 } 211 } 212 213 /** 214 * Read the dex usage from disk and populate the code cache locations. 215 * @param existingPackages a map containing information about what packages 216 * are available to what users. Only packages in this list will be 217 * recognized during notifyDexLoad(). 218 */ 219 public void load(Map<Integer, List<PackageInfo>> existingPackages) { 220 try { 221 loadInternal(existingPackages); 222 } catch (Exception e) { 223 mPackageDexUsage.clear(); 224 Slog.w(TAG, "Exception while loading package dex usage. " + 225 "Starting with a fresh state.", e); 226 } 227 } 228 229 /** 230 * Notifies that a new package was installed for {@code userId}. 231 * {@code userId} must not be {@code UserHandle.USER_ALL}. 232 * 233 * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}. 234 */ 235 public void notifyPackageInstalled(PackageInfo pi, int userId) { 236 if (userId == UserHandle.USER_ALL) { 237 throw new IllegalArgumentException( 238 "notifyPackageInstalled called with USER_ALL"); 239 } 240 cachePackageInfo(pi, userId); 241 } 242 243 /** 244 * Notifies that package {@code packageName} was updated. 245 * This will clear the UsedByOtherApps mark if it exists. 246 */ 247 public void notifyPackageUpdated(String packageName, String baseCodePath, 248 String[] splitCodePaths) { 249 cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1); 250 // In case there was an update, write the package use info to disk async. 251 // Note that we do the writing here and not in PackageDexUsage in order to be 252 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs 253 // multiple updates in PackageDexUsage before writing it). 254 if (mPackageDexUsage.clearUsedByOtherApps(packageName)) { 255 mPackageDexUsage.maybeWriteAsync(); 256 } 257 } 258 259 /** 260 * Notifies that the user {@code userId} data for package {@code packageName} 261 * was destroyed. This will remove all usage info associated with the package 262 * for the given user. 263 * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case 264 * all usage information for the package will be removed. 265 */ 266 public void notifyPackageDataDestroyed(String packageName, int userId) { 267 boolean updated = userId == UserHandle.USER_ALL 268 ? mPackageDexUsage.removePackage(packageName) 269 : mPackageDexUsage.removeUserPackage(packageName, userId); 270 // In case there was an update, write the package use info to disk async. 271 // Note that we do the writing here and not in PackageDexUsage in order to be 272 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs 273 // multiple updates in PackageDexUsage before writing it). 274 if (updated) { 275 mPackageDexUsage.maybeWriteAsync(); 276 } 277 } 278 279 /** 280 * Caches the code location from the given package info. 281 */ 282 private void cachePackageInfo(PackageInfo pi, int userId) { 283 ApplicationInfo ai = pi.applicationInfo; 284 String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir, 285 ai.credentialProtectedDataDir}; 286 cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs, 287 dataDirs, userId); 288 } 289 290 private void cachePackageCodeLocation(String packageName, String baseCodePath, 291 String[] splitCodePaths, String[] dataDirs, int userId) { 292 synchronized (mPackageCodeLocationsCache) { 293 PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName, 294 new PackageCodeLocations(packageName, baseCodePath, splitCodePaths)); 295 // TODO(calin): We are forced to extend the scope of this synchronization because 296 // the values of the cache (PackageCodeLocations) are updated in place. 297 // Make PackageCodeLocations immutable to simplify the synchronization reasoning. 298 pcl.updateCodeLocation(baseCodePath, splitCodePaths); 299 if (dataDirs != null) { 300 for (String dataDir : dataDirs) { 301 // The set of data dirs includes deviceProtectedDataDir and 302 // credentialProtectedDataDir which might be null for shared 303 // libraries. Currently we don't track these but be lenient 304 // and check in case we ever decide to store their usage data. 305 if (dataDir != null) { 306 pcl.mergeAppDataDirs(dataDir, userId); 307 } 308 } 309 } 310 } 311 } 312 313 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { 314 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 315 Map<String, Set<String>> packageToCodePaths = new HashMap<>(); 316 317 // Cache the code locations for the installed packages. This allows for 318 // faster lookups (no locks) when finding what package owns the dex file. 319 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { 320 List<PackageInfo> packageInfoList = entry.getValue(); 321 int userId = entry.getKey(); 322 for (PackageInfo pi : packageInfoList) { 323 // Cache the code locations. 324 cachePackageInfo(pi, userId); 325 326 // Cache two maps: 327 // - from package name to the set of user ids who installed the package. 328 // - from package name to the set of code paths. 329 // We will use it to sync the data and remove obsolete entries from 330 // mPackageDexUsage. 331 Set<Integer> users = putIfAbsent( 332 packageToUsersMap, pi.packageName, new HashSet<>()); 333 users.add(userId); 334 335 Set<String> codePaths = putIfAbsent( 336 packageToCodePaths, pi.packageName, new HashSet<>()); 337 codePaths.add(pi.applicationInfo.sourceDir); 338 if (pi.applicationInfo.splitSourceDirs != null) { 339 Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs); 340 } 341 } 342 } 343 344 mPackageDexUsage.read(); 345 mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); 346 } 347 348 /** 349 * Get the package dex usage for the given package name. 350 * If there is no usage info the method will return a default {@code PackageUseInfo} with 351 * no data about secondary dex files and marked as not being used by other apps. 352 * 353 * Note that no use info means the package was not used or it was used but not by other apps. 354 * Also, note that right now we might prune packages which are not used by other apps. 355 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try 356 * to access the package use. 357 */ 358 public PackageUseInfo getPackageUseInfoOrDefault(String packageName) { 359 PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName); 360 return useInfo == null ? DEFAULT_USE_INFO : useInfo; 361 } 362 363 /** 364 * Return whether or not the manager has usage information on the give package. 365 * 366 * Note that no use info means the package was not used or it was used but not by other apps. 367 * Also, note that right now we might prune packages which are not used by other apps. 368 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try 369 * to access the package use. 370 */ 371 /*package*/ boolean hasInfoOnPackage(String packageName) { 372 return mPackageDexUsage.getPackageUseInfo(packageName) != null; 373 } 374 375 /** 376 * Perform dexopt on with the given {@code options} on the secondary dex files. 377 * @return true if all secondary dex files were processed successfully (compiled or skipped 378 * because they don't need to be compiled).. 379 */ 380 public boolean dexoptSecondaryDex(DexoptOptions options) { 381 // Select the dex optimizer based on the force parameter. 382 // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust 383 // the necessary dexopt flags to make sure that compilation is not skipped. This avoid 384 // passing the force flag through the multitude of layers. 385 // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to 386 // allocate an object here. 387 PackageDexOptimizer pdo = options.isForce() 388 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) 389 : mPackageDexOptimizer; 390 String packageName = options.getPackageName(); 391 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); 392 if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { 393 if (DEBUG) { 394 Slog.d(TAG, "No secondary dex use for package:" + packageName); 395 } 396 // Nothing to compile, return true. 397 return true; 398 } 399 boolean success = true; 400 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 401 String dexPath = entry.getKey(); 402 DexUseInfo dexUseInfo = entry.getValue(); 403 404 PackageInfo pkg; 405 try { 406 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 407 dexUseInfo.getOwnerUserId()); 408 } catch (RemoteException e) { 409 throw new AssertionError(e); 410 } 411 // It may be that the package gets uninstalled while we try to compile its 412 // secondary dex files. If that's the case, just ignore. 413 // Note that we don't break the entire loop because the package might still be 414 // installed for other users. 415 if (pkg == null) { 416 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 417 + " for user " + dexUseInfo.getOwnerUserId()); 418 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); 419 continue; 420 } 421 422 int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, 423 dexUseInfo, options); 424 success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); 425 } 426 return success; 427 } 428 429 /** 430 * Reconcile the information we have about the secondary dex files belonging to 431 * {@code packagName} and the actual dex files. For all dex files that were 432 * deleted, update the internal records and delete any generated oat files. 433 */ 434 public void reconcileSecondaryDexFiles(String packageName) { 435 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); 436 if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { 437 if (DEBUG) { 438 Slog.d(TAG, "No secondary dex use for package:" + packageName); 439 } 440 // Nothing to reconcile. 441 return; 442 } 443 444 boolean updated = false; 445 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 446 String dexPath = entry.getKey(); 447 DexUseInfo dexUseInfo = entry.getValue(); 448 PackageInfo pkg = null; 449 try { 450 // Note that we look for the package in the PackageManager just to be able 451 // to get back the real app uid and its storage kind. These are only used 452 // to perform extra validation in installd. 453 // TODO(calin): maybe a bit overkill. 454 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 455 dexUseInfo.getOwnerUserId()); 456 } catch (RemoteException ignore) { 457 // Can't happen, DexManager is local. 458 } 459 if (pkg == null) { 460 // It may be that the package was uninstalled while we process the secondary 461 // dex files. 462 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 463 + " for user " + dexUseInfo.getOwnerUserId()); 464 // Update the usage and continue, another user might still have the package. 465 updated = mPackageDexUsage.removeUserPackage( 466 packageName, dexUseInfo.getOwnerUserId()) || updated; 467 continue; 468 } 469 ApplicationInfo info = pkg.applicationInfo; 470 int flags = 0; 471 if (info.deviceProtectedDataDir != null && 472 FileUtils.contains(info.deviceProtectedDataDir, dexPath)) { 473 flags |= StorageManager.FLAG_STORAGE_DE; 474 } else if (info.credentialProtectedDataDir!= null && 475 FileUtils.contains(info.credentialProtectedDataDir, dexPath)) { 476 flags |= StorageManager.FLAG_STORAGE_CE; 477 } else { 478 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath); 479 updated = mPackageDexUsage.removeDexFile( 480 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 481 continue; 482 } 483 484 boolean dexStillExists = true; 485 synchronized(mInstallLock) { 486 try { 487 String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]); 488 dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName, 489 pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags); 490 } catch (InstallerException e) { 491 Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath + 492 " : " + e.getMessage()); 493 } 494 } 495 if (!dexStillExists) { 496 updated = mPackageDexUsage.removeDexFile( 497 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 498 } 499 500 } 501 if (updated) { 502 mPackageDexUsage.maybeWriteAsync(); 503 } 504 } 505 506 // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the 507 // compilation happening here will use a pessimistic context. 508 public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, 509 boolean isUsedByOtherApps, int userId) { 510 // Find the owning package record. 511 DexSearchResult searchResult = getDexPackage(info, dexPath, userId); 512 513 if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) { 514 return new RegisterDexModuleResult(false, "Package not found"); 515 } 516 if (!info.packageName.equals(searchResult.mOwningPackageName)) { 517 return new RegisterDexModuleResult(false, "Dex path does not belong to package"); 518 } 519 if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 520 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) { 521 return new RegisterDexModuleResult(false, "Main apks cannot be registered"); 522 } 523 524 // We found the package. Now record the usage for all declared ISAs. 525 boolean update = false; 526 for (String isa : getAppDexInstructionSets(info)) { 527 boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, 528 dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false, 529 searchResult.mOwningPackageName, 530 PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT); 531 update |= newUpdate; 532 } 533 if (update) { 534 mPackageDexUsage.maybeWriteAsync(); 535 } 536 537 // Try to optimize the package according to the install reason. 538 String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason( 539 PackageManagerService.REASON_INSTALL); 540 DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName) 541 .getDexUseInfoMap().get(dexPath); 542 543 DexoptOptions options = new DexoptOptions(info.packageName, compilerFilter, /*flags*/0); 544 545 int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo, 546 options); 547 548 // If we fail to optimize the package log an error but don't propagate the error 549 // back to the app. The app cannot do much about it and the background job 550 // will rety again when it executes. 551 // TODO(calin): there might be some value to return the error here but it may 552 // cause red herrings since that doesn't mean the app cannot use the module. 553 if (result != PackageDexOptimizer.DEX_OPT_FAILED) { 554 Slog.e(TAG, "Failed to optimize dex module " + dexPath); 555 } 556 return new RegisterDexModuleResult(true, "Dex module registered successfully"); 557 } 558 559 /** 560 * Return all packages that contain records of secondary dex files. 561 */ 562 public Set<String> getAllPackagesWithSecondaryDexFiles() { 563 return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); 564 } 565 566 /** 567 * Retrieves the package which owns the given dexPath. 568 */ 569 private DexSearchResult getDexPackage( 570 ApplicationInfo loadingAppInfo, String dexPath, int userId) { 571 // Ignore framework code. 572 // TODO(calin): is there a better way to detect it? 573 if (dexPath.startsWith("/system/framework/")) { 574 return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); 575 } 576 577 // First, check if the package which loads the dex file actually owns it. 578 // Most of the time this will be true and we can return early. 579 PackageCodeLocations loadingPackageCodeLocations = 580 new PackageCodeLocations(loadingAppInfo, userId); 581 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); 582 if (outcome != DEX_SEARCH_NOT_FOUND) { 583 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. 584 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); 585 } 586 587 // The loadingPackage does not own the dex file. 588 // Perform a reverse look-up in the cache to detect if any package has ownership. 589 // Note that we can have false negatives if the cache falls out of date. 590 synchronized (mPackageCodeLocationsCache) { 591 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { 592 outcome = pcl.searchDex(dexPath, userId); 593 if (outcome != DEX_SEARCH_NOT_FOUND) { 594 return new DexSearchResult(pcl.mPackageName, outcome); 595 } 596 } 597 } 598 599 if (DEBUG) { 600 // TODO(calin): Consider checking for /data/data symlink. 601 // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps 602 // to load dex files through it. 603 try { 604 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); 605 if (dexPathReal != dexPath) { 606 Slog.d(TAG, "Dex loaded with symlink. dexPath=" + 607 dexPath + " dexPathReal=" + dexPathReal); 608 } 609 } catch (IOException e) { 610 // Ignore 611 } 612 } 613 // Cache miss. The cache is updated during installs and uninstalls, 614 // so if we get here we're pretty sure the dex path does not exist. 615 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); 616 } 617 618 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { 619 V existingValue = map.putIfAbsent(key, newValue); 620 return existingValue == null ? newValue : existingValue; 621 } 622 623 /** 624 * Writes the in-memory package dex usage to disk right away. 625 */ 626 public void writePackageDexUsageNow() { 627 mPackageDexUsage.writeNow(); 628 } 629 630 public static class RegisterDexModuleResult { 631 public RegisterDexModuleResult() { 632 this(false, null); 633 } 634 635 public RegisterDexModuleResult(boolean success, String message) { 636 this.success = success; 637 this.message = message; 638 } 639 640 public final boolean success; 641 public final String message; 642 } 643 644 /** 645 * Convenience class to store the different locations where a package might 646 * own code. 647 */ 648 private static class PackageCodeLocations { 649 private final String mPackageName; 650 private String mBaseCodePath; 651 private final Set<String> mSplitCodePaths; 652 // Maps user id to the application private directory. 653 private final Map<Integer, Set<String>> mAppDataDirs; 654 655 public PackageCodeLocations(ApplicationInfo ai, int userId) { 656 this(ai.packageName, ai.sourceDir, ai.splitSourceDirs); 657 mergeAppDataDirs(ai.dataDir, userId); 658 } 659 public PackageCodeLocations(String packageName, String baseCodePath, 660 String[] splitCodePaths) { 661 mPackageName = packageName; 662 mSplitCodePaths = new HashSet<>(); 663 mAppDataDirs = new HashMap<>(); 664 updateCodeLocation(baseCodePath, splitCodePaths); 665 } 666 667 public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) { 668 mBaseCodePath = baseCodePath; 669 mSplitCodePaths.clear(); 670 if (splitCodePaths != null) { 671 for (String split : splitCodePaths) { 672 mSplitCodePaths.add(split); 673 } 674 } 675 } 676 677 public void mergeAppDataDirs(String dataDir, int userId) { 678 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); 679 dataDirs.add(dataDir); 680 } 681 682 public int searchDex(String dexPath, int userId) { 683 // First check that this package is installed or active for the given user. 684 // A missing data dir means the package is not installed. 685 Set<String> userDataDirs = mAppDataDirs.get(userId); 686 if (userDataDirs == null) { 687 return DEX_SEARCH_NOT_FOUND; 688 } 689 690 if (mBaseCodePath.equals(dexPath)) { 691 return DEX_SEARCH_FOUND_PRIMARY; 692 } 693 if (mSplitCodePaths.contains(dexPath)) { 694 return DEX_SEARCH_FOUND_SPLIT; 695 } 696 for (String dataDir : userDataDirs) { 697 if (dexPath.startsWith(dataDir)) { 698 return DEX_SEARCH_FOUND_SECONDARY; 699 } 700 } 701 702 return DEX_SEARCH_NOT_FOUND; 703 } 704 } 705 706 /** 707 * Convenience class to store ownership search results. 708 */ 709 private class DexSearchResult { 710 private String mOwningPackageName; 711 private int mOutcome; 712 713 public DexSearchResult(String owningPackageName, int outcome) { 714 this.mOwningPackageName = owningPackageName; 715 this.mOutcome = outcome; 716 } 717 718 @Override 719 public String toString() { 720 return mOwningPackageName + "-" + mOutcome; 721 } 722 } 723} 724