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

Commit 340dc0a9 authored by Sudheer Shanka's avatar Sudheer Shanka Committed by Automerger Merge Worker
Browse files

Merge "Delete a blob after the last lease of it is released." into rvc-dev am:...

Merge "Delete a blob after the last lease of it is released." into rvc-dev am: 0c0abbf7 am: 518f6623

Change-Id: I217b5376bb70e2d6d1cb7c4a576e0311fa3870ca
parents 494bcd9d 518f6623
Loading
Loading
Loading
Loading
+46 −18
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import static android.system.OsConstants.O_RDONLY;
import static com.android.server.blob.BlobStoreConfig.TAG;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed;
import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
import static com.android.server.blob.BlobStoreUtils.getPackageResources;

@@ -227,6 +228,35 @@ class BlobMetadata {
        return false;
    }

    boolean isACommitter(@NonNull String packageName, int uid) {
        synchronized (mMetadataLock) {
            return isAnAccessor(mCommitters, packageName, uid);
        }
    }

    boolean isALeasee(@Nullable String packageName, int uid) {
        synchronized (mMetadataLock) {
            return isAnAccessor(mLeasees, packageName, uid);
        }
    }

    private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors,
            @Nullable String packageName, int uid) {
        // Check if the package is an accessor of the data blob.
        for (int i = 0, size = accessors.size(); i < size; ++i) {
            final Accessor accessor = accessors.valueAt(i);
            if (packageName != null && uid != INVALID_UID
                    && accessor.equals(packageName, uid)) {
                return true;
            } else if (packageName != null && accessor.packageName.equals(packageName)) {
                return true;
            } else if (uid != INVALID_UID && accessor.uid == uid) {
                return true;
            }
        }
        return false;
    }

    boolean isALeasee(@NonNull String packageName) {
        return isALeasee(packageName, INVALID_UID);
    }
@@ -243,24 +273,6 @@ class BlobMetadata {
        return hasOtherLeasees(null, 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) {
                final Leasee leasee = mLeasees.valueAt(i);
                if (packageName != null && uid != INVALID_UID
                        && leasee.equals(packageName, uid)) {
                    return true;
                } else if (packageName != null && leasee.packageName.equals(packageName)) {
                    return true;
                } else if (uid != INVALID_UID && leasee.uid == uid) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean hasOtherLeasees(@Nullable String packageName, int uid) {
        synchronized (mMetadataLock) {
            if (mCommitters.size() > 1 || mLeasees.size() > 1) {
@@ -355,6 +367,22 @@ class BlobMetadata {
        return revocableFd.getRevocableFileDescriptor();
    }

    boolean shouldBeDeleted(boolean respectLeaseWaitTime) {
        // Expired data blobs
        if (getBlobHandle().isExpired()) {
            return true;
        }

        // Blobs with no active leases
        // TODO: Track commit time instead of using last modified time.
        if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsed(getBlobFile().lastModified()))
                && !hasLeases()) {
            return true;
        }

        return false;
    }

    void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) {
        fout.println("blobHandle:");
        fout.increaseIndent();
+27 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.provider.DeviceConfig.Properties;
import android.util.DataUnit;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;

import com.android.internal.util.IndentingPrintWriter;

@@ -88,6 +89,17 @@ class BlobStoreConfig {
        public static float TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
                DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION;

        /**
         * Denotes the duration from the time a blob is committed that we wait for a lease to
         * be acquired before deciding to delete the blob for having no leases.
         */
        public static final String KEY_LEASE_ACQUISITION_WAIT_DURATION_MS =
                "lease_acquisition_wait_time_ms";
        public static final long DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS =
                TimeUnit.HOURS.toMillis(6);
        public static long LEASE_ACQUISITION_WAIT_DURATION_MS =
                DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS;

        static void refresh(Properties properties) {
            if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
                return;
@@ -102,6 +114,10 @@ class BlobStoreConfig {
                        TOTAL_BYTES_PER_APP_LIMIT_FRACTION = properties.getFloat(key,
                                DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
                        break;
                    case KEY_LEASE_ACQUISITION_WAIT_DURATION_MS:
                        LEASE_ACQUISITION_WAIT_DURATION_MS = properties.getLong(key,
                                DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS);
                        break;
                    default:
                        Slog.wtf(TAG, "Unknown key in device config properties: " + key);
                }
@@ -117,6 +133,9 @@ class BlobStoreConfig {
            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));
            fout.println(String.format(dumpFormat, KEY_LEASE_ACQUISITION_WAIT_DURATION_MS,
                    TimeUtils.formatDuration(LEASE_ACQUISITION_WAIT_DURATION_MS),
                    TimeUtils.formatDuration(DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS)));
        }
    }

@@ -136,6 +155,14 @@ class BlobStoreConfig {
        return Math.max(DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FLOOR, totalBytesLimit);
    }

    /**
     * Returns whether the wait time for lease acquisition for a blob has elapsed.
     */
    public static boolean hasLeaseWaitTimeElapsed(long commitTimeMs) {
        return commitTimeMs + DeviceConfigProperties.LEASE_ACQUISITION_WAIT_DURATION_MS
                < System.currentTimeMillis();
    }

    @Nullable
    public static File prepareBlobFile(long sessionId) {
        final File blobsDir = prepareBlobsDir();
+27 −28
Original line number Diff line number Diff line
@@ -424,8 +424,9 @@ public class BlobStoreManagerService extends SystemService {
    private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
            String callingPackage) {
        synchronized (mBlobsLock) {
            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
                    .get(blobHandle);
            final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
                    getUserBlobsLocked(UserHandle.getUserId(callingUid));
            final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
            if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
                    callingPackage, callingUid)) {
                throw new SecurityException("Caller not allowed to access " + blobHandle
@@ -436,6 +437,10 @@ public class BlobStoreManagerService extends SystemService {
                Slog.v(TAG, "Released lease on " + blobHandle
                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
            }
            if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
                deleteBlobLocked(blobMetadata);
                userBlobs.remove(blobHandle);
            }
            writeBlobsInfoAsync();
        }
    }
@@ -863,12 +868,15 @@ public class BlobStoreManagerService extends SystemService {
                    getUserBlobsLocked(UserHandle.getUserId(uid));
            userBlobs.entrySet().removeIf(entry -> {
                final BlobMetadata blobMetadata = entry.getValue();
                final boolean isACommitter = blobMetadata.isACommitter(packageName, uid);
                if (isACommitter) {
                    blobMetadata.removeCommitter(packageName, uid);
                }
                blobMetadata.removeLeasee(packageName, uid);
                // Delete the blob if it doesn't have any active leases.
                if (!blobMetadata.hasLeases()) {
                    blobMetadata.getBlobFile().delete();
                    mActiveBlobIds.remove(blobMetadata.getBlobId());
                // Regardless of when the blob is committed, we need to delete
                // it if it was from the deleted package to ensure we delete all traces of it.
                if (blobMetadata.shouldBeDeleted(isACommitter /* respectLeaseWaitTime */)) {
                    deleteBlobLocked(blobMetadata);
                    return true;
                }
                return false;
@@ -899,8 +907,7 @@ public class BlobStoreManagerService extends SystemService {
            if (userBlobs != null) {
                for (int i = 0, count = userBlobs.size(); i < count; ++i) {
                    final BlobMetadata blobMetadata = userBlobs.valueAt(i);
                    blobMetadata.getBlobFile().delete();
                    mActiveBlobIds.remove(blobMetadata.getBlobId());
                    deleteBlobLocked(blobMetadata);
                }
            }
            if (LOGV) {
@@ -938,27 +945,14 @@ public class BlobStoreManagerService extends SystemService {
        for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
            userBlobs.entrySet().removeIf(entry -> {
                final BlobHandle blobHandle = entry.getKey();
                final BlobMetadata blobMetadata = entry.getValue();
                boolean shouldRemove = false;

                // Cleanup expired data blobs.
                if (blobHandle.isExpired()) {
                    shouldRemove = true;
                }

                // Cleanup blobs with no active leases.
                // TODO: Exclude blobs which were just committed.
                if (!blobMetadata.hasLeases()) {
                    shouldRemove = true;
                }

                if (shouldRemove) {
                    blobMetadata.getBlobFile().delete();
                    mActiveBlobIds.remove(blobMetadata.getBlobId());
                if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
                    deleteBlobLocked(blobMetadata);
                    deletedBlobIds.add(blobMetadata.getBlobId());
                    return true;
                }
                return shouldRemove;
                return false;
            });
        }
        writeBlobsInfoAsync();
@@ -995,6 +989,12 @@ public class BlobStoreManagerService extends SystemService {
        writeBlobSessionsAsync();
    }

    @GuardedBy("mBlobsLock")
    private void deleteBlobLocked(BlobMetadata blobMetadata) {
        blobMetadata.getBlobFile().delete();
        mActiveBlobIds.remove(blobMetadata.getBlobId());
    }

    void runClearAllSessions(@UserIdInt int userId) {
        synchronized (mBlobsLock) {
            if (userId == UserHandle.USER_ALL) {
@@ -1024,9 +1024,8 @@ public class BlobStoreManagerService extends SystemService {
            if (blobMetadata == null) {
                return;
            }
            blobMetadata.getBlobFile().delete();
            deleteBlobLocked(blobMetadata);
            userBlobs.remove(blobHandle);
            mActiveBlobIds.remove(blobMetadata.getBlobId());
            writeBlobsInfoAsync();
        }
    }
+38 −11
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.server.blob;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -22,6 +23,8 @@ import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MIL

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

@@ -89,6 +92,7 @@ public class BlobStoreManagerServiceTest {
        doReturn(mBlobsDir).when(() -> BlobStoreConfig.getBlobsDir());
        doReturn(true).when(mBlobsDir).exists();
        doReturn(new File[0]).when(mBlobsDir).listFiles();
        doReturn(true).when(() -> BlobStoreConfig.hasLeaseWaitTimeElapsed(anyLong()));

        mContext = InstrumentationRegistry.getTargetContext();
        mHandler = new TestHandler(Looper.getMainLooper());
@@ -138,19 +142,32 @@ public class BlobStoreManagerServiceTest {
        final long blobId1 = 978;
        final File blobFile1 = mock(File.class);
        final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
                "label1", System.currentTimeMillis(), "tag1");
        final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, true);
                "label1", System.currentTimeMillis() + 10000, "tag1");
        final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1,
                blobHandle1, true /* hasLeases */);
        doReturn(true).when(blobMetadata1).isACommitter(TEST_PKG1, TEST_UID1);
        mUserBlobs.put(blobHandle1, blobMetadata1);

        final long blobId2 = 347;
        final File blobFile2 = mock(File.class);
        final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
                "label2", System.currentTimeMillis(), "tag2");
        final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, false);
                "label2", System.currentTimeMillis() + 20000, "tag2");
        final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2,
                blobHandle2, false /* hasLeases */);
        doReturn(false).when(blobMetadata2).isACommitter(TEST_PKG1, TEST_UID1);
        mUserBlobs.put(blobHandle2, blobMetadata2);

        final long blobId3 = 49875;
        final File blobFile3 = mock(File.class);
        final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(),
                "label3", System.currentTimeMillis() - 1000, "tag3");
        final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3,
                blobHandle3, true /* hasLeases */);
        doReturn(true).when(blobMetadata3).isACommitter(TEST_PKG1, TEST_UID1);
        mUserBlobs.put(blobHandle3, blobMetadata3);

        mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4,
                blobId1, blobId2);
                blobId1, blobId2, blobId3);

        // Invoke test method
        mService.handlePackageRemoved(TEST_PKG1, TEST_UID1);
@@ -170,20 +187,24 @@ public class BlobStoreManagerServiceTest {
        // Verify blobs are removed
        verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1);
        verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1);
        verify(blobMetadata2).removeCommitter(TEST_PKG1, TEST_UID1);
        verify(blobMetadata2, never()).removeCommitter(TEST_PKG1, TEST_UID1);
        verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1);
        verify(blobMetadata3).removeCommitter(TEST_PKG1, TEST_UID1);
        verify(blobMetadata3).removeLeasee(TEST_PKG1, TEST_UID1);

        verify(blobFile1, never()).delete();
        verify(blobFile2).delete();
        verify(blobFile3).delete();

        assertThat(mUserBlobs.size()).isEqualTo(1);
        assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
        assertThat(mUserBlobs.get(blobHandle2)).isNull();
        assertThat(mUserBlobs.get(blobHandle3)).isNull();

        assertThat(mService.getActiveIdsForTest()).containsExactly(
                sessionId2, sessionId3, blobId1);
        assertThat(mService.getKnownIdsForTest()).containsExactly(
                sessionId1, sessionId2, sessionId3, sessionId4, blobId1, blobId2);
                sessionId1, sessionId2, sessionId3, sessionId4, blobId1, blobId2, blobId3);
    }

    @Test
@@ -269,21 +290,24 @@ public class BlobStoreManagerServiceTest {
        final File blobFile1 = mock(File.class);
        final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
                "label1", System.currentTimeMillis() - 2000, "tag1");
        final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, true);
        final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, blobHandle1,
                true /* hasLeases */);
        mUserBlobs.put(blobHandle1, blobMetadata1);

        final long blobId2 = 78974;
        final File blobFile2 = mock(File.class);
        final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
                "label2", System.currentTimeMillis() + 30000, "tag2");
        final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, true);
        final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, blobHandle2,
                true /* hasLeases */);
        mUserBlobs.put(blobHandle2, blobMetadata2);

        final long blobId3 = 97;
        final File blobFile3 = mock(File.class);
        final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(),
                "label3", System.currentTimeMillis() + 4400000, "tag3");
        final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, false);
        final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, blobHandle3,
                false /* hasLeases */);
        mUserBlobs.put(blobHandle3, blobMetadata3);

        mService.addActiveIdsForTest(blobId1, blobId2, blobId3);
@@ -350,11 +374,14 @@ public class BlobStoreManagerServiceTest {
        return session;
    }

    private BlobMetadata createBlobMetadataMock(long blobId, File blobFile, boolean hasLeases) {
    private BlobMetadata createBlobMetadataMock(long blobId, File blobFile,
            BlobHandle blobHandle, boolean hasLeases) {
        final BlobMetadata blobMetadata = mock(BlobMetadata.class);
        doReturn(blobId).when(blobMetadata).getBlobId();
        doReturn(blobFile).when(blobMetadata).getBlobFile();
        doReturn(hasLeases).when(blobMetadata).hasLeases();
        doReturn(blobHandle).when(blobMetadata).getBlobHandle();
        doCallRealMethod().when(blobMetadata).shouldBeDeleted(anyBoolean());
        return blobMetadata;
    }

+1 −1
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ public class DummyBlobData {
    byte[] mFileDigest;
    long mExpiryTimeMs;

    public DummyBlobData(Builder builder) {
    private DummyBlobData(Builder builder) {
        mRandom = new Random(builder.getRandomSeed());
        mFile = new File(builder.getContext().getFilesDir(), builder.getFileName());
        mFileSize = builder.getFileSize();
Loading