diff options
author | Sudheer Shanka <sudheersai@google.com> | 2020-02-01 12:54:33 -0800 |
---|---|---|
committer | Sudheer Shanka <sudheersai@google.com> | 2020-02-01 19:34:38 -0800 |
commit | 5caeed5d6ce3a757c318fea859d2761b151965b7 (patch) | |
tree | dba2245af245ee274782b8ff6993c7cf79a2d14d | |
parent | 7469bbce8e7240617112d07b87945a0f71e351f9 (diff) |
Add shell command to clear blobs data.
+ Add arguments to allow blobstore dump data to be
filtered.
Test: manual
Change-Id: Ida26ce1d3a09aa457885a869efc13f8d06781bd5
5 files changed, 374 insertions, 47 deletions
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index ee0ee9894d4b..f110b36c7e90 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -214,12 +214,16 @@ public final class BlobHandle implements Parcelable { } /** @hide */ - public void dump(IndentingPrintWriter fout) { - fout.println("algo: " + algorithm); - fout.println("digest: " + Base64.encodeToString(digest, Base64.NO_WRAP)); - fout.println("label: " + label); - fout.println("expiryMs: " + expiryTimeMillis); - fout.println("tag: " + tag); + public void dump(IndentingPrintWriter fout, boolean dumpFull) { + if (dumpFull) { + fout.println("algo: " + algorithm); + fout.println("digest: " + (dumpFull ? encodeDigest() : safeDigest())); + fout.println("label: " + label); + fout.println("expiryMs: " + expiryTimeMillis); + fout.println("tag: " + tag); + } else { + fout.println(toString()); + } } /** @hide */ @@ -233,6 +237,26 @@ public final class BlobHandle implements Parcelable { Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long"); } + @Override + public String toString() { + return "BlobHandle {" + + "algo:" + algorithm + "," + + "digest:" + safeDigest() + "," + + "label:" + label + "," + + "expiryMs:" + expiryTimeMillis + "," + + "tag:" + tag + + "}"; + } + + private String safeDigest() { + final String digestStr = encodeDigest(); + return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2); + } + + private String encodeDigest() { + return Base64.encodeToString(digest, Base64.NO_WRAP); + } + public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() { @Override public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index 7052d600323d..aba3e8cadfa3 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -48,6 +48,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; +import com.android.server.blob.BlobStoreManagerService.DumpArgs; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -240,10 +241,10 @@ class BlobMetadata { return revocableFd.getRevocableFileDescriptor(); } - void dump(IndentingPrintWriter fout) { + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { fout.println("blobHandle:"); fout.increaseIndent(); - blobHandle.dump(fout); + blobHandle.dump(fout, dumpArgs.shouldDumpFull()); fout.decreaseIndent(); fout.println("Committers:"); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 215e9c17becb..13f095e5a503 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -40,6 +40,7 @@ import android.annotation.IdRes; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.blob.BlobHandle; import android.app.blob.IBlobStoreManager; import android.app.blob.IBlobStoreSession; @@ -69,6 +70,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; @@ -121,6 +123,9 @@ public class BlobStoreManagerService extends SystemService { private PackageManagerInternal mPackageManagerInternal; + private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo; + private final Runnable mSaveSessionsRunnable = this::writeBlobSessions; + public BlobStoreManagerService(Context context) { this(context, new Injector()); } @@ -533,9 +538,9 @@ public class BlobStoreManagerService extends SystemService { } private void writeBlobsInfoAsync() { - mHandler.post(PooledLambda.obtainRunnable( - BlobStoreManagerService::writeBlobsInfo, - BlobStoreManagerService.this).recycleOnUse()); + if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) { + mHandler.post(mSaveBlobsInfoRunnable); + } } private void writeBlobSessions() { @@ -549,9 +554,9 @@ public class BlobStoreManagerService extends SystemService { } private void writeBlobSessionsAsync() { - mHandler.post(PooledLambda.obtainRunnable( - BlobStoreManagerService::writeBlobSessions, - BlobStoreManagerService.this).recycleOnUse()); + if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) { + mHandler.post(mSaveSessionsRunnable); + } } private int getPackageUid(String packageName, int userId) { @@ -597,6 +602,7 @@ public class BlobStoreManagerService extends SystemService { return new AtomicFile(file, "blobs_index" /* commitLogTag */); } + @VisibleForTesting void handlePackageRemoved(String packageName, int uid) { synchronized (mBlobsLock) { // Clean up any pending sessions @@ -659,6 +665,80 @@ public class BlobStoreManagerService extends SystemService { } } + void runClearAllSessions(@UserIdInt int userId) { + synchronized (mBlobsLock) { + if (userId == UserHandle.USER_ALL) { + mSessions.clear(); + } else { + mSessions.remove(userId); + } + writeBlobSessionsAsync(); + } + } + + void runClearAllBlobs(@UserIdInt int userId) { + synchronized (mBlobsLock) { + if (userId == UserHandle.USER_ALL) { + mBlobsMap.clear(); + } else { + mBlobsMap.remove(userId); + } + writeBlobsInfoAsync(); + } + } + + @GuardedBy("mBlobsLock") + private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final int userId = mSessions.keyAt(i); + if (!dumpArgs.shouldDumpUser(userId)) { + continue; + } + final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); + fout.println("List of sessions in user #" + + userId + " (" + userSessions.size() + "):"); + fout.increaseIndent(); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + final long sessionId = userSessions.keyAt(j); + final BlobStoreSession session = userSessions.valueAt(j); + if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(), + session.getOwnerUid(), session.getSessionId())) { + continue; + } + fout.println("Session #" + sessionId); + fout.increaseIndent(); + session.dump(fout, dumpArgs); + fout.decreaseIndent(); + } + fout.decreaseIndent(); + } + } + + @GuardedBy("mBlobsLock") + private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final int userId = mBlobsMap.keyAt(i); + if (!dumpArgs.shouldDumpUser(userId)) { + continue; + } + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + fout.println("List of blobs in user #" + + userId + " (" + userBlobs.size() + "):"); + fout.increaseIndent(); + for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { + final BlobMetadata blobMetadata = userBlobs.valueAt(j); + if (!dumpArgs.shouldDumpBlob(blobMetadata.blobId)) { + continue; + } + fout.println("Blob #" + blobMetadata.blobId); + fout.increaseIndent(); + blobMetadata.dump(fout, dumpArgs); + fout.decreaseIndent(); + } + fout.decreaseIndent(); + } + } + private class PackageChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -808,47 +888,157 @@ public class BlobStoreManagerService extends SystemService { public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { // TODO: add proto-based version of this. - if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return; + + final DumpArgs dumpArgs = DumpArgs.parse(args); final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); synchronized (mBlobsLock) { fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId); fout.println(); - for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { - final int userId = mSessions.keyAt(i); - final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); - fout.println("List of sessions in user #" - + userId + " (" + userSessions.size() + "):"); - fout.increaseIndent(); - for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { - final long sessionId = userSessions.keyAt(j); - final BlobStoreSession session = userSessions.valueAt(j); - fout.println("Session #" + sessionId); - fout.increaseIndent(); - session.dump(fout); - fout.decreaseIndent(); - } - fout.decreaseIndent(); + + if (dumpArgs.shouldDumpSessions()) { + dumpSessionsLocked(fout, dumpArgs); + fout.println(); } + if (dumpArgs.shouldDumpBlobs()) { + dumpBlobsLocked(fout, dumpArgs); + fout.println(); + } + } + } - fout.print("\n\n"); - - for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { - final int userId = mBlobsMap.keyAt(i); - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); - fout.println("List of blobs in user #" - + userId + " (" + userBlobs.size() + "):"); - fout.increaseIndent(); - for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { - final BlobMetadata blobMetadata = userBlobs.valueAt(j); - fout.println("Blob #" + blobMetadata.blobId); - fout.increaseIndent(); - blobMetadata.dump(fout); - fout.decreaseIndent(); + @Override + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return (new BlobStoreManagerShellCommand(BlobStoreManagerService.this)).exec(this, + in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); + } + } + + static final class DumpArgs { + private boolean mDumpFull; + private final ArrayList<String> mDumpPackages = new ArrayList<>(); + private final ArrayList<Integer> mDumpUids = new ArrayList<>(); + private final ArrayList<Integer> mDumpUserIds = new ArrayList<>(); + private final ArrayList<Long> mDumpBlobIds = new ArrayList<>(); + private boolean mDumpOnlySelectedSections; + private boolean mDumpSessions; + private boolean mDumpBlobs; + + public boolean shouldDumpSession(String packageName, int uid, long blobId) { + if (!CollectionUtils.isEmpty(mDumpPackages) + && mDumpPackages.indexOf(packageName) < 0) { + return false; + } + if (!CollectionUtils.isEmpty(mDumpUids) + && mDumpUids.indexOf(uid) < 0) { + return false; + } + if (!CollectionUtils.isEmpty(mDumpBlobIds) + && mDumpBlobIds.indexOf(blobId) < 0) { + return false; + } + return true; + } + + public boolean shouldDumpSessions() { + if (!mDumpOnlySelectedSections) { + return true; + } + return mDumpSessions; + } + + public boolean shouldDumpBlobs() { + if (!mDumpOnlySelectedSections) { + return true; + } + return mDumpBlobs; + } + + public boolean shouldDumpBlob(long blobId) { + return CollectionUtils.isEmpty(mDumpBlobIds) + || mDumpBlobIds.indexOf(blobId) >= 0; + } + + public boolean shouldDumpFull() { + return mDumpFull; + } + + public boolean shouldDumpUser(int userId) { + return CollectionUtils.isEmpty(mDumpUserIds) + || mDumpUserIds.indexOf(userId) >= 0; + } + + private DumpArgs() {} + + public static DumpArgs parse(String[] args) { + final DumpArgs dumpArgs = new DumpArgs(); + if (args == null) { + return dumpArgs; + } + + for (int i = 0; i < args.length; ++i) { + final String opt = args[i]; + if ("--full".equals(opt) || "-f".equals(opt)) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { + dumpArgs.mDumpFull = true; } - fout.decreaseIndent(); + } else if ("--sessions".equals(opt)) { + dumpArgs.mDumpOnlySelectedSections = true; + dumpArgs.mDumpSessions = true; + } else if ("--blobs".equals(opt)) { + dumpArgs.mDumpOnlySelectedSections = true; + dumpArgs.mDumpBlobs = true; + } else if ("--package".equals(opt) || "-p".equals(opt)) { + dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName")); + } else if ("--uid".equals(opt) || "-u".equals(opt)) { + dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid")); + } else if ("--user".equals(opt)) { + dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId")); + } else if ("--blob".equals(opt) || "-b".equals(opt)) { + dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId")); + } else { + // Everything else is assumed to be blob ids. + dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId")); } } + return dumpArgs; + } + + private static String getStringArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + return args[index]; + } + + private static int getIntArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + final int value; + try { + value = Integer.parseInt(args[index]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); + } + return value; + } + + private static long getLongArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + final long value; + try { + value = Long.parseLong(args[index]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); + } + return value; } } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java new file mode 100644 index 000000000000..3ac30f8fff6c --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java @@ -0,0 +1,111 @@ +/* + * Copyright 2020 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.blob; + +import android.os.ShellCommand; +import android.os.UserHandle; + +import java.io.PrintWriter; + +class BlobStoreManagerShellCommand extends ShellCommand { + + private final BlobStoreManagerService mService; + + BlobStoreManagerShellCommand(BlobStoreManagerService blobStoreManagerService) { + mService = blobStoreManagerService; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(null); + } + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "clear-all-sessions": + return runClearAllSessions(pw); + case "clear-all-blobs": + return runClearAllBlobs(pw); + default: + return handleDefaultCommands(cmd); + } + } + + private int runClearAllSessions(PrintWriter pw) { + final ParsedArgs args = new ParsedArgs(); + args.userId = UserHandle.USER_ALL; + + if (parseOptions(pw, args) < 0) { + return -1; + } + + mService.runClearAllSessions(args.userId); + return 0; + } + + private int runClearAllBlobs(PrintWriter pw) { + final ParsedArgs args = new ParsedArgs(); + args.userId = UserHandle.USER_ALL; + + if (parseOptions(pw, args) < 0) { + return -1; + } + + mService.runClearAllBlobs(args.userId); + return 0; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("BlobStore service (blob_store) commands:"); + pw.println("help"); + pw.println(" Print this help text."); + pw.println(); + pw.println("clear-all-sessions [-u | --user USER_ID]"); + pw.println(" Remove all sessions."); + pw.println(" Options:"); + pw.println(" -u or --user: specify which user's sessions to be removed;"); + pw.println(" If not specified, sessions in all users are removed."); + pw.println(); + pw.println("clear-all-blobs [-u | --user USER_ID]"); + pw.println(" Remove all blobs."); + pw.println(" Options:"); + pw.println(" -u or --user: specify which user's blobs to be removed;"); + pw.println(" If not specified, blobs in all users are removed."); + pw.println(); + } + + private int parseOptions(PrintWriter pw, ParsedArgs args) { + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-u": + case "--user": + args.userId = Integer.parseInt(getNextArgRequired()); + break; + default: + pw.println("Error: unknown option '" + opt + "'"); + return -1; + } + } + return 0; + } + + private static class ParsedArgs { + public int userId; + } +} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index b457396c6587..54a299722754 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -51,6 +51,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.server.blob.BlobStoreManagerService.DumpArgs; import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; import org.xmlpull.v1.XmlPullParser; @@ -453,7 +454,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } } - void dump(IndentingPrintWriter fout) { + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { synchronized (mSessionLock) { fout.println("state: " + stateToString(mState)); fout.println("ownerUid: " + mOwnerUid); @@ -461,7 +462,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { fout.println("blobHandle:"); fout.increaseIndent(); - mBlobHandle.dump(fout); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); fout.decreaseIndent(); fout.println("accessMode:"); |