diff options
| author | 2024-09-05 17:49:37 +0000 | |
|---|---|---|
| committer | 2024-09-05 17:49:37 +0000 | |
| commit | 6043d7c246d6abc11e704f62b5ec42707e2f25e5 (patch) | |
| tree | 3cc876db92d5f909d5a0828297aeb7fff3105559 | |
| parent | 584002d13fd6651adf499a59493285fbf21655fb (diff) | |
| parent | c20e5d73cbd39ea580b68ae745453b7ce9a236a1 (diff) | |
Merge "Separating logging for Apexd triggered rollback" into main
5 files changed, 283 insertions, 70 deletions
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 74594cce0041..94bdfbd9c6f5 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -56,8 +56,8 @@ import com.android.server.SystemServiceManager; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; +import com.android.server.rollback.ApexdRevertLogger; import com.android.server.rollback.RollbackManagerInternal; -import com.android.server.rollback.WatchdogRollbackLogger; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -764,7 +764,7 @@ public class StagingManager { private void logFailedApexSessionsIfNecessary() { synchronized (mFailedPackageNames) { if (!mFailedPackageNames.isEmpty()) { - WatchdogRollbackLogger.logApexdRevert(mContext, + ApexdRevertLogger.logApexdRevert(mContext, mFailedPackageNames, mNativeFailureReason); } } diff --git a/services/core/java/com/android/server/rollback/ApexdRevertLogger.java b/services/core/java/com/android/server/rollback/ApexdRevertLogger.java new file mode 100644 index 000000000000..9950cc7645b9 --- /dev/null +++ b/services/core/java/com/android/server/rollback/ApexdRevertLogger.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.rollback; + +import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED; +import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT; +import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.VersionedPackage; +import android.os.SystemProperties; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog; + +import java.util.List; +import java.util.Set; + +/** + * This class handles the logic for logging Apexd-triggered rollback events. + * TODO: b/354112511 Refactor to have a separate metric for ApexdReverts + */ +public final class ApexdRevertLogger { + private static final String TAG = "WatchdogRollbackLogger"; + + private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT"; + + /** + * Logs that one or more apexd reverts have occurred, along with the crashing native process + * that caused apexd to revert during boot. + * + * @param context the context to use when determining the log packages + * @param failedPackageNames a list of names of packages which were reverted + * @param failingNativeProcess the crashing native process which caused a revert + */ + public static void logApexdRevert(Context context, @NonNull List<String> failedPackageNames, + @NonNull String failingNativeProcess) { + Set<VersionedPackage> logPackages = getLogPackages(context, failedPackageNames); + for (VersionedPackage logPackage: logPackages) { + logEvent(logPackage, + failingNativeProcess); + } + } + + /** + * Gets the set of parent packages for a given set of failed package names. In the case that + * multiple sessions have failed, we want to log failure for each of the parent packages. + * Even if multiple failed packages have the same parent, we only log the parent package once. + */ + private static Set<VersionedPackage> getLogPackages(Context context, + @NonNull List<String> failedPackageNames) { + Set<VersionedPackage> parentPackages = new ArraySet<>(); + for (String failedPackageName: failedPackageNames) { + parentPackages.add(getLogPackage(context, new VersionedPackage(failedPackageName, 0))); + } + return parentPackages; + } + + /** + * Returns the logging parent of a given package if it exists, {@code null} otherwise. + * + * The logging parent is defined by the {@code android.content.pm.LOGGING_PARENT} field in the + * metadata of a package's AndroidManifest.xml. + */ + @VisibleForTesting + @Nullable + private static VersionedPackage getLogPackage(Context context, + @NonNull VersionedPackage failingPackage) { + String logPackageName; + VersionedPackage loggingParent; + logPackageName = getLoggingParentName(context, failingPackage.getPackageName()); + if (logPackageName == null) { + return null; + } + try { + loggingParent = new VersionedPackage(logPackageName, context.getPackageManager() + .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode()); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + return loggingParent; + } + + @Nullable + private static String getLoggingParentName(Context context, @NonNull String packageName) { + PackageManager packageManager = context.getPackageManager(); + try { + int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA; + ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo; + if (ai == null || ai.metaData == null) { + return null; + } + return ai.metaData.getString(LOGGING_PARENT_KEY); + } catch (Exception e) { + Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e); + return null; + } + } + + /** + * Log a Apexd rollback event to statsd. + * + * @param logPackage the package to associate the rollback with. + * @param failingPackageName the failing package or process which triggered the rollback. + */ + private static void logEvent(@Nullable VersionedPackage logPackage, + @NonNull String failingPackageName) { + Slog.i(TAG, "Watchdog event occurred with type: ROLLBACK_SUCCESS" + + " logPackage: " + logPackage + + " rollbackReason: REASON_NATIVE_CRASH_DURING_BOOT" + + " failedPackageName: " + failingPackageName); + CrashRecoveryStatsLog.write( + WATCHDOG_ROLLBACK_OCCURRED, + WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, + (logPackage != null) ? logPackage.getPackageName() : "", + (logPackage != null) ? logPackage.getVersionCode() : 0, + WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT, + failingPackageName, + new byte[]{}); + + logTestProperties(logPackage, failingPackageName); + } + + /** + * Writes properties which will be used by rollback tests to check if rollback has occurred + * have occurred. + * + * persist.sys.rollbacktest.enabled: true if rollback tests are running + * persist.sys.rollbacktest.ROLLBACK_SUCCESS.logPackage: the package to associate the rollback + * persist.sys.rollbacktest.ROLLBACK_SUCCESS.rollbackReason: the reason Apexd triggered it + * persist.sys.rollbacktest.ROLLBACK_SUCCESS.failedPackageName: the failing package or process + * which triggered the rollback + */ + private static void logTestProperties(@Nullable VersionedPackage logPackage, + @NonNull String failingPackageName) { + // This property should be on only during the tests + final String prefix = "persist.sys.rollbacktest."; + if (!SystemProperties.getBoolean(prefix + "enabled", false)) { + return; + } + String key = prefix + "ROLLBACK_SUCCESS"; + SystemProperties.set(key, String.valueOf(true)); + SystemProperties.set(key + ".logPackage", logPackage != null ? logPackage.toString() : ""); + SystemProperties.set(key + ".rollbackReason", "REASON_NATIVE_CRASH_DURING_BOOT"); + SystemProperties.set(key + ".failedPackageName", failingPackageName); + } +} diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java index 7fc02923bfed..d763199ee019 100644 --- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java @@ -165,25 +165,6 @@ public final class WatchdogRollbackLogger { } /** - * Logs that one or more apexd reverts have occurred, along with the crashing native process - * that caused apexd to revert during boot. - * - * @param context the context to use when determining the log packages - * @param failedPackageNames a list of names of packages which were reverted - * @param failingNativeProcess the crashing native process which caused a revert - */ - public static void logApexdRevert(Context context, @NonNull List<String> failedPackageNames, - @NonNull String failingNativeProcess) { - Set<VersionedPackage> logPackages = getLogPackages(context, failedPackageNames); - for (VersionedPackage logPackage: logPackages) { - logEvent(logPackage, - WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, - WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT, - failingNativeProcess); - } - } - - /** * Log a Watchdog rollback event to statsd. * * @param logPackage the package to associate the rollback with. diff --git a/services/tests/servicestests/src/com/android/server/rollback/ApexdRevertLoggerTest.java b/services/tests/servicestests/src/com/android/server/rollback/ApexdRevertLoggerTest.java new file mode 100644 index 000000000000..4aa6d398f9c7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/rollback/ApexdRevertLoggerTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.rollback; + + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public class ApexdRevertLoggerTest { + + private Context mMockContext = mock(Context.class); + private PackageManager mMockPm; + private PackageInfo mPackageInfo; + + private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT"; + private static final int PACKAGE_INFO_FLAGS = PackageManager.MATCH_APEX + | PackageManager.GET_META_DATA; + private static final List<String> sFailingPackages = + List.of("package1", "package2", "package3"); + + @Before + public void setUp() { + mMockPm = mock(PackageManager.class); + when(mMockContext.getPackageManager()).thenReturn(mMockPm); + PackageInstaller mockPi = mock(PackageInstaller.class); + when(mMockPm.getPackageInstaller()).thenReturn(mockPi); + PackageInstaller.SessionInfo mockSessionInfo = mock(PackageInstaller.SessionInfo.class); + when(mockPi.getSessionInfo(anyInt())).thenReturn(mockSessionInfo); + mPackageInfo = new PackageInfo(); + } + + /** + * Ensures that we make the correct Package Manager calls in the case that the failing packages + * are correctly configured with parent packages. + */ + @Test + public void testApexdLoggingCallsWithParents() throws Exception { + for (String failingPackage: sFailingPackages) { + PackageInfo packageInfo = new PackageInfo(); + ApplicationInfo applicationInfo = new ApplicationInfo(); + Bundle bundle = new Bundle(); + bundle.putString(LOGGING_PARENT_KEY, getParent(failingPackage)); + applicationInfo.metaData = bundle; + packageInfo.applicationInfo = applicationInfo; + when(mMockPm.getPackageInfo(same(failingPackage), anyInt())).thenReturn(packageInfo); + } + + when(mMockPm.getPackageInfo(anyString(), eq(0))).thenReturn(mPackageInfo); + ApexdRevertLogger.logApexdRevert(mMockContext, sFailingPackages, "test_process"); + for (String failingPackage: sFailingPackages) { + verify(mMockPm, times(1)).getPackageInfo(failingPackage, PACKAGE_INFO_FLAGS); + verify(mMockPm, times(1)).getPackageInfo(getParent(failingPackage), 0); + } + } + + /** + * Ensures that we don't make any calls to parent packages in the case that packages are not + * correctly configured with parent packages. + */ + @Test + public void testApexdLoggingCallsWithNoParents() throws Exception { + for (String failingPackage: sFailingPackages) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.applicationInfo = new ApplicationInfo(); + when(mMockPm.getPackageInfo(same(failingPackage), anyInt())).thenReturn(packageInfo); + } + when(mMockPm.getPackageInfo(anyString(), eq(0))).thenReturn(mPackageInfo); + + ApexdRevertLogger.logApexdRevert(mMockContext, sFailingPackages, "test_process"); + verify(mMockPm, times(sFailingPackages.size())).getPackageInfo(anyString(), anyInt()); + for (String failingPackage: sFailingPackages) { + verify(mMockPm, times(1)).getPackageInfo(failingPackage, PACKAGE_INFO_FLAGS); + } + } + + private String getParent(String packageName) { + return packageName + "-parent"; + } +} diff --git a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java b/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java index d1c9643859e3..8257168f8d08 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -139,52 +138,4 @@ public class WatchdogRollbackLoggerTest { verify(mMockPm, times(1)).getPackageInfo( sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS); } - - /** - * Ensures that we make the correct Package Manager calls in the case that the failing packages - * are correctly configured with parent packages. - */ - @Test - public void testApexdLoggingCallsWithParents() throws Exception { - for (String failingPackage: sFailingPackages) { - PackageInfo packageInfo = new PackageInfo(); - ApplicationInfo applicationInfo = new ApplicationInfo(); - Bundle bundle = new Bundle(); - bundle.putString(LOGGING_PARENT_KEY, getParent(failingPackage)); - applicationInfo.metaData = bundle; - packageInfo.applicationInfo = applicationInfo; - when(mMockPm.getPackageInfo(same(failingPackage), anyInt())).thenReturn(packageInfo); - } - - when(mMockPm.getPackageInfo(anyString(), eq(0))).thenReturn(mPackageInfo); - WatchdogRollbackLogger.logApexdRevert(mMockContext, sFailingPackages, "test_process"); - for (String failingPackage: sFailingPackages) { - verify(mMockPm, times(1)).getPackageInfo(failingPackage, PACKAGE_INFO_FLAGS); - verify(mMockPm, times(1)).getPackageInfo(getParent(failingPackage), 0); - } - } - - /** - * Ensures that we don't make any calls to parent packages in the case that packages are not - * correctly configured with parent packages. - */ - @Test - public void testApexdLoggingCallsWithNoParents() throws Exception { - for (String failingPackage: sFailingPackages) { - PackageInfo packageInfo = new PackageInfo(); - packageInfo.applicationInfo = new ApplicationInfo(); - when(mMockPm.getPackageInfo(same(failingPackage), anyInt())).thenReturn(packageInfo); - } - when(mMockPm.getPackageInfo(anyString(), eq(0))).thenReturn(mPackageInfo); - - WatchdogRollbackLogger.logApexdRevert(mMockContext, sFailingPackages, "test_process"); - verify(mMockPm, times(sFailingPackages.size())).getPackageInfo(anyString(), anyInt()); - for (String failingPackage: sFailingPackages) { - verify(mMockPm, times(1)).getPackageInfo(failingPackage, PACKAGE_INFO_FLAGS); - } - } - - private String getParent(String packageName) { - return packageName + "-parent"; - } } |