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