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, committer.packageName)) {
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, committer.packageName)) {
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