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