diff options
author | Sudheer Shanka <sudheersai@google.com> | 2020-01-24 22:42:38 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-01-24 22:42:38 +0000 |
commit | 4266a1994baaab3343b444af1a993e6d1942aa58 (patch) | |
tree | d4f530a378db46321533dfd6f327d75fe9d0d2db /apex/blobstore | |
parent | 0111ea772c3c0c903800b216129a3bbfccd5126f (diff) | |
parent | 22f0b166e89b7b8ad8e82a4e2dd359e692e0ae84 (diff) |
Merge changes from topics "persist-data", "pkg-uninstall"
* changes:
Delete sessions belonging to uninstalled packages.
Persist committed blobs data and any pending sessions.
Diffstat (limited to 'apex/blobstore')
9 files changed, 1012 insertions, 28 deletions
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index 60c313683240..f7e6a987ded3 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -15,13 +15,26 @@ */ package android.app.blob; +import static android.app.blob.XmlTags.ATTR_ALGO; +import static android.app.blob.XmlTags.ATTR_DIGEST; +import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; +import static android.app.blob.XmlTags.ATTR_LABEL; +import static android.app.blob.XmlTags.ATTR_TAG; + import android.annotation.CurrentTimeMillisLong; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.util.Base64; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; +import java.io.IOException; import java.util.Arrays; import java.util.Objects; @@ -41,17 +54,20 @@ public final class BlobHandle implements Parcelable { * @hide */ @NonNull public final String algorithm; + /** * Hash of the blob this handle is representing using {@link #algorithm}. * * @hide */ @NonNull public final byte[] digest; + /** * Label of the blob that can be surfaced to the user. * @hide */ @NonNull public final CharSequence label; + /** * Time in milliseconds after which the blob should be invalidated and not * allowed to be accessed by any other app, in {@link System#currentTimeMillis()} timebase. @@ -59,6 +75,7 @@ public final class BlobHandle implements Parcelable { * @hide */ @CurrentTimeMillisLong public final long expiryTimeMillis; + /** * An opaque {@link String} associated with the blob. * @@ -197,6 +214,15 @@ public final class BlobHandle implements Parcelable { return Objects.hash(algorithm, Arrays.hashCode(digest), label, expiryTimeMillis, tag); } + /** @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 static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() { @Override public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) { @@ -208,4 +234,25 @@ public final class BlobHandle implements Parcelable { return new BlobHandle[size]; } }; + + /** @hide */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + XmlUtils.writeStringAttribute(out, ATTR_ALGO, algorithm); + XmlUtils.writeByteArrayAttribute(out, ATTR_DIGEST, digest); + XmlUtils.writeStringAttribute(out, ATTR_LABEL, label); + XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis); + XmlUtils.writeStringAttribute(out, ATTR_TAG, tag); + } + + /** @hide */ + @NonNull + public static BlobHandle createFromXml(@NonNull XmlPullParser in) throws IOException { + final String algo = XmlUtils.readStringAttribute(in, ATTR_ALGO); + final byte[] digest = XmlUtils.readByteArrayAttribute(in, ATTR_DIGEST); + final CharSequence label = XmlUtils.readStringAttribute(in, ATTR_LABEL); + final long expiryTimeMs = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME); + final String tag = XmlUtils.readStringAttribute(in, ATTR_TAG); + + return BlobHandle.create(algo, digest, label, expiryTimeMs, tag); + } } diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index 756bc5ee14c7..8cea645f4e71 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -25,13 +25,17 @@ import android.annotation.SystemService; import android.content.Context; import android.os.ParcelFileDescriptor; import android.os.ParcelableException; +import android.os.RemoteCallback; import android.os.RemoteException; import com.android.internal.util.function.pooled.PooledLambda; import java.io.Closeable; import java.io.IOException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; /** @@ -353,6 +357,25 @@ public class BlobStoreManager { } /** + * Wait until any pending tasks (like persisting data to disk) have finished. + * + * @hide + */ + public void waitForIdle(long timeoutMillis) throws InterruptedException, TimeoutException { + try { + final CountDownLatch countDownLatch = new CountDownLatch(1); + mService.waitForIdle(new RemoteCallback((result) -> countDownLatch.countDown())); + if (!countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("Timed out waiting for service to become idle"); + } + } catch (ParcelableException e) { + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Represents an ongoing session of a blob's contribution to the blob store managed by the * system. * diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl index dfbf78f4009b..e2128b421746 100644 --- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl @@ -17,6 +17,7 @@ package android.app.blob; import android.app.blob.BlobHandle; import android.app.blob.IBlobStoreSession; +import android.os.RemoteCallback; /** {@hide} */ interface IBlobStoreManager { @@ -28,4 +29,6 @@ interface IBlobStoreManager { void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout, in String packageName); void releaseLease(in BlobHandle handle, in String packageName); + + void waitForIdle(in RemoteCallback callback); }
\ No newline at end of file diff --git a/apex/blobstore/framework/java/android/app/blob/XmlTags.java b/apex/blobstore/framework/java/android/app/blob/XmlTags.java new file mode 100644 index 000000000000..803c9a40e5ea --- /dev/null +++ b/apex/blobstore/framework/java/android/app/blob/XmlTags.java @@ -0,0 +1,55 @@ +/* + * 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 android.app.blob; + +/** @hide */ +public final class XmlTags { + public static final String ATTR_VERSION = "v"; + + public static final String TAG_SESSIONS = "ss"; + public static final String TAG_BLOBS = "bs"; + + // For BlobStoreSession + public static final String TAG_SESSION = "s"; + public static final String ATTR_ID = "id"; + public static final String ATTR_PACKAGE = "p"; + public static final String ATTR_UID = "u"; + + // For BlobMetadata + public static final String TAG_BLOB = "b"; + public static final String ATTR_USER_ID = "us"; + + // For BlobAccessMode + public static final String TAG_ACCESS_MODE = "am"; + public static final String ATTR_TYPE = "t"; + public static final String TAG_WHITELISTED_PACKAGE = "wl"; + public static final String ATTR_CERTIFICATE = "ct"; + + // For BlobHandle + public static final String TAG_BLOB_HANDLE = "bh"; + public static final String ATTR_ALGO = "al"; + public static final String ATTR_DIGEST = "dg"; + public static final String ATTR_LABEL = "lbl"; + public static final String ATTR_EXPIRY_TIME = "ex"; + public static final String ATTR_TAG = "tg"; + + // For committer + public static final String TAG_COMMITTER = "c"; + + // For leasee + public static final String TAG_LEASEE = "l"; + public static final String ATTR_DESCRIPTION_RES_ID = "rid"; +} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java index 357250a3244c..ec7ba287acec 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java @@ -15,12 +15,27 @@ */ package com.android.server.blob; +import static android.app.blob.XmlTags.ATTR_CERTIFICATE; +import static android.app.blob.XmlTags.ATTR_PACKAGE; +import static android.app.blob.XmlTags.ATTR_TYPE; +import static android.app.blob.XmlTags.TAG_WHITELISTED_PACKAGE; + import android.annotation.IntDef; import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.util.ArraySet; +import android.util.Base64; +import android.util.DebugUtils; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -40,10 +55,10 @@ class BlobAccessMode { ACCESS_TYPE_WHITELIST, }) @interface AccessType {} - static final int ACCESS_TYPE_PRIVATE = 1 << 0; - static final int ACCESS_TYPE_PUBLIC = 1 << 1; - static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2; - static final int ACCESS_TYPE_WHITELIST = 1 << 3; + public static final int ACCESS_TYPE_PRIVATE = 1 << 0; + public static final int ACCESS_TYPE_PUBLIC = 1 << 1; + public static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2; + public static final int ACCESS_TYPE_WHITELIST = 1 << 3; private int mAccessType = ACCESS_TYPE_PRIVATE; @@ -112,6 +127,51 @@ class BlobAccessMode { return false; } + void dump(IndentingPrintWriter fout) { + fout.println("accessType: " + DebugUtils.flagsToString( + BlobAccessMode.class, "ACCESS_TYPE_", mAccessType)); + fout.print("Whitelisted pkgs:"); + if (mWhitelistedPackages.isEmpty()) { + fout.println(" (Empty)"); + } else { + fout.increaseIndent(); + for (int i = 0, count = mWhitelistedPackages.size(); i < count; ++i) { + fout.println(mWhitelistedPackages.valueAt(i).toString()); + } + fout.decreaseIndent(); + } + } + + void writeToXml(@NonNull XmlSerializer out) throws IOException { + XmlUtils.writeIntAttribute(out, ATTR_TYPE, mAccessType); + for (int i = 0, count = mWhitelistedPackages.size(); i < count; ++i) { + out.startTag(null, TAG_WHITELISTED_PACKAGE); + final PackageIdentifier packageIdentifier = mWhitelistedPackages.valueAt(i); + XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageIdentifier.packageName); + XmlUtils.writeByteArrayAttribute(out, ATTR_CERTIFICATE, packageIdentifier.certificate); + out.endTag(null, TAG_WHITELISTED_PACKAGE); + } + } + + @NonNull + static BlobAccessMode createFromXml(@NonNull XmlPullParser in) + throws IOException, XmlPullParserException { + final BlobAccessMode blobAccessMode = new BlobAccessMode(); + + final int accessType = XmlUtils.readIntAttribute(in, ATTR_TYPE); + blobAccessMode.mAccessType = accessType; + + final int depth = in.getDepth(); + while (XmlUtils.nextElementWithin(in, depth)) { + if (TAG_WHITELISTED_PACKAGE.equals(in.getName())) { + final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); + final byte[] certificate = XmlUtils.readByteArrayAttribute(in, ATTR_CERTIFICATE); + blobAccessMode.allowPackageAccess(packageName, certificate); + } + } + return blobAccessMode; + } + private static final class PackageIdentifier { public final String packageName; public final byte[] certificate; @@ -143,5 +203,11 @@ class BlobAccessMode { public int hashCode() { return Objects.hash(packageName, Arrays.hashCode(certificate)); } + + @Override + public String toString() { + return "[" + packageName + ", " + + Base64.encodeToString(certificate, Base64.NO_WRAP) + "]"; + } } } 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 d3a227152627..e9838d6b9712 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -15,20 +15,45 @@ */ package com.android.server.blob; +import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_ID; +import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; +import static android.app.blob.XmlTags.ATTR_ID; +import static android.app.blob.XmlTags.ATTR_PACKAGE; +import static android.app.blob.XmlTags.ATTR_UID; +import static android.app.blob.XmlTags.ATTR_USER_ID; +import static android.app.blob.XmlTags.TAG_ACCESS_MODE; +import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; +import static android.app.blob.XmlTags.TAG_COMMITTER; +import static android.app.blob.XmlTags.TAG_LEASEE; import static android.system.OsConstants.O_RDONLY; +import static com.android.server.blob.BlobStoreConfig.TAG; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.blob.BlobHandle; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; import android.os.ParcelFileDescriptor; import android.os.RevocableFileDescriptor; +import android.os.UserHandle; import android.system.ErrnoException; import android.system.Os; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.util.Objects; @@ -37,8 +62,10 @@ class BlobMetadata { private final Object mMetadataLock = new Object(); private final Context mContext; - private final long mBlobId; - private final BlobHandle mBlobHandle; + + public final long blobId; + public final BlobHandle blobHandle; + public final int userId; @GuardedBy("mMetadataLock") private final ArraySet<Committer> mCommitters = new ArraySet<>(); @@ -57,15 +84,47 @@ class BlobMetadata { private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds = new ArrayMap<>(); - BlobMetadata(Context context, long blobId, BlobHandle blobHandle) { + // Do not access this directly, instead use getSessionFile(). + private File mBlobFile; + + BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) { mContext = context; - mBlobId = blobId; - mBlobHandle = blobHandle; + this.blobId = blobId; + this.blobHandle = blobHandle; + this.userId = userId; + } + + void addCommitter(@NonNull Committer committer) { + synchronized (mMetadataLock) { + mCommitters.add(committer); + } + } + + void addCommitters(ArraySet<Committer> committers) { + synchronized (mMetadataLock) { + mCommitters.addAll(committers); + } + } + + void removeCommitter(@NonNull String packageName, int uid) { + synchronized (mMetadataLock) { + mCommitters.removeIf((committer) -> + committer.uid == uid && committer.packageName.equals(packageName)); + } } - void addCommitter(String packageName, int uid, BlobAccessMode blobAccessMode) { + void removeInvalidCommitters(SparseArray<String> packages) { synchronized (mMetadataLock) { - mCommitters.add(new Committer(packageName, uid, blobAccessMode)); + mCommitters.removeIf(committer -> + !committer.packageName.equals(packages.get(committer.uid))); + } + } + + @Nullable + Committer getExistingCommitter(@NonNull Committer newCommitter) { + synchronized (mCommitters) { + final int index = mCommitters.indexOf(newCommitter); + return index >= 0 ? mCommitters.valueAt(index) : null; } } @@ -77,9 +136,23 @@ class BlobMetadata { } } + void addLeasees(ArraySet<Leasee> leasees) { + synchronized (mMetadataLock) { + mLeasees.addAll(leasees); + } + } + void removeLeasee(String packageName, int uid) { synchronized (mMetadataLock) { - mLeasees.remove(new Accessor(packageName, uid)); + mLeasees.removeIf((leasee) -> + leasee.uid == uid && leasee.packageName.equals(packageName)); + } + } + + void removeInvalidLeasees(SparseArray<String> packages) { + synchronized (mMetadataLock) { + mLeasees.removeIf(leasee -> + !leasee.packageName.equals(packages.get(leasee.uid))); } } @@ -114,11 +187,18 @@ class BlobMetadata { return false; } + File getBlobFile() { + if (mBlobFile == null) { + mBlobFile = BlobStoreConfig.getBlobFile(blobId); + } + return mBlobFile; + } + ParcelFileDescriptor openForRead(String callingPackage) throws IOException { // TODO: Add limit on opened fds FileDescriptor fd; try { - fd = Os.open(BlobStoreConfig.getBlobFile(mBlobId).getPath(), O_RDONLY, 0); + fd = Os.open(getBlobFile().getPath(), O_RDONLY, 0); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } @@ -154,6 +234,94 @@ class BlobMetadata { return revocableFd.getRevocableFileDescriptor(); } + void dump(IndentingPrintWriter fout) { + fout.println("blobHandle:"); + fout.increaseIndent(); + blobHandle.dump(fout); + fout.decreaseIndent(); + + fout.println("Committers:"); + fout.increaseIndent(); + for (int i = 0, count = mCommitters.size(); i < count; ++i) { + final Committer committer = mCommitters.valueAt(i); + fout.println("committer " + committer.toString()); + fout.increaseIndent(); + committer.dump(fout); + fout.decreaseIndent(); + } + fout.decreaseIndent(); + + fout.println("Leasees:"); + fout.increaseIndent(); + for (int i = 0, count = mLeasees.size(); i < count; ++i) { + final Leasee leasee = mLeasees.valueAt(i); + fout.println("leasee " + leasee.toString()); + fout.increaseIndent(); + leasee.dump(mContext, fout); + fout.decreaseIndent(); + } + fout.decreaseIndent(); + + fout.println("Open fds: #" + mRevocableFds.size()); + } + + void writeToXml(XmlSerializer out) throws IOException { + synchronized (mMetadataLock) { + XmlUtils.writeLongAttribute(out, ATTR_ID, blobId); + XmlUtils.writeIntAttribute(out, ATTR_USER_ID, userId); + + out.startTag(null, TAG_BLOB_HANDLE); + blobHandle.writeToXml(out); + out.endTag(null, TAG_BLOB_HANDLE); + + for (int i = 0, count = mCommitters.size(); i < count; ++i) { + out.startTag(null, TAG_COMMITTER); + mCommitters.valueAt(i).writeToXml(out); + out.endTag(null, TAG_COMMITTER); + } + + for (int i = 0, count = mLeasees.size(); i < count; ++i) { + out.startTag(null, TAG_LEASEE); + mLeasees.valueAt(i).writeToXml(out); + out.endTag(null, TAG_LEASEE); + } + } + } + + @Nullable + static BlobMetadata createFromXml(Context context, XmlPullParser in) + throws XmlPullParserException, IOException { + final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID); + final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID); + + BlobHandle blobHandle = null; + final ArraySet<Committer> committers = new ArraySet<>(); + final ArraySet<Leasee> leasees = new ArraySet<>(); + final int depth = in.getDepth(); + while (XmlUtils.nextElementWithin(in, depth)) { + if (TAG_BLOB_HANDLE.equals(in.getName())) { + blobHandle = BlobHandle.createFromXml(in); + } else if (TAG_COMMITTER.equals(in.getName())) { + final Committer committer = Committer.createFromXml(in); + if (committer != null) { + committers.add(committer); + } + } else if (TAG_LEASEE.equals(in.getName())) { + leasees.add(Leasee.createFromXml(in)); + } + } + + if (blobHandle == null) { + Slog.wtf(TAG, "blobHandle should be available"); + return null; + } + + final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId); + blobMetadata.addCommitters(committers); + blobMetadata.addLeasees(leasees); + return blobMetadata; + } + static final class Committer extends Accessor { public final BlobAccessMode blobAccessMode; @@ -161,6 +329,42 @@ class BlobMetadata { super(packageName, uid); this.blobAccessMode = blobAccessMode; } + + void dump(IndentingPrintWriter fout) { + fout.println("accessMode:"); + fout.increaseIndent(); + blobAccessMode.dump(fout); + fout.decreaseIndent(); + } + + void writeToXml(@NonNull XmlSerializer out) throws IOException { + XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); + XmlUtils.writeIntAttribute(out, ATTR_UID, uid); + + out.startTag(null, TAG_ACCESS_MODE); + blobAccessMode.writeToXml(out); + out.endTag(null, TAG_ACCESS_MODE); + } + + @Nullable + static Committer createFromXml(@NonNull XmlPullParser in) + throws XmlPullParserException, IOException { + final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); + final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); + + final int depth = in.getDepth(); + BlobAccessMode blobAccessMode = null; + while (XmlUtils.nextElementWithin(in, depth)) { + if (TAG_ACCESS_MODE.equals(in.getName())) { + blobAccessMode = BlobAccessMode.createFromXml(in); + } + } + if (blobAccessMode == null) { + Slog.wtf(TAG, "blobAccessMode should be available"); + return null; + } + return new Committer(packageName, uid, blobAccessMode); + } } static final class Leasee extends Accessor { @@ -176,6 +380,38 @@ class BlobMetadata { boolean isStillValid() { return expiryTimeMillis == 0 || expiryTimeMillis <= System.currentTimeMillis(); } + + void dump(Context context, IndentingPrintWriter fout) { + String desc = null; + try { + final Resources leaseeRes = context.getPackageManager() + .getResourcesForApplicationAsUser(packageName, UserHandle.getUserId(uid)); + desc = leaseeRes.getString(descriptionResId); + } catch (PackageManager.NameNotFoundException e) { + Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": " + + packageName, e); + desc = "<none>"; + } + fout.println("desc: " + desc); + fout.println("expiryMs: " + expiryTimeMillis); + } + + void writeToXml(@NonNull XmlSerializer out) throws IOException { + XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); + XmlUtils.writeIntAttribute(out, ATTR_UID, uid); + XmlUtils.writeIntAttribute(out, ATTR_DESCRIPTION_RES_ID, descriptionResId); + XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis); + } + + @NonNull + static Leasee createFromXml(@NonNull XmlPullParser in) throws IOException { + final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); + final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); + final int descriptionResId = XmlUtils.readIntAttribute(in, ATTR_DESCRIPTION_RES_ID); + final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME); + + return new Leasee(packageName, uid, descriptionResId, expiryTimeMillis); + } } static class Accessor { @@ -207,5 +443,10 @@ class BlobMetadata { public int hashCode() { return Objects.hash(packageName, uid); } + + @Override + public String toString() { + return "[" + packageName + ", " + uid + "]"; + } } } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index b9a4b17c53cc..20661c6f0833 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -25,6 +25,8 @@ import java.io.File; class BlobStoreConfig { public static final String TAG = "BlobStore"; + public static final int CURRENT_XML_VERSION = 1; + @Nullable public static File prepareBlobFile(long sessionId) { final File blobsDir = prepareBlobsDir(); @@ -62,6 +64,24 @@ class BlobStoreConfig { } @Nullable + public static File prepareSessionIndexFile() { + final File blobStoreRootDir = prepareBlobStoreRootDir(); + if (blobStoreRootDir == null) { + return null; + } + return new File(blobStoreRootDir, "sessions_index.xml"); + } + + @Nullable + public static File prepareBlobsIndexFile() { + final File blobsStoreRootDir = prepareBlobStoreRootDir(); + if (blobsStoreRootDir == null) { + return null; + } + return new File(blobsStoreRootDir, "blobs_index.xml"); + } + + @Nullable public static File prepareBlobStoreRootDir() { final File blobStoreRootDir = getBlobStoreRootDir(); if (!blobStoreRootDir.exists() && !blobStoreRootDir.mkdir()) { 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 9d60f861e8eb..fcc30e30dfaa 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -15,8 +15,19 @@ */ package com.android.server.blob; +import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR; import static android.app.blob.BlobStoreManager.COMMIT_RESULT_SUCCESS; - +import static android.app.blob.XmlTags.ATTR_VERSION; +import static android.app.blob.XmlTags.TAG_BLOB; +import static android.app.blob.XmlTags.TAG_BLOBS; +import static android.app.blob.XmlTags.TAG_SESSION; +import static android.app.blob.XmlTags.TAG_SESSIONS; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; +import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; +import static android.os.UserHandle.USER_NULL; + +import static com.android.server.blob.BlobStoreConfig.CURRENT_XML_VERSION; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; @@ -28,32 +39,58 @@ import android.annotation.CurrentTimeSecondsLong; import android.annotation.IdRes; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.blob.BlobHandle; import android.app.blob.IBlobStoreManager; import android.app.blob.IBlobStoreSession; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteCallback; +import android.os.SystemClock; import android.os.UserHandle; +import android.os.UserManagerInternal; import android.util.ArrayMap; +import android.util.AtomicFile; import android.util.ExceptionUtils; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; +import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; +import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.Watchdog; +import com.android.server.blob.BlobMetadata.Committer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; /** * Service responsible for maintaining and facilitating access to data blobs published by apps. @@ -96,14 +133,34 @@ public class BlobStoreManagerService extends SystemService { publishBinderService(Context.BLOB_STORE_SERVICE, new Stub()); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + registerReceivers(); } + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + synchronized (mBlobsLock) { + final SparseArray<SparseArray<String>> allPackages = getAllPackages(); + readBlobSessionsLocked(allPackages); + readBlobsInfoLocked(allPackages); + } + } + } @GuardedBy("mBlobsLock") private long generateNextSessionIdLocked() { return ++mCurrentMaxSessionId; } + private void registerReceivers() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); + intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL, + intentFilter, null, mHandler); + } + @GuardedBy("mBlobsLock") private LongSparseArray<BlobStoreSession> getUserSessionsLocked(int userId) { LongSparseArray<BlobStoreSession> userSessions = mSessions.get(userId); @@ -133,7 +190,7 @@ public class BlobStoreManagerService extends SystemService { sessionId, blobHandle, callingUid, callingPackage, mSessionStateChangeListener); getUserSessionsLocked(UserHandle.getUserId(callingUid)).put(sessionId, session); - // TODO: persist sessions data + writeBlobSessionsAsync(); return sessionId; } } @@ -160,7 +217,8 @@ public class BlobStoreManagerService extends SystemService { callingUid, callingPackage); session.open(); session.abandon(); - // TODO: persist sessions data + + writeBlobSessionsAsync(); } } @@ -194,7 +252,7 @@ public class BlobStoreManagerService extends SystemService { } blobMetadata.addLeasee(callingPackage, callingUid, descriptionResId, leaseExpiryTimeMillis); - // TODO: persist blobs data + writeBlobsInfoAsync(); } } @@ -209,6 +267,7 @@ public class BlobStoreManagerService extends SystemService { + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } blobMetadata.removeLeasee(callingPackage, callingUid); + writeBlobsInfoAsync(); } } @@ -241,18 +300,25 @@ public class BlobStoreManagerService extends SystemService { session.verifyBlobData(); break; case STATE_VERIFIED_VALID: - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = - getUserBlobsLocked(UserHandle.getUserId(session.ownerUid)); + final int userId = UserHandle.getUserId(session.ownerUid); + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId); BlobMetadata blob = userBlobs.get(session.blobHandle); if (blob == null) { blob = new BlobMetadata(mContext, - session.sessionId, session.blobHandle); + session.sessionId, session.blobHandle, userId); userBlobs.put(session.blobHandle, blob); } - blob.addCommitter(session.ownerPackageName, session.ownerUid, - session.getBlobAccessMode()); - // TODO: Persist blobs data. - session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS); + final Committer newCommitter = new Committer(session.ownerPackageName, + session.ownerUid, session.getBlobAccessMode()); + final Committer existingCommitter = blob.getExistingCommitter(newCommitter); + blob.addCommitter(newCommitter); + try { + writeBlobsInfoLocked(); + session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS); + } catch (Exception e) { + blob.addCommitter(existingCommitter); + session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); + } getUserSessionsLocked(UserHandle.getUserId(session.ownerUid)) .remove(session.sessionId); break; @@ -260,7 +326,330 @@ public class BlobStoreManagerService extends SystemService { Slog.wtf(TAG, "Invalid session state: " + stateToString(session.getState())); } - // TODO: Persist sessions data. + try { + writeBlobSessionsLocked(); + } catch (Exception e) { + // already logged, ignore. + } + } + } + + @GuardedBy("mBlobsLock") + private void writeBlobSessionsLocked() throws Exception { + final AtomicFile sessionsIndexFile = prepareSessionsIndexFile(); + if (sessionsIndexFile == null) { + Slog.wtf(TAG, "Error creating sessions index file"); + return; + } + FileOutputStream fos = null; + try { + fos = sessionsIndexFile.startWrite(SystemClock.uptimeMillis()); + final XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.startTag(null, TAG_SESSIONS); + XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION); + + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final LongSparseArray<BlobStoreSession> userSessions = + mSessions.valueAt(i); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + out.startTag(null, TAG_SESSION); + userSessions.valueAt(j).writeToXml(out); + out.endTag(null, TAG_SESSION); + } + } + + out.endTag(null, TAG_SESSIONS); + out.endDocument(); + sessionsIndexFile.finishWrite(fos); + } catch (Exception e) { + sessionsIndexFile.failWrite(fos); + Slog.wtf(TAG, "Error writing sessions data", e); + throw e; + } + } + + @GuardedBy("mBlobsLock") + private void readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages) { + if (!BlobStoreConfig.getBlobStoreRootDir().exists()) { + return; + } + final AtomicFile sessionsIndexFile = prepareSessionsIndexFile(); + if (sessionsIndexFile == null) { + Slog.wtf(TAG, "Error creating sessions index file"); + return; + } + + mSessions.clear(); + try (FileInputStream fis = sessionsIndexFile.openRead()) { + final XmlPullParser in = Xml.newPullParser(); + in.setInput(fis, StandardCharsets.UTF_8.name()); + XmlUtils.beginDocument(in, TAG_SESSIONS); + while (true) { + XmlUtils.nextElement(in); + if (in.getEventType() == XmlPullParser.END_DOCUMENT) { + break; + } + + if (TAG_SESSION.equals(in.getName())) { + final BlobStoreSession session = BlobStoreSession.createFromXml( + in, mContext, mSessionStateChangeListener); + if (session == null) { + continue; + } + final SparseArray<String> userPackages = allPackages.get( + UserHandle.getUserId(session.ownerUid)); + if (userPackages != null + && session.ownerPackageName.equals( + userPackages.get(session.ownerUid))) { + getUserSessionsLocked(UserHandle.getUserId(session.ownerUid)).put( + session.sessionId, session); + } else { + // Unknown package or the session data does not belong to this package. + session.getSessionFile().delete(); + } + mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.sessionId); + } + } + } catch (Exception e) { + Slog.wtf(TAG, "Error reading sessions data", e); + } + } + + @GuardedBy("mBlobsLock") + private void writeBlobsInfoLocked() throws Exception { + final AtomicFile blobsIndexFile = prepareBlobsIndexFile(); + if (blobsIndexFile == null) { + Slog.wtf(TAG, "Error creating blobs index file"); + return; + } + FileOutputStream fos = null; + try { + fos = blobsIndexFile.startWrite(SystemClock.uptimeMillis()); + final XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.startTag(null, TAG_BLOBS); + XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION); + + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { + out.startTag(null, TAG_BLOB); + userBlobs.valueAt(j).writeToXml(out); + out.endTag(null, TAG_BLOB); + } + } + + out.endTag(null, TAG_BLOBS); + out.endDocument(); + blobsIndexFile.finishWrite(fos); + } catch (Exception e) { + blobsIndexFile.failWrite(fos); + Slog.wtf(TAG, "Error writing blobs data", e); + throw e; + } + } + + @GuardedBy("mBlobsLock") + private void readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages) { + if (!BlobStoreConfig.getBlobStoreRootDir().exists()) { + return; + } + final AtomicFile blobsIndexFile = prepareBlobsIndexFile(); + if (blobsIndexFile == null) { + Slog.wtf(TAG, "Error creating blobs index file"); + return; + } + + mBlobsMap.clear(); + try (FileInputStream fis = blobsIndexFile.openRead()) { + final XmlPullParser in = Xml.newPullParser(); + in.setInput(fis, StandardCharsets.UTF_8.name()); + XmlUtils.beginDocument(in, TAG_BLOBS); + while (true) { + XmlUtils.nextElement(in); + if (in.getEventType() == XmlPullParser.END_DOCUMENT) { + break; + } + + if (TAG_BLOB.equals(in.getName())) { + final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in); + final SparseArray<String> userPackages = allPackages.get(blobMetadata.userId); + if (userPackages == null) { + blobMetadata.getBlobFile().delete(); + } else { + getUserBlobsLocked(blobMetadata.userId).put( + blobMetadata.blobHandle, blobMetadata); + blobMetadata.removeInvalidCommitters(userPackages); + blobMetadata.removeInvalidLeasees(userPackages); + } + mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.blobId); + } + } + } catch (Exception e) { + Slog.wtf(TAG, "Error reading blobs data", e); + } + } + + private void writeBlobsInfo() { + synchronized (mBlobsLock) { + try { + writeBlobsInfoLocked(); + } catch (Exception e) { + // Already logged, ignore + } + } + } + + private void writeBlobsInfoAsync() { + mHandler.post(PooledLambda.obtainRunnable( + BlobStoreManagerService::writeBlobsInfo, + BlobStoreManagerService.this).recycleOnUse()); + } + + private void writeBlobSessions() { + synchronized (mBlobsLock) { + try { + writeBlobSessionsLocked(); + } catch (Exception e) { + // Already logged, ignore + } + } + } + + private void writeBlobSessionsAsync() { + mHandler.post(PooledLambda.obtainRunnable( + BlobStoreManagerService::writeBlobSessions, + BlobStoreManagerService.this).recycleOnUse()); + } + + private int getPackageUid(String packageName, int userId) { + final int uid = mPackageManagerInternal.getPackageUid( + packageName, + MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES, + userId); + return uid; + } + + private SparseArray<SparseArray<String>> getAllPackages() { + final SparseArray<SparseArray<String>> allPackages = new SparseArray<>(); + final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds(); + for (int userId : allUsers) { + final SparseArray<String> userPackages = new SparseArray<>(); + allPackages.put(userId, userPackages); + final List<ApplicationInfo> applicationInfos = mPackageManagerInternal + .getInstalledApplications( + MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE + | MATCH_UNINSTALLED_PACKAGES, + userId, Process.myUid()); + for (int i = 0, count = applicationInfos.size(); i < count; ++i) { + final ApplicationInfo applicationInfo = applicationInfos.get(i); + userPackages.put(applicationInfo.uid, applicationInfo.packageName); + } + } + return allPackages; + } + + AtomicFile prepareSessionsIndexFile() { + final File file = BlobStoreConfig.prepareSessionIndexFile(); + if (file == null) { + return null; + } + return new AtomicFile(file, "session_index" /* commitLogTag */); + } + + AtomicFile prepareBlobsIndexFile() { + final File file = BlobStoreConfig.prepareBlobsIndexFile(); + if (file == null) { + return null; + } + return new AtomicFile(file, "blobs_index" /* commitLogTag */); + } + + private void handlePackageRemoved(String packageName, int uid) { + synchronized (mBlobsLock) { + // Clean up any pending sessions + final LongSparseArray<BlobStoreSession> userSessions = + getUserSessionsLocked(UserHandle.getUserId(uid)); + final ArrayList<Integer> indicesToRemove = new ArrayList<>(); + for (int i = 0, count = userSessions.size(); i < count; ++i) { + final BlobStoreSession session = userSessions.valueAt(i); + if (session.ownerUid == uid + && session.ownerPackageName.equals(packageName)) { + session.getSessionFile().delete(); + indicesToRemove.add(i); + } + } + for (int i = 0, count = indicesToRemove.size(); i < count; ++i) { + userSessions.removeAt(i); + } + + // Remove the package from the committer and leasee list + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = + getUserBlobsLocked(UserHandle.getUserId(uid)); + for (int i = 0, count = userBlobs.size(); i < count; ++i) { + final BlobMetadata blobMetadata = userBlobs.valueAt(i); + blobMetadata.removeCommitter(packageName, uid); + blobMetadata.removeLeasee(packageName, uid); + } + // TODO: clean-up blobs which doesn't have any active leases. + } + } + + private void handleUserRemoved(int userId) { + synchronized (mBlobsLock) { + final LongSparseArray<BlobStoreSession> userSessions = + mSessions.removeReturnOld(userId); + if (userSessions != null) { + for (int i = 0, count = userSessions.size(); i < count; ++i) { + final BlobStoreSession session = userSessions.valueAt(i); + session.getSessionFile().delete(); + } + } + + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = + mBlobsMap.removeReturnOld(userId); + if (userBlobs != null) { + for (int i = 0, count = userBlobs.size(); i < count; ++i) { + final BlobMetadata blobMetadata = userBlobs.valueAt(i); + blobMetadata.getBlobFile().delete(); + } + } + } + } + + private class PackageChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case Intent.ACTION_PACKAGE_FULLY_REMOVED: + case Intent.ACTION_PACKAGE_DATA_CLEARED: + final String packageName = intent.getData().getSchemeSpecificPart(); + if (packageName == null) { + Slog.wtf(TAG, "Package name is missing in the intent: " + intent); + return; + } + final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + if (uid == -1) { + Slog.wtf(TAG, "uid is missing in the intent: " + intent); + return; + } + handlePackageRemoved(packageName, uid); + break; + case Intent.ACTION_USER_REMOVED: + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, + USER_NULL); + if (userId == USER_NULL) { + Slog.wtf(TAG, "userId is missing in the intent: " + intent); + return; + } + handleUserRemoved(userId); + break; + default: + Slog.wtf(TAG, "Received unknown intent: " + intent); + } } } @@ -341,6 +730,8 @@ public class BlobStoreManagerService extends SystemService { @CurrentTimeSecondsLong long leaseTimeoutSecs, @NonNull String packageName) { Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); Preconditions.checkNotNull(packageName, "packageName must not be null"); + Preconditions.checkArgumentPositive(descriptionResId, + "descriptionResId must be positive; value=" + descriptionResId); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -360,5 +751,62 @@ public class BlobStoreManagerService extends SystemService { releaseLeaseInternal(blobHandle, callingUid, packageName); } + + @Override + public void waitForIdle(@NonNull RemoteCallback remoteCallback) { + Preconditions.checkNotNull(remoteCallback, "remoteCallback must not be null"); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, + "Caller is not allowed to call this; caller=" + Binder.getCallingUid()); + mHandler.post(PooledLambda.obtainRunnable(remoteCallback::sendResult, null) + .recycleOnUse()); + } + + @Override + 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; + + 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(); + } + + 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(); + } + fout.decreaseIndent(); + } + } + } } -} +}
\ No newline at end of file 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 612fd89ebbe0..7d1c16653383 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -16,6 +16,11 @@ package com.android.server.blob; import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR; +import static android.app.blob.XmlTags.ATTR_ID; +import static android.app.blob.XmlTags.ATTR_PACKAGE; +import static android.app.blob.XmlTags.ATTR_UID; +import static android.app.blob.XmlTags.TAG_ACCESS_MODE; +import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_RDWR; @@ -42,9 +47,15 @@ import android.util.ExceptionUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; +import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.File; import java.io.FileDescriptor; import java.io.IOException; @@ -224,7 +235,7 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { @Override @BytesLong public long getSize() { - return 0; + return getSessionFile().length(); } @Override @@ -366,8 +377,8 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { private void revokeAllFdsLocked() { for (int i = mRevocableFds.size() - 1; i >= 0; --i) { mRevocableFds.get(i).revoke(); - mRevocableFds.remove(i); } + mRevocableFds.clear(); } @GuardedBy("mSessionLock") @@ -418,4 +429,74 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { throw new SecurityException(ownerUid + " is not the session owner"); } } + + void dump(IndentingPrintWriter fout) { + synchronized (mSessionLock) { + fout.println("state: " + stateToString(mState)); + fout.println("ownerUid: " + ownerUid); + fout.println("ownerPkg: " + ownerPackageName); + + fout.println("blobHandle:"); + fout.increaseIndent(); + blobHandle.dump(fout); + fout.decreaseIndent(); + + fout.println("accessMode:"); + fout.increaseIndent(); + mBlobAccessMode.dump(fout); + fout.decreaseIndent(); + + fout.println("Open fds: #" + mRevocableFds.size()); + } + } + + void writeToXml(@NonNull XmlSerializer out) throws IOException { + synchronized (mSessionLock) { + XmlUtils.writeLongAttribute(out, ATTR_ID, sessionId); + XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, ownerPackageName); + XmlUtils.writeIntAttribute(out, ATTR_UID, ownerUid); + + out.startTag(null, TAG_BLOB_HANDLE); + blobHandle.writeToXml(out); + out.endTag(null, TAG_BLOB_HANDLE); + + out.startTag(null, TAG_ACCESS_MODE); + mBlobAccessMode.writeToXml(out); + out.endTag(null, TAG_ACCESS_MODE); + } + } + + @Nullable + static BlobStoreSession createFromXml(@NonNull XmlPullParser in, + @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener) + throws IOException, XmlPullParserException { + final int sessionId = XmlUtils.readIntAttribute(in, ATTR_ID); + final String ownerPackageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); + final int ownerUid = XmlUtils.readIntAttribute(in, ATTR_UID); + + final int depth = in.getDepth(); + BlobHandle blobHandle = null; + BlobAccessMode blobAccessMode = null; + while (XmlUtils.nextElementWithin(in, depth)) { + if (TAG_BLOB_HANDLE.equals(in.getName())) { + blobHandle = BlobHandle.createFromXml(in); + } else if (TAG_ACCESS_MODE.equals(in.getName())) { + blobAccessMode = BlobAccessMode.createFromXml(in); + } + } + + if (blobHandle == null) { + Slog.wtf(TAG, "blobHandle should be available"); + return null; + } + if (blobAccessMode == null) { + Slog.wtf(TAG, "blobAccessMode should be available"); + return null; + } + + final BlobStoreSession blobStoreSession = new BlobStoreSession(context, sessionId, + blobHandle, ownerUid, ownerPackageName, stateChangeListener); + blobStoreSession.mBlobAccessMode.allow(blobAccessMode); + return blobStoreSession; + } } |