summaryrefslogtreecommitdiff
path: root/apex/blobstore
diff options
context:
space:
mode:
authorSudheer Shanka <sudheersai@google.com>2020-01-24 22:42:38 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-01-24 22:42:38 +0000
commit4266a1994baaab3343b444af1a993e6d1942aa58 (patch)
treed4f530a378db46321533dfd6f327d75fe9d0d2db /apex/blobstore
parent0111ea772c3c0c903800b216129a3bbfccd5126f (diff)
parent22f0b166e89b7b8ad8e82a4e2dd359e692e0ae84 (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')
-rw-r--r--apex/blobstore/framework/java/android/app/blob/BlobHandle.java47
-rw-r--r--apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java23
-rw-r--r--apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl3
-rw-r--r--apex/blobstore/framework/java/android/app/blob/XmlTags.java55
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java74
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java259
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java20
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java474
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java85
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;
+ }
}