Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5caeed5d authored by Sudheer Shanka's avatar Sudheer Shanka
Browse files

Add shell command to clear blobs data.

+ Add arguments to allow blobstore dump data to be
  filtered.

Test: manual
Change-Id: Ida26ce1d3a09aa457885a869efc13f8d06781bd5
parent 7469bbce
Loading
Loading
Loading
Loading
+30 −6
Original line number Diff line number Diff line
@@ -214,12 +214,16 @@ public final class BlobHandle implements Parcelable {
    }

    /** @hide */
    public void dump(IndentingPrintWriter fout) {
    public void dump(IndentingPrintWriter fout, boolean dumpFull) {
        if (dumpFull) {
            fout.println("algo: " + algorithm);
        fout.println("digest: " + Base64.encodeToString(digest, Base64.NO_WRAP));
            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) {
+3 −2
Original line number Diff line number Diff line
@@ -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:");
+227 −37
Original line number Diff line number Diff line
@@ -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();

                if (dumpArgs.shouldDumpSessions()) {
                    dumpSessionsLocked(fout, dumpArgs);
                    fout.println();
                }
                if (dumpArgs.shouldDumpBlobs()) {
                    dumpBlobsLocked(fout, dumpArgs);
                    fout.println();
                }
            }
                    fout.decreaseIndent();
        }

                fout.print("\n\n");
        @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);
        }
    }

                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();
    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;
            }
                    fout.decreaseIndent();
            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;
                    }
                } 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;
        }
    }

+111 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -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:");