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 com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
20 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
21 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
22 
23 import static java.util.function.Function.identity;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.content.Context;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.IPackageManager;
30 import android.content.pm.PackageInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackagePartitions;
33 import android.os.BatteryManager;
34 import android.os.FileUtils;
35 import android.os.PowerManager;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.os.UserHandle;
39 import android.os.storage.StorageManager;
40 import android.util.Log;
41 import android.util.Slog;
42 import android.util.jar.StrictJarFile;
43 
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.server.pm.Installer;
47 import com.android.server.pm.Installer.InstallerException;
48 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
49 import com.android.server.pm.PackageDexOptimizer;
50 import com.android.server.pm.PackageManagerService;
51 import com.android.server.pm.PackageManagerServiceUtils;
52 
53 import dalvik.system.VMRuntime;
54 
55 import java.io.File;
56 import java.io.IOException;
57 import java.nio.file.Files;
58 import java.nio.file.Paths;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.HashSet;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.zip.ZipEntry;
68 
69 /**
70  * This class keeps track of how dex files are used.
71  * Every time it gets a notification about a dex file being loaded it tracks
72  * its owning package and records it in PackageDexUsage (package-dex-usage.list).
73  *
74  * TODO(calin): Extract related dexopt functionality from PackageManagerService
75  * into this class.
76  */
77 public class DexManager {
78     private static final String TAG = "DexManager";
79     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
80 
81     // System server cannot load executable code outside system partitions.
82     // However it can load verification data - thus we pick the "verify" compiler filter.
83     private static final String SYSTEM_SERVER_COMPILER_FILTER = "verify";
84 
85     // The suffix we add to the package name when the loading happens in an isolated process.
86     // Note that the double dot creates and "invalid" package name which makes it clear that this
87     // is an artificially constructed name.
88     private static final String ISOLATED_PROCESS_PACKAGE_SUFFIX = "..isolated";
89 
90     private final Context mContext;
91 
92     // Maps package name to code locations.
93     // It caches the code locations for the installed packages. This allows for
94     // faster lookups (no locks) when finding what package owns the dex file.
95     @GuardedBy("mPackageCodeLocationsCache")
96     private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;
97 
98     // PackageDexUsage handles the actual I/O operations. It is responsible to
99     // encode and save the dex usage data.
100     private final PackageDexUsage mPackageDexUsage;
101 
102     // DynamicCodeLogger handles recording of dynamic code loading - which is similar to
103     // PackageDexUsage but records a different aspect of the data.
104     // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't
105     // record class loaders or ISAs.)
106     private final DynamicCodeLogger mDynamicCodeLogger;
107 
108     private IPackageManager mPackageManager;
109     private final PackageDexOptimizer mPackageDexOptimizer;
110     private final Object mInstallLock;
111     @GuardedBy("mInstallLock")
112     private final Installer mInstaller;
113 
114     private BatteryManager mBatteryManager = null;
115     private PowerManager mPowerManager = null;
116 
117     // An integer percentage value used to determine when the device is considered to be on low
118     // power for compilation purposes.
119     private final int mCriticalBatteryLevel;
120 
121     // Possible outcomes of a dex search.
122     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
123     private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
124     private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
125     private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
126 
DexManager(Context context, PackageDexOptimizer pdo, Installer installer, Object installLock, DynamicCodeLogger dynamicCodeLogger)127     public DexManager(Context context, PackageDexOptimizer pdo, Installer installer,
128             Object installLock, DynamicCodeLogger dynamicCodeLogger) {
129         this(context, pdo, installer, installLock, dynamicCodeLogger, null);
130     }
131 
132     @VisibleForTesting
DexManager(Context context, PackageDexOptimizer pdo, Installer installer, Object installLock, DynamicCodeLogger dynamicCodeLogger, @Nullable IPackageManager packageManager)133     public DexManager(Context context, PackageDexOptimizer pdo, Installer installer,
134             Object installLock, DynamicCodeLogger dynamicCodeLogger,
135             @Nullable IPackageManager packageManager) {
136         mContext = context;
137         mPackageCodeLocationsCache = new HashMap<>();
138         mPackageDexUsage = new PackageDexUsage();
139         mPackageDexOptimizer = pdo;
140         mInstaller = installer;
141         mInstallLock = installLock;
142         mDynamicCodeLogger = dynamicCodeLogger;
143         mPackageManager = packageManager;
144 
145         // This is currently checked to handle tests that pass in a null context.
146         // TODO(b/174783329): Modify the tests to pass in a mocked Context, PowerManager,
147         //      and BatteryManager.
148         if (mContext != null) {
149             mPowerManager = mContext.getSystemService(PowerManager.class);
150 
151             if (mPowerManager == null) {
152                 Slog.wtf(TAG, "Power Manager is unavailable at time of Dex Manager start");
153             }
154 
155             mCriticalBatteryLevel = mContext.getResources().getInteger(
156                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
157         } else {
158             // This value will never be used as the Battery Manager null check will fail first.
159             mCriticalBatteryLevel = 0;
160         }
161     }
162 
163     @NonNull
getPackageManager()164     private IPackageManager getPackageManager() {
165         if (mPackageManager == null) {
166             mPackageManager = IPackageManager.Stub.asInterface(
167                     ServiceManager.getService("package"));
168         }
169         return mPackageManager;
170     }
171 
172     /**
173      * Notify about dex files loads.
174      * Note that this method is invoked when apps load dex files and it should
175      * return as fast as possible.
176      *
177      * @param loadingAppInfo the package performing the load
178      * @param classLoaderContextMap a map from file paths to dex files that have been loaded to
179      *     the class loader context that was used to load them.
180      * @param loaderIsa the ISA of the app loading the dex files
181      * @param loaderUserId the user id which runs the code loading the dex files
182      * @param loaderIsIsolatedProcess whether or not the loading process is isolated.
183      */
notifyDexLoad(ApplicationInfo loadingAppInfo, Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId, boolean loaderIsIsolatedProcess)184     public void notifyDexLoad(ApplicationInfo loadingAppInfo,
185             Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId,
186             boolean loaderIsIsolatedProcess) {
187         try {
188             notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa,
189                     loaderUserId, loaderIsIsolatedProcess);
190         } catch (RuntimeException e) {
191             Slog.w(TAG, "Exception while notifying dex load for package " +
192                     loadingAppInfo.packageName, e);
193         }
194     }
195 
196     @VisibleForTesting
notifyDexLoadInternal(ApplicationInfo loadingAppInfo, Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId, boolean loaderIsIsolatedProcess)197     /*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
198             Map<String, String> classLoaderContextMap, String loaderIsa,
199             int loaderUserId, boolean loaderIsIsolatedProcess) {
200         if (classLoaderContextMap == null) {
201             return;
202         }
203         if (classLoaderContextMap.isEmpty()) {
204             Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
205             return;
206         }
207         if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
208             Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet()
209                     + " in unsupported ISA: " + loaderIsa + "?");
210             return;
211         }
212 
213         // If this load is coming from an isolated process we need to be able to prevent profile
214         // based optimizations. This is because isolated processes are sandboxed and can only read
215         // world readable files, so they need world readable optimization files. An
216         // example of such a package is webview.
217         //
218         // In order to prevent profile optimization we pretend that the load is coming from a
219         // different package, and so we assign a artificial name to the loading package making it
220         // clear that it comes from an isolated process. This blends well with the entire
221         // usedByOthers logic without needing to special handle isolated process in all dexopt
222         // layers.
223         String loadingPackageAmendedName = loadingAppInfo.packageName;
224         if (loaderIsIsolatedProcess) {
225             loadingPackageAmendedName += ISOLATED_PROCESS_PACKAGE_SUFFIX;
226         }
227         for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) {
228             String dexPath = mapping.getKey();
229             // Find the owning package name.
230             DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
231 
232             if (DEBUG) {
233                 Slog.i(TAG, loadingPackageAmendedName
234                         + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
235             }
236 
237             if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
238                 // TODO(calin): extend isUsedByOtherApps check to detect the cases where
239                 // different apps share the same runtime. In that case we should not mark the dex
240                 // file as isUsedByOtherApps. Currently this is a safe approximation.
241                 boolean isUsedByOtherApps =
242                         !loadingPackageAmendedName.equals(searchResult.mOwningPackageName);
243                 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
244                         searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;
245 
246                 if (primaryOrSplit && !isUsedByOtherApps
247                         && !isPlatformPackage(searchResult.mOwningPackageName)) {
248                     // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
249                     // do not record it. This case does not bring any new usable information
250                     // and can be safely skipped.
251                     // Note this is just an optimization that makes things easier to read in the
252                     // package-dex-use file since we don't need to pollute it with redundant info.
253                     // However, we always record system server packages.
254                     continue;
255                 }
256 
257                 if (!primaryOrSplit) {
258                     // Record loading of a DEX file from an app data directory.
259                     mDynamicCodeLogger.recordDex(loaderUserId, dexPath,
260                             searchResult.mOwningPackageName, loadingAppInfo.packageName);
261                 }
262 
263                 String classLoaderContext = mapping.getValue();
264 
265                 // Overwrite the class loader context for system server (instead of merging it).
266                 // We expect system server jars to only change contexts in between OTAs and to
267                 // otherwise be stable.
268                 // Instead of implementing a complex clear-context logic post OTA, it is much
269                 // simpler to always override the context for system server. This way, the context
270                 // will always be up to date and we will avoid merging which could lead to the
271                 // the context being marked as variable and thus making dexopt non-optimal.
272                 boolean overwriteCLC = isPlatformPackage(searchResult.mOwningPackageName);
273 
274                 if (classLoaderContext != null
275                         && VMRuntime.isValidClassLoaderContext(classLoaderContext)) {
276                     // Record dex file usage. If the current usage is a new pattern (e.g. new
277                     // secondary, or UsedByOtherApps), record will return true and we trigger an
278                     // async write to disk to make sure we don't loose the data in case of a reboot.
279                     if (mPackageDexUsage.record(searchResult.mOwningPackageName,
280                             dexPath, loaderUserId, loaderIsa, primaryOrSplit,
281                             loadingPackageAmendedName, classLoaderContext, overwriteCLC)) {
282                         mPackageDexUsage.maybeWriteAsync();
283                     }
284                 }
285             } else {
286                 // If we can't find the owner of the dex we simply do not track it. The impact is
287                 // that the dex file will not be considered for offline optimizations.
288                 if (DEBUG) {
289                     Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
290                 }
291             }
292         }
293     }
294 
295     /**
296      * Check if the dexPath belongs to system server.
297      * System server can load code from different location, so we cast a wide-net here, and
298      * assume that if the paths is on any of the registered system partitions then it can be loaded
299      * by system server.
300      */
isSystemServerDexPathSupportedForOdex(String dexPath)301     private boolean isSystemServerDexPathSupportedForOdex(String dexPath) {
302         ArrayList<PackagePartitions.SystemPartition> partitions =
303                 PackagePartitions.getOrderedPartitions(identity());
304         // First check the apex partition as it's not part of the SystemPartitions.
305         if (dexPath.startsWith("/apex/")) {
306             return true;
307         }
308         for (int i = 0; i < partitions.size(); i++) {
309             if (partitions.get(i).containsPath(dexPath)) {
310                 return true;
311             }
312         }
313         return false;
314     }
315 
316     /**
317      * Read the dex usage from disk and populate the code cache locations.
318      * @param existingPackages a map containing information about what packages
319      *          are available to what users. Only packages in this list will be
320      *          recognized during notifyDexLoad().
321      */
load(Map<Integer, List<PackageInfo>> existingPackages)322     public void load(Map<Integer, List<PackageInfo>> existingPackages) {
323         try {
324             loadInternal(existingPackages);
325         } catch (RuntimeException e) {
326             mPackageDexUsage.clear();
327             Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
328         }
329     }
330 
331     /**
332      * Notifies that a new package was installed for {@code userId}.
333      * {@code userId} must not be {@code UserHandle.USER_ALL}.
334      *
335      * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}.
336      */
notifyPackageInstalled(PackageInfo pi, int userId)337     public void notifyPackageInstalled(PackageInfo pi, int userId) {
338         if (userId == UserHandle.USER_ALL) {
339             throw new IllegalArgumentException(
340                 "notifyPackageInstalled called with USER_ALL");
341         }
342         cachePackageInfo(pi, userId);
343     }
344 
345     /**
346      * Notifies that package {@code packageName} was updated.
347      * This will clear the UsedByOtherApps mark if it exists.
348      */
notifyPackageUpdated(String packageName, String baseCodePath, String[] splitCodePaths)349     public void notifyPackageUpdated(String packageName, String baseCodePath,
350             String[] splitCodePaths) {
351         cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1);
352         // In case there was an update, write the package use info to disk async.
353         // Note that we do the writing here and not in PackageDexUsage in order to be
354         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
355         // multiple updates in PackageDexUsage before writing it).
356         if (mPackageDexUsage.clearUsedByOtherApps(packageName)) {
357             mPackageDexUsage.maybeWriteAsync();
358         }
359     }
360 
361     /**
362      * Notifies that the user {@code userId} data for package {@code packageName}
363      * was destroyed. This will remove all usage info associated with the package
364      * for the given user.
365      * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case
366      * all usage information for the package will be removed.
367      */
notifyPackageDataDestroyed(String packageName, int userId)368     public void notifyPackageDataDestroyed(String packageName, int userId) {
369         // In case there was an update, write the package use info to disk async.
370         // Note that we do the writing here and not in the lower level classes in order to be
371         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
372         // multiple updates in PackageDexUsage before writing it).
373         if (userId == UserHandle.USER_ALL) {
374             if (mPackageDexUsage.removePackage(packageName)) {
375                 mPackageDexUsage.maybeWriteAsync();
376             }
377         } else {
378             if (mPackageDexUsage.removeUserPackage(packageName, userId)) {
379                 mPackageDexUsage.maybeWriteAsync();
380             }
381         }
382     }
383 
384     /**
385      * Caches the code location from the given package info.
386      */
cachePackageInfo(PackageInfo pi, int userId)387     private void cachePackageInfo(PackageInfo pi, int userId) {
388         ApplicationInfo ai = pi.applicationInfo;
389         String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir,
390                 ai.credentialProtectedDataDir};
391         cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs,
392                 dataDirs, userId);
393     }
394 
cachePackageCodeLocation(String packageName, String baseCodePath, String[] splitCodePaths, String[] dataDirs, int userId)395     private void cachePackageCodeLocation(String packageName, String baseCodePath,
396             String[] splitCodePaths, String[] dataDirs, int userId) {
397         synchronized (mPackageCodeLocationsCache) {
398             PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName,
399                     new PackageCodeLocations(packageName, baseCodePath, splitCodePaths));
400             // TODO(calin): We are forced to extend the scope of this synchronization because
401             // the values of the cache (PackageCodeLocations) are updated in place.
402             // Make PackageCodeLocations immutable to simplify the synchronization reasoning.
403             pcl.updateCodeLocation(baseCodePath, splitCodePaths);
404             if (dataDirs != null) {
405                 for (String dataDir : dataDirs) {
406                     // The set of data dirs includes deviceProtectedDataDir and
407                     // credentialProtectedDataDir which might be null for shared
408                     // libraries. Currently we don't track these but be lenient
409                     // and check in case we ever decide to store their usage data.
410                     if (dataDir != null) {
411                         pcl.mergeAppDataDirs(dataDir, userId);
412                     }
413                 }
414             }
415         }
416     }
417 
loadInternal(Map<Integer, List<PackageInfo>> existingPackages)418     private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
419         Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
420         Map<String, Set<String>> packageToCodePaths = new HashMap<>();
421 
422         // Cache the code locations for the installed packages. This allows for
423         // faster lookups (no locks) when finding what package owns the dex file.
424         for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
425             List<PackageInfo> packageInfoList = entry.getValue();
426             int userId = entry.getKey();
427             for (PackageInfo pi : packageInfoList) {
428                 // Cache the code locations.
429                 cachePackageInfo(pi, userId);
430 
431                 // Cache two maps:
432                 //   - from package name to the set of user ids who installed the package.
433                 //   - from package name to the set of code paths.
434                 // We will use it to sync the data and remove obsolete entries from
435                 // mPackageDexUsage.
436                 Set<Integer> users = putIfAbsent(
437                         packageToUsersMap, pi.packageName, new HashSet<>());
438                 users.add(userId);
439 
440                 Set<String> codePaths = putIfAbsent(
441                     packageToCodePaths, pi.packageName, new HashSet<>());
442                 codePaths.add(pi.applicationInfo.sourceDir);
443                 if (pi.applicationInfo.splitSourceDirs != null) {
444                     Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs);
445                 }
446             }
447         }
448 
449         try {
450             mPackageDexUsage.read();
451             List<String> packagesToKeepDataAbout = new ArrayList<>();
452             mPackageDexUsage.syncData(
453                     packageToUsersMap, packageToCodePaths, packagesToKeepDataAbout);
454         } catch (RuntimeException e) {
455             mPackageDexUsage.clear();
456             Slog.w(TAG, "Exception while loading package dex usage. "
457                     + "Starting with a fresh state.", e);
458         }
459     }
460 
461     /**
462      * Get the package dex usage for the given package name.
463      * If there is no usage info the method will return a default {@code PackageUseInfo} with
464      * no data about secondary dex files and marked as not being used by other apps.
465      *
466      * Note that no use info means the package was not used or it was used but not by other apps.
467      * Also, note that right now we might prune packages which are not used by other apps.
468      * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
469      * to access the package use.
470      */
getPackageUseInfoOrDefault(String packageName)471     public PackageUseInfo getPackageUseInfoOrDefault(String packageName) {
472         // We do not record packages that have no secondary dex files or that are not used by other
473         // apps. This is an optimization to reduce the amount of data that needs to be written to
474         // disk (apps will not usually be shared so this trims quite a bit the number we record).
475         //
476         // To make this behaviour transparent to the callers which need use information on packages,
477         // DexManager will return this DEFAULT instance from
478         // {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files
479         // and is marked as not being used by other apps. This reflects the intended behaviour when
480         // we don't find the package in the underlying data file.
481         PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName);
482         return useInfo == null ? new PackageUseInfo(packageName) : useInfo;
483     }
484 
485     /**
486      * Return whether or not the manager has usage information on the give package.
487      *
488      * Note that no use info means the package was not used or it was used but not by other apps.
489      * Also, note that right now we might prune packages which are not used by other apps.
490      * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
491      * to access the package use.
492      */
493     @VisibleForTesting
hasInfoOnPackage(String packageName)494     /*package*/ boolean hasInfoOnPackage(String packageName) {
495         return mPackageDexUsage.getPackageUseInfo(packageName) != null;
496     }
497 
498     /**
499      * Perform dexopt on with the given {@code options} on the secondary dex files.
500      * @return true if all secondary dex files were processed successfully (compiled or skipped
501      *         because they don't need to be compiled)..
502      */
dexoptSecondaryDex(DexoptOptions options)503     public boolean dexoptSecondaryDex(DexoptOptions options) throws LegacyDexoptDisabledException {
504         if (isPlatformPackage(options.getPackageName())) {
505             // We could easily redirect to #dexoptSystemServer in this case. But there should be
506             // no-one calling this method directly for system server.
507             // As such we prefer to abort in this case.
508             Slog.wtf(TAG, "System server jars should be optimized with dexoptSystemServer");
509             return false;
510         }
511 
512         PackageDexOptimizer pdo = getPackageDexOptimizer(options);
513         String packageName = options.getPackageName();
514         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
515         if (useInfo.getDexUseInfoMap().isEmpty()) {
516             if (DEBUG) {
517                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
518             }
519             // Nothing to compile, return true.
520             return true;
521         }
522         boolean success = true;
523         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
524             String dexPath = entry.getKey();
525             DexUseInfo dexUseInfo = entry.getValue();
526 
527             PackageInfo pkg;
528             try {
529                 pkg = getPackageManager().getPackageInfo(packageName, /*flags*/0,
530                     dexUseInfo.getOwnerUserId());
531             } catch (RemoteException e) {
532                 throw new AssertionError(e);
533             }
534             // It may be that the package gets uninstalled while we try to compile its
535             // secondary dex files. If that's the case, just ignore.
536             // Note that we don't break the entire loop because the package might still be
537             // installed for other users.
538             if (pkg == null) {
539                 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
540                         + " for user " + dexUseInfo.getOwnerUserId());
541                 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
542                 continue;
543             }
544 
545             int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
546                     dexUseInfo, options);
547             success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
548         }
549         return success;
550     }
551 
552     /**
553      * Select the dex optimizer based on the force parameter.
554      * Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
555      * the necessary dexopt flags to make sure that compilation is not skipped. This avoid
556      * passing the force flag through the multitude of layers.
557      * Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
558      *       allocate an object here.
559      */
getPackageDexOptimizer(DexoptOptions options)560     private PackageDexOptimizer getPackageDexOptimizer(DexoptOptions options) {
561         return options.isForce()
562                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
563                 : mPackageDexOptimizer;
564     }
565 
566     /**
567      * Reconcile the information we have about the secondary dex files belonging to
568      * {@code packagName} and the actual dex files. For all dex files that were
569      * deleted, update the internal records and delete any generated oat files.
570      */
reconcileSecondaryDexFiles(String packageName)571     public void reconcileSecondaryDexFiles(String packageName)
572             throws LegacyDexoptDisabledException {
573         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
574         if (useInfo.getDexUseInfoMap().isEmpty()) {
575             if (DEBUG) {
576                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
577             }
578             // Nothing to reconcile.
579             return;
580         }
581 
582         boolean updated = false;
583         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
584             String dexPath = entry.getKey();
585             DexUseInfo dexUseInfo = entry.getValue();
586             PackageInfo pkg = null;
587             try {
588                 // Note that we look for the package in the PackageManager just to be able
589                 // to get back the real app uid and its storage kind. These are only used
590                 // to perform extra validation in installd.
591                 // TODO(calin): maybe a bit overkill.
592                 pkg = getPackageManager().getPackageInfo(packageName, /*flags*/0,
593                     dexUseInfo.getOwnerUserId());
594             } catch (RemoteException ignore) {
595                 // Can't happen, DexManager is local.
596             }
597             if (pkg == null) {
598                 // It may be that the package was uninstalled while we process the secondary
599                 // dex files.
600                 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
601                         + " for user " + dexUseInfo.getOwnerUserId());
602                 // Update the usage and continue, another user might still have the package.
603                 updated = mPackageDexUsage.removeUserPackage(
604                         packageName, dexUseInfo.getOwnerUserId()) || updated;
605                 continue;
606             }
607 
608             // Special handle system server files.
609             // We don't need an installd call because we have permissions to check if the file
610             // exists.
611             if (isPlatformPackage(packageName)) {
612                 if (!Files.exists(Paths.get(dexPath))) {
613                     if (DEBUG) {
614                         Slog.w(TAG, "A dex file previously loaded by System Server does not exist "
615                                 + " anymore: " + dexPath);
616                     }
617                     updated = mPackageDexUsage.removeUserPackage(
618                             packageName, dexUseInfo.getOwnerUserId()) || updated;
619                 }
620                 continue;
621             }
622 
623             // This is a regular application.
624             ApplicationInfo info = pkg.applicationInfo;
625             int flags = 0;
626             if (info.deviceProtectedDataDir != null &&
627                     FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
628                 flags |= StorageManager.FLAG_STORAGE_DE;
629             } else if (info.credentialProtectedDataDir!= null &&
630                     FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
631                 flags |= StorageManager.FLAG_STORAGE_CE;
632             } else {
633                 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
634                 updated = mPackageDexUsage.removeDexFile(
635                         packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
636                 continue;
637             }
638 
639             boolean dexStillExists = true;
640             synchronized(mInstallLock) {
641                 try {
642                     String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
643                     dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
644                             info.uid, isas, info.volumeUuid, flags);
645                 } catch (InstallerException e) {
646                     Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
647                             " : " + e.getMessage());
648                 }
649             }
650             if (!dexStillExists) {
651                 updated = mPackageDexUsage.removeDexFile(
652                         packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
653             }
654 
655         }
656         if (updated) {
657             mPackageDexUsage.maybeWriteAsync();
658         }
659     }
660 
661     /**
662      * Return all packages that contain records of secondary dex files.
663      */
getAllPackagesWithSecondaryDexFiles()664     public Set<String> getAllPackagesWithSecondaryDexFiles() {
665         return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
666     }
667 
668     /**
669      * Retrieves the package which owns the given dexPath.
670      */
getDexPackage( ApplicationInfo loadingAppInfo, String dexPath, int userId)671     private DexSearchResult getDexPackage(
672             ApplicationInfo loadingAppInfo, String dexPath, int userId) {
673         // First, check if the package which loads the dex file actually owns it.
674         // Most of the time this will be true and we can return early.
675         PackageCodeLocations loadingPackageCodeLocations =
676                 new PackageCodeLocations(loadingAppInfo, userId);
677         int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
678         if (outcome != DEX_SEARCH_NOT_FOUND) {
679             // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
680             return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
681         }
682 
683         // The loadingPackage does not own the dex file.
684         // Perform a reverse look-up in the cache to detect if any package has ownership.
685         // Note that we can have false negatives if the cache falls out of date.
686         synchronized (mPackageCodeLocationsCache) {
687             for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
688                 outcome = pcl.searchDex(dexPath, userId);
689                 if (outcome != DEX_SEARCH_NOT_FOUND) {
690                     return new DexSearchResult(pcl.mPackageName, outcome);
691                 }
692             }
693         }
694 
695         // We could not find the owning package amongst regular apps.
696         // If the loading package is system server, see if the dex file resides
697         // on any of the potentially system server owning location and if so,
698         // assuming system server ownership.
699         //
700         // Note: We don't have any way to detect which code paths are actually
701         // owned by system server. We can only assume that such paths are on
702         // system partitions.
703         if (isPlatformPackage(loadingAppInfo.packageName)) {
704             if (isSystemServerDexPathSupportedForOdex(dexPath)) {
705                 // We record system server dex files as secondary dex files.
706                 // The reason is that we only record the class loader context for secondary dex
707                 // files and we expect that all primary apks are loaded with an empty class loader.
708                 // System server dex files may be loaded in non-empty class loader so we need to
709                 // keep track of their context.
710                 return new DexSearchResult(PLATFORM_PACKAGE_NAME, DEX_SEARCH_FOUND_SECONDARY);
711             } else {
712                 Slog.wtf(TAG, "System server loads dex files outside paths supported for odex: "
713                         + dexPath);
714             }
715         }
716 
717         if (DEBUG) {
718             // TODO(calin): Consider checking for /data/data symlink.
719             // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
720             // to load dex files through it.
721             try {
722                 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
723                 if (!dexPath.equals(dexPathReal)) {
724                     Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
725                             dexPath + " dexPathReal=" + dexPathReal);
726                 }
727             } catch (IOException e) {
728                 // Ignore
729             }
730         }
731         // Cache miss. The cache is updated during installs and uninstalls,
732         // so if we get here we're pretty sure the dex path does not exist.
733         return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
734     }
735 
736     /** Returns true if this is the platform package .*/
isPlatformPackage(String packageName)737     private static boolean isPlatformPackage(String packageName) {
738         return PLATFORM_PACKAGE_NAME.equals(packageName);
739     }
740 
putIfAbsent(Map<K,V> map, K key, V newValue)741     private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
742         V existingValue = map.putIfAbsent(key, newValue);
743         return existingValue == null ? newValue : existingValue;
744     }
745 
746     /**
747      * Writes the in-memory package dex usage to disk right away.
748      */
writePackageDexUsageNow()749     public void writePackageDexUsageNow() {
750         mPackageDexUsage.writeNow();
751     }
752 
753     /**
754      * Generates log if the archive located at {@code fileName} has uncompressed dex file that can
755      * be direclty mapped.
756      */
auditUncompressedDexInApk(String fileName)757     public static boolean auditUncompressedDexInApk(String fileName) {
758         StrictJarFile jarFile = null;
759         try {
760             jarFile = new StrictJarFile(fileName,
761                     false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
762             Iterator<ZipEntry> it = jarFile.iterator();
763             boolean allCorrect = true;
764             while (it.hasNext()) {
765                 ZipEntry entry = it.next();
766                 if (entry.getName().endsWith(".dex")) {
767                     if (entry.getMethod() != ZipEntry.STORED) {
768                         allCorrect = false;
769                         Slog.w(TAG, "APK " + fileName + " has compressed dex code " +
770                                 entry.getName());
771                     } else if ((entry.getDataOffset() & 0x3) != 0) {
772                         allCorrect = false;
773                         Slog.w(TAG, "APK " + fileName + " has unaligned dex code " +
774                                 entry.getName());
775                     }
776                 }
777             }
778             return allCorrect;
779         } catch (IOException ignore) {
780             Slog.wtf(TAG, "Error when parsing APK " + fileName);
781             return false;
782         } finally {
783             try {
784                 if (jarFile != null) {
785                     jarFile.close();
786                 }
787             } catch (IOException ignore) {}
788         }
789     }
790 
791     /**
792      * Translates install scenarios into compilation reasons.  This process can be influenced
793      * by the state of the device.
794      */
getCompilationReasonForInstallScenario(int installScenario)795     public int getCompilationReasonForInstallScenario(int installScenario) {
796         // Compute the compilation reason from the installation scenario.
797 
798         boolean resourcesAreCritical = areBatteryThermalOrMemoryCritical();
799         switch (installScenario) {
800             case PackageManager.INSTALL_SCENARIO_DEFAULT: {
801                 return PackageManagerService.REASON_INSTALL;
802             }
803             case PackageManager.INSTALL_SCENARIO_FAST: {
804                 return PackageManagerService.REASON_INSTALL_FAST;
805             }
806             case PackageManager.INSTALL_SCENARIO_BULK: {
807                 if (resourcesAreCritical) {
808                     return PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED;
809                 } else {
810                     return PackageManagerService.REASON_INSTALL_BULK;
811                 }
812             }
813             case PackageManager.INSTALL_SCENARIO_BULK_SECONDARY: {
814                 if (resourcesAreCritical) {
815                     return PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
816                 } else {
817                     return PackageManagerService.REASON_INSTALL_BULK_SECONDARY;
818                 }
819             }
820             default: {
821                 throw new IllegalArgumentException("Invalid installation scenario");
822             }
823         }
824     }
825 
826     /**
827      * Fetches the battery manager object and caches it if it hasn't been fetched already.
828      */
getBatteryManager()829     private BatteryManager getBatteryManager() {
830         if (mBatteryManager == null && mContext != null) {
831             mBatteryManager = mContext.getSystemService(BatteryManager.class);
832         }
833 
834         return mBatteryManager;
835     }
836 
837     /**
838      * Returns true if the battery level, device temperature, or memory usage are considered to be
839      * in a critical state.
840      */
areBatteryThermalOrMemoryCritical()841     private boolean areBatteryThermalOrMemoryCritical() {
842         BatteryManager batteryManager = getBatteryManager();
843         boolean isBtmCritical = (batteryManager != null
844                 && batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS)
845                     == BatteryManager.BATTERY_STATUS_DISCHARGING
846                 && batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
847                     <= mCriticalBatteryLevel)
848                 || (mPowerManager != null
849                     && mPowerManager.getCurrentThermalStatus()
850                         >= PowerManager.THERMAL_STATUS_SEVERE);
851 
852         return isBtmCritical;
853     }
854 
855     /**
856      * Deletes all the optimizations files generated by ART.
857      * This is best effort, and the method will log but not throw errors
858      * for individual deletes
859      *
860      * @param packageInfo the package information.
861      * @return the number of freed bytes or -1 if there was an error in the process.
862      */
deleteOptimizedFiles(ArtPackageInfo packageInfo)863     public long deleteOptimizedFiles(ArtPackageInfo packageInfo)
864             throws LegacyDexoptDisabledException {
865         long freedBytes = 0;
866         boolean hadErrors = false;
867         final String packageName = packageInfo.getPackageName();
868         for (String codePath : packageInfo.getCodePaths()) {
869             for (String isa : packageInfo.getInstructionSets()) {
870                 try {
871                     freedBytes += mInstaller.deleteOdex(packageName, codePath, isa,
872                             packageInfo.getOatDir());
873                 } catch (InstallerException e) {
874                     Log.e(TAG, "Failed deleting oat files for " + codePath, e);
875                     hadErrors = true;
876                 }
877             }
878         }
879         return hadErrors ? -1 : freedBytes;
880     }
881 
882     public static class RegisterDexModuleResult {
RegisterDexModuleResult()883         public RegisterDexModuleResult() {
884             this(false, null);
885         }
886 
RegisterDexModuleResult(boolean success, String message)887         public RegisterDexModuleResult(boolean success, String message) {
888             this.success = success;
889             this.message = message;
890         }
891 
892         public final boolean success;
893         public final String message;
894     }
895 
896     /**
897      * Convenience class to store the different locations where a package might
898      * own code.
899      */
900     private static class PackageCodeLocations {
901         private final String mPackageName;
902         private String mBaseCodePath;
903         private final Set<String> mSplitCodePaths;
904         // Maps user id to the application private directory.
905         private final Map<Integer, Set<String>> mAppDataDirs;
906 
PackageCodeLocations(ApplicationInfo ai, int userId)907         public PackageCodeLocations(ApplicationInfo ai, int userId) {
908             this(ai.packageName, ai.sourceDir, ai.splitSourceDirs);
909             mergeAppDataDirs(ai.dataDir, userId);
910         }
PackageCodeLocations(String packageName, String baseCodePath, String[] splitCodePaths)911         public PackageCodeLocations(String packageName, String baseCodePath,
912                 String[] splitCodePaths) {
913             mPackageName = packageName;
914             mSplitCodePaths = new HashSet<>();
915             mAppDataDirs = new HashMap<>();
916             updateCodeLocation(baseCodePath, splitCodePaths);
917         }
918 
updateCodeLocation(String baseCodePath, String[] splitCodePaths)919         public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) {
920             mBaseCodePath = baseCodePath;
921             mSplitCodePaths.clear();
922             if (splitCodePaths != null) {
923                 for (String split : splitCodePaths) {
924                     mSplitCodePaths.add(split);
925                 }
926             }
927         }
928 
mergeAppDataDirs(String dataDir, int userId)929         public void mergeAppDataDirs(String dataDir, int userId) {
930             Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
931             dataDirs.add(dataDir);
932         }
933 
searchDex(String dexPath, int userId)934         public int searchDex(String dexPath, int userId) {
935             // First check that this package is installed or active for the given user.
936             // A missing data dir means the package is not installed.
937             Set<String> userDataDirs = mAppDataDirs.get(userId);
938             if (userDataDirs == null) {
939                 return DEX_SEARCH_NOT_FOUND;
940             }
941 
942             if (mBaseCodePath.equals(dexPath)) {
943                 return DEX_SEARCH_FOUND_PRIMARY;
944             }
945             if (mSplitCodePaths.contains(dexPath)) {
946                 return DEX_SEARCH_FOUND_SPLIT;
947             }
948             for (String dataDir : userDataDirs) {
949                 if (dexPath.startsWith(dataDir)) {
950                     return DEX_SEARCH_FOUND_SECONDARY;
951                 }
952             }
953 
954             return DEX_SEARCH_NOT_FOUND;
955         }
956     }
957 
958     /**
959      * Convenience class to store ownership search results.
960      */
961     private class DexSearchResult {
962         private String mOwningPackageName;
963         private int mOutcome;
964 
DexSearchResult(String owningPackageName, int outcome)965         public DexSearchResult(String owningPackageName, int outcome) {
966             this.mOwningPackageName = owningPackageName;
967             this.mOutcome = outcome;
968         }
969 
970         @Override
toString()971         public String toString() {
972             return mOwningPackageName + "-" + mOutcome;
973         }
974     }
975 }
976