summaryrefslogtreecommitdiff
path: root/apex/blobstore
diff options
context:
space:
mode:
authorSudheer Shanka <sudheersai@google.com>2020-03-06 22:39:06 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-03-06 22:39:06 +0000
commitcd629dd39af8fd9fa10b590ce43bd7ff9dba52d3 (patch)
tree072a90f14b6f0feddb89dcb64354211c24ccbbe0 /apex/blobstore
parent6c725002b7c98334ece241d7c33620057c21a1ab (diff)
parent364364ba411892217764212c4dc242ba1e6a95f0 (diff)
Merge "Add a limit on how much data an app can acquire a lease on." into rvc-dev
Diffstat (limited to 'apex/blobstore')
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java2
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java98
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java84
3 files changed, 169 insertions, 15 deletions
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 888c3af8bbf7..dab4797b313f 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -242,7 +242,7 @@ class BlobMetadata {
return hasOtherLeasees(null, uid);
}
- private boolean isALeasee(@Nullable String packageName, int uid) {
+ boolean isALeasee(@Nullable String packageName, int uid) {
synchronized (mMetadataLock) {
// Check if the package is a leasee of the data blob.
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
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 6d31b2c30067..5e8ea7afaec7 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -15,12 +15,23 @@
*/
package com.android.server.blob;
+import static android.provider.DeviceConfig.NAMESPACE_BLOBSTORE;
+import static android.text.format.Formatter.FLAG_IEC_UNITS;
+import static android.text.format.Formatter.formatFileSize;
+import static android.util.TimeUtils.formatDuration;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.os.Environment;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.util.DataUnit;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.util.IndentingPrintWriter;
+
import java.io.File;
import java.util.concurrent.TimeUnit;
@@ -55,6 +66,76 @@ class BlobStoreConfig {
*/
public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7);
+ public static class DeviceConfigProperties {
+ /**
+ * Denotes how low the limit for the amount of data, that an app will be allowed to acquire
+ * a lease on, can be.
+ */
+ public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
+ "total_bytes_per_app_limit_floor";
+ public static final long DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
+ DataUnit.MEBIBYTES.toBytes(300); // 300 MiB
+ public static long TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
+ DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR;
+
+ /**
+ * Denotes the maximum amount of data an app can acquire a lease on, in terms of fraction
+ * of total disk space.
+ */
+ public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
+ "total_bytes_per_app_limit_fraction";
+ public static final float DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION = 0.01f;
+ public static float TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
+ DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION;
+
+ static void refresh(Properties properties) {
+ if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
+ return;
+ }
+ properties.getKeyset().forEach(key -> {
+ switch (key) {
+ case KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR:
+ TOTAL_BYTES_PER_APP_LIMIT_FLOOR = properties.getLong(key,
+ DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR);
+ break;
+ case KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION:
+ TOTAL_BYTES_PER_APP_LIMIT_FRACTION = properties.getFloat(key,
+ DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
+ break;
+ default:
+ Slog.wtf(TAG, "Unknown key in device config properties: " + key);
+ }
+ });
+ }
+
+ static void dump(IndentingPrintWriter fout, Context context) {
+ final String dumpFormat = "%s: [cur: %s, def: %s]";
+ fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
+ formatFileSize(context, TOTAL_BYTES_PER_APP_LIMIT_FLOOR, FLAG_IEC_UNITS),
+ formatFileSize(context, DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
+ FLAG_IEC_UNITS)));
+ fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
+ TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
+ DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION));
+ }
+ }
+
+ public static void initialize(Context context) {
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_BLOBSTORE,
+ context.getMainExecutor(),
+ properties -> DeviceConfigProperties.refresh(properties));
+ DeviceConfigProperties.refresh(DeviceConfig.getProperties(NAMESPACE_BLOBSTORE));
+ }
+
+ /**
+ * Returns the maximum amount of data that an app can acquire a lease on.
+ */
+ public static long getAppDataBytesLimit() {
+ final long totalBytesLimit = (long) (Environment.getDataSystemDirectory().getTotalSpace()
+ * DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
+ return Math.max(DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FLOOR, totalBytesLimit);
+ }
+
@Nullable
public static File prepareBlobFile(long sessionId) {
final File blobsDir = prepareBlobsDir();
@@ -123,4 +204,21 @@ class BlobStoreConfig {
public static File getBlobStoreRootDir() {
return new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME);
}
+
+ public static void dump(IndentingPrintWriter fout, Context context) {
+ fout.println("XML current version: " + XML_VERSION_CURRENT);
+
+ fout.println("Idle job ID: " + IDLE_JOB_ID);
+ fout.println("Idle job period: " + formatDuration(IDLE_JOB_PERIOD_MILLIS));
+
+ fout.println("Session expiry timeout: " + formatDuration(SESSION_EXPIRY_TIMEOUT_MILLIS));
+
+ fout.println("Total bytes per app limit: " + formatFileSize(context,
+ getAppDataBytesLimit(), FLAG_IEC_UNITS));
+
+ fout.println("Device config properties:");
+ fout.increaseIndent();
+ DeviceConfigProperties.dump(fout, context);
+ fout.decreaseIndent();
+ }
}
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 3116eb4c2407..7006781e4e4e 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -188,7 +188,9 @@ public class BlobStoreManagerService extends SystemService {
@Override
public void onBootPhase(int phase) {
- if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+ BlobStoreConfig.initialize(mContext);
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mBlobsLock) {
final SparseArray<SparseArray<String>> allPackages = getAllPackages();
readBlobSessionsLocked(allPackages);
@@ -381,6 +383,11 @@ public class BlobStoreManagerService extends SystemService {
throw new IllegalArgumentException(
"Lease expiry cannot be later than blobs expiry time");
}
+ if (getTotalUsageBytesLocked(callingUid, callingPackage)
+ + blobMetadata.getSize() > BlobStoreConfig.getAppDataBytesLimit()) {
+ throw new IllegalStateException("Total amount of data with an active lease"
+ + " is exceeding the max limit");
+ }
blobMetadata.addLeasee(callingPackage, callingUid,
descriptionResId, description, leaseExpiryTimeMillis);
if (LOGV) {
@@ -391,6 +398,18 @@ public class BlobStoreManagerService extends SystemService {
}
}
+ @VisibleForTesting
+ @GuardedBy("mBlobsLock")
+ long getTotalUsageBytesLocked(int callingUid, String callingPackage) {
+ final AtomicLong totalBytes = new AtomicLong(0);
+ forEachBlobInUser((blobMetadata) -> {
+ if (blobMetadata.isALeasee(callingPackage, callingUid)) {
+ totalBytes.getAndAdd(blobMetadata.getSize());
+ }
+ }, UserHandle.getUserId(callingUid));
+ return totalBytes.get();
+ }
+
private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
String callingPackage) {
synchronized (mBlobsLock) {
@@ -1236,8 +1255,10 @@ public class BlobStoreManagerService extends SystemService {
}
synchronized (mBlobsLock) {
- fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
- fout.println();
+ if (dumpArgs.shouldDumpAllSections()) {
+ fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
+ fout.println();
+ }
if (dumpArgs.shouldDumpSessions()) {
dumpSessionsLocked(fout, dumpArgs);
@@ -1248,6 +1269,14 @@ public class BlobStoreManagerService extends SystemService {
fout.println();
}
}
+
+ if (dumpArgs.shouldDumpConfig()) {
+ fout.println("BlobStore config:");
+ fout.increaseIndent();
+ BlobStoreConfig.dump(fout, mContext);
+ fout.decreaseIndent();
+ fout.println();
+ }
}
@Override
@@ -1260,14 +1289,16 @@ public class BlobStoreManagerService extends SystemService {
}
static final class DumpArgs {
+ private static final int FLAG_DUMP_SESSIONS = 1 << 0;
+ private static final int FLAG_DUMP_BLOBS = 1 << 1;
+ private static final int FLAG_DUMP_CONFIG = 1 << 2;
+
+ private int mSelectedSectionFlags;
private boolean mDumpFull;
private final ArrayList<String> mDumpPackages = new ArrayList<>();
private final ArrayList<Integer> mDumpUids = new ArrayList<>();
private final ArrayList<Integer> mDumpUserIds = new ArrayList<>();
private final ArrayList<Long> mDumpBlobIds = new ArrayList<>();
- private boolean mDumpOnlySelectedSections;
- private boolean mDumpSessions;
- private boolean mDumpBlobs;
private boolean mDumpHelp;
public boolean shouldDumpSession(String packageName, int uid, long blobId) {
@@ -1286,18 +1317,41 @@ public class BlobStoreManagerService extends SystemService {
return true;
}
+ public boolean shouldDumpAllSections() {
+ return mSelectedSectionFlags == 0;
+ }
+
+ public void allowDumpSessions() {
+ mSelectedSectionFlags |= FLAG_DUMP_SESSIONS;
+ }
+
public boolean shouldDumpSessions() {
- if (!mDumpOnlySelectedSections) {
+ if (shouldDumpAllSections()) {
return true;
}
- return mDumpSessions;
+ return (mSelectedSectionFlags & FLAG_DUMP_SESSIONS) != 0;
+ }
+
+ public void allowDumpBlobs() {
+ mSelectedSectionFlags |= FLAG_DUMP_BLOBS;
}
public boolean shouldDumpBlobs() {
- if (!mDumpOnlySelectedSections) {
+ if (shouldDumpAllSections()) {
+ return true;
+ }
+ return (mSelectedSectionFlags & FLAG_DUMP_BLOBS) != 0;
+ }
+
+ public void allowDumpConfig() {
+ mSelectedSectionFlags |= FLAG_DUMP_CONFIG;
+ }
+
+ public boolean shouldDumpConfig() {
+ if (shouldDumpAllSections()) {
return true;
}
- return mDumpBlobs;
+ return (mSelectedSectionFlags & FLAG_DUMP_CONFIG) != 0;
}
public boolean shouldDumpBlob(long blobId) {
@@ -1334,11 +1388,11 @@ public class BlobStoreManagerService extends SystemService {
dumpArgs.mDumpFull = true;
}
} else if ("--sessions".equals(opt)) {
- dumpArgs.mDumpOnlySelectedSections = true;
- dumpArgs.mDumpSessions = true;
+ dumpArgs.allowDumpSessions();
} else if ("--blobs".equals(opt)) {
- dumpArgs.mDumpOnlySelectedSections = true;
- dumpArgs.mDumpBlobs = true;
+ dumpArgs.allowDumpBlobs();
+ } else if ("--config".equals(opt)) {
+ dumpArgs.allowDumpConfig();
} else if ("--package".equals(opt) || "-p".equals(opt)) {
dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName"));
} else if ("--uid".equals(opt) || "-u".equals(opt)) {
@@ -1397,6 +1451,8 @@ public class BlobStoreManagerService extends SystemService {
printWithIndent(pw, "Dump only the sessions info");
pw.println("--blobs");
printWithIndent(pw, "Dump only the committed blobs info");
+ pw.println("--config");
+ printWithIndent(pw, "Dump only the config values");
pw.println("--package | -p [package-name]");
printWithIndent(pw, "Dump blobs info associated with the given package");
pw.println("--uid | -u [uid]");