Merge "apk-in-apex-cache 1/n: Fix isCacheUpToDate for apk-in-apex" into tm-dev

This commit is contained in:
Nikita Ioffe
2022-05-03 09:32:41 +00:00
committed by Android (Google) Code Review
3 changed files with 102 additions and 6 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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();