From c6d1dec22ca189a059807360678269cfa67d74ec Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Thu, 30 Dec 2021 16:48:08 +0000 Subject: [PATCH] Allow framework-res APK splits from Apexes Test: m FrameworkResApkSplitsHostTest Bug: 195732042 Change-Id: I516e6267c661e112c234aed27e2eef2ce9ffaf9a --- .../src/android/os/PackageParsingPerfTest.kt | 2 +- .../content/pm/parsing/ApkLiteParseUtils.java | 58 ++++++++++++++----- .../server/pm/InitAndSystemPackageHelper.java | 50 ++++++++++++---- .../server/pm/InstallPackageHelper.java | 7 ++- .../server/pm/ParallelPackageParser.java | 11 +++- .../server/pm/parsing/PackageParser2.java | 18 ++++-- .../pm/pkg/parsing/ParsingPackageUtils.java | 47 +++++++++++---- .../src/com/android/server/pm/MockSystem.kt | 2 + .../pm/PackageManagerServiceBootTest.kt | 4 +- 9 files changed, 155 insertions(+), 44 deletions(-) diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt index e873514f11a06..67a33802f5560 100644 --- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt @@ -218,7 +218,7 @@ public class PackageParsingPerfTest { }) override fun parseImpl(file: File) = - parser.parsePackage(input.get()!!.reset(), file, 0).result + parser.parsePackage(input.get()!!.reset(), file, 0, null).result as ParsingPackageImpl } diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index d5498a0dc8cd2..165cae8261875 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -78,6 +78,7 @@ public class ApkLiteParseUtils { public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; private static final int PARSE_IS_SYSTEM_DIR = 1 << 4; private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5; + private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8; private static final String TAG_APPLICATION = "application"; private static final String TAG_PACKAGE_VERIFIER = "package-verifier"; private static final String TAG_PROFILEABLE = "profileable"; @@ -101,7 +102,7 @@ public class ApkLiteParseUtils { public static ParseResult parsePackageLite(ParseInput input, File packageFile, int flags) { if (packageFile.isDirectory()) { - return parseClusterPackageLite(input, packageFile, flags); + return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags); } else { return parseMonolithicPackageLite(input, packageFile, flags); } @@ -134,21 +135,44 @@ public class ApkLiteParseUtils { /** * Parse lightweight details about a directory of APKs. + * + * @param packageDirOrApk is the folder that contains split apks for a regular app or the + * framework-res.apk for framwork-res splits (in which case the + * splits come in the frameworkSplits parameter) */ public static ParseResult parseClusterPackageLite(ParseInput input, - File packageDir, int flags) { - final File[] files = packageDir.listFiles(); - if (ArrayUtils.isEmpty(files)) { - return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, - "No packages found in split"); + File packageDirOrApk, List frameworkSplits, int flags) { + final File[] files; + final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0; + if (parsingFrameworkSplits) { + if (ArrayUtils.isEmpty(frameworkSplits)) { + return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, + "No packages found in split"); + } + files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]); + // we also want to process the base apk so add it to the array + files[files.length - 1] = packageDirOrApk; + } else { + files = packageDirOrApk.listFiles(); + if (ArrayUtils.isEmpty(files)) { + return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, + "No packages found in split"); + } + // Apk directory is directly nested under the current directory + if (files.length == 1 && files[0].isDirectory()) { + return parseClusterPackageLite(input, files[0], frameworkSplits, flags); + } } - // Apk directory is directly nested under the current directory - if (files.length == 1 && files[0].isDirectory()) { - return parseClusterPackageLite(input, files[0], flags); + + if (parsingFrameworkSplits) { + // disable the flag for checking the certificates of the splits. We know they + // won't match, but we rely on the mainline apex to be safe if it was installed + flags = flags & ~PARSE_COLLECT_CERTIFICATES; } String packageName = null; int versionCode = 0; + ApkLite baseApk = null; final ArrayMap apks = new ArrayMap<>(); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); @@ -161,6 +185,10 @@ public class ApkLiteParseUtils { } final ApkLite lite = result.getResult(); + if (parsingFrameworkSplits && file == files[files.length - 1]) { + baseApk = lite; + break; + } // Assert that all package names and version codes are // consistent with the first one we encounter. if (packageName == null) { @@ -172,7 +200,8 @@ public class ApkLiteParseUtils { "Inconsistent package " + lite.getPackageName() + " in " + file + "; expected " + packageName); } - if (versionCode != lite.getVersionCode()) { + // we allow version codes that do not match for framework splits + if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, "Inconsistent version " + lite.getVersionCode() + " in " + file + "; expected " + versionCode); @@ -187,12 +216,15 @@ public class ApkLiteParseUtils { } } } + // baseApk is set in the last iteration of the for each loop when we are parsing + // frameworkRes splits or needs to be done now otherwise + if (!parsingFrameworkSplits) { + baseApk = apks.remove(null); + } } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - - final ApkLite baseApk = apks.remove(null); - return composePackageLiteFromApks(input, packageDir, baseApk, apks); + return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks); } /** diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java index 9efe81aed24d3..06405ae32fcf3 100644 --- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java +++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java @@ -30,8 +30,10 @@ import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX; import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN; import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS; import static com.android.server.pm.PackageManagerService.TAG; +import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS; import android.annotation.Nullable; +import android.content.pm.parsing.ApkLiteParseUtils; import android.os.Environment; import android.os.SystemClock; import android.os.Trace; @@ -91,6 +93,29 @@ final class InitAndSystemPackageHelper { mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM; } + private List getFrameworkResApkSplitFiles() { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits"); + try { + final List splits = new ArrayList<>(); + final List activeApexInfos = + mPm.mApexManager.getActiveApexInfos(); + for (int i = 0; i < activeApexInfos.size(); i++) { + ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i); + File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits"); + if (splitsFolder.isDirectory()) { + for (File file : splitsFolder.listFiles()) { + if (ApkLiteParseUtils.isApkFile(file)) { + splits.add(file); + } + } + } + } + return splits; + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + } + private List getSystemScanPartitions() { final List scanPartitions = new ArrayList<>(); scanPartitions.addAll(mPm.mInjector.getSystemPartitions()); @@ -184,7 +209,8 @@ final class InitAndSystemPackageHelper { if (!mPm.isOnlyCoreApps()) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis()); - scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0, + scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0, + mScanFlags | SCAN_REQUIRE_KNOWN, 0, packageParser, executorService); } @@ -247,12 +273,14 @@ final class InitAndSystemPackageHelper { if (partition.getOverlayFolder() == null) { continue; } - scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags, - mSystemScanFlags | partition.scanFlag, 0, + scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null, + mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0, packageParser, executorService); } - scanDirTracedLI(frameworkDir, mSystemParseFlags, + List frameworkSplits = getFrameworkResApkSplitFiles(); + scanDirTracedLI(frameworkDir, frameworkSplits, + mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS, mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0, packageParser, executorService); if (!mPm.mPackages.containsKey("android")) { @@ -263,12 +291,13 @@ final class InitAndSystemPackageHelper { for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) { final ScanPartition partition = mDirsToScanAsSystem.get(i); if (partition.getPrivAppFolder() != null) { - scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags, + scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null, + mSystemParseFlags, mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0, packageParser, executorService); } - scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags, - mSystemScanFlags | partition.scanFlag, 0, + scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null, + mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0, packageParser, executorService); } } @@ -285,12 +314,13 @@ final class InitAndSystemPackageHelper { } @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, + private void scanDirTracedLI(File scanDir, List frameworkSplits, + final int parseFlags, int scanFlags, long currentTime, PackageParser2 packageParser, ExecutorService executorService) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]"); try { - mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags, - currentTime, packageParser, executorService); + mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags, + scanFlags, currentTime, packageParser, executorService); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 6a5d76bc248b4..afd01bee67780 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -3401,8 +3401,9 @@ final class InstallPackageHelper { } @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags, - long currentTime, PackageParser2 packageParser, ExecutorService executorService) { + public void installPackagesFromDir(File scanDir, List frameworkSplits, int parseFlags, + int scanFlags, long currentTime, PackageParser2 packageParser, + ExecutorService executorService) { final File[] files = scanDir.listFiles(); if (ArrayUtils.isEmpty(files)) { Log.d(TAG, "No files in app dir " + scanDir); @@ -3414,7 +3415,7 @@ final class InstallPackageHelper { + " flags=0x" + Integer.toHexString(parseFlags)); } ParallelPackageParser parallelPackageParser = - new ParallelPackageParser(packageParser, executorService); + new ParallelPackageParser(packageParser, executorService, frameworkSplits); // Submit files for parsing in parallel int fileCount = 0; diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java index 56258844a3fef..45030bfa59cec 100644 --- a/services/core/java/com/android/server/pm/ParallelPackageParser.java +++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java @@ -27,6 +27,7 @@ import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.ParsedPackage; import java.io.File; +import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; @@ -54,9 +55,17 @@ class ParallelPackageParser { private final ExecutorService mExecutorService; + private final List mFrameworkSplits; + ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) { + this(packageParser, executorService, /* frameworkSplits= */ null); + } + + ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService, + List frameworkSplits) { mPackageParser = packageParser; mExecutorService = executorService; + mFrameworkSplits = frameworkSplits; } static class ParseResult { @@ -125,6 +134,6 @@ class ParallelPackageParser { @VisibleForTesting protected ParsedPackage parsePackage(File scanFile, int parseFlags) throws PackageManagerException { - return mPackageParser.parsePackage(scanFile, parseFlags, true); + return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits); } } diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java index 08e2f7da1ebf5..b2e15e7a5bab5 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java +++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java @@ -22,9 +22,6 @@ import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; import android.content.pm.ApplicationInfo; -import com.android.server.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; @@ -41,6 +38,9 @@ import com.android.server.pm.PackageManagerException; import com.android.server.pm.PackageManagerService; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import java.io.File; import java.util.List; @@ -147,6 +147,15 @@ public class PackageParser2 implements AutoCloseable { @AnyThread public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) throws PackageManagerException { + return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null); + } + + /** + * TODO(b/135203078): Document new package parsing + */ + @AnyThread + public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches, + List frameworkSplits) throws PackageManagerException { if (useCaches && mCacher != null) { ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags); if (parsed != null) { @@ -156,7 +165,8 @@ public class PackageParser2 implements AutoCloseable { long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; ParseInput input = mSharedResult.get().reset(); - ParseResult result = parsingUtils.parsePackage(input, packageFile, flags); + ParseResult result = parsingUtils.parsePackage(input, packageFile, flags, + frameworkSplits); if (result.isError()) { throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(), result.getException()); diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 1ce01f6337912..1888d85cd8ebd 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -65,6 +65,7 @@ import android.content.res.XmlResourceParser; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.os.Parcel; import android.os.RemoteException; import android.os.SystemProperties; @@ -230,6 +231,8 @@ public class ParsingPackageUtils { * of required system property within the overlay tag. */ public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; + public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8; + public static final int PARSE_CHATTY = 1 << 31; @IntDef(flag = true, prefix = { "PARSE_" }, value = { @@ -241,6 +244,7 @@ public class ParsingPackageUtils { PARSE_IS_SYSTEM_DIR, PARSE_MUST_BE_APK, PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY, + PARSE_FRAMEWORK_RES_SPLITS }) @Retention(RetentionPolicy.SOURCE) public @interface ParseFlags {} @@ -289,7 +293,7 @@ public class ParsingPackageUtils { return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray); } }); - result = parser.parsePackage(input, file, parseFlags); + result = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null); if (result.isError()) { return input.error(result); } @@ -343,26 +347,44 @@ public class ParsingPackageUtils { * not check whether {@code packageFile} has changed since the last parse, it's up to callers to * do so. */ - public ParseResult parsePackage(ParseInput input, File packageFile, int flags) { - if (packageFile.isDirectory()) { - return parseClusterPackage(input, packageFile, flags); + public ParseResult parsePackage(ParseInput input, File packageFile, int flags, + List frameworkSplits) { + if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) + && frameworkSplits.size() > 0 + && packageFile.getAbsolutePath().endsWith("/framework-res.apk")) { + return parseClusterPackage(input, packageFile, frameworkSplits, flags); + } else if (packageFile.isDirectory()) { + return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags); } else { return parseMonolithicPackage(input, packageFile, flags); } } /** - * Parse all APKs contained in the given directory, treating them as a single package. This also - * performs validity checking, such as requiring identical package name and version codes, a - * single base APK, and unique split names. + * Parse all APKs contained in the given directory, treating them as a + * single package. This also performs validity checking, such as requiring + * identical package name and version codes, a single base APK, and unique + * split names. + *

+ * Can also be passed the framework-res.apk file and a list of split apks coming from apexes + * (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages + * even if they are in different folders. Note that this code path may have other behaviour + * differences. *

* Note that this does not perform signature verification; that must be done separately * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. */ private ParseResult parseClusterPackage(ParseInput input, File packageDir, - int flags) { + List frameworkSplits, int flags) { + // parseClusterPackageLite should receive no flags (0) for regular splits but we want to + // pass the flags for framework splits + int liteParseFlags = 0; + if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) { + liteParseFlags = flags; + } final ParseResult liteResult = - ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0); + ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits, + liteParseFlags); if (liteResult.isError()) { return input.error(liteResult); } @@ -2989,9 +3011,12 @@ public class ParsingPackageUtils { } signingDetails = result.getResult(); - + final File frameworkRes = new File(Environment.getRootDirectory(), + "framework/framework-res.apk"); + boolean isFrameworkResSplit = frameworkRes.getAbsolutePath() + .equals(pkg.getBaseApkPath()); String[] splitCodePaths = pkg.getSplitCodePaths(); - if (!ArrayUtils.isEmpty(splitCodePaths)) { + if (!ArrayUtils.isEmpty(splitCodePaths) && !isFrameworkResSplit) { for (int i = 0; i < splitCodePaths.length; i++) { result = getSigningDetails( input, diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index c2e0a04e3caa9..12c1c4669e57b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -519,6 +519,8 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { val parsedPackage = parseResult.hideAsParsed() as ParsedPackage whenever(mocks.packageParser.parsePackage( or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage } + whenever(mocks.packageParser.parsePackage( + or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage } return parsedPackage } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt index dbd540371d779..0820a3c24c7ec 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt @@ -33,6 +33,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.argThat @@ -121,7 +122,8 @@ class PackageManagerServiceBootTest { whenever(rule.mocks().packageParser.parsePackage( argThat { path: File -> path.path.contains("a.data.package") }, anyInt(), - anyBoolean())) + anyBoolean(), + any())) .thenThrow(PackageManagerException( PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!")) val pm = createPackageManagerService()