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