Merge "Fail staged install if any apk-in-apex fails to install" into rvc-dev

This commit is contained in:
Mohammad Samiul Islam
2020-04-30 10:39:53 +00:00
committed by Android (Google) Code Review
4 changed files with 141 additions and 33 deletions

View File

@@ -60,7 +60,6 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@@ -289,6 +288,21 @@ public abstract class ApexManager {
*/ */
abstract void registerApkInApex(AndroidPackage pkg); abstract void registerApkInApex(AndroidPackage pkg);
/**
* Reports error raised during installation of apk-in-apex.
*
* @param scanDir the directory of the apex inside which apk-in-apex resides.
*/
abstract void reportErrorWithApkInApex(String scanDirPath);
/**
* Returns true if there were no errors when installing apk-in-apex inside
* {@param apexPackageName}, otherwise false.
*
* @param apexPackageName Package name of the apk container of apex
*/
abstract boolean isApkInApexInstallSuccess(String apexPackageName);
/** /**
* Returns list of {@code packageName} of apks inside the given apex. * Returns list of {@code packageName} of apks inside the given apex.
* @param apexPackageName Package name of the apk container of apex * @param apexPackageName Package name of the apk container of apex
@@ -368,6 +382,13 @@ public abstract class ApexManager {
@GuardedBy("mLock") @GuardedBy("mLock")
private ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>(); private ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>();
/**
* Contains the list of {@code Exception}s that were raised when installing apk-in-apex
* inside {@code apexModuleName}.
*/
@GuardedBy("mLock")
private Set<String> mErrorWithApkInApex = new ArraySet<>();
@GuardedBy("mLock") @GuardedBy("mLock")
private List<PackageInfo> mAllPackagesCache; private List<PackageInfo> mAllPackagesCache;
@@ -733,9 +754,7 @@ public abstract class ApexManager {
@Override @Override
void registerApkInApex(AndroidPackage pkg) { void registerApkInApex(AndroidPackage pkg) {
synchronized (mLock) { synchronized (mLock) {
final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator(); for (ActiveApexInfo aai : mActiveApexInfosCache) {
while (it.hasNext()) {
final ActiveApexInfo aai = it.next();
if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) { if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
List<String> apks = mApksInApex.get(aai.apexModuleName); List<String> apks = mApksInApex.get(aai.apexModuleName);
if (apks == null) { if (apks == null) {
@@ -748,6 +767,30 @@ public abstract class ApexManager {
} }
} }
@Override
void reportErrorWithApkInApex(String scanDirPath) {
synchronized (mLock) {
for (ActiveApexInfo aai : mActiveApexInfosCache) {
if (scanDirPath.startsWith(aai.apexDirectory.getAbsolutePath())) {
mErrorWithApkInApex.add(aai.apexModuleName);
}
}
}
}
@Override
boolean isApkInApexInstallSuccess(String apexPackageName) {
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
if (moduleName == null) {
return false;
}
return !mErrorWithApkInApex.contains(moduleName);
}
}
@Override @Override
List<String> getApksInApex(String apexPackageName) { List<String> getApksInApex(String apexPackageName) {
synchronized (mLock) { synchronized (mLock) {
@@ -1039,6 +1082,16 @@ public abstract class ApexManager {
// No-op // No-op
} }
@Override
void reportErrorWithApkInApex(String scanDirPath) {
// No-op
}
@Override
boolean isApkInApexInstallSuccess(String apexPackageName) {
return true;
}
@Override @Override
List<String> getApksInApex(String apexPackageName) { List<String> getApksInApex(String apexPackageName) {
return Collections.emptyList(); return Collections.emptyList();

View File

@@ -8985,6 +8985,10 @@ public class PackageManagerService extends IPackageManager.Stub
+ parseResult.scanFile, throwable); + parseResult.scanFile, throwable);
} }
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {
mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath());
}
// Delete invalid userdata apps // Delete invalid userdata apps
if ((scanFlags & SCAN_AS_SYSTEM) == 0 if ((scanFlags & SCAN_AS_SYSTEM) == 0
&& errorCode != PackageManager.INSTALL_SUCCEEDED) { && errorCode != PackageManager.INSTALL_SUCCEEDED) {

View File

@@ -370,24 +370,9 @@ public class StagingManager {
} }
/** /**
* Perform snapshot and restore as required both for APEXes themselves and for apks in APEX. * Utility function for extracting apex sessions out of multi-package/single session.
* Apks inside apex are not installed using apk-install flow. They are scanned from the system
* directory directly by PackageManager, as such, RollbackManager need to handle their data
* separately here.
*/ */
private void snapshotAndRestoreForApexSession(PackageInstallerSession session) { private List<PackageInstallerSession> extractApexSessions(PackageInstallerSession session) {
if (!sessionContainsApex(session)) {
return;
}
boolean doSnapshotOrRestore =
(session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
|| session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
if (!doSnapshotOrRestore) {
return;
}
// Find all the apex sessions that needs processing
List<PackageInstallerSession> apexSessions = new ArrayList<>(); List<PackageInstallerSession> apexSessions = new ArrayList<>();
if (session.isMultiPackage()) { if (session.isMultiPackage()) {
List<PackageInstallerSession> childrenSessions = new ArrayList<>(); List<PackageInstallerSession> childrenSessions = new ArrayList<>();
@@ -408,6 +393,50 @@ public class StagingManager {
} else { } else {
apexSessions.add(session); apexSessions.add(session);
} }
return apexSessions;
}
/**
* Checks if all apk-in-apex were installed without errors for all of the apex sessions. Throws
* error for any apk-in-apex failed to install.
*
* @throws PackageManagerException if any apk-in-apex failed to install
*/
private void checkInstallationOfApkInApexSuccessful(PackageInstallerSession session)
throws PackageManagerException {
final List<PackageInstallerSession> apexSessions = extractApexSessions(session);
if (apexSessions.isEmpty()) {
return;
}
for (PackageInstallerSession apexSession : apexSessions) {
String packageName = apexSession.getPackageName();
if (!mApexManager.isApkInApexInstallSuccess(packageName)) {
throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"Failed to install apk-in-apex of " + packageName);
}
}
}
/**
* Perform snapshot and restore as required both for APEXes themselves and for apks in APEX.
* Apks inside apex are not installed using apk-install flow. They are scanned from the system
* directory directly by PackageManager, as such, RollbackManager need to handle their data
* separately here.
*/
private void snapshotAndRestoreForApexSession(PackageInstallerSession session) {
boolean doSnapshotOrRestore =
(session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
|| session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
if (!doSnapshotOrRestore) {
return;
}
// Find all the apex sessions that needs processing
final List<PackageInstallerSession> apexSessions = extractApexSessions(session);
if (apexSessions.isEmpty()) {
return;
}
final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
final int[] allUsers = um.getUserIds(); final int[] allUsers = um.getUserIds();
@@ -545,18 +574,19 @@ public class StagingManager {
return; return;
} }
// Check if apex packages in the session failed to activate
if (hasApex) { if (hasApex) {
if (apexSessionInfo == null) { if (apexSessionInfo == null) {
String errorMsg = "apexd did not know anything about a staged session supposed to" final String errorMsg = "apexd did not know anything about a staged session "
+ " be activated"; + "supposed to be activated";
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
errorMsg); errorMsg);
abortCheckpoint(errorMsg); abortCheckpoint(errorMsg);
return; return;
} }
if (isApexSessionFailed(apexSessionInfo)) { if (isApexSessionFailed(apexSessionInfo)) {
String errorMsg = "APEX activation failed. Check logcat messages from apexd for " String errorMsg = "APEX activation failed. Check logcat messages from apexd "
+ "more information."; + "for more information.";
if (!TextUtils.isEmpty(mNativeFailureReason)) { if (!TextUtils.isEmpty(mNativeFailureReason)) {
errorMsg = "Session reverted due to crashing native process: " errorMsg = "Session reverted due to crashing native process: "
+ mNativeFailureReason; + mNativeFailureReason;
@@ -567,21 +597,26 @@ public class StagingManager {
return; return;
} }
if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) { if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
// Apexd did not apply the session for some unknown reason. There is no guarantee // Apexd did not apply the session for some unknown reason. There is no
// that apexd will install it next time. Safer to proactively mark as failed. // guarantee that apexd will install it next time. Safer to proactively mark
String errorMsg = "Staged session " + session.sessionId + "at boot didn't " // it as failed.
+ "activate nor fail. Marking it as failed anyway."; final String errorMsg = "Staged session " + session.sessionId + "at boot "
+ "didn't activate nor fail. Marking it as failed anyway.";
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
errorMsg); errorMsg);
abortCheckpoint(errorMsg); abortCheckpoint(errorMsg);
return; return;
} }
snapshotAndRestoreForApexSession(session);
Slog.i(TAG, "APEX packages in session " + session.sessionId
+ " were successfully activated. Proceeding with APK packages, if any");
} }
// The APEX part of the session is activated, proceed with the installation of APKs. // Handle apk and apk-in-apex installation
try { try {
if (hasApex) {
checkInstallationOfApkInApexSuccessful(session);
snapshotAndRestoreForApexSession(session);
Slog.i(TAG, "APEX packages in session " + session.sessionId
+ " were successfully activated. Proceeding with APK packages, if any");
}
// The APEX part of the session is activated, proceed with the installation of APKs.
Slog.d(TAG, "Installing APK packages in session " + session.sessionId); Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
installApksInSession(session); installApksInSession(session);
} catch (PackageManagerException e) { } catch (PackageManagerException e) {

View File

@@ -273,6 +273,21 @@ public class ApexManagerTest {
assertThat(mApexManager.uninstallApex(TEST_APEX_PKG)).isFalse(); assertThat(mApexManager.uninstallApex(TEST_APEX_PKG)).isFalse();
} }
@Test
public void testReportErrorWithApkInApex() throws RemoteException {
when(mApexService.getActivePackages()).thenReturn(createApexInfo(true, true));
final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG);
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.isApkInApexInstallSuccess(activeApex.apexModuleName)).isTrue();
mApexManager.reportErrorWithApkInApex(activeApex.apexDirectory.getAbsolutePath());
assertThat(mApexManager.isApkInApexInstallSuccess(activeApex.apexModuleName)).isFalse();
}
private ApexInfo[] createApexInfo(boolean isActive, boolean isFactory) { private ApexInfo[] createApexInfo(boolean isActive, boolean isFactory) {
File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME); File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME);
ApexInfo apexInfo = new ApexInfo(); ApexInfo apexInfo = new ApexInfo();
@@ -281,6 +296,7 @@ public class ApexManagerTest {
apexInfo.moduleName = TEST_APEX_PKG; apexInfo.moduleName = TEST_APEX_PKG;
apexInfo.modulePath = apexFile.getPath(); apexInfo.modulePath = apexFile.getPath();
apexInfo.versionCode = 191000070; apexInfo.versionCode = 191000070;
apexInfo.preinstalledModulePath = apexFile.getPath();
return new ApexInfo[]{apexInfo}; return new ApexInfo[]{apexInfo};
} }