diff options
author | 2022-02-24 16:10:43 +0000 | |
---|---|---|
committer | 2022-02-24 16:10:43 +0000 | |
commit | 1ab404993d480dce788c2b0aa5af4f6758cc22f7 (patch) | |
tree | c5fc8cade53ee4a87c3662c1d0a5638a73a3ed2e | |
parent | c1931a482e74f3f6151218401f30e2fd5ab54e2c (diff) | |
parent | 2c2dfad53538e9e5d0262d57ee3e8629ae88546e (diff) |
Merge changes from topic "presubmit-am-5e074e05e4c74163b4f9a025baec3cd0"
* changes:
[automerge] Add a runtime check to ensure that system server jars are prefetched. 2p: 418ab8212c 2p: 1cab79eff5
[automerge] Add a runtime check to ensure that system server jars are prefetched. 2p: 418ab8212c
Add a runtime check to ensure that system server jars are prefetched.
5 files changed, 131 insertions, 12 deletions
diff --git a/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java b/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java index 615e4b793752..a03bac45d14f 100644 --- a/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java +++ b/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java @@ -29,22 +29,66 @@ public final class SystemServerClassLoaderFactory { private static final ArrayMap<String, PathClassLoader> sLoadedPaths = new ArrayMap<>(); /** - * Creates and caches a ClassLoader for the jar at the given path, or returns a cached - * ClassLoader if it exists. + * Creates and caches a ClassLoader for the jar at the given path. + * + * This method should only be called by ZygoteInit to prefetch jars. For other users, use + * {@link getOrCreateClassLoader} instead. * * The parent class loader should always be the system server class loader. Changing it has * implications that require discussion with the mainline team. * * @hide for internal use only */ - public static PathClassLoader getOrCreateClassLoader(String path, ClassLoader parent) { - PathClassLoader pathClassLoader = sLoadedPaths.get(path); - if (pathClassLoader == null) { - pathClassLoader = (PathClassLoader) ClassLoaderFactory.createClassLoader( - path, /*librarySearchPath=*/null, /*libraryPermittedPath=*/null, parent, - Build.VERSION.SDK_INT, /*isNamespaceShared=*/true , /*classLoaderName=*/null); - sLoadedPaths.put(path, pathClassLoader); + /* package */ static PathClassLoader createClassLoader(String path, ClassLoader parent) { + if (sLoadedPaths.containsKey(path)) { + throw new IllegalStateException("A ClassLoader for " + path + " already exists"); } + PathClassLoader pathClassLoader = (PathClassLoader) ClassLoaderFactory.createClassLoader( + path, /*librarySearchPath=*/null, /*libraryPermittedPath=*/null, parent, + Build.VERSION.SDK_INT, /*isNamespaceShared=*/true , /*classLoaderName=*/null); + sLoadedPaths.put(path, pathClassLoader); return pathClassLoader; } + + /** + * Returns a cached ClassLoader to be used at runtime for the jar at the given path. Or, creates + * one if it is not prefetched and is allowed to be created at runtime. + * + * The parent class loader should always be the system server class loader. Changing it has + * implications that require discussion with the mainline team. + * + * @hide for internal use only + */ + public static PathClassLoader getOrCreateClassLoader( + String path, ClassLoader parent, boolean isTestOnly) { + PathClassLoader pathClassLoader = sLoadedPaths.get(path); + if (pathClassLoader != null) { + return pathClassLoader; + } + if (!allowClassLoaderCreation(path, isTestOnly)) { + throw new RuntimeException("Creating a ClassLoader from " + path + " is not allowed. " + + "Please make sure that the jar is listed in " + + "`PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS` in the Makefile and added as a " + + "`standalone_contents` of a `systemserverclasspath_fragment` in " + + "`Android.bp`."); + } + return createClassLoader(path, parent); + } + + /** + * Returns whether a class loader for the jar is allowed to be created at runtime. + */ + private static boolean allowClassLoaderCreation(String path, boolean isTestOnly) { + // Currently, we only enforce prefetching for APEX jars. + if (!path.startsWith("/apex/")) { + return true; + } + // APEXes for testing only are okay to ignore. + if (isTestOnly) { + return true; + } + return false; + } + + } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 3d24aa2db247..ca1ae194cb12 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -586,7 +586,7 @@ public class ZygoteInit { } for (String jar : envStr.split(":")) { try { - SystemServerClassLoaderFactory.getOrCreateClassLoader( + SystemServerClassLoaderFactory.createClassLoader( jar, getOrCreateSystemServerClassLoader()); } catch (Error e) { // We don't want the process to crash for this error because prefetching is just an diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index 12e438d1f2a9..78df983c83f7 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -21,6 +21,8 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.UserInfo; import android.os.Environment; import android.os.SystemClock; @@ -38,6 +40,7 @@ import com.android.internal.util.Preconditions; import com.android.server.SystemService.TargetUser; import com.android.server.SystemService.UserCompletedEventType; import com.android.server.am.EventLogTags; +import com.android.server.pm.ApexManager; import com.android.server.pm.UserManagerInternal; import com.android.server.utils.TimingsTraceAndSlog; @@ -47,6 +50,8 @@ import java.io.File; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -147,12 +152,31 @@ public final class SystemServiceManager implements Dumpable { * @return The service instance. */ public SystemService startServiceFromJar(String className, String path) { - PathClassLoader pathClassLoader = SystemServerClassLoaderFactory.getOrCreateClassLoader( - path, this.getClass().getClassLoader()); + PathClassLoader pathClassLoader = + SystemServerClassLoaderFactory.getOrCreateClassLoader( + path, this.getClass().getClassLoader(), isJarInTestApex(path)); final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader); return startService(serviceClass); } + /** + * Returns true if the jar is in a test APEX. + */ + private static boolean isJarInTestApex(String pathStr) { + Path path = Paths.get(pathStr); + if (path.getNameCount() >= 2 && path.getName(0).toString().equals("apex")) { + String apexModuleName = path.getName(1).toString(); + ApexManager apexManager = ApexManager.getInstance(); + String packageName = apexManager.getActivePackageNameForApexModuleName(apexModuleName); + PackageInfo packageInfo = apexManager.getPackageInfo( + packageName, ApexManager.MATCH_ACTIVE_PACKAGE); + if (packageInfo != null) { + return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; + } + } + return false; + } + /* * Loads and initializes a class from the given classLoader. Returns the class. */ diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index a5067439db58..76d3d233d49a 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -347,6 +347,13 @@ public abstract class ApexManager { public abstract String getApexModuleNameForPackageName(String apexPackageName); /** + * Returns the package name of the active APEX whose name is {@code apexModuleName}. If not + * found, returns {@code null}. + */ + @Nullable + public abstract String getActivePackageNameForApexModuleName(String apexModuleName); + + /** * Copies the CE apex data directory for the given {@code userId} to a backup location, for use * in case of rollback. * @@ -484,6 +491,12 @@ public abstract class ApexManager { private ArrayMap<String, String> mPackageNameToApexModuleName; /** + * Reverse mapping of {@link #mPackageNameToApexModuleName}, for active packages only. + */ + @GuardedBy("mLock") + private ArrayMap<String, String> mApexModuleNameToActivePackageName; + + /** * Whether an APEX package is active or not. * * @param packageInfo the package to check @@ -551,6 +564,7 @@ public abstract class ApexManager { try { mAllPackagesCache = new ArrayList<>(); mPackageNameToApexModuleName = new ArrayMap<>(); + mApexModuleNameToActivePackageName = new ArrayMap<>(); allPkgs = waitForApexService().getAllPackages(); } catch (RemoteException re) { Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); @@ -633,6 +647,13 @@ public abstract class ApexManager { + packageInfo.packageName); } activePackagesSet.add(packageInfo.packageName); + if (mApexModuleNameToActivePackageName.containsKey(ai.moduleName)) { + throw new IllegalStateException( + "Two active packages have the same APEX module name: " + + ai.moduleName); + } + mApexModuleNameToActivePackageName.put( + ai.moduleName, packageInfo.packageName); } if (ai.isFactory) { // Don't throw when the duplicating APEX is VNDK APEX @@ -966,6 +987,16 @@ public abstract class ApexManager { } @Override + @Nullable + public String getActivePackageNameForApexModuleName(String apexModuleName) { + synchronized (mLock) { + Preconditions.checkState(mApexModuleNameToActivePackageName != null, + "APEX packages have not been scanned"); + return mApexModuleNameToActivePackageName.get(apexModuleName); + } + } + + @Override public boolean snapshotCeData(int userId, int rollbackId, String apexPackageName) { String apexModuleName; synchronized (mLock) { @@ -1390,6 +1421,12 @@ public abstract class ApexManager { } @Override + @Nullable + public String getActivePackageNameForApexModuleName(String apexModuleName) { + return null; + } + + @Override public boolean snapshotCeData(int userId, int rollbackId, String apexPackageName) { throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java index 5d3da43c5327..c7a903be3bd2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java @@ -489,6 +489,20 @@ public class ApexManagerTest { assertThat(e).hasMessageThat().contains("Failed to collect certificates from "); } + @Test + public void testGetActivePackageNameForApexModuleName() throws Exception { + final String moduleName = "com.android.module_name"; + + ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false); + apexInfo[0].moduleName = moduleName; + when(mApexService.getAllPackages()).thenReturn(apexInfo); + mApexManager.scanApexPackagesTraced(mPackageParser2, + ParallelPackageParser.makeExecutorService()); + + assertThat(mApexManager.getActivePackageNameForApexModuleName(moduleName)) + .isEqualTo(TEST_APEX_PKG); + } + private ApexInfo createApexInfoForTestPkg(boolean isActive, boolean isFactory, int version) { File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME); ApexInfo apexInfo = new ApexInfo(); |