diff options
| author | 2020-11-26 11:03:43 +0000 | |
|---|---|---|
| committer | 2020-11-26 11:03:43 +0000 | |
| commit | 8cba96af6d7ccb7d93a4af461f3b38b86ee9baac (patch) | |
| tree | cd7423973bb831e11a80d70b01279e99d42cadf9 | |
| parent | 4a57cbaa565b243aa6883eb30d4cb870603f75fe (diff) | |
| parent | 1318983d20262576b9333087487773804fb7a88a (diff) | |
Merge changes I0ca6d083,I96d81313,I6010de4d,I608316d2
* changes:
Record reason why the rollback is deleted (5/n)
Delete committed rollbacks when they expire (4/n)
Dump historical rollbacks for debugging purpose (3/n)
Save deleted rollbacks for debugging purpose (2/n)
5 files changed, 128 insertions, 33 deletions
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 2d51cf9a5083..63ed416f2859 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -141,11 +141,17 @@ class Rollback { /** * The current state of the rollback. - * ENABLING, AVAILABLE, or COMMITTED. + * ENABLING, AVAILABLE, DELETED, or COMMITTED. */ private @RollbackState int mState; /** + * The detailed description of the current state. For a DELETED state, it describes + * the reason why the rollback is deleted. + */ + private @NonNull String mStateDescription = ""; + + /** * True if we are expecting the package manager to call restoreUserData * for this rollback because it has just been committed but the rollback * has not yet been fully applied. @@ -231,7 +237,7 @@ class Rollback { * Constructs a pre-populated Rollback instance. */ Rollback(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId, - @RollbackState int state, boolean restoreUserDataInProgress, + @RollbackState int state, String stateDescription, boolean restoreUserDataInProgress, int userId, String installerPackageName, SparseIntArray extensionVersions) { this.info = info; mUserId = userId; @@ -240,6 +246,7 @@ class Rollback { mTimestamp = timestamp; mStagedSessionId = stagedSessionId; mState = state; + mStateDescription = stateDescription; mRestoreUserDataInProgress = restoreUserDataInProgress; mExtensionVersions = Objects.requireNonNull(extensionVersions); // TODO(b/120200473): Include this field during persistence. This field will be used to @@ -478,7 +485,7 @@ class Rollback { Slog.w(TAG, "Cannot make deleted rollback available."); return; } - mState = ROLLBACK_STATE_AVAILABLE; + setState(ROLLBACK_STATE_AVAILABLE, ""); mTimestamp = Instant.now(); RollbackStore.saveRollback(this); } @@ -598,7 +605,7 @@ class Rollback { // Why would we expect commit not to fail again? // TODO: Could this cause a rollback to be resurrected // if it should otherwise have expired by now? - mState = ROLLBACK_STATE_AVAILABLE; + setState(ROLLBACK_STATE_AVAILABLE, "Commit failed"); mRestoreUserDataInProgress = false; info.setCommittedSessionId(-1); sendFailure(context, statusReceiver, @@ -642,7 +649,7 @@ class Rollback { }; final LocalIntentReceiver receiver = new LocalIntentReceiver(onResult); - mState = ROLLBACK_STATE_COMMITTED; + setState(ROLLBACK_STATE_COMMITTED, ""); info.setCommittedSessionId(parentSessionId); mRestoreUserDataInProgress = true; parentSession.commit(receiver.getIntentSender()); @@ -691,7 +698,7 @@ class Rollback { * Deletes app data snapshots associated with this rollback, and moves to the DELETED state. */ @WorkerThread - void delete(AppDataRollbackHelper dataHelper) { + void delete(AppDataRollbackHelper dataHelper, @NonNull String reason) { assertInWorkerThread(); boolean containsApex = false; Set<Integer> apexUsers = new ArraySet<>(); @@ -717,7 +724,7 @@ class Rollback { } RollbackStore.deleteRollback(this); - mState = ROLLBACK_STATE_DELETED; + setState(ROLLBACK_STATE_DELETED, reason); } /** @@ -847,6 +854,7 @@ class Rollback { case Rollback.ROLLBACK_STATE_ENABLING: return "enabling"; case Rollback.ROLLBACK_STATE_AVAILABLE: return "available"; case Rollback.ROLLBACK_STATE_COMMITTED: return "committed"; + case Rollback.ROLLBACK_STATE_DELETED: return "deleted"; } throw new AssertionError("Invalid rollback state: " + state); } @@ -858,6 +866,7 @@ class Rollback { case "enabling": return Rollback.ROLLBACK_STATE_ENABLING; case "available": return Rollback.ROLLBACK_STATE_AVAILABLE; case "committed": return Rollback.ROLLBACK_STATE_COMMITTED; + case "deleted": return Rollback.ROLLBACK_STATE_DELETED; } throw new ParseException("Invalid rollback state: " + state, 0); } @@ -926,6 +935,7 @@ class Rollback { ipw.println(info.getRollbackId() + ":"); ipw.increaseIndent(); ipw.println("-state: " + getStateAsString()); + ipw.println("-stateDescription: " + mStateDescription); ipw.println("-timestamp: " + getTimestamp()); if (getStagedSessionId() != -1) { ipw.println("-stagedSessionId: " + getStagedSessionId()); @@ -955,4 +965,17 @@ class Rollback { } ipw.decreaseIndent(); } + + @WorkerThread + String getStateDescription() { + assertInWorkerThread(); + return mStateDescription; + } + + @VisibleForTesting + void setState(@RollbackState int state, String description) { + assertInWorkerThread(); + mState = state; + mStateDescription = description; + } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index b34d46ff1a0c..192a00303a30 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -179,7 +179,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba mInstaller = new Installer(mContext); mInstaller.onStart(); - mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); + mRollbackStore = new RollbackStore( + new File(Environment.getDataDirectory(), "rollback"), + new File(Environment.getDataDirectory(), "rollback-history")); mPackageHealthObserver = new RollbackPackageHealthObserver(mContext); mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller); @@ -201,7 +203,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } else { // Delete rollbacks when build fingerprint has changed. for (Rollback rollback : mRollbacks) { - rollback.delete(mAppDataRollbackHelper); + deleteRollback(rollback, "Fingerprint changed"); } mRollbacks.clear(); } @@ -271,7 +273,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba Rollback rollback = getRollbackForSession(sessionId); if (rollback != null && rollback.isEnabling()) { mRollbacks.remove(rollback); - rollback.delete(mAppDataRollbackHelper); + deleteRollback(rollback, "Rollback canceled"); } } } @@ -477,14 +479,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } @WorkerThread - private void expireRollbackForPackageInternal(String packageName) { + private void expireRollbackForPackageInternal(String packageName, String reason) { assertInWorkerThread(); Iterator<Rollback> iter = mRollbacks.iterator(); while (iter.hasNext()) { Rollback rollback = iter.next(); if (rollback.includesPackage(packageName)) { iter.remove(); - rollback.delete(mAppDataRollbackHelper); + deleteRollback(rollback, reason); } } } @@ -496,7 +498,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba mContext.enforceCallingOrSelfPermission( Manifest.permission.TEST_MANAGE_ROLLBACKS, "expireRollbackForPackage"); - awaitResult(() -> expireRollbackForPackageInternal(packageName)); + awaitResult(() -> expireRollbackForPackageInternal(packageName, "Expired by API")); } @ExtThread @@ -612,7 +614,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba .getPackageInstaller().getSessionInfo(rollback.getStagedSessionId()); if (session == null || session.isStagedSessionFailed()) { iter.remove(); - rollback.delete(mAppDataRollbackHelper); + deleteRollback(rollback, + "Session " + session.getSessionId() + " not existed or failed"); continue; } @@ -666,7 +669,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba && rollback.includesPackageWithDifferentVersion(packageName, installedVersion)) { iter.remove(); - rollback.delete(mAppDataRollbackHelper); + deleteRollback(rollback, "Package " + packageName + " replaced"); } } } @@ -678,7 +681,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba @WorkerThread private void onPackageFullyRemoved(String packageName) { assertInWorkerThread(); - expireRollbackForPackageInternal(packageName); + expireRollbackForPackageInternal(packageName, "Package " + packageName + " removed"); } /** @@ -713,14 +716,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba Iterator<Rollback> iter = mRollbacks.iterator(); while (iter.hasNext()) { Rollback rollback = iter.next(); - if (!rollback.isAvailable()) { + if (!rollback.isAvailable() && !rollback.isCommitted()) { continue; } Instant rollbackTimestamp = rollback.getTimestamp(); if (!now.isBefore(rollbackTimestamp.plusMillis(mRollbackLifetimeDurationInMillis))) { Slog.i(TAG, "runExpiration id=" + rollback.info.getRollbackId()); iter.remove(); - rollback.delete(mAppDataRollbackHelper); + deleteRollback(rollback, "Expired by timeout"); } else if (oldest == null || oldest.isAfter(rollbackTimestamp)) { oldest = rollbackTimestamp; } @@ -1132,7 +1135,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba Slog.w(TAG, "Delete rollback id=" + rollback.info.getRollbackId() + " for failed session id=" + sessionId); mRollbacks.remove(rollback); - rollback.delete(mAppDataRollbackHelper); + deleteRollback(rollback, "Session " + sessionId + " failed"); } } } @@ -1159,7 +1162,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba if (!rollback.allPackagesEnabled()) { Slog.e(TAG, "Failed to enable rollback for all packages in session."); mRollbacks.remove(rollback); - rollback.delete(mAppDataRollbackHelper); + deleteRollback(rollback, "Failed to enable rollback for all packages in session"); return false; } @@ -1240,6 +1243,18 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba rollback.dump(ipw); } ipw.println(); + + List<Rollback> historicalRollbacks = mRollbackStore.loadHistorialRollbacks(); + if (!historicalRollbacks.isEmpty()) { + ipw.println("Historical rollbacks:"); + ipw.increaseIndent(); + for (Rollback rollback : historicalRollbacks) { + rollback.dump(ipw); + } + ipw.decreaseIndent(); + ipw.println(); + } + PackageWatchdog.getInstance(mContext).dump(ipw); }); } @@ -1329,4 +1344,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } return null; } + + @WorkerThread + private void deleteRollback(Rollback rollback, String reason) { + assertInWorkerThread(); + rollback.delete(mAppDataRollbackHelper, reason); + mRollbackStore.saveRollbackToHistory(rollback); + } } diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 2ee87e67e467..35c9f9ae6683 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -69,18 +69,20 @@ class RollbackStore { // * XXX, YYY are the rollbackIds for the corresponding rollbacks. // * rollback.json contains all relevant metadata for the rollback. private final File mRollbackDataDir; + private final File mRollbackHistoryDir; - RollbackStore(File rollbackDataDir) { + RollbackStore(File rollbackDataDir, File rollbackHistoryDir) { mRollbackDataDir = rollbackDataDir; + mRollbackHistoryDir = rollbackHistoryDir; } /** * Reads the rollbacks from persistent storage. */ - List<Rollback> loadRollbacks() { + private static List<Rollback> loadRollbacks(File rollbackDataDir) { List<Rollback> rollbacks = new ArrayList<>(); - mRollbackDataDir.mkdirs(); - for (File rollbackDir : mRollbackDataDir.listFiles()) { + rollbackDataDir.mkdirs(); + for (File rollbackDir : rollbackDataDir.listFiles()) { if (rollbackDir.isDirectory()) { try { rollbacks.add(loadRollback(rollbackDir)); @@ -93,6 +95,14 @@ class RollbackStore { return rollbacks; } + List<Rollback> loadRollbacks() { + return loadRollbacks(mRollbackDataDir); + } + + List<Rollback> loadHistorialRollbacks() { + return loadRollbacks(mRollbackHistoryDir); + } + /** * Converts a {@code JSONArray} of integers to a {@code List<Integer>}. */ @@ -258,15 +268,17 @@ class RollbackStore { /** * Saves the given rollback to persistent storage. */ - static void saveRollback(Rollback rollback) { + private static void saveRollback(Rollback rollback, File backDir) { FileOutputStream fos = null; - AtomicFile file = new AtomicFile(new File(rollback.getBackupDir(), "rollback.json")); + AtomicFile file = new AtomicFile(new File(backDir, "rollback.json")); try { + backDir.mkdirs(); JSONObject dataJson = new JSONObject(); dataJson.put("info", rollbackInfoToJson(rollback.info)); dataJson.put("timestamp", rollback.getTimestamp().toString()); dataJson.put("stagedSessionId", rollback.getStagedSessionId()); dataJson.put("state", rollback.getStateAsString()); + dataJson.put("stateDescription", rollback.getStateDescription()); dataJson.put("restoreUserDataInProgress", rollback.isRestoreUserDataInProgress()); dataJson.put("userId", rollback.getUserId()); dataJson.putOpt("installerPackageName", rollback.getInstallerPackageName()); @@ -286,6 +298,22 @@ class RollbackStore { } } + static void saveRollback(Rollback rollback) { + saveRollback(rollback, rollback.getBackupDir()); + } + + /** + * Saves the rollback to $mRollbackHistoryDir/ROLLBACKID-HEX for debugging purpose. + */ + void saveRollbackToHistory(Rollback rollback) { + // The same id might be allocated to different historical rollbacks. + // Let's add a suffix to avoid naming collision. + String suffix = Long.toHexString(rollback.getTimestamp().getEpochSecond()); + String dirName = Integer.toString(rollback.info.getRollbackId()); + File backupDir = new File(mRollbackHistoryDir, dirName + "-" + suffix); + saveRollback(rollback, backupDir); + } + /** * Removes all persistent storage associated with the given rollback. */ @@ -318,6 +346,7 @@ class RollbackStore { Instant.parse(dataJson.getString("timestamp")), dataJson.getInt("stagedSessionId"), rollbackStateFromString(dataJson.getString("state")), + dataJson.optString("stateDescription"), dataJson.getBoolean("restoreUserDataInProgress"), dataJson.optInt("userId", UserHandle.SYSTEM.getIdentifier()), dataJson.optString("installerPackageName", ""), diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java index eedc9781aa40..c42f936d3ab4 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java @@ -110,6 +110,8 @@ public class RollbackStoreTest { @Rule public TemporaryFolder mFolder = new TemporaryFolder(); + @Rule + public TemporaryFolder mHistoryDir = new TemporaryFolder(); private File mRollbackDir; @@ -117,7 +119,7 @@ public class RollbackStoreTest { @Before public void setUp() throws Exception { - mRollbackStore = new RollbackStore(mFolder.getRoot()); + mRollbackStore = new RollbackStore(mFolder.getRoot(), mHistoryDir.getRoot()); mRollbackDir = mFolder.newFolder(ID + ""); mFolder.newFile("rollback.json"); } @@ -202,6 +204,8 @@ public class RollbackStoreTest { origRb.info.getPackages().add(pkgInfo1); origRb.info.getPackages().add(pkgInfo2); + origRb.setState(Rollback.ROLLBACK_STATE_AVAILABLE, "hello world"); + RollbackStore.saveRollback(origRb); List<Rollback> loadedRollbacks = mRollbackStore.loadRollbacks(); @@ -324,10 +328,26 @@ public class RollbackStoreTest { assertThat(expectedFile.exists()).isFalse(); } - private void assertRollbacksAreEquivalent(Rollback b, Rollback a) { - assertThat(b.info.getRollbackId()).isEqualTo(ID); + @Test + public void saveToHistoryAndLoad() { + Rollback origRb = mRollbackStore.createNonStagedRollback( + ID, USER, INSTALLER, null, new SparseIntArray(0)); + mRollbackStore.saveRollbackToHistory(origRb); + + List<Rollback> loadedRollbacks = mRollbackStore.loadHistorialRollbacks(); + assertThat(loadedRollbacks).hasSize(1); + Rollback loadedRb = loadedRollbacks.get(0); + + assertRollbacksAreEquivalentExcludingBackupDir(loadedRb, origRb); + } + private void assertRollbacksAreEquivalent(Rollback b, Rollback a) { assertThat(b.getBackupDir()).isEqualTo(a.getBackupDir()); + assertRollbacksAreEquivalentExcludingBackupDir(b, a); + } + + private void assertRollbacksAreEquivalentExcludingBackupDir(Rollback b, Rollback a) { + assertThat(b.info.getRollbackId()).isEqualTo(ID); assertThat(b.isRestoreUserDataInProgress()) .isEqualTo(a.isRestoreUserDataInProgress()); @@ -337,6 +357,7 @@ public class RollbackStoreTest { assertThat(b.isEnabling()).isEqualTo(a.isEnabling()); assertThat(b.isAvailable()).isEqualTo(a.isAvailable()); assertThat(b.isCommitted()).isEqualTo(a.isCommitted()); + assertThat(b.getStateDescription()).isEqualTo(a.getStateDescription()); assertThat(b.isStaged()).isEqualTo(a.isStaged()); diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index cd2c9230221c..cf1ed4815a74 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -123,7 +123,7 @@ public class RollbackUnitTest { public void deletedRollbackCannotBeMadeAvailable() { Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); - rollback.delete(mMockDataHelper); + rollback.delete(mMockDataHelper, "test"); assertThat(rollback.isDeleted()).isTrue(); @@ -221,7 +221,7 @@ public class RollbackUnitTest { PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); - rollback.delete(mMockDataHelper); + rollback.delete(mMockDataHelper, "test"); assertThat(rollback.isDeleted()).isTrue(); @@ -247,7 +247,7 @@ public class RollbackUnitTest { verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds)); - rollback.delete(mMockDataHelper); + rollback.delete(mMockDataHelper, "test"); verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(111)); verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(222)); @@ -269,7 +269,7 @@ public class RollbackUnitTest { verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds)); - rollback.delete(mMockDataHelper); + rollback.delete(mMockDataHelper, "test"); verify(mMockDataHelper, never()) .destroyAppDataSnapshot(anyInt(), pkgRollbackInfoFor(PKG_2), anyInt()); |