From e87ec2bec84e62d1d4f52b39794dbaddef098daa Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Wed, 20 Nov 2019 13:53:07 +0000 Subject: [PATCH 1/6] Rebrand the term "rollback" to "revert" for apexd StagingManager is a client of apexd. Rebrand in apexd requires we make similar changes in StagingManager. Bug: 144831762 Test: Builds without error Change-Id: Idf2104e76a89bd8cd08778ca53129377255031af Merged-In: Idf2104e76a89bd8cd08778ca53129377255031af --- .../core/java/com/android/server/pm/ApexManager.java | 12 ++++++------ .../java/com/android/server/pm/StagingManager.java | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index cf5ec05a73a7e..7690b0b3598cb 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -569,12 +569,12 @@ abstract class ApexManager { ipw.println("State: ACTIVATION FAILED"); } else if (si.isSuccess) { ipw.println("State: SUCCESS"); - } else if (si.isRollbackInProgress) { - ipw.println("State: ROLLBACK IN PROGRESS"); - } else if (si.isRolledBack) { - ipw.println("State: ROLLED BACK"); - } else if (si.isRollbackFailed) { - ipw.println("State: ROLLBACK FAILED"); + } else if (si.isRevertInProgress) { + ipw.println("State: REVERT IN PROGRESS"); + } else if (si.isReverted) { + ipw.println("State: REVERTED"); + } else if (si.isRevertFailed) { + ipw.println("State: REVERT FAILED"); } ipw.decreaseIndent(); } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 96ccba5b4d6f1..9f76d0d7a1d82 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -665,15 +665,15 @@ public class StagingManager { private boolean isApexSessionFinalized(ApexSessionInfo session) { /* checking if the session is in a final state, i.e., not active anymore */ return session.isUnknown || session.isActivationFailed || session.isSuccess - || session.isRolledBack; + || session.isReverted; } private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) { - // isRollbackInProgress is included to cover the scenario, when a device is rebooted in - // during the rollback, and apexd fails to resume the rollback after reboot. + // isRevertInProgress is included to cover the scenario, when a device is rebooted + // during the revert, and apexd fails to resume the revert after reboot. return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown - || apexSessionInfo.isRolledBack || apexSessionInfo.isRollbackInProgress - || apexSessionInfo.isRollbackFailed; + || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress + || apexSessionInfo.isRevertFailed; } @GuardedBy("mStagedSessions") From 8c7142a6587f21ba5101622afc4b3c879d8b9fd7 Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Tue, 5 Nov 2019 18:18:28 +0000 Subject: [PATCH 2/6] Prevent staging multiple sessions on devices not supporting checkpoint Bug: 141843321 Test: atest StagedInstallTest#testFailStagingMultipleSessionsIfNoCheckPoint Change-Id: I118ae628649e9bef82641ef18841205091e98a6a Merged-In: I118ae628649e9bef82641ef18841205091e98a6a (cherry picked from commit cc4c7d8cc69a90762e88d307fed3331322385cc6) --- api/current.txt | 1 + .../android/os/storage/StorageManager.java | 13 +++++++++++ .../android/server/StorageManagerService.java | 7 ------ .../com/android/server/pm/StagingManager.java | 22 ++++++++++++++++++- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/api/current.txt b/api/current.txt index 00a6c24f2c28f..9e50a5afaaefb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -36025,6 +36025,7 @@ package android.os.storage { method public boolean isAllocationSupported(@NonNull java.io.FileDescriptor); method public boolean isCacheBehaviorGroup(java.io.File) throws java.io.IOException; method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException; + method public boolean isCheckpointSupported(); method public boolean isEncrypted(java.io.File); method public boolean isObbMounted(String); method public boolean mountObb(String, String, android.os.storage.OnObbStateChangeListener); diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index c7709b988e49f..c31017bce3db6 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2304,6 +2304,19 @@ public class StorageManager { } } + /** + * Check whether the device supports filesystem checkpoint. + * + * @return true if the device supports filesystem checkpoint, false otherwise. + */ + public boolean isCheckpointSupported() { + try { + return mStorageManager.supportsCheckpoint(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final Object mFuseAppLoopLock = new Object(); @GuardedBy("mFuseAppLoopLock") diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 39ad3546e452d..515ffc71e874b 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2648,13 +2648,6 @@ class StorageManagerService extends IStorageManager.Stub */ @Override public boolean supportsCheckpoint() throws RemoteException { - // Only the root, system_server and shell processes are permitted to start checkpoints - final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID - && callingUid != Process.SHELL_UID) { - throw new SecurityException("no permission to start filesystem checkpoint"); - } - return mVold.supportsCheckpoint(); } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 9f76d0d7a1d82..cb9a090b37e99 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -46,6 +46,7 @@ import android.os.ParcelableException; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.storage.StorageManager; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; @@ -76,6 +77,7 @@ public class StagingManager { private final PackageInstallerService mPi; private final ApexManager mApexManager; private final PowerManager mPowerManager; + private final Context mContext; private final PreRebootVerificationHandler mPreRebootVerificationHandler; @GuardedBy("mStagedSessions") @@ -84,6 +86,7 @@ public class StagingManager { StagingManager(PackageInstallerService pi, ApexManager am, Context context) { mPi = pi; mApexManager = am; + mContext = context; mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mPreRebootVerificationHandler = new PreRebootVerificationHandler( BackgroundThread.get().getLooper()); @@ -539,6 +542,10 @@ public class StagingManager { mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId); } + private int parentOrOwnSessionId(PackageInstallerSession session) { + return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId; + } + /** *

Check if the session provided is non-overlapping with the active staged sessions. * @@ -561,6 +568,9 @@ public class StagingManager { "Cannot stage session " + session.sessionId + " with package name null"); } + boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService( + Context.STORAGE_SERVICE)).isCheckpointSupported(); + synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); @@ -601,7 +611,17 @@ public class StagingManager { + stagedSession.sessionId, null); } - // TODO(b/141843321): Add support for staging multiple sessions in apexd + // Staging multiple root sessions is not allowed if device doesn't support + // checkpoint. If session and stagedSession do not have common ancestor, they are + // from two different root sessions. + if (!supportsCheckpoint + && parentOrOwnSessionId(session) != parentOrOwnSessionId(stagedSession)) { + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, + "Cannot stage multiple sessions without checkpoint support", null); + } + + // TODO:b/141843321 Add support for staging multiple sessions in apexd // Since apexd doesn't support multiple staged sessions yet, we have to careful how // we handle apex sessions. We want to allow a set of apex sessions under the same // parent to be staged when there is no previously staged apex sessions. From 0943a760f59bb38da8de0ff154a1343c7b5e9b6d Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Wed, 20 Nov 2019 15:14:36 +0000 Subject: [PATCH 3/6] Allow StagingManager to abort individual staged session Now that apexd can stage multiple sessions, we need to be able to abort specific staged session during pre-reboot verification. The only need to abort activated session while resuming staged session during boot. Bug: 141843321 Test: atest StagedInstallTest Change-Id: I97d4216e1fe9ec6c98474ec12f4046b002d9edda Merged-In: I97d4216e1fe9ec6c98474ec12f4046b002d9edda (cherry picked from commit 44ad95be0c82287834b909a46cc72becf314a354) --- .../com/android/server/pm/ApexManager.java | 27 +++++++++++++++++++ .../com/android/server/pm/StagingManager.java | 9 ++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 7690b0b3598cb..46a3276edfa33 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -209,6 +209,13 @@ abstract class ApexManager { */ abstract boolean abortActiveSession(); + /** + * Abandons the staged session with the given sessionId. + * + * @return {@code true} upon success, {@code false} if any remote exception occurs + */ + abstract boolean abortStagedSession(int sessionId) throws PackageManagerException; + /** * Uninstalls given {@code apexPackage}. * @@ -499,6 +506,21 @@ abstract class ApexManager { } } + @Override + boolean abortStagedSession(int sessionId) throws PackageManagerException { + try { + mApexService.abortStagedSession(sessionId); + return true; + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + return false; + } catch (Exception e) { + throw new PackageManagerException( + PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Failed to abort staged session : " + e.getMessage()); + } + } + @Override boolean uninstallApex(String apexPackagePath) { try { @@ -686,6 +708,11 @@ abstract class ApexManager { throw new UnsupportedOperationException(); } + @Override + boolean abortStagedSession(int sessionId) throws PackageManagerException { + throw new UnsupportedOperationException(); + } + @Override boolean uninstallApex(String apexPackagePath) { throw new UnsupportedOperationException(); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index cb9a090b37e99..11dba25616cab 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -216,7 +216,7 @@ public class StagingManager { } final long activeVersion = activePackage.applicationInfo.longVersionCode; if (activeVersion != session.params.requiredInstalledVersionCode) { - if (!mApexManager.abortActiveSession()) { + if (!mApexManager.abortStagedSession(session.sessionId)) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } throw new PackageManagerException( @@ -235,7 +235,7 @@ public class StagingManager { final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( session.params.installFlags, activePackage.applicationInfo.flags); if (activeVersion > newVersionCode && !allowsDowngrade) { - if (!mApexManager.abortActiveSession()) { + if (!mApexManager.abortStagedSession(session.sessionId)) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } throw new PackageManagerException( @@ -678,7 +678,10 @@ public class StagingManager { + " because it is not active or APEXD is not reachable"); return; } - mApexManager.abortActiveSession(); + try { + mApexManager.abortStagedSession(session.sessionId); + } catch (Exception ignore) { + } } } From 5f38f28e954130bff275bcf142661a45288602a4 Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Thu, 21 Nov 2019 10:38:06 +0000 Subject: [PATCH 4/6] Replace abortActiveSession in StagingManager with revertActiveSessions Bug: 141843321 Test: atest StagedInstallTest Change-Id: I7fc5397f86f843cfb956364775af6fa5369037ba Merged-In: I7fc5397f86f843cfb956364775af6fa5369037ba (cherry picked from commit 1a96eb6dc37f491f76785e18c82b27fec7242685) --- services/core/java/com/android/server/pm/ApexManager.java | 8 ++++---- .../core/java/com/android/server/pm/StagingManager.java | 2 +- .../src/com/android/server/pm/ApexManagerTest.java | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 46a3276edfa33..3a6839bbf4617 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -207,7 +207,7 @@ abstract class ApexManager { * * @return {@code true} upon success, {@code false} if any remote exception occurs */ - abstract boolean abortActiveSession(); + abstract boolean revertActiveSessions(); /** * Abandons the staged session with the given sessionId. @@ -496,9 +496,9 @@ abstract class ApexManager { } @Override - boolean abortActiveSession() { + boolean revertActiveSessions() { try { - mApexService.abortActiveSession(); + mApexService.revertActiveSessions(); return true; } catch (RemoteException re) { Slog.e(TAG, "Unable to contact apexservice", re); @@ -704,7 +704,7 @@ abstract class ApexManager { } @Override - boolean abortActiveSession() { + boolean revertActiveSessions() { throw new UnsupportedOperationException(); } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 11dba25616cab..956664a8e9e77 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -323,7 +323,7 @@ public class StagingManager { return; } - if (!mApexManager.abortActiveSession()) { + if (!mApexManager.revertActiveSessions()) { Slog.e(TAG, "Failed to abort APEXd session"); } else { Slog.e(TAG, diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java index 143dc28f57d1d..30d103576c997 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java @@ -216,11 +216,11 @@ public class ApexManagerTest { } @Test - public void testAbortActiveSession_remoteException() throws RemoteException { - doThrow(RemoteException.class).when(mApexService).abortActiveSession(); + public void testRevertActiveSessions_remoteException() throws RemoteException { + doThrow(RemoteException.class).when(mApexService).revertActiveSessions(); try { - assertThat(mApexManager.abortActiveSession()).isFalse(); + assertThat(mApexManager.revertActiveSessions()).isFalse(); } catch (Exception e) { throw new AssertionError("ApexManager should not raise Exception"); } From ca438ddbf3a895ab970b670211da3f65cc6dfbd9 Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Tue, 26 Nov 2019 11:31:48 +0000 Subject: [PATCH 5/6] Allow staging multiple sessions with non-overlapping packages (apk+apex) CL contains changes made in PackageManager world to allow staging multiple apexes on apexd. Bug: 141843321 Test: atest StagedInstallTest#testInstallMultipleStagedSession_ApexOnly Test: atest StagedInstallTest#testInstallMultipleStagedSession_PartialAbandon_ApexOnly Change-Id: Ic7fbdddf30047cafb712a102556b70c6dab271b1 Merged-In: Ic7fbdddf30047cafb712a102556b70c6dab271b1 (cherry picked from commit da4eeabf867bf03e8ce8e40e5b33f9b820f66bc5) --- .../com/android/server/pm/StagingManager.java | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 956664a8e9e77..4bf86d647530a 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -620,32 +620,6 @@ public class StagingManager { PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Cannot stage multiple sessions without checkpoint support", null); } - - // TODO:b/141843321 Add support for staging multiple sessions in apexd - // Since apexd doesn't support multiple staged sessions yet, we have to careful how - // we handle apex sessions. We want to allow a set of apex sessions under the same - // parent to be staged when there is no previously staged apex sessions. - if (isApexSession(session) && isApexSession(stagedSession)) { - // session is apex and it can co-exist with stagedSession only if they are from - // same parent - final boolean coExist; - if (!session.hasParentSessionId() && !stagedSession.hasParentSessionId()) { - // Both single package apex sessions. Cannot co-exist. - coExist = false; - } else { - // At least one of the session has parent. Both must be from same parent. - coExist = - session.getParentSessionId() == stagedSession.getParentSessionId(); - } - if (!coExist) { - throw new PackageManagerException( - PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, - "Package: " + session.getPackageName() + " in session: " - + session.sessionId + " cannot be staged as there is " - + "already another apex staged session: " - + stagedSession.sessionId, null); - } - } } } } From 92b791a3c2cc42f158ae65ec6c0c8fde62530fba Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Thu, 28 Nov 2019 18:14:40 +0000 Subject: [PATCH 6/6] 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 Merged-In: I1619c6f113fc0b07f2c69215683e4964e0e99458 (cherry picked from commit 7e9fdb09fda8205e7f98406689e463ab1e277c42) --- .../com/android/server/pm/ApexManager.java | 3 + .../com/android/server/pm/StagingManager.java | 108 +++++++++++++++--- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 3a6839bbf4617..341a88dd0a878 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -503,6 +503,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; } } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 4bf86d647530a..3e9610ecee498 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -46,6 +46,7 @@ import android.os.ParcelableException; 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; @@ -53,6 +54,7 @@ import android.util.SparseArray; 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; @@ -274,39 +276,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 @@ -318,7 +381,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; } @@ -947,6 +1012,19 @@ public class StagingManager { + session.sessionId, re); } } + // 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