DexManager.java revision 0d4b8f8b0c963d9a1f5cb6aff11a11195a3df225
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.PackageInfo; 20import android.content.pm.PackageParser; 21import android.content.pm.ApplicationInfo; 22 23import android.util.Slog; 24 25import com.android.server.pm.PackageManagerServiceUtils; 26 27import java.io.File; 28import java.io.IOException; 29import java.util.List; 30import java.util.HashMap; 31import java.util.HashSet; 32import java.util.Map; 33import java.util.Set; 34 35/** 36 * This class keeps track of how dex files are used. 37 * Every time it gets a notification about a dex file being loaded it tracks 38 * its owning package and records it in PackageDexUsage (package-dex-usage.list). 39 * 40 * TODO(calin): Extract related dexopt functionality from PackageManagerService 41 * into this class. 42 */ 43public class DexManager { 44 private static final String TAG = "DexManager"; 45 46 private static final boolean DEBUG = false; 47 48 // Maps package name to code locations. 49 // It caches the code locations for the installed packages. This allows for 50 // faster lookups (no locks) when finding what package owns the dex file. 51 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; 52 53 // PackageDexUsage handles the actual I/O operations. It is responsible to 54 // encode and save the dex usage data. 55 private final PackageDexUsage mPackageDexUsage; 56 57 // Possible outcomes of a dex search. 58 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found 59 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk 60 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 61 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex 62 63 public DexManager() { 64 mPackageCodeLocationsCache = new HashMap<>(); 65 mPackageDexUsage = new PackageDexUsage(); 66 } 67 68 /** 69 * Notify about dex files loads. 70 * Note that this method is invoked when apps load dex files and it should 71 * return as fast as possible. 72 * 73 * @param loadingPackage the package performing the load 74 * @param dexPaths the list of dex files being loaded 75 * @param loaderIsa the ISA of the app loading the dex files 76 * @param loaderUserId the user id which runs the code loading the dex files 77 */ 78 public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths, 79 String loaderIsa, int loaderUserId) { 80 try { 81 notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId); 82 } catch (Exception e) { 83 Slog.w(TAG, "Exception while notifying dex load for package " + 84 loadingAppInfo.packageName, e); 85 } 86 } 87 88 private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths, 89 String loaderIsa, int loaderUserId) { 90 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 91 Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " + 92 loaderIsa + "?"); 93 return; 94 } 95 96 for (String dexPath : dexPaths) { 97 // Find the owning package name. 98 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); 99 100 if (DEBUG) { 101 Slog.i(TAG, loadingAppInfo.packageName 102 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); 103 } 104 105 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { 106 // TODO(calin): extend isUsedByOtherApps check to detect the cases where 107 // different apps share the same runtime. In that case we should not mark the dex 108 // file as isUsedByOtherApps. Currently this is a safe approximation. 109 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals( 110 searchResult.mOwningPackageName); 111 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 112 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; 113 114 if (primaryOrSplit && !isUsedByOtherApps) { 115 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps 116 // do not record it. This case does not bring any new usable information 117 // and can be safely skipped. 118 continue; 119 } 120 121 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, 122 // or UsedBytOtherApps), record will return true and we trigger an async write 123 // to disk to make sure we don't loose the data in case of a reboot. 124 if (mPackageDexUsage.record(searchResult.mOwningPackageName, 125 dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) { 126 mPackageDexUsage.maybeWriteAsync(); 127 } 128 } else { 129 // This can happen in a few situations: 130 // - bogus dex loads 131 // - recent installs/uninstalls that we didn't detect. 132 // - new installed splits 133 // If we can't find the owner of the dex we simply do not track it. The impact is 134 // that the dex file will not be considered for offline optimizations. 135 // TODO(calin): add hooks for move/uninstall notifications to 136 // capture package moves or obsolete packages. 137 if (DEBUG) { 138 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); 139 } 140 } 141 } 142 } 143 144 /** 145 * Read the dex usage from disk and populate the code cache locations. 146 * @param existingPackages a map containing information about what packages 147 * are available to what users. Only packages in this list will be 148 * recognized during notifyDexLoad(). 149 */ 150 public void load(Map<Integer, List<PackageInfo>> existingPackages) { 151 try { 152 loadInternal(existingPackages); 153 } catch (Exception e) { 154 mPackageDexUsage.clear(); 155 Slog.w(TAG, "Exception while loading package dex usage. " + 156 "Starting with a fresh state.", e); 157 } 158 } 159 160 public void notifyPackageInstalled(PackageInfo info, int userId) { 161 cachePackageCodeLocation(info, userId); 162 } 163 164 private void cachePackageCodeLocation(PackageInfo info, int userId) { 165 PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName); 166 if (pcl != null) { 167 pcl.mergeAppDataDirs(info.applicationInfo, userId); 168 } else { 169 mPackageCodeLocationsCache.put(info.packageName, 170 new PackageCodeLocations(info.applicationInfo, userId)); 171 } 172 } 173 174 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { 175 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 176 // Cache the code locations for the installed packages. This allows for 177 // faster lookups (no locks) when finding what package owns the dex file. 178 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { 179 List<PackageInfo> packageInfoList = entry.getValue(); 180 int userId = entry.getKey(); 181 for (PackageInfo pi : packageInfoList) { 182 // Cache the code locations. 183 cachePackageCodeLocation(pi, userId); 184 185 // Cache a map from package name to the set of user ids who installed the package. 186 // We will use it to sync the data and remove obsolete entries from 187 // mPackageDexUsage. 188 Set<Integer> users = putIfAbsent( 189 packageToUsersMap, pi.packageName, new HashSet<>()); 190 users.add(userId); 191 } 192 } 193 194 mPackageDexUsage.read(); 195 mPackageDexUsage.syncData(packageToUsersMap); 196 } 197 198 /** 199 * Get the package dex usage for the given package name. 200 * @return the package data or null if there is no data available for this package. 201 */ 202 public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) { 203 return mPackageDexUsage.getPackageUseInfo(packageName); 204 } 205 206 /** 207 * Retrieves the package which owns the given dexPath. 208 */ 209 private DexSearchResult getDexPackage( 210 ApplicationInfo loadingAppInfo, String dexPath, int userId) { 211 // Ignore framework code. 212 // TODO(calin): is there a better way to detect it? 213 if (dexPath.startsWith("/system/framework/")) { 214 new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); 215 } 216 217 // First, check if the package which loads the dex file actually owns it. 218 // Most of the time this will be true and we can return early. 219 PackageCodeLocations loadingPackageCodeLocations = 220 new PackageCodeLocations(loadingAppInfo, userId); 221 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); 222 if (outcome != DEX_SEARCH_NOT_FOUND) { 223 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. 224 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); 225 } 226 227 // The loadingPackage does not own the dex file. 228 // Perform a reverse look-up in the cache to detect if any package has ownership. 229 // Note that we can have false negatives if the cache falls out of date. 230 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { 231 outcome = pcl.searchDex(dexPath, userId); 232 if (outcome != DEX_SEARCH_NOT_FOUND) { 233 return new DexSearchResult(pcl.mPackageName, outcome); 234 } 235 } 236 237 // Cache miss. Return not found for the moment. 238 // 239 // TODO(calin): this may be because of a newly installed package, an update 240 // or a new added user. We can either perform a full look up again or register 241 // observers to be notified of package/user updates. 242 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); 243 } 244 245 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { 246 V existingValue = map.putIfAbsent(key, newValue); 247 return existingValue == null ? newValue : existingValue; 248 } 249 250 /** 251 * Convenience class to store the different locations where a package might 252 * own code. 253 */ 254 private static class PackageCodeLocations { 255 private final String mPackageName; 256 private final String mBaseCodePath; 257 private final Set<String> mSplitCodePaths; 258 // Maps user id to the application private directory. 259 private final Map<Integer, Set<String>> mAppDataDirs; 260 261 public PackageCodeLocations(ApplicationInfo ai, int userId) { 262 mPackageName = ai.packageName; 263 mBaseCodePath = ai.sourceDir; 264 mSplitCodePaths = new HashSet<>(); 265 if (ai.splitSourceDirs != null) { 266 for (String split : ai.splitSourceDirs) { 267 mSplitCodePaths.add(split); 268 } 269 } 270 mAppDataDirs = new HashMap<>(); 271 mergeAppDataDirs(ai, userId); 272 } 273 274 public void mergeAppDataDirs(ApplicationInfo ai, int userId) { 275 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); 276 dataDirs.add(ai.dataDir); 277 } 278 279 public int searchDex(String dexPath, int userId) { 280 // First check that this package is installed or active for the given user. 281 // If we don't have a data dir it means this user is trying to load something 282 // unavailable for them. 283 Set<String> userDataDirs = mAppDataDirs.get(userId); 284 if (userDataDirs == null) { 285 Slog.w(TAG, "Trying to load a dex path which does not exist for the current " + 286 "user. dexPath=" + dexPath + ", userId=" + userId); 287 return DEX_SEARCH_NOT_FOUND; 288 } 289 290 if (mBaseCodePath.equals(dexPath)) { 291 return DEX_SEARCH_FOUND_PRIMARY; 292 } 293 if (mSplitCodePaths.contains(dexPath)) { 294 return DEX_SEARCH_FOUND_SPLIT; 295 } 296 for (String dataDir : userDataDirs) { 297 if (dexPath.startsWith(dataDir)) { 298 return DEX_SEARCH_FOUND_SECONDARY; 299 } 300 } 301 302 // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink, 303 // /data/data/ -> /data/user/0/. 304 if (DEBUG) { 305 try { 306 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); 307 if (dexPathReal != dexPath) { 308 Slog.d(TAG, "Dex loaded with symlink. dexPath=" + 309 dexPath + " dexPathReal=" + dexPathReal); 310 } 311 } catch (IOException e) { 312 // Ignore 313 } 314 } 315 return DEX_SEARCH_NOT_FOUND; 316 } 317 } 318 319 /** 320 * Convenience class to store ownership search results. 321 */ 322 private class DexSearchResult { 323 private String mOwningPackageName; 324 private int mOutcome; 325 326 public DexSearchResult(String owningPackageName, int outcome) { 327 this.mOwningPackageName = owningPackageName; 328 this.mOutcome = outcome; 329 } 330 331 @Override 332 public String toString() { 333 return mOwningPackageName + "-" + mOutcome; 334 } 335 } 336 337 338} 339