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