diff options
author | Sudheer Shanka <sudheersai@google.com> | 2020-02-19 17:56:09 -0800 |
---|---|---|
committer | Sudheer Shanka <sudheersai@google.com> | 2020-02-24 05:52:33 +0000 |
commit | 364364ba411892217764212c4dc242ba1e6a95f0 (patch) | |
tree | 0d5963558814b39d68d97c14493d509ce43c6f26 /apex/blobstore | |
parent | 227ae85ddf99b7a28baff2166a5492c453e719f6 (diff) |
Add a limit on how much data an app can acquire a lease on.
+ Allow this limit to be modified using DeviceConfig properties.
+ Support DeviceConfig.getProperties() in TestableDeviceConfig.
Bug: 144155182
Test: atest --test-mapping apex/blobstore
Test: atest services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java
Test: atest services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
Change-Id: I28e67a27771be04ed1d37f367abd392505adc5c4
Diffstat (limited to 'apex/blobstore')
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 4a85a69b82d3..909e8984454a 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -239,7 +239,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 bcc1610435d9..0910e3392bb9 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; @@ -54,6 +65,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(); @@ -122,4 +203,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 91df1df0c5b5..d3c8ddc72553 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -184,7 +184,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); @@ -377,6 +379,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) { @@ -387,6 +394,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) { @@ -1218,8 +1237,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); @@ -1230,6 +1251,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 @@ -1242,14 +1271,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) { @@ -1268,18 +1299,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) { @@ -1316,11 +1370,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)) { @@ -1379,6 +1433,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]"); |