Enable filesystem checkpoint for staged apk installation
Because: We want to make multiple staged sessions' installation atomic. This behaviour is consistent with what we do with apex packages. File system checkpoint will be enabled during the end-phase of pre-reboot verification of staged session. On reboot, if anything goes wrong during apk installation, we reboot again to enter rollback-mode. On rollback-mode, we mark all active staged session as failed. The new implementation is unaware of its surrounding sessions, i.e, when a session fails we have no way to know if there is any other staged session which was applied on this boot. As such, whenever a session fails, we reboot immediately to revert back to safe state. Bug: 141843321 Test: atest StagedInstallTest#testInstallMultipleStagedSession_PartialFail_ApkOnly Change-Id: I1619c6f113fc0b07f2c69215683e4964e0e99458
This commit is contained in:
@@ -502,6 +502,9 @@ abstract class ApexManager {
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.storage.IStorageManager;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.util.IntArray;
|
||||
import android.util.Slog;
|
||||
@@ -58,6 +59,7 @@ import android.util.SparseIntArray;
|
||||
import android.util.apk.ApkSignatureVerifier;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.content.PackageHelper;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
|
||||
import java.io.File;
|
||||
@@ -309,39 +311,100 @@ public class StagingManager {
|
||||
return sessionContains(session, (s) -> !isApexSession(s));
|
||||
}
|
||||
|
||||
// Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
|
||||
private void abortCheckpoint() {
|
||||
try {
|
||||
if (supportsCheckpoint() && needsCheckpoint()) {
|
||||
mApexManager.revertActiveSessions();
|
||||
PackageHelper.getStorageManager().abortChanges(
|
||||
"StagingManager initiated", false /*retry*/);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Slog.wtf(TAG, "Failed to abort checkpoint", e);
|
||||
mApexManager.revertActiveSessions();
|
||||
mPowerManager.reboot(null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean supportsCheckpoint() throws RemoteException {
|
||||
return PackageHelper.getStorageManager().supportsCheckpoint();
|
||||
}
|
||||
|
||||
private boolean needsCheckpoint() throws RemoteException {
|
||||
return PackageHelper.getStorageManager().needsCheckpoint();
|
||||
}
|
||||
|
||||
private void resumeSession(@NonNull PackageInstallerSession session) {
|
||||
Slog.d(TAG, "Resuming session " + session.sessionId);
|
||||
|
||||
final boolean hasApex = sessionContainsApex(session);
|
||||
ApexSessionInfo apexSessionInfo = null;
|
||||
if (hasApex) {
|
||||
// Check with apexservice whether the apex packages have been activated.
|
||||
ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
|
||||
apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
|
||||
|
||||
if (apexSessionInfo != null && apexSessionInfo.isVerified) {
|
||||
// Session has been previously submitted to apexd, but didn't complete all the
|
||||
// pre-reboot verification, perhaps because the device rebooted in the meantime.
|
||||
// Greedily re-trigger the pre-reboot verification. We want to avoid marking it as
|
||||
// failed when not in checkpoint mode, hence it is being processed separately.
|
||||
Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to "
|
||||
+ "be verified, resuming pre-reboot verification");
|
||||
mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Before we resume session, we check if revert is needed or not. Typically, we enter file-
|
||||
// system checkpoint mode when we reboot first time in order to install staged sessions. We
|
||||
// want to install staged sessions in this mode as rebooting now will revert user data. If
|
||||
// something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
|
||||
// have no effect on user data, so mark the sessions as failed instead.
|
||||
try {
|
||||
// If checkpoint is supported, then we only resume sessions if we are in checkpointing
|
||||
// mode. If not, we fail all sessions.
|
||||
if (supportsCheckpoint() && !needsCheckpoint()) {
|
||||
// TODO(b/146343545): Persist failure reason across checkpoint reboot
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
|
||||
"Reverting back to safe state");
|
||||
return;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// Cannot continue staged install without knowing if fs-checkpoint is supported
|
||||
Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session "
|
||||
+ session.sessionId, e);
|
||||
// TODO: Mark all staged sessions together and reboot only once
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
|
||||
"Checkpoint support unknown. Aborting staged install.");
|
||||
if (hasApex) {
|
||||
mApexManager.revertActiveSessions();
|
||||
}
|
||||
mPowerManager.reboot("Checkpoint support unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasApex) {
|
||||
if (apexSessionInfo == null) {
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
|
||||
"apexd did not know anything about a staged session supposed to be"
|
||||
+ "activated");
|
||||
abortCheckpoint();
|
||||
return;
|
||||
}
|
||||
if (isApexSessionFailed(apexSessionInfo)) {
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
|
||||
"APEX activation failed. Check logcat messages from apexd for "
|
||||
+ "more information.");
|
||||
return;
|
||||
}
|
||||
if (apexSessionInfo.isVerified) {
|
||||
// Session has been previously submitted to apexd, but didn't complete all the
|
||||
// pre-reboot verification, perhaps because the device rebooted in the meantime.
|
||||
// Greedily re-trigger the pre-reboot verification.
|
||||
Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
|
||||
+ "verified, resuming pre-reboot verification");
|
||||
mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
|
||||
abortCheckpoint();
|
||||
return;
|
||||
}
|
||||
if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
|
||||
// In all the remaining cases apexd will try to apply the session again at next
|
||||
// boot. Nothing to do here for now.
|
||||
Slog.w(TAG, "Staged session " + session.sessionId + " scheduled to be applied "
|
||||
+ "at boot didn't activate nor fail. This usually means that apexd will "
|
||||
+ "retry at next reboot.");
|
||||
// Apexd did not apply the session for some unknown reason. There is no guarantee
|
||||
// that apexd will install it next time. Safer to proactively mark as failed.
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
|
||||
"Staged session " + session.sessionId + "at boot didn't "
|
||||
+ "activate nor fail. Marking it as failed anyway.");
|
||||
abortCheckpoint();
|
||||
return;
|
||||
}
|
||||
Slog.i(TAG, "APEX packages in session " + session.sessionId
|
||||
@@ -353,7 +416,9 @@ public class StagingManager {
|
||||
installApksInSession(session);
|
||||
} catch (PackageManagerException e) {
|
||||
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||
abortCheckpoint();
|
||||
|
||||
// If checkpoint is not supported, we have to handle failure for one staged session.
|
||||
if (!hasApex) {
|
||||
return;
|
||||
}
|
||||
@@ -1020,6 +1085,20 @@ public class StagingManager {
|
||||
* </ul></p>
|
||||
*/
|
||||
private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
|
||||
// Before marking the session as ready, start checkpoint service if available
|
||||
try {
|
||||
IStorageManager storageManager = PackageHelper.getStorageManager();
|
||||
if (storageManager.supportsCheckpoint()) {
|
||||
storageManager.startCheckpoint(1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Failed to get hold of StorageManager
|
||||
Slog.e(TAG, "Failed to get hold of StorageManager", e);
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
|
||||
"Failed to get hold of StorageManager");
|
||||
return;
|
||||
}
|
||||
|
||||
// Proactively mark session as ready before calling apexd. Although this call order
|
||||
// looks counter-intuitive, this is the easiest way to ensure that session won't end up
|
||||
// in the inconsistent state:
|
||||
|
||||
Reference in New Issue
Block a user