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