1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import android.annotation.NonNull; 20 import android.apex.ApexInfo; 21 import android.apex.ApexSessionInfo; 22 import android.apex.ApexSessionParams; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.ApexStagedEvent; 28 import android.content.pm.IStagedApexObserver; 29 import android.content.pm.PackageInstaller; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackageManagerInternal; 32 import android.content.pm.StagedApexInfo; 33 import android.os.IBinder; 34 import android.os.PowerManager; 35 import android.os.RemoteException; 36 import android.os.SystemProperties; 37 import android.os.Trace; 38 import android.os.UserHandle; 39 import android.text.TextUtils; 40 import android.util.ArraySet; 41 import android.util.IntArray; 42 import android.util.Slog; 43 import android.util.SparseArray; 44 import android.util.TimingsTraceLog; 45 46 import com.android.internal.annotations.GuardedBy; 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.content.InstallLocationUtils; 49 import com.android.internal.os.BackgroundThread; 50 import com.android.internal.util.Preconditions; 51 import com.android.server.LocalServices; 52 import com.android.server.SystemService; 53 import com.android.server.SystemServiceManager; 54 import com.android.server.pm.pkg.AndroidPackage; 55 import com.android.server.pm.pkg.PackageStateInternal; 56 import com.android.server.pm.pkg.PackageStateUtils; 57 import com.android.server.rollback.ApexdRevertLogger; 58 import com.android.server.rollback.RollbackManagerInternal; 59 60 import java.io.BufferedReader; 61 import java.io.BufferedWriter; 62 import java.io.File; 63 import java.io.FileReader; 64 import java.io.FileWriter; 65 import java.util.ArrayList; 66 import java.util.Arrays; 67 import java.util.Collections; 68 import java.util.List; 69 import java.util.Set; 70 import java.util.concurrent.CompletableFuture; 71 import java.util.concurrent.ExecutionException; 72 import java.util.function.Predicate; 73 74 /** 75 * This class handles staged install sessions, i.e. install sessions that require packages to 76 * be installed only after a reboot. 77 */ 78 public class StagingManager { 79 80 private static final String TAG = "StagingManager"; 81 82 private final ApexManager mApexManager; 83 private final PowerManager mPowerManager; 84 private final Context mContext; 85 86 private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt"); 87 private String mFailureReason; 88 89 @GuardedBy("mStagedSessions") 90 private final SparseArray<StagedSession> mStagedSessions = new SparseArray<>(); 91 92 @GuardedBy("mFailedPackageNames") 93 private final List<String> mFailedPackageNames = new ArrayList<>(); 94 private String mNativeFailureReason; 95 96 @GuardedBy("mSuccessfulStagedSessionIds") 97 private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>(); 98 99 @GuardedBy("mStagedApexObservers") 100 private final List<IStagedApexObserver> mStagedApexObservers = new ArrayList<>(); 101 102 private final CompletableFuture<Void> mBootCompleted = new CompletableFuture<>(); 103 104 interface StagedSession { isMultiPackage()105 boolean isMultiPackage(); isApexSession()106 boolean isApexSession(); isCommitted()107 boolean isCommitted(); isInTerminalState()108 boolean isInTerminalState(); isDestroyed()109 boolean isDestroyed(); isSessionReady()110 boolean isSessionReady(); isSessionApplied()111 boolean isSessionApplied(); isSessionFailed()112 boolean isSessionFailed(); getChildSessions()113 List<StagedSession> getChildSessions(); getPackageName()114 String getPackageName(); getParentSessionId()115 int getParentSessionId(); sessionId()116 int sessionId(); sessionParams()117 PackageInstaller.SessionParams sessionParams(); sessionContains(Predicate<StagedSession> filter)118 boolean sessionContains(Predicate<StagedSession> filter); containsApkSession()119 boolean containsApkSession(); containsApexSession()120 boolean containsApexSession(); setSessionReady()121 void setSessionReady(); setSessionFailed(int errorCode, String errorMessage)122 void setSessionFailed(int errorCode, String errorMessage); setSessionApplied()123 void setSessionApplied(); installSession()124 CompletableFuture<Void> installSession(); hasParentSessionId()125 boolean hasParentSessionId(); getCommittedMillis()126 long getCommittedMillis(); abandon()127 void abandon(); verifySession()128 void verifySession(); 129 } 130 StagingManager(Context context)131 StagingManager(Context context) { 132 this(context, ApexManager.getInstance()); 133 } 134 135 @VisibleForTesting StagingManager(Context context, ApexManager apexManager)136 StagingManager(Context context, ApexManager apexManager) { 137 mContext = context; 138 139 mApexManager = apexManager; 140 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 141 142 if (mFailureReasonFile.exists()) { 143 try (BufferedReader reader = new BufferedReader(new FileReader(mFailureReasonFile))) { 144 mFailureReason = reader.readLine(); 145 } catch (Exception ignore) { } 146 } 147 } 148 149 /** 150 This class manages lifecycle events for StagingManager. 151 */ 152 public static final class Lifecycle extends SystemService { 153 private static StagingManager sStagingManager; 154 Lifecycle(Context context)155 public Lifecycle(Context context) { 156 super(context); 157 } 158 startService(StagingManager stagingManager)159 void startService(StagingManager stagingManager) { 160 sStagingManager = stagingManager; 161 LocalServices.getService(SystemServiceManager.class).startService(this); 162 } 163 164 @Override onStart()165 public void onStart() { 166 // no-op 167 } 168 169 @Override onBootPhase(int phase)170 public void onBootPhase(int phase) { 171 if (phase == SystemService.PHASE_BOOT_COMPLETED && sStagingManager != null) { 172 sStagingManager.markStagedSessionsAsSuccessful(); 173 sStagingManager.markBootCompleted(); 174 } 175 } 176 } 177 markBootCompleted()178 private void markBootCompleted() { 179 mApexManager.markBootCompleted(); 180 } 181 registerStagedApexObserver(IStagedApexObserver observer)182 void registerStagedApexObserver(IStagedApexObserver observer) { 183 if (observer == null) { 184 return; 185 } 186 if (observer.asBinder() != null) { 187 try { 188 observer.asBinder().linkToDeath(new IBinder.DeathRecipient() { 189 @Override 190 public void binderDied() { 191 synchronized (mStagedApexObservers) { 192 mStagedApexObservers.remove(observer); 193 } 194 } 195 }, 0); 196 } catch (RemoteException re) { 197 Slog.w(TAG, re.getMessage()); 198 } 199 } 200 synchronized (mStagedApexObservers) { 201 mStagedApexObservers.add(observer); 202 } 203 } 204 unregisterStagedApexObserver(IStagedApexObserver observer)205 void unregisterStagedApexObserver(IStagedApexObserver observer) { 206 synchronized (mStagedApexObservers) { 207 mStagedApexObservers.remove(observer); 208 } 209 } 210 211 // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device. abortCheckpoint(String failureReason, boolean supportsCheckpoint, boolean needsCheckpoint)212 private void abortCheckpoint(String failureReason, boolean supportsCheckpoint, 213 boolean needsCheckpoint) { 214 Slog.e(TAG, failureReason); 215 try { 216 if (supportsCheckpoint && needsCheckpoint) { 217 // Store failure reason for next reboot 218 try (BufferedWriter writer = 219 new BufferedWriter(new FileWriter(mFailureReasonFile))) { 220 writer.write(failureReason); 221 } catch (Exception e) { 222 Slog.w(TAG, "Failed to save failure reason: ", e); 223 } 224 225 // Only revert apex sessions if device supports updating apex 226 if (mApexManager.isApexSupported()) { 227 mApexManager.revertActiveSessions(); 228 } 229 230 InstallLocationUtils.getStorageManager().abortChanges( 231 "abort-staged-install", false /*retry*/); 232 } 233 } catch (Exception e) { 234 Slog.wtf(TAG, "Failed to abort checkpoint", e); 235 // Only revert apex sessions if device supports updating apex 236 if (mApexManager.isApexSupported()) { 237 mApexManager.revertActiveSessions(); 238 } 239 mPowerManager.reboot(null); 240 } 241 } 242 243 /** 244 * Utility function for extracting apex sessions out of multi-package/single session. 245 */ extractApexSessions(StagedSession session)246 private List<StagedSession> extractApexSessions(StagedSession session) { 247 List<StagedSession> apexSessions = new ArrayList<>(); 248 if (session.isMultiPackage()) { 249 for (StagedSession s : session.getChildSessions()) { 250 if (s.containsApexSession()) { 251 apexSessions.add(s); 252 } 253 } 254 } else { 255 apexSessions.add(session); 256 } 257 return apexSessions; 258 } 259 260 /** 261 * Checks if all apk-in-apex were installed without errors for all of the apex sessions. Throws 262 * error for any apk-in-apex failed to install. 263 * 264 * @throws PackageManagerException if any apk-in-apex failed to install 265 */ checkInstallationOfApkInApexSuccessful(StagedSession session)266 private void checkInstallationOfApkInApexSuccessful(StagedSession session) 267 throws PackageManagerException { 268 final List<StagedSession> apexSessions = extractApexSessions(session); 269 if (apexSessions.isEmpty()) { 270 return; 271 } 272 273 for (StagedSession apexSession : apexSessions) { 274 String packageName = apexSession.getPackageName(); 275 String errorMsg = mApexManager.getApkInApexInstallError(packageName); 276 if (errorMsg != null) { 277 throw new PackageManagerException(PackageManager.INSTALL_ACTIVATION_FAILED, 278 "Failed to install apk-in-apex of " + packageName + " : " + errorMsg); 279 } 280 } 281 } 282 283 /** 284 * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX. 285 * Apks inside apex are not installed using apk-install flow. They are scanned from the system 286 * directory directly by PackageManager, as such, RollbackManager need to handle their data 287 * separately here. 288 */ snapshotAndRestoreForApexSession(StagedSession session)289 private void snapshotAndRestoreForApexSession(StagedSession session) { 290 boolean doSnapshotOrRestore = 291 (session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 292 || session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK; 293 if (!doSnapshotOrRestore) { 294 return; 295 } 296 297 // Find all the apex sessions that needs processing 298 final List<StagedSession> apexSessions = extractApexSessions(session); 299 if (apexSessions.isEmpty()) { 300 return; 301 } 302 303 final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); 304 final int[] allUsers = um.getUserIds(); 305 RollbackManagerInternal rm = LocalServices.getService(RollbackManagerInternal.class); 306 307 for (int i = 0, sessionsSize = apexSessions.size(); i < sessionsSize; i++) { 308 final String packageName = apexSessions.get(i).getPackageName(); 309 // Perform any snapshots or restores for the APEX itself 310 snapshotAndRestoreApexUserData(packageName, allUsers, rm); 311 312 // Process the apks inside the APEX 313 final List<String> apksInApex = mApexManager.getApksInApex(packageName); 314 for (int j = 0, apksSize = apksInApex.size(); j < apksSize; j++) { 315 snapshotAndRestoreApkInApexUserData(apksInApex.get(j), allUsers, rm); 316 } 317 } 318 } 319 snapshotAndRestoreApexUserData( String packageName, int[] allUsers, RollbackManagerInternal rm)320 private void snapshotAndRestoreApexUserData( 321 String packageName, int[] allUsers, RollbackManagerInternal rm) { 322 // appId, ceDataInode, and seInfo are not needed for APEXes 323 rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(allUsers), 0, 0, 324 null, 0 /*token*/); 325 } 326 snapshotAndRestoreApkInApexUserData( String packageName, int[] allUsers, RollbackManagerInternal rm)327 private void snapshotAndRestoreApkInApexUserData( 328 String packageName, int[] allUsers, RollbackManagerInternal rm) { 329 PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class); 330 AndroidPackage pkg = mPmi.getPackage(packageName); 331 if (pkg == null) { 332 Slog.e(TAG, "Could not find package: " + packageName 333 + "for snapshotting/restoring user data."); 334 return; 335 } 336 337 int appId = -1; 338 long ceDataInode = -1; 339 final PackageStateInternal ps = mPmi.getPackageStateInternal(packageName); 340 if (ps != null) { 341 appId = ps.getAppId(); 342 ceDataInode = ps.getUserStateOrDefault(UserHandle.USER_SYSTEM).getCeDataInode(); 343 // NOTE: We ignore the user specified in the InstallParam because we know this is 344 // an update, and hence need to restore data for all installed users. 345 final int[] installedUsers = PackageStateUtils.queryInstalledUsers(ps, allUsers, true); 346 347 final String seInfo = ps.getSeInfo(); 348 rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers), 349 appId, ceDataInode, seInfo, 0 /*token*/); 350 } 351 } 352 353 /** 354 * Prepares for the logging of apexd reverts by storing the native failure reason if necessary, 355 * and adding the package name of the session which apexd reverted to the list of reverted 356 * session package names. 357 * Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent. 358 */ prepareForLoggingApexdRevert(@onNull StagedSession session, @NonNull String nativeFailureReason)359 private void prepareForLoggingApexdRevert(@NonNull StagedSession session, 360 @NonNull String nativeFailureReason) { 361 synchronized (mFailedPackageNames) { 362 mNativeFailureReason = nativeFailureReason; 363 if (session.getPackageName() != null) { 364 mFailedPackageNames.add(session.getPackageName()); 365 } 366 } 367 } 368 resumeSession(@onNull StagedSession session, boolean supportsCheckpoint, boolean needsCheckpoint)369 private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint, 370 boolean needsCheckpoint) throws PackageManagerException { 371 Slog.d(TAG, "Resuming session " + session.sessionId()); 372 373 final boolean hasApex = session.containsApexSession(); 374 375 // Before we resume session, we check if revert is needed or not. Typically, we enter file- 376 // system checkpoint mode when we reboot first time in order to install staged sessions. We 377 // want to install staged sessions in this mode as rebooting now will revert user data. If 378 // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will 379 // have no effect on user data, so mark the sessions as failed instead. 380 // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode. 381 // If not, we fail all sessions. 382 if (supportsCheckpoint && !needsCheckpoint) { 383 String revertMsg = "Reverting back to safe state. Marking " + session.sessionId() 384 + " as failed."; 385 final String reasonForRevert = getReasonForRevert(); 386 if (!TextUtils.isEmpty(reasonForRevert)) { 387 revertMsg += " Reason for revert: " + reasonForRevert; 388 } 389 Slog.d(TAG, revertMsg); 390 session.setSessionFailed(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, revertMsg); 391 return; 392 } 393 394 // Handle apk and apk-in-apex installation 395 if (hasApex) { 396 checkInstallationOfApkInApexSuccessful(session); 397 checkDuplicateApkInApex(session); 398 snapshotAndRestoreForApexSession(session); 399 Slog.i(TAG, "APEX packages in session " + session.sessionId() 400 + " were successfully activated. Proceeding with APK packages, if any"); 401 } 402 // The APEX part of the session is activated, proceed with the installation of APKs. 403 Slog.d(TAG, "Installing APK packages in session " + session.sessionId()); 404 TimingsTraceLog t = new TimingsTraceLog( 405 "StagingManagerTiming", Trace.TRACE_TAG_PACKAGE_MANAGER); 406 t.traceBegin("installApksInSession"); 407 installApksInSession(session); 408 t.traceEnd(); 409 410 if (hasApex) { 411 if (supportsCheckpoint) { 412 // Store the session ID, which will be marked as successful by ApexManager upon 413 // boot completion. 414 synchronized (mSuccessfulStagedSessionIds) { 415 mSuccessfulStagedSessionIds.add(session.sessionId()); 416 } 417 } else { 418 // Mark sessions as successful immediately on non-checkpointing devices. 419 mApexManager.markStagedSessionSuccessful(session.sessionId()); 420 } 421 } 422 } 423 onInstallationFailure(StagedSession session, PackageManagerException e, boolean supportsCheckpoint, boolean needsCheckpoint)424 void onInstallationFailure(StagedSession session, PackageManagerException e, 425 boolean supportsCheckpoint, boolean needsCheckpoint) { 426 session.setSessionFailed(e.error, e.getMessage()); 427 abortCheckpoint("Failed to install sessionId: " + session.sessionId() 428 + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint); 429 430 // If checkpoint is not supported, we have to handle failure for one staged session. 431 if (!session.containsApexSession()) { 432 return; 433 } 434 435 if (!mApexManager.revertActiveSessions()) { 436 Slog.e(TAG, "Failed to abort APEXd session"); 437 } else { 438 Slog.e(TAG, 439 "Successfully aborted apexd session. Rebooting device in order to revert " 440 + "to the previous state of APEXd."); 441 mPowerManager.reboot(null); 442 } 443 } 444 getReasonForRevert()445 private String getReasonForRevert() { 446 if (!TextUtils.isEmpty(mFailureReason)) { 447 return mFailureReason; 448 } 449 if (!TextUtils.isEmpty(mNativeFailureReason)) { 450 return "Session reverted due to crashing native process: " + mNativeFailureReason; 451 } 452 return ""; 453 } 454 455 /** 456 * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex. 457 */ checkDuplicateApkInApex(@onNull StagedSession session)458 private void checkDuplicateApkInApex(@NonNull StagedSession session) 459 throws PackageManagerException { 460 if (!session.isMultiPackage()) { 461 return; 462 } 463 final Set<String> apkNames = new ArraySet<>(); 464 for (StagedSession s : session.getChildSessions()) { 465 if (!s.isApexSession()) { 466 apkNames.add(s.getPackageName()); 467 } 468 } 469 final List<StagedSession> apexSessions = extractApexSessions(session); 470 for (StagedSession apexSession : apexSessions) { 471 String packageName = apexSession.getPackageName(); 472 for (String apkInApex : mApexManager.getApksInApex(packageName)) { 473 if (!apkNames.add(apkInApex)) { 474 throw new PackageManagerException( 475 PackageManager.INSTALL_ACTIVATION_FAILED, 476 "Package: " + packageName + " in session: " 477 + apexSession.sessionId() + " has duplicate apk-in-apex: " 478 + apkInApex, null); 479 480 } 481 } 482 } 483 } 484 installApksInSession(StagedSession session)485 private void installApksInSession(StagedSession session) throws PackageManagerException { 486 try { 487 // Blocking wait for installation to complete 488 session.installSession().get(); 489 } catch (InterruptedException e) { 490 // Should be impossible 491 throw new RuntimeException(e); 492 } catch (ExecutionException ee) { 493 throw (PackageManagerException) ee.getCause(); 494 } 495 } 496 497 @VisibleForTesting commitSession(@onNull StagedSession session)498 void commitSession(@NonNull StagedSession session) { 499 createSession(session); 500 handleCommittedSession(session); 501 } 502 handleCommittedSession(@onNull StagedSession session)503 private void handleCommittedSession(@NonNull StagedSession session) { 504 if (session.isSessionReady() && session.containsApexSession()) { 505 notifyStagedApexObservers(); 506 } 507 } 508 509 @VisibleForTesting createSession(@onNull StagedSession sessionInfo)510 void createSession(@NonNull StagedSession sessionInfo) { 511 synchronized (mStagedSessions) { 512 mStagedSessions.append(sessionInfo.sessionId(), sessionInfo); 513 } 514 } 515 abortSession(@onNull StagedSession session)516 void abortSession(@NonNull StagedSession session) { 517 synchronized (mStagedSessions) { 518 mStagedSessions.remove(session.sessionId()); 519 } 520 } 521 522 /** 523 * <p>Abort committed staged session 524 */ abortCommittedSession(@onNull StagedSession session)525 void abortCommittedSession(@NonNull StagedSession session) { 526 int sessionId = session.sessionId(); 527 if (session.isInTerminalState()) { 528 Slog.w(TAG, "Cannot abort session in final state: " + sessionId); 529 return; 530 } 531 if (!session.isDestroyed()) { 532 throw new IllegalStateException("Committed session must be destroyed before aborting it" 533 + " from StagingManager"); 534 } 535 if (getStagedSession(sessionId) == null) { 536 Slog.w(TAG, "Session " + sessionId + " has been abandoned already"); 537 return; 538 } 539 540 // A session could be marked ready once its pre-reboot verification ends 541 if (session.isSessionReady()) { 542 if (!ensureActiveApexSessionIsAborted(session)) { 543 // Failed to ensure apex session is aborted, so it can still be staged. We can still 544 // safely cleanup the staged session since pre-reboot verification is complete. 545 // Also, cleaning up the stageDir prevents the apex from being activated. 546 Slog.e(TAG, "Failed to abort apex session " + session.sessionId()); 547 } 548 if (session.containsApexSession()) { 549 notifyStagedApexObservers(); 550 } 551 } 552 553 // Session was successfully aborted from apexd (if required) and pre-reboot verification 554 // is also complete. It is now safe to clean up the session from system. 555 abortSession(session); 556 } 557 558 /** 559 * Ensure that there is no active apex session staged in apexd for the given session. 560 * 561 * @return returns true if it is ensured that there is no active apex session, otherwise false 562 */ ensureActiveApexSessionIsAborted(StagedSession session)563 private boolean ensureActiveApexSessionIsAborted(StagedSession session) { 564 if (!session.containsApexSession()) { 565 return true; 566 } 567 final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId()); 568 if (apexSession == null || isApexSessionFinalized(apexSession)) { 569 return true; 570 } 571 return mApexManager.abortStagedSession(session.sessionId()); 572 } 573 isApexSessionFinalized(ApexSessionInfo session)574 private boolean isApexSessionFinalized(ApexSessionInfo session) { 575 /* checking if the session is in a final state, i.e., not active anymore */ 576 return session.isUnknown || session.isActivationFailed || session.isSuccess 577 || session.isReverted; 578 } 579 isApexSessionFailed(ApexSessionInfo apexSessionInfo)580 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) { 581 // isRevertInProgress is included to cover the scenario, when a device is rebooted 582 // during the revert, and apexd fails to resume the revert after reboot. 583 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown 584 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress 585 || apexSessionInfo.isRevertFailed; 586 } 587 handleNonReadyAndDestroyedSessions(List<StagedSession> sessions)588 private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) { 589 int j = sessions.size(); 590 for (int i = 0; i < j; ) { 591 // Maintain following invariant: 592 // * elements at positions [0, i) should be kept 593 // * elements at positions [j, n) should be remove. 594 // * n = sessions.size() 595 StagedSession session = sessions.get(i); 596 if (session.isDestroyed()) { 597 // Device rebooted before abandoned session was cleaned up. 598 session.abandon(); 599 StagedSession session2 = sessions.set(j - 1, session); 600 sessions.set(i, session2); 601 j--; 602 } else if (!session.isSessionReady()) { 603 // The framework got restarted before the pre-reboot verification could complete, 604 // restart the verification. 605 Slog.i(TAG, "Restart verification for session=" + session.sessionId()); 606 mBootCompleted.thenRun(() -> session.verifySession()); 607 StagedSession session2 = sessions.set(j - 1, session); 608 sessions.set(i, session2); 609 j--; 610 } else { 611 i++; 612 } 613 } 614 // Delete last j elements. 615 sessions.subList(j, sessions.size()).clear(); 616 } 617 restoreSessions(@onNull List<StagedSession> sessions, boolean isDeviceUpgrading)618 void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) { 619 TimingsTraceLog t = new TimingsTraceLog( 620 "StagingManagerTiming", Trace.TRACE_TAG_PACKAGE_MANAGER); 621 t.traceBegin("restoreSessions"); 622 623 // Do not resume sessions if boot completed already 624 if (SystemProperties.getBoolean("sys.boot_completed", false)) { 625 return; 626 } 627 628 for (int i = 0; i < sessions.size(); i++) { 629 StagedSession session = sessions.get(i); 630 // Quick check that PackageInstallerService gave us sessions we expected. 631 Preconditions.checkArgument(!session.hasParentSessionId(), 632 session.sessionId() + " is a child session"); 633 Preconditions.checkArgument(session.isCommitted(), 634 session.sessionId() + " is not committed"); 635 Preconditions.checkArgument(!session.isInTerminalState(), 636 session.sessionId() + " is in terminal state"); 637 // Store this parent session which will be used to check overlapping later 638 createSession(session); 639 } 640 641 if (isDeviceUpgrading) { 642 // TODO(ioffe): check that corresponding apex sessions are failed. 643 // The preconditions used during pre-reboot verification might have changed when device 644 // is upgrading. Fail all the sessions and exit early. 645 for (int i = 0; i < sessions.size(); i++) { 646 StagedSession session = sessions.get(i); 647 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, 648 "Build fingerprint has changed"); 649 } 650 return; 651 } 652 653 boolean needsCheckpoint = false; 654 boolean supportsCheckpoint = false; 655 try { 656 supportsCheckpoint = InstallLocationUtils.getStorageManager().supportsCheckpoint(); 657 needsCheckpoint = InstallLocationUtils.getStorageManager().needsCheckpoint(); 658 } catch (RemoteException e) { 659 // This means that vold has crashed, and device is in a bad state. 660 throw new IllegalStateException("Failed to get checkpoint status", e); 661 } 662 663 if (sessions.size() > 1 && !supportsCheckpoint) { 664 throw new IllegalStateException("Detected multiple staged sessions on a device without " 665 + "fs-checkpoint support"); 666 } 667 668 // Do a set of quick checks before resuming individual sessions: 669 // 1. Schedule a pre-reboot verification for non-ready sessions. 670 // 2. Abandon destroyed sessions. 671 handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions| 672 673 // 3. Check state of apex sessions is consistent. All non-applied sessions will be marked 674 // as failed. 675 final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions(); 676 boolean hasFailedApexSession = false; 677 boolean hasAppliedApexSession = false; 678 for (int i = 0; i < sessions.size(); i++) { 679 StagedSession session = sessions.get(i); 680 if (!session.containsApexSession()) { 681 // At this point we are only interested in apex sessions. 682 continue; 683 } 684 final ApexSessionInfo apexSession = apexSessions.get(session.sessionId()); 685 if (apexSession == null || apexSession.isUnknown) { 686 hasFailedApexSession = true; 687 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, "apexd did " 688 + "not know anything about a staged session supposed to be activated"); 689 continue; 690 } else if (isApexSessionFailed(apexSession)) { 691 hasFailedApexSession = true; 692 if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) { 693 prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess); 694 } 695 String errorMsg = "APEX activation failed."; 696 final String reasonForRevert = getReasonForRevert(); 697 if (!TextUtils.isEmpty(reasonForRevert)) { 698 errorMsg += " Reason: " + reasonForRevert; 699 } else if (!TextUtils.isEmpty(apexSession.errorMessage)) { 700 errorMsg += " Error: " + apexSession.errorMessage; 701 } 702 Slog.d(TAG, errorMsg); 703 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, errorMsg); 704 continue; 705 } else if (apexSession.isActivated || apexSession.isSuccess) { 706 hasAppliedApexSession = true; 707 continue; 708 } else if (apexSession.isStaged) { 709 // Apexd did not apply the session for some unknown reason. There is no guarantee 710 // that apexd will install it next time. Safer to proactively mark it as failed. 711 hasFailedApexSession = true; 712 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, 713 "Staged session " + session.sessionId() + " at boot didn't activate nor " 714 + "fail. Marking it as failed anyway."); 715 } else { 716 Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state"); 717 hasFailedApexSession = true; 718 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, 719 "Impossible state"); 720 } 721 } 722 723 if (hasAppliedApexSession && hasFailedApexSession) { 724 abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint, 725 needsCheckpoint); 726 return; 727 } 728 729 if (hasFailedApexSession) { 730 // Either of those means that we failed at least one apex session, hence we should fail 731 // all other sessions. 732 for (int i = 0; i < sessions.size(); i++) { 733 StagedSession session = sessions.get(i); 734 if (session.isSessionFailed()) { 735 // Session has been already failed in the loop above. 736 continue; 737 } 738 session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, 739 "Another apex session failed"); 740 } 741 return; 742 } 743 744 // Time to resume sessions. 745 for (int i = 0; i < sessions.size(); i++) { 746 StagedSession session = sessions.get(i); 747 try { 748 resumeSession(session, supportsCheckpoint, needsCheckpoint); 749 } catch (PackageManagerException e) { 750 onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint); 751 } catch (Exception e) { 752 Slog.e(TAG, "Staged install failed due to unhandled exception", e); 753 onInstallationFailure(session, new PackageManagerException( 754 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, 755 "Staged install failed due to unhandled exception: " + e), 756 supportsCheckpoint, needsCheckpoint); 757 } 758 } 759 t.traceEnd(); 760 } 761 logFailedApexSessionsIfNecessary()762 private void logFailedApexSessionsIfNecessary() { 763 synchronized (mFailedPackageNames) { 764 if (!mFailedPackageNames.isEmpty()) { 765 ApexdRevertLogger.logApexdRevert(mContext, 766 mFailedPackageNames, mNativeFailureReason); 767 } 768 } 769 } 770 markStagedSessionsAsSuccessful()771 private void markStagedSessionsAsSuccessful() { 772 synchronized (mSuccessfulStagedSessionIds) { 773 for (int i = 0; i < mSuccessfulStagedSessionIds.size(); i++) { 774 mApexManager.markStagedSessionSuccessful(mSuccessfulStagedSessionIds.get(i)); 775 } 776 } 777 } 778 systemReady()779 void systemReady() { 780 new Lifecycle(mContext).startService(this); 781 // Register the receiver of boot completed intent for staging manager. 782 mContext.registerReceiver(new BroadcastReceiver() { 783 @Override 784 public void onReceive(Context ctx, Intent intent) { 785 onBootCompletedBroadcastReceived(); 786 ctx.unregisterReceiver(this); 787 } 788 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); 789 790 mFailureReasonFile.delete(); 791 } 792 793 @VisibleForTesting onBootCompletedBroadcastReceived()794 void onBootCompletedBroadcastReceived() { 795 mBootCompleted.complete(null); 796 BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary()); 797 } 798 getStagedSession(int sessionId)799 private StagedSession getStagedSession(int sessionId) { 800 StagedSession session; 801 synchronized (mStagedSessions) { 802 session = mStagedSessions.get(sessionId); 803 } 804 return session; 805 } 806 807 /** 808 * Returns ApexInfo about APEX contained inside the session. 809 * 810 * Returns an empty list if there is any error. 811 */ 812 @VisibleForTesting 813 @NonNull getStagedApexInfos(@onNull StagedSession session)814 List<ApexInfo> getStagedApexInfos(@NonNull StagedSession session) { 815 Preconditions.checkArgument(session != null, "Session is null"); 816 Preconditions.checkArgument(!session.hasParentSessionId(), 817 session.sessionId() + " session has parent session"); 818 Preconditions.checkArgument(session.containsApexSession(), 819 session.sessionId() + " session does not contain apex"); 820 821 // Even if caller calls this method on ready session, the session could be abandoned 822 // right after this method is called. 823 if (!session.isSessionReady() || session.isDestroyed()) { 824 return Collections.emptyList(); 825 } 826 827 ApexSessionParams params = new ApexSessionParams(); 828 params.sessionId = session.sessionId(); 829 final IntArray childSessionIds = new IntArray(); 830 if (session.isMultiPackage()) { 831 for (StagedSession s : session.getChildSessions()) { 832 if (s.isApexSession()) { 833 childSessionIds.add(s.sessionId()); 834 } 835 } 836 } 837 params.childSessionIds = childSessionIds.toArray(); 838 return Arrays.asList(mApexManager.getStagedApexInfos(params)); 839 } 840 841 /** 842 * Returns ApexInfo list about APEXes contained inside all staged sessions. 843 * 844 * Returns an empty list if there is any error. 845 */ 846 @NonNull getStagedApexInfos()847 List<StagedApexInfo> getStagedApexInfos() { 848 List<StagedApexInfo> result = new ArrayList<>(); 849 synchronized (mStagedSessions) { 850 for (int i = 0; i < mStagedSessions.size(); i++) { 851 final StagedSession session = mStagedSessions.valueAt(i); 852 if (!session.isSessionReady() || session.isDestroyed() 853 || session.hasParentSessionId() || !session.containsApexSession()) { 854 continue; 855 } 856 getStagedApexInfos(session).stream().map(ai -> { 857 StagedApexInfo info = new StagedApexInfo(); 858 info.moduleName = ai.moduleName; 859 info.diskImagePath = ai.modulePath; 860 info.versionCode = ai.versionCode; 861 info.versionName = ai.versionName; 862 info.hasClassPathJars = ai.hasClassPathJars; 863 return info; 864 }).forEach(result::add); 865 } 866 } 867 return result; 868 } 869 notifyStagedApexObservers()870 private void notifyStagedApexObservers() { 871 synchronized (mStagedApexObservers) { 872 List<StagedApexInfo> stagedApexInfos = getStagedApexInfos(); 873 ApexStagedEvent event = new ApexStagedEvent(); 874 event.stagedApexInfos = 875 stagedApexInfos.toArray(new StagedApexInfo[stagedApexInfos.size()]); 876 for (IStagedApexObserver observer : mStagedApexObservers) { 877 try { 878 observer.onApexStaged(event); 879 } catch (RemoteException re) { 880 Slog.w(TAG, "Failed to contact the observer " + re.getMessage()); 881 } 882 } 883 } 884 } 885 } 886