blob: 00f3711c7038725c0c033026f5215e82a269031a [file] [log] [blame]
Calin Juravleb8976d82016-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
Calin Juravlec22c30e2017-01-16 19:18:48 -080019import android.content.pm.ApplicationInfo;
20import android.content.pm.IPackageManager;
Calin Juravleb8976d82016-12-16 16:22:00 +000021import android.content.pm.PackageInfo;
22import android.content.pm.PackageParser;
Calin Juravlec22c30e2017-01-16 19:18:48 -080023import android.os.RemoteException;
Calin Juravle1aa5f882017-01-25 01:05:50 -080024import android.os.storage.StorageManager;
25
Calin Juravleb8976d82016-12-16 16:22:00 +000026import android.util.Slog;
27
Calin Juravle1aa5f882017-01-25 01:05:50 -080028import com.android.internal.annotations.GuardedBy;
29import com.android.server.pm.Installer;
30import com.android.server.pm.Installer.InstallerException;
Calin Juravlec22c30e2017-01-16 19:18:48 -080031import com.android.server.pm.PackageDexOptimizer;
Calin Juravleb8976d82016-12-16 16:22:00 +000032import com.android.server.pm.PackageManagerServiceUtils;
Calin Juravlec22c30e2017-01-16 19:18:48 -080033import com.android.server.pm.PackageManagerServiceCompilerMapping;
Calin Juravleb8976d82016-12-16 16:22:00 +000034
35import java.io.File;
36import java.io.IOException;
37import java.util.List;
38import java.util.HashMap;
39import java.util.HashSet;
40import java.util.Map;
41import java.util.Set;
42
Calin Juravlec22c30e2017-01-16 19:18:48 -080043import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
44import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
45
Calin Juravleb8976d82016-12-16 16:22:00 +000046/**
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 */
54public 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 Juravlec22c30e2017-01-16 19:18:48 -080068 private final IPackageManager mPackageManager;
69 private final PackageDexOptimizer mPackageDexOptimizer;
Calin Juravle1aa5f882017-01-25 01:05:50 -080070 private final Object mInstallLock;
71 @GuardedBy("mInstallLock")
72 private final Installer mInstaller;
Calin Juravlec22c30e2017-01-16 19:18:48 -080073
Calin Juravleb8976d82016-12-16 16:22:00 +000074 // 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 Juravle1aa5f882017-01-25 01:05:50 -080080 public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
81 Installer installer, Object installLock) {
Calin Juravleb8976d82016-12-16 16:22:00 +000082 mPackageCodeLocationsCache = new HashMap<>();
83 mPackageDexUsage = new PackageDexUsage();
Calin Juravlec22c30e2017-01-16 19:18:48 -080084 mPackageManager = pms;
85 mPackageDexOptimizer = pdo;
Calin Juravle1aa5f882017-01-25 01:05:50 -080086 mInstaller = installer;
87 mInstallLock = installLock;
Calin Juravleb8976d82016-12-16 16:22:00 +000088 }
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 Juravle0d4b8f82017-01-23 23:34:25 -0800157 // TODO(calin): add hooks for move/uninstall notifications to
158 // capture package moves or obsolete packages.
Calin Juravleb8976d82016-12-16 16:22:00 +0000159 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 Juravle0d4b8f82017-01-23 23:34:25 -0800182 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 Juravleb8976d82016-12-16 16:22:00 +0000196 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 Juravle0d4b8f82017-01-23 23:34:25 -0800205 cachePackageCodeLocation(pi, userId);
206
Calin Juravleb8976d82016-12-16 16:22:00 +0000207 // 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 Juravlec22c30e2017-01-16 19:18:48 -0800224 public PackageUseInfo getPackageUseInfo(String packageName) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000225 return mPackageDexUsage.getPackageUseInfo(packageName);
226 }
227
228 /**
Calin Juravlec22c30e2017-01-16 19:18:48 -0800229 * 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 Juravle1aa5f882017-01-25 01:05:50 -0800269 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
Calin Juravlec22c30e2017-01-16 19:18:48 -0800270 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 Juravle1aa5f882017-01-25 01:05:50 -0800280 * 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 Juravleb1097412017-01-26 18:53:23 -0800294 boolean updated = false;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800295 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 Juravleb1097412017-01-26 18:53:23 -0800315 updated = mPackageDexUsage.removeUserPackage(
316 packageName, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800317 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 Juravleb1097412017-01-26 18:53:23 -0800327 updated = mPackageDexUsage.removeUserPackage(
328 packageName, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800329 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 Juravleb1097412017-01-26 18:53:23 -0800344 updated = mPackageDexUsage.removeDexFile(
345 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800346 }
Calin Juravleb1097412017-01-26 18:53:23 -0800347
Calin Juravle1aa5f882017-01-25 01:05:50 -0800348 }
Calin Juravleb1097412017-01-26 18:53:23 -0800349 if (updated) {
350 mPackageDexUsage.maybeWriteAsync();
Calin Juravle1aa5f882017-01-25 01:05:50 -0800351 }
352 }
353
354 /**
Calin Juravle51f521c2017-01-25 18:00:05 -0800355 * Return all packages that contain records of secondary dex files.
356 */
357 public Set<String> getAllPackagesWithSecondaryDexFiles() {
358 return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
Calin Juravleb8976d82016-12-16 16:22:00 +0000359 }
360
361 /**
Calin Juravleb8976d82016-12-16 16:22:00 +0000362 * 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 Juravlec0662052016-12-22 18:47:05 +0200456
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 Juravleb8976d82016-12-16 16:22:00 +0000470 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}