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