Merge changes from topic "abandon-race" into rvc-dev am: 6b58f8ca37
Change-Id: If8b8b5802f903ed69ba68539fc0c78471c750f43
This commit is contained in:
committed by
Automerger Merge Worker
commit
9b3af1f7e5
@@ -1270,7 +1270,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
||||
public void onStagedSessionChanged(PackageInstallerSession session) {
|
||||
session.markUpdated();
|
||||
writeSessionsAsync();
|
||||
if (mOkToSendBroadcasts) {
|
||||
if (mOkToSendBroadcasts && !session.isDestroyed()) {
|
||||
// we don't scrub the data here as this is sent only to the installer several
|
||||
// privileged system packages
|
||||
mPm.sendSessionUpdatedBroadcast(
|
||||
|
||||
@@ -1076,6 +1076,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the caller is the owner of this session. Otherwise throw a
|
||||
* {@link SecurityException}.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private void assertCallerIsOwnerOrRootOrSystemLocked() {
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid
|
||||
&& callingUid != Process.SYSTEM_UID) {
|
||||
throw new SecurityException("Session does not belong to uid " + callingUid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If anybody is reading or writing data of the session, throw an {@link SecurityException}.
|
||||
*/
|
||||
@@ -2561,7 +2574,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
+ mParentSessionId + " and may not be abandoned directly.");
|
||||
}
|
||||
synchronized (mLock) {
|
||||
assertCallerIsOwnerOrRootLocked();
|
||||
if (params.isStaged && 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
|
||||
@@ -2571,11 +2590,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
return;
|
||||
}
|
||||
if (mCommitted && params.isStaged) {
|
||||
synchronized (mLock) {
|
||||
mDestroyed = true;
|
||||
mDestroyed = true;
|
||||
if (!mStagingManager.abortCommittedSessionLocked(this)) {
|
||||
// Do not clean up the staged session from system. It is not safe yet.
|
||||
mCallback.onStagedSessionChanged(this);
|
||||
return;
|
||||
}
|
||||
mStagingManager.abortCommittedSession(this);
|
||||
|
||||
cleanStageDir();
|
||||
}
|
||||
|
||||
@@ -2935,6 +2955,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
/** {@hide} */
|
||||
void setStagedSessionReady() {
|
||||
synchronized (mLock) {
|
||||
if (mDestroyed) return; // Do not allow destroyed staged session to change state
|
||||
mStagedSessionReady = true;
|
||||
mStagedSessionApplied = false;
|
||||
mStagedSessionFailed = false;
|
||||
@@ -2948,6 +2969,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
void setStagedSessionFailed(@StagedSessionErrorCode int errorCode,
|
||||
String errorMessage) {
|
||||
synchronized (mLock) {
|
||||
if (mDestroyed) return; // Do not allow destroyed staged session to change state
|
||||
mStagedSessionReady = false;
|
||||
mStagedSessionApplied = false;
|
||||
mStagedSessionFailed = true;
|
||||
@@ -2962,6 +2984,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
/** {@hide} */
|
||||
void setStagedSessionApplied() {
|
||||
synchronized (mLock) {
|
||||
if (mDestroyed) return; // Do not allow destroyed staged session to change state
|
||||
mStagedSessionReady = false;
|
||||
mStagedSessionApplied = true;
|
||||
mStagedSessionFailed = false;
|
||||
|
||||
@@ -61,6 +61,7 @@ import android.text.TextUtils;
|
||||
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;
|
||||
|
||||
@@ -205,7 +206,7 @@ public class StagingManager {
|
||||
final IntArray childSessionIds = new IntArray();
|
||||
if (session.isMultiPackage()) {
|
||||
for (int id : session.getChildSessionIds()) {
|
||||
if (isApexSession(mStagedSessions.get(id))) {
|
||||
if (isApexSession(getStagedSession(id))) {
|
||||
childSessionIds.add(id);
|
||||
}
|
||||
}
|
||||
@@ -800,6 +801,8 @@ public class StagingManager {
|
||||
+ session.sessionId + " [" + errorMessage + "]");
|
||||
session.setStagedSessionFailed(
|
||||
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
|
||||
mPreRebootVerificationHandler.onPreRebootVerificationComplete(
|
||||
session.sessionId);
|
||||
return;
|
||||
}
|
||||
mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
|
||||
@@ -883,7 +886,8 @@ public class StagingManager {
|
||||
synchronized (mStagedSessions) {
|
||||
for (int i = 0; i < mStagedSessions.size(); i++) {
|
||||
final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
|
||||
if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) {
|
||||
if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()
|
||||
|| stagedSession.isDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
if (stagedSession.isMultiPackage()) {
|
||||
@@ -946,27 +950,68 @@ public class StagingManager {
|
||||
}
|
||||
}
|
||||
|
||||
void abortCommittedSession(@NonNull PackageInstallerSession session) {
|
||||
/**
|
||||
* <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) {
|
||||
int sessionId = session.sessionId;
|
||||
if (session.isStagedSessionApplied()) {
|
||||
Slog.w(TAG, "Cannot abort applied session : " + session.sessionId);
|
||||
return;
|
||||
Slog.w(TAG, "Cannot abort applied session : " + sessionId);
|
||||
return false;
|
||||
}
|
||||
if (!session.isDestroyed()) {
|
||||
throw new IllegalStateException("Committed session must be destroyed before aborting it"
|
||||
+ " from StagingManager");
|
||||
}
|
||||
if (getStagedSession(sessionId) == null) {
|
||||
Slog.w(TAG, "Session " + sessionId + " has been abandoned already");
|
||||
return false;
|
||||
}
|
||||
abortSession(session);
|
||||
|
||||
boolean hasApex = sessionContainsApex(session);
|
||||
if (hasApex) {
|
||||
ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
|
||||
if (apexSession == null || isApexSessionFinalized(apexSession)) {
|
||||
Slog.w(TAG,
|
||||
"Cannot abort session " + session.sessionId
|
||||
+ " because it is not active or APEXD is not reachable");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mApexManager.abortStagedSession(session.sessionId);
|
||||
} catch (Exception ignore) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// A session could be marked ready once its pre-reboot verification ends
|
||||
if (session.isStagedSessionReady()) {
|
||||
if (sessionContainsApex(session)) {
|
||||
try {
|
||||
ApexSessionInfo apexSession =
|
||||
mApexManager.getStagedSessionInfo(session.sessionId);
|
||||
if (apexSession == null || isApexSessionFinalized(apexSession)) {
|
||||
Slog.w(TAG,
|
||||
"Cannot abort session " + session.sessionId
|
||||
+ " because it is not active.");
|
||||
} else {
|
||||
mApexManager.abortStagedSession(session.sessionId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Failed to contact apexd service. The apex might still be staged. We can still
|
||||
// safely cleanup the staged session since pre-reboot verification is complete.
|
||||
// Also, cleaning up the stageDir prevents the apex from being activated.
|
||||
Slog.w(TAG, "Could not contact apexd to abort staged session " + sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
private boolean isApexSessionFinalized(ApexSessionInfo session) {
|
||||
@@ -1045,6 +1090,11 @@ public class StagingManager {
|
||||
// Final states, nothing to do.
|
||||
return;
|
||||
}
|
||||
if (session.isDestroyed()) {
|
||||
// Device rebooted before abandoned session was cleaned up.
|
||||
session.abandon();
|
||||
return;
|
||||
}
|
||||
if (!session.isStagedSessionReady()) {
|
||||
// The framework got restarted before the pre-reboot verification could complete,
|
||||
// restart the verification.
|
||||
@@ -1127,10 +1177,20 @@ public class StagingManager {
|
||||
}
|
||||
}
|
||||
|
||||
private PackageInstallerSession getStagedSession(int sessionId) {
|
||||
PackageInstallerSession session;
|
||||
synchronized (mStagedSessions) {
|
||||
session = mStagedSessions.get(sessionId);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
private final class PreRebootVerificationHandler extends Handler {
|
||||
// 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);
|
||||
@@ -1158,13 +1218,15 @@ public class StagingManager {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
final int sessionId = msg.arg1;
|
||||
final PackageInstallerSession session;
|
||||
synchronized (mStagedSessions) {
|
||||
session = mStagedSessions.get(sessionId);
|
||||
}
|
||||
// Maybe session was aborted before pre-reboot verification was complete
|
||||
final PackageInstallerSession session = getStagedSession(sessionId);
|
||||
if (session == null) {
|
||||
Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
|
||||
Slog.wtf(TAG, "Session disappeared in the middle of pre-reboot verification: "
|
||||
+ sessionId);
|
||||
return;
|
||||
}
|
||||
if (session.isDestroyed()) {
|
||||
// No point in running verification on a destroyed session
|
||||
onPreRebootVerificationComplete(sessionId);
|
||||
return;
|
||||
}
|
||||
switch (msg.what) {
|
||||
@@ -1203,9 +1265,40 @@ public class StagingManager {
|
||||
mPendingSessionIds.add(sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
PackageInstallerSession session = getStagedSession(sessionId);
|
||||
synchronized (mVerificationRunning) {
|
||||
// Do not start verification on a session that has been abandoned
|
||||
if (session == null || session.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
Slog.d(TAG, "Starting preRebootVerification for session " + sessionId);
|
||||
mVerificationRunning.put(sessionId, true);
|
||||
}
|
||||
obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyPreRebootVerification_Start_Complete(int sessionId) {
|
||||
obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
|
||||
}
|
||||
@@ -1224,8 +1317,6 @@ public class StagingManager {
|
||||
* See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
|
||||
*/
|
||||
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
|
||||
@@ -1272,6 +1363,7 @@ public class StagingManager {
|
||||
}
|
||||
} catch (PackageManagerException e) {
|
||||
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||
onPreRebootVerificationComplete(session.sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1304,6 +1396,7 @@ public class StagingManager {
|
||||
// TODO(b/118865310): abort the session on apexd.
|
||||
} catch (PackageManagerException e) {
|
||||
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||
onPreRebootVerificationComplete(session.sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1326,9 +1419,18 @@ public class StagingManager {
|
||||
Slog.e(TAG, "Failed to get hold of StorageManager", e);
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
|
||||
"Failed to get hold of StorageManager");
|
||||
onPreRebootVerificationComplete(session.sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop pre-reboot verification before marking session ready. From this point on, if we
|
||||
// abandon the session then it will be cleaned up immediately. If session is abandoned
|
||||
// after this point, then even if for some reason system tries to install the session
|
||||
// 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);
|
||||
|
||||
// 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:
|
||||
@@ -1340,15 +1442,16 @@ public class StagingManager {
|
||||
// only apex part of the train will be applied, leaving device in an inconsistent state.
|
||||
Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
|
||||
session.setStagedSessionReady();
|
||||
final boolean hasApex = sessionContainsApex(session);
|
||||
if (!hasApex) {
|
||||
// Session doesn't contain apex, nothing to do.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mApexManager.markStagedSessionReady(session.sessionId);
|
||||
} catch (PackageManagerException e) {
|
||||
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||
if (session.isStagedSessionReady()) {
|
||||
final boolean hasApex = sessionContainsApex(session);
|
||||
if (hasApex) {
|
||||
try {
|
||||
mApexManager.markStagedSessionReady(session.sessionId);
|
||||
} catch (PackageManagerException e) {
|
||||
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user