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