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