1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.blob; 17 18 import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR; 19 import static android.app.blob.BlobStoreManager.COMMIT_RESULT_SUCCESS; 20 import static android.app.blob.XmlTags.ATTR_VERSION; 21 import static android.app.blob.XmlTags.TAG_BLOB; 22 import static android.app.blob.XmlTags.TAG_BLOBS; 23 import static android.app.blob.XmlTags.TAG_SESSION; 24 import static android.app.blob.XmlTags.TAG_SESSIONS; 25 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; 26 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 27 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; 28 import static android.os.UserHandle.USER_CURRENT; 29 import static android.os.UserHandle.USER_NULL; 30 31 import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID; 32 import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE; 33 import static com.android.server.blob.BlobStoreConfig.LOGV; 34 import static com.android.server.blob.BlobStoreConfig.TAG; 35 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS; 36 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; 37 import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs; 38 import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs; 39 import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions; 40 import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs; 41 import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs; 42 import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; 43 import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; 44 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID; 45 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID; 46 import static com.android.server.blob.BlobStoreSession.stateToString; 47 import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId; 48 import static com.android.server.blob.BlobStoreUtils.getPackageResources; 49 50 import android.annotation.CurrentTimeSecondsLong; 51 import android.annotation.IdRes; 52 import android.annotation.IntRange; 53 import android.annotation.NonNull; 54 import android.annotation.Nullable; 55 import android.annotation.UserIdInt; 56 import android.app.ActivityManager; 57 import android.app.ActivityManagerInternal; 58 import android.app.StatsManager; 59 import android.app.blob.BlobHandle; 60 import android.app.blob.BlobInfo; 61 import android.app.blob.IBlobStoreManager; 62 import android.app.blob.IBlobStoreSession; 63 import android.app.blob.LeaseInfo; 64 import android.content.BroadcastReceiver; 65 import android.content.Context; 66 import android.content.Intent; 67 import android.content.IntentFilter; 68 import android.content.pm.ApplicationInfo; 69 import android.content.pm.PackageManagerInternal; 70 import android.content.pm.PackageStats; 71 import android.content.res.ResourceId; 72 import android.content.res.Resources; 73 import android.os.Binder; 74 import android.os.Handler; 75 import android.os.HandlerThread; 76 import android.os.LimitExceededException; 77 import android.os.ParcelFileDescriptor; 78 import android.os.ParcelableException; 79 import android.os.Process; 80 import android.os.RemoteCallback; 81 import android.os.SystemClock; 82 import android.os.UserHandle; 83 import android.util.ArrayMap; 84 import android.util.ArraySet; 85 import android.util.AtomicFile; 86 import android.util.ExceptionUtils; 87 import android.util.IndentingPrintWriter; 88 import android.util.LongSparseArray; 89 import android.util.Slog; 90 import android.util.SparseArray; 91 import android.util.StatsEvent; 92 import android.util.Xml; 93 94 import com.android.internal.annotations.GuardedBy; 95 import com.android.internal.annotations.VisibleForTesting; 96 import com.android.internal.os.BackgroundThread; 97 import com.android.internal.util.CollectionUtils; 98 import com.android.internal.util.DumpUtils; 99 import com.android.internal.util.FastXmlSerializer; 100 import com.android.internal.util.FrameworkStatsLog; 101 import com.android.internal.util.Preconditions; 102 import com.android.internal.util.XmlUtils; 103 import com.android.internal.util.function.pooled.PooledLambda; 104 import com.android.server.LocalManagerRegistry; 105 import com.android.server.LocalServices; 106 import com.android.server.ServiceThread; 107 import com.android.server.SystemService; 108 import com.android.server.Watchdog; 109 import com.android.server.blob.BlobMetadata.Committer; 110 import com.android.server.pm.UserManagerInternal; 111 import com.android.server.usage.StorageStatsManagerLocal; 112 import com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter; 113 114 import org.xmlpull.v1.XmlPullParser; 115 import org.xmlpull.v1.XmlSerializer; 116 117 import java.io.File; 118 import java.io.FileDescriptor; 119 import java.io.FileInputStream; 120 import java.io.FileOutputStream; 121 import java.io.IOException; 122 import java.io.PrintWriter; 123 import java.lang.ref.WeakReference; 124 import java.nio.charset.StandardCharsets; 125 import java.security.SecureRandom; 126 import java.util.ArrayList; 127 import java.util.Arrays; 128 import java.util.List; 129 import java.util.Objects; 130 import java.util.Random; 131 import java.util.Set; 132 import java.util.concurrent.atomic.AtomicInteger; 133 import java.util.concurrent.atomic.AtomicLong; 134 import java.util.function.BiConsumer; 135 import java.util.function.Consumer; 136 import java.util.function.Function; 137 138 /** 139 * Service responsible for maintaining and facilitating access to data blobs published by apps. 140 */ 141 public class BlobStoreManagerService extends SystemService { 142 143 private final Object mBlobsLock = new Object(); 144 145 // Contains data of userId -> {sessionId -> {BlobStoreSession}}. 146 @GuardedBy("mBlobsLock") 147 private final SparseArray<LongSparseArray<BlobStoreSession>> mSessions = new SparseArray<>(); 148 149 @GuardedBy("mBlobsLock") 150 private long mCurrentMaxSessionId; 151 152 // Contains data of BlobHandle -> BlobMetadata. 153 @GuardedBy("mBlobsLock") 154 private final ArrayMap<BlobHandle, BlobMetadata> mBlobsMap = new ArrayMap<>(); 155 156 // Contains all ids that are currently in use. 157 @GuardedBy("mBlobsLock") 158 private final ArraySet<Long> mActiveBlobIds = new ArraySet<>(); 159 // Contains all ids that are currently in use and those that were in use but got deleted in the 160 // current boot session. 161 @GuardedBy("mBlobsLock") 162 private final ArraySet<Long> mKnownBlobIds = new ArraySet<>(); 163 164 // Random number generator for new session ids. 165 private final Random mRandom = new SecureRandom(); 166 167 private final Context mContext; 168 private final Handler mHandler; 169 private final Handler mBackgroundHandler; 170 private final Injector mInjector; 171 private final SessionStateChangeListener mSessionStateChangeListener = 172 new SessionStateChangeListener(); 173 174 private PackageManagerInternal mPackageManagerInternal; 175 private StatsManager mStatsManager; 176 private StatsPullAtomCallbackImpl mStatsCallbackImpl = new StatsPullAtomCallbackImpl(); 177 178 private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo; 179 private final Runnable mSaveSessionsRunnable = this::writeBlobSessions; 180 BlobStoreManagerService(Context context)181 public BlobStoreManagerService(Context context) { 182 this(context, new Injector()); 183 } 184 185 @VisibleForTesting BlobStoreManagerService(Context context, Injector injector)186 BlobStoreManagerService(Context context, Injector injector) { 187 super(context); 188 189 mContext = context; 190 mInjector = injector; 191 mHandler = mInjector.initializeMessageHandler(); 192 mBackgroundHandler = mInjector.getBackgroundHandler(); 193 } 194 initializeMessageHandler()195 private static Handler initializeMessageHandler() { 196 final HandlerThread handlerThread = new ServiceThread(TAG, 197 Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */); 198 handlerThread.start(); 199 final Handler handler = new Handler(handlerThread.getLooper()); 200 Watchdog.getInstance().addThread(handler); 201 return handler; 202 } 203 204 @Override onStart()205 public void onStart() { 206 publishBinderService(Context.BLOB_STORE_SERVICE, new Stub()); 207 LocalServices.addService(BlobStoreManagerInternal.class, new LocalService()); 208 209 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 210 mStatsManager = getContext().getSystemService(StatsManager.class); 211 registerReceivers(); 212 LocalManagerRegistry.getManager(StorageStatsManagerLocal.class) 213 .registerStorageStatsAugmenter(new BlobStorageStatsAugmenter(), TAG); 214 } 215 216 @Override onBootPhase(int phase)217 public void onBootPhase(int phase) { 218 if (phase == PHASE_ACTIVITY_MANAGER_READY) { 219 BlobStoreConfig.initialize(mContext); 220 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 221 synchronized (mBlobsLock) { 222 final SparseArray<SparseArray<String>> allPackages = getAllPackages(); 223 readBlobSessionsLocked(allPackages); 224 readBlobsInfoLocked(allPackages); 225 } 226 registerBlobStorePuller(); 227 } else if (phase == PHASE_BOOT_COMPLETED) { 228 BlobStoreIdleJobService.schedule(mContext); 229 } 230 } 231 232 @GuardedBy("mBlobsLock") generateNextSessionIdLocked()233 private long generateNextSessionIdLocked() { 234 // Logic borrowed from PackageInstallerService. 235 int n = 0; 236 long sessionId; 237 do { 238 final long randomLong = mRandom.nextLong(); 239 sessionId = (randomLong == Long.MIN_VALUE) ? INVALID_BLOB_ID : Math.abs(randomLong); 240 if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != INVALID_BLOB_ID) { 241 return sessionId; 242 } 243 } while (n++ < 32); 244 throw new IllegalStateException("Failed to allocate session ID"); 245 } 246 registerReceivers()247 private void registerReceivers() { 248 final IntentFilter packageChangedFilter = new IntentFilter(); 249 packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); 250 packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); 251 packageChangedFilter.addDataScheme("package"); 252 mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL, 253 packageChangedFilter, null, mHandler); 254 255 final IntentFilter userActionFilter = new IntentFilter(); 256 userActionFilter.addAction(Intent.ACTION_USER_REMOVED); 257 mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL, 258 userActionFilter, null, mHandler); 259 } 260 261 @GuardedBy("mBlobsLock") getUserSessionsLocked(int userId)262 private LongSparseArray<BlobStoreSession> getUserSessionsLocked(int userId) { 263 LongSparseArray<BlobStoreSession> userSessions = mSessions.get(userId); 264 if (userSessions == null) { 265 userSessions = new LongSparseArray<>(); 266 mSessions.put(userId, userSessions); 267 } 268 return userSessions; 269 } 270 271 @VisibleForTesting addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId)272 void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) { 273 synchronized (mBlobsLock) { 274 mSessions.put(userId, userSessions); 275 } 276 } 277 278 @VisibleForTesting getBlobForTest(BlobHandle blobHandle)279 BlobMetadata getBlobForTest(BlobHandle blobHandle) { 280 synchronized (mBlobsLock) { 281 return mBlobsMap.get(blobHandle); 282 } 283 } 284 285 @VisibleForTesting getBlobsCountForTest()286 int getBlobsCountForTest() { 287 synchronized (mBlobsLock) { 288 return mBlobsMap.size(); 289 } 290 } 291 292 @VisibleForTesting addActiveIdsForTest(long... activeIds)293 void addActiveIdsForTest(long... activeIds) { 294 synchronized (mBlobsLock) { 295 for (long id : activeIds) { 296 addActiveBlobIdLocked(id); 297 } 298 } 299 } 300 301 @VisibleForTesting getActiveIdsForTest()302 Set<Long> getActiveIdsForTest() { 303 synchronized (mBlobsLock) { 304 return mActiveBlobIds; 305 } 306 } 307 308 @VisibleForTesting getKnownIdsForTest()309 Set<Long> getKnownIdsForTest() { 310 synchronized (mBlobsLock) { 311 return mKnownBlobIds; 312 } 313 } 314 315 @GuardedBy("mBlobsLock") addSessionForUserLocked(BlobStoreSession session, int userId)316 private void addSessionForUserLocked(BlobStoreSession session, int userId) { 317 getUserSessionsLocked(userId).put(session.getSessionId(), session); 318 addActiveBlobIdLocked(session.getSessionId()); 319 } 320 321 @GuardedBy("mBlobsLock") 322 @VisibleForTesting addBlobLocked(BlobMetadata blobMetadata)323 void addBlobLocked(BlobMetadata blobMetadata) { 324 mBlobsMap.put(blobMetadata.getBlobHandle(), blobMetadata); 325 addActiveBlobIdLocked(blobMetadata.getBlobId()); 326 } 327 328 @GuardedBy("mBlobsLock") addActiveBlobIdLocked(long id)329 private void addActiveBlobIdLocked(long id) { 330 mActiveBlobIds.add(id); 331 mKnownBlobIds.add(id); 332 } 333 334 @GuardedBy("mBlobsLock") getSessionsCountLocked(int uid, String packageName)335 private int getSessionsCountLocked(int uid, String packageName) { 336 // TODO: Maintain a counter instead of traversing all the sessions 337 final AtomicInteger sessionsCount = new AtomicInteger(0); 338 forEachSessionInUser(session -> { 339 if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) { 340 sessionsCount.getAndIncrement(); 341 } 342 }, UserHandle.getUserId(uid)); 343 return sessionsCount.get(); 344 } 345 createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage)346 private long createSessionInternal(BlobHandle blobHandle, 347 int callingUid, String callingPackage) { 348 synchronized (mBlobsLock) { 349 final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage); 350 if (sessionsCount >= getMaxActiveSessions()) { 351 throw new LimitExceededException("Too many active sessions for the caller: " 352 + sessionsCount); 353 } 354 // TODO: throw if there is already an active session associated with blobHandle. 355 final long sessionId = generateNextSessionIdLocked(); 356 final BlobStoreSession session = new BlobStoreSession(mContext, 357 sessionId, blobHandle, callingUid, callingPackage, 358 mSessionStateChangeListener); 359 addSessionForUserLocked(session, UserHandle.getUserId(callingUid)); 360 if (LOGV) { 361 Slog.v(TAG, "Created session for " + blobHandle 362 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 363 } 364 writeBlobSessionsAsync(); 365 return sessionId; 366 } 367 } 368 openSessionInternal(long sessionId, int callingUid, String callingPackage)369 private BlobStoreSession openSessionInternal(long sessionId, 370 int callingUid, String callingPackage) { 371 final BlobStoreSession session; 372 synchronized (mBlobsLock) { 373 session = getUserSessionsLocked( 374 UserHandle.getUserId(callingUid)).get(sessionId); 375 if (session == null || !session.hasAccess(callingUid, callingPackage) 376 || session.isFinalized()) { 377 throw new SecurityException("Session not found: " + sessionId); 378 } 379 } 380 session.open(); 381 return session; 382 } 383 abandonSessionInternal(long sessionId, int callingUid, String callingPackage)384 private void abandonSessionInternal(long sessionId, 385 int callingUid, String callingPackage) { 386 synchronized (mBlobsLock) { 387 final BlobStoreSession session = openSessionInternal(sessionId, 388 callingUid, callingPackage); 389 session.open(); 390 session.abandon(); 391 if (LOGV) { 392 Slog.v(TAG, "Abandoned session with id " + sessionId 393 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 394 } 395 writeBlobSessionsAsync(); 396 } 397 } 398 openBlobInternal(BlobHandle blobHandle, int callingUid, String callingPackage)399 private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid, 400 String callingPackage) throws IOException { 401 synchronized (mBlobsLock) { 402 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 403 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( 404 callingPackage, callingUid)) { 405 if (blobMetadata == null) { 406 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, 407 INVALID_BLOB_ID, INVALID_BLOB_SIZE, 408 FrameworkStatsLog.BLOB_OPENED__RESULT__BLOB_DNE); 409 } else { 410 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, 411 blobMetadata.getBlobId(), blobMetadata.getSize(), 412 FrameworkStatsLog.BLOB_OPENED__RESULT__ACCESS_NOT_ALLOWED); 413 } 414 throw new SecurityException("Caller not allowed to access " + blobHandle 415 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 416 } 417 418 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, 419 blobMetadata.getBlobId(), blobMetadata.getSize(), 420 FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS); 421 422 return blobMetadata.openForRead(callingPackage, callingUid); 423 } 424 } 425 426 @GuardedBy("mBlobsLock") getCommittedBlobsCountLocked(int uid, String packageName)427 private int getCommittedBlobsCountLocked(int uid, String packageName) { 428 // TODO: Maintain a counter instead of traversing all the blobs 429 final AtomicInteger blobsCount = new AtomicInteger(0); 430 forEachBlobLocked(blobMetadata -> { 431 if (blobMetadata.isACommitter(packageName, uid)) { 432 blobsCount.getAndIncrement(); 433 } 434 }); 435 return blobsCount.get(); 436 } 437 438 @GuardedBy("mBlobsLock") getLeasedBlobsCountLocked(int uid, String packageName)439 private int getLeasedBlobsCountLocked(int uid, String packageName) { 440 // TODO: Maintain a counter instead of traversing all the blobs 441 final AtomicInteger blobsCount = new AtomicInteger(0); 442 forEachBlobLocked(blobMetadata -> { 443 if (blobMetadata.isALeasee(packageName, uid)) { 444 blobsCount.getAndIncrement(); 445 } 446 }); 447 return blobsCount.get(); 448 } 449 acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, CharSequence description, long leaseExpiryTimeMillis, int callingUid, String callingPackage)450 private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, 451 CharSequence description, long leaseExpiryTimeMillis, 452 int callingUid, String callingPackage) { 453 synchronized (mBlobsLock) { 454 final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage); 455 if (leasesCount >= getMaxLeasedBlobs()) { 456 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 457 INVALID_BLOB_ID, INVALID_BLOB_SIZE, 458 FrameworkStatsLog.BLOB_LEASED__RESULT__COUNT_LIMIT_EXCEEDED); 459 throw new LimitExceededException("Too many leased blobs for the caller: " 460 + leasesCount); 461 } 462 if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0 463 && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) { 464 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 465 INVALID_BLOB_ID, INVALID_BLOB_SIZE, 466 FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID); 467 throw new IllegalArgumentException( 468 "Lease expiry cannot be later than blobs expiry time"); 469 } 470 471 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 472 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( 473 callingPackage, callingUid)) { 474 if (blobMetadata == null) { 475 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 476 INVALID_BLOB_ID, INVALID_BLOB_SIZE, 477 FrameworkStatsLog.BLOB_LEASED__RESULT__BLOB_DNE); 478 } else { 479 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 480 blobMetadata.getBlobId(), blobMetadata.getSize(), 481 FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED); 482 } 483 throw new SecurityException("Caller not allowed to access " + blobHandle 484 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 485 } 486 487 if (blobMetadata.getSize() 488 > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) { 489 490 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 491 blobMetadata.getBlobId(), blobMetadata.getSize(), 492 FrameworkStatsLog.BLOB_LEASED__RESULT__DATA_SIZE_LIMIT_EXCEEDED); 493 throw new LimitExceededException("Total amount of data with an active lease" 494 + " is exceeding the max limit"); 495 } 496 497 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 498 blobMetadata.getBlobId(), blobMetadata.getSize(), 499 FrameworkStatsLog.BLOB_LEASED__RESULT__SUCCESS); 500 501 blobMetadata.addOrReplaceLeasee(callingPackage, callingUid, 502 descriptionResId, description, leaseExpiryTimeMillis); 503 if (LOGV) { 504 Slog.v(TAG, "Acquired lease on " + blobHandle 505 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 506 } 507 writeBlobsInfoAsync(); 508 } 509 } 510 511 @VisibleForTesting 512 @GuardedBy("mBlobsLock") getTotalUsageBytesLocked(int callingUid, String callingPackage)513 long getTotalUsageBytesLocked(int callingUid, String callingPackage) { 514 final AtomicLong totalBytes = new AtomicLong(0); 515 forEachBlobLocked((blobMetadata) -> { 516 if (blobMetadata.isALeasee(callingPackage, callingUid)) { 517 totalBytes.getAndAdd(blobMetadata.getSize()); 518 } 519 }); 520 return totalBytes.get(); 521 } 522 releaseLeaseInternal(BlobHandle blobHandle, int callingUid, String callingPackage)523 private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid, 524 String callingPackage) { 525 synchronized (mBlobsLock) { 526 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 527 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( 528 callingPackage, callingUid)) { 529 throw new SecurityException("Caller not allowed to access " + blobHandle 530 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 531 } 532 blobMetadata.removeLeasee(callingPackage, callingUid); 533 if (LOGV) { 534 Slog.v(TAG, "Released lease on " + blobHandle 535 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 536 } 537 if (!blobMetadata.hasValidLeases()) { 538 mHandler.postDelayed(() -> { 539 synchronized (mBlobsLock) { 540 // Check if blobMetadata object is still valid. If it is not, then 541 // it means that it was already deleted and nothing else to do here. 542 if (!Objects.equals(mBlobsMap.get(blobHandle), blobMetadata)) { 543 return; 544 } 545 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { 546 deleteBlobLocked(blobMetadata); 547 mBlobsMap.remove(blobHandle); 548 } 549 writeBlobsInfoAsync(); 550 } 551 }, getDeletionOnLastLeaseDelayMs()); 552 } 553 writeBlobsInfoAsync(); 554 } 555 } 556 getRemainingLeaseQuotaBytesInternal(int callingUid, String callingPackage)557 private long getRemainingLeaseQuotaBytesInternal(int callingUid, String callingPackage) { 558 synchronized (mBlobsLock) { 559 final long remainingQuota = BlobStoreConfig.getAppDataBytesLimit() 560 - getTotalUsageBytesLocked(callingUid, callingPackage); 561 return remainingQuota > 0 ? remainingQuota : 0; 562 } 563 } 564 queryBlobsForUserInternal(int userId)565 private List<BlobInfo> queryBlobsForUserInternal(int userId) { 566 final ArrayList<BlobInfo> blobInfos = new ArrayList<>(); 567 synchronized (mBlobsLock) { 568 final ArrayMap<String, WeakReference<Resources>> resources = new ArrayMap<>(); 569 final Function<String, Resources> resourcesGetter = (packageName) -> { 570 final WeakReference<Resources> resourcesRef = resources.get(packageName); 571 Resources packageResources = resourcesRef == null ? null : resourcesRef.get(); 572 if (packageResources == null) { 573 packageResources = getPackageResources(mContext, packageName, userId); 574 resources.put(packageName, new WeakReference<>(packageResources)); 575 } 576 return packageResources; 577 }; 578 forEachBlobLocked((blobHandle, blobMetadata) -> { 579 if (!blobMetadata.hasACommitterOrLeaseeInUser(userId)) { 580 return; 581 } 582 final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>(); 583 blobMetadata.forEachLeasee(leasee -> { 584 if (!leasee.isStillValid()) { 585 return; 586 } 587 if (userId != UserHandle.getUserId(leasee.uid)) { 588 return; 589 } 590 final int descriptionResId = leasee.descriptionResEntryName == null 591 ? Resources.ID_NULL 592 : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName), 593 leasee.descriptionResEntryName, leasee.packageName); 594 final long expiryTimeMs = leasee.expiryTimeMillis == 0 595 ? blobHandle.getExpiryTimeMillis() : leasee.expiryTimeMillis; 596 leaseInfos.add(new LeaseInfo(leasee.packageName, expiryTimeMs, 597 descriptionResId, leasee.description)); 598 }); 599 blobInfos.add(new BlobInfo(blobMetadata.getBlobId(), 600 blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), 601 blobMetadata.getSize(), leaseInfos)); 602 }); 603 } 604 return blobInfos; 605 } 606 deleteBlobInternal(long blobId, int callingUid)607 private void deleteBlobInternal(long blobId, int callingUid) { 608 synchronized (mBlobsLock) { 609 mBlobsMap.entrySet().removeIf(entry -> { 610 final BlobMetadata blobMetadata = entry.getValue(); 611 if (blobMetadata.getBlobId() == blobId) { 612 deleteBlobLocked(blobMetadata); 613 return true; 614 } 615 return false; 616 }); 617 writeBlobsInfoAsync(); 618 } 619 } 620 getLeasedBlobsInternal(int callingUid, @NonNull String callingPackage)621 private List<BlobHandle> getLeasedBlobsInternal(int callingUid, 622 @NonNull String callingPackage) { 623 final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>(); 624 synchronized (mBlobsLock) { 625 forEachBlobLocked(blobMetadata -> { 626 if (blobMetadata.isALeasee(callingPackage, callingUid)) { 627 leasedBlobs.add(blobMetadata.getBlobHandle()); 628 } 629 }); 630 } 631 return leasedBlobs; 632 } 633 getLeaseInfoInternal(BlobHandle blobHandle, int callingUid, @NonNull String callingPackage)634 private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle, 635 int callingUid, @NonNull String callingPackage) { 636 synchronized (mBlobsLock) { 637 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 638 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( 639 callingPackage, callingUid)) { 640 throw new SecurityException("Caller not allowed to access " + blobHandle 641 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 642 } 643 return blobMetadata.getLeaseInfo(callingPackage, callingUid); 644 } 645 } 646 verifyCallingPackage(int callingUid, String callingPackage)647 private void verifyCallingPackage(int callingUid, String callingPackage) { 648 if (mPackageManagerInternal.getPackageUid( 649 callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) { 650 throw new SecurityException("Specified calling package [" + callingPackage 651 + "] does not match the calling uid " + callingUid); 652 } 653 } 654 655 class SessionStateChangeListener { onStateChanged(@onNull BlobStoreSession session)656 public void onStateChanged(@NonNull BlobStoreSession session) { 657 mHandler.post(PooledLambda.obtainRunnable( 658 BlobStoreManagerService::onStateChangedInternal, 659 BlobStoreManagerService.this, session).recycleOnUse()); 660 } 661 } 662 onStateChangedInternal(@onNull BlobStoreSession session)663 private void onStateChangedInternal(@NonNull BlobStoreSession session) { 664 switch (session.getState()) { 665 case STATE_ABANDONED: 666 case STATE_VERIFIED_INVALID: 667 synchronized (mBlobsLock) { 668 deleteSessionLocked(session); 669 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) 670 .remove(session.getSessionId()); 671 if (LOGV) { 672 Slog.v(TAG, "Session is invalid; deleted " + session); 673 } 674 } 675 break; 676 case STATE_COMMITTED: 677 mBackgroundHandler.post(() -> { 678 session.computeDigest(); 679 mHandler.post(PooledLambda.obtainRunnable( 680 BlobStoreSession::verifyBlobData, session).recycleOnUse()); 681 }); 682 break; 683 case STATE_VERIFIED_VALID: 684 synchronized (mBlobsLock) { 685 final int committedBlobsCount = getCommittedBlobsCountLocked( 686 session.getOwnerUid(), session.getOwnerPackageName()); 687 if (committedBlobsCount >= getMaxCommittedBlobs()) { 688 Slog.d(TAG, "Failed to commit: too many committed blobs. count: " 689 + committedBlobsCount + "; blob: " + session); 690 session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); 691 deleteSessionLocked(session); 692 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) 693 .remove(session.getSessionId()); 694 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, 695 session.getOwnerUid(), session.getSessionId(), session.getSize(), 696 FrameworkStatsLog.BLOB_COMMITTED__RESULT__COUNT_LIMIT_EXCEEDED); 697 break; 698 } 699 final BlobMetadata blob; 700 final int blobIndex = mBlobsMap.indexOfKey(session.getBlobHandle()); 701 if (blobIndex >= 0) { 702 blob = mBlobsMap.valueAt(blobIndex); 703 } else { 704 blob = new BlobMetadata(mContext, session.getSessionId(), 705 session.getBlobHandle()); 706 addBlobLocked(blob); 707 } 708 final Committer existingCommitter = blob.getExistingCommitter( 709 session.getOwnerPackageName(), session.getOwnerUid()); 710 final long existingCommitTimeMs = 711 (existingCommitter == null) ? 0 : existingCommitter.getCommitTimeMs(); 712 final Committer newCommitter = new Committer(session.getOwnerPackageName(), 713 session.getOwnerUid(), session.getBlobAccessMode(), 714 getAdjustedCommitTimeMs(existingCommitTimeMs, 715 System.currentTimeMillis())); 716 blob.addOrReplaceCommitter(newCommitter); 717 try { 718 writeBlobsInfoLocked(); 719 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, 720 session.getOwnerUid(), blob.getBlobId(), blob.getSize(), 721 FrameworkStatsLog.BLOB_COMMITTED__RESULT__SUCCESS); 722 session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS); 723 } catch (Exception e) { 724 if (existingCommitter == null) { 725 blob.removeCommitter(newCommitter); 726 } else { 727 blob.addOrReplaceCommitter(existingCommitter); 728 } 729 Slog.d(TAG, "Error committing the blob: " + session, e); 730 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, 731 session.getOwnerUid(), session.getSessionId(), blob.getSize(), 732 FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT); 733 session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); 734 // If the commit fails and this blob data didn't exist before, delete it. 735 // But if it is a recommit, just leave it as is. 736 if (session.getSessionId() == blob.getBlobId()) { 737 deleteBlobLocked(blob); 738 mBlobsMap.remove(blob.getBlobHandle()); 739 } 740 } 741 // Delete redundant data from recommits. 742 if (session.getSessionId() != blob.getBlobId()) { 743 deleteSessionLocked(session); 744 } 745 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) 746 .remove(session.getSessionId()); 747 if (LOGV) { 748 Slog.v(TAG, "Successfully committed session " + session); 749 } 750 } 751 break; 752 default: 753 Slog.wtf(TAG, "Invalid session state: " 754 + stateToString(session.getState())); 755 } 756 synchronized (mBlobsLock) { 757 try { 758 writeBlobSessionsLocked(); 759 } catch (Exception e) { 760 // already logged, ignore. 761 } 762 } 763 } 764 765 @GuardedBy("mBlobsLock") writeBlobSessionsLocked()766 private void writeBlobSessionsLocked() throws Exception { 767 final AtomicFile sessionsIndexFile = prepareSessionsIndexFile(); 768 if (sessionsIndexFile == null) { 769 Slog.wtf(TAG, "Error creating sessions index file"); 770 return; 771 } 772 FileOutputStream fos = null; 773 try { 774 fos = sessionsIndexFile.startWrite(SystemClock.uptimeMillis()); 775 final XmlSerializer out = new FastXmlSerializer(); 776 out.setOutput(fos, StandardCharsets.UTF_8.name()); 777 out.startDocument(null, true); 778 out.startTag(null, TAG_SESSIONS); 779 XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); 780 781 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { 782 final LongSparseArray<BlobStoreSession> userSessions = 783 mSessions.valueAt(i); 784 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { 785 out.startTag(null, TAG_SESSION); 786 userSessions.valueAt(j).writeToXml(out); 787 out.endTag(null, TAG_SESSION); 788 } 789 } 790 791 out.endTag(null, TAG_SESSIONS); 792 out.endDocument(); 793 sessionsIndexFile.finishWrite(fos); 794 if (LOGV) { 795 Slog.v(TAG, "Finished persisting sessions data"); 796 } 797 } catch (Exception e) { 798 sessionsIndexFile.failWrite(fos); 799 Slog.wtf(TAG, "Error writing sessions data", e); 800 throw e; 801 } 802 } 803 804 @GuardedBy("mBlobsLock") readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages)805 private void readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages) { 806 if (!BlobStoreConfig.getBlobStoreRootDir().exists()) { 807 return; 808 } 809 final AtomicFile sessionsIndexFile = prepareSessionsIndexFile(); 810 if (sessionsIndexFile == null) { 811 Slog.wtf(TAG, "Error creating sessions index file"); 812 return; 813 } else if (!sessionsIndexFile.exists()) { 814 Slog.w(TAG, "Sessions index file not available: " + sessionsIndexFile.getBaseFile()); 815 return; 816 } 817 818 mSessions.clear(); 819 try (FileInputStream fis = sessionsIndexFile.openRead()) { 820 final XmlPullParser in = Xml.newPullParser(); 821 in.setInput(fis, StandardCharsets.UTF_8.name()); 822 XmlUtils.beginDocument(in, TAG_SESSIONS); 823 final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); 824 while (true) { 825 XmlUtils.nextElement(in); 826 if (in.getEventType() == XmlPullParser.END_DOCUMENT) { 827 break; 828 } 829 830 if (TAG_SESSION.equals(in.getName())) { 831 final BlobStoreSession session = BlobStoreSession.createFromXml( 832 in, version, mContext, mSessionStateChangeListener); 833 if (session == null) { 834 continue; 835 } 836 final SparseArray<String> userPackages = allPackages.get( 837 UserHandle.getUserId(session.getOwnerUid())); 838 if (userPackages != null 839 && session.getOwnerPackageName().equals( 840 userPackages.get(session.getOwnerUid()))) { 841 addSessionForUserLocked(session, 842 UserHandle.getUserId(session.getOwnerUid())); 843 } else { 844 // Unknown package or the session data does not belong to this package. 845 session.getSessionFile().delete(); 846 } 847 mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId()); 848 } 849 } 850 if (LOGV) { 851 Slog.v(TAG, "Finished reading sessions data"); 852 } 853 } catch (Exception e) { 854 Slog.wtf(TAG, "Error reading sessions data", e); 855 } 856 } 857 858 @GuardedBy("mBlobsLock") writeBlobsInfoLocked()859 private void writeBlobsInfoLocked() throws Exception { 860 final AtomicFile blobsIndexFile = prepareBlobsIndexFile(); 861 if (blobsIndexFile == null) { 862 Slog.wtf(TAG, "Error creating blobs index file"); 863 return; 864 } 865 FileOutputStream fos = null; 866 try { 867 fos = blobsIndexFile.startWrite(SystemClock.uptimeMillis()); 868 final XmlSerializer out = new FastXmlSerializer(); 869 out.setOutput(fos, StandardCharsets.UTF_8.name()); 870 out.startDocument(null, true); 871 out.startTag(null, TAG_BLOBS); 872 XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); 873 874 for (int i = 0, count = mBlobsMap.size(); i < count; ++i) { 875 out.startTag(null, TAG_BLOB); 876 mBlobsMap.valueAt(i).writeToXml(out); 877 out.endTag(null, TAG_BLOB); 878 } 879 880 out.endTag(null, TAG_BLOBS); 881 out.endDocument(); 882 blobsIndexFile.finishWrite(fos); 883 if (LOGV) { 884 Slog.v(TAG, "Finished persisting blobs data"); 885 } 886 } catch (Exception e) { 887 blobsIndexFile.failWrite(fos); 888 Slog.wtf(TAG, "Error writing blobs data", e); 889 throw e; 890 } 891 } 892 893 @GuardedBy("mBlobsLock") readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages)894 private void readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages) { 895 if (!BlobStoreConfig.getBlobStoreRootDir().exists()) { 896 return; 897 } 898 final AtomicFile blobsIndexFile = prepareBlobsIndexFile(); 899 if (blobsIndexFile == null) { 900 Slog.wtf(TAG, "Error creating blobs index file"); 901 return; 902 } else if (!blobsIndexFile.exists()) { 903 Slog.w(TAG, "Blobs index file not available: " + blobsIndexFile.getBaseFile()); 904 return; 905 } 906 907 mBlobsMap.clear(); 908 try (FileInputStream fis = blobsIndexFile.openRead()) { 909 final XmlPullParser in = Xml.newPullParser(); 910 in.setInput(fis, StandardCharsets.UTF_8.name()); 911 XmlUtils.beginDocument(in, TAG_BLOBS); 912 final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); 913 while (true) { 914 XmlUtils.nextElement(in); 915 if (in.getEventType() == XmlPullParser.END_DOCUMENT) { 916 break; 917 } 918 919 if (TAG_BLOB.equals(in.getName())) { 920 final BlobMetadata blobMetadata = BlobMetadata.createFromXml( 921 in, version, mContext); 922 blobMetadata.removeCommittersFromUnknownPkgs(allPackages); 923 blobMetadata.removeLeaseesFromUnknownPkgs(allPackages); 924 mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId()); 925 if (version >= XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) { 926 addBlobLocked(blobMetadata); 927 } else { 928 final BlobMetadata existingBlobMetadata = mBlobsMap.get( 929 blobMetadata.getBlobHandle()); 930 if (existingBlobMetadata == null) { 931 addBlobLocked(blobMetadata); 932 } else { 933 existingBlobMetadata.addCommittersAndLeasees(blobMetadata); 934 blobMetadata.getBlobFile().delete(); 935 } 936 } 937 } 938 } 939 if (LOGV) { 940 Slog.v(TAG, "Finished reading blobs data"); 941 } 942 } catch (Exception e) { 943 Slog.wtf(TAG, "Error reading blobs data", e); 944 } 945 } 946 writeBlobsInfo()947 private void writeBlobsInfo() { 948 synchronized (mBlobsLock) { 949 try { 950 writeBlobsInfoLocked(); 951 } catch (Exception e) { 952 // Already logged, ignore 953 } 954 } 955 } 956 writeBlobsInfoAsync()957 private void writeBlobsInfoAsync() { 958 if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) { 959 mHandler.post(mSaveBlobsInfoRunnable); 960 } 961 } 962 writeBlobSessions()963 private void writeBlobSessions() { 964 synchronized (mBlobsLock) { 965 try { 966 writeBlobSessionsLocked(); 967 } catch (Exception e) { 968 // Already logged, ignore 969 } 970 } 971 } 972 writeBlobSessionsAsync()973 private void writeBlobSessionsAsync() { 974 if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) { 975 mHandler.post(mSaveSessionsRunnable); 976 } 977 } 978 getAllPackages()979 private SparseArray<SparseArray<String>> getAllPackages() { 980 final SparseArray<SparseArray<String>> allPackages = new SparseArray<>(); 981 final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds(); 982 for (int userId : allUsers) { 983 final SparseArray<String> userPackages = new SparseArray<>(); 984 allPackages.put(userId, userPackages); 985 final List<ApplicationInfo> applicationInfos = mPackageManagerInternal 986 .getInstalledApplications( 987 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE 988 | MATCH_UNINSTALLED_PACKAGES, 989 userId, Process.myUid()); 990 for (int i = 0, count = applicationInfos.size(); i < count; ++i) { 991 final ApplicationInfo applicationInfo = applicationInfos.get(i); 992 userPackages.put(applicationInfo.uid, applicationInfo.packageName); 993 } 994 } 995 return allPackages; 996 } 997 prepareSessionsIndexFile()998 private AtomicFile prepareSessionsIndexFile() { 999 final File file = BlobStoreConfig.prepareSessionIndexFile(); 1000 if (file == null) { 1001 return null; 1002 } 1003 return new AtomicFile(file, "session_index" /* commitLogTag */); 1004 } 1005 prepareBlobsIndexFile()1006 private AtomicFile prepareBlobsIndexFile() { 1007 final File file = BlobStoreConfig.prepareBlobsIndexFile(); 1008 if (file == null) { 1009 return null; 1010 } 1011 return new AtomicFile(file, "blobs_index" /* commitLogTag */); 1012 } 1013 1014 @VisibleForTesting handlePackageRemoved(String packageName, int uid)1015 void handlePackageRemoved(String packageName, int uid) { 1016 synchronized (mBlobsLock) { 1017 // Clean up any pending sessions 1018 final LongSparseArray<BlobStoreSession> userSessions = 1019 getUserSessionsLocked(UserHandle.getUserId(uid)); 1020 userSessions.removeIf((sessionId, blobStoreSession) -> { 1021 if (blobStoreSession.getOwnerUid() == uid 1022 && blobStoreSession.getOwnerPackageName().equals(packageName)) { 1023 deleteSessionLocked(blobStoreSession); 1024 return true; 1025 } 1026 return false; 1027 }); 1028 writeBlobSessionsAsync(); 1029 1030 // Remove the package from the committer and leasee list 1031 mBlobsMap.entrySet().removeIf(entry -> { 1032 final BlobMetadata blobMetadata = entry.getValue(); 1033 final boolean isACommitter = blobMetadata.isACommitter(packageName, uid); 1034 if (isACommitter) { 1035 blobMetadata.removeCommitter(packageName, uid); 1036 } 1037 blobMetadata.removeLeasee(packageName, uid); 1038 // Regardless of when the blob is committed, we need to delete 1039 // it if it was from the deleted package to ensure we delete all traces of it. 1040 if (blobMetadata.shouldBeDeleted(isACommitter /* respectLeaseWaitTime */)) { 1041 deleteBlobLocked(blobMetadata); 1042 return true; 1043 } 1044 return false; 1045 }); 1046 writeBlobsInfoAsync(); 1047 1048 if (LOGV) { 1049 Slog.v(TAG, "Removed blobs data associated with pkg=" 1050 + packageName + ", uid=" + uid); 1051 } 1052 } 1053 } 1054 handleUserRemoved(int userId)1055 private void handleUserRemoved(int userId) { 1056 synchronized (mBlobsLock) { 1057 final LongSparseArray<BlobStoreSession> userSessions = 1058 mSessions.removeReturnOld(userId); 1059 if (userSessions != null) { 1060 for (int i = 0, count = userSessions.size(); i < count; ++i) { 1061 final BlobStoreSession session = userSessions.valueAt(i); 1062 deleteSessionLocked(session); 1063 } 1064 } 1065 1066 mBlobsMap.entrySet().removeIf(entry -> { 1067 final BlobMetadata blobMetadata = entry.getValue(); 1068 blobMetadata.removeDataForUser(userId); 1069 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { 1070 deleteBlobLocked(blobMetadata); 1071 return true; 1072 } 1073 return false; 1074 }); 1075 if (LOGV) { 1076 Slog.v(TAG, "Removed blobs data in user " + userId); 1077 } 1078 } 1079 } 1080 1081 @GuardedBy("mBlobsLock") 1082 @VisibleForTesting handleIdleMaintenanceLocked()1083 void handleIdleMaintenanceLocked() { 1084 // Cleanup any left over data on disk that is not part of index. 1085 final ArrayList<Long> deletedBlobIds = new ArrayList<>(); 1086 final ArrayList<File> filesToDelete = new ArrayList<>(); 1087 final File blobsDir = BlobStoreConfig.getBlobsDir(); 1088 if (blobsDir.exists()) { 1089 for (File file : blobsDir.listFiles()) { 1090 try { 1091 final long id = Long.parseLong(file.getName()); 1092 if (mActiveBlobIds.indexOf(id) < 0) { 1093 filesToDelete.add(file); 1094 deletedBlobIds.add(id); 1095 } 1096 } catch (NumberFormatException e) { 1097 Slog.wtf(TAG, "Error parsing the file name: " + file, e); 1098 filesToDelete.add(file); 1099 } 1100 } 1101 for (int i = 0, count = filesToDelete.size(); i < count; ++i) { 1102 filesToDelete.get(i).delete(); 1103 } 1104 } 1105 1106 // Cleanup any stale blobs. 1107 mBlobsMap.entrySet().removeIf(entry -> { 1108 final BlobMetadata blobMetadata = entry.getValue(); 1109 1110 // Remove expired leases 1111 blobMetadata.removeExpiredLeases(); 1112 1113 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { 1114 deleteBlobLocked(blobMetadata); 1115 deletedBlobIds.add(blobMetadata.getBlobId()); 1116 return true; 1117 } 1118 return false; 1119 }); 1120 writeBlobsInfoAsync(); 1121 1122 // Cleanup any stale sessions. 1123 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { 1124 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); 1125 userSessions.removeIf((sessionId, blobStoreSession) -> { 1126 boolean shouldRemove = false; 1127 1128 // Cleanup sessions which haven't been modified in a while. 1129 if (blobStoreSession.isExpired()) { 1130 shouldRemove = true; 1131 } 1132 1133 // Cleanup sessions with already expired data. 1134 if (blobStoreSession.getBlobHandle().isExpired()) { 1135 shouldRemove = true; 1136 } 1137 1138 if (shouldRemove) { 1139 deleteSessionLocked(blobStoreSession); 1140 deletedBlobIds.add(blobStoreSession.getSessionId()); 1141 } 1142 return shouldRemove; 1143 }); 1144 } 1145 Slog.d(TAG, "Completed idle maintenance; deleted " 1146 + Arrays.toString(deletedBlobIds.toArray())); 1147 writeBlobSessionsAsync(); 1148 } 1149 1150 @GuardedBy("mBlobsLock") deleteSessionLocked(BlobStoreSession blobStoreSession)1151 private void deleteSessionLocked(BlobStoreSession blobStoreSession) { 1152 blobStoreSession.destroy(); 1153 mActiveBlobIds.remove(blobStoreSession.getSessionId()); 1154 } 1155 1156 @GuardedBy("mBlobsLock") deleteBlobLocked(BlobMetadata blobMetadata)1157 private void deleteBlobLocked(BlobMetadata blobMetadata) { 1158 blobMetadata.destroy(); 1159 mActiveBlobIds.remove(blobMetadata.getBlobId()); 1160 } 1161 runClearAllSessions(@serIdInt int userId)1162 void runClearAllSessions(@UserIdInt int userId) { 1163 synchronized (mBlobsLock) { 1164 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { 1165 final int sessionUserId = mSessions.keyAt(i); 1166 if (userId != UserHandle.USER_ALL && userId != sessionUserId) { 1167 continue; 1168 } 1169 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); 1170 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { 1171 mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId()); 1172 } 1173 } 1174 if (userId == UserHandle.USER_ALL) { 1175 mSessions.clear(); 1176 } else { 1177 mSessions.remove(userId); 1178 } 1179 writeBlobSessionsAsync(); 1180 } 1181 } 1182 runClearAllBlobs(@serIdInt int userId)1183 void runClearAllBlobs(@UserIdInt int userId) { 1184 synchronized (mBlobsLock) { 1185 mBlobsMap.entrySet().removeIf(entry -> { 1186 final BlobMetadata blobMetadata = entry.getValue(); 1187 if (userId == UserHandle.USER_ALL) { 1188 mActiveBlobIds.remove(blobMetadata.getBlobId()); 1189 return true; 1190 } 1191 blobMetadata.removeDataForUser(userId); 1192 if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) { 1193 mActiveBlobIds.remove(blobMetadata.getBlobId()); 1194 return true; 1195 } 1196 return false; 1197 }); 1198 writeBlobsInfoAsync(); 1199 } 1200 } 1201 deleteBlob(@onNull BlobHandle blobHandle, @UserIdInt int userId)1202 void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) { 1203 synchronized (mBlobsLock) { 1204 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 1205 if (blobMetadata == null) { 1206 return; 1207 } 1208 blobMetadata.removeDataForUser(userId); 1209 if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) { 1210 deleteBlobLocked(blobMetadata); 1211 mBlobsMap.remove(blobHandle); 1212 } 1213 writeBlobsInfoAsync(); 1214 } 1215 } 1216 runIdleMaintenance()1217 void runIdleMaintenance() { 1218 synchronized (mBlobsLock) { 1219 handleIdleMaintenanceLocked(); 1220 } 1221 } 1222 isBlobAvailable(long blobId, int userId)1223 boolean isBlobAvailable(long blobId, int userId) { 1224 synchronized (mBlobsLock) { 1225 for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) { 1226 final BlobMetadata blobMetadata = mBlobsMap.valueAt(i); 1227 if (blobMetadata.getBlobId() != blobId) { 1228 continue; 1229 } 1230 return blobMetadata.hasACommitterInUser(userId); 1231 } 1232 return false; 1233 } 1234 } 1235 1236 @GuardedBy("mBlobsLock") dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs)1237 private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { 1238 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { 1239 final int userId = mSessions.keyAt(i); 1240 if (!dumpArgs.shouldDumpUser(userId)) { 1241 continue; 1242 } 1243 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); 1244 fout.println("List of sessions in user #" 1245 + userId + " (" + userSessions.size() + "):"); 1246 fout.increaseIndent(); 1247 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { 1248 final long sessionId = userSessions.keyAt(j); 1249 final BlobStoreSession session = userSessions.valueAt(j); 1250 if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(), 1251 session.getOwnerUid(), session.getSessionId())) { 1252 continue; 1253 } 1254 fout.println("Session #" + sessionId); 1255 fout.increaseIndent(); 1256 session.dump(fout, dumpArgs); 1257 fout.decreaseIndent(); 1258 } 1259 fout.decreaseIndent(); 1260 } 1261 } 1262 1263 @GuardedBy("mBlobsLock") dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs)1264 private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { 1265 fout.println("List of blobs (" + mBlobsMap.size() + "):"); 1266 fout.increaseIndent(); 1267 for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) { 1268 final BlobMetadata blobMetadata = mBlobsMap.valueAt(i); 1269 if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) { 1270 continue; 1271 } 1272 fout.println("Blob #" + blobMetadata.getBlobId()); 1273 fout.increaseIndent(); 1274 blobMetadata.dump(fout, dumpArgs); 1275 fout.decreaseIndent(); 1276 } 1277 if (mBlobsMap.isEmpty()) { 1278 fout.println("<empty>"); 1279 } 1280 fout.decreaseIndent(); 1281 } 1282 1283 private class BlobStorageStatsAugmenter implements StorageStatsAugmenter { 1284 @Override augmentStatsForPackageForUser( @onNull PackageStats stats, @NonNull String packageName, @NonNull UserHandle userHandle, boolean callerHasStatsPermission)1285 public void augmentStatsForPackageForUser( 1286 @NonNull PackageStats stats, 1287 @NonNull String packageName, 1288 @NonNull UserHandle userHandle, 1289 boolean callerHasStatsPermission) { 1290 final AtomicLong blobsDataSize = new AtomicLong(0); 1291 forEachSessionInUser(session -> { 1292 if (session.getOwnerPackageName().equals(packageName)) { 1293 blobsDataSize.getAndAdd(session.getSize()); 1294 } 1295 }, userHandle.getIdentifier()); 1296 1297 forEachBlob(blobMetadata -> { 1298 if (blobMetadata.shouldAttributeToLeasee(packageName, userHandle.getIdentifier(), 1299 callerHasStatsPermission)) { 1300 blobsDataSize.getAndAdd(blobMetadata.getSize()); 1301 } 1302 }); 1303 1304 stats.dataSize += blobsDataSize.get(); 1305 } 1306 1307 @Override augmentStatsForUid(@onNull PackageStats stats, int uid, boolean callerHasStatsPermission)1308 public void augmentStatsForUid(@NonNull PackageStats stats, int uid, 1309 boolean callerHasStatsPermission) { 1310 final int userId = UserHandle.getUserId(uid); 1311 final AtomicLong blobsDataSize = new AtomicLong(0); 1312 forEachSessionInUser(session -> { 1313 if (session.getOwnerUid() == uid) { 1314 blobsDataSize.getAndAdd(session.getSize()); 1315 } 1316 }, userId); 1317 1318 forEachBlob(blobMetadata -> { 1319 if (blobMetadata.shouldAttributeToLeasee(uid, 1320 callerHasStatsPermission)) { 1321 blobsDataSize.getAndAdd(blobMetadata.getSize()); 1322 } 1323 }); 1324 1325 stats.dataSize += blobsDataSize.get(); 1326 } 1327 1328 @Override augmentStatsForUser( @onNull PackageStats stats, @NonNull UserHandle userHandle)1329 public void augmentStatsForUser( 1330 @NonNull PackageStats stats, @NonNull UserHandle userHandle) { 1331 final AtomicLong blobsDataSize = new AtomicLong(0); 1332 forEachSessionInUser(session -> { 1333 blobsDataSize.getAndAdd(session.getSize()); 1334 }, userHandle.getIdentifier()); 1335 1336 forEachBlob(blobMetadata -> { 1337 if (blobMetadata.shouldAttributeToUser(userHandle.getIdentifier())) { 1338 blobsDataSize.getAndAdd(blobMetadata.getSize()); 1339 } 1340 }); 1341 1342 stats.dataSize += blobsDataSize.get(); 1343 } 1344 } 1345 forEachSessionInUser(Consumer<BlobStoreSession> consumer, int userId)1346 private void forEachSessionInUser(Consumer<BlobStoreSession> consumer, int userId) { 1347 synchronized (mBlobsLock) { 1348 final LongSparseArray<BlobStoreSession> userSessions = getUserSessionsLocked(userId); 1349 for (int i = 0, count = userSessions.size(); i < count; ++i) { 1350 final BlobStoreSession session = userSessions.valueAt(i); 1351 consumer.accept(session); 1352 } 1353 } 1354 } 1355 forEachBlob(Consumer<BlobMetadata> consumer)1356 private void forEachBlob(Consumer<BlobMetadata> consumer) { 1357 synchronized (mBlobsMap) { 1358 forEachBlobLocked(consumer); 1359 } 1360 } 1361 1362 @GuardedBy("mBlobsMap") forEachBlobLocked(Consumer<BlobMetadata> consumer)1363 private void forEachBlobLocked(Consumer<BlobMetadata> consumer) { 1364 for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) { 1365 final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx); 1366 consumer.accept(blobMetadata); 1367 } 1368 } 1369 1370 @GuardedBy("mBlobsMap") forEachBlobLocked(BiConsumer<BlobHandle, BlobMetadata> consumer)1371 private void forEachBlobLocked(BiConsumer<BlobHandle, BlobMetadata> consumer) { 1372 for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) { 1373 final BlobHandle blobHandle = mBlobsMap.keyAt(blobIdx); 1374 final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx); 1375 consumer.accept(blobHandle, blobMetadata); 1376 } 1377 } 1378 1379 private class PackageChangedReceiver extends BroadcastReceiver { 1380 @Override onReceive(Context context, Intent intent)1381 public void onReceive(Context context, Intent intent) { 1382 if (LOGV) { 1383 Slog.v(TAG, "Received " + intent); 1384 } 1385 switch (intent.getAction()) { 1386 case Intent.ACTION_PACKAGE_FULLY_REMOVED: 1387 case Intent.ACTION_PACKAGE_DATA_CLEARED: 1388 final String packageName = intent.getData().getSchemeSpecificPart(); 1389 if (packageName == null) { 1390 Slog.wtf(TAG, "Package name is missing in the intent: " + intent); 1391 return; 1392 } 1393 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 1394 if (uid == -1) { 1395 Slog.wtf(TAG, "uid is missing in the intent: " + intent); 1396 return; 1397 } 1398 handlePackageRemoved(packageName, uid); 1399 break; 1400 default: 1401 Slog.wtf(TAG, "Received unknown intent: " + intent); 1402 } 1403 } 1404 } 1405 1406 private class UserActionReceiver extends BroadcastReceiver { 1407 @Override onReceive(Context context, Intent intent)1408 public void onReceive(Context context, Intent intent) { 1409 if (LOGV) { 1410 Slog.v(TAG, "Received: " + intent); 1411 } 1412 switch (intent.getAction()) { 1413 case Intent.ACTION_USER_REMOVED: 1414 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 1415 USER_NULL); 1416 if (userId == USER_NULL) { 1417 Slog.wtf(TAG, "userId is missing in the intent: " + intent); 1418 return; 1419 } 1420 handleUserRemoved(userId); 1421 break; 1422 default: 1423 Slog.wtf(TAG, "Received unknown intent: " + intent); 1424 } 1425 } 1426 } 1427 1428 private class Stub extends IBlobStoreManager.Stub { 1429 @Override 1430 @IntRange(from = 1) createSession(@onNull BlobHandle blobHandle, @NonNull String packageName)1431 public long createSession(@NonNull BlobHandle blobHandle, 1432 @NonNull String packageName) { 1433 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1434 blobHandle.assertIsValid(); 1435 Objects.requireNonNull(packageName, "packageName must not be null"); 1436 1437 final int callingUid = Binder.getCallingUid(); 1438 verifyCallingPackage(callingUid, packageName); 1439 1440 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( 1441 packageName, UserHandle.getUserId(callingUid))) { 1442 throw new SecurityException("Caller not allowed to create session; " 1443 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1444 } 1445 1446 try { 1447 return createSessionInternal(blobHandle, callingUid, packageName); 1448 } catch (LimitExceededException e) { 1449 throw new ParcelableException(e); 1450 } 1451 } 1452 1453 @Override 1454 @NonNull openSession(@ntRangefrom = 1) long sessionId, @NonNull String packageName)1455 public IBlobStoreSession openSession(@IntRange(from = 1) long sessionId, 1456 @NonNull String packageName) { 1457 Preconditions.checkArgumentPositive(sessionId, 1458 "sessionId must be positive: " + sessionId); 1459 Objects.requireNonNull(packageName, "packageName must not be null"); 1460 1461 final int callingUid = Binder.getCallingUid(); 1462 verifyCallingPackage(callingUid, packageName); 1463 1464 return openSessionInternal(sessionId, callingUid, packageName); 1465 } 1466 1467 @Override abandonSession(@ntRangefrom = 1) long sessionId, @NonNull String packageName)1468 public void abandonSession(@IntRange(from = 1) long sessionId, 1469 @NonNull String packageName) { 1470 Preconditions.checkArgumentPositive(sessionId, 1471 "sessionId must be positive: " + sessionId); 1472 Objects.requireNonNull(packageName, "packageName must not be null"); 1473 1474 final int callingUid = Binder.getCallingUid(); 1475 verifyCallingPackage(callingUid, packageName); 1476 1477 abandonSessionInternal(sessionId, callingUid, packageName); 1478 } 1479 1480 @Override openBlob(@onNull BlobHandle blobHandle, @NonNull String packageName)1481 public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle, 1482 @NonNull String packageName) { 1483 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1484 blobHandle.assertIsValid(); 1485 Objects.requireNonNull(packageName, "packageName must not be null"); 1486 1487 final int callingUid = Binder.getCallingUid(); 1488 verifyCallingPackage(callingUid, packageName); 1489 1490 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( 1491 packageName, UserHandle.getUserId(callingUid))) { 1492 throw new SecurityException("Caller not allowed to open blob; " 1493 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1494 } 1495 1496 try { 1497 return openBlobInternal(blobHandle, callingUid, packageName); 1498 } catch (IOException e) { 1499 throw ExceptionUtils.wrap(e); 1500 } 1501 } 1502 1503 @Override acquireLease(@onNull BlobHandle blobHandle, @IdRes int descriptionResId, @Nullable CharSequence description, @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName)1504 public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, 1505 @Nullable CharSequence description, 1506 @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) { 1507 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1508 blobHandle.assertIsValid(); 1509 Preconditions.checkArgument( 1510 ResourceId.isValid(descriptionResId) || description != null, 1511 "Description must be valid; descriptionId=" + descriptionResId 1512 + ", description=" + description); 1513 Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis, 1514 "leaseExpiryTimeMillis must not be negative"); 1515 Objects.requireNonNull(packageName, "packageName must not be null"); 1516 1517 description = BlobStoreConfig.getTruncatedLeaseDescription(description); 1518 1519 final int callingUid = Binder.getCallingUid(); 1520 verifyCallingPackage(callingUid, packageName); 1521 1522 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( 1523 packageName, UserHandle.getUserId(callingUid))) { 1524 throw new SecurityException("Caller not allowed to open blob; " 1525 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1526 } 1527 1528 try { 1529 acquireLeaseInternal(blobHandle, descriptionResId, description, 1530 leaseExpiryTimeMillis, callingUid, packageName); 1531 } catch (Resources.NotFoundException e) { 1532 throw new IllegalArgumentException(e); 1533 } catch (LimitExceededException e) { 1534 throw new ParcelableException(e); 1535 } 1536 } 1537 1538 @Override releaseLease(@onNull BlobHandle blobHandle, @NonNull String packageName)1539 public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) { 1540 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1541 blobHandle.assertIsValid(); 1542 Objects.requireNonNull(packageName, "packageName must not be null"); 1543 1544 final int callingUid = Binder.getCallingUid(); 1545 verifyCallingPackage(callingUid, packageName); 1546 1547 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( 1548 packageName, UserHandle.getUserId(callingUid))) { 1549 throw new SecurityException("Caller not allowed to open blob; " 1550 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1551 } 1552 1553 releaseLeaseInternal(blobHandle, callingUid, packageName); 1554 } 1555 1556 @Override getRemainingLeaseQuotaBytes(@onNull String packageName)1557 public long getRemainingLeaseQuotaBytes(@NonNull String packageName) { 1558 final int callingUid = Binder.getCallingUid(); 1559 verifyCallingPackage(callingUid, packageName); 1560 1561 return getRemainingLeaseQuotaBytesInternal(callingUid, packageName); 1562 } 1563 1564 @Override waitForIdle(@onNull RemoteCallback remoteCallback)1565 public void waitForIdle(@NonNull RemoteCallback remoteCallback) { 1566 Objects.requireNonNull(remoteCallback, "remoteCallback must not be null"); 1567 1568 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, 1569 "Caller is not allowed to call this; caller=" + Binder.getCallingUid()); 1570 // We post messages back and forth between mHandler thread and mBackgroundHandler 1571 // thread while committing a blob. We need to replicate the same pattern here to 1572 // ensure pending messages have been handled. 1573 mHandler.post(() -> { 1574 mBackgroundHandler.post(() -> { 1575 mHandler.post(PooledLambda.obtainRunnable(remoteCallback::sendResult, null) 1576 .recycleOnUse()); 1577 }); 1578 }); 1579 } 1580 1581 @Override 1582 @NonNull queryBlobsForUser(@serIdInt int userId)1583 public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) { 1584 if (Binder.getCallingUid() != Process.SYSTEM_UID) { 1585 throw new SecurityException("Only system uid is allowed to call " 1586 + "queryBlobsForUser()"); 1587 } 1588 1589 final int resolvedUserId = userId == USER_CURRENT 1590 ? ActivityManager.getCurrentUser() : userId; 1591 // Don't allow any other special user ids apart from USER_CURRENT 1592 final ActivityManagerInternal amInternal = LocalServices.getService( 1593 ActivityManagerInternal.class); 1594 amInternal.ensureNotSpecialUser(resolvedUserId); 1595 1596 return queryBlobsForUserInternal(resolvedUserId); 1597 } 1598 1599 @Override deleteBlob(long blobId)1600 public void deleteBlob(long blobId) { 1601 final int callingUid = Binder.getCallingUid(); 1602 if (callingUid != Process.SYSTEM_UID) { 1603 throw new SecurityException("Only system uid is allowed to call " 1604 + "deleteBlob()"); 1605 } 1606 1607 deleteBlobInternal(blobId, callingUid); 1608 } 1609 1610 @Override 1611 @NonNull getLeasedBlobs(@onNull String packageName)1612 public List<BlobHandle> getLeasedBlobs(@NonNull String packageName) { 1613 Objects.requireNonNull(packageName, "packageName must not be null"); 1614 1615 final int callingUid = Binder.getCallingUid(); 1616 verifyCallingPackage(callingUid, packageName); 1617 1618 return getLeasedBlobsInternal(callingUid, packageName); 1619 } 1620 1621 @Override 1622 @Nullable getLeaseInfo(@onNull BlobHandle blobHandle, @NonNull String packageName)1623 public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName) { 1624 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1625 blobHandle.assertIsValid(); 1626 Objects.requireNonNull(packageName, "packageName must not be null"); 1627 1628 final int callingUid = Binder.getCallingUid(); 1629 verifyCallingPackage(callingUid, packageName); 1630 1631 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( 1632 packageName, UserHandle.getUserId(callingUid))) { 1633 throw new SecurityException("Caller not allowed to open blob; " 1634 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1635 } 1636 1637 return getLeaseInfoInternal(blobHandle, callingUid, packageName); 1638 } 1639 1640 @Override dump(@onNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args)1641 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, 1642 @Nullable String[] args) { 1643 // TODO: add proto-based version of this. 1644 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return; 1645 1646 final DumpArgs dumpArgs = DumpArgs.parse(args); 1647 1648 final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); 1649 if (dumpArgs.shouldDumpHelp()) { 1650 writer.println("dumpsys blob_store [options]:"); 1651 fout.increaseIndent(); 1652 dumpArgs.dumpArgsUsage(fout); 1653 fout.decreaseIndent(); 1654 return; 1655 } 1656 1657 synchronized (mBlobsLock) { 1658 if (dumpArgs.shouldDumpAllSections()) { 1659 fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId); 1660 fout.println(); 1661 } 1662 1663 if (dumpArgs.shouldDumpSessions()) { 1664 dumpSessionsLocked(fout, dumpArgs); 1665 fout.println(); 1666 } 1667 if (dumpArgs.shouldDumpBlobs()) { 1668 dumpBlobsLocked(fout, dumpArgs); 1669 fout.println(); 1670 } 1671 } 1672 1673 if (dumpArgs.shouldDumpConfig()) { 1674 fout.println("BlobStore config:"); 1675 fout.increaseIndent(); 1676 BlobStoreConfig.dump(fout, mContext); 1677 fout.decreaseIndent(); 1678 fout.println(); 1679 } 1680 } 1681 1682 @Override handleShellCommand(@onNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args)1683 public int handleShellCommand(@NonNull ParcelFileDescriptor in, 1684 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, 1685 @NonNull String[] args) { 1686 return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this, 1687 in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); 1688 } 1689 } 1690 1691 static final class DumpArgs { 1692 private static final int FLAG_DUMP_SESSIONS = 1 << 0; 1693 private static final int FLAG_DUMP_BLOBS = 1 << 1; 1694 private static final int FLAG_DUMP_CONFIG = 1 << 2; 1695 1696 private int mSelectedSectionFlags; 1697 private boolean mDumpUnredacted; 1698 private final ArrayList<String> mDumpPackages = new ArrayList<>(); 1699 private final ArrayList<Integer> mDumpUids = new ArrayList<>(); 1700 private final ArrayList<Integer> mDumpUserIds = new ArrayList<>(); 1701 private final ArrayList<Long> mDumpBlobIds = new ArrayList<>(); 1702 private boolean mDumpHelp; 1703 private boolean mDumpAll; 1704 shouldDumpSession(String packageName, int uid, long blobId)1705 public boolean shouldDumpSession(String packageName, int uid, long blobId) { 1706 if (!CollectionUtils.isEmpty(mDumpPackages) 1707 && mDumpPackages.indexOf(packageName) < 0) { 1708 return false; 1709 } 1710 if (!CollectionUtils.isEmpty(mDumpUids) 1711 && mDumpUids.indexOf(uid) < 0) { 1712 return false; 1713 } 1714 if (!CollectionUtils.isEmpty(mDumpBlobIds) 1715 && mDumpBlobIds.indexOf(blobId) < 0) { 1716 return false; 1717 } 1718 return true; 1719 } 1720 shouldDumpAllSections()1721 public boolean shouldDumpAllSections() { 1722 return mDumpAll || (mSelectedSectionFlags == 0); 1723 } 1724 allowDumpSessions()1725 public void allowDumpSessions() { 1726 mSelectedSectionFlags |= FLAG_DUMP_SESSIONS; 1727 } 1728 shouldDumpSessions()1729 public boolean shouldDumpSessions() { 1730 if (shouldDumpAllSections()) { 1731 return true; 1732 } 1733 return (mSelectedSectionFlags & FLAG_DUMP_SESSIONS) != 0; 1734 } 1735 allowDumpBlobs()1736 public void allowDumpBlobs() { 1737 mSelectedSectionFlags |= FLAG_DUMP_BLOBS; 1738 } 1739 shouldDumpBlobs()1740 public boolean shouldDumpBlobs() { 1741 if (shouldDumpAllSections()) { 1742 return true; 1743 } 1744 return (mSelectedSectionFlags & FLAG_DUMP_BLOBS) != 0; 1745 } 1746 allowDumpConfig()1747 public void allowDumpConfig() { 1748 mSelectedSectionFlags |= FLAG_DUMP_CONFIG; 1749 } 1750 shouldDumpConfig()1751 public boolean shouldDumpConfig() { 1752 if (shouldDumpAllSections()) { 1753 return true; 1754 } 1755 return (mSelectedSectionFlags & FLAG_DUMP_CONFIG) != 0; 1756 } 1757 shouldDumpBlob(long blobId)1758 public boolean shouldDumpBlob(long blobId) { 1759 return CollectionUtils.isEmpty(mDumpBlobIds) 1760 || mDumpBlobIds.indexOf(blobId) >= 0; 1761 } 1762 shouldDumpFull()1763 public boolean shouldDumpFull() { 1764 return mDumpUnredacted; 1765 } 1766 shouldDumpUser(int userId)1767 public boolean shouldDumpUser(int userId) { 1768 return CollectionUtils.isEmpty(mDumpUserIds) 1769 || mDumpUserIds.indexOf(userId) >= 0; 1770 } 1771 shouldDumpHelp()1772 public boolean shouldDumpHelp() { 1773 return mDumpHelp; 1774 } 1775 DumpArgs()1776 private DumpArgs() {} 1777 parse(String[] args)1778 public static DumpArgs parse(String[] args) { 1779 final DumpArgs dumpArgs = new DumpArgs(); 1780 if (args == null) { 1781 return dumpArgs; 1782 } 1783 1784 for (int i = 0; i < args.length; ++i) { 1785 final String opt = args[i]; 1786 if ("--all".equals(opt) || "-a".equals(opt)) { 1787 dumpArgs.mDumpAll = true; 1788 } else if ("--unredacted".equals(opt) || "-u".equals(opt)) { 1789 final int callingUid = Binder.getCallingUid(); 1790 if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { 1791 dumpArgs.mDumpUnredacted = true; 1792 } 1793 } else if ("--sessions".equals(opt)) { 1794 dumpArgs.allowDumpSessions(); 1795 } else if ("--blobs".equals(opt)) { 1796 dumpArgs.allowDumpBlobs(); 1797 } else if ("--config".equals(opt)) { 1798 dumpArgs.allowDumpConfig(); 1799 } else if ("--package".equals(opt) || "-p".equals(opt)) { 1800 dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName")); 1801 } else if ("--uid".equals(opt)) { 1802 dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid")); 1803 } else if ("--user".equals(opt)) { 1804 dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId")); 1805 } else if ("--blob".equals(opt) || "-b".equals(opt)) { 1806 dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId")); 1807 } else if ("--help".equals(opt) || "-h".equals(opt)) { 1808 dumpArgs.mDumpHelp = true; 1809 } else { 1810 // Everything else is assumed to be blob ids. 1811 dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId")); 1812 } 1813 } 1814 return dumpArgs; 1815 } 1816 getStringArgRequired(String[] args, int index, String argName)1817 private static String getStringArgRequired(String[] args, int index, String argName) { 1818 if (index >= args.length) { 1819 throw new IllegalArgumentException("Missing " + argName); 1820 } 1821 return args[index]; 1822 } 1823 getIntArgRequired(String[] args, int index, String argName)1824 private static int getIntArgRequired(String[] args, int index, String argName) { 1825 if (index >= args.length) { 1826 throw new IllegalArgumentException("Missing " + argName); 1827 } 1828 final int value; 1829 try { 1830 value = Integer.parseInt(args[index]); 1831 } catch (NumberFormatException e) { 1832 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); 1833 } 1834 return value; 1835 } 1836 getLongArgRequired(String[] args, int index, String argName)1837 private static long getLongArgRequired(String[] args, int index, String argName) { 1838 if (index >= args.length) { 1839 throw new IllegalArgumentException("Missing " + argName); 1840 } 1841 final long value; 1842 try { 1843 value = Long.parseLong(args[index]); 1844 } catch (NumberFormatException e) { 1845 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); 1846 } 1847 return value; 1848 } 1849 dumpArgsUsage(IndentingPrintWriter pw)1850 private void dumpArgsUsage(IndentingPrintWriter pw) { 1851 pw.println("--help | -h"); 1852 printWithIndent(pw, "Dump this help text"); 1853 pw.println("--sessions"); 1854 printWithIndent(pw, "Dump only the sessions info"); 1855 pw.println("--blobs"); 1856 printWithIndent(pw, "Dump only the committed blobs info"); 1857 pw.println("--config"); 1858 printWithIndent(pw, "Dump only the config values"); 1859 pw.println("--package | -p [package-name]"); 1860 printWithIndent(pw, "Dump blobs info associated with the given package"); 1861 pw.println("--uid | -u [uid]"); 1862 printWithIndent(pw, "Dump blobs info associated with the given uid"); 1863 pw.println("--user [user-id]"); 1864 printWithIndent(pw, "Dump blobs info in the given user"); 1865 pw.println("--blob | -b [session-id | blob-id]"); 1866 printWithIndent(pw, "Dump blob info corresponding to the given ID"); 1867 pw.println("--full | -f"); 1868 printWithIndent(pw, "Dump full unredacted blobs data"); 1869 } 1870 printWithIndent(IndentingPrintWriter pw, String str)1871 private void printWithIndent(IndentingPrintWriter pw, String str) { 1872 pw.increaseIndent(); 1873 pw.println(str); 1874 pw.decreaseIndent(); 1875 } 1876 } 1877 registerBlobStorePuller()1878 private void registerBlobStorePuller() { 1879 mStatsManager.setPullAtomCallback( 1880 FrameworkStatsLog.BLOB_INFO, 1881 null, // use default PullAtomMetadata values 1882 BackgroundThread.getExecutor(), 1883 mStatsCallbackImpl 1884 ); 1885 } 1886 1887 private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { 1888 @Override onPullAtom(int atomTag, List<StatsEvent> data)1889 public int onPullAtom(int atomTag, List<StatsEvent> data) { 1890 switch (atomTag) { 1891 case FrameworkStatsLog.BLOB_INFO: 1892 return pullBlobData(atomTag, data); 1893 default: 1894 throw new UnsupportedOperationException("Unknown tagId=" + atomTag); 1895 } 1896 } 1897 } 1898 pullBlobData(int atomTag, List<StatsEvent> data)1899 private int pullBlobData(int atomTag, List<StatsEvent> data) { 1900 forEachBlob(blobMetadata -> data.add(blobMetadata.dumpAsStatsEvent(atomTag))); 1901 return StatsManager.PULL_SUCCESS; 1902 } 1903 1904 private class LocalService extends BlobStoreManagerInternal { 1905 @Override onIdleMaintenance()1906 public void onIdleMaintenance() { 1907 runIdleMaintenance(); 1908 } 1909 } 1910 1911 @VisibleForTesting 1912 static class Injector { initializeMessageHandler()1913 public Handler initializeMessageHandler() { 1914 return BlobStoreManagerService.initializeMessageHandler(); 1915 } 1916 getBackgroundHandler()1917 public Handler getBackgroundHandler() { 1918 return BackgroundThread.getHandler(); 1919 } 1920 } 1921 } 1922