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

Commit 76e66906 authored by Ryan Mitchell's avatar Ryan Mitchell
Browse files

Extract system partitions into standalone class

Extract the order system partition list from package manager so
OverlayConfig can reuse the PMS partition order.

Bug: 119442586
Bug: 135048762
Test: ScanTests and PackageManagerServiceTest
Change-Id: If281c5d47e3551eb635c466af6d2400c514518eb
parent 004e36da
Loading
Loading
Loading
Loading
+213 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content.pm;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Environment;
import android.os.FileUtils;

import com.android.internal.annotations.VisibleForTesting;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Function;

/**
 * Exposes {@link #SYSTEM_PARTITIONS} which represents the partitions in which application packages
 * can be installed. The partitions are ordered from most generic (lowest priority) to most specific
 * (greatest priority).
 *
 * @hide
 **/
public class PackagePartitions {
    public static final int PARTITION_SYSTEM = 0;
    public static final int PARTITION_VENDOR = 1;
    public static final int PARTITION_ODM = 2;
    public static final int PARTITION_OEM = 3;
    public static final int PARTITION_PRODUCT = 4;
    public static final int PARTITION_SYSTEM_EXT = 5;

    @IntDef(flag = true, prefix = { "PARTITION_" }, value = {
        PARTITION_SYSTEM,
        PARTITION_VENDOR,
        PARTITION_ODM,
        PARTITION_OEM,
        PARTITION_PRODUCT,
        PARTITION_SYSTEM_EXT
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface PartitionType {}

    /**
     * The list of all system partitions that may contain packages in ascending order of
     * specificity (the more generic, the earlier in the list a partition appears).
     */
    private static final ArrayList<SystemPartition> SYSTEM_PARTITIONS =
            new ArrayList<>(Arrays.asList(
                    new SystemPartition(Environment.getRootDirectory(), PARTITION_SYSTEM,
                            true /* containsPrivApp */, false /* containsOverlay */),
                    new SystemPartition(Environment.getVendorDirectory(), PARTITION_VENDOR,
                            true /* containsPrivApp */, true /* containsOverlay */),
                    new SystemPartition(Environment.getOdmDirectory(), PARTITION_ODM,
                            true /* containsPrivApp */, true /* containsOverlay */),
                    new SystemPartition(Environment.getOemDirectory(), PARTITION_OEM,
                            false /* containsPrivApp */, true /* containsOverlay */),
                    new SystemPartition(Environment.getProductDirectory(), PARTITION_PRODUCT,
                            true /* containsPrivApp */, true /* containsOverlay */),
                    new SystemPartition(Environment.getSystemExtDirectory(), PARTITION_SYSTEM_EXT,
                            true /* containsPrivApp */, true /* containsOverlay */)));

    /**
     * Returns a list in which the elements are products of the specified function applied to the
     * list of {@link #SYSTEM_PARTITIONS} in increasing specificity order.
     */
    public static <T> ArrayList<T> getOrderedPartitions(
            @NonNull Function<SystemPartition, T> producer) {
        final ArrayList<T> out = new ArrayList<>();
        for (int i = 0, n = SYSTEM_PARTITIONS.size(); i < n; i++) {
            final T v = producer.apply(SYSTEM_PARTITIONS.get(i));
            if (v != null)  {
                out.add(v);
            }
        }
        return out;
    }

    /** Represents a partition that contains application packages. */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public static class SystemPartition {
        @NonNull
        public final File folder;

        @PartitionType
        public final int type;

        @Nullable
        private final DeferredCanonicalFile mAppFolder;

        @Nullable
        private final DeferredCanonicalFile mPrivAppFolder;

        @Nullable
        private final DeferredCanonicalFile mOverlayFolder;

        private SystemPartition(@NonNull File folder, @PartitionType int type,
                boolean containsPrivApp, boolean containsOverlay) {
            this.folder = folder;
            this.type = type;
            this.mAppFolder = new DeferredCanonicalFile(folder, "app");
            this.mPrivAppFolder = containsPrivApp ?
                    new DeferredCanonicalFile(folder, "priv-app") : null;
            this.mOverlayFolder = containsOverlay ?
                    new DeferredCanonicalFile(folder, "overlay") : null;
        }

        public SystemPartition(@NonNull SystemPartition original) {
            this.folder = original.folder;
            this.type = original.type;
            this.mAppFolder = original.mAppFolder;
            this.mPrivAppFolder = original.mPrivAppFolder;
            this.mOverlayFolder = original.mOverlayFolder;
        }

        /**
         * Creates a partition containing the same folders as the original partition but with a
         * different root folder.
         */
        public SystemPartition(@NonNull File rootFolder, @NonNull SystemPartition partition) {
            this(rootFolder, partition.type, partition.mPrivAppFolder != null,
                    partition.mOverlayFolder != null);
        }

        /** Returns the canonical app folder of the partition. */
        @Nullable
        public File getAppFolder() {
            return mAppFolder == null ? null : mAppFolder.getFile();
        }

        /** Returns the canonical priv-app folder of the partition, if one exists. */
        @Nullable
        public File getPrivAppFolder() {
            return mPrivAppFolder == null ? null : mPrivAppFolder.getFile();
        }

        /** Returns the canonical overlay folder of the partition, if one exists. */
        @Nullable
        public File getOverlayFolder() {
            return mOverlayFolder == null ? null : mOverlayFolder.getFile();
        }

        /** Returns whether the partition contains the specified file in its priv-app folder. */
        public boolean containsPrivApp(@NonNull File scanFile) {
            return FileUtils.contains(mPrivAppFolder.getFile(), scanFile);
        }

        /** Returns whether the partition contains the specified file in its app folder. */
        public boolean containsApp(@NonNull File scanFile) {
            return FileUtils.contains(mAppFolder.getFile(), scanFile);
        }

        /** Returns whether the partition contains the specified file in its overlay folder. */
        public boolean containsOverlay(@NonNull File scanFile) {
            return FileUtils.contains(mOverlayFolder.getFile(), scanFile);
        }

        /** Returns whether the partition contains the specified file. */
        public boolean containsPath(@NonNull String path) {
            return path.startsWith(folder.getPath() + "/");
        }

        /** Returns whether the partition contains the specified file in its priv-app folder. */
        public boolean containsPrivPath(@NonNull String path) {
            return mPrivAppFolder != null
                    && path.startsWith(mPrivAppFolder.getFile().getPath() + "/");
        }
    }

    /**
     * A class that defers the canonicalization of its underlying file. This must be done so
     * processes do not attempt to canonicalize files in directories for which the process does not
     * have the correct selinux policies.
     */
    private static class DeferredCanonicalFile {
        private boolean mIsCanonical;
        private File mFile;
        private DeferredCanonicalFile(File dir, String fileName) {
            mFile = new File(dir, fileName);
            mIsCanonical = false;
        }

        private File getFile() {
            if (mIsCanonical) {
                return mFile;
            }
            mIsCanonical = true;
            try {
                mFile = mFile.getCanonicalFile();
            } catch (IOException ignore) {
                // failed to look up canonical path, continue with original one
            }
            return mFile;
        }
    }
}
+51 −84
Original line number Original line Diff line number Diff line
@@ -185,6 +185,8 @@ import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.ParseFlags;
import android.content.pm.PackageParser.ParseFlags;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.PackagePartitions;
import android.content.pm.PackagePartitions.SystemPartition;
import android.content.pm.PackageStats;
import android.content.pm.PackageStats;
import android.content.pm.PackageUserState;
import android.content.pm.PackageUserState;
import android.content.pm.ParceledListSlice;
import android.content.pm.ParceledListSlice;
@@ -792,22 +794,10 @@ public class PackageManagerService extends IPackageManager.Stub
     * specificity (the more generic, the earlier in the list a partition appears).
     * specificity (the more generic, the earlier in the list a partition appears).
     */
     */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    public static final List<SystemPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
    public static final List<ScanPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
            Arrays.asList(
            PackagePartitions.getOrderedPartitions(ScanPartition::new));
                    new SystemPartition(Environment.getRootDirectory(), 0 /* scanFlag */,
                            false /* hasOverlays */),
    private final List<ScanPartition> mDirsToScanAsSystem;
                    new SystemPartition(Environment.getVendorDirectory(), SCAN_AS_VENDOR,
                            true /* hasOverlays */),
                    new SystemPartition(Environment.getOdmDirectory(), SCAN_AS_ODM,
                            true /* hasOverlays */),
                    new SystemPartition(Environment.getOemDirectory(), SCAN_AS_OEM,
                            true /* hasOverlays */),
                    new SystemPartition(Environment.getProductDirectory(), SCAN_AS_PRODUCT,
                            true /* hasOverlays */),
                    new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT,
                            true /* hasOverlays */)));
    private final List<SystemPartition> mDirsToScanAsSystem;
    /**
    /**
     * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
     * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
@@ -2588,66 +2578,44 @@ public class PackageManagerService extends IPackageManager.Stub
        }
        }
    }
    }
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    @VisibleForTesting
    public static class SystemPartition {
    public static class ScanPartition extends SystemPartition {
        public final File folder;
        @ScanFlags
        public final int scanFlag;
        public final int scanFlag;
        public final File appFolder;
        @Nullable
        public final File privAppFolder;
        @Nullable
        public final File overlayFolder;
        private static boolean shouldScanPrivApps(@ScanFlags int scanFlags) {
        public ScanPartition(@NonNull SystemPartition partition) {
            if ((scanFlags & SCAN_AS_OEM) != 0) {
            super(partition);
                return false;
            scanFlag = scanFlagForPartition(partition);
            }
            if (scanFlags == 0) {  // /system partition
                return true;
            }
            if ((scanFlags
                    & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) {
                return true;
            }
            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
                return true;
            }
            return false;
        }
        }
        private SystemPartition(File folder, int scanFlag, boolean hasOverlays) {
        /**
            this.folder = folder;
         * Creates a partition containing the same folders as the original partition but with a
            this.scanFlag = scanFlag;
         * different root folder. The new partition will include the scan flags of the original
            this.appFolder = toCanonical(new File(folder, "app"));
         * partition along with any specified additional scan flags.
            this.privAppFolder = shouldScanPrivApps(scanFlag)
         */
                    ? toCanonical(new File(folder, "priv-app"))
        public ScanPartition(@NonNull File folder, @NonNull ScanPartition original,
                    : null;
                @ScanFlags int additionalScanFlag) {
            this.overlayFolder = hasOverlays ? toCanonical(new File(folder, "overlay")) : null;
            super(folder, original);
        }
            this.scanFlag = original.scanFlag | additionalScanFlag;
        public boolean containsPrivApp(File scanFile) {
            return FileUtils.contains(privAppFolder, scanFile);
        }
        public boolean containsApp(File scanFile) {
            return FileUtils.contains(appFolder, scanFile);
        }
        public boolean containsPath(String path) {
            return path.startsWith(folder.getPath() + "/");
        }
        public boolean containsPrivPath(String path) {
            return privAppFolder != null && path.startsWith(privAppFolder.getPath() + "/");
        }
        }
        private static File toCanonical(File dir) {
        private static int scanFlagForPartition(PackagePartitions.SystemPartition partition) {
            try {
            switch (partition.type) {
                return dir.getCanonicalFile();
                case PackagePartitions.PARTITION_SYSTEM:
            } catch (IOException e) {
                    return 0;
                // failed to look up canonical path, continue with original one
                case PackagePartitions.PARTITION_VENDOR:
                return dir;
                    return SCAN_AS_VENDOR;
                case PackagePartitions.PARTITION_ODM:
                    return SCAN_AS_ODM;
                case PackagePartitions.PARTITION_OEM:
                    return SCAN_AS_OEM;
                case PackagePartitions.PARTITION_PRODUCT:
                    return SCAN_AS_PRODUCT;
                case PackagePartitions.PARTITION_SYSTEM_EXT:
                    return SCAN_AS_SYSTEM_EXT;
                default:
                    throw new IllegalStateException("Unable to determine scan flag for "
                            + partition.folder);
            }
            }
        }
        }
    }
    }
@@ -2751,7 +2719,7 @@ public class PackageManagerService extends IPackageManager.Stub
        mDirsToScanAsSystem = new ArrayList<>();
        mDirsToScanAsSystem = new ArrayList<>();
        mDirsToScanAsSystem.addAll(SYSTEM_PARTITIONS);
        mDirsToScanAsSystem.addAll(SYSTEM_PARTITIONS);
        mDirsToScanAsSystem.addAll(mApexManager.getActiveApexInfos().stream()
        mDirsToScanAsSystem.addAll(mApexManager.getActiveApexInfos().stream()
                .map(ai -> resolveApexToSystemPartition(ai))
                .map(PackageManagerService::resolveApexToScanPartition)
                .filter(Objects::nonNull).collect(Collectors.toList()));
                .filter(Objects::nonNull).collect(Collectors.toList()));
        Slog.d(TAG,
        Slog.d(TAG,
                "Directories scanned as system partitions: [" + mDirsToScanAsSystem.stream().map(
                "Directories scanned as system partitions: [" + mDirsToScanAsSystem.stream().map(
@@ -2900,11 +2868,11 @@ public class PackageManagerService extends IPackageManager.Stub
            // For security and version matching reason, only consider overlay packages if they
            // For security and version matching reason, only consider overlay packages if they
            // reside in the right directory.
            // reside in the right directory.
            for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
            for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
                final SystemPartition partition = mDirsToScanAsSystem.get(i);
                final ScanPartition partition = mDirsToScanAsSystem.get(i);
                if (partition.overlayFolder == null) {
                if (partition.getOverlayFolder() == null) {
                    continue;
                    continue;
                }
                }
                scanDirTracedLI(partition.overlayFolder, systemParseFlags,
                scanDirTracedLI(partition.getOverlayFolder(), systemParseFlags,
                        systemScanFlags | partition.scanFlag, 0,
                        systemScanFlags | partition.scanFlag, 0,
                        packageParser, executorService);
                        packageParser, executorService);
            }
            }
@@ -2917,13 +2885,13 @@ public class PackageManagerService extends IPackageManager.Stub
                        "Failed to load frameworks package; check log for warnings");
                        "Failed to load frameworks package; check log for warnings");
            }
            }
            for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
            for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
                final SystemPartition partition = mDirsToScanAsSystem.get(i);
                final ScanPartition partition = mDirsToScanAsSystem.get(i);
                if (partition.privAppFolder != null) {
                if (partition.getPrivAppFolder() != null) {
                    scanDirTracedLI(partition.privAppFolder, systemParseFlags,
                    scanDirTracedLI(partition.getPrivAppFolder(), systemParseFlags,
                            systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
                            systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
                            packageParser, executorService);
                            packageParser, executorService);
                }
                }
                scanDirTracedLI(partition.appFolder, systemParseFlags,
                scanDirTracedLI(partition.getAppFolder(), systemParseFlags,
                        systemScanFlags | partition.scanFlag, 0,
                        systemScanFlags | partition.scanFlag, 0,
                        packageParser, executorService);
                        packageParser, executorService);
            }
            }
@@ -3105,7 +3073,7 @@ public class PackageManagerService extends IPackageManager.Stub
                        @ParseFlags int reparseFlags = 0;
                        @ParseFlags int reparseFlags = 0;
                        @ScanFlags int rescanFlags = 0;
                        @ScanFlags int rescanFlags = 0;
                        for (int i1 = 0, size = mDirsToScanAsSystem.size(); i1 < size; i1++) {
                        for (int i1 = 0, size = mDirsToScanAsSystem.size(); i1 < size; i1++) {
                            SystemPartition partition = mDirsToScanAsSystem.get(i1);
                            final ScanPartition partition = mDirsToScanAsSystem.get(i1);
                            if (partition.containsPrivApp(scanFile)) {
                            if (partition.containsPrivApp(scanFile)) {
                                reparseFlags = systemParseFlags;
                                reparseFlags = systemParseFlags;
                                rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
                                rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
@@ -17957,14 +17925,13 @@ public class PackageManagerService extends IPackageManager.Stub
        }
        }
    }
    }
    private static @Nullable SystemPartition resolveApexToSystemPartition(
    private static @Nullable ScanPartition resolveApexToScanPartition(
            ApexManager.ActiveApexInfo apexInfo) {
            ApexManager.ActiveApexInfo apexInfo) {
        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
            SystemPartition sp = SYSTEM_PARTITIONS.get(i);
            ScanPartition sp = SYSTEM_PARTITIONS.get(i);
            if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
            if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
                    sp.folder.getAbsolutePath())) {
                    sp.folder.getAbsolutePath())) {
                return new SystemPartition(apexInfo.apexDirectory,
                return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX);
                        sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */);
            }
            }
        }
        }
        return null;
        return null;
@@ -18065,7 +18032,7 @@ public class PackageManagerService extends IPackageManager.Stub
                | PackageParser.PARSE_IS_SYSTEM_DIR;
                | PackageParser.PARSE_IS_SYSTEM_DIR;
        @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
        @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
        for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
        for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
            SystemPartition partition = mDirsToScanAsSystem.get(i);
            ScanPartition partition = mDirsToScanAsSystem.get(i);
            if (partition.containsPath(codePathString)) {
            if (partition.containsPath(codePathString)) {
                scanFlags |= partition.scanFlag;
                scanFlags |= partition.scanFlag;
                if (partition.containsPrivPath(codePathString)) {
                if (partition.containsPrivPath(codePathString)) {
+3 −3
Original line number Original line Diff line number Diff line
@@ -118,15 +118,15 @@ public class PackageManagerServiceTest {
        String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
        String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
        String[] appdir = { "app", "priv-app" };
        String[] appdir = { "app", "priv-app" };
        for (int i = 0; i < partitions.length; i++) {
        for (int i = 0; i < partitions.length; i++) {
            final PackageManagerService.SystemPartition systemPartition =
            final PackageManagerService.ScanPartition scanPartition =
                    PackageManagerService.SYSTEM_PARTITIONS.get(i);
                    PackageManagerService.SYSTEM_PARTITIONS.get(i);
            for (int j = 0; j < appdir.length; j++) {
            for (int j = 0; j < appdir.length; j++) {
                String canonical = new File("/" + partitions[i]).getCanonicalPath();
                String canonical = new File("/" + partitions[i]).getCanonicalPath();
                String path = String.format("%s/%s/A.apk", canonical, appdir[j]);
                String path = String.format("%s/%s/A.apk", canonical, appdir[j]);


                Assert.assertEquals(j == 1 && i != 3, systemPartition.containsPrivPath(path));
                Assert.assertEquals(j == 1 && i != 3, scanPartition.containsPrivPath(path));


                final int scanFlag = systemPartition.scanFlag;
                final int scanFlag = scanPartition.scanFlag;
                Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR);
                Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR);
                Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM);
                Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM);
                Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);
                Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);