1 /* 2 * Copyright 2020 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.Manifest.permission.ACCESS_BLOBS_ACROSS_USERS; 19 import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS; 20 import static android.app.blob.XmlTags.ATTR_DESCRIPTION; 21 import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME; 22 import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; 23 import static android.app.blob.XmlTags.ATTR_ID; 24 import static android.app.blob.XmlTags.ATTR_PACKAGE; 25 import static android.app.blob.XmlTags.ATTR_UID; 26 import static android.app.blob.XmlTags.ATTR_USER_ID; 27 import static android.app.blob.XmlTags.TAG_ACCESS_MODE; 28 import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; 29 import static android.app.blob.XmlTags.TAG_COMMITTER; 30 import static android.app.blob.XmlTags.TAG_LEASEE; 31 import static android.os.Process.INVALID_UID; 32 import static android.system.OsConstants.O_RDONLY; 33 import static android.text.format.Formatter.FLAG_IEC_UNITS; 34 import static android.text.format.Formatter.formatFileSize; 35 36 import static com.android.server.blob.BlobStoreConfig.TAG; 37 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME; 38 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME; 39 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC; 40 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS; 41 import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed; 42 import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId; 43 import static com.android.server.blob.BlobStoreUtils.getPackageResources; 44 45 import android.annotation.NonNull; 46 import android.annotation.Nullable; 47 import android.app.blob.BlobHandle; 48 import android.app.blob.LeaseInfo; 49 import android.content.Context; 50 import android.content.pm.PackageManager; 51 import android.content.res.ResourceId; 52 import android.content.res.Resources; 53 import android.os.Binder; 54 import android.os.ParcelFileDescriptor; 55 import android.os.RevocableFileDescriptor; 56 import android.os.UserHandle; 57 import android.permission.PermissionManager; 58 import android.system.ErrnoException; 59 import android.system.Os; 60 import android.util.ArrayMap; 61 import android.util.ArraySet; 62 import android.util.IndentingPrintWriter; 63 import android.util.Slog; 64 import android.util.SparseArray; 65 import android.util.StatsEvent; 66 import android.util.proto.ProtoOutputStream; 67 68 import com.android.internal.annotations.GuardedBy; 69 import com.android.internal.annotations.VisibleForTesting; 70 import com.android.internal.util.FrameworkStatsLog; 71 import com.android.internal.util.XmlUtils; 72 import com.android.server.blob.BlobStoreManagerService.DumpArgs; 73 74 import libcore.io.IoUtils; 75 76 import org.xmlpull.v1.XmlPullParser; 77 import org.xmlpull.v1.XmlPullParserException; 78 import org.xmlpull.v1.XmlSerializer; 79 80 import java.io.File; 81 import java.io.FileDescriptor; 82 import java.io.IOException; 83 import java.util.Objects; 84 import java.util.function.Consumer; 85 86 class BlobMetadata { 87 private final Object mMetadataLock = new Object(); 88 89 private final Context mContext; 90 91 private final long mBlobId; 92 private final BlobHandle mBlobHandle; 93 94 @GuardedBy("mMetadataLock") 95 private final ArraySet<Committer> mCommitters = new ArraySet<>(); 96 97 @GuardedBy("mMetadataLock") 98 private final ArraySet<Leasee> mLeasees = new ArraySet<>(); 99 100 /** 101 * Contains Accessor -> {RevocableFileDescriptors}. 102 * 103 * Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so 104 * that when clients access is revoked or the blob gets deleted, we can be sure that clients 105 * do not have any reference to the blob and the space occupied by the blob can be freed. 106 */ 107 @GuardedBy("mRevocableFds") 108 private final ArrayMap<Accessor, ArraySet<RevocableFileDescriptor>> mRevocableFds = 109 new ArrayMap<>(); 110 111 // Do not access this directly, instead use #getBlobFile(). 112 private File mBlobFile; 113 BlobMetadata(Context context, long blobId, BlobHandle blobHandle)114 BlobMetadata(Context context, long blobId, BlobHandle blobHandle) { 115 mContext = context; 116 this.mBlobId = blobId; 117 this.mBlobHandle = blobHandle; 118 } 119 getBlobId()120 long getBlobId() { 121 return mBlobId; 122 } 123 getBlobHandle()124 BlobHandle getBlobHandle() { 125 return mBlobHandle; 126 } 127 addOrReplaceCommitter(@onNull Committer committer)128 void addOrReplaceCommitter(@NonNull Committer committer) { 129 synchronized (mMetadataLock) { 130 // We need to override the committer data, so first remove any existing 131 // committer before adding the new one. 132 mCommitters.remove(committer); 133 mCommitters.add(committer); 134 } 135 } 136 setCommitters(ArraySet<Committer> committers)137 void setCommitters(ArraySet<Committer> committers) { 138 synchronized (mMetadataLock) { 139 mCommitters.clear(); 140 mCommitters.addAll(committers); 141 } 142 } 143 removeCommitter(@onNull String packageName, int uid)144 void removeCommitter(@NonNull String packageName, int uid) { 145 synchronized (mMetadataLock) { 146 mCommitters.removeIf((committer) -> 147 committer.uid == uid && committer.packageName.equals(packageName)); 148 } 149 } 150 removeCommitter(@onNull Committer committer)151 void removeCommitter(@NonNull Committer committer) { 152 synchronized (mMetadataLock) { 153 mCommitters.remove(committer); 154 } 155 } 156 removeCommittersFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages)157 void removeCommittersFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) { 158 synchronized (mMetadataLock) { 159 mCommitters.removeIf(committer -> { 160 final int userId = UserHandle.getUserId(committer.uid); 161 final SparseArray<String> userPackages = knownPackages.get(userId); 162 if (userPackages == null) { 163 return true; 164 } 165 return !committer.packageName.equals(userPackages.get(committer.uid)); 166 }); 167 } 168 } 169 addCommittersAndLeasees(BlobMetadata blobMetadata)170 void addCommittersAndLeasees(BlobMetadata blobMetadata) { 171 mCommitters.addAll(blobMetadata.mCommitters); 172 mLeasees.addAll(blobMetadata.mLeasees); 173 } 174 175 @Nullable getExistingCommitter(@onNull String packageName, int uid)176 Committer getExistingCommitter(@NonNull String packageName, int uid) { 177 synchronized (mCommitters) { 178 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 179 final Committer committer = mCommitters.valueAt(i); 180 if (committer.uid == uid && committer.packageName.equals(packageName)) { 181 return committer; 182 } 183 } 184 } 185 return null; 186 } 187 addOrReplaceLeasee(String callingPackage, int callingUid, int descriptionResId, CharSequence description, long leaseExpiryTimeMillis)188 void addOrReplaceLeasee(String callingPackage, int callingUid, int descriptionResId, 189 CharSequence description, long leaseExpiryTimeMillis) { 190 synchronized (mMetadataLock) { 191 // We need to override the leasee data, so first remove any existing 192 // leasee before adding the new one. 193 final Leasee leasee = new Leasee(mContext, callingPackage, callingUid, 194 descriptionResId, description, leaseExpiryTimeMillis); 195 mLeasees.remove(leasee); 196 mLeasees.add(leasee); 197 } 198 } 199 setLeasees(ArraySet<Leasee> leasees)200 void setLeasees(ArraySet<Leasee> leasees) { 201 synchronized (mMetadataLock) { 202 mLeasees.clear(); 203 mLeasees.addAll(leasees); 204 } 205 } 206 removeLeasee(String packageName, int uid)207 void removeLeasee(String packageName, int uid) { 208 synchronized (mMetadataLock) { 209 mLeasees.removeIf((leasee) -> 210 leasee.uid == uid && leasee.packageName.equals(packageName)); 211 } 212 } 213 removeLeaseesFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages)214 void removeLeaseesFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) { 215 synchronized (mMetadataLock) { 216 mLeasees.removeIf(leasee -> { 217 final int userId = UserHandle.getUserId(leasee.uid); 218 final SparseArray<String> userPackages = knownPackages.get(userId); 219 if (userPackages == null) { 220 return true; 221 } 222 return !leasee.packageName.equals(userPackages.get(leasee.uid)); 223 }); 224 } 225 } 226 removeExpiredLeases()227 void removeExpiredLeases() { 228 synchronized (mMetadataLock) { 229 mLeasees.removeIf(leasee -> !leasee.isStillValid()); 230 } 231 } 232 removeDataForUser(int userId)233 void removeDataForUser(int userId) { 234 synchronized (mMetadataLock) { 235 mCommitters.removeIf(committer -> (userId == UserHandle.getUserId(committer.uid))); 236 mLeasees.removeIf(leasee -> (userId == UserHandle.getUserId(leasee.uid))); 237 mRevocableFds.entrySet().removeIf(entry -> { 238 final Accessor accessor = entry.getKey(); 239 final ArraySet<RevocableFileDescriptor> rFds = entry.getValue(); 240 if (userId != UserHandle.getUserId(accessor.uid)) { 241 return false; 242 } 243 for (int i = 0, fdCount = rFds.size(); i < fdCount; ++i) { 244 rFds.valueAt(i).revoke(); 245 } 246 rFds.clear(); 247 return true; 248 }); 249 } 250 } 251 hasValidLeases()252 boolean hasValidLeases() { 253 synchronized (mMetadataLock) { 254 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 255 if (mLeasees.valueAt(i).isStillValid()) { 256 return true; 257 } 258 } 259 return false; 260 } 261 } 262 getSize()263 long getSize() { 264 return getBlobFile().length(); 265 } 266 isAccessAllowedForCaller(@onNull String callingPackage, int callingUid)267 boolean isAccessAllowedForCaller(@NonNull String callingPackage, int callingUid) { 268 // Don't allow the blob to be accessed after it's expiry time has passed. 269 if (getBlobHandle().isExpired()) { 270 return false; 271 } 272 synchronized (mMetadataLock) { 273 // Check if packageName already holds a lease on the blob. 274 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 275 final Leasee leasee = mLeasees.valueAt(i); 276 if (leasee.isStillValid() && leasee.equals(callingPackage, callingUid)) { 277 return true; 278 } 279 } 280 281 final int callingUserId = UserHandle.getUserId(callingUid); 282 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 283 final Committer committer = mCommitters.valueAt(i); 284 if (callingUserId != UserHandle.getUserId(committer.uid)) { 285 continue; 286 } 287 288 // Check if the caller is the same package that committed the blob. 289 if (committer.equals(callingPackage, callingUid)) { 290 return true; 291 } 292 293 // Check if the caller is allowed access as per the access mode specified 294 // by the committer. 295 if (committer.blobAccessMode.isAccessAllowedForCaller(mContext, 296 callingPackage, callingUid, committer.uid)) { 297 return true; 298 } 299 } 300 301 final boolean canCallerAccessBlobsAcrossUsers = checkCallerCanAccessBlobsAcrossUsers( 302 callingPackage, callingUserId); 303 if (!canCallerAccessBlobsAcrossUsers) { 304 return false; 305 } 306 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 307 final Committer committer = mCommitters.valueAt(i); 308 final int committerUserId = UserHandle.getUserId(committer.uid); 309 if (callingUserId == committerUserId) { 310 continue; 311 } 312 if (!isPackageInstalledOnUser(callingPackage, committerUserId)) { 313 continue; 314 } 315 316 // Check if the caller is allowed access as per the access mode specified 317 // by the committer. 318 if (committer.blobAccessMode.isAccessAllowedForCaller(mContext, 319 callingPackage, callingUid, committer.uid)) { 320 return true; 321 } 322 } 323 324 } 325 return false; 326 } 327 checkCallerCanAccessBlobsAcrossUsers( String callingPackage, int callingUserId)328 private static boolean checkCallerCanAccessBlobsAcrossUsers( 329 String callingPackage, int callingUserId) { 330 final long token = Binder.clearCallingIdentity(); 331 try { 332 return PermissionManager.checkPackageNamePermission(ACCESS_BLOBS_ACROSS_USERS, 333 callingPackage, callingUserId) == PackageManager.PERMISSION_GRANTED; 334 } finally { 335 Binder.restoreCallingIdentity(token); 336 } 337 } 338 isPackageInstalledOnUser(String packageName, int userId)339 private boolean isPackageInstalledOnUser(String packageName, int userId) { 340 final long token = Binder.clearCallingIdentity(); 341 try { 342 mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, userId); 343 return true; 344 } catch (PackageManager.NameNotFoundException e) { 345 return false; 346 } finally { 347 Binder.restoreCallingIdentity(token); 348 } 349 } 350 hasACommitterOrLeaseeInUser(int userId)351 boolean hasACommitterOrLeaseeInUser(int userId) { 352 return hasACommitterInUser(userId) || hasALeaseeInUser(userId); 353 } 354 hasACommitterInUser(int userId)355 boolean hasACommitterInUser(int userId) { 356 synchronized (mMetadataLock) { 357 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 358 final Committer committer = mCommitters.valueAt(i); 359 if (userId == UserHandle.getUserId(committer.uid)) { 360 return true; 361 } 362 } 363 } 364 return false; 365 } 366 hasALeaseeInUser(int userId)367 private boolean hasALeaseeInUser(int userId) { 368 synchronized (mMetadataLock) { 369 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 370 final Leasee leasee = mLeasees.valueAt(i); 371 if (userId == UserHandle.getUserId(leasee.uid)) { 372 return true; 373 } 374 } 375 } 376 return false; 377 } 378 isACommitter(@onNull String packageName, int uid)379 boolean isACommitter(@NonNull String packageName, int uid) { 380 synchronized (mMetadataLock) { 381 return isAnAccessor(mCommitters, packageName, uid, UserHandle.getUserId(uid)); 382 } 383 } 384 isALeasee(@ullable String packageName, int uid)385 boolean isALeasee(@Nullable String packageName, int uid) { 386 synchronized (mMetadataLock) { 387 final Leasee leasee = getAccessor(mLeasees, packageName, uid, 388 UserHandle.getUserId(uid)); 389 return leasee != null && leasee.isStillValid(); 390 } 391 } 392 isALeaseeInUser(@ullable String packageName, int uid, int userId)393 private boolean isALeaseeInUser(@Nullable String packageName, int uid, int userId) { 394 synchronized (mMetadataLock) { 395 final Leasee leasee = getAccessor(mLeasees, packageName, uid, userId); 396 return leasee != null && leasee.isStillValid(); 397 } 398 } 399 isAnAccessor(@onNull ArraySet<T> accessors, @Nullable String packageName, int uid, int userId)400 private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors, 401 @Nullable String packageName, int uid, int userId) { 402 // Check if the package is an accessor of the data blob. 403 return getAccessor(accessors, packageName, uid, userId) != null; 404 } 405 getAccessor(@onNull ArraySet<T> accessors, @Nullable String packageName, int uid, int userId)406 private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors, 407 @Nullable String packageName, int uid, int userId) { 408 // Check if the package is an accessor of the data blob. 409 for (int i = 0, size = accessors.size(); i < size; ++i) { 410 final Accessor accessor = accessors.valueAt(i); 411 if (packageName != null && uid != INVALID_UID 412 && accessor.equals(packageName, uid)) { 413 return (T) accessor; 414 } else if (packageName != null && accessor.packageName.equals(packageName) 415 && userId == UserHandle.getUserId(accessor.uid)) { 416 return (T) accessor; 417 } else if (uid != INVALID_UID && accessor.uid == uid) { 418 return (T) accessor; 419 } 420 } 421 return null; 422 } 423 shouldAttributeToUser(int userId)424 boolean shouldAttributeToUser(int userId) { 425 synchronized (mMetadataLock) { 426 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 427 final Leasee leasee = mLeasees.valueAt(i); 428 // Don't attribute the blob to userId if there is a lease on it from another user. 429 if (userId != UserHandle.getUserId(leasee.uid)) { 430 return false; 431 } 432 } 433 } 434 return true; 435 } 436 shouldAttributeToLeasee(@onNull String packageName, int userId, boolean callerHasStatsPermission)437 boolean shouldAttributeToLeasee(@NonNull String packageName, int userId, 438 boolean callerHasStatsPermission) { 439 if (!isALeaseeInUser(packageName, INVALID_UID, userId)) { 440 return false; 441 } 442 if (!callerHasStatsPermission || !hasOtherLeasees(packageName, INVALID_UID, userId)) { 443 return true; 444 } 445 return false; 446 } 447 shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission)448 boolean shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission) { 449 final int userId = UserHandle.getUserId(uid); 450 if (!isALeaseeInUser(null, uid, userId)) { 451 return false; 452 } 453 if (!callerHasStatsPermission || !hasOtherLeasees(null, uid, userId)) { 454 return true; 455 } 456 return false; 457 } 458 hasOtherLeasees(@ullable String packageName, int uid, int userId)459 private boolean hasOtherLeasees(@Nullable String packageName, int uid, int userId) { 460 synchronized (mMetadataLock) { 461 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 462 final Leasee leasee = mLeasees.valueAt(i); 463 if (!leasee.isStillValid()) { 464 continue; 465 } 466 // TODO: Also exclude packages which are signed with same cert? 467 if (packageName != null && uid != INVALID_UID 468 && !leasee.equals(packageName, uid)) { 469 return true; 470 } else if (packageName != null && (!leasee.packageName.equals(packageName) 471 || userId != UserHandle.getUserId(leasee.uid))) { 472 return true; 473 } else if (uid != INVALID_UID && leasee.uid != uid) { 474 return true; 475 } 476 } 477 } 478 return false; 479 } 480 481 @Nullable getLeaseInfo(@onNull String packageName, int uid)482 LeaseInfo getLeaseInfo(@NonNull String packageName, int uid) { 483 synchronized (mMetadataLock) { 484 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 485 final Leasee leasee = mLeasees.valueAt(i); 486 if (!leasee.isStillValid()) { 487 continue; 488 } 489 if (leasee.uid == uid && leasee.packageName.equals(packageName)) { 490 final int descriptionResId = leasee.descriptionResEntryName == null 491 ? Resources.ID_NULL 492 : BlobStoreUtils.getDescriptionResourceId( 493 mContext, leasee.descriptionResEntryName, leasee.packageName, 494 UserHandle.getUserId(leasee.uid)); 495 return new LeaseInfo(packageName, leasee.expiryTimeMillis, 496 descriptionResId, leasee.description); 497 } 498 } 499 } 500 return null; 501 } 502 forEachLeasee(Consumer<Leasee> consumer)503 void forEachLeasee(Consumer<Leasee> consumer) { 504 synchronized (mMetadataLock) { 505 mLeasees.forEach(consumer); 506 } 507 } 508 getBlobFile()509 File getBlobFile() { 510 if (mBlobFile == null) { 511 mBlobFile = BlobStoreConfig.getBlobFile(mBlobId); 512 } 513 return mBlobFile; 514 } 515 openForRead(String callingPackage, int callingUid)516 ParcelFileDescriptor openForRead(String callingPackage, int callingUid) throws IOException { 517 // TODO: Add limit on opened fds 518 FileDescriptor fd; 519 try { 520 fd = Os.open(getBlobFile().getPath(), O_RDONLY, 0); 521 } catch (ErrnoException e) { 522 throw e.rethrowAsIOException(); 523 } 524 try { 525 if (BlobStoreConfig.shouldUseRevocableFdForReads()) { 526 return createRevocableFd(fd, callingPackage, callingUid); 527 } else { 528 return new ParcelFileDescriptor(fd); 529 } 530 } catch (IOException e) { 531 IoUtils.closeQuietly(fd); 532 throw e; 533 } 534 } 535 536 @NonNull createRevocableFd(FileDescriptor fd, String callingPackage, int callingUid)537 private ParcelFileDescriptor createRevocableFd(FileDescriptor fd, 538 String callingPackage, int callingUid) throws IOException { 539 final RevocableFileDescriptor revocableFd = 540 new RevocableFileDescriptor(mContext, fd); 541 final Accessor accessor; 542 synchronized (mRevocableFds) { 543 accessor = new Accessor(callingPackage, callingUid); 544 ArraySet<RevocableFileDescriptor> revocableFdsForAccessor = 545 mRevocableFds.get(accessor); 546 if (revocableFdsForAccessor == null) { 547 revocableFdsForAccessor = new ArraySet<>(); 548 mRevocableFds.put(accessor, revocableFdsForAccessor); 549 } 550 revocableFdsForAccessor.add(revocableFd); 551 } 552 revocableFd.addOnCloseListener((e) -> { 553 synchronized (mRevocableFds) { 554 final ArraySet<RevocableFileDescriptor> revocableFdsForAccessor = 555 mRevocableFds.get(accessor); 556 if (revocableFdsForAccessor != null) { 557 revocableFdsForAccessor.remove(revocableFd); 558 if (revocableFdsForAccessor.isEmpty()) { 559 mRevocableFds.remove(accessor); 560 } 561 } 562 } 563 }); 564 return revocableFd.getRevocableFileDescriptor(); 565 } 566 destroy()567 void destroy() { 568 revokeAndClearAllFds(); 569 getBlobFile().delete(); 570 } 571 revokeAndClearAllFds()572 private void revokeAndClearAllFds() { 573 synchronized (mRevocableFds) { 574 for (int i = 0, accessorCount = mRevocableFds.size(); i < accessorCount; ++i) { 575 final ArraySet<RevocableFileDescriptor> rFds = 576 mRevocableFds.valueAt(i); 577 if (rFds == null) { 578 continue; 579 } 580 for (int j = 0, fdCount = rFds.size(); j < fdCount; ++j) { 581 rFds.valueAt(j).revoke(); 582 } 583 } 584 mRevocableFds.clear(); 585 } 586 } 587 shouldBeDeleted(boolean respectLeaseWaitTime)588 boolean shouldBeDeleted(boolean respectLeaseWaitTime) { 589 // Expired data blobs 590 if (getBlobHandle().isExpired()) { 591 return true; 592 } 593 594 // Blobs with no active leases 595 if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsedForAll()) 596 && !hasValidLeases()) { 597 return true; 598 } 599 600 return false; 601 } 602 603 @VisibleForTesting hasLeaseWaitTimeElapsedForAll()604 boolean hasLeaseWaitTimeElapsedForAll() { 605 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 606 final Committer committer = mCommitters.valueAt(i); 607 if (!hasLeaseWaitTimeElapsed(committer.getCommitTimeMs())) { 608 return false; 609 } 610 } 611 return true; 612 } 613 dumpAsStatsEvent(int atomTag)614 StatsEvent dumpAsStatsEvent(int atomTag) { 615 synchronized (mMetadataLock) { 616 ProtoOutputStream proto = new ProtoOutputStream(); 617 // Write Committer data to proto format 618 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 619 final Committer committer = mCommitters.valueAt(i); 620 final long token = proto.start( 621 BlobStatsEventProto.BlobCommitterListProto.COMMITTER); 622 proto.write(BlobStatsEventProto.BlobCommitterProto.UID, committer.uid); 623 proto.write(BlobStatsEventProto.BlobCommitterProto.COMMIT_TIMESTAMP_MILLIS, 624 committer.commitTimeMs); 625 proto.write(BlobStatsEventProto.BlobCommitterProto.ACCESS_MODE, 626 committer.blobAccessMode.getAccessType()); 627 proto.write(BlobStatsEventProto.BlobCommitterProto.NUM_WHITELISTED_PACKAGE, 628 committer.blobAccessMode.getAllowedPackagesCount()); 629 proto.end(token); 630 } 631 final byte[] committersBytes = proto.getBytes(); 632 633 proto = new ProtoOutputStream(); 634 // Write Leasee data to proto format 635 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 636 final Leasee leasee = mLeasees.valueAt(i); 637 final long token = proto.start(BlobStatsEventProto.BlobLeaseeListProto.LEASEE); 638 proto.write(BlobStatsEventProto.BlobLeaseeProto.UID, leasee.uid); 639 proto.write(BlobStatsEventProto.BlobLeaseeProto.LEASE_EXPIRY_TIMESTAMP_MILLIS, 640 leasee.expiryTimeMillis); 641 proto.end(token); 642 } 643 final byte[] leaseesBytes = proto.getBytes(); 644 645 // Construct the StatsEvent to represent this Blob 646 return FrameworkStatsLog.buildStatsEvent(atomTag, mBlobId, getSize(), 647 mBlobHandle.getExpiryTimeMillis(), committersBytes, leaseesBytes); 648 } 649 } 650 dump(IndentingPrintWriter fout, DumpArgs dumpArgs)651 void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { 652 synchronized (mMetadataLock) { 653 fout.println("blobHandle:"); 654 fout.increaseIndent(); 655 mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); 656 fout.decreaseIndent(); 657 fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); 658 659 fout.println("Committers:"); 660 fout.increaseIndent(); 661 if (mCommitters.isEmpty()) { 662 fout.println("<empty>"); 663 } else { 664 for (int i = 0, count = mCommitters.size(); i < count; ++i) { 665 final Committer committer = mCommitters.valueAt(i); 666 fout.println("committer " + committer.toString()); 667 fout.increaseIndent(); 668 committer.dump(fout); 669 fout.decreaseIndent(); 670 } 671 } 672 fout.decreaseIndent(); 673 674 fout.println("Leasees:"); 675 fout.increaseIndent(); 676 if (mLeasees.isEmpty()) { 677 fout.println("<empty>"); 678 } else { 679 for (int i = 0, count = mLeasees.size(); i < count; ++i) { 680 final Leasee leasee = mLeasees.valueAt(i); 681 fout.println("leasee " + leasee.toString()); 682 fout.increaseIndent(); 683 leasee.dump(mContext, fout); 684 fout.decreaseIndent(); 685 } 686 } 687 fout.decreaseIndent(); 688 689 fout.println("Open fds:"); 690 fout.increaseIndent(); 691 if (mRevocableFds.isEmpty()) { 692 fout.println("<empty>"); 693 } else { 694 for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { 695 final Accessor accessor = mRevocableFds.keyAt(i); 696 final ArraySet<RevocableFileDescriptor> rFds = 697 mRevocableFds.valueAt(i); 698 fout.println(accessor + ": #" + rFds.size()); 699 } 700 } 701 fout.decreaseIndent(); 702 } 703 } 704 writeToXml(XmlSerializer out)705 void writeToXml(XmlSerializer out) throws IOException { 706 synchronized (mMetadataLock) { 707 XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId); 708 709 out.startTag(null, TAG_BLOB_HANDLE); 710 mBlobHandle.writeToXml(out); 711 out.endTag(null, TAG_BLOB_HANDLE); 712 713 for (int i = 0, count = mCommitters.size(); i < count; ++i) { 714 out.startTag(null, TAG_COMMITTER); 715 mCommitters.valueAt(i).writeToXml(out); 716 out.endTag(null, TAG_COMMITTER); 717 } 718 719 for (int i = 0, count = mLeasees.size(); i < count; ++i) { 720 out.startTag(null, TAG_LEASEE); 721 mLeasees.valueAt(i).writeToXml(out); 722 out.endTag(null, TAG_LEASEE); 723 } 724 } 725 } 726 727 @Nullable createFromXml(XmlPullParser in, int version, Context context)728 static BlobMetadata createFromXml(XmlPullParser in, int version, Context context) 729 throws XmlPullParserException, IOException { 730 final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID); 731 if (version < XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) { 732 XmlUtils.readIntAttribute(in, ATTR_USER_ID); 733 } 734 735 BlobHandle blobHandle = null; 736 final ArraySet<Committer> committers = new ArraySet<>(); 737 final ArraySet<Leasee> leasees = new ArraySet<>(); 738 final int depth = in.getDepth(); 739 while (XmlUtils.nextElementWithin(in, depth)) { 740 if (TAG_BLOB_HANDLE.equals(in.getName())) { 741 blobHandle = BlobHandle.createFromXml(in); 742 } else if (TAG_COMMITTER.equals(in.getName())) { 743 final Committer committer = Committer.createFromXml(in, version); 744 if (committer != null) { 745 committers.add(committer); 746 } 747 } else if (TAG_LEASEE.equals(in.getName())) { 748 leasees.add(Leasee.createFromXml(in, version)); 749 } 750 } 751 752 if (blobHandle == null) { 753 Slog.wtf(TAG, "blobHandle should be available"); 754 return null; 755 } 756 757 final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle); 758 blobMetadata.setCommitters(committers); 759 blobMetadata.setLeasees(leasees); 760 return blobMetadata; 761 } 762 763 static final class Committer extends Accessor { 764 public final BlobAccessMode blobAccessMode; 765 public final long commitTimeMs; 766 Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs)767 Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs) { 768 super(packageName, uid); 769 this.blobAccessMode = blobAccessMode; 770 this.commitTimeMs = commitTimeMs; 771 } 772 getCommitTimeMs()773 long getCommitTimeMs() { 774 return commitTimeMs; 775 } 776 dump(IndentingPrintWriter fout)777 void dump(IndentingPrintWriter fout) { 778 fout.println("commit time: " 779 + (commitTimeMs == 0 ? "<null>" : BlobStoreUtils.formatTime(commitTimeMs))); 780 fout.println("accessMode:"); 781 fout.increaseIndent(); 782 blobAccessMode.dump(fout); 783 fout.decreaseIndent(); 784 } 785 writeToXml(@onNull XmlSerializer out)786 void writeToXml(@NonNull XmlSerializer out) throws IOException { 787 XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); 788 XmlUtils.writeIntAttribute(out, ATTR_UID, uid); 789 XmlUtils.writeLongAttribute(out, ATTR_COMMIT_TIME_MS, commitTimeMs); 790 791 out.startTag(null, TAG_ACCESS_MODE); 792 blobAccessMode.writeToXml(out); 793 out.endTag(null, TAG_ACCESS_MODE); 794 } 795 796 @Nullable createFromXml(@onNull XmlPullParser in, int version)797 static Committer createFromXml(@NonNull XmlPullParser in, int version) 798 throws XmlPullParserException, IOException { 799 final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); 800 final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); 801 final long commitTimeMs = version >= XML_VERSION_ADD_COMMIT_TIME 802 ? XmlUtils.readLongAttribute(in, ATTR_COMMIT_TIME_MS) 803 : 0; 804 805 final int depth = in.getDepth(); 806 BlobAccessMode blobAccessMode = null; 807 while (XmlUtils.nextElementWithin(in, depth)) { 808 if (TAG_ACCESS_MODE.equals(in.getName())) { 809 blobAccessMode = BlobAccessMode.createFromXml(in); 810 } 811 } 812 if (blobAccessMode == null) { 813 Slog.wtf(TAG, "blobAccessMode should be available"); 814 return null; 815 } 816 return new Committer(packageName, uid, blobAccessMode, commitTimeMs); 817 } 818 } 819 820 static final class Leasee extends Accessor { 821 public final String descriptionResEntryName; 822 public final CharSequence description; 823 public final long expiryTimeMillis; 824 Leasee(@onNull Context context, @NonNull String packageName, int uid, int descriptionResId, @Nullable CharSequence description, long expiryTimeMillis)825 Leasee(@NonNull Context context, @NonNull String packageName, 826 int uid, int descriptionResId, 827 @Nullable CharSequence description, long expiryTimeMillis) { 828 super(packageName, uid); 829 final Resources packageResources = getPackageResources(context, packageName, 830 UserHandle.getUserId(uid)); 831 this.descriptionResEntryName = getResourceEntryName(packageResources, descriptionResId); 832 this.expiryTimeMillis = expiryTimeMillis; 833 this.description = description == null 834 ? getDescription(packageResources, descriptionResId) 835 : description; 836 } 837 Leasee(String packageName, int uid, @Nullable String descriptionResEntryName, @Nullable CharSequence description, long expiryTimeMillis)838 Leasee(String packageName, int uid, @Nullable String descriptionResEntryName, 839 @Nullable CharSequence description, long expiryTimeMillis) { 840 super(packageName, uid); 841 this.descriptionResEntryName = descriptionResEntryName; 842 this.expiryTimeMillis = expiryTimeMillis; 843 this.description = description; 844 } 845 846 @Nullable getResourceEntryName(@ullable Resources packageResources, int resId)847 private static String getResourceEntryName(@Nullable Resources packageResources, 848 int resId) { 849 if (!ResourceId.isValid(resId) || packageResources == null) { 850 return null; 851 } 852 return packageResources.getResourceEntryName(resId); 853 } 854 855 @Nullable getDescription(@onNull Context context, @NonNull String descriptionResEntryName, @NonNull String packageName, int userId)856 private static String getDescription(@NonNull Context context, 857 @NonNull String descriptionResEntryName, @NonNull String packageName, int userId) { 858 if (descriptionResEntryName == null || descriptionResEntryName.isEmpty()) { 859 return null; 860 } 861 final Resources resources = getPackageResources(context, packageName, userId); 862 if (resources == null) { 863 return null; 864 } 865 final int resId = getDescriptionResourceId(resources, descriptionResEntryName, 866 packageName); 867 return resId == Resources.ID_NULL ? null : resources.getString(resId); 868 } 869 870 @Nullable getDescription(@ullable Resources packageResources, int descriptionResId)871 private static String getDescription(@Nullable Resources packageResources, 872 int descriptionResId) { 873 if (!ResourceId.isValid(descriptionResId) || packageResources == null) { 874 return null; 875 } 876 return packageResources.getString(descriptionResId); 877 } 878 isStillValid()879 boolean isStillValid() { 880 return expiryTimeMillis == 0 || expiryTimeMillis >= System.currentTimeMillis(); 881 } 882 dump(@onNull Context context, @NonNull IndentingPrintWriter fout)883 void dump(@NonNull Context context, @NonNull IndentingPrintWriter fout) { 884 fout.println("desc: " + getDescriptionToDump(context)); 885 fout.println("expiryMs: " + expiryTimeMillis); 886 } 887 888 @NonNull getDescriptionToDump(@onNull Context context)889 private String getDescriptionToDump(@NonNull Context context) { 890 String desc = getDescription(context, descriptionResEntryName, packageName, 891 UserHandle.getUserId(uid)); 892 if (desc == null) { 893 desc = description.toString(); 894 } 895 return desc == null ? "<none>" : desc; 896 } 897 writeToXml(@onNull XmlSerializer out)898 void writeToXml(@NonNull XmlSerializer out) throws IOException { 899 XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); 900 XmlUtils.writeIntAttribute(out, ATTR_UID, uid); 901 XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION_RES_NAME, descriptionResEntryName); 902 XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis); 903 XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION, description); 904 } 905 906 @NonNull createFromXml(@onNull XmlPullParser in, int version)907 static Leasee createFromXml(@NonNull XmlPullParser in, int version) 908 throws IOException { 909 final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); 910 final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); 911 final String descriptionResEntryName; 912 if (version >= XML_VERSION_ADD_DESC_RES_NAME) { 913 descriptionResEntryName = XmlUtils.readStringAttribute( 914 in, ATTR_DESCRIPTION_RES_NAME); 915 } else { 916 descriptionResEntryName = null; 917 } 918 final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME); 919 final CharSequence description; 920 if (version >= XML_VERSION_ADD_STRING_DESC) { 921 description = XmlUtils.readStringAttribute(in, ATTR_DESCRIPTION); 922 } else { 923 description = null; 924 } 925 926 return new Leasee(packageName, uid, descriptionResEntryName, 927 description, expiryTimeMillis); 928 } 929 } 930 931 static class Accessor { 932 public final String packageName; 933 public final int uid; 934 Accessor(String packageName, int uid)935 Accessor(String packageName, int uid) { 936 this.packageName = packageName; 937 this.uid = uid; 938 } 939 equals(String packageName, int uid)940 public boolean equals(String packageName, int uid) { 941 return this.uid == uid && this.packageName.equals(packageName); 942 } 943 944 @Override equals(Object obj)945 public boolean equals(Object obj) { 946 if (this == obj) { 947 return true; 948 } 949 if (obj == null || !(obj instanceof Accessor)) { 950 return false; 951 } 952 final Accessor other = (Accessor) obj; 953 return this.uid == other.uid && this.packageName.equals(other.packageName); 954 } 955 956 @Override hashCode()957 public int hashCode() { 958 return Objects.hash(packageName, uid); 959 } 960 961 @Override toString()962 public String toString() { 963 return "[" + packageName + ", " + uid + "]"; 964 } 965 } 966 } 967