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.Process; 56 import android.os.RevocableFileDescriptor; 57 import android.os.UserHandle; 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 = 302 checkCallerCanAccessBlobsAcrossUsers(callingUid); 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(int callingUid)328 private boolean checkCallerCanAccessBlobsAcrossUsers(int callingUid) { 329 final long token = Binder.clearCallingIdentity(); 330 try { 331 return mContext.checkPermission(ACCESS_BLOBS_ACROSS_USERS, 332 Process.INVALID_PID, callingUid) == PackageManager.PERMISSION_GRANTED; 333 } finally { 334 Binder.restoreCallingIdentity(token); 335 } 336 } 337 isPackageInstalledOnUser(String packageName, int userId)338 private boolean isPackageInstalledOnUser(String packageName, int userId) { 339 final long token = Binder.clearCallingIdentity(); 340 try { 341 mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, userId); 342 return true; 343 } catch (PackageManager.NameNotFoundException e) { 344 return false; 345 } finally { 346 Binder.restoreCallingIdentity(token); 347 } 348 } 349 hasACommitterOrLeaseeInUser(int userId)350 boolean hasACommitterOrLeaseeInUser(int userId) { 351 return hasACommitterInUser(userId) || hasALeaseeInUser(userId); 352 } 353 hasACommitterInUser(int userId)354 boolean hasACommitterInUser(int userId) { 355 synchronized (mMetadataLock) { 356 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 357 final Committer committer = mCommitters.valueAt(i); 358 if (userId == UserHandle.getUserId(committer.uid)) { 359 return true; 360 } 361 } 362 } 363 return false; 364 } 365 hasALeaseeInUser(int userId)366 private boolean hasALeaseeInUser(int userId) { 367 synchronized (mMetadataLock) { 368 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 369 final Leasee leasee = mLeasees.valueAt(i); 370 if (userId == UserHandle.getUserId(leasee.uid)) { 371 return true; 372 } 373 } 374 } 375 return false; 376 } 377 isACommitter(@onNull String packageName, int uid)378 boolean isACommitter(@NonNull String packageName, int uid) { 379 synchronized (mMetadataLock) { 380 return isAnAccessor(mCommitters, packageName, uid, UserHandle.getUserId(uid)); 381 } 382 } 383 isALeasee(@ullable String packageName, int uid)384 boolean isALeasee(@Nullable String packageName, int uid) { 385 synchronized (mMetadataLock) { 386 final Leasee leasee = getAccessor(mLeasees, packageName, uid, 387 UserHandle.getUserId(uid)); 388 return leasee != null && leasee.isStillValid(); 389 } 390 } 391 isALeaseeInUser(@ullable String packageName, int uid, int userId)392 private boolean isALeaseeInUser(@Nullable String packageName, int uid, int userId) { 393 synchronized (mMetadataLock) { 394 final Leasee leasee = getAccessor(mLeasees, packageName, uid, userId); 395 return leasee != null && leasee.isStillValid(); 396 } 397 } 398 isAnAccessor(@onNull ArraySet<T> accessors, @Nullable String packageName, int uid, int userId)399 private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors, 400 @Nullable String packageName, int uid, int userId) { 401 // Check if the package is an accessor of the data blob. 402 return getAccessor(accessors, packageName, uid, userId) != null; 403 } 404 getAccessor(@onNull ArraySet<T> accessors, @Nullable String packageName, int uid, int userId)405 private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors, 406 @Nullable String packageName, int uid, int userId) { 407 // Check if the package is an accessor of the data blob. 408 for (int i = 0, size = accessors.size(); i < size; ++i) { 409 final Accessor accessor = accessors.valueAt(i); 410 if (packageName != null && uid != INVALID_UID 411 && accessor.equals(packageName, uid)) { 412 return (T) accessor; 413 } else if (packageName != null && accessor.packageName.equals(packageName) 414 && userId == UserHandle.getUserId(accessor.uid)) { 415 return (T) accessor; 416 } else if (uid != INVALID_UID && accessor.uid == uid) { 417 return (T) accessor; 418 } 419 } 420 return null; 421 } 422 shouldAttributeToUser(int userId)423 boolean shouldAttributeToUser(int userId) { 424 synchronized (mMetadataLock) { 425 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 426 final Leasee leasee = mLeasees.valueAt(i); 427 // Don't attribute the blob to userId if there is a lease on it from another user. 428 if (userId != UserHandle.getUserId(leasee.uid)) { 429 return false; 430 } 431 } 432 } 433 return true; 434 } 435 shouldAttributeToLeasee(@onNull String packageName, int userId, boolean callerHasStatsPermission)436 boolean shouldAttributeToLeasee(@NonNull String packageName, int userId, 437 boolean callerHasStatsPermission) { 438 if (!isALeaseeInUser(packageName, INVALID_UID, userId)) { 439 return false; 440 } 441 if (!callerHasStatsPermission || !hasOtherLeasees(packageName, INVALID_UID, userId)) { 442 return true; 443 } 444 return false; 445 } 446 shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission)447 boolean shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission) { 448 final int userId = UserHandle.getUserId(uid); 449 if (!isALeaseeInUser(null, uid, userId)) { 450 return false; 451 } 452 if (!callerHasStatsPermission || !hasOtherLeasees(null, uid, userId)) { 453 return true; 454 } 455 return false; 456 } 457 hasOtherLeasees(@ullable String packageName, int uid, int userId)458 private boolean hasOtherLeasees(@Nullable String packageName, int uid, int userId) { 459 synchronized (mMetadataLock) { 460 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 461 final Leasee leasee = mLeasees.valueAt(i); 462 if (!leasee.isStillValid()) { 463 continue; 464 } 465 // TODO: Also exclude packages which are signed with same cert? 466 if (packageName != null && uid != INVALID_UID 467 && !leasee.equals(packageName, uid)) { 468 return true; 469 } else if (packageName != null && (!leasee.packageName.equals(packageName) 470 || userId != UserHandle.getUserId(leasee.uid))) { 471 return true; 472 } else if (uid != INVALID_UID && leasee.uid != uid) { 473 return true; 474 } 475 } 476 } 477 return false; 478 } 479 480 @Nullable getLeaseInfo(@onNull String packageName, int uid)481 LeaseInfo getLeaseInfo(@NonNull String packageName, int uid) { 482 synchronized (mMetadataLock) { 483 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 484 final Leasee leasee = mLeasees.valueAt(i); 485 if (!leasee.isStillValid()) { 486 continue; 487 } 488 if (leasee.uid == uid && leasee.packageName.equals(packageName)) { 489 final int descriptionResId = leasee.descriptionResEntryName == null 490 ? Resources.ID_NULL 491 : BlobStoreUtils.getDescriptionResourceId( 492 mContext, leasee.descriptionResEntryName, leasee.packageName, 493 UserHandle.getUserId(leasee.uid)); 494 return new LeaseInfo(packageName, leasee.expiryTimeMillis, 495 descriptionResId, leasee.description); 496 } 497 } 498 } 499 return null; 500 } 501 forEachLeasee(Consumer<Leasee> consumer)502 void forEachLeasee(Consumer<Leasee> consumer) { 503 synchronized (mMetadataLock) { 504 mLeasees.forEach(consumer); 505 } 506 } 507 getBlobFile()508 File getBlobFile() { 509 if (mBlobFile == null) { 510 mBlobFile = BlobStoreConfig.getBlobFile(mBlobId); 511 } 512 return mBlobFile; 513 } 514 openForRead(String callingPackage, int callingUid)515 ParcelFileDescriptor openForRead(String callingPackage, int callingUid) throws IOException { 516 // TODO: Add limit on opened fds 517 FileDescriptor fd; 518 try { 519 fd = Os.open(getBlobFile().getPath(), O_RDONLY, 0); 520 } catch (ErrnoException e) { 521 throw e.rethrowAsIOException(); 522 } 523 try { 524 if (BlobStoreConfig.shouldUseRevocableFdForReads()) { 525 return createRevocableFd(fd, callingPackage, callingUid); 526 } else { 527 return new ParcelFileDescriptor(fd); 528 } 529 } catch (IOException e) { 530 IoUtils.closeQuietly(fd); 531 throw e; 532 } 533 } 534 535 @NonNull createRevocableFd(FileDescriptor fd, String callingPackage, int callingUid)536 private ParcelFileDescriptor createRevocableFd(FileDescriptor fd, 537 String callingPackage, int callingUid) throws IOException { 538 final RevocableFileDescriptor revocableFd = 539 new RevocableFileDescriptor(mContext, fd, BlobStoreUtils.getRevocableFdHandler()); 540 final Accessor accessor; 541 synchronized (mRevocableFds) { 542 accessor = new Accessor(callingPackage, callingUid); 543 ArraySet<RevocableFileDescriptor> revocableFdsForAccessor = 544 mRevocableFds.get(accessor); 545 if (revocableFdsForAccessor == null) { 546 revocableFdsForAccessor = new ArraySet<>(); 547 mRevocableFds.put(accessor, revocableFdsForAccessor); 548 } 549 revocableFdsForAccessor.add(revocableFd); 550 } 551 revocableFd.addOnCloseListener((e) -> { 552 synchronized (mRevocableFds) { 553 final ArraySet<RevocableFileDescriptor> revocableFdsForAccessor = 554 mRevocableFds.get(accessor); 555 if (revocableFdsForAccessor != null) { 556 revocableFdsForAccessor.remove(revocableFd); 557 if (revocableFdsForAccessor.isEmpty()) { 558 mRevocableFds.remove(accessor); 559 } 560 } 561 } 562 }); 563 return revocableFd.getRevocableFileDescriptor(); 564 } 565 destroy()566 void destroy() { 567 revokeAndClearAllFds(); 568 getBlobFile().delete(); 569 } 570 revokeAndClearAllFds()571 private void revokeAndClearAllFds() { 572 synchronized (mRevocableFds) { 573 for (int i = 0, accessorCount = mRevocableFds.size(); i < accessorCount; ++i) { 574 final ArraySet<RevocableFileDescriptor> rFds = 575 mRevocableFds.valueAt(i); 576 if (rFds == null) { 577 continue; 578 } 579 for (int j = 0, fdCount = rFds.size(); j < fdCount; ++j) { 580 rFds.valueAt(j).revoke(); 581 } 582 } 583 mRevocableFds.clear(); 584 } 585 } 586 shouldBeDeleted(boolean respectLeaseWaitTime)587 boolean shouldBeDeleted(boolean respectLeaseWaitTime) { 588 // Expired data blobs 589 if (getBlobHandle().isExpired()) { 590 return true; 591 } 592 593 // Blobs with no active leases 594 if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsedForAll()) 595 && !hasValidLeases()) { 596 return true; 597 } 598 599 return false; 600 } 601 602 @VisibleForTesting hasLeaseWaitTimeElapsedForAll()603 boolean hasLeaseWaitTimeElapsedForAll() { 604 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 605 final Committer committer = mCommitters.valueAt(i); 606 if (!hasLeaseWaitTimeElapsed(committer.getCommitTimeMs())) { 607 return false; 608 } 609 } 610 return true; 611 } 612 dumpAsStatsEvent(int atomTag)613 StatsEvent dumpAsStatsEvent(int atomTag) { 614 synchronized (mMetadataLock) { 615 ProtoOutputStream proto = new ProtoOutputStream(); 616 // Write Committer data to proto format 617 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 618 final Committer committer = mCommitters.valueAt(i); 619 final long token = proto.start( 620 BlobStatsEventProto.BlobCommitterListProto.COMMITTER); 621 proto.write(BlobStatsEventProto.BlobCommitterProto.UID, committer.uid); 622 proto.write(BlobStatsEventProto.BlobCommitterProto.COMMIT_TIMESTAMP_MILLIS, 623 committer.commitTimeMs); 624 proto.write(BlobStatsEventProto.BlobCommitterProto.ACCESS_MODE, 625 committer.blobAccessMode.getAccessType()); 626 proto.write(BlobStatsEventProto.BlobCommitterProto.NUM_WHITELISTED_PACKAGE, 627 committer.blobAccessMode.getAllowedPackagesCount()); 628 proto.end(token); 629 } 630 final byte[] committersBytes = proto.getBytes(); 631 632 proto = new ProtoOutputStream(); 633 // Write Leasee data to proto format 634 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 635 final Leasee leasee = mLeasees.valueAt(i); 636 final long token = proto.start(BlobStatsEventProto.BlobLeaseeListProto.LEASEE); 637 proto.write(BlobStatsEventProto.BlobLeaseeProto.UID, leasee.uid); 638 proto.write(BlobStatsEventProto.BlobLeaseeProto.LEASE_EXPIRY_TIMESTAMP_MILLIS, 639 leasee.expiryTimeMillis); 640 proto.end(token); 641 } 642 final byte[] leaseesBytes = proto.getBytes(); 643 644 // Construct the StatsEvent to represent this Blob 645 return FrameworkStatsLog.buildStatsEvent(atomTag, mBlobId, getSize(), 646 mBlobHandle.getExpiryTimeMillis(), committersBytes, leaseesBytes); 647 } 648 } 649 dump(IndentingPrintWriter fout, DumpArgs dumpArgs)650 void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { 651 synchronized (mMetadataLock) { 652 fout.println("blobHandle:"); 653 fout.increaseIndent(); 654 mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); 655 fout.decreaseIndent(); 656 fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); 657 658 fout.println("Committers:"); 659 fout.increaseIndent(); 660 if (mCommitters.isEmpty()) { 661 fout.println("<empty>"); 662 } else { 663 for (int i = 0, count = mCommitters.size(); i < count; ++i) { 664 final Committer committer = mCommitters.valueAt(i); 665 fout.println("committer " + committer.toString()); 666 fout.increaseIndent(); 667 committer.dump(fout); 668 fout.decreaseIndent(); 669 } 670 } 671 fout.decreaseIndent(); 672 673 fout.println("Leasees:"); 674 fout.increaseIndent(); 675 if (mLeasees.isEmpty()) { 676 fout.println("<empty>"); 677 } else { 678 for (int i = 0, count = mLeasees.size(); i < count; ++i) { 679 final Leasee leasee = mLeasees.valueAt(i); 680 fout.println("leasee " + leasee.toString()); 681 fout.increaseIndent(); 682 leasee.dump(mContext, fout); 683 fout.decreaseIndent(); 684 } 685 } 686 fout.decreaseIndent(); 687 688 fout.println("Open fds:"); 689 fout.increaseIndent(); 690 if (mRevocableFds.isEmpty()) { 691 fout.println("<empty>"); 692 } else { 693 for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { 694 final Accessor accessor = mRevocableFds.keyAt(i); 695 final ArraySet<RevocableFileDescriptor> rFds = 696 mRevocableFds.valueAt(i); 697 fout.println(accessor + ": #" + rFds.size()); 698 } 699 } 700 fout.decreaseIndent(); 701 } 702 } 703 writeToXml(XmlSerializer out)704 void writeToXml(XmlSerializer out) throws IOException { 705 synchronized (mMetadataLock) { 706 XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId); 707 708 out.startTag(null, TAG_BLOB_HANDLE); 709 mBlobHandle.writeToXml(out); 710 out.endTag(null, TAG_BLOB_HANDLE); 711 712 for (int i = 0, count = mCommitters.size(); i < count; ++i) { 713 out.startTag(null, TAG_COMMITTER); 714 mCommitters.valueAt(i).writeToXml(out); 715 out.endTag(null, TAG_COMMITTER); 716 } 717 718 for (int i = 0, count = mLeasees.size(); i < count; ++i) { 719 out.startTag(null, TAG_LEASEE); 720 mLeasees.valueAt(i).writeToXml(out); 721 out.endTag(null, TAG_LEASEE); 722 } 723 } 724 } 725 726 @Nullable createFromXml(XmlPullParser in, int version, Context context)727 static BlobMetadata createFromXml(XmlPullParser in, int version, Context context) 728 throws XmlPullParserException, IOException { 729 final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID); 730 if (version < XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) { 731 XmlUtils.readIntAttribute(in, ATTR_USER_ID); 732 } 733 734 BlobHandle blobHandle = null; 735 final ArraySet<Committer> committers = new ArraySet<>(); 736 final ArraySet<Leasee> leasees = new ArraySet<>(); 737 final int depth = in.getDepth(); 738 while (XmlUtils.nextElementWithin(in, depth)) { 739 if (TAG_BLOB_HANDLE.equals(in.getName())) { 740 blobHandle = BlobHandle.createFromXml(in); 741 } else if (TAG_COMMITTER.equals(in.getName())) { 742 final Committer committer = Committer.createFromXml(in, version); 743 if (committer != null) { 744 committers.add(committer); 745 } 746 } else if (TAG_LEASEE.equals(in.getName())) { 747 leasees.add(Leasee.createFromXml(in, version)); 748 } 749 } 750 751 if (blobHandle == null) { 752 Slog.wtf(TAG, "blobHandle should be available"); 753 return null; 754 } 755 756 final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle); 757 blobMetadata.setCommitters(committers); 758 blobMetadata.setLeasees(leasees); 759 return blobMetadata; 760 } 761 762 static final class Committer extends Accessor { 763 public final BlobAccessMode blobAccessMode; 764 public final long commitTimeMs; 765 Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs)766 Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs) { 767 super(packageName, uid); 768 this.blobAccessMode = blobAccessMode; 769 this.commitTimeMs = commitTimeMs; 770 } 771 getCommitTimeMs()772 long getCommitTimeMs() { 773 return commitTimeMs; 774 } 775 dump(IndentingPrintWriter fout)776 void dump(IndentingPrintWriter fout) { 777 fout.println("commit time: " 778 + (commitTimeMs == 0 ? "<null>" : BlobStoreUtils.formatTime(commitTimeMs))); 779 fout.println("accessMode:"); 780 fout.increaseIndent(); 781 blobAccessMode.dump(fout); 782 fout.decreaseIndent(); 783 } 784 writeToXml(@onNull XmlSerializer out)785 void writeToXml(@NonNull XmlSerializer out) throws IOException { 786 XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); 787 XmlUtils.writeIntAttribute(out, ATTR_UID, uid); 788 XmlUtils.writeLongAttribute(out, ATTR_COMMIT_TIME_MS, commitTimeMs); 789 790 out.startTag(null, TAG_ACCESS_MODE); 791 blobAccessMode.writeToXml(out); 792 out.endTag(null, TAG_ACCESS_MODE); 793 } 794 795 @Nullable createFromXml(@onNull XmlPullParser in, int version)796 static Committer createFromXml(@NonNull XmlPullParser in, int version) 797 throws XmlPullParserException, IOException { 798 final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); 799 final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); 800 final long commitTimeMs = version >= XML_VERSION_ADD_COMMIT_TIME 801 ? XmlUtils.readLongAttribute(in, ATTR_COMMIT_TIME_MS) 802 : 0; 803 804 final int depth = in.getDepth(); 805 BlobAccessMode blobAccessMode = null; 806 while (XmlUtils.nextElementWithin(in, depth)) { 807 if (TAG_ACCESS_MODE.equals(in.getName())) { 808 blobAccessMode = BlobAccessMode.createFromXml(in); 809 } 810 } 811 if (blobAccessMode == null) { 812 Slog.wtf(TAG, "blobAccessMode should be available"); 813 return null; 814 } 815 return new Committer(packageName, uid, blobAccessMode, commitTimeMs); 816 } 817 } 818 819 static final class Leasee extends Accessor { 820 public final String descriptionResEntryName; 821 public final CharSequence description; 822 public final long expiryTimeMillis; 823 Leasee(@onNull Context context, @NonNull String packageName, int uid, int descriptionResId, @Nullable CharSequence description, long expiryTimeMillis)824 Leasee(@NonNull Context context, @NonNull String packageName, 825 int uid, int descriptionResId, 826 @Nullable CharSequence description, long expiryTimeMillis) { 827 super(packageName, uid); 828 final Resources packageResources = getPackageResources(context, packageName, 829 UserHandle.getUserId(uid)); 830 this.descriptionResEntryName = getResourceEntryName(packageResources, descriptionResId); 831 this.expiryTimeMillis = expiryTimeMillis; 832 this.description = description == null 833 ? getDescription(packageResources, descriptionResId) 834 : description; 835 } 836 Leasee(String packageName, int uid, @Nullable String descriptionResEntryName, @Nullable CharSequence description, long expiryTimeMillis)837 Leasee(String packageName, int uid, @Nullable String descriptionResEntryName, 838 @Nullable CharSequence description, long expiryTimeMillis) { 839 super(packageName, uid); 840 this.descriptionResEntryName = descriptionResEntryName; 841 this.expiryTimeMillis = expiryTimeMillis; 842 this.description = description; 843 } 844 845 @Nullable getResourceEntryName(@ullable Resources packageResources, int resId)846 private static String getResourceEntryName(@Nullable Resources packageResources, 847 int resId) { 848 if (!ResourceId.isValid(resId) || packageResources == null) { 849 return null; 850 } 851 return packageResources.getResourceEntryName(resId); 852 } 853 854 @Nullable getDescription(@onNull Context context, @NonNull String descriptionResEntryName, @NonNull String packageName, int userId)855 private static String getDescription(@NonNull Context context, 856 @NonNull String descriptionResEntryName, @NonNull String packageName, int userId) { 857 if (descriptionResEntryName == null || descriptionResEntryName.isEmpty()) { 858 return null; 859 } 860 final Resources resources = getPackageResources(context, packageName, userId); 861 if (resources == null) { 862 return null; 863 } 864 final int resId = getDescriptionResourceId(resources, descriptionResEntryName, 865 packageName); 866 return resId == Resources.ID_NULL ? null : resources.getString(resId); 867 } 868 869 @Nullable getDescription(@ullable Resources packageResources, int descriptionResId)870 private static String getDescription(@Nullable Resources packageResources, 871 int descriptionResId) { 872 if (!ResourceId.isValid(descriptionResId) || packageResources == null) { 873 return null; 874 } 875 return packageResources.getString(descriptionResId); 876 } 877 isStillValid()878 boolean isStillValid() { 879 return expiryTimeMillis == 0 || expiryTimeMillis >= System.currentTimeMillis(); 880 } 881 dump(@onNull Context context, @NonNull IndentingPrintWriter fout)882 void dump(@NonNull Context context, @NonNull IndentingPrintWriter fout) { 883 fout.println("desc: " + getDescriptionToDump(context)); 884 fout.println("expiryMs: " + expiryTimeMillis); 885 } 886 887 @NonNull getDescriptionToDump(@onNull Context context)888 private String getDescriptionToDump(@NonNull Context context) { 889 String desc = getDescription(context, descriptionResEntryName, packageName, 890 UserHandle.getUserId(uid)); 891 if (desc == null) { 892 desc = description.toString(); 893 } 894 return desc == null ? "<none>" : desc; 895 } 896 writeToXml(@onNull XmlSerializer out)897 void writeToXml(@NonNull XmlSerializer out) throws IOException { 898 XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); 899 XmlUtils.writeIntAttribute(out, ATTR_UID, uid); 900 XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION_RES_NAME, descriptionResEntryName); 901 XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis); 902 XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION, description); 903 } 904 905 @NonNull createFromXml(@onNull XmlPullParser in, int version)906 static Leasee createFromXml(@NonNull XmlPullParser in, int version) 907 throws IOException { 908 final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); 909 final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); 910 final String descriptionResEntryName; 911 if (version >= XML_VERSION_ADD_DESC_RES_NAME) { 912 descriptionResEntryName = XmlUtils.readStringAttribute( 913 in, ATTR_DESCRIPTION_RES_NAME); 914 } else { 915 descriptionResEntryName = null; 916 } 917 final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME); 918 final CharSequence description; 919 if (version >= XML_VERSION_ADD_STRING_DESC) { 920 description = XmlUtils.readStringAttribute(in, ATTR_DESCRIPTION); 921 } else { 922 description = null; 923 } 924 925 return new Leasee(packageName, uid, descriptionResEntryName, 926 description, expiryTimeMillis); 927 } 928 } 929 930 static class Accessor { 931 public final String packageName; 932 public final int uid; 933 Accessor(String packageName, int uid)934 Accessor(String packageName, int uid) { 935 this.packageName = packageName; 936 this.uid = uid; 937 } 938 equals(String packageName, int uid)939 public boolean equals(String packageName, int uid) { 940 return this.uid == uid && this.packageName.equals(packageName); 941 } 942 943 @Override equals(Object obj)944 public boolean equals(Object obj) { 945 if (this == obj) { 946 return true; 947 } 948 if (obj == null || !(obj instanceof Accessor)) { 949 return false; 950 } 951 final Accessor other = (Accessor) obj; 952 return this.uid == other.uid && this.packageName.equals(other.packageName); 953 } 954 955 @Override hashCode()956 public int hashCode() { 957 return Objects.hash(packageName, uid); 958 } 959 960 @Override toString()961 public String toString() { 962 return "[" + packageName + ", " + uid + "]"; 963 } 964 } 965 } 966