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

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

Merge changes from topic "apexcerollback"

* changes:
  Add tests for DE_sys and CE apex data rollback.
  Snapshot and restore CE apex data directories.
parents 768686b6 26fdbb1a
Loading
Loading
Loading
Loading
+65 −1
Original line number Diff line number Diff line
@@ -96,7 +96,7 @@ public abstract class ApexManager {
     * depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
     * evaluates to {@code true}.
     */
    static ApexManager getInstance() {
    public static ApexManager getInstance() {
        return sApexManagerSingleton.get();
    }

@@ -271,6 +271,21 @@ public abstract class ApexManager {
    @Nullable
    public abstract String getApexModuleNameForPackageName(String apexPackageName);

    /**
     * Copies the CE apex data directory for the given {@code userId} to a backup location, for use
     * in case of rollback.
     *
     * @return long inode for the snapshot directory if the snapshot was successful, or -1 if not
     */
    public abstract long snapshotCeData(int userId, int rollbackId, String apexPackageName);

    /**
     * Restores the snapshot of the CE apex data directory for the given {@code userId}.
     *
     * @return boolean true if the restore was successful
     */
    public abstract boolean restoreCeData(int userId, int rollbackId, String apexPackageName);

    /**
     * Dumps various state information to the provided {@link PrintWriter} object.
     *
@@ -662,6 +677,45 @@ public abstract class ApexManager {
            }
        }

        @Override
        public long snapshotCeData(int userId, int rollbackId, String apexPackageName) {
            populatePackageNameToApexModuleNameIfNeeded();
            String apexModuleName;
            synchronized (mLock) {
                apexModuleName = mPackageNameToApexModuleName.get(apexPackageName);
            }
            if (apexModuleName == null) {
                Slog.e(TAG, "Invalid apex package name: " + apexPackageName);
                return -1;
            }
            try {
                return mApexService.snapshotCeData(userId, rollbackId, apexModuleName);
            } catch (Exception e) {
                Slog.e(TAG, e.getMessage(), e);
                return -1;
            }
        }

        @Override
        public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
            populatePackageNameToApexModuleNameIfNeeded();
            String apexModuleName;
            synchronized (mLock) {
                apexModuleName = mPackageNameToApexModuleName.get(apexPackageName);
            }
            if (apexModuleName == null) {
                Slog.e(TAG, "Invalid apex package name: " + apexPackageName);
                return false;
            }
            try {
                mApexService.restoreCeData(userId, rollbackId, apexModuleName);
                return true;
            } catch (Exception e) {
                Slog.e(TAG, e.getMessage(), e);
                return false;
            }
        }

        /**
         * Dump information about the packages contained in a particular cache
         * @param packagesCache the cache to print information about.
@@ -864,6 +918,16 @@ public abstract class ApexManager {
            return null;
        }

        @Override
        public long snapshotCeData(int userId, int rollbackId, String apexPackageName) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
            throw new UnsupportedOperationException();
        }

        @Override
        void dump(PrintWriter pw, String packageName) {
            // No-op
+28 −12
Original line number Diff line number Diff line
@@ -344,12 +344,12 @@ public class StagingManager {
    }

    /**
     * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX.
     * Apks inside apex are not installed using apk-install flow. They are scanned from the system
     * directory directly by PackageManager, as such, RollbackManager need to handle their data
     * separately here.
     */
    private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) {
        // We want to process apks inside apex. So current session needs to contain apex.
    private void snapshotAndRestoreForApexSession(PackageInstallerSession session) {
        if (!sessionContainsApex(session)) {
            return;
        }
@@ -382,19 +382,37 @@ public class StagingManager {
            apexSessions.add(session);
        }

        // For each apex, process the apks inside it
        final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
        final int[] allUsers = um.getUserIds();
        IRollbackManager rm = IRollbackManager.Stub.asInterface(
                ServiceManager.getService(Context.ROLLBACK_SERVICE));

        for (PackageInstallerSession apexSession : apexSessions) {
            List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName());
            String packageName = apexSession.getPackageName();
            // Perform any snapshots or restores for the APEX itself
            snapshotAndRestoreApexUserData(packageName, allUsers, rm);

            // Process the apks inside the APEX
            List<String> apksInApex = mApexManager.getApksInApex(packageName);
            for (String apk: apksInApex) {
                snapshotAndRestoreApkInApexUserData(apk);
                snapshotAndRestoreApkInApexUserData(apk, allUsers, rm);
            }
        }
    }

    private void snapshotAndRestoreApkInApexUserData(String packageName) {
        IRollbackManager rm = IRollbackManager.Stub.asInterface(
                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
    private void snapshotAndRestoreApexUserData(
            String packageName, int[] allUsers, IRollbackManager rm) {
        try {
            // appId, ceDataInode, and seInfo are not needed for APEXes
            rm.snapshotAndRestoreUserData(packageName, allUsers, 0, 0,
                    null, 0 /*token*/);
        } catch (RemoteException re) {
            Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
        }
    }

    private void snapshotAndRestoreApkInApexUserData(
            String packageName, int[] allUsers, IRollbackManager rm) {
        PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
        AndroidPackage pkg = mPmi.getPackage(packageName);
        if (pkg == null) {
@@ -403,13 +421,11 @@ public class StagingManager {
            return;
        }
        final String seInfo = pkg.getSeInfo();
        final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
        final int[] allUsers = um.getUserIds();

        int appId = -1;
        long ceDataInode = -1;
        final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName);
        if (ps != null && rm != null) {
        if (ps != null) {
            appId = ps.appId;
            ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
            // NOTE: We ignore the user specified in the InstallParam because we know this is
@@ -498,7 +514,7 @@ public class StagingManager {
                abortCheckpoint();
                return;
            }
            snapshotAndRestoreApkInApexUserData(session);
            snapshotAndRestoreForApexSession(session);
            Slog.i(TAG, "APEX packages in session " + session.sessionId
                    + " were successfully activated. Proceeding with APK packages, if any");
        }
+83 −52
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.util.SparseLongArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.ApexManager;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;

@@ -42,9 +43,17 @@ public class AppDataRollbackHelper {
    private static final String TAG = "RollbackManager";

    private final Installer mInstaller;
    private final ApexManager mApexManager;

    public AppDataRollbackHelper(Installer installer) {
        mInstaller = installer;
        mApexManager = ApexManager.getInstance();
    }

    @VisibleForTesting
    AppDataRollbackHelper(Installer installer, ApexManager apexManager) {
        mInstaller = installer;
        mApexManager = apexManager;
    }

    /**
@@ -55,7 +64,7 @@ public class AppDataRollbackHelper {
    @GuardedBy("rollback.mLock")
    // TODO(b/136241838): Move into Rollback and synchronize there.
    public void snapshotAppData(
            int snapshotId, PackageRollbackInfo packageRollbackInfo, int[] userIds) {
            int rollbackId, PackageRollbackInfo packageRollbackInfo, int[] userIds) {
        for (int user : userIds) {
            final int storageFlags;
            if (isUserCredentialLocked(user)) {
@@ -68,16 +77,7 @@ public class AppDataRollbackHelper {
                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
            }

            try {
                long ceSnapshotInode = mInstaller.snapshotAppData(
                        packageRollbackInfo.getPackageName(), user, snapshotId, storageFlags);
                if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) {
                    packageRollbackInfo.putCeSnapshotInode(user, ceSnapshotInode);
                }
            } catch (InstallerException ie) {
                Slog.e(TAG, "Unable to create app data snapshot for: "
                        + packageRollbackInfo.getPackageName() + ", userId: " + user, ie);
            }
            doSnapshot(packageRollbackInfo, user, rollbackId, storageFlags);
        }
    }

@@ -119,15 +119,70 @@ public class AppDataRollbackHelper {
            }
        }

        doRestoreOrWipe(packageRollbackInfo, userId, rollbackId, appId, seInfo, storageFlags);

        return changedRollback;
    }

    private boolean doSnapshot(
            PackageRollbackInfo packageRollbackInfo, int userId, int rollbackId, int flags) {
        if (packageRollbackInfo.isApex()) {
            // For APEX, only snapshot CE here
            if ((flags & Installer.FLAG_STORAGE_CE) != 0) {
                long ceSnapshotInode = mApexManager.snapshotCeData(
                        userId, rollbackId, packageRollbackInfo.getPackageName());
                if (ceSnapshotInode > 0) {
                    packageRollbackInfo.putCeSnapshotInode(userId, ceSnapshotInode);
                } else {
                    return false;
                }
            }
        } else {
            // APK
            try {
                long ceSnapshotInode = mInstaller.snapshotAppData(
                        packageRollbackInfo.getPackageName(), userId, rollbackId, flags);
                if ((flags & Installer.FLAG_STORAGE_CE) != 0) {
                    packageRollbackInfo.putCeSnapshotInode(userId, ceSnapshotInode);
                }
            } catch (InstallerException ie) {
                Slog.e(TAG, "Unable to create app data snapshot for: "
                        + packageRollbackInfo.getPackageName() + ", userId: " + userId, ie);
                return false;
            }
        }
        return true;
    }

    private boolean doRestoreOrWipe(PackageRollbackInfo packageRollbackInfo, int userId,
            int rollbackId, int appId, String seInfo, int flags) {
        if (packageRollbackInfo.isApex()) {
            switch (packageRollbackInfo.getRollbackDataPolicy()) {
                case PackageManager.RollbackDataPolicy.WIPE:
                    // TODO: Implement WIPE for apex CE data
                    break;
                case PackageManager.RollbackDataPolicy.RESTORE:
                    // For APEX, only restore of CE may be done here.
                    if ((flags & Installer.FLAG_STORAGE_CE) != 0) {
                        mApexManager.restoreCeData(
                                userId, rollbackId, packageRollbackInfo.getPackageName());
                    }
                    break;
                default:
                    break;
            }
        } else {
            // APK
            try {
                switch (packageRollbackInfo.getRollbackDataPolicy()) {
                    case PackageManager.RollbackDataPolicy.WIPE:
                        mInstaller.clearAppData(null, packageRollbackInfo.getPackageName(),
                            userId, storageFlags, 0);
                                userId, flags, 0);
                        break;
                    case PackageManager.RollbackDataPolicy.RESTORE:
                    mInstaller.restoreAppDataSnapshot(packageRollbackInfo.getPackageName(), appId,
                            seInfo, userId, rollbackId, storageFlags);

                        mInstaller.restoreAppDataSnapshot(packageRollbackInfo.getPackageName(),
                                appId, seInfo, userId, rollbackId, flags);
                        break;
                    default:
                        break;
@@ -136,9 +191,10 @@ public class AppDataRollbackHelper {
                Slog.e(TAG, "Unable to restore/wipe app data: "
                        + packageRollbackInfo.getPackageName() + " policy="
                        + packageRollbackInfo.getRollbackDataPolicy(), ie);
                return false;
            }

        return changedRollback;
        }
        return true;
    }

    /**
@@ -204,40 +260,15 @@ public class AppDataRollbackHelper {

            if (hasPendingBackup) {
                int idx = pendingBackupUsers.indexOf(userId);
                try {
                    long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
                            userId, rollback.info.getRollbackId(),
                            Installer.FLAG_STORAGE_CE);
                    info.putCeSnapshotInode(userId, ceSnapshotInode);
                if (doSnapshot(
                        info, userId, rollback.info.getRollbackId(), Installer.FLAG_STORAGE_CE)) {
                    pendingBackupUsers.remove(idx);
                } catch (InstallerException ie) {
                    Slog.e(TAG,
                            "Unable to create app data snapshot for: "
                                    + info.getPackageName() + ", userId: " + userId, ie);
                }
            }

            if (hasPendingRestore) {
                try {
                    switch (info.getRollbackDataPolicy()) {
                        case PackageManager.RollbackDataPolicy.WIPE:
                            mInstaller.clearAppData(null, info.getPackageName(), userId,
                                    Installer.FLAG_STORAGE_CE, 0);
                            break;
                        case PackageManager.RollbackDataPolicy.RESTORE:
                            mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
                                    ri.seInfo, userId, rollback.info.getRollbackId(),
                                    Installer.FLAG_STORAGE_CE);
                            break;
                        default:
                            break;
                    }
            if (hasPendingRestore && doRestoreOrWipe(info, userId, rollback.info.getRollbackId(),
                    ri.appId, ri.seInfo, Installer.FLAG_STORAGE_CE)) {
                info.removeRestoreInfo(ri);
                } catch (InstallerException ie) {
                    Slog.e(TAG, "Unable to restore/wipe app data for: "
                            + info.getPackageName() + " policy="
                            + info.getRollbackDataPolicy(), ie);
                }
            }
        }
        return foundBackupOrRestore;
+17 −6
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
@@ -34,12 +35,15 @@ import android.content.rollback.PackageRollbackInfo.RestoreInfo;
import android.util.IntArray;
import android.util.SparseLongArray;

import com.android.server.pm.ApexManager;
import com.android.server.pm.Installer;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;

import java.io.File;
@@ -48,10 +52,17 @@ import java.util.ArrayList;
@RunWith(JUnit4.class)
public class AppDataRollbackHelperTest {

    @Mock private ApexManager mApexManager;

    @Before
    public void setUp() {
        initMocks(this);
    }

    @Test
    public void testSnapshotAppData() throws Exception {
        Installer installer = mock(Installer.class);
        AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer));
        AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer, mApexManager));

        // All users are unlocked so we should snapshot data for them.
        doReturn(true).when(helper).isUserCredentialLocked(eq(10));
@@ -114,7 +125,7 @@ public class AppDataRollbackHelperTest {
    @Test
    public void testRestoreAppDataSnapshot_pendingBackupForUser() throws Exception {
        Installer installer = mock(Installer.class);
        AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer));
        AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer, mApexManager));

        PackageRollbackInfo info = createPackageRollbackInfo("com.foo");
        IntArray pendingBackups = info.getPendingBackups();
@@ -139,7 +150,7 @@ public class AppDataRollbackHelperTest {
    @Test
    public void testRestoreAppDataSnapshot_availableBackupForLockedUser() throws Exception {
        Installer installer = mock(Installer.class);
        AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer));
        AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer, mApexManager));
        doReturn(true).when(helper).isUserCredentialLocked(eq(10));

        PackageRollbackInfo info = createPackageRollbackInfo("com.foo");
@@ -163,7 +174,7 @@ public class AppDataRollbackHelperTest {
    @Test
    public void testRestoreAppDataSnapshot_availableBackupForUnlockedUser() throws Exception {
        Installer installer = mock(Installer.class);
        AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer));
        AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer, mApexManager));
        doReturn(false).when(helper).isUserCredentialLocked(eq(10));

        PackageRollbackInfo info = createPackageRollbackInfo("com.foo");
@@ -184,7 +195,7 @@ public class AppDataRollbackHelperTest {
    @Test
    public void destroyAppData() throws Exception {
        Installer installer = mock(Installer.class);
        AppDataRollbackHelper helper = new AppDataRollbackHelper(installer);
        AppDataRollbackHelper helper = new AppDataRollbackHelper(installer, mApexManager);

        PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar");
        info.putCeSnapshotInode(11, 239L);
@@ -206,7 +217,7 @@ public class AppDataRollbackHelperTest {
    @Test
    public void commitPendingBackupAndRestoreForUser() throws Exception {
        Installer installer = mock(Installer.class);
        AppDataRollbackHelper helper = new AppDataRollbackHelper(installer);
        AppDataRollbackHelper helper = new AppDataRollbackHelper(installer, mApexManager);

        when(installer.snapshotAppData(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(53L);

+19 −0
Original line number Diff line number Diff line
@@ -584,6 +584,25 @@ public class StagedRollbackTest {
        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
    }

    @Test
    public void testRollbackApexDataDirectories_Phase1() throws Exception {
        int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback()
                .commit();
        InstallUtils.waitForSessionReady(sessionId);
    }

    @Test
    public void testRollbackApexDataDirectories_Phase2() throws Exception {
        RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);

        RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2);
        RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());

        // Note: The app is not rolled back until after the rollback is staged
        // and the device has been rebooted.
        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
    }

    private static void runShellCommand(String cmd) {
        ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
                .executeShellCommand(cmd);
Loading