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

Commit c72b0bba authored by Oli Lan's avatar Oli Lan
Browse files

Add rollback parameters when submitting sessions to apexd.

This adds parameters to tell apexd if a session has rollback enabled,
or if it is a rollback. The rollback id is also included.

Previously in StagingManager, rollback was only enabled after the
session was submitted. This ordering is reversed in this change to
allow the rollback id to be retrieved.

For the 'is a rollback' case, a new InstallReason of INSTALL_REASON_ROLLBACK
is added, and the rollback id can be retrieved from RollbackManager.

This is an alternate approach to ag/9826517.

Bug: 141148175
Test: atest CtsRollbackManagerHostTestCases
Test: atest CtsStagedInstallHostTestCases
Change-Id: I560ee4ed6dd82277892a511909378d5a5a8502ff
parent 0f15f727
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -946,7 +946,8 @@ public abstract class PackageManager {
            INSTALL_REASON_POLICY,
            INSTALL_REASON_DEVICE_RESTORE,
            INSTALL_REASON_DEVICE_SETUP,
            INSTALL_REASON_USER
            INSTALL_REASON_USER,
            INSTALL_REASON_ROLLBACK
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface InstallReason {}
@@ -976,6 +977,13 @@ public abstract class PackageManager {
     */
    public static final int INSTALL_REASON_USER = 4;

    /**
     * Code indicating that the package installation was a rollback initiated by RollbackManager.
     *
     * @hide
     */
    public static final int INSTALL_REASON_ROLLBACK = 5;

    /**
     * @hide
     */
+2 −1
Original line number Diff line number Diff line
@@ -44,9 +44,10 @@ interface IRollbackManager {
    // Used by the staging manager to notify the RollbackManager that a session is
    // being staged. In the case of multi-package sessions, the specified sessionId
    // is that of the parent session.
    // Returns the rollback id if rollback was enabled successfully, or -1 if not.
    //
    // NOTE: This call is synchronous.
    boolean notifyStagedSession(int sessionId);
    int notifyStagedSession(int sessionId);

    // Used by the staging manager to notify the RollbackManager of the apk
    // session for a staged session.
+5 −10
Original line number Diff line number Diff line
@@ -17,11 +17,11 @@
package com.android.server.pm;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
import android.apex.IApexService;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -176,13 +176,9 @@ abstract class ApexManager {
     * enough for it to be activated at the next boot, the caller needs to call
     * {@link #markStagedSessionReady(int)}.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
     * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
     *                        an array of identifiers of all the child sessions. Otherwise it should
     *                        be an empty array.
     * @throws PackageManagerException if call to apexd fails
     */
    abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
    abstract ApexInfoList submitStagedSession(ApexSessionParams params)
            throws PackageManagerException;

    /**
@@ -450,11 +446,10 @@ abstract class ApexManager {
        }

        @Override
        ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
                throws PackageManagerException {
        ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
            try {
                final ApexInfoList apexInfoList = new ApexInfoList();
                mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
                mApexService.submitStagedSession(params, apexInfoList);
                return apexInfoList;
            } catch (RemoteException re) {
                Slog.e(TAG, "Unable to contact apexservice", re);
@@ -686,7 +681,7 @@ abstract class ApexManager {
        }

        @Override
        ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds)
        ApexInfoList submitStagedSession(ApexSessionParams params)
                throws PackageManagerException {
            throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                    "Device doesn't support updating APEX");
+61 −23
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
@@ -36,6 +37,8 @@ import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
import android.content.rollback.IRollbackManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -49,6 +52,7 @@ import android.os.storage.StorageManager;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.apk.ApkSignatureVerifier;

import com.android.internal.annotations.GuardedBy;
@@ -82,6 +86,9 @@ public class StagingManager {
    @GuardedBy("mStagedSessions")
    private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();

    @GuardedBy("mStagedSessions")
    private final SparseIntArray mSessionRollbackIds = new SparseIntArray();

    StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
        mPi = pi;
        mApexManager = am;
@@ -166,18 +173,32 @@ public class StagingManager {

    private List<PackageInfo> submitSessionToApexService(
            @NonNull PackageInstallerSession session) throws PackageManagerException {
        final IntArray childSessionsIds = new IntArray();
        final IntArray childSessionIds = new IntArray();
        if (session.isMultiPackage()) {
            for (int id : session.getChildSessionIds()) {
                if (isApexSession(mStagedSessions.get(id))) {
                    childSessionsIds.add(id);
                    childSessionIds.add(id);
                }
            }
        }
        ApexSessionParams apexSessionParams = new ApexSessionParams();
        apexSessionParams.sessionId = session.sessionId;
        apexSessionParams.childSessionIds = childSessionIds.toArray();
        if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
            apexSessionParams.isRollback = true;
            apexSessionParams.rollbackId = retrieveRollbackIdForCommitSession(session.sessionId);
        } else {
            synchronized (mStagedSessions) {
                int rollbackId = mSessionRollbackIds.get(session.sessionId, -1);
                if (rollbackId != -1) {
                    apexSessionParams.hasRollbackEnabled = true;
                    apexSessionParams.rollbackId = rollbackId;
                }
            }
        }
        // submitStagedSession will throw a PackageManagerException if apexd verification fails,
        // which will be propagated to populate stagedSessionErrorMessage of this session.
        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
                childSessionsIds.toArray());
        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
        final List<PackageInfo> result = new ArrayList<>();
        for (ApexInfo apexInfo : apexInfoList.apexInfos) {
            final PackageInfo packageInfo;
@@ -208,6 +229,19 @@ public class StagingManager {
        return result;
    }

    private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
        RollbackManager rm = mContext.getSystemService(RollbackManager.class);

        List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
        for (RollbackInfo rollback : rollbacks) {
            if (rollback.getCommittedSessionId() == sessionId) {
                return rollback.getRollbackId();
            }
        }
        throw new PackageManagerException(
                "Could not find rollback id for commit session: " + sessionId);
    }

    private void checkRequiredVersionCode(final PackageInstallerSession session,
            final PackageInfo activePackage) throws PackageManagerException {
        if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
@@ -633,6 +667,7 @@ public class StagingManager {
    void abortSession(@NonNull PackageInstallerSession session) {
        synchronized (mStagedSessions) {
            mStagedSessions.remove(session.sessionId);
            mSessionRollbackIds.delete(session.sessionId);
        }
    }

@@ -865,6 +900,28 @@ public class StagingManager {
         */
        private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
            Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);

            if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
                // If rollback is enabled for this session, we call through to the RollbackManager
                // with the list of sessions it must enable rollback for. Note that
                // notifyStagedSession is a synchronous operation.
                final IRollbackManager rm = IRollbackManager.Stub.asInterface(
                        ServiceManager.getService(Context.ROLLBACK_SERVICE));
                try {
                    // NOTE: To stay consistent with the non-staged install flow, we don't fail the
                    // entire install if rollbacks can't be enabled.
                    int rollbackId = rm.notifyStagedSession(session.sessionId);
                    if (rollbackId != -1) {
                        synchronized (mStagedSessions) {
                            mSessionRollbackIds.put(session.sessionId, rollbackId);
                        }
                    }
                } catch (RemoteException re) {
                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
                            + session.sessionId, re);
                }
            }

            notifyPreRebootVerification_Start_Complete(session.sessionId);
        }

@@ -929,25 +986,6 @@ public class StagingManager {
         * </ul></p>
         */
        private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
            if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
                // If rollback is enabled for this session, we call through to the RollbackManager
                // with the list of sessions it must enable rollback for. Note that
                // notifyStagedSession is a synchronous operation.
                final IRollbackManager rm = IRollbackManager.Stub.asInterface(
                        ServiceManager.getService(Context.ROLLBACK_SERVICE));
                try {
                    // NOTE: To stay consistent with the non-staged install flow, we don't fail the
                    // entire install if rollbacks can't be enabled.
                    if (!rm.notifyStagedSession(session.sessionId)) {
                        Slog.e(TAG, "Unable to enable rollback for session: "
                                + session.sessionId);
                    }
                } catch (RemoteException re) {
                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
                            + session.sessionId, re);
                }
            }

            // Proactively mark session as ready before calling apexd. Although this call order
            // looks counter-intuitive, this is the easiest way to ensure that session won't end up
            // in the inconsistent state:
+3 −1
Original line number Diff line number Diff line
@@ -418,6 +418,7 @@ class Rollback {
                if (isStaged()) {
                    parentParams.setStaged();
                }
                parentParams.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);

                int parentSessionId = packageInstaller.createSession(parentParams);
                PackageInstaller.Session parentSession = packageInstaller.openSession(
@@ -484,6 +485,7 @@ class Rollback {
                                synchronized (mLock) {
                                    mState = ROLLBACK_STATE_AVAILABLE;
                                    mRestoreUserDataInProgress = false;
                                    info.setCommittedSessionId(-1);
                                }
                                sendFailure(context, statusReceiver,
                                        RollbackManager.STATUS_FAILURE_INSTALL,
@@ -500,7 +502,6 @@ class Rollback {
                                    mRestoreUserDataInProgress = false;
                                }

                                info.setCommittedSessionId(parentSessionId);
                                info.getCausePackages().addAll(causePackages);
                                RollbackStore.deletePackageCodePaths(this);
                                RollbackStore.saveRollback(this);
@@ -528,6 +529,7 @@ class Rollback {
                );

                mState = ROLLBACK_STATE_COMMITTED;
                info.setCommittedSessionId(parentSessionId);
                mRestoreUserDataInProgress = true;
                parentSession.commit(receiver.getIntentSender());
            } catch (IOException e) {
Loading