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

Commit 7bbd8b47 authored by Oli Lan's avatar Oli Lan
Browse files

Snapshot and restore CE apex data directories.

This change extends the existing mechanisms for snapshotting and
restoring CE data for APKs to also support CE apex data.

StagingManager now calls through to RollbackManager to perform the
snapshot/restore (at the same time as it does for APK-in-APEX,
added recently).

For APEXes, the actual snapshot or restore is performed by apexd instead
of installd. Tracking of pending backups and restores is performed by
RollbackManager in the same way as for APKs.

Bug: 141148175
Test: Manual install/rollback of an APEX with dummy files in CE data directory.
Check that snapshot and restore occur as expected.
Test: atest CtsStagedInstallHostTestCases
Change-Id: I82e1818f927881ea774148d97ca1981a447b598d
parent e428a2cc
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
@@ -342,12 +342,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;
        }
@@ -380,19 +380,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) {
@@ -401,13 +419,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
@@ -496,7 +512,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);