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