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