diff options
| author | 2017-01-25 01:05:50 -0800 | |
|---|---|---|
| committer | 2017-01-27 18:54:35 -0800 | |
| commit | 1aa5f88e35734383e66ecd65e82e83d788e18ccb (patch) | |
| tree | 5deb05529421d94a6daf8a0fd8ec8a8d0bb689a9 | |
| parent | c22c30ed1c05c5c24185dc4d380d1c5026923d46 (diff) | |
[PM] Clean up logic for secondary dex oat files
Add logic in DexManager to reconcile secondary dex records with the
actual files on disk. If secondary dex files are moved or removed then
DexManager will remove the generated oat files during the call to
reconcileSecondaryOdex() and update its internal state.
Add 'adb shell cmd package reconcile-secondary-dex packageName' which
will force DexManager to sync its data with the actual secondary dex
files.
Test: devices bots
runtest -x .../PackageDexUsageTests.java
runtest -x .../DexManagerTests.java
adb shell cmd package reconcile-secondary-dex
com.android.google.gms (after artificially/temporarily renaming some
dex files)
Bug: 32871170
Change-Id: Id2d72dc89995f89cf1ddf79ae4e992afd3f3c127
8 files changed, 205 insertions, 5 deletions
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 775043055b5b..1ba747122fdc 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -518,6 +518,13 @@ interface IPackageManager { void forceDexOpt(String packageName); /** + * Reconcile the information we have about the secondary dex files belonging to + * {@code packagName} and the actual dex files. For all dex files that were + * deleted, update the internal records and delete the generated oat files. + */ + void reconcileSecondaryDexFiles(String packageName); + + /** * Update status of external media on the package manager to scan and * install packages installed on the external media. Like say the * StorageManagerService uses this to call into the package manager to update diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index e1b4c845d9a4..449d8086e083 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -442,6 +442,20 @@ public class Installer extends SystemService { } } + public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid, + String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException { + for (int i = 0; i < isas.length; i++) { + assertValidInstructionSet(isas[i]); + } + if (!checkBeforeRemote()) return false; + try { + return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas, + volumeUuid, flags); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + public void invalidateMounts() throws InstallerException { if (!checkBeforeRemote()) return; try { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c6b7ddef9cd7..f6bf2db0c584 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2212,7 +2212,7 @@ public class PackageManagerService extends IPackageManager.Stub { mInstaller = installer; mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*"); - mDexManager = new DexManager(this, mPackageDexOptimizer); + mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners( @@ -8220,6 +8220,16 @@ public class PackageManagerService extends IPackageManager.Stub { return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force); } + /** + * Reconcile the information we have about the secondary dex files belonging to + * {@code packagName} and the actual dex files. For all dex files that were + * deleted, update the internal records and delete the generated oat files. + */ + @Override + public void reconcileSecondaryDexFiles(String packageName) { + mDexManager.reconcileSecondaryDexFiles(packageName); + } + List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) { if (p.usesLibraries != null || p.usesOptionalLibraries != null || p.usesStaticLibraries != null) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 4078245de9f3..a179a6dc3f49 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -118,6 +118,8 @@ class PackageManagerShellCommand extends ShellCommand { return runInstallWrite(); case "compile": return runCompile(); + case "reconcile-secondary-dex-files": + return runreconcileSecondaryDexFiles(); case "dump-profiles": return runDumpProfiles(); case "list": @@ -441,6 +443,12 @@ class PackageManagerShellCommand extends ShellCommand { } } + private int runreconcileSecondaryDexFiles() throws RemoteException { + String packageName = getNextArg(); + mInterface.reconcileSecondaryDexFiles(packageName); + return 0; + } + private int runDumpProfiles() throws RemoteException { String packageName = getNextArg(); mInterface.dumpProfiles(packageName); @@ -1547,6 +1555,8 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -u: also include uninstalled packages"); pw.println(" --uid UID: filter to only show packages with the given UID"); pw.println(" --user USER_ID: only list packages belonging to the given user"); + pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE"); + pw.println(" Reconciles the package secondary dex files with the generated oat files."); pw.println(" list permission-groups"); pw.println(" Prints all known permission groups."); pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]"); diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index a179aab401e4..e978700340eb 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -21,8 +21,13 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageParser; import android.os.RemoteException; +import android.os.storage.StorageManager; + import android.util.Slog; +import com.android.internal.annotations.GuardedBy; +import com.android.server.pm.Installer; +import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PackageDexOptimizer; import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.PackageManagerServiceCompilerMapping; @@ -62,6 +67,9 @@ public class DexManager { private final IPackageManager mPackageManager; private final PackageDexOptimizer mPackageDexOptimizer; + private final Object mInstallLock; + @GuardedBy("mInstallLock") + private final Installer mInstaller; // Possible outcomes of a dex search. private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found @@ -69,11 +77,14 @@ public class DexManager { private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex - public DexManager(IPackageManager pms, PackageDexOptimizer pdo) { + public DexManager(IPackageManager pms, PackageDexOptimizer pdo, + Installer installer, Object installLock) { mPackageCodeLocationsCache = new HashMap<>(); mPackageDexUsage = new PackageDexUsage(); mPackageManager = pms; mPackageDexOptimizer = pdo; + mInstaller = installer; + mInstallLock = installLock; } /** @@ -246,7 +257,7 @@ public class DexManager { if (pkg == null) { Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName + " for user " + dexUseInfo.getOwnerUserId()); - // Skip over it, another user might still have the package. + mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); continue; } int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, @@ -257,6 +268,73 @@ public class DexManager { } /** + * Reconcile the information we have about the secondary dex files belonging to + * {@code packagName} and the actual dex files. For all dex files that were + * deleted, update the internal records and delete any generated oat files. + */ + public void reconcileSecondaryDexFiles(String packageName) { + PackageUseInfo useInfo = getPackageUseInfo(packageName); + if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { + if (DEBUG) { + Slog.d(TAG, "No secondary dex use for package:" + packageName); + } + // Nothing to reconcile. + return; + } + Set<String> dexFilesToRemove = new HashSet<>(); + for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { + String dexPath = entry.getKey(); + DexUseInfo dexUseInfo = entry.getValue(); + PackageInfo pkg = null; + try { + // Note that we look for the package in the PackageManager just to be able + // to get back the real app uid and its storage kind. These are only used + // to perform extra validation in installd. + // TODO(calin): maybe a bit overkill. + pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, + dexUseInfo.getOwnerUserId()); + } catch (RemoteException ignore) { + // Can't happen, DexManager is local. + } + if (pkg == null) { + // It may be that the package was uninstalled while we process the secondary + // dex files. + Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName + + " for user " + dexUseInfo.getOwnerUserId()); + // Update the usage and continue, another user might still have the package. + mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); + continue; + } + ApplicationInfo info = pkg.applicationInfo; + int flags = 0; + if (info.dataDir.equals(info.deviceProtectedDataDir)) { + flags |= StorageManager.FLAG_STORAGE_DE; + } else if (info.dataDir.equals(info.credentialProtectedDataDir)) { + flags |= StorageManager.FLAG_STORAGE_CE; + } else { + Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName); + mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); + continue; + } + + boolean dexStillExists = true; + synchronized(mInstallLock) { + try { + String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]); + dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName, + pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags); + } catch (InstallerException e) { + Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath + + " : " + e.getMessage()); + } + } + if (!dexStillExists) { + mPackageDexUsage.removeDexFile(packageName, dexPath, dexUseInfo.getOwnerUserId()); + } + } + } + + /** * Retrieves the package which owns the given dexPath. */ private DexSearchResult getDexPackage( diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 10384a2749e8..253d0e97f51e 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -376,9 +376,64 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } } + /** + * Remove all the records about package {@code packageName} belonging to user {@code userId}. + */ + public boolean removeUserPackage(String packageName, int userId) { + synchronized (mPackageUseInfoMap) { + PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); + if (packageUseInfo == null) { + return false; + } + boolean updated = false; + Iterator<Map.Entry<String, DexUseInfo>> dIt = + packageUseInfo.mDexUseInfoMap.entrySet().iterator(); + while (dIt.hasNext()) { + DexUseInfo dexUseInfo = dIt.next().getValue(); + if (dexUseInfo.mOwnerUserId == userId) { + dIt.remove(); + updated = true; + } + } + return updated; + } + } + + /** + * Remove the secondary dex file record belonging to the package {@code packageName} + * and user {@code userId}. + */ + public boolean removeDexFile(String packageName, String dexFile, int userId) { + synchronized (mPackageUseInfoMap) { + PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); + if (packageUseInfo == null) { + return false; + } + return removeDexFile(packageUseInfo, dexFile, userId); + } + } + + private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) { + DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile); + if (dexUseInfo == null) { + return false; + } + if (dexUseInfo.mOwnerUserId == userId) { + packageUseInfo.mDexUseInfoMap.remove(dexFile); + return true; + } + return false; + } + public PackageUseInfo getPackageUseInfo(String packageName) { synchronized (mPackageUseInfoMap) { - return mPackageUseInfoMap.get(packageName); + PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); + // The useInfo contains a map for secondary dex files which could be modified + // concurrently after this method returns and thus outside the locking we do here. + // (i.e. the map is updated when new class loaders are created, which can happen anytime + // after this method returns) + // Make a defensive copy to be sure we don't get concurrent modifications. + return useInfo == null ? null : new PackageUseInfo(useInfo); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index 0361cb5c6dbf..c9036c39f7ca 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -73,7 +73,7 @@ public class DexManagerTests { mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0); mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1); - mDexManager = new DexManager(null, null); + mDexManager = new DexManager(null, null, null, null); // Foo and Bar are available to user0. // Only Bar is available to user1; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index 5a428414a970..19e0bcf6ba56 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -256,6 +256,32 @@ public class PackageDexUsageTests { assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName)); } + @Test + public void testRemoveUserPackage() { + // Record Bar secondaries for two different users. + assertTrue(record(mBarSecondary1User0)); + assertTrue(record(mBarSecondary2User1)); + + // Remove user 0 files. + assertTrue(mPackageDexUsage.removeUserPackage(mBarSecondary1User0.mPackageName, + mBarSecondary1User0.mOwnerUserId)); + // Assert that only user 1 files are there. + assertPackageDexUsage(null, mBarSecondary2User1); + } + + @Test + public void testRemoveDexFile() { + // Record Bar secondaries for two different users. + assertTrue(record(mBarSecondary1User0)); + assertTrue(record(mBarSecondary2User1)); + + // Remove mBarSecondary1User0 file. + assertTrue(mPackageDexUsage.removeDexFile(mBarSecondary1User0.mPackageName, + mBarSecondary1User0.mDexFile, mBarSecondary1User0.mOwnerUserId)); + // Assert that only user 1 files are there. + assertPackageDexUsage(null, mBarSecondary2User1); + } + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps; |