• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
19 import static android.provider.DeviceConfig.NAMESPACE_DEX_BOOT;
20 
21 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
22 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
23 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
24 
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.IPackageManager;
28 import android.content.pm.PackageInfo;
29 import android.os.FileUtils;
30 import android.os.RemoteException;
31 import android.os.SystemProperties;
32 import android.os.UserHandle;
33 import android.os.storage.StorageManager;
34 import android.provider.DeviceConfig;
35 import android.util.Log;
36 import android.util.Slog;
37 import android.util.jar.StrictJarFile;
38 
39 import com.android.internal.annotations.GuardedBy;
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.server.pm.Installer;
42 import com.android.server.pm.Installer.InstallerException;
43 import com.android.server.pm.PackageDexOptimizer;
44 import com.android.server.pm.PackageManagerService;
45 import com.android.server.pm.PackageManagerServiceUtils;
46 
47 import java.io.File;
48 import java.io.IOException;
49 import java.util.Arrays;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.Iterator;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.zip.ZipEntry;
59 
60 /**
61  * This class keeps track of how dex files are used.
62  * Every time it gets a notification about a dex file being loaded it tracks
63  * its owning package and records it in PackageDexUsage (package-dex-usage.list).
64  *
65  * TODO(calin): Extract related dexopt functionality from PackageManagerService
66  * into this class.
67  */
68 public class DexManager {
69     private static final String TAG = "DexManager";
70 
71     private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
72     private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST =
73             "pm.dexopt.priv-apps-oob-list";
74 
75     // flags for Device Config API
76     private static final String PRIV_APPS_OOB_ENABLED = "priv_apps_oob_enabled";
77     private static final String PRIV_APPS_OOB_WHITELIST = "priv_apps_oob_whitelist";
78 
79     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
80 
81     private final Context mContext;
82 
83     // Maps package name to code locations.
84     // It caches the code locations for the installed packages. This allows for
85     // faster lookups (no locks) when finding what package owns the dex file.
86     @GuardedBy("mPackageCodeLocationsCache")
87     private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;
88 
89     // PackageDexUsage handles the actual I/O operations. It is responsible to
90     // encode and save the dex usage data.
91     private final PackageDexUsage mPackageDexUsage;
92 
93     // DynamicCodeLogger handles recording of dynamic code loading - which is similar to
94     // PackageDexUsage but records a different aspect of the data.
95     // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't
96     // record class loaders or ISAs.)
97     private final DynamicCodeLogger mDynamicCodeLogger;
98 
99     private final IPackageManager mPackageManager;
100     private final PackageDexOptimizer mPackageDexOptimizer;
101     private final Object mInstallLock;
102     @GuardedBy("mInstallLock")
103     private final Installer mInstaller;
104 
105     // Possible outcomes of a dex search.
106     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
107     private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
108     private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
109     private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
110 
111     /**
112      * We do not record packages that have no secondary dex files or that are not used by other
113      * apps. This is an optimization to reduce the amount of data that needs to be written to
114      * disk (apps will not usually be shared so this trims quite a bit the number we record).
115      *
116      * To make this behaviour transparent to the callers which need use information on packages,
117      * DexManager will return this DEFAULT instance from
118      * {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and
119      * is marked as not being used by other apps. This reflects the intended behaviour when we don't
120      * find the package in the underlying data file.
121      */
122     private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
123 
DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock)124     public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
125             Installer installer, Object installLock) {
126         mContext = context;
127         mPackageCodeLocationsCache = new HashMap<>();
128         mPackageDexUsage = new PackageDexUsage();
129         mPackageManager = pms;
130         mPackageDexOptimizer = pdo;
131         mInstaller = installer;
132         mInstallLock = installLock;
133         mDynamicCodeLogger = new DynamicCodeLogger(pms, installer);
134     }
135 
getDynamicCodeLogger()136     public DynamicCodeLogger getDynamicCodeLogger() {
137         return mDynamicCodeLogger;
138     }
139 
140     /**
141      * Notify about dex files loads.
142      * Note that this method is invoked when apps load dex files and it should
143      * return as fast as possible.
144      *
145      * @param loadingAppInfo the package performing the load
146      * @param classLoadersNames the names of the class loaders present in the loading chain. The
147      *    list encodes the class loader chain in the natural order. The first class loader has
148      *    the second one as its parent and so on. The dex files present in the class path of the
149      *    first class loader will be recorded in the usage file.
150      * @param classPaths the class paths corresponding to the class loaders names from
151      *     {@param classLoadersNames}. The the first element corresponds to the first class loader
152      *     and so on. A classpath is represented as a list of dex files separated by
153      *     {@code File.pathSeparator}, or null if the class loader's classpath is not known.
154      *     The dex files found in the first class path will be recorded in the usage file.
155      * @param loaderIsa the ISA of the app loading the dex files
156      * @param loaderUserId the user id which runs the code loading the dex files
157      */
notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames, List<String> classPaths, String loaderIsa, int loaderUserId)158     public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames,
159             List<String> classPaths, String loaderIsa, int loaderUserId) {
160         try {
161             notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
162                     loaderUserId);
163         } catch (Exception e) {
164             Slog.w(TAG, "Exception while notifying dex load for package " +
165                     loadingAppInfo.packageName, e);
166         }
167     }
168 
169     @VisibleForTesting
notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> classLoaderNames, List<String> classPaths, String loaderIsa, int loaderUserId)170     /*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
171             List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
172             int loaderUserId) {
173         if (classLoaderNames.size() != classPaths.size()) {
174             Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
175             return;
176         }
177         if (classLoaderNames.isEmpty()) {
178             Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
179             return;
180         }
181         if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
182             Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
183                     loaderIsa + "?");
184             return;
185         }
186 
187         // The first classpath should never be null because the first classloader
188         // should always be an instance of BaseDexClassLoader.
189         String firstClassPath = classPaths.get(0);
190         if (firstClassPath == null) {
191             return;
192         }
193         // The classpath is represented as a list of dex files separated by File.pathSeparator.
194         String[] dexPathsToRegister = firstClassPath.split(File.pathSeparator);
195 
196         // Encode the class loader contexts for the dexPathsToRegister.
197         String[] classLoaderContexts = DexoptUtils.processContextForDexLoad(
198                 classLoaderNames, classPaths);
199 
200         // A null classLoaderContexts means that there are unsupported class loaders in the
201         // chain.
202         if (classLoaderContexts == null) {
203             if (DEBUG) {
204                 Slog.i(TAG, loadingAppInfo.packageName +
205                         " uses unsupported class loader in " + classLoaderNames);
206             }
207         }
208 
209         int dexPathIndex = 0;
210         for (String dexPath : dexPathsToRegister) {
211             // Find the owning package name.
212             DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
213 
214             if (DEBUG) {
215                 Slog.i(TAG, loadingAppInfo.packageName
216                     + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
217             }
218 
219             if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
220                 // TODO(calin): extend isUsedByOtherApps check to detect the cases where
221                 // different apps share the same runtime. In that case we should not mark the dex
222                 // file as isUsedByOtherApps. Currently this is a safe approximation.
223                 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals(
224                         searchResult.mOwningPackageName);
225                 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
226                         searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;
227 
228                 if (primaryOrSplit && !isUsedByOtherApps) {
229                     // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
230                     // do not record it. This case does not bring any new usable information
231                     // and can be safely skipped.
232                     continue;
233                 }
234 
235                 if (!primaryOrSplit) {
236                     // Record loading of a DEX file from an app data directory.
237                     mDynamicCodeLogger.recordDex(loaderUserId, dexPath,
238                             searchResult.mOwningPackageName, loadingAppInfo.packageName);
239                 }
240 
241                 if (classLoaderContexts != null) {
242 
243                     // Record dex file usage. If the current usage is a new pattern (e.g. new
244                     // secondary, or UsedByOtherApps), record will return true and we trigger an
245                     // async write to disk to make sure we don't loose the data in case of a reboot.
246 
247                     String classLoaderContext = classLoaderContexts[dexPathIndex];
248                     if (mPackageDexUsage.record(searchResult.mOwningPackageName,
249                             dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
250                             loadingAppInfo.packageName, classLoaderContext)) {
251                         mPackageDexUsage.maybeWriteAsync();
252                     }
253                 }
254             } else {
255                 // If we can't find the owner of the dex we simply do not track it. The impact is
256                 // that the dex file will not be considered for offline optimizations.
257                 if (DEBUG) {
258                     Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
259                 }
260             }
261             dexPathIndex++;
262         }
263     }
264 
265     /**
266      * Read the dex usage from disk and populate the code cache locations.
267      * @param existingPackages a map containing information about what packages
268      *          are available to what users. Only packages in this list will be
269      *          recognized during notifyDexLoad().
270      */
load(Map<Integer, List<PackageInfo>> existingPackages)271     public void load(Map<Integer, List<PackageInfo>> existingPackages) {
272         try {
273             loadInternal(existingPackages);
274         } catch (Exception e) {
275             mPackageDexUsage.clear();
276             mDynamicCodeLogger.clear();
277             Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
278         }
279     }
280 
281     /**
282      * Notifies that a new package was installed for {@code userId}.
283      * {@code userId} must not be {@code UserHandle.USER_ALL}.
284      *
285      * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}.
286      */
notifyPackageInstalled(PackageInfo pi, int userId)287     public void notifyPackageInstalled(PackageInfo pi, int userId) {
288         if (userId == UserHandle.USER_ALL) {
289             throw new IllegalArgumentException(
290                 "notifyPackageInstalled called with USER_ALL");
291         }
292         cachePackageInfo(pi, userId);
293     }
294 
295     /**
296      * Notifies that package {@code packageName} was updated.
297      * This will clear the UsedByOtherApps mark if it exists.
298      */
notifyPackageUpdated(String packageName, String baseCodePath, String[] splitCodePaths)299     public void notifyPackageUpdated(String packageName, String baseCodePath,
300             String[] splitCodePaths) {
301         cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1);
302         // In case there was an update, write the package use info to disk async.
303         // Note that we do the writing here and not in PackageDexUsage in order to be
304         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
305         // multiple updates in PackageDexUsage before writing it).
306         if (mPackageDexUsage.clearUsedByOtherApps(packageName)) {
307             mPackageDexUsage.maybeWriteAsync();
308         }
309     }
310 
311     /**
312      * Notifies that the user {@code userId} data for package {@code packageName}
313      * was destroyed. This will remove all usage info associated with the package
314      * for the given user.
315      * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case
316      * all usage information for the package will be removed.
317      */
notifyPackageDataDestroyed(String packageName, int userId)318     public void notifyPackageDataDestroyed(String packageName, int userId) {
319         // In case there was an update, write the package use info to disk async.
320         // Note that we do the writing here and not in the lower level classes in order to be
321         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
322         // multiple updates in PackageDexUsage before writing it).
323         if (userId == UserHandle.USER_ALL) {
324             if (mPackageDexUsage.removePackage(packageName)) {
325                 mPackageDexUsage.maybeWriteAsync();
326             }
327             mDynamicCodeLogger.removePackage(packageName);
328         } else {
329             if (mPackageDexUsage.removeUserPackage(packageName, userId)) {
330                 mPackageDexUsage.maybeWriteAsync();
331             }
332             mDynamicCodeLogger.removeUserPackage(packageName, userId);
333         }
334     }
335 
336     /**
337      * Caches the code location from the given package info.
338      */
cachePackageInfo(PackageInfo pi, int userId)339     private void cachePackageInfo(PackageInfo pi, int userId) {
340         ApplicationInfo ai = pi.applicationInfo;
341         String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir,
342                 ai.credentialProtectedDataDir};
343         cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs,
344                 dataDirs, userId);
345     }
346 
cachePackageCodeLocation(String packageName, String baseCodePath, String[] splitCodePaths, String[] dataDirs, int userId)347     private void cachePackageCodeLocation(String packageName, String baseCodePath,
348             String[] splitCodePaths, String[] dataDirs, int userId) {
349         synchronized (mPackageCodeLocationsCache) {
350             PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName,
351                     new PackageCodeLocations(packageName, baseCodePath, splitCodePaths));
352             // TODO(calin): We are forced to extend the scope of this synchronization because
353             // the values of the cache (PackageCodeLocations) are updated in place.
354             // Make PackageCodeLocations immutable to simplify the synchronization reasoning.
355             pcl.updateCodeLocation(baseCodePath, splitCodePaths);
356             if (dataDirs != null) {
357                 for (String dataDir : dataDirs) {
358                     // The set of data dirs includes deviceProtectedDataDir and
359                     // credentialProtectedDataDir which might be null for shared
360                     // libraries. Currently we don't track these but be lenient
361                     // and check in case we ever decide to store their usage data.
362                     if (dataDir != null) {
363                         pcl.mergeAppDataDirs(dataDir, userId);
364                     }
365                 }
366             }
367         }
368     }
369 
loadInternal(Map<Integer, List<PackageInfo>> existingPackages)370     private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
371         Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
372         Map<String, Set<String>> packageToCodePaths = new HashMap<>();
373 
374         // Cache the code locations for the installed packages. This allows for
375         // faster lookups (no locks) when finding what package owns the dex file.
376         for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
377             List<PackageInfo> packageInfoList = entry.getValue();
378             int userId = entry.getKey();
379             for (PackageInfo pi : packageInfoList) {
380                 // Cache the code locations.
381                 cachePackageInfo(pi, userId);
382 
383                 // Cache two maps:
384                 //   - from package name to the set of user ids who installed the package.
385                 //   - from package name to the set of code paths.
386                 // We will use it to sync the data and remove obsolete entries from
387                 // mPackageDexUsage.
388                 Set<Integer> users = putIfAbsent(
389                         packageToUsersMap, pi.packageName, new HashSet<>());
390                 users.add(userId);
391 
392                 Set<String> codePaths = putIfAbsent(
393                     packageToCodePaths, pi.packageName, new HashSet<>());
394                 codePaths.add(pi.applicationInfo.sourceDir);
395                 if (pi.applicationInfo.splitSourceDirs != null) {
396                     Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs);
397                 }
398             }
399         }
400 
401         try {
402             mPackageDexUsage.read();
403             mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths);
404         } catch (Exception e) {
405             mPackageDexUsage.clear();
406             Slog.w(TAG, "Exception while loading package dex usage. "
407                     + "Starting with a fresh state.", e);
408         }
409 
410         try {
411             mDynamicCodeLogger.readAndSync(packageToUsersMap);
412         } catch (Exception e) {
413             mDynamicCodeLogger.clear();
414             Slog.w(TAG, "Exception while loading package dynamic code usage. "
415                     + "Starting with a fresh state.", e);
416         }
417     }
418 
419     /**
420      * Get the package dex usage for the given package name.
421      * If there is no usage info the method will return a default {@code PackageUseInfo} with
422      * no data about secondary dex files and marked as not being used by other apps.
423      *
424      * Note that no use info means the package was not used or it was used but not by other apps.
425      * Also, note that right now we might prune packages which are not used by other apps.
426      * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
427      * to access the package use.
428      */
getPackageUseInfoOrDefault(String packageName)429     public PackageUseInfo getPackageUseInfoOrDefault(String packageName) {
430         PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName);
431         return useInfo == null ? DEFAULT_USE_INFO : useInfo;
432     }
433 
434     /**
435      * Return whether or not the manager has usage information on the give package.
436      *
437      * Note that no use info means the package was not used or it was used but not by other apps.
438      * Also, note that right now we might prune packages which are not used by other apps.
439      * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
440      * to access the package use.
441      */
442     @VisibleForTesting
hasInfoOnPackage(String packageName)443     /*package*/ boolean hasInfoOnPackage(String packageName) {
444         return mPackageDexUsage.getPackageUseInfo(packageName) != null;
445     }
446 
447     /**
448      * Perform dexopt on with the given {@code options} on the secondary dex files.
449      * @return true if all secondary dex files were processed successfully (compiled or skipped
450      *         because they don't need to be compiled)..
451      */
dexoptSecondaryDex(DexoptOptions options)452     public boolean dexoptSecondaryDex(DexoptOptions options) {
453         // Select the dex optimizer based on the force parameter.
454         // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
455         // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
456         // passing the force flag through the multitude of layers.
457         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
458         //       allocate an object here.
459         PackageDexOptimizer pdo = options.isForce()
460                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
461                 : mPackageDexOptimizer;
462         String packageName = options.getPackageName();
463         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
464         if (useInfo.getDexUseInfoMap().isEmpty()) {
465             if (DEBUG) {
466                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
467             }
468             // Nothing to compile, return true.
469             return true;
470         }
471         boolean success = true;
472         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
473             String dexPath = entry.getKey();
474             DexUseInfo dexUseInfo = entry.getValue();
475 
476             PackageInfo pkg;
477             try {
478                 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
479                     dexUseInfo.getOwnerUserId());
480             } catch (RemoteException e) {
481                 throw new AssertionError(e);
482             }
483             // It may be that the package gets uninstalled while we try to compile its
484             // secondary dex files. If that's the case, just ignore.
485             // Note that we don't break the entire loop because the package might still be
486             // installed for other users.
487             if (pkg == null) {
488                 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
489                         + " for user " + dexUseInfo.getOwnerUserId());
490                 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
491                 continue;
492             }
493 
494             int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
495                     dexUseInfo, options);
496             success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
497         }
498         return success;
499     }
500 
501     /**
502      * Reconcile the information we have about the secondary dex files belonging to
503      * {@code packagName} and the actual dex files. For all dex files that were
504      * deleted, update the internal records and delete any generated oat files.
505      */
reconcileSecondaryDexFiles(String packageName)506     public void reconcileSecondaryDexFiles(String packageName) {
507         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
508         if (useInfo.getDexUseInfoMap().isEmpty()) {
509             if (DEBUG) {
510                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
511             }
512             // Nothing to reconcile.
513             return;
514         }
515 
516         boolean updated = false;
517         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
518             String dexPath = entry.getKey();
519             DexUseInfo dexUseInfo = entry.getValue();
520             PackageInfo pkg = null;
521             try {
522                 // Note that we look for the package in the PackageManager just to be able
523                 // to get back the real app uid and its storage kind. These are only used
524                 // to perform extra validation in installd.
525                 // TODO(calin): maybe a bit overkill.
526                 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
527                     dexUseInfo.getOwnerUserId());
528             } catch (RemoteException ignore) {
529                 // Can't happen, DexManager is local.
530             }
531             if (pkg == null) {
532                 // It may be that the package was uninstalled while we process the secondary
533                 // dex files.
534                 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
535                         + " for user " + dexUseInfo.getOwnerUserId());
536                 // Update the usage and continue, another user might still have the package.
537                 updated = mPackageDexUsage.removeUserPackage(
538                         packageName, dexUseInfo.getOwnerUserId()) || updated;
539                 continue;
540             }
541             ApplicationInfo info = pkg.applicationInfo;
542             int flags = 0;
543             if (info.deviceProtectedDataDir != null &&
544                     FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
545                 flags |= StorageManager.FLAG_STORAGE_DE;
546             } else if (info.credentialProtectedDataDir!= null &&
547                     FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
548                 flags |= StorageManager.FLAG_STORAGE_CE;
549             } else {
550                 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
551                 updated = mPackageDexUsage.removeDexFile(
552                         packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
553                 continue;
554             }
555 
556             boolean dexStillExists = true;
557             synchronized(mInstallLock) {
558                 try {
559                     String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
560                     dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
561                             info.uid, isas, info.volumeUuid, flags);
562                 } catch (InstallerException e) {
563                     Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
564                             " : " + e.getMessage());
565                 }
566             }
567             if (!dexStillExists) {
568                 updated = mPackageDexUsage.removeDexFile(
569                         packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
570             }
571 
572         }
573         if (updated) {
574             mPackageDexUsage.maybeWriteAsync();
575         }
576     }
577 
578     // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
579     // compilation happening here will use a pessimistic context.
registerDexModule(ApplicationInfo info, String dexPath, boolean isUsedByOtherApps, int userId)580     public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
581             boolean isUsedByOtherApps, int userId) {
582         // Find the owning package record.
583         DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
584 
585         if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) {
586             return new RegisterDexModuleResult(false, "Package not found");
587         }
588         if (!info.packageName.equals(searchResult.mOwningPackageName)) {
589             return new RegisterDexModuleResult(false, "Dex path does not belong to package");
590         }
591         if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
592                 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) {
593             return new RegisterDexModuleResult(false, "Main apks cannot be registered");
594         }
595 
596         // We found the package. Now record the usage for all declared ISAs.
597         boolean update = false;
598         for (String isa : getAppDexInstructionSets(info)) {
599             boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
600                     dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false,
601                     searchResult.mOwningPackageName,
602                     PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT);
603             update |= newUpdate;
604         }
605         if (update) {
606             mPackageDexUsage.maybeWriteAsync();
607         }
608 
609         DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName)
610                 .getDexUseInfoMap().get(dexPath);
611 
612         // Try to optimize the package according to the install reason.
613         DexoptOptions options = new DexoptOptions(info.packageName,
614                 PackageManagerService.REASON_INSTALL, /*flags*/0);
615 
616         int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo,
617                 options);
618 
619         // If we fail to optimize the package log an error but don't propagate the error
620         // back to the app. The app cannot do much about it and the background job
621         // will rety again when it executes.
622         // TODO(calin): there might be some value to return the error here but it may
623         // cause red herrings since that doesn't mean the app cannot use the module.
624         if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
625             Slog.e(TAG, "Failed to optimize dex module " + dexPath);
626         }
627         return new RegisterDexModuleResult(true, "Dex module registered successfully");
628     }
629 
630     /**
631      * Return all packages that contain records of secondary dex files.
632      */
getAllPackagesWithSecondaryDexFiles()633     public Set<String> getAllPackagesWithSecondaryDexFiles() {
634         return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
635     }
636 
637     /**
638      * Retrieves the package which owns the given dexPath.
639      */
getDexPackage( ApplicationInfo loadingAppInfo, String dexPath, int userId)640     private DexSearchResult getDexPackage(
641             ApplicationInfo loadingAppInfo, String dexPath, int userId) {
642         // Ignore framework code.
643         // TODO(calin): is there a better way to detect it?
644         if (dexPath.startsWith("/system/framework/")) {
645             return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
646         }
647 
648         // First, check if the package which loads the dex file actually owns it.
649         // Most of the time this will be true and we can return early.
650         PackageCodeLocations loadingPackageCodeLocations =
651                 new PackageCodeLocations(loadingAppInfo, userId);
652         int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
653         if (outcome != DEX_SEARCH_NOT_FOUND) {
654             // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
655             return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
656         }
657 
658         // The loadingPackage does not own the dex file.
659         // Perform a reverse look-up in the cache to detect if any package has ownership.
660         // Note that we can have false negatives if the cache falls out of date.
661         synchronized (mPackageCodeLocationsCache) {
662             for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
663                 outcome = pcl.searchDex(dexPath, userId);
664                 if (outcome != DEX_SEARCH_NOT_FOUND) {
665                     return new DexSearchResult(pcl.mPackageName, outcome);
666                 }
667             }
668         }
669 
670         if (DEBUG) {
671             // TODO(calin): Consider checking for /data/data symlink.
672             // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
673             // to load dex files through it.
674             try {
675                 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
676                 if (!dexPath.equals(dexPathReal)) {
677                     Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
678                             dexPath + " dexPathReal=" + dexPathReal);
679                 }
680             } catch (IOException e) {
681                 // Ignore
682             }
683         }
684         // Cache miss. The cache is updated during installs and uninstalls,
685         // so if we get here we're pretty sure the dex path does not exist.
686         return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
687     }
688 
putIfAbsent(Map<K,V> map, K key, V newValue)689     private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
690         V existingValue = map.putIfAbsent(key, newValue);
691         return existingValue == null ? newValue : existingValue;
692     }
693 
694     /**
695      * Writes the in-memory package dex usage to disk right away.
696      */
writePackageDexUsageNow()697     public void writePackageDexUsageNow() {
698         mPackageDexUsage.writeNow();
699         mDynamicCodeLogger.writeNow();
700     }
701 
702     /**
703      * Returns whether the given package is in the list of privilaged apps that should run out of
704      * box. This only makes sense if the feature is enabled. Note that when the the OOB list is
705      * empty, all priv apps will run in OOB mode.
706      */
isPackageSelectedToRunOob(String packageName)707     public static boolean isPackageSelectedToRunOob(String packageName) {
708         return isPackageSelectedToRunOob(Arrays.asList(packageName));
709     }
710 
711     /**
712      * Returns whether any of the given packages are in the list of privilaged apps that should run
713      * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list
714      * is empty, all priv apps will run in OOB mode.
715      */
isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess)716     public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
717         return isPackageSelectedToRunOobInternal(
718                 SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false),
719                 SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"),
720                 DeviceConfig.getProperty(NAMESPACE_DEX_BOOT, PRIV_APPS_OOB_ENABLED),
721                 DeviceConfig.getProperty(NAMESPACE_DEX_BOOT, PRIV_APPS_OOB_WHITELIST),
722                 packageNamesInSameProcess);
723     }
724 
725     @VisibleForTesting
isPackageSelectedToRunOobInternal( boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled, String overrideWhitelist, Collection<String> packageNamesInSameProcess)726     /* package */ static boolean isPackageSelectedToRunOobInternal(
727             boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled,
728             String overrideWhitelist, Collection<String> packageNamesInSameProcess) {
729         // Allow experiment (if exists) to override device configuration.
730         boolean enabled = overrideEnabled != null ? overrideEnabled.equals("true")
731                 : isDefaultEnabled;
732         if (!enabled) {
733             return false;
734         }
735 
736         // Similarly, experiment flag can override the whitelist.
737         String whitelist = overrideWhitelist != null ? overrideWhitelist : defaultWhitelist;
738         if ("ALL".equals(whitelist)) {
739             return true;
740         }
741         for (String oobPkgName : whitelist.split(",")) {
742             if (packageNamesInSameProcess.contains(oobPkgName)) {
743                 return true;
744             }
745         }
746         return false;
747     }
748 
749     /**
750      * Generates log if the archive located at {@code fileName} has uncompressed dex file that can
751      * be direclty mapped.
752      */
auditUncompressedDexInApk(String fileName)753     public static boolean auditUncompressedDexInApk(String fileName) {
754         StrictJarFile jarFile = null;
755         try {
756             jarFile = new StrictJarFile(fileName,
757                     false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
758             Iterator<ZipEntry> it = jarFile.iterator();
759             boolean allCorrect = true;
760             while (it.hasNext()) {
761                 ZipEntry entry = it.next();
762                 if (entry.getName().endsWith(".dex")) {
763                     if (entry.getMethod() != ZipEntry.STORED) {
764                         allCorrect = false;
765                         Slog.w(TAG, "APK " + fileName + " has compressed dex code " +
766                                 entry.getName());
767                     } else if ((entry.getDataOffset() & 0x3) != 0) {
768                         allCorrect = false;
769                         Slog.w(TAG, "APK " + fileName + " has unaligned dex code " +
770                                 entry.getName());
771                     }
772                 }
773             }
774             return allCorrect;
775         } catch (IOException ignore) {
776             Slog.wtf(TAG, "Error when parsing APK " + fileName);
777             return false;
778         } finally {
779             try {
780                 if (jarFile != null) {
781                     jarFile.close();
782                 }
783             } catch (IOException ignore) {}
784         }
785     }
786 
787     public static class RegisterDexModuleResult {
RegisterDexModuleResult()788         public RegisterDexModuleResult() {
789             this(false, null);
790         }
791 
RegisterDexModuleResult(boolean success, String message)792         public RegisterDexModuleResult(boolean success, String message) {
793             this.success = success;
794             this.message = message;
795         }
796 
797         public final boolean success;
798         public final String message;
799     }
800 
801     /**
802      * Convenience class to store the different locations where a package might
803      * own code.
804      */
805     private static class PackageCodeLocations {
806         private final String mPackageName;
807         private String mBaseCodePath;
808         private final Set<String> mSplitCodePaths;
809         // Maps user id to the application private directory.
810         private final Map<Integer, Set<String>> mAppDataDirs;
811 
PackageCodeLocations(ApplicationInfo ai, int userId)812         public PackageCodeLocations(ApplicationInfo ai, int userId) {
813             this(ai.packageName, ai.sourceDir, ai.splitSourceDirs);
814             mergeAppDataDirs(ai.dataDir, userId);
815         }
PackageCodeLocations(String packageName, String baseCodePath, String[] splitCodePaths)816         public PackageCodeLocations(String packageName, String baseCodePath,
817                 String[] splitCodePaths) {
818             mPackageName = packageName;
819             mSplitCodePaths = new HashSet<>();
820             mAppDataDirs = new HashMap<>();
821             updateCodeLocation(baseCodePath, splitCodePaths);
822         }
823 
updateCodeLocation(String baseCodePath, String[] splitCodePaths)824         public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) {
825             mBaseCodePath = baseCodePath;
826             mSplitCodePaths.clear();
827             if (splitCodePaths != null) {
828                 for (String split : splitCodePaths) {
829                     mSplitCodePaths.add(split);
830                 }
831             }
832         }
833 
mergeAppDataDirs(String dataDir, int userId)834         public void mergeAppDataDirs(String dataDir, int userId) {
835             Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
836             dataDirs.add(dataDir);
837         }
838 
searchDex(String dexPath, int userId)839         public int searchDex(String dexPath, int userId) {
840             // First check that this package is installed or active for the given user.
841             // A missing data dir means the package is not installed.
842             Set<String> userDataDirs = mAppDataDirs.get(userId);
843             if (userDataDirs == null) {
844                 return DEX_SEARCH_NOT_FOUND;
845             }
846 
847             if (mBaseCodePath.equals(dexPath)) {
848                 return DEX_SEARCH_FOUND_PRIMARY;
849             }
850             if (mSplitCodePaths.contains(dexPath)) {
851                 return DEX_SEARCH_FOUND_SPLIT;
852             }
853             for (String dataDir : userDataDirs) {
854                 if (dexPath.startsWith(dataDir)) {
855                     return DEX_SEARCH_FOUND_SECONDARY;
856                 }
857             }
858 
859             return DEX_SEARCH_NOT_FOUND;
860         }
861     }
862 
863     /**
864      * Convenience class to store ownership search results.
865      */
866     private class DexSearchResult {
867         private String mOwningPackageName;
868         private int mOutcome;
869 
DexSearchResult(String owningPackageName, int outcome)870         public DexSearchResult(String owningPackageName, int outcome) {
871             this.mOwningPackageName = owningPackageName;
872             this.mOutcome = outcome;
873         }
874 
875         @Override
toString()876         public String toString() {
877             return mOwningPackageName + "-" + mOutcome;
878         }
879     }
880 }
881