Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8cba96af authored by JW Wang's avatar JW Wang Committed by Android (Google) Code Review
Browse files

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)
parents 4a57cbaa 1318983d
Loading
Loading
Loading
Loading
+30 −7
Original line number Diff line number Diff line
@@ -141,10 +141,16 @@ 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
@@ -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;
    }
}
+35 −13
Original line number Diff line number Diff line
@@ -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);
    }
}
+35 −6
Original line number Diff line number Diff line
@@ -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", ""),
+24 −3
Original line number Diff line number Diff line
@@ -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());

+4 −4
Original line number Diff line number Diff line
@@ -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());