diff options
| -rw-r--r-- | Android.bp | 1 | ||||
| -rw-r--r-- | core/java/com/android/server/BootReceiver.java | 60 | ||||
| -rw-r--r-- | services/core/java/com/android/server/os/NativeTombstoneManager.java | 241 | ||||
| -rw-r--r-- | services/core/java/com/android/server/os/NativeTombstoneManagerService.java | 50 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 6 |
5 files changed, 320 insertions, 38 deletions
diff --git a/Android.bp b/Android.bp index 202e548d56c8..2ccadd318df6 100644 --- a/Android.bp +++ b/Android.bp @@ -760,6 +760,7 @@ gensrcs { srcs: [ ":ipconnectivity-proto-src", ":libstats_atom_enum_protos", + ":libtombstone_proto-src", "core/proto/**/*.proto", "libs/incident/**/*.proto", ":service-permission-protos", diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index b4d8e506c61a..95999a716707 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -23,7 +23,6 @@ import android.content.pm.IPackageManager; import android.os.Build; import android.os.DropBoxManager; import android.os.Environment; -import android.os.FileObserver; import android.os.FileUtils; import android.os.RecoverySystem; import android.os.RemoteException; @@ -74,7 +73,6 @@ public class BootReceiver extends BroadcastReceiver { SystemProperties.getInt("ro.debuggable", 0) == 1 ? 196608 : 65536; private static final int GMSCORE_LASTK_LOG_SIZE = 196608; - private static final File TOMBSTONE_DIR = new File("/data/tombstones"); private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE"; // The pre-froyo package and class of the system updater, which @@ -85,9 +83,6 @@ public class BootReceiver extends BroadcastReceiver { private static final String OLD_UPDATER_CLASS = "com.google.android.systemupdater.SystemUpdateReceiver"; - // Keep a reference to the observer so the finalizer doesn't disable it. - private static FileObserver sTombstoneObserver = null; - private static final String LOG_FILES_FILE = "log-files.xml"; private static final AtomicFile sFile = new AtomicFile(new File( Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files"); @@ -153,7 +148,7 @@ public class BootReceiver extends BroadcastReceiver { Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); } - private String getPreviousBootHeaders() { + private static String getPreviousBootHeaders() { try { return FileUtils.readTextFile(lastHeaderFile, 0, null); } catch (IOException e) { @@ -161,7 +156,7 @@ public class BootReceiver extends BroadcastReceiver { } } - private String getCurrentBootHeaders() throws IOException { + private static String getCurrentBootHeaders() throws IOException { return new StringBuilder(512) .append("Build: ").append(Build.FINGERPRINT).append("\n") .append("Hardware: ").append(Build.BOARD).append("\n") @@ -175,7 +170,7 @@ public class BootReceiver extends BroadcastReceiver { } - private String getBootHeadersToLogAndUpdate() throws IOException { + private static String getBootHeadersToLogAndUpdate() throws IOException { final String oldHeaders = getPreviousBootHeaders(); final String newHeaders = getCurrentBootHeaders(); @@ -247,38 +242,27 @@ public class BootReceiver extends BroadcastReceiver { logFsMountTime(); addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK"); logSystemServerShutdownTimeMetrics(); + writeTimestamps(timestamps); + } - // Scan existing tombstones (in case any new ones appeared) - File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); - for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { - if (tombstoneFiles[i].isFile()) { - addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(), - LOG_SIZE, "SYSTEM_TOMBSTONE"); - } + /** + * Add a tombstone to the DropBox. + * + * @param ctx Context + * @param tombstone path to the tombstone + */ + public static void addTombstoneToDropBox(Context ctx, File tombstone) { + final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); + final String bootReason = SystemProperties.get("ro.boot.bootreason", null); + HashMap<String, Long> timestamps = readTimestamps(); + try { + final String headers = getBootHeadersToLogAndUpdate(); + addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE, + TAG_TOMBSTONE); + } catch (IOException e) { + Slog.e(TAG, "Can't log tombstone", e); } - writeTimestamps(timestamps); - - // Start watching for new tombstone files; will record them as they occur. - // This gets registered with the singleton file observer thread. - sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CREATE) { - @Override - public void onEvent(int event, String path) { - HashMap<String, Long> timestamps = readTimestamps(); - try { - File file = new File(TOMBSTONE_DIR, path); - if (file.isFile() && file.getName().startsWith("tombstone_")) { - addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE, - TAG_TOMBSTONE); - } - } catch (IOException e) { - Slog.e(TAG, "Can't log tombstone", e); - } - writeTimestamps(timestamps); - } - }; - - sTombstoneObserver.startWatching(); } private static void addLastkToDropBox( @@ -761,7 +745,7 @@ public class BootReceiver extends BroadcastReceiver { } } - private void writeTimestamps(HashMap<String, Long> timestamps) { + private static void writeTimestamps(HashMap<String, Long> timestamps) { synchronized (sFile) { final FileOutputStream stream; try { diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java new file mode 100644 index 000000000000..a83edb75badb --- /dev/null +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2021 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.os; + +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; + +import android.annotation.AppIdInt; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.Context; +import android.os.FileObserver; +import android.os.Handler; +import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.util.Slog; +import android.util.SparseArray; +import android.util.proto.ProtoInputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.BootReceiver; +import com.android.server.ServiceThread; +import com.android.server.os.TombstoneProtos.Tombstone; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Optional; + +/** + * A class to manage native tombstones. + */ +public final class NativeTombstoneManager { + private static final String TAG = NativeTombstoneManager.class.getSimpleName(); + + private static final File TOMBSTONE_DIR = new File("/data/tombstones"); + + private final Context mContext; + private final Handler mHandler; + private final TombstoneWatcher mWatcher; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final SparseArray<TombstoneFile> mTombstones; + + NativeTombstoneManager(Context context) { + mTombstones = new SparseArray<TombstoneFile>(); + mContext = context; + + final ServiceThread thread = new ServiceThread(TAG + ":tombstoneWatcher", + THREAD_PRIORITY_BACKGROUND, true /* allowIo */); + thread.start(); + mHandler = thread.getThreadHandler(); + + mWatcher = new TombstoneWatcher(); + mWatcher.startWatching(); + } + + void onSystemReady() { + // Scan existing tombstones. + mHandler.post(() -> { + final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); + for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { + if (tombstoneFiles[i].isFile()) { + handleTombstone(tombstoneFiles[i]); + } + } + }); + } + + private void handleTombstone(File path) { + final String filename = path.getName(); + if (!filename.startsWith("tombstone_")) { + return; + } + + if (filename.endsWith(".pb")) { + handleProtoTombstone(path); + } else { + BootReceiver.addTombstoneToDropBox(mContext, path); + } + } + + private void handleProtoTombstone(File path) { + final String filename = path.getName(); + if (!filename.endsWith(".pb")) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + + final String suffix = filename.substring("tombstone_".length()); + final String numberStr = suffix.substring(0, suffix.length() - 3); + + int number; + try { + number = Integer.parseInt(numberStr); + if (number < 0 || number > 99) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + } catch (NumberFormatException ex) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + + ParcelFileDescriptor pfd; + try { + pfd = ParcelFileDescriptor.open(path, MODE_READ_WRITE); + } catch (FileNotFoundException ex) { + Slog.w(TAG, "failed to open " + path, ex); + return; + } + + final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd); + if (!parsedTombstone.isPresent()) { + IoUtils.closeQuietly(pfd); + return; + } + + synchronized (mLock) { + TombstoneFile previous = mTombstones.get(number); + if (previous != null) { + previous.dispose(); + } + + mTombstones.put(number, parsedTombstone.get()); + } + } + + static class TombstoneFile { + final ParcelFileDescriptor mPfd; + + final @UserIdInt int mUserId; + final @AppIdInt int mAppId; + + boolean mPurged = false; + + TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) { + mPfd = pfd; + mUserId = userId; + mAppId = appId; + } + + public boolean matches(Optional<Integer> userId, Optional<Integer> appId) { + if (mPurged) { + return false; + } + + if (userId.isPresent() && userId.get() != mUserId) { + return false; + } + + if (appId.isPresent() && appId.get() != mAppId) { + return false; + } + + return true; + } + + public void dispose() { + IoUtils.closeQuietly(mPfd); + } + + static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) { + final FileInputStream is = new FileInputStream(pfd.getFileDescriptor()); + final ProtoInputStream stream = new ProtoInputStream(is); + + int uid = 0; + String selinuxLabel = ""; + + try { + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) Tombstone.UID: + uid = stream.readInt(Tombstone.UID); + break; + + case (int) Tombstone.SELINUX_LABEL: + selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL); + break; + + default: + break; + } + } + } catch (IOException ex) { + Slog.e(TAG, "Failed to parse tombstone", ex); + return Optional.empty(); + } + + if (!UserHandle.isApp(uid)) { + Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring"); + return Optional.empty(); + } + + final int userId = UserHandle.getUserId(uid); + final int appId = UserHandle.getAppId(uid); + + if (!selinuxLabel.startsWith("u:r:untrusted_app")) { + Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring"); + return Optional.empty(); + } + + return Optional.of(new TombstoneFile(pfd, userId, appId)); + } + } + + class TombstoneWatcher extends FileObserver { + TombstoneWatcher() { + // Tombstones can be created either by linking an O_TMPFILE temporary file (CREATE), + // or by moving a named temporary file in the same directory on kernels where O_TMPFILE + // isn't supported (MOVED_TO). + super(TOMBSTONE_DIR, FileObserver.CREATE | FileObserver.MOVED_TO); + } + + @Override + public void onEvent(int event, @Nullable String path) { + mHandler.post(() -> { + handleTombstone(new File(TOMBSTONE_DIR, path)); + }); + } + } +} diff --git a/services/core/java/com/android/server/os/NativeTombstoneManagerService.java b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java new file mode 100644 index 000000000000..cb3c7ff0c07d --- /dev/null +++ b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 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.os; + +import android.content.Context; + +import com.android.server.LocalServices; +import com.android.server.SystemService; + +/** + * Service that tracks and manages native tombstones. + * + * @hide + */ +public class NativeTombstoneManagerService extends SystemService { + private static final String TAG = "NativeTombstoneManagerService"; + + private NativeTombstoneManager mManager; + + public NativeTombstoneManagerService(Context context) { + super(context); + } + + @Override + public void onStart() { + mManager = new NativeTombstoneManager(getContext()); + LocalServices.addService(NativeTombstoneManager.class, mManager); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { + mManager.onSystemReady(); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 62592698488e..7a4c611c57b0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -147,6 +147,7 @@ import com.android.server.oemlock.OemLockService; import com.android.server.om.OverlayManagerService; import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; +import com.android.server.os.NativeTombstoneManagerService; import com.android.server.os.SchedulingPolicyService; import com.android.server.people.PeopleService; import com.android.server.pm.BackgroundDexOptService; @@ -1265,6 +1266,11 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS); t.traceEnd(); + // Tracks native tombstones. + t.traceBegin("StartNativeTombstoneManagerService"); + mSystemServiceManager.startService(NativeTombstoneManagerService.class); + t.traceEnd(); + // Service to capture bugreports. t.traceBegin("StartBugreportManagerService"); mSystemServiceManager.startService(BugreportManagerService.class); |