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

Commit 143ebe51 authored by JW Wang's avatar JW Wang Committed by Android (Google) Code Review
Browse files

Merge changes I301ab7d8,I66eda02e,I0d7cbe83,Ic37b58e6

* changes:
  Ensure no state changes after mDestroyed=true (4/n)
  Remove unused code (3/n)
  Merge streamValidateAndCommit() and streamAndValidateLocked() (2/n)
  Rewrite how we abandon stages sessions (1/n)
parents 0928724a 6acd2bda
Loading
Loading
Loading
Loading
+134 −82
Original line number Diff line number Diff line
@@ -249,6 +249,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private final PackageManagerService mPm;
    private final Handler mHandler;
    private final PackageSessionProvider mSessionProvider;
    /**
     * Note all calls must be done outside {@link #mLock} to prevent lock inversion.
     */
    private final StagingManager mStagingManager;

    final int sessionId;
@@ -388,6 +391,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @GuardedBy("mLock")
    private String mStagedSessionErrorMessage;

    /**
     * The callback to run when pre-reboot verification has ended. Used by {@link #abandonStaged()}
     * to delay session clean-up until it is safe to do so.
     */
    @GuardedBy("mLock")
    @Nullable
    private Runnable mPendingAbandonCallback;
    /**
     * {@code true} if pre-reboot verification is ongoing which means it is not safe for
     * {@link #abandon()} to clean up staging directories.
     */
    @GuardedBy("mLock")
    private boolean mInPreRebootVerification;

    /**
     * Path to the validated base APK for this session, which may point at an
     * APK inside the session (when the session defines the base), or it may
@@ -1454,15 +1471,36 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        // TODO(patb): since the work done here for a parent session in a multi-package install is
        //             mostly superficial, consider splitting this method for the parent and
        //             single / child sessions.
        try {
            synchronized (mLock) {
                if (mCommitted) {
                    return true;
                }

            if (!streamAndValidateLocked()) {
                // Read transfers from the original owner stay open, but as the session's data
                // cannot be modified anymore, there is no leak of information. For staged sessions,
                // further validation is performed by the staging manager.
                if (!params.isMultiPackage) {
                    if (!prepareDataLoaderLocked()) {
                        return false;
                    }

                    if (isApexInstallation()) {
                        validateApexInstallLocked();
                    } else {
                        validateApkInstallLocked();
                    }
                }
            }

            if (params.isStaged) {
                mStagingManager.checkNonOverlappingWithStagedSessions(this);
            }

            synchronized (mLock) {
                if (mDestroyed) {
                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                            "Session destroyed");
                }
                // Client staging is fully done at this point
                mClientProgress = 1f;
                computeProgressLocked(true);
@@ -1474,6 +1512,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                mCommitted = true;
            }
            return true;
        } catch (PackageManagerException e) {
            throw onSessionValidationFailure(e);
        } catch (Throwable e) {
            // Convert all exceptions into package manager exceptions as only those are handled
            // in the code above.
            throw onSessionValidationFailure(new PackageManagerException(e));
        }
    }

    @GuardedBy("mLock")
@@ -1511,44 +1556,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    /**
     * Prepare DataLoader and stream content for DataLoader sessions.
     * Validate the contents of all session.
     *
     * @return false if the data loader could not be prepared.
     * @throws PackageManagerException when an unrecoverable exception is encountered
     */
    @GuardedBy("mLock")
    private boolean streamAndValidateLocked() throws PackageManagerException {
        try {
            // Read transfers from the original owner stay open, but as the session's data cannot
            // be modified anymore, there is no leak of information. For staged sessions, further
            // validation is performed by the staging manager.
            if (!params.isMultiPackage) {
                if (!prepareDataLoaderLocked()) {
                    return false;
                }

                if (isApexInstallation()) {
                    validateApexInstallLocked();
                } else {
                    validateApkInstallLocked();
                }
            }

            if (params.isStaged) {
                mStagingManager.checkNonOverlappingWithStagedSessions(this);
            }
            return true;
        } catch (PackageManagerException e) {
            throw onSessionValidationFailure(e);
        } catch (Throwable e) {
            // Convert all exceptions into package manager exceptions as only those are handled
            // in the code above.
            throw onSessionValidationFailure(new PackageManagerException(e));
        }
    }

    private PackageManagerException onSessionValidationFailure(PackageManagerException e) {
        onSessionValidationFailure(e.error, ExceptionUtils.getCompleteMessage(e));
        return e;
@@ -1571,7 +1578,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode);
            // TODO(b/136257624): Remove this once all verification logic has been transferred out
            //  of StagingManager.
            mStagingManager.notifyVerificationComplete(sessionId);
            mStagingManager.notifyVerificationComplete(this);
        } else {
            // Dispatch message to remove session from PackageInstallerService.
            dispatchSessionFinished(error, msg, null);
@@ -1837,21 +1844,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            throws PackageManagerException {
        assertNotLocked("makeSessionActive");

        synchronized (mLock) {
            if (mRelinquished) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session relinquished");
            }
            if (mDestroyed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session destroyed");
            }
            if (!mSealed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session not sealed");
            }
        }

        // TODO(b/159331446): Move this to makeSessionActiveForInstall and update javadoc
        if (!params.isMultiPackage && needToAskForPermissions()) {
            // User needs to confirm installation;
@@ -1881,6 +1873,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @GuardedBy("mLock")
    private PackageManagerService.VerificationParams makeVerificationParamsLocked()
            throws PackageManagerException {
        if (mRelinquished) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session relinquished");
        }
        if (mDestroyed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session destroyed");
        }
        if (!mSealed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session not sealed");
        }

        // TODO(b/136257624): Some logic in this if block probably belongs in
        //  makeInstallParams().
        if (!params.isMultiPackage && !isApexInstallation()) {
@@ -2787,14 +2792,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    }

    private void abandonStaged() {
        final Runnable r;
        synchronized (mLock) {
            if (mDestroyed) {
                // If a user abandons staged session in an unsafe state, then system will try to
                // abandon the destroyed staged session when it is safe on behalf of the user.
                assertCallerIsOwnerOrRootOrSystemLocked();
            } else {
            assertCallerIsOwnerOrRootLocked();
            }
            if (isStagedAndInTerminalState()) {
                // We keep the session in the database if it's in a finalized state. It will be
                // removed by PackageInstallerService when the last update time is old enough.
@@ -2803,17 +2803,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                return;
            }
            mDestroyed = true;
            if (mCommitted) {
                if (!mStagingManager.abortCommittedSessionLocked(this)) {
                    // Do not clean up the staged session from system. It is not safe yet.
            boolean isCommitted = mCommitted;
            List<PackageInstallerSession> childSessions = getChildSessionsLocked();
            r = () -> {
                assertNotLocked("abandonStaged");
                if (isCommitted) {
                    mStagingManager.abortCommittedSession(this);
                }
                cleanStageDir(childSessions);
                destroyInternal();
                dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
            };
            if (mInPreRebootVerification) {
                // Pre-reboot verification is ongoing. It is not safe to clean up the session yet.
                mPendingAbandonCallback = r;
                mCallback.onStagedSessionChanged(this);
                return;
            }
        }
            cleanStageDir(getChildSessionsLocked());
            destroyInternal();
        }
        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
        r.run();
    }

    @Override
@@ -2830,6 +2838,50 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    /**
     * Notified by the staging manager that pre-reboot verification is about to start. The return
     * value should be checked to decide whether it is OK to start pre-reboot verification. In
     * the case of a destroyed session, {@code false} is returned and there is no need to start
     * pre-reboot verification.
     */
    boolean notifyStagedStartPreRebootVerification() {
        synchronized (mLock) {
            if (mInPreRebootVerification) {
                throw new IllegalStateException("Pre-reboot verification has started");
            }
            if (mDestroyed) {
                return false;
            }
            mInPreRebootVerification = true;
            return true;
        }
    }

    private void dispatchPendingAbandonCallback() {
        final Runnable callback;
        synchronized (mLock) {
            callback = mPendingAbandonCallback;
            mPendingAbandonCallback = null;
        }
        if (callback != null) {
            callback.run();
        }
    }

    /**
     * Notified by the staging manager that pre-reboot verification has ended. Now it is safe to
     * clean up the session if {@link #abandon()} has been called previously.
     */
    void notifyStagedEndPreRebootVerification() {
        synchronized (mLock) {
            if (!mInPreRebootVerification) {
                throw new IllegalStateException("Pre-reboot verification not started");
            }
            mInPreRebootVerification = false;
        }
        dispatchPendingAbandonCallback();
    }

    @Override
    public boolean isMultiPackage() {
        return params.isMultiPackage;
+16 −57
Original line number Diff line number Diff line
@@ -60,7 +60,6 @@ import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.apk.ApkSignatureVerifier;

@@ -1028,22 +1027,12 @@ public class StagingManager {

    /**
     * <p>Abort committed staged session
     *
     * <p>This method must be called while holding {@link PackageInstallerSession#mLock}.
     *
     * <p>The method returns {@code false} to indicate it is not safe to clean up the session from
     * system yet. When it is safe, the method returns {@code true}.
     *
     * <p> When it is safe to clean up, {@link StagingManager} will call
     * {@link PackageInstallerSession#abandon()} on the session again.
     *
     * @return {@code true} if it is safe to cleanup the session resources, otherwise {@code false}.
     */
    boolean abortCommittedSessionLocked(@NonNull PackageInstallerSession session) {
    void abortCommittedSession(@NonNull PackageInstallerSession session) {
        int sessionId = session.sessionId;
        if (session.isStagedSessionApplied()) {
            Slog.w(TAG, "Cannot abort applied session : " + sessionId);
            return false;
        if (session.isStagedAndInTerminalState()) {
            Slog.w(TAG, "Cannot abort session in final state: " + sessionId);
            return;
        }
        if (!session.isDestroyed()) {
            throw new IllegalStateException("Committed session must be destroyed before aborting it"
@@ -1051,15 +1040,7 @@ public class StagingManager {
        }
        if (getStagedSession(sessionId) == null) {
            Slog.w(TAG, "Session " + sessionId + " has been abandoned already");
            return false;
        }

        // If pre-reboot verification is running, then return false. StagingManager will call
        // abandon again when pre-reboot verification ends.
        if (mPreRebootVerificationHandler.isVerificationRunning(sessionId)) {
            Slog.w(TAG, "Session " + sessionId + " aborted before pre-reboot "
                    + "verification completed.");
            return false;
            return;
        }

        // A session could be marked ready once its pre-reboot verification ends
@@ -1075,7 +1056,6 @@ public class StagingManager {
        // Session was successfully aborted from apexd (if required) and pre-reboot verification
        // is also complete. It is now safe to clean up the session from system.
        abortSession(session);
        return true;
    }

    /**
@@ -1264,8 +1244,8 @@ public class StagingManager {
    // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
    //  verification logic is extracted out of StagingManager into PMS, we can remove
    //  this.
    void notifyVerificationComplete(int sessionId) {
        mPreRebootVerificationHandler.onPreRebootVerificationComplete(sessionId);
    void notifyVerificationComplete(PackageInstallerSession session) {
        mPreRebootVerificationHandler.onPreRebootVerificationComplete(session);
    }

    // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
@@ -1279,8 +1259,6 @@ public class StagingManager {
        // Hold session ids before handler gets ready to do the verification.
        private IntArray mPendingSessionIds;
        private boolean mIsReady;
        @GuardedBy("mVerificationRunning")
        private final SparseBooleanArray mVerificationRunning = new SparseBooleanArray();

        PreRebootVerificationHandler(Looper looper) {
            super(looper);
@@ -1316,7 +1294,7 @@ public class StagingManager {
            }
            if (session.isDestroyed() || session.isStagedSessionFailed()) {
                // No point in running verification on a destroyed/failed session
                onPreRebootVerificationComplete(sessionId);
                onPreRebootVerificationComplete(session);
                return;
            }
            switch (msg.what) {
@@ -1357,16 +1335,11 @@ public class StagingManager {
            }

            PackageInstallerSession session = getStagedSession(sessionId);
            synchronized (mVerificationRunning) {
                // Do not start verification on a session that has been abandoned
                if (session == null || session.isDestroyed()) {
                    return;
                }
            if (session != null && session.notifyStagedStartPreRebootVerification()) {
                Slog.d(TAG, "Starting preRebootVerification for session " + sessionId);
                mVerificationRunning.put(sessionId, true);
            }
                obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
            }
        }

        private void onPreRebootVerificationFailure(PackageInstallerSession session,
                @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) {
@@ -1376,28 +1349,14 @@ public class StagingManager {
                // failed on next step and staging directory for session will be deleted.
            }
            session.setStagedSessionFailed(errorCode, errorMessage);
            onPreRebootVerificationComplete(session.sessionId);
            onPreRebootVerificationComplete(session);
        }

        // Things to do when pre-reboot verification completes for a particular sessionId
        private void onPreRebootVerificationComplete(int sessionId) {
            // Remove it from mVerificationRunning so that verification is considered complete
            synchronized (mVerificationRunning) {
        private void onPreRebootVerificationComplete(PackageInstallerSession session) {
            int sessionId = session.sessionId;
            Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId);
                mVerificationRunning.delete(sessionId);
            }
            // Check if the session was destroyed while pre-reboot verification was running. If so,
            // abandon it again.
            PackageInstallerSession session = getStagedSession(sessionId);
            if (session != null && session.isDestroyed()) {
                session.abandon();
            }
        }

        private boolean isVerificationRunning(int sessionId) {
            synchronized (mVerificationRunning) {
                return mVerificationRunning.get(sessionId);
            }
            session.notifyStagedEndPreRebootVerification();
        }

        private void notifyPreRebootVerification_Start_Complete(int sessionId) {
@@ -1516,7 +1475,7 @@ public class StagingManager {
            // or activate its apex, there won't be any files to work with as they will be cleaned
            // up by the system as part of abandonment. If session is abandoned before this point,
            // then the session is already destroyed and cannot be marked ready anymore.
            onPreRebootVerificationComplete(session.sessionId);
            onPreRebootVerificationComplete(session);

            // 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