DexManager.java revision 51f521c3bf46e6040f36757bc53ea57ddc7be85e
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.content.pm.PackageParser;
23import android.os.RemoteException;
24import android.os.storage.StorageManager;
25
26import android.util.Slog;
27
28import com.android.internal.annotations.GuardedBy;
29import com.android.server.pm.Installer;
30import com.android.server.pm.Installer.InstallerException;
31import com.android.server.pm.PackageDexOptimizer;
32import com.android.server.pm.PackageManagerServiceUtils;
33import com.android.server.pm.PackageManagerServiceCompilerMapping;
34
35import java.io.File;
36import java.io.IOException;
37import java.util.List;
38import java.util.HashMap;
39import java.util.HashSet;
40import java.util.Map;
41import java.util.Set;
42
43import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
44import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
45
46/**
47 * This class keeps track of how dex files are used.
48 * Every time it gets a notification about a dex file being loaded it tracks
49 * its owning package and records it in PackageDexUsage (package-dex-usage.list).
50 *
51 * TODO(calin): Extract related dexopt functionality from PackageManagerService
52 * into this class.
53 */
54public class DexManager {
55    private static final String TAG = "DexManager";
56
57    private static final boolean DEBUG = false;
58
59    // Maps package name to code locations.
60    // It caches the code locations for the installed packages. This allows for
61    // faster lookups (no locks) when finding what package owns the dex file.
62    private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;
63
64    // PackageDexUsage handles the actual I/O operations. It is responsible to
65    // encode and save the dex usage data.
66    private final PackageDexUsage mPackageDexUsage;
67
68    private final IPackageManager mPackageManager;
69    private final PackageDexOptimizer mPackageDexOptimizer;
70    private final Object mInstallLock;
71    @GuardedBy("mInstallLock")
72    private final Installer mInstaller;
73
74    // Possible outcomes of a dex search.
75    private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
76    private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
77    private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
78    private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
79
80    public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
81            Installer installer, Object installLock) {
82      mPackageCodeLocationsCache = new HashMap<>();
83      mPackageDexUsage = new PackageDexUsage();
84      mPackageManager = pms;
85      mPackageDexOptimizer = pdo;
86      mInstaller = installer;
87      mInstallLock = installLock;
88    }
89
90    /**
91     * Notify about dex files loads.
92     * Note that this method is invoked when apps load dex files and it should
93     * return as fast as possible.
94     *
95     * @param loadingPackage the package performing the load
96     * @param dexPaths the list of dex files being loaded
97     * @param loaderIsa the ISA of the app loading the dex files
98     * @param loaderUserId the user id which runs the code loading the dex files
99     */
100    public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths,
101            String loaderIsa, int loaderUserId) {
102        try {
103            notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId);
104        } catch (Exception e) {
105            Slog.w(TAG, "Exception while notifying dex load for package " +
106                    loadingAppInfo.packageName, e);
107        }
108    }
109
110    private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths,
111            String loaderIsa, int loaderUserId) {
112        if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
113            Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " +
114                    loaderIsa + "?");
115            return;
116        }
117
118        for (String dexPath : dexPaths) {
119            // Find the owning package name.
120            DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
121
122            if (DEBUG) {
123                Slog.i(TAG, loadingAppInfo.packageName
124                    + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
125            }
126
127            if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
128                // TODO(calin): extend isUsedByOtherApps check to detect the cases where
129                // different apps share the same runtime. In that case we should not mark the dex
130                // file as isUsedByOtherApps. Currently this is a safe approximation.
131                boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals(
132                        searchResult.mOwningPackageName);
133                boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
134                        searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;
135
136                if (primaryOrSplit && !isUsedByOtherApps) {
137                    // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
138                    // do not record it. This case does not bring any new usable information
139                    // and can be safely skipped.
140                    continue;
141                }
142
143                // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
144                // or UsedBytOtherApps), record will return true and we trigger an async write
145                // to disk to make sure we don't loose the data in case of a reboot.
146                if (mPackageDexUsage.record(searchResult.mOwningPackageName,
147                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) {
148                    mPackageDexUsage.maybeWriteAsync();
149                }
150            } else {
151                // This can happen in a few situations:
152                // - bogus dex loads
153                // - recent installs/uninstalls that we didn't detect.
154                // - new installed splits
155                // If we can't find the owner of the dex we simply do not track it. The impact is
156                // that the dex file will not be considered for offline optimizations.
157                // TODO(calin): add hooks for install/uninstall notifications to
158                // capture new or obsolete packages.
159                if (DEBUG) {
160                    Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
161                }
162            }
163        }
164    }
165
166    /**
167     * Read the dex usage from disk and populate the code cache locations.
168     * @param existingPackages a map containing information about what packages
169     *          are available to what users. Only packages in this list will be
170     *          recognized during notifyDexLoad().
171     */
172    public void load(Map<Integer, List<PackageInfo>> existingPackages) {
173        try {
174            loadInternal(existingPackages);
175        } catch (Exception e) {
176            mPackageDexUsage.clear();
177            Slog.w(TAG, "Exception while loading package dex usage. " +
178                    "Starting with a fresh state.", e);
179        }
180    }
181
182    private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
183        Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
184        // Cache the code locations for the installed packages. This allows for
185        // faster lookups (no locks) when finding what package owns the dex file.
186        for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
187            List<PackageInfo> packageInfoList = entry.getValue();
188            int userId = entry.getKey();
189            for (PackageInfo pi : packageInfoList) {
190                // Cache the code locations.
191                PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName);
192                if (pcl != null) {
193                    pcl.mergeAppDataDirs(pi.applicationInfo, userId);
194                } else {
195                    mPackageCodeLocationsCache.put(pi.packageName,
196                        new PackageCodeLocations(pi.applicationInfo, userId));
197                }
198                // Cache a map from package name to the set of user ids who installed the package.
199                // We will use it to sync the data and remove obsolete entries from
200                // mPackageDexUsage.
201                Set<Integer> users = putIfAbsent(
202                        packageToUsersMap, pi.packageName, new HashSet<>());
203                users.add(userId);
204            }
205        }
206
207        mPackageDexUsage.read();
208        mPackageDexUsage.syncData(packageToUsersMap);
209    }
210
211    /**
212     * Get the package dex usage for the given package name.
213     * @return the package data or null if there is no data available for this package.
214     */
215    public PackageUseInfo getPackageUseInfo(String packageName) {
216        return mPackageDexUsage.getPackageUseInfo(packageName);
217    }
218
219    /**
220     * Perform dexopt on the package {@code packageName} secondary dex files.
221     * @return true if all secondary dex files were processed successfully (compiled or skipped
222     *         because they don't need to be compiled)..
223     */
224    public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
225        // Select the dex optimizer based on the force parameter.
226        // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
227        // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
228        // passing the force flag through the multitude of layers.
229        // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
230        //       allocate an object here.
231        PackageDexOptimizer pdo = force
232                ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
233                : mPackageDexOptimizer;
234        PackageUseInfo useInfo = getPackageUseInfo(packageName);
235        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
236            if (DEBUG) {
237                Slog.d(TAG, "No secondary dex use for package:" + packageName);
238            }
239            // Nothing to compile, return true.
240            return true;
241        }
242        boolean success = true;
243        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
244            String dexPath = entry.getKey();
245            DexUseInfo dexUseInfo = entry.getValue();
246            PackageInfo pkg = null;
247            try {
248                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
249                    dexUseInfo.getOwnerUserId());
250            } catch (RemoteException e) {
251                throw new AssertionError(e);
252            }
253            // It may be that the package gets uninstalled while we try to compile its
254            // secondary dex files. If that's the case, just ignore.
255            // Note that we don't break the entire loop because the package might still be
256            // installed for other users.
257            if (pkg == null) {
258                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
259                        + " for user " + dexUseInfo.getOwnerUserId());
260                mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
261                continue;
262            }
263            int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
264                    dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
265            success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
266        }
267        return success;
268    }
269
270    /**
271     * Reconcile the information we have about the secondary dex files belonging to
272     * {@code packagName} and the actual dex files. For all dex files that were
273     * deleted, update the internal records and delete any generated oat files.
274     */
275    public void reconcileSecondaryDexFiles(String packageName) {
276        PackageUseInfo useInfo = getPackageUseInfo(packageName);
277        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
278            if (DEBUG) {
279                Slog.d(TAG, "No secondary dex use for package:" + packageName);
280            }
281            // Nothing to reconcile.
282            return;
283        }
284        Set<String> dexFilesToRemove = new HashSet<>();
285        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
286            String dexPath = entry.getKey();
287            DexUseInfo dexUseInfo = entry.getValue();
288            PackageInfo pkg = null;
289            try {
290                // Note that we look for the package in the PackageManager just to be able
291                // to get back the real app uid and its storage kind. These are only used
292                // to perform extra validation in installd.
293                // TODO(calin): maybe a bit overkill.
294                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
295                    dexUseInfo.getOwnerUserId());
296            } catch (RemoteException ignore) {
297                // Can't happen, DexManager is local.
298            }
299            if (pkg == null) {
300                // It may be that the package was uninstalled while we process the secondary
301                // dex files.
302                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
303                        + " for user " + dexUseInfo.getOwnerUserId());
304                // Update the usage and continue, another user might still have the package.
305                mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
306                continue;
307            }
308            ApplicationInfo info = pkg.applicationInfo;
309            int flags = 0;
310            if (info.dataDir.equals(info.deviceProtectedDataDir)) {
311                flags |= StorageManager.FLAG_STORAGE_DE;
312            } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
313                flags |= StorageManager.FLAG_STORAGE_CE;
314            } else {
315                Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
316                mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
317                continue;
318            }
319
320            boolean dexStillExists = true;
321            synchronized(mInstallLock) {
322                try {
323                    String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
324                    dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
325                            pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
326                } catch (InstallerException e) {
327                    Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
328                            " : " + e.getMessage());
329                }
330            }
331            if (!dexStillExists) {
332                mPackageDexUsage.removeDexFile(packageName, dexPath, dexUseInfo.getOwnerUserId());
333            }
334        }
335    }
336
337    /**
338     * Return all packages that contain records of secondary dex files.
339     */
340    public Set<String> getAllPackagesWithSecondaryDexFiles() {
341        return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
342    }
343
344    /**
345     * Retrieves the package which owns the given dexPath.
346     */
347    private DexSearchResult getDexPackage(
348            ApplicationInfo loadingAppInfo, String dexPath, int userId) {
349        // Ignore framework code.
350        // TODO(calin): is there a better way to detect it?
351        if (dexPath.startsWith("/system/framework/")) {
352            new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
353        }
354
355        // First, check if the package which loads the dex file actually owns it.
356        // Most of the time this will be true and we can return early.
357        PackageCodeLocations loadingPackageCodeLocations =
358                new PackageCodeLocations(loadingAppInfo, userId);
359        int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
360        if (outcome != DEX_SEARCH_NOT_FOUND) {
361            // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
362            return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
363        }
364
365        // The loadingPackage does not own the dex file.
366        // Perform a reverse look-up in the cache to detect if any package has ownership.
367        // Note that we can have false negatives if the cache falls out of date.
368        for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
369            outcome = pcl.searchDex(dexPath, userId);
370            if (outcome != DEX_SEARCH_NOT_FOUND) {
371                return new DexSearchResult(pcl.mPackageName, outcome);
372            }
373        }
374
375        // Cache miss. Return not found for the moment.
376        //
377        // TODO(calin): this may be because of a newly installed package, an update
378        // or a new added user. We can either perform a full look up again or register
379        // observers to be notified of package/user updates.
380        return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
381    }
382
383    private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
384        V existingValue = map.putIfAbsent(key, newValue);
385        return existingValue == null ? newValue : existingValue;
386    }
387
388    /**
389     * Convenience class to store the different locations where a package might
390     * own code.
391     */
392    private static class PackageCodeLocations {
393        private final String mPackageName;
394        private final String mBaseCodePath;
395        private final Set<String> mSplitCodePaths;
396        // Maps user id to the application private directory.
397        private final Map<Integer, Set<String>> mAppDataDirs;
398
399        public PackageCodeLocations(ApplicationInfo ai, int userId) {
400            mPackageName = ai.packageName;
401            mBaseCodePath = ai.sourceDir;
402            mSplitCodePaths = new HashSet<>();
403            if (ai.splitSourceDirs != null) {
404                for (String split : ai.splitSourceDirs) {
405                    mSplitCodePaths.add(split);
406                }
407            }
408            mAppDataDirs = new HashMap<>();
409            mergeAppDataDirs(ai, userId);
410        }
411
412        public void mergeAppDataDirs(ApplicationInfo ai, int userId) {
413            Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
414            dataDirs.add(ai.dataDir);
415        }
416
417        public int searchDex(String dexPath, int userId) {
418            // First check that this package is installed or active for the given user.
419            // If we don't have a data dir it means this user is trying to load something
420            // unavailable for them.
421            Set<String> userDataDirs = mAppDataDirs.get(userId);
422            if (userDataDirs == null) {
423                Slog.w(TAG, "Trying to load a dex path which does not exist for the current " +
424                        "user. dexPath=" + dexPath + ", userId=" + userId);
425                return DEX_SEARCH_NOT_FOUND;
426            }
427
428            if (mBaseCodePath.equals(dexPath)) {
429                return DEX_SEARCH_FOUND_PRIMARY;
430            }
431            if (mSplitCodePaths.contains(dexPath)) {
432                return DEX_SEARCH_FOUND_SPLIT;
433            }
434            for (String dataDir : userDataDirs) {
435                if (dexPath.startsWith(dataDir)) {
436                    return DEX_SEARCH_FOUND_SECONDARY;
437                }
438            }
439
440            // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink,
441            // /data/data/ -> /data/user/0/.
442            if (DEBUG) {
443                try {
444                    String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
445                    if (dexPathReal != dexPath) {
446                        Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
447                                dexPath + " dexPathReal=" + dexPathReal);
448                    }
449                } catch (IOException e) {
450                    // Ignore
451                }
452            }
453            return DEX_SEARCH_NOT_FOUND;
454        }
455    }
456
457    /**
458     * Convenience class to store ownership search results.
459     */
460    private class DexSearchResult {
461        private String mOwningPackageName;
462        private int mOutcome;
463
464        public DexSearchResult(String owningPackageName, int outcome) {
465            this.mOwningPackageName = owningPackageName;
466            this.mOutcome = outcome;
467        }
468
469        @Override
470        public String toString() {
471            return mOwningPackageName + "-" + mOutcome;
472        }
473    }
474
475
476}
477