Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cdeb61eb authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Add a runtime check to ensure that system server jars are prefetched."...

Merge "Add a runtime check to ensure that system server jars are prefetched." am: 990ac01f am: a80c2cd0

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1925695

Change-Id: I38f4a3f9f523fc2a59c0dd9569870c0a7de7379f
parents b5fd2d8e a80c2cd0
Loading
Loading
Loading
Loading
+53 −9
Original line number Diff line number Diff line
@@ -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(
    /* 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;
    }


}
+1 −1
Original line number Diff line number Diff line
@@ -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
+26 −2
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
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;
@@ -34,6 +36,7 @@ import com.android.internal.os.SystemServerClassLoaderFactory;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService.TargetUser;
import com.android.server.am.EventLogTags;
import com.android.server.pm.ApexManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;

@@ -42,6 +45,8 @@ import dalvik.system.PathClassLoader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;

/**
@@ -114,12 +119,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.
     */
+37 −0
Original line number Diff line number Diff line
@@ -342,6 +342,13 @@ public abstract class ApexManager {
    @Nullable
    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.
@@ -466,6 +473,12 @@ public abstract class ApexManager {
        @GuardedBy("mLock")
        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.
         *
@@ -534,6 +547,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());
@@ -580,6 +594,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
@@ -912,6 +933,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;
@@ -1329,6 +1360,12 @@ public abstract class ApexManager {
            return null;
        }

        @Override
        @Nullable
        public String getActivePackageNameForApexModuleName(String apexModuleName) {
            return null;
        }

        @Override
        public boolean snapshotCeData(int userId, int rollbackId, String apexPackageName) {
            throw new UnsupportedOperationException();
+14 −0
Original line number Diff line number Diff line
@@ -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) {
        File apexFile = extractResource(TEST_APEX_PKG,  TEST_APEX_FILE_NAME);
        ApexInfo apexInfo = new ApexInfo();