summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author JW Wang <wangchun@google.com> 2020-11-26 11:03:43 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-11-26 11:03:43 +0000
commit8cba96af6d7ccb7d93a4af461f3b38b86ee9baac (patch)
treecd7423973bb831e11a80d70b01279e99d42cadf9
parent4a57cbaa565b243aa6883eb30d4cb870603f75fe (diff)
parent1318983d20262576b9333087487773804fb7a88a (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)
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java37
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java48
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java8
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());