blob: 6d06838cd24f4dab0f7a95be6aa4840e840715c1 [file] [log] [blame]
Calin Juravle5a9094c2016-12-16 16:22:00 +00001/*
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 install/uninstall notifications to
136 // capture new 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 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
161 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
162 // Cache the code locations for the installed packages. This allows for
163 // faster lookups (no locks) when finding what package owns the dex file.
164 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
165 List<PackageInfo> packageInfoList = entry.getValue();
166 int userId = entry.getKey();
167 for (PackageInfo pi : packageInfoList) {
168 // Cache the code locations.
169 PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName);
170 if (pcl != null) {
171 pcl.mergeAppDataDirs(pi.applicationInfo, userId);
172 } else {
173 mPackageCodeLocationsCache.put(pi.packageName,
174 new PackageCodeLocations(pi.applicationInfo, userId));
175 }
176 // Cache a map from package name to the set of user ids who installed the package.
177 // We will use it to sync the data and remove obsolete entries from
178 // mPackageDexUsage.
179 Set<Integer> users = putIfAbsent(
180 packageToUsersMap, pi.packageName, new HashSet<>());
181 users.add(userId);
182 }
183 }
184
185 mPackageDexUsage.read();
186 mPackageDexUsage.syncData(packageToUsersMap);
187 }
188
189 /**
190 * Get the package dex usage for the given package name.
191 * @return the package data or null if there is no data available for this package.
192 */
193 public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) {
194 return mPackageDexUsage.getPackageUseInfo(packageName);
195 }
196
197 /**
198 * Retrieves the package which owns the given dexPath.
199 */
200 private DexSearchResult getDexPackage(
201 ApplicationInfo loadingAppInfo, String dexPath, int userId) {
202 // Ignore framework code.
203 // TODO(calin): is there a better way to detect it?
204 if (dexPath.startsWith("/system/framework/")) {
205 new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
206 }
207
208 // First, check if the package which loads the dex file actually owns it.
209 // Most of the time this will be true and we can return early.
210 PackageCodeLocations loadingPackageCodeLocations =
211 new PackageCodeLocations(loadingAppInfo, userId);
212 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
213 if (outcome != DEX_SEARCH_NOT_FOUND) {
214 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
215 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
216 }
217
218 // The loadingPackage does not own the dex file.
219 // Perform a reverse look-up in the cache to detect if any package has ownership.
220 // Note that we can have false negatives if the cache falls out of date.
221 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
222 outcome = pcl.searchDex(dexPath, userId);
223 if (outcome != DEX_SEARCH_NOT_FOUND) {
224 return new DexSearchResult(pcl.mPackageName, outcome);
225 }
226 }
227
228 // Cache miss. Return not found for the moment.
229 //
230 // TODO(calin): this may be because of a newly installed package, an update
231 // or a new added user. We can either perform a full look up again or register
232 // observers to be notified of package/user updates.
233 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
234 }
235
236 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
237 V existingValue = map.putIfAbsent(key, newValue);
238 return existingValue == null ? newValue : existingValue;
239 }
240
241 /**
242 * Convenience class to store the different locations where a package might
243 * own code.
244 */
245 private static class PackageCodeLocations {
246 private final String mPackageName;
247 private final String mBaseCodePath;
248 private final Set<String> mSplitCodePaths;
249 // Maps user id to the application private directory.
250 private final Map<Integer, Set<String>> mAppDataDirs;
251
252 public PackageCodeLocations(ApplicationInfo ai, int userId) {
253 mPackageName = ai.packageName;
254 mBaseCodePath = ai.sourceDir;
255 mSplitCodePaths = new HashSet<>();
256 if (ai.splitSourceDirs != null) {
257 for (String split : ai.splitSourceDirs) {
258 mSplitCodePaths.add(split);
259 }
260 }
261 mAppDataDirs = new HashMap<>();
262 mergeAppDataDirs(ai, userId);
263 }
264
265 public void mergeAppDataDirs(ApplicationInfo ai, int userId) {
266 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
267 dataDirs.add(ai.dataDir);
Calin Juravle5a9094c2016-12-16 16:22:00 +0000268 }
269
270 public int searchDex(String dexPath, int userId) {
271 // First check that this package is installed or active for the given user.
272 // If we don't have a data dir it means this user is trying to load something
273 // unavailable for them.
274 Set<String> userDataDirs = mAppDataDirs.get(userId);
275 if (userDataDirs == null) {
276 Slog.w(TAG, "Trying to load a dex path which does not exist for the current " +
277 "user. dexPath=" + dexPath + ", userId=" + userId);
278 return DEX_SEARCH_NOT_FOUND;
279 }
280
281 if (mBaseCodePath.equals(dexPath)) {
282 return DEX_SEARCH_FOUND_PRIMARY;
283 }
284 if (mSplitCodePaths.contains(dexPath)) {
285 return DEX_SEARCH_FOUND_SPLIT;
286 }
287 for (String dataDir : userDataDirs) {
288 if (dexPath.startsWith(dataDir)) {
289 return DEX_SEARCH_FOUND_SECONDARY;
290 }
291 }
Calin Juravleb96dba92016-12-22 18:47:05 +0200292
293 // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink,
294 // /data/data/ -> /data/user/0/.
295 if (DEBUG) {
296 try {
297 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
298 if (dexPathReal != dexPath) {
299 Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
300 dexPath + " dexPathReal=" + dexPathReal);
301 }
302 } catch (IOException e) {
303 // Ignore
304 }
305 }
Calin Juravle5a9094c2016-12-16 16:22:00 +0000306 return DEX_SEARCH_NOT_FOUND;
307 }
308 }
309
310 /**
311 * Convenience class to store ownership search results.
312 */
313 private class DexSearchResult {
314 private String mOwningPackageName;
315 private int mOutcome;
316
317 public DexSearchResult(String owningPackageName, int outcome) {
318 this.mOwningPackageName = owningPackageName;
319 this.mOutcome = outcome;
320 }
321
322 @Override
323 public String toString() {
324 return mOwningPackageName + "-" + mOutcome;
325 }
326 }
327
328
329}