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

Commit 1fa00a50 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topic "staged_userdata_restore"

* changes:
  Followup cleanup after refactoring rollback states.
  Use a single list for available and committed rollbacks.
parents be943c0f e9aaf638
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -187,7 +187,8 @@ public final class RollbackManager {
    /**
     * Expire the rollback data for a given package.
     * This API is meant to facilitate testing of rollback logic for
     * expiring rollback data.
     * expiring rollback data. Removes rollback data for available and
     * recently committed rollbacks that contain the given package.
     *
     * @param packageName the name of the package to expire data for.
     * @throws SecurityException if the caller does not have the
+24 −21
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.server.rollback;

import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
import android.content.rollback.RollbackInfo;
import android.os.storage.StorageManager;
import android.util.IntArray;
import android.util.Log;
@@ -30,9 +29,11 @@ import com.android.server.pm.Installer.InstallerException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Encapsulates the logic for initiating userdata snapshots and rollbacks via installd.
@@ -153,7 +154,7 @@ public class AppDataRollbackHelper {
    }

    /**
     * Computes the list of pending backups for {@code userId} given lists of available rollbacks.
     * Computes the list of pending backups for {@code userId} given lists of rollbacks.
     * Packages pending backup for the given user are added to {@code pendingBackupPackages} along
     * with their corresponding {@code PackageRollbackInfo}.
     *
@@ -162,10 +163,10 @@ public class AppDataRollbackHelper {
     */
    private static List<RollbackData> computePendingBackups(int userId,
            Map<String, PackageRollbackInfo> pendingBackupPackages,
            List<RollbackData> availableRollbacks) {
            List<RollbackData> rollbacks) {
        List<RollbackData> rd = new ArrayList<>();

        for (RollbackData data : availableRollbacks) {
        for (RollbackData data : rollbacks) {
            for (PackageRollbackInfo info : data.info.getPackages()) {
                final IntArray pendingBackupUsers = info.getPendingBackups();
                if (pendingBackupUsers != null) {
@@ -183,20 +184,20 @@ public class AppDataRollbackHelper {
    }

    /**
     * Computes the list of pending restores for {@code userId} given lists of recent rollbacks.
     * Computes the list of pending restores for {@code userId} given lists of rollbacks.
     * Packages pending restore are added to {@code pendingRestores} along with their corresponding
     * {@code PackageRollbackInfo}.
     *
     * @return the list of {@code RollbackInfo} that has pending restores. Note that some of the
     * @return the list of {@code RollbackData} that has pending restores. Note that some of the
     *         restores won't be performed, because they might be counteracted by pending backups.
     */
    private static List<RollbackInfo> computePendingRestores(int userId,
    private static List<RollbackData> computePendingRestores(int userId,
            Map<String, PackageRollbackInfo> pendingRestorePackages,
            List<RollbackInfo> recentRollbacks) {
        List<RollbackInfo> rd = new ArrayList<>();
            List<RollbackData> rollbacks) {
        List<RollbackData> rd = new ArrayList<>();

        for (RollbackInfo data : recentRollbacks) {
            for (PackageRollbackInfo info : data.getPackages()) {
        for (RollbackData data : rollbacks) {
            for (PackageRollbackInfo info : data.info.getPackages()) {
                final RestoreInfo ri = info.getRestoreInfo(userId);
                if (ri != null) {
                    pendingRestorePackages.put(info.getPackageName(), info);
@@ -215,18 +216,18 @@ public class AppDataRollbackHelper {
     * backups updates corresponding {@code changedRollbackData} with a mapping from {@code userId}
     * to a inode of theirs CE user data snapshot.
     *
     * @return a list {@code RollbackData} that have been changed and should be stored on disk.
     * @return the set of {@code RollbackData} that have been changed and should be stored on disk.
     */
    public List<RollbackData> commitPendingBackupAndRestoreForUser(int userId,
            List<RollbackData> availableRollbacks, List<RollbackInfo> recentlyExecutedRollbacks) {
    public Set<RollbackData> commitPendingBackupAndRestoreForUser(int userId,
            List<RollbackData> rollbacks) {

        final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
        final List<RollbackData> pendingBackups = computePendingBackups(userId,
                pendingBackupPackages, availableRollbacks);
                pendingBackupPackages, rollbacks);

        final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
        final List<RollbackInfo> pendingRestores = computePendingRestores(userId,
                pendingRestorePackages, recentlyExecutedRollbacks);
        final List<RollbackData> pendingRestores = computePendingRestores(userId,
                pendingRestorePackages, rollbacks);

        // First remove unnecessary backups, i.e. when user did not unlock their phone between the
        // request to backup data and the request to restore it.
@@ -266,13 +267,13 @@ public class AppDataRollbackHelper {
        }

        if (!pendingRestorePackages.isEmpty()) {
            for (RollbackInfo data : pendingRestores) {
                for (PackageRollbackInfo info : data.getPackages()) {
            for (RollbackData data : pendingRestores) {
                for (PackageRollbackInfo info : data.info.getPackages()) {
                    final RestoreInfo ri = info.getRestoreInfo(userId);
                    if (ri != null) {
                        try {
                            mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
                                    ri.seInfo, userId, data.getRollbackId(),
                                    ri.seInfo, userId, data.info.getRollbackId(),
                                    Installer.FLAG_STORAGE_CE);
                            info.removeRestoreInfo(ri);
                        } catch (InstallerException ie) {
@@ -284,7 +285,9 @@ public class AppDataRollbackHelper {
            }
        }

        return pendingBackups;
        final Set<RollbackData> changed = new HashSet<>(pendingBackups);
        changed.addAll(pendingRestores);
        return changed;
    }

    /**
+40 −10
Original line number Diff line number Diff line
@@ -16,9 +16,13 @@

package com.android.server.rollback;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.rollback.RollbackInfo;

import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
import java.util.ArrayList;

@@ -27,6 +31,30 @@ import java.util.ArrayList;
 * packages.
 */
class RollbackData {
    @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
            ROLLBACK_STATE_ENABLING,
            ROLLBACK_STATE_AVAILABLE,
            ROLLBACK_STATE_COMMITTED,
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface RollbackState {}

    /**
     * The rollback is in the process of being enabled. It is not yet
     * available for use.
     */
    static final int ROLLBACK_STATE_ENABLING = 0;

    /**
     * The rollback is currently available.
     */
    static final int ROLLBACK_STATE_AVAILABLE = 1;

    /**
     * The rollback has been committed.
     */
    static final int ROLLBACK_STATE_COMMITTED = 3;

    /**
     * The rollback info for this rollback.
     */
@@ -40,22 +68,23 @@ class RollbackData {
    /**
     * The time when the upgrade occurred, for purposes of expiring
     * rollback data.
     *
     * The timestamp is not applicable for all rollback states, but we make
     * sure to keep it non-null to avoid potential errors there.
     */
    public Instant timestamp;
    public @NonNull Instant timestamp;

    /**
     * The session ID for the staged session if this rollback data represents a staged session,
     * {@code -1} otherwise.
     */
    public int stagedSessionId;
    public final int stagedSessionId;

    /**
     * A flag to indicate whether the rollback should be considered available
     * for use. This will always be true for rollbacks of non-staged sessions.
     * For rollbacks of staged sessions, this is not set to true until after
     * the staged session has been applied.
     * The current state of the rollback.
     * ENABLING, AVAILABLE, or COMMITTED.
     */
    public boolean isAvailable;
    public @RollbackState int state;

    /**
     * The id of the post-reboot apk session for a staged install, if any.
@@ -85,19 +114,20 @@ class RollbackData {
                /* committedSessionId */ -1);
        this.backupDir = backupDir;
        this.stagedSessionId = stagedSessionId;
        this.isAvailable = (stagedSessionId == -1);
        this.state = ROLLBACK_STATE_ENABLING;
        this.timestamp = Instant.now();
    }

    /**
     * Constructs a RollbackData instance with full rollback data information.
     */
    RollbackData(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
            boolean isAvailable, int apkSessionId, boolean restoreUserDataInProgress) {
            @RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress) {
        this.info = info;
        this.backupDir = backupDir;
        this.timestamp = timestamp;
        this.stagedSessionId = stagedSessionId;
        this.isAvailable = isAvailable;
        this.state = state;
        this.apkSessionId = apkSessionId;
        this.restoreUserDataInProgress = restoreUserDataInProgress;
    }
+200 −289

File changed.

Preview size limit exceeded, changes collapsed.

+54 −88
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.text.ParseException;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
@@ -47,10 +48,8 @@ class RollbackStore {
    private static final String TAG = "RollbackManager";

    // Assuming the rollback data directory is /data/rollback, we use the
    // following directory structure to store persisted data for available and
    // recently executed rollbacks:
    // following directory structure to store persisted data for rollbacks:
    //   /data/rollback/
    //      available/
    //       XXX/
    //           rollback.json
    //           com.package.A/
@@ -59,48 +58,34 @@ class RollbackStore {
    //               base.apk
    //       YYY/
    //           rollback.json
    //              com.package.C/
    //                  base.apk
    //      recently_executed.json
    //
    // * XXX, YYY are the rollbackIds for the corresponding rollbacks.
    // * rollback.json contains all relevant metadata for the rollback. This
    //   file is not written until the rollback is made available.
    // * rollback.json contains all relevant metadata for the rollback.
    //
    // TODO: Use AtomicFile for all the .json files?
    private final File mRollbackDataDir;
    private final File mAvailableRollbacksDir;
    private final File mRecentlyExecutedRollbacksFile;

    RollbackStore(File rollbackDataDir) {
        mRollbackDataDir = rollbackDataDir;
        mAvailableRollbacksDir = new File(mRollbackDataDir, "available");
        mRecentlyExecutedRollbacksFile = new File(mRollbackDataDir, "recently_executed.json");
    }

    /**
     * Reads the list of available rollbacks from persistent storage.
     * Reads the rollback data from persistent storage.
     */
    List<RollbackData> loadAvailableRollbacks() {
        List<RollbackData> availableRollbacks = new ArrayList<>();
        mAvailableRollbacksDir.mkdirs();
        for (File rollbackDir : mAvailableRollbacksDir.listFiles()) {
    List<RollbackData> loadAllRollbackData() {
        List<RollbackData> rollbacks = new ArrayList<>();
        mRollbackDataDir.mkdirs();
        for (File rollbackDir : mRollbackDataDir.listFiles()) {
            if (rollbackDir.isDirectory()) {
                try {
                    RollbackData data = loadRollbackData(rollbackDir);
                    availableRollbacks.add(data);
                    rollbacks.add(loadRollbackData(rollbackDir));
                } catch (IOException e) {
                    // Note: Deleting the rollbackDir here will cause pending
                    // rollbacks to be deleted. This should only ever happen
                    // if reloadPersistedData is called while there are
                    // pending rollbacks. The reloadPersistedData method is
                    // currently only for testing, so that should be okay.
                    Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
                    removeFile(rollbackDir);
                }
            }
        }
        return availableRollbacks;
        return rollbacks;
    }

    /**
@@ -202,38 +187,12 @@ class RollbackStore {
                json.getInt("committedSessionId"));
    }

    /**
     * Reads the list of recently executed rollbacks from persistent storage.
     */
    List<RollbackInfo> loadRecentlyExecutedRollbacks() {
        List<RollbackInfo> recentlyExecutedRollbacks = new ArrayList<>();
        if (mRecentlyExecutedRollbacksFile.exists()) {
            try {
                // TODO: How to cope with changes to the format of this file from
                // when RollbackStore is updated in the future?
                String jsonString = IoUtils.readFileAsString(
                        mRecentlyExecutedRollbacksFile.getAbsolutePath());
                JSONObject object = new JSONObject(jsonString);
                JSONArray array = object.getJSONArray("recentlyExecuted");
                for (int i = 0; i < array.length(); ++i) {
                    recentlyExecutedRollbacks.add(rollbackInfoFromJson(array.getJSONObject(i)));
                }
            } catch (IOException | JSONException e) {
                // TODO: What to do here? Surely we shouldn't just forget about
                // everything after the point of exception?
                Log.e(TAG, "Failed to read recently executed rollbacks", e);
            }
        }

        return recentlyExecutedRollbacks;
    }

    /**
     * Creates a new RollbackData instance for a non-staged rollback with
     * backupDir assigned.
     */
    RollbackData createNonStagedRollback(int rollbackId) throws IOException {
        File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
        File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
        return new RollbackData(rollbackId, backupDir, -1);
    }

@@ -243,7 +202,7 @@ class RollbackStore {
     */
    RollbackData createStagedRollback(int rollbackId, int stagedSessionId)
            throws IOException {
        File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
        File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
        return new RollbackData(rollbackId, backupDir, stagedSessionId);
    }

@@ -276,6 +235,17 @@ class RollbackStore {
        return files;
    }

    /**
     * Deletes all backed up apks and apex files associated with the given
     * rollback.
     */
    static void deletePackageCodePaths(RollbackData data) {
        for (PackageRollbackInfo info : data.info.getPackages()) {
            File targetDir = new File(data.backupDir, info.getPackageName());
            removeFile(targetDir);
        }
    }

    /**
     * Saves the rollback data to persistent storage.
     */
@@ -285,7 +255,7 @@ class RollbackStore {
            dataJson.put("info", rollbackInfoToJson(data.info));
            dataJson.put("timestamp", data.timestamp.toString());
            dataJson.put("stagedSessionId", data.stagedSessionId);
            dataJson.put("isAvailable", data.isAvailable);
            dataJson.put("state", rollbackStateToString(data.state));
            dataJson.put("apkSessionId", data.apkSessionId);
            dataJson.put("restoreUserDataInProgress", data.restoreUserDataInProgress);

@@ -304,29 +274,6 @@ class RollbackStore {
        removeFile(data.backupDir);
    }

    /**
     * Writes the list of recently executed rollbacks to storage.
     */
    void saveRecentlyExecutedRollbacks(List<RollbackInfo> recentlyExecutedRollbacks) {
        try {
            JSONObject json = new JSONObject();
            JSONArray array = new JSONArray();
            json.put("recentlyExecuted", array);

            for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) {
                RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
                array.put(rollbackInfoToJson(rollback));
            }

            PrintWriter pw = new PrintWriter(mRecentlyExecutedRollbacksFile);
            pw.println(json.toString());
            pw.close();
        } catch (IOException | JSONException e) {
            // TODO: What to do here?
            Log.e(TAG, "Failed to save recently executed rollbacks", e);
        }
    }

    /**
     * Reads the metadata for a rollback from the given directory.
     * @throws IOException in case of error reading the data.
@@ -342,10 +289,10 @@ class RollbackStore {
                    backupDir,
                    Instant.parse(dataJson.getString("timestamp")),
                    dataJson.getInt("stagedSessionId"),
                    dataJson.getBoolean("isAvailable"),
                    rollbackStateFromString(dataJson.getString("state")),
                    dataJson.getInt("apkSessionId"),
                    dataJson.getBoolean("restoreUserDataInProgress"));
        } catch (JSONException | DateTimeParseException e) {
        } catch (JSONException | DateTimeParseException | ParseException e) {
            throw new IOException(e);
        }
    }
@@ -444,7 +391,7 @@ class RollbackStore {
     * If the file is a directory, its contents are deleted as well.
     * Has no effect if the directory does not exist.
     */
    private void removeFile(File file) {
    private static void removeFile(File file) {
        if (file.isDirectory()) {
            for (File child : file.listFiles()) {
                removeFile(child);
@@ -454,4 +401,23 @@ class RollbackStore {
            file.delete();
        }
    }

    private static String rollbackStateToString(@RollbackData.RollbackState int state) {
        switch (state) {
            case RollbackData.ROLLBACK_STATE_ENABLING: return "enabling";
            case RollbackData.ROLLBACK_STATE_AVAILABLE: return "available";
            case RollbackData.ROLLBACK_STATE_COMMITTED: return "committed";
        }
        throw new AssertionError("Invalid rollback state: " + state);
    }

    private static @RollbackData.RollbackState int rollbackStateFromString(String state)
            throws ParseException {
        switch (state) {
            case "enabling": return RollbackData.ROLLBACK_STATE_ENABLING;
            case "available": return RollbackData.ROLLBACK_STATE_AVAILABLE;
            case "committed": return RollbackData.ROLLBACK_STATE_COMMITTED;
        }
        throw new ParseException("Invalid rollback state: " + state, 0);
    }
}
Loading