• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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