DexManager.java revision 61fd6eab463d5b86ef177537c149ee45a0a40dcc
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 move/uninstall notifications to
158                // capture package moves 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    public void notifyPackageInstalled(PackageInfo info, int userId) {
183        cachePackageCodeLocation(info, userId);
184    }
185
186    private void cachePackageCodeLocation(PackageInfo info, int userId) {
187        PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName);
188        if (pcl != null) {
189            pcl.mergeAppDataDirs(info.applicationInfo, userId);
190        } else {
191            mPackageCodeLocationsCache.put(info.packageName,
192                new PackageCodeLocations(info.applicationInfo, userId));
193        }
194    }
195
196    private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
197        Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
198        // Cache the code locations for the installed packages. This allows for
199        // faster lookups (no locks) when finding what package owns the dex file.
200        for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
201            List<PackageInfo> packageInfoList = entry.getValue();
202            int userId = entry.getKey();
203            for (PackageInfo pi : packageInfoList) {
204                // Cache the code locations.
205                cachePackageCodeLocation(pi, userId);
206
207                // Cache a map from package name to the set of user ids who installed the package.
208                // We will use it to sync the data and remove obsolete entries from
209                // mPackageDexUsage.
210                Set<Integer> users = putIfAbsent(
211                        packageToUsersMap, pi.packageName, new HashSet<>());
212                users.add(userId);
213            }
214        }
215
216        mPackageDexUsage.read();
217        mPackageDexUsage.syncData(packageToUsersMap);
218    }
219
220    /**
221     * Get the package dex usage for the given package name.
222     * @return the package data or null if there is no data available for this package.
223     */
224    public PackageUseInfo getPackageUseInfo(String packageName) {
225        return mPackageDexUsage.getPackageUseInfo(packageName);
226    }
227
228    /**
229     * Perform dexopt on the package {@code packageName} secondary dex files.
230     * @return true if all secondary dex files were processed successfully (compiled or skipped
231     *         because they don't need to be compiled)..
232     */
233    public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
234        // Select the dex optimizer based on the force parameter.
235        // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
236        // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
237        // passing the force flag through the multitude of layers.
238        // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
239        //       allocate an object here.
240        PackageDexOptimizer pdo = force
241                ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
242                : mPackageDexOptimizer;
243        PackageUseInfo useInfo = getPackageUseInfo(packageName);
244        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
245            if (DEBUG) {
246                Slog.d(TAG, "No secondary dex use for package:" + packageName);
247            }
248            // Nothing to compile, return true.
249            return true;
250        }
251        boolean success = true;
252        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
253            String dexPath = entry.getKey();
254            DexUseInfo dexUseInfo = entry.getValue();
255            PackageInfo pkg = null;
256            try {
257                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
258                    dexUseInfo.getOwnerUserId());
259            } catch (RemoteException e) {
260                throw new AssertionError(e);
261            }
262            // It may be that the package gets uninstalled while we try to compile its
263            // secondary dex files. If that's the case, just ignore.
264            // Note that we don't break the entire loop because the package might still be
265            // installed for other users.
266            if (pkg == null) {
267                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
268                        + " for user " + dexUseInfo.getOwnerUserId());
269                mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
270                continue;
271            }
272            int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
273                    dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
274            success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
275        }
276        return success;
277    }
278
279    /**
280     * Reconcile the information we have about the secondary dex files belonging to
281     * {@code packagName} and the actual dex files. For all dex files that were
282     * deleted, update the internal records and delete any generated oat files.
283     */
284    public void reconcileSecondaryDexFiles(String packageName) {
285        PackageUseInfo useInfo = getPackageUseInfo(packageName);
286        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
287            if (DEBUG) {
288                Slog.d(TAG, "No secondary dex use for package:" + packageName);
289            }
290            // Nothing to reconcile.
291            return;
292        }
293        Set<String> dexFilesToRemove = new HashSet<>();
294        boolean updated = false;
295        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
296            String dexPath = entry.getKey();
297            DexUseInfo dexUseInfo = entry.getValue();
298            PackageInfo pkg = null;
299            try {
300                // Note that we look for the package in the PackageManager just to be able
301                // to get back the real app uid and its storage kind. These are only used
302                // to perform extra validation in installd.
303                // TODO(calin): maybe a bit overkill.
304                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
305                    dexUseInfo.getOwnerUserId());
306            } catch (RemoteException ignore) {
307                // Can't happen, DexManager is local.
308            }
309            if (pkg == null) {
310                // It may be that the package was uninstalled while we process the secondary
311                // dex files.
312                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
313                        + " for user " + dexUseInfo.getOwnerUserId());
314                // Update the usage and continue, another user might still have the package.
315                updated = mPackageDexUsage.removeUserPackage(
316                        packageName, dexUseInfo.getOwnerUserId()) || updated;
317                continue;
318            }
319            ApplicationInfo info = pkg.applicationInfo;
320            int flags = 0;
321            if (info.dataDir.equals(info.deviceProtectedDataDir)) {
322                flags |= StorageManager.FLAG_STORAGE_DE;
323            } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
324                flags |= StorageManager.FLAG_STORAGE_CE;
325            } else {
326                Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
327                updated = mPackageDexUsage.removeUserPackage(
328                        packageName, dexUseInfo.getOwnerUserId()) || updated;
329                continue;
330            }
331
332            boolean dexStillExists = true;
333            synchronized(mInstallLock) {
334                try {
335                    String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
336                    dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
337                            pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
338                } catch (InstallerException e) {
339                    Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
340                            " : " + e.getMessage());
341                }
342            }
343            if (!dexStillExists) {
344                updated = mPackageDexUsage.removeDexFile(
345                        packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
346            }
347
348        }
349        if (updated) {
350            mPackageDexUsage.maybeWriteAsync();
351        }
352    }
353
354    /**
355     * Return all packages that contain records of secondary dex files.
356     */
357    public Set<String> getAllPackagesWithSecondaryDexFiles() {
358        return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
359    }
360
361    /**
362     * Retrieves the package which owns the given dexPath.
363     */
364    private DexSearchResult getDexPackage(
365            ApplicationInfo loadingAppInfo, String dexPath, int userId) {
366        // Ignore framework code.
367        // TODO(calin): is there a better way to detect it?
368        if (dexPath.startsWith("/system/framework/")) {
369            new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
370        }
371
372        // First, check if the package which loads the dex file actually owns it.
373        // Most of the time this will be true and we can return early.
374        PackageCodeLocations loadingPackageCodeLocations =
375                new PackageCodeLocations(loadingAppInfo, userId);
376        int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
377        if (outcome != DEX_SEARCH_NOT_FOUND) {
378            // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
379            return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
380        }
381
382        // The loadingPackage does not own the dex file.
383        // Perform a reverse look-up in the cache to detect if any package has ownership.
384        // Note that we can have false negatives if the cache falls out of date.
385        for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
386            outcome = pcl.searchDex(dexPath, userId);
387            if (outcome != DEX_SEARCH_NOT_FOUND) {
388                return new DexSearchResult(pcl.mPackageName, outcome);
389            }
390        }
391
392        // Cache miss. Return not found for the moment.
393        //
394        // TODO(calin): this may be because of a newly installed package, an update
395        // or a new added user. We can either perform a full look up again or register
396        // observers to be notified of package/user updates.
397        return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
398    }
399
400    private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
401        V existingValue = map.putIfAbsent(key, newValue);
402        return existingValue == null ? newValue : existingValue;
403    }
404
405    /**
406     * Convenience class to store the different locations where a package might
407     * own code.
408     */
409    private static class PackageCodeLocations {
410        private final String mPackageName;
411        private final String mBaseCodePath;
412        private final Set<String> mSplitCodePaths;
413        // Maps user id to the application private directory.
414        private final Map<Integer, Set<String>> mAppDataDirs;
415
416        public PackageCodeLocations(ApplicationInfo ai, int userId) {
417            mPackageName = ai.packageName;
418            mBaseCodePath = ai.sourceDir;
419            mSplitCodePaths = new HashSet<>();
420            if (ai.splitSourceDirs != null) {
421                for (String split : ai.splitSourceDirs) {
422                    mSplitCodePaths.add(split);
423                }
424            }
425            mAppDataDirs = new HashMap<>();
426            mergeAppDataDirs(ai, userId);
427        }
428
429        public void mergeAppDataDirs(ApplicationInfo ai, int userId) {
430            Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
431            dataDirs.add(ai.dataDir);
432        }
433
434        public int searchDex(String dexPath, int userId) {
435            // First check that this package is installed or active for the given user.
436            // If we don't have a data dir it means this user is trying to load something
437            // unavailable for them.
438            Set<String> userDataDirs = mAppDataDirs.get(userId);
439            if (userDataDirs == null) {
440                Slog.w(TAG, "Trying to load a dex path which does not exist for the current " +
441                        "user. dexPath=" + dexPath + ", userId=" + userId);
442                return DEX_SEARCH_NOT_FOUND;
443            }
444
445            if (mBaseCodePath.equals(dexPath)) {
446                return DEX_SEARCH_FOUND_PRIMARY;
447            }
448            if (mSplitCodePaths.contains(dexPath)) {
449                return DEX_SEARCH_FOUND_SPLIT;
450            }
451            for (String dataDir : userDataDirs) {
452                if (dexPath.startsWith(dataDir)) {
453                    return DEX_SEARCH_FOUND_SECONDARY;
454                }
455            }
456
457            // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink,
458            // /data/data/ -> /data/user/0/.
459            if (DEBUG) {
460                try {
461                    String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
462                    if (dexPathReal != dexPath) {
463                        Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
464                                dexPath + " dexPathReal=" + dexPathReal);
465                    }
466                } catch (IOException e) {
467                    // Ignore
468                }
469            }
470            return DEX_SEARCH_NOT_FOUND;
471        }
472    }
473
474    /**
475     * Convenience class to store ownership search results.
476     */
477    private class DexSearchResult {
478        private String mOwningPackageName;
479        private int mOutcome;
480
481        public DexSearchResult(String owningPackageName, int outcome) {
482            this.mOwningPackageName = owningPackageName;
483            this.mOutcome = outcome;
484        }
485
486        @Override
487        public String toString() {
488            return mOwningPackageName + "-" + mOutcome;
489        }
490    }
491
492
493}
494