From d9b65326b4536df896a7880b1e2df4781dedf689 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Thu, 10 Feb 2022 15:52:51 +0800 Subject: [PATCH] Merge abandon() methods (14/n) Unify the code flow of abandon() since non-staged sessions now also have READY/APPLIED/FAILED states. 1. set mStageDirInUse to true to prevent deleting staging files when verification is in progress. 2. After verification, process pending abandon() request if any and abort the install process. 3. set mStageDirInUse to true again when install phase is about to start. Bug: 210359798 Test: atest CtsRootPackageInstallerHostTestCases \ CtsAtomicInstallTestCases Change-Id: I2b2e417ba09304daad5edee03e5717a414d4c023 --- .../server/pm/PackageInstallerSession.java | 184 ++++++++---------- 1 file changed, 81 insertions(+), 103 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d230004e62134..ced1c7d136aa6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -469,16 +469,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Nullable final StagedSession mStagedSession; + /** + * The callback to run when pre-reboot verification has ended. Used by {@link #abandon()} + * to delay session clean-up until it is safe to do so. + */ + @GuardedBy("mLock") + @Nullable + private Runnable mPendingAbandonCallback; + @VisibleForTesting public class StagedSession implements StagingManager.StagedSession { - /** - * The callback to run when pre-reboot verification has ended. Used by {@link #abandon()} - * to delay session clean-up until it is safe to do so. - */ - @GuardedBy("mLock") - @Nullable - private Runnable mPendingAbandonCallback; - @Override public List getChildSessions() { if (!params.isMultiPackage) { @@ -575,9 +575,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public boolean isInTerminalState() { - synchronized (mLock) { - return mSessionApplied || mSessionFailed; - } + return PackageInstallerSession.this.isInTerminalState(); } @Override @@ -612,48 +610,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void abandon() { - final Runnable r; - synchronized (mLock) { - assertNotChild("StagedSession#abandon"); - assertCallerIsOwnerOrRootOrSystem(); - if (isInTerminalState()) { - // 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. - // Also, in such cases cleanStageDir() has already been executed so no need to - // do it now. - return; - } - mDestroyed = true; - r = () -> { - assertNotLocked("abandonStaged"); - if (mCommitted.get()) { - mStagingManager.abortCommittedSession(this); - } - destroy(); - dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); - maybeFinishChildSessions(INSTALL_FAILED_ABORTED, - "Session was abandoned because the parent session is abandoned"); - }; - if (mStageDirInUse) { - // Pre-reboot verification is ongoing, not safe to clean up the session yet. - mPendingAbandonCallback = r; - mCallback.onSessionChanged(PackageInstallerSession.this); - return; - } - } - r.run(); - } - - /** - * Called when pre-reboot verification has ended. - * Now it is safe to clean up the session if {@link #abandon()} has been called previously. - */ - private void notifyEndPreRebootVerification() { - synchronized (mLock) { - Preconditions.checkState(mStageDirInUse); - mStageDirInUse = false; - } - dispatchPendingAbandonCallback(); + PackageInstallerSession.this.abandon(); } /** @@ -669,17 +626,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { Preconditions.checkArgument(!isInTerminalState()); verify(); } - - private void dispatchPendingAbandonCallback() { - final Runnable callback; - synchronized (mLock) { - callback = mPendingAbandonCallback; - mPendingAbandonCallback = null; - } - if (callback != null) { - callback.run(); - } - } } /** @@ -1138,9 +1084,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private boolean isInTerminalState() { + synchronized (mLock) { + return mSessionApplied || mSessionFailed; + } + } + /** Returns true if a staged session has reached a final state and can be forgotten about */ public boolean isStagedAndInTerminalState() { - return params.isStaged && mStagedSession.isInTerminalState(); + return params.isStaged && isInTerminalState(); } private void assertNotLocked(String cookie) { @@ -1968,9 +1920,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void onSessionVerificationFailure(int error, String msg) { Slog.e(TAG, "Failed to verify session " + sessionId); - if (isStaged()) { - mStagedSession.notifyEndPreRebootVerification(); - } // Dispatch message to remove session from PackageInstallerService. dispatchSessionFinished(error, msg, null); maybeFinishChildSessions(error, msg); @@ -2279,6 +2228,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @GuardedBy("mLock") + private void markStageDirInUseLocked() throws PackageManagerException { + if (mDestroyed) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Session destroyed"); + } + // Set this flag to prevent abandon() from deleting staging files when verification or + // installation is about to start. + mStageDirInUse = true; + } + private void parseApkAndExtractNativeLibraries() throws PackageManagerException { synchronized (mLock) { if (mStageDirInUse) { @@ -2324,19 +2284,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void verifyNonStaged() throws PackageManagerException { synchronized (mLock) { - if (mDestroyed) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, - "Session destroyed"); - } - // Set this flag to prevent abandon() from deleting staging files while verification is - // in progress. For staged sessions, we will reset this flag when verification is done - // so abandon() can take effect. For non-staged sessions, the staging files will be - // deleted when install is completed (no matter success or not). No need to reset - // the flag. - mStageDirInUse = true; + markStageDirInUseLocked(); } mSessionProvider.getSessionVerifier().verify(this, (error, msg) -> { mHandler.post(() -> { + if (dispatchPendingAbandonCallback()) { + // No need to continue if abandoned + return; + } if (error == INSTALL_SUCCEEDED) { onVerificationComplete(); } else { @@ -2426,7 +2381,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @WorkerThread private void onVerificationComplete() { if (isStaged()) { - mStagedSession.notifyEndPreRebootVerification(); mStagingManager.commitSession(mStagedSession); sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null); return; @@ -2444,14 +2398,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private InstallParams makeInstallParams(CompletableFuture future) throws PackageManagerException { synchronized (mLock) { - if (mDestroyed) { - throw new PackageManagerException( - INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); - } if (!mSealed) { throw new PackageManagerException( INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); } + markStageDirInUseLocked(); } if (isMultiPackage()) { @@ -3523,22 +3474,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - private void abandonNonStaged() { - synchronized (mLock) { - assertNotChild("abandonNonStaged"); - assertCallerIsOwnerOrRootOrSystem(); - if (mStageDirInUse) { - if (LOGD) Slog.d(TAG, "Ignoring abandon for staging files are in use"); - return; - } - mDestroyed = true; - } - destroy(); - dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); - maybeFinishChildSessions(INSTALL_FAILED_ABORTED, - "Session was abandoned because the parent session is abandoned"); - } - private void assertNotChild(String cookie) { if (hasParentSessionId()) { throw new IllegalStateException(cookie + " can't be called on a child session, id=" @@ -3546,13 +3481,56 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Called when verification has completed. Now it is safe to clean up the session + * if {@link #abandon()} has been called previously. + * + * @return True if this session has been abandoned. + */ + private boolean dispatchPendingAbandonCallback() { + final Runnable callback; + synchronized (mLock) { + Preconditions.checkState(mStageDirInUse); + mStageDirInUse = false; + callback = mPendingAbandonCallback; + mPendingAbandonCallback = null; + } + if (callback != null) { + callback.run(); + return true; + } + return false; + } + @Override public void abandon() { - if (params.isStaged) { - mStagedSession.abandon(); - } else { - abandonNonStaged(); + final Runnable r; + synchronized (mLock) { + assertNotChild("abandon"); + assertCallerIsOwnerOrRootOrSystem(); + if (isInTerminalState()) { + // Finalized sessions have been properly cleaned up. No need to abandon them. + return; + } + mDestroyed = true; + r = () -> { + assertNotLocked("abandonStaged"); + if (isStaged() && mCommitted.get()) { + mStagingManager.abortCommittedSession(mStagedSession); + } + destroy(); + dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); + maybeFinishChildSessions(INSTALL_FAILED_ABORTED, + "Session was abandoned because the parent session is abandoned"); + }; + if (mStageDirInUse) { + // Verification is ongoing, not safe to clean up the session yet. + mPendingAbandonCallback = r; + mCallback.onSessionChanged(this); + return; + } } + r.run(); } @Override