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