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

Commit c50ac5df authored by Oli Lan's avatar Oli Lan Committed by Android (Google) Code Review
Browse files

Merge changes If00f36da,I995f1647,I226fc2ab

* changes:
  Add methods to Rollback to retrieve and check package names.
  Add per-Rollback synchronisation.
  Change commit of pending backup and restore to work per-Rollback.
parents 644c2bb9 3143ee3f
Loading
Loading
Loading
Loading
+53 −125
Original line number Diff line number Diff line
@@ -23,17 +23,12 @@ import android.util.IntArray;
import android.util.Slog;
import android.util.SparseLongArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.Installer;
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.
@@ -56,6 +51,8 @@ public class AppDataRollbackHelper {
     * {@code userIds}. Updates said {@code packageRollbackInfo} with the inodes of the CE user data
     * snapshot folders.
     */
    @GuardedBy("rollback.getLock")
    // TODO(b/136241838): Move into Rollback and synchronize there.
    public void snapshotAppData(
            int snapshotId, PackageRollbackInfo packageRollbackInfo, int[] userIds) {
        for (int user : userIds) {
@@ -92,6 +89,8 @@ public class AppDataRollbackHelper {
     *         to {@code packageRollbackInfo} are restricted to the removal or addition of {@code
     *         userId} to the list of pending backups or restores.
     */
    @GuardedBy("rollback.getLock")
    // TODO(b/136241838): Move into Rollback and synchronize there.
    public boolean restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo,
            int userId, int appId, String seInfo) {
        int storageFlags = Installer.FLAG_STORAGE_DE;
@@ -135,6 +134,8 @@ public class AppDataRollbackHelper {
     * Deletes an app data snapshot with a given {@code rollbackId} for a specified package
     * {@code packageName} for a given {@code user}.
     */
    @GuardedBy("rollback.getLock")
    // TODO(b/136241838): Move into Rollback and synchronize there.
    public void destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo,
            int user) {
        int storageFlags = Installer.FLAG_STORAGE_DE;
@@ -156,103 +157,42 @@ public class AppDataRollbackHelper {
    }

    /**
     * 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}.
     * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If
     * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode
     * of the CE user data snapshot.
     *
     * @return the list of rollbacks that have pending backups. Note that some of the
     *         backups won't be performed, because they might be counteracted by pending restores.
     * @return true if any backups or restores were found for the userId
     */
    private static List<Rollback> computePendingBackups(int userId,
            Map<String, PackageRollbackInfo> pendingBackupPackages,
            List<Rollback> rollbacks) {
        List<Rollback> rollbacksWithPendingBackups = new ArrayList<>();

        for (Rollback rollback : rollbacks) {
    @GuardedBy("rollback.getLock")
    boolean commitPendingBackupAndRestoreForUser(int userId, Rollback rollback) {
        boolean foundBackupOrRestore = false;
        for (PackageRollbackInfo info : rollback.info.getPackages()) {
            boolean hasPendingBackup = false;
            boolean hasPendingRestore = false;
            final IntArray pendingBackupUsers = info.getPendingBackups();
            if (pendingBackupUsers != null) {
                    final int idx = pendingBackupUsers.indexOf(userId);
                    if (idx != -1) {
                        pendingBackupPackages.put(info.getPackageName(), info);
                        if (rollbacksWithPendingBackups.indexOf(rollback) == -1) {
                            rollbacksWithPendingBackups.add(rollback);
                        }
                    }
                }
                if (pendingBackupUsers.indexOf(userId) != -1) {
                    hasPendingBackup = true;
                    foundBackupOrRestore = true;
                }
            }
        return rollbacksWithPendingBackups;
    }

    /**
     * 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 rollbacks that have pending restores. Note that some of the
     *         restores won't be performed, because they might be counteracted by pending backups.
     */
    private static List<Rollback> computePendingRestores(int userId,
            Map<String, PackageRollbackInfo> pendingRestorePackages,
            List<Rollback> rollbacks) {
        List<Rollback> rollbacksWithPendingRestores = new ArrayList<>();

        for (Rollback rollback : rollbacks) {
            for (PackageRollbackInfo info : rollback.info.getPackages()) {
                final RestoreInfo ri = info.getRestoreInfo(userId);
            RestoreInfo ri = info.getRestoreInfo(userId);
            if (ri != null) {
                    pendingRestorePackages.put(info.getPackageName(), info);
                    if (rollbacksWithPendingRestores.indexOf(rollback) == -1) {
                        rollbacksWithPendingRestores.add(rollback);
                    }
                }
                hasPendingRestore = true;
                foundBackupOrRestore = true;
            }
        }

        return rollbacksWithPendingRestores;
    }

    /**
     * Commits the list of pending backups and restores for a given {@code userId}. For rollbacks
     * with pending backups, updates the {@code Rollback} instance with a mapping from
     * {@code userId} to inode of the CE user data snapshot.
     *
     * @return the set of rollbacks with changes that should be stored on disk.
     */
    public Set<Rollback> commitPendingBackupAndRestoreForUser(int userId,
            List<Rollback> rollbacks) {

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

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

        // First remove unnecessary backups, i.e. when user did not unlock their phone between the
            if (hasPendingBackup && hasPendingRestore) {
                // Remove unnecessary backup, i.e. when user did not unlock their phone between the
                // request to backup data and the request to restore it.
        Iterator<Map.Entry<String, PackageRollbackInfo>> iter =
                pendingBackupPackages.entrySet().iterator();
        while (iter.hasNext()) {
            PackageRollbackInfo backupPackage = iter.next().getValue();
            PackageRollbackInfo restorePackage =
                    pendingRestorePackages.get(backupPackage.getPackageName());
            if (restorePackage != null) {
                backupPackage.removePendingBackup(userId);
                backupPackage.removePendingRestoreInfo(userId);
                iter.remove();
                pendingRestorePackages.remove(backupPackage.getPackageName());
            }
                info.removePendingBackup(userId);
                info.removePendingRestoreInfo(userId);
                continue;
            }

        if (!pendingBackupPackages.isEmpty()) {
            for (Rollback rollback : pendingBackups) {
                for (PackageRollbackInfo info : rollback.info.getPackages()) {
                    final IntArray pendingBackupUsers = info.getPendingBackups();
                    final int idx = pendingBackupUsers.indexOf(userId);
                    if (idx != -1) {
            if (hasPendingBackup) {
                int idx = pendingBackupUsers.indexOf(userId);
                try {
                    long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
                            userId, rollback.info.getRollbackId(),
@@ -265,15 +205,8 @@ public class AppDataRollbackHelper {
                                    + info.getPackageName() + ", userId: " + userId, ie);
                }
            }
                }
            }
        }

        if (!pendingRestorePackages.isEmpty()) {
            for (Rollback rollback : pendingRestores) {
                for (PackageRollbackInfo info : rollback.info.getPackages()) {
                    final RestoreInfo ri = info.getRestoreInfo(userId);
                    if (ri != null) {
            if (hasPendingRestore) {
                try {
                    mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
                            ri.seInfo, userId, rollback.info.getRollbackId(),
@@ -285,12 +218,7 @@ public class AppDataRollbackHelper {
                }
            }
        }
            }
        }

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

    /**
+112 −9
Original line number Diff line number Diff line
@@ -18,19 +18,27 @@ package com.android.server.rollback;

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

import com.android.internal.annotations.GuardedBy;

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


/**
 * Information about a rollback available for a set of atomically installed
 * packages.
 * Information about a rollback available for a set of atomically installed packages.
 *
 * <p>When accessing the state of a Rollback object, the caller is responsible for synchronization.
 * The lock object provided by {@link #getLock} should be acquired when accessing any of the mutable
 * state of a Rollback, including from the {@link RollbackInfo} and any of the
 * {@link PackageRollbackInfo} objects held within.
 */
class Rollback {
    @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
@@ -57,9 +65,19 @@ class Rollback {
     */
    static final int ROLLBACK_STATE_COMMITTED = 3;

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

    /**
     * The rollback info for this rollback.
     *
     * <p>Any access to this field that touches any mutable state should be synchronized on
     * {@link #getLock}.
     */
    @GuardedBy("getLock")
    public final RollbackInfo info;

    /**
@@ -74,23 +92,20 @@ class Rollback {
     * The timestamp is not applicable for all rollback states, but we make
     * sure to keep it non-null to avoid potential errors there.
     */
    @GuardedBy("mLock")
    private @NonNull Instant mTimestamp;

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

    /**
     * The current state of the rollback.
     * ENABLING, AVAILABLE, or COMMITTED.
     */
    @GuardedBy("mLock")
    private @RollbackState int mState;

    /**
     * The id of the post-reboot apk session for a staged install, if any.
     */
    @GuardedBy("mLock")
    private int mApkSessionId = -1;

    /**
@@ -98,9 +113,16 @@ class Rollback {
     * for this rollback because it has just been committed but the rollback
     * has not yet been fully applied.
     */
    // NOTE: All accesses to this field are from the RollbackManager handler thread.
    @GuardedBy("mLock")
    private boolean mRestoreUserDataInProgress = false;

    /**
     * Lock object to guard all access to Rollback state.
     *
     * @see #getLock
     */
    private final Object mLock = new Object();

    /**
     * Constructs a new, empty Rollback instance.
     *
@@ -134,9 +156,24 @@ class Rollback {
        mRestoreUserDataInProgress = restoreUserDataInProgress;
    }

    /**
     * Returns a lock object that should be acquired before accessing any Rollback state from
     * {@link RollbackManagerServiceImpl}.
     *
     * <p>Note that while holding this lock, the lock for {@link RollbackManagerServiceImpl} should
     * not be acquired (but it is ok to acquire this lock while already holding the lock for that
     * class).
     */
    // TODO(b/136241838): Move rollback functionality into this class and synchronize on the lock
    // internally. Remove this method once this has been done for all cases.
    Object getLock() {
        return mLock;
    }

    /**
     * Whether the rollback is for rollback of a staged install.
     */
    @GuardedBy("getLock")
    boolean isStaged() {
        return info.isStaged();
    }
@@ -151,6 +188,7 @@ class Rollback {
    /**
     * Returns the time when the upgrade occurred, for purposes of expiring rollback data.
     */
    @GuardedBy("getLock")
    Instant getTimestamp() {
        return mTimestamp;
    }
@@ -158,6 +196,7 @@ class Rollback {
    /**
     * Sets the time at which upgrade occurred.
     */
    @GuardedBy("getLock")
    void setTimestamp(Instant timestamp) {
        mTimestamp = timestamp;
    }
@@ -173,6 +212,7 @@ class Rollback {
    /**
     * Returns true if the rollback is in the ENABLING state.
     */
    @GuardedBy("getLock")
    boolean isEnabling() {
        return mState == ROLLBACK_STATE_ENABLING;
    }
@@ -180,6 +220,7 @@ class Rollback {
    /**
     * Returns true if the rollback is in the AVAILABLE state.
     */
    @GuardedBy("getLock")
    boolean isAvailable() {
        return mState == ROLLBACK_STATE_AVAILABLE;
    }
@@ -187,6 +228,7 @@ class Rollback {
    /**
     * Returns true if the rollback is in the COMMITTED state.
     */
    @GuardedBy("getLock")
    boolean isCommitted() {
        return mState == ROLLBACK_STATE_COMMITTED;
    }
@@ -194,6 +236,7 @@ class Rollback {
    /**
     * Sets the state of the rollback to AVAILABLE.
     */
    @GuardedBy("getLock")
    void setAvailable() {
        mState = ROLLBACK_STATE_AVAILABLE;
    }
@@ -201,6 +244,7 @@ class Rollback {
    /**
     * Sets the state of the rollback to COMMITTED.
     */
    @GuardedBy("getLock")
    void setCommitted() {
        mState = ROLLBACK_STATE_COMMITTED;
    }
@@ -208,6 +252,7 @@ class Rollback {
    /**
     * Returns the id of the post-reboot apk session for a staged install, if any.
     */
    @GuardedBy("getLock")
    int getApkSessionId() {
        return mApkSessionId;
    }
@@ -215,6 +260,7 @@ class Rollback {
    /**
     * Sets the id of the post-reboot apk session for a staged install.
     */
    @GuardedBy("getLock")
    void setApkSessionId(int apkSessionId) {
        mApkSessionId = apkSessionId;
    }
@@ -223,6 +269,7 @@ class Rollback {
     * Returns 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.
     */
    @GuardedBy("getLock")
    boolean isRestoreUserDataInProgress() {
        return mRestoreUserDataInProgress;
    }
@@ -231,10 +278,65 @@ class Rollback {
     * Sets whether 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.
     */
    @GuardedBy("getLock")
    void setRestoreUserDataInProgress(boolean restoreUserDataInProgress) {
        mRestoreUserDataInProgress = restoreUserDataInProgress;
    }

    /**
     * Returns true if this rollback includes the package with the provided {@code packageName}.
     */
    @GuardedBy("getLock")
    boolean includesPackage(String packageName) {
        for (PackageRollbackInfo info : info.getPackages()) {
            if (info.getPackageName().equals(packageName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if this rollback includes the package with the provided {@code packageName}
     * with a <i>version rolled back from</i> that is not {@code versionCode}.
     */
    @GuardedBy("getLock")
    boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
        for (PackageRollbackInfo info : info.getPackages()) {
            if (info.getPackageName().equals(packageName)
                    && info.getVersionRolledBackFrom().getLongVersionCode() != versionCode) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns a list containing the names of all the packages included in this rollback.
     */
    @GuardedBy("getLock")
    List<String> getPackageNames() {
        List<String> result = new ArrayList<>();
        for (PackageRollbackInfo info : info.getPackages()) {
            result.add(info.getPackageName());
        }
        return result;
    }

    /**
     * Returns a list containing the names of all the apex packages included in this rollback.
     */
    @GuardedBy("getLock")
    List<String> getApexPackageNames() {
        List<String> result = new ArrayList<>();
        for (PackageRollbackInfo info : info.getPackages()) {
            if (info.isApex()) {
                result.add(info.getPackageName());
            }
        }
        return result;
    }

    static String rollbackStateToString(@RollbackState int state) {
        switch (state) {
            case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
@@ -254,6 +356,7 @@ class Rollback {
        throw new ParseException("Invalid rollback state: " + state, 0);
    }

    @GuardedBy("getLock")
    String getStateAsString() {
        return rollbackStateToString(mState);
    }
+273 −239

File changed.

Preview size limit exceeded, changes collapsed.

+3 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.util.IntArray;
import android.util.Slog;
import android.util.SparseLongArray;

import com.android.internal.annotations.GuardedBy;

import libcore.io.IoUtils;

import org.json.JSONArray;
@@ -250,6 +252,7 @@ class RollbackStore {
    /**
     * Saves the given rollback to persistent storage.
     */
    @GuardedBy("rollback.getLock")
    void saveRollback(Rollback rollback) throws IOException {
        try {
            JSONObject dataJson = new JSONObject();
+3 −11
Original line number Diff line number Diff line
@@ -45,8 +45,6 @@ import org.mockito.Mockito;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;

@RunWith(JUnit4.class)
public class AppDataRollbackHelperTest {
@@ -250,28 +248,22 @@ public class AppDataRollbackHelperTest {
        dataForRestore.info.getPackages().add(pendingRestore);
        dataForRestore.info.getPackages().add(wasRecentlyRestored);

        Set<Rollback> changed = helper.commitPendingBackupAndRestoreForUser(37,
                Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser,
                    dataForRestore));
        InOrder inOrder = Mockito.inOrder(installer);

        // Check that pending backup and restore for the same package mutually destroyed each other.
        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithRecentRestore));
        assertEquals(-1, wasRecentlyRestored.getPendingBackups().indexOf(37));
        assertNull(wasRecentlyRestored.getRestoreInfo(37));

        // Check that backup was performed.
        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithPendingBackup));
        inOrder.verify(installer).snapshotAppData(eq("com.foo"), eq(37), eq(101),
                eq(Installer.FLAG_STORAGE_CE));
        assertEquals(-1, pendingBackup.getPendingBackups().indexOf(37));
        assertEquals(53, pendingBackup.getCeSnapshotInodes().get(37));

        // Check that changed returns correct Rollback.
        assertEquals(3, changed.size());
        assertTrue(changed.contains(dataWithPendingBackup));
        assertTrue(changed.contains(dataWithRecentRestore));
        assertTrue(changed.contains(dataForRestore));

        // Check that restore was performed.
        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataForRestore));
        inOrder.verify(installer).restoreAppDataSnapshot(
                eq("com.abc"), eq(57) /* appId */, eq("seInfo"), eq(37) /* userId */,
                eq(17239) /* rollbackId */, eq(Installer.FLAG_STORAGE_CE));
Loading