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

Commit 2589a7e8 authored by Nikita Ioffe's avatar Nikita Ioffe
Browse files

apk-in-apex-cache 1/n: Fix isCacheUpToDate for apk-in-apex

In case an APK resides on the APEX mount point it's mtime will always be
zero. This breaks the isCacheUpToDate check, which is based on comparing
the mtime of the apk to the mtime of the cache.

This cl fixes the issue by using the mtime of the APEX file backing the
/apex mount point the apk resides on.

Also noticed some inefficiencies in ApexManager and added TODO's to fix
them in the follow-up cls.
s
Bug: 225435110
Test: atest PackageParserTest
Test: atest ApexManagerTest

Change-Id: I8a0ab8952454652048076a26e81097b95991ae60
Merged-In: I8a0ab8952454652048076a26e81097b95991ae60
parent 439a7fe9
Loading
Loading
Loading
Loading
+46 −6
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ import java.io.File;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -119,16 +120,18 @@ public abstract class ApexManager {
        @Nullable public final String apexModuleName;
        public final File apexDirectory;
        public final File preInstalledApexPath;
        public final File apexFile;

        private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) {
            this(null, apexDirectory, preInstalledApexPath);
        private ActiveApexInfo(File apexDirectory, File preInstalledApexPath, File apexFile) {
            this(null, apexDirectory, preInstalledApexPath, apexFile);
        }

        private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory,
                File preInstalledApexPath) {
                File preInstalledApexPath, File apexFile) {
            this.apexModuleName = apexModuleName;
            this.apexDirectory = apexDirectory;
            this.preInstalledApexPath = preInstalledApexPath;
            this.apexFile = apexFile;
        }

        private ActiveApexInfo(ApexInfo apexInfo) {
@@ -136,7 +139,8 @@ public abstract class ApexManager {
                    apexInfo.moduleName,
                    new File(Environment.getApexDirectory() + File.separator
                            + apexInfo.moduleName),
                    new File(apexInfo.preinstalledModulePath));
                    new File(apexInfo.preinstalledModulePath),
                    new File(apexInfo.modulePath));
        }
    }

@@ -427,6 +431,15 @@ public abstract class ApexManager {
     */
    public abstract List<ApexSystemServiceInfo> getApexSystemServices();

    /**
     * Returns an APEX file backing the mount point {@code file} is located on, or {@code null} if
     * {@code file} doesn't belong to a {@code /apex} mount point.
     *
     * <p>Also returns {@code null} if device doesn't support updatable APEX packages.
     */
    @Nullable
    public abstract File getBackingApexFile(@NonNull File file);

    /**
     * Dumps various state information to the provided {@link PrintWriter} object.
     *
@@ -451,6 +464,7 @@ public abstract class ApexManager {
    protected static class ApexManagerImpl extends ApexManager {
        private final Object mLock = new Object();

        // TODO(ioffe): this should be either List or ArrayMap.
        @GuardedBy("mLock")
        private Set<ActiveApexInfo> mActiveApexInfosCache;

@@ -1184,6 +1198,25 @@ public abstract class ApexManager {
            }
        }

        @Override
        public File getBackingApexFile(File file) {
            Path path = file.toPath();
            if (!path.startsWith(Environment.getApexDirectory().toPath())) {
                return null;
            }
            if (path.getNameCount() < 2) {
                return null;
            }
            String moduleName = file.toPath().getName(1).toString();
            final List<ActiveApexInfo> apexes = getActiveApexInfos();
            for (int i = 0; i < apexes.size(); i++) {
                if (apexes.get(i).apexModuleName.equals(moduleName)) {
                    return apexes.get(i).apexFile;
                }
            }
            return null;
        }

        /**
         * Dump information about the packages contained in a particular cache
         * @param packagesCache the cache to print information about.
@@ -1274,7 +1307,8 @@ public abstract class ApexManager {
     * An implementation of {@link ApexManager} that should be used in case device does not support
     * updating APEX packages.
     */
    private static final class ApexManagerFlattenedApex extends ApexManager {
    @VisibleForTesting
    static final class ApexManagerFlattenedApex extends ApexManager {
        @Override
        public List<ActiveApexInfo> getActiveApexInfos() {
            // There is no apexd running in case of flattened apex
@@ -1293,7 +1327,8 @@ public abstract class ApexManager {
                                // In flattened configuration, init special-cases the art directory
                                // and bind-mounts com.android.art.debug to com.android.art.
                                && !file.getName().equals("com.android.art.debug")) {
                            result.add(new ActiveApexInfo(file, Environment.getRootDirectory()));
                            result.add(
                                    new ActiveApexInfo(file, Environment.getRootDirectory(), file));
                        }
                    }
                }
@@ -1478,6 +1513,11 @@ public abstract class ApexManager {
            return Collections.emptyList();
        }

        @Override
        public File getBackingApexFile(File file) {
            return null;
        }

        @Override
        void dump(PrintWriter pw, String packageName) {
            // No-op
+13 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.pm.parsing;

import android.annotation.NonNull;
import android.content.pm.PackageParserCacheHelper;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Parcel;
import android.system.ErrnoException;
@@ -27,6 +28,7 @@ import android.system.StructStat;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.ApexManager;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;

@@ -118,6 +120,17 @@ public class PackageCacher {
     */
    private static boolean isCacheUpToDate(File packageFile, File cacheFile) {
        try {
            // In case packageFile is located on one of /apex mount points it's mtime will always be
            // 0. Instead, we can use mtime of the APEX file backing the corresponding mount point.
            if (packageFile.toPath().startsWith(Environment.getApexDirectory().toPath())) {
                File backingApexFile = ApexManager.getInstance().getBackingApexFile(packageFile);
                if (backingApexFile == null) {
                    Slog.w(TAG,
                            "Failed to find APEX file backing " + packageFile.getAbsolutePath());
                } else {
                    packageFile = backingApexFile;
                }
            }
            // NOTE: We don't use the File.lastModified API because it has the very
            // non-ideal failure mode of returning 0 with no excepions thrown.
            // The nio2 Files API is a little better but is considerably more expensive.
+43 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.apex.IApexService;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.platform.test.annotations.Presubmit;
@@ -503,6 +504,48 @@ public class ApexManagerTest {
                .isEqualTo(TEST_APEX_PKG);
    }

    @Test
    public void testGetBackingApexFiles() throws Exception {
        final ApexInfo apex = createApexInfoForTestPkg(true, true, 37);
        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex});

        final File backingApexFile = mApexManager.getBackingApexFile(
                new File("/apex/" + TEST_APEX_PKG + "/apk/App/App.apk"));
        assertThat(backingApexFile.getAbsolutePath()).isEqualTo(apex.modulePath);
    }

    @Test
    public void testGetBackingApexFile_fileNotOnApexMountPoint_returnsNull() throws Exception {
        File result = mApexManager.getBackingApexFile(
                new File("/data/local/tmp/whatever/does-not-matter"));
        assertThat(result).isNull();
    }

    @Test
    public void testGetBackingApexFiles_unknownApex_returnsNull() throws Exception {
        final ApexInfo apex = createApexInfoForTestPkg(true, true, 37);
        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex});

        final File backingApexFile = mApexManager.getBackingApexFile(
                new File("/apex/com.wrong.apex/apk/App"));
        assertThat(backingApexFile).isNull();
    }

    @Test
    public void testGetBackingApexFiles_topLevelApexDir_returnsNull() throws Exception {
        assertThat(mApexManager.getBackingApexFile(Environment.getApexDirectory())).isNull();
        assertThat(mApexManager.getBackingApexFile(new File("/apex/"))).isNull();
        assertThat(mApexManager.getBackingApexFile(new File("/apex//"))).isNull();
    }

    @Test
    public void testGetBackingApexFiles_flattenedApex() throws Exception {
        ApexManager flattenedApexManager = new ApexManager.ApexManagerFlattenedApex();
        final File backingApexFile = flattenedApexManager.getBackingApexFile(
                new File("/apex/com.android.apex.cts.shim/app/CtsShim/CtsShim.apk"));
        assertThat(backingApexFile).isNull();
    }

    private ApexInfo createApexInfoForTestPkg(boolean isActive, boolean isFactory, int version) {
        File apexFile = extractResource(TEST_APEX_PKG,  TEST_APEX_FILE_NAME);
        ApexInfo apexInfo = new ApexInfo();