summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistry.java212
1 files changed, 161 insertions, 51 deletions
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index c5842e59cce1..708de73f8eb2 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -27,7 +27,9 @@ import android.app.AppOpsManager.UidState;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Environment;
import android.os.Message;
import android.os.Process;
@@ -38,6 +40,7 @@ import android.util.ArraySet;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.Xml;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.AtomicDirectory;
import com.android.internal.os.BackgroundThread;
@@ -45,6 +48,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -59,10 +63,12 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
@@ -105,6 +111,7 @@ import java.util.concurrent.TimeUnit;
// TODO (bug:122218838): Validate changed time is handled correctly
final class HistoricalRegistry {
private static final boolean DEBUG = false;
+ private static final boolean KEEP_WTF_LOG = Build.IS_DEBUGGABLE;
private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName();
@@ -123,7 +130,7 @@ final class HistoricalRegistry {
private static final int MSG_WRITE_PENDING_HISTORY = 1;
// See mMode
- private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_DISABLED;
+ private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
// See mBaseSnapshotInterval
private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15);
@@ -131,11 +138,13 @@ final class HistoricalRegistry {
// See mIntervalCompressionMultiplier
private static final long DEFAULT_COMPRESSION_STEP = 10;
+ private static final String HISTORY_FILE_SUFFIX = ".xml";
+
/**
* Whether history is enabled.
*/
@GuardedBy("mInMemoryLock")
- private int mMode = AppOpsManager.HISTORICAL_MODE_DISABLED;
+ private int mMode = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
/**
* This granularity has been chosen to allow clean delineation for intervals
@@ -172,10 +181,16 @@ final class HistoricalRegistry {
HistoricalRegistry(@NonNull Object lock) {
mInMemoryLock = lock;
if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
- synchronized (mInMemoryLock) {
- // When starting always adjust history to now.
- mPendingHistoryOffsetMillis = System.currentTimeMillis()
- - mPersistence.getLastPersistTimeMillisDLocked();
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ // When starting always adjust history to now.
+ final long lastPersistTimeMills =
+ mPersistence.getLastPersistTimeMillisDLocked();
+ if (lastPersistTimeMills > 0) {
+ mPendingHistoryOffsetMillis =
+ System.currentTimeMillis() - lastPersistTimeMills;
+ }
+ }
}
}
}
@@ -236,8 +251,8 @@ final class HistoricalRegistry {
+ "=" + setting + " resetting!");
}
- void dump(String prefix, PrintWriter pw, int filterUid,
- String filterPackage, int filterOp) {
+ void dump(String prefix, PrintWriter pw, int filterUid,
+ String filterPackage, int filterOp) {
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
pw.println();
@@ -522,6 +537,7 @@ final class HistoricalRegistry {
persistPendingHistory(pendingWrites);
}
}
+
private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
synchronized (mOnDiskLock) {
BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
@@ -574,8 +590,6 @@ final class HistoricalRegistry {
private static final String LOG_TAG = Persistence.class.getSimpleName();
- private static final String HISTORY_FILE_SUFFIX = ".xml";
-
private static final String TAG_HISTORY = "history";
private static final String TAG_OPS = "ops";
private static final String TAG_UID = "uid";
@@ -603,7 +617,7 @@ final class HistoricalRegistry {
}
private final AtomicDirectory mHistoricalAppOpsDir = new AtomicDirectory(
- new File(new File(Environment.getDataSystemDeDirectory(), "appops"), "history"));
+ new File(new File(Environment.getDataSystemDirectory(), "appops"), "history"));
private File generateFile(@NonNull File baseDir, int depth) {
final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth);
@@ -622,10 +636,20 @@ final class HistoricalRegistry {
try {
final File newBaseDir = mHistoricalAppOpsDir.startWrite();
final File oldBaseDir = mHistoricalAppOpsDir.getBackupDirectory();
- handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops, 0);
+ final HistoricalFilesInvariant filesInvariant;
+ if (DEBUG) {
+ filesInvariant = new HistoricalFilesInvariant();
+ filesInvariant.startTracking(oldBaseDir);
+ }
+ final Set<String> oldFileNames = getHistoricalFileNames(oldBaseDir);
+ handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops,
+ oldFileNames, 0);
+ if (DEBUG) {
+ filesInvariant.stopTracking(newBaseDir);
+ }
mHistoricalAppOpsDir.finishWrite();
} catch (Throwable t) {
- Slog.wtf(LOG_TAG, "Failed to write historical app ops, restoring backup", t);
+ wtf("Failed to write historical app ops, restoring backup", t, null);
mHistoricalAppOpsDir.failWrite();
}
}
@@ -649,15 +673,23 @@ final class HistoricalRegistry {
}
long getLastPersistTimeMillisDLocked() {
+ File baseDir = null;
try {
- final File baseDir = mHistoricalAppOpsDir.startRead();
- final File file = generateFile(baseDir, 0);
- if (file.exists()) {
- return file.lastModified();
+ baseDir = mHistoricalAppOpsDir.startRead();
+ final File[] files = baseDir.listFiles();
+ if (files != null && files.length > 0) {
+ final Set<File> historyFiles = new ArraySet<>();
+ Collections.addAll(historyFiles, files);
+ for (int i = 0;; i++) {
+ final File file = generateFile(baseDir, i);
+ if (historyFiles.contains(file)) {
+ return file.lastModified();
+ }
+ }
}
mHistoricalAppOpsDir.finishRead();
- } catch (IOException e) {
- Slog.wtf("Error reading historical app ops. Deleting history.", e);
+ } catch (Throwable e) {
+ wtf("Error reading historical app ops. Deleting history.", e, baseDir);
mHistoricalAppOpsDir.delete();
}
return 0;
@@ -680,27 +712,27 @@ final class HistoricalRegistry {
private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
long filterBeginTimeMillis, long filterEndTimeMillis) {
+ File baseDir = null;
try {
- final File baseDir = mHistoricalAppOpsDir.startRead();
- final File[] files = baseDir.listFiles();
- if (files == null) {
- return null;
- }
- final ArraySet<File> historyFiles = new ArraySet<>(files.length);
- for (File file : files) {
- if (file.isFile() && file.getName().endsWith(HISTORY_FILE_SUFFIX)) {
- historyFiles.add(file);
- }
+ baseDir = mHistoricalAppOpsDir.startRead();
+ final HistoricalFilesInvariant filesInvariant;
+ if (DEBUG) {
+ filesInvariant = new HistoricalFilesInvariant();
+ filesInvariant.startTracking(baseDir);
}
+ final Set<String> historyFiles = getHistoricalFileNames(baseDir);
final long[] globalContentOffsetMillis = {0};
final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
filterEndTimeMillis, globalContentOffsetMillis, null /*outOps*/,
0 /*depth*/, historyFiles);
+ if (DEBUG) {
+ filesInvariant.stopTracking(baseDir);
+ }
mHistoricalAppOpsDir.finishRead();
return ops;
- } catch (IOException | XmlPullParserException e) {
- Slog.wtf("Error reading historical app ops. Deleting history.", e);
+ } catch (Throwable t) {
+ wtf("Error reading historical app ops. Deleting history.", t, baseDir);
mHistoricalAppOpsDir.delete();
}
return null;
@@ -711,7 +743,7 @@ final class HistoricalRegistry {
@Nullable String[] filterOpNames, long filterBeginTimeMillis,
long filterEndTimeMillis, @NonNull long[] globalContentOffsetMillis,
@Nullable LinkedList<HistoricalOps> outOps, int depth,
- @NonNull ArraySet<File> historyFiles)
+ @NonNull Set<String> historyFiles)
throws IOException, XmlPullParserException {
final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
depth) * mBaseSnapshotInterval;
@@ -761,17 +793,9 @@ final class HistoricalRegistry {
return outOps;
}
- private boolean createHardLinkToExistingFile(@NonNull File fromFile, @NonNull File toFile)
- throws IOException {
- if (!fromFile.exists()) {
- return false;
- }
- Files.createLink(toFile.toPath(), fromFile.toPath());
- return true;
- }
-
private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir,
- @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, int depth)
+ @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps,
+ @NonNull Set<String> oldFileNames, int depth)
throws IOException, XmlPullParserException {
final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
depth) * mBaseSnapshotInterval;
@@ -779,12 +803,15 @@ final class HistoricalRegistry {
depth + 1) * mBaseSnapshotInterval;
if (passedOps == null || passedOps.isEmpty()) {
- // If there is an old file we need to copy it over to the new state.
- final File oldFile = generateFile(oldBaseDir, depth);
- final File newFile = generateFile(newBaseDir, depth);
- if (createHardLinkToExistingFile(oldFile, newFile)) {
+ if (!oldFileNames.isEmpty()) {
+ // If there is an old file we need to copy it over to the new state.
+ final File oldFile = generateFile(oldBaseDir, depth);
+ if (oldFileNames.remove(oldFile.getName())) {
+ final File newFile = generateFile(newBaseDir, depth);
+ Files.createLink(newFile.toPath(), oldFile.toPath());
+ }
handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
- passedOps, depth + 1);
+ passedOps, oldFileNames, depth + 1);
}
return;
}
@@ -900,9 +927,11 @@ final class HistoricalRegistry {
enforceOpsWellFormed(overflowedOps);
}
+ final File newFile = generateFile(newBaseDir, depth);
+ oldFileNames.remove(newFile.getName());
+
if (persistedOps != null) {
normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis);
- final File newFile = generateFile(newBaseDir, depth);
writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile);
if (DEBUG) {
Slog.i(LOG_TAG, "Persisted at depth: " + depth
@@ -912,7 +941,7 @@ final class HistoricalRegistry {
}
handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
- overflowedOps, depth + 1);
+ overflowedOps, oldFileNames, depth + 1);
}
private @NonNull List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
@@ -920,11 +949,11 @@ final class HistoricalRegistry {
@Nullable String filterPackageName, @Nullable String[] filterOpNames,
long filterBeginTimeMillis, long filterEndTimeMillis,
@Nullable long[] cumulativeOverflowMillis, int depth,
- @NonNull ArraySet<File> historyFiles)
+ @NonNull Set<String> historyFiles)
throws IOException, XmlPullParserException {
final File file = generateFile(baseDir, depth);
if (historyFiles != null) {
- historyFiles.remove(file);
+ historyFiles.remove(file.getName());
}
if (filterBeginTimeMillis >= filterEndTimeMillis
|| filterEndTimeMillis < intervalBeginMillis) {
@@ -1364,6 +1393,61 @@ final class HistoricalRegistry {
}
return builder.toString();
}
+
+ private static Set<String> getHistoricalFileNames(@NonNull File historyDir) {
+ final File[] files = historyDir.listFiles();
+ if (files == null) {
+ return Collections.emptySet();
+ }
+ final ArraySet<String> fileNames = new ArraySet<>(files.length);
+ for (File file : files) {
+ fileNames.add(file.getName());
+
+ }
+ return fileNames;
+ }
+ }
+
+ private static class HistoricalFilesInvariant {
+ private final @NonNull List<File> mBeginFiles = new ArrayList<>();
+
+ public void startTracking(@NonNull File folder) {
+ final File[] files = folder.listFiles();
+ if (files != null) {
+ Collections.addAll(mBeginFiles, files);
+ }
+ }
+
+ public void stopTracking(@NonNull File folder) {
+ final List<File> endFiles = new ArrayList<>();
+ final File[] files = folder.listFiles();
+ if (files != null) {
+ Collections.addAll(endFiles, files);
+ }
+ final long beginOldestFileOffsetMillis = getOldestFileOffsetMillis(mBeginFiles);
+ final long endOldestFileOffsetMillis = getOldestFileOffsetMillis(endFiles);
+ if (endOldestFileOffsetMillis < beginOldestFileOffsetMillis) {
+ final String message = "History loss detected!"
+ + "\nold files: " + mBeginFiles;
+ wtf(message, null, folder);
+ throw new IllegalStateException(message);
+ }
+ }
+
+ private static long getOldestFileOffsetMillis(@NonNull List<File> files) {
+ if (files.isEmpty()) {
+ return 0;
+ }
+ String longestName = files.get(0).getName();
+ final int fileCount = files.size();
+ for (int i = 1; i < fileCount; i++) {
+ final File file = files.get(i);
+ if (file.getName().length() > longestName.length()) {
+ longestName = file.getName();
+ }
+ }
+ return Long.parseLong(longestName.replace(HISTORY_FILE_SUFFIX, ""));
+ }
}
private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor {
@@ -1493,4 +1577,30 @@ final class HistoricalRegistry {
}
}
}
+
+ private static void wtf(@Nullable String message, @Nullable Throwable t,
+ @Nullable File storage) {
+ Slog.wtf(LOG_TAG, message, t);
+ if (KEEP_WTF_LOG) {
+ try {
+ final File file = new File(new File(Environment.getDataSystemDirectory(), "appops"),
+ "wtf" + TimeUtils.formatForLogging(System.currentTimeMillis()));
+ if (file.createNewFile()) {
+ try (PrintWriter writer = new PrintWriter(file)) {
+ if (t != null) {
+ writer.append('\n').append(t.toString());
+ }
+ writer.append('\n').append(Debug.getCallers(10));
+ if (storage != null) {
+ writer.append("\nfiles: " + Arrays.toString(storage.listFiles()));
+ } else {
+ writer.append("\nfiles: none");
+ }
+ }
+ }
+ } catch (IOException e) {
+ /* ignore */
+ }
+ }
+ }
}