From: Calin Juravle Date: Wed, 25 Jan 2017 09:05:50 +0000 (-0800) Subject: [PM] Clean up logic for secondary dex oat files X-Git-Tag: android-x86-8.1-r1~2121^2~9^2~120^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=e69fba3f7f;p=android-x86%2Fframeworks-base.git [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 (cherry picked from commit c22c30ed1c05c5c24185dc4d380d1c5026923d46) Change-Id: Ied9fcbfe367ed3a8250a9ba8d202518b264c64e8 Merged-In: Id2d72dc89995f89cf1ddf79ae4e992afd3f3c127 --- diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 4ed84ab34561..d27b0df08b18 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -510,6 +510,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 * MountService 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 bc80374435ea..42a0bad302bf 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -433,6 +433,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); + } + } + private static void assertValidInstructionSet(String instructionSet) throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b99de780edcc..328109ebd1a1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2133,7 +2133,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( @@ -7517,6 +7517,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); + } + Collection findSharedNonSystemLibraries(PackageParser.Package p) { if (p.usesLibraries != null || p.usesOptionalLibraries != null) { ArrayList retValue = new ArrayList<>(); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 181069672bc2..5b2bf8062fa6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -110,6 +110,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": @@ -416,6 +418,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); @@ -1468,6 +1476,8 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -3: filter to only show third party packages"); pw.println(" -i: see the installer for the packages"); pw.println(" -u: also include uninstalled packages"); + 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 c5f1646d3c81..28bf58fdfbef 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; } /** @@ -255,7 +266,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, @@ -266,6 +277,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 dexFilesToRemove = new HashSet<>(); + for (Map.Entry 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 { } } + /** + * 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> 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 4d76312a2b1c..90a2ec0c7628 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;