xref: /aosp_15_r20/frameworks/native/opengl/libs/EGL/MultifileBlobCache.cpp (revision 38e8c45f13ce32b0dcecb25141ffecaf386fa17f)
1 /*
2  ** Copyright 2022, 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 
17 // #define LOG_NDEBUG 0
18 
19 #include "MultifileBlobCache.h"
20 
21 #include <android-base/properties.h>
22 #include <dirent.h>
23 #include <fcntl.h>
24 #include <inttypes.h>
25 #include <log/log.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <sys/mman.h>
29 #include <sys/stat.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include <utime.h>
33 
34 #include <algorithm>
35 #include <chrono>
36 #include <limits>
37 #include <locale>
38 
39 #include <utils/JenkinsHash.h>
40 
41 #include <com_android_graphics_egl_flags.h>
42 
43 using namespace com::android::graphics::egl;
44 
45 using namespace std::literals;
46 
47 constexpr uint32_t kMultifileMagic = 'MFB$';
48 constexpr uint32_t kCrcPlaceholder = 0;
49 
50 // When removing files, what fraction of the overall limit should be reached when removing files
51 // A divisor of two will decrease the cache to 50%, four to 25% and so on
52 // We use the same limit to manage size and entry count
53 constexpr uint32_t kCacheLimitDivisor = 2;
54 
55 namespace {
56 
57 // Helper function to close entries or free them
freeHotCacheEntry(android::MultifileHotCache & entry)58 void freeHotCacheEntry(android::MultifileHotCache& entry) {
59     if (entry.entryFd != -1) {
60         // If we have an fd, then this entry was added to hot cache via INIT or GET
61         // We need to unmap the entry
62         munmap(entry.entryBuffer, entry.entrySize);
63     } else {
64         // Otherwise, this was added to hot cache during SET, so it was never mapped
65         // and fd was only on the deferred thread.
66         delete[] entry.entryBuffer;
67     }
68 }
69 
70 } // namespace
71 
72 namespace android {
73 
MultifileBlobCache(size_t maxKeySize,size_t maxValueSize,size_t maxTotalSize,size_t maxTotalEntries,const std::string & baseDir)74 MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
75                                        size_t maxTotalEntries, const std::string& baseDir)
76       : mInitialized(false),
77         mCacheVersion(0),
78         mMaxKeySize(maxKeySize),
79         mMaxValueSize(maxValueSize),
80         mMaxTotalSize(maxTotalSize),
81         mMaxTotalEntries(maxTotalEntries),
82         mTotalCacheSize(0),
83         mTotalCacheEntries(0),
84         mTotalCacheSizeDivisor(kCacheLimitDivisor),
85         mHotCacheLimit(0),
86         mHotCacheSize(0),
87         mWorkerThreadIdle(true) {
88     if (baseDir.empty()) {
89         ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
90         return;
91     }
92 
93     // Set the cache version
94     mCacheVersion = kMultifileBlobCacheVersion;
95     // Bump the version if we're using flagged features
96     if (flags::multifile_blobcache_advanced_usage()) {
97         mCacheVersion++;
98     }
99     // Override if debug value set
100     int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
101     if (debugCacheVersion >= 0) {
102         ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
103         mCacheVersion = debugCacheVersion;
104     }
105 
106     // Set the platform build ID, override if debug value set
107     mBuildId = base::GetProperty("ro.build.id", "");
108     std::string debugBuildId = base::GetProperty("debug.egl.blobcache.build_id", "");
109     if (!debugBuildId.empty()) {
110         ALOGV("INIT: Using %s as buildId instead of %s", debugBuildId.c_str(), mBuildId.c_str());
111         if (debugBuildId.length() > PROP_VALUE_MAX) {
112             ALOGV("INIT: debugBuildId is too long (%zu), reduce it to %u", debugBuildId.length(),
113                   PROP_VALUE_MAX);
114         }
115         mBuildId = debugBuildId;
116     }
117 
118     // Establish the name of our multifile directory
119     mMultifileDirName = baseDir + ".multifile";
120 
121     // Set the hotcache limit to be large enough to contain one max entry
122     // This ensure the hot cache is always large enough for single entry
123     mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader);
124 
125     ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
126           mMaxKeySize, mMaxValueSize);
127 
128     // Initialize our cache with the contents of the directory
129     mTotalCacheSize = 0;
130 
131     // Create the worker thread
132     mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
133 
134     // See if the dir exists, and initialize using its contents
135     bool statusGood = false;
136 
137     // Check that our cacheVersion and buildId match
138     struct stat st;
139     if (stat(mMultifileDirName.c_str(), &st) == 0) {
140         if (checkStatus(mMultifileDirName)) {
141             statusGood = true;
142         } else {
143             ALOGV("INIT: Cache status has changed, clearing the cache");
144             if (!clearCache()) {
145                 ALOGE("INIT: Unable to clear cache");
146                 return;
147             }
148         }
149     }
150 
151     if (statusGood) {
152         // Read all the files and gather details, then preload their contents
153         DIR* dir;
154         struct dirent* entry;
155         if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
156             while ((entry = readdir(dir)) != nullptr) {
157                 if (entry->d_name == "."s || entry->d_name == ".."s ||
158                     strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
159                     continue;
160                 }
161 
162                 std::string entryName = entry->d_name;
163                 std::string fullPath = mMultifileDirName + "/" + entryName;
164 
165                 // The filename is the same as the entryHash
166                 uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
167 
168                 ALOGV("INIT: Checking entry %u", entryHash);
169 
170                 // Look up the details of the file
171                 struct stat st;
172                 if (stat(fullPath.c_str(), &st) != 0) {
173                     ALOGE("Failed to stat %s", fullPath.c_str());
174                     return;
175                 }
176 
177                 // If the cache entry is damaged or no good, remove it
178                 if (st.st_size <= 0 || st.st_atime <= 0) {
179                     ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
180                     if (remove(fullPath.c_str()) != 0) {
181                         ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
182                               std::strerror(errno));
183                     }
184                     continue;
185                 }
186 
187                 // Open the file so we can read its header
188                 int fd = open(fullPath.c_str(), O_RDONLY);
189                 if (fd == -1) {
190                     ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
191                           std::strerror(errno));
192                     return;
193                 }
194 
195                 // Read the beginning of the file to get header
196                 MultifileHeader header;
197                 size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
198                 if (result != sizeof(MultifileHeader)) {
199                     ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
200                           fullPath.c_str(), std::strerror(errno));
201                     close(fd);
202                     return;
203                 }
204 
205                 // Verify header magic
206                 if (header.magic != kMultifileMagic) {
207                     ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
208                     if (remove(fullPath.c_str()) != 0) {
209                         ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
210                               std::strerror(errno));
211                     }
212                     close(fd);
213                     continue;
214                 }
215 
216                 // Note: Converting from off_t (signed) to size_t (unsigned)
217                 size_t fileSize = static_cast<size_t>(st.st_size);
218 
219                 // Memory map the file
220                 uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
221                         mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
222 
223                 // We can close the file now and the mmap will remain
224                 close(fd);
225 
226                 if (mappedEntry == MAP_FAILED) {
227                     ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
228                     return;
229                 }
230 
231                 // Ensure we have a good CRC
232                 if (header.crc != GenerateCRC32(mappedEntry + sizeof(MultifileHeader),
233                                                 fileSize - sizeof(MultifileHeader))) {
234                     ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
235                     if (remove(fullPath.c_str()) != 0) {
236                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
237                     }
238                     continue;
239                 }
240 
241                 // If the cache entry is damaged or no good, remove it
242                 if (header.keySize <= 0 || header.valueSize <= 0) {
243                     ALOGV("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
244                           "removing.",
245                           entryHash, header.keySize, header.valueSize);
246                     if (remove(fullPath.c_str()) != 0) {
247                         ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
248                               std::strerror(errno));
249                     }
250                     continue;
251                 }
252 
253                 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
254 
255                 // Track details for rapid lookup later and update total size
256                 // Note access time is a full timespec instead of just seconds
257                 trackEntry(entryHash, header.valueSize, fileSize, st.st_atim);
258 
259                 // Preload the entry for fast retrieval
260                 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
261                     ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
262                           "entryHash %u",
263                           fd, mappedEntry, entryHash);
264 
265                     // Track the details of the preload so they can be retrieved later
266                     if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
267                         ALOGE("INIT Failed to add %u to hot cache", entryHash);
268                         munmap(mappedEntry, fileSize);
269                         return;
270                     }
271                 } else {
272                     // If we're not keeping it in hot cache, unmap it now
273                     munmap(mappedEntry, fileSize);
274                 }
275             }
276             closedir(dir);
277         } else {
278             ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
279         }
280     } else {
281         // If the multifile directory does not exist, create it and start from scratch
282         if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
283             ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
284             return;
285         }
286 
287         // Create new status file
288         if (!createStatus(mMultifileDirName.c_str())) {
289             ALOGE("INIT: Failed to create status file!");
290             return;
291         }
292     }
293 
294     ALOGV("INIT: Multifile BlobCache initialization succeeded");
295     mInitialized = true;
296 }
297 
~MultifileBlobCache()298 MultifileBlobCache::~MultifileBlobCache() {
299     if (!mInitialized) {
300         return;
301     }
302 
303     // Inform the worker thread we're done
304     ALOGV("DESCTRUCTOR: Shutting down worker thread");
305     DeferredTask task(TaskCommand::Exit);
306     queueTask(std::move(task));
307 
308     // Wait for it to complete
309     ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
310     waitForWorkComplete();
311     if (mTaskThread.joinable()) {
312         mTaskThread.join();
313     }
314 }
315 
316 // Set will add the entry to hot cache and start a deferred process to write it to disk
set(const void * key,EGLsizeiANDROID keySize,const void * value,EGLsizeiANDROID valueSize)317 void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
318                              EGLsizeiANDROID valueSize) {
319     if (!mInitialized) {
320         return;
321     }
322 
323     // Ensure key and value are under their limits
324     if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
325         ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
326               valueSize, mMaxValueSize);
327         return;
328     }
329 
330     // Generate a hash of the key and use it to track this entry
331     uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
332 
333     std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
334 
335     // See if we already have this file
336     if (flags::multifile_blobcache_advanced_usage() && contains(entryHash)) {
337         // Remove previous entry from hot cache
338         removeFromHotCache(entryHash);
339 
340         // Remove previous entry and update the overall cache size
341         removeEntry(entryHash);
342 
343         // If valueSize is zero, this is an indication that the user wants to remove the entry from
344         // cache It has already been removed from tracking, now remove it from disk It is safe to do
345         // this immediately because we drained the write queue in removeFromHotCache
346         if (valueSize == 0) {
347             ALOGV("SET: Zero size detected for existing entry, removing %u from cache", entryHash);
348             if (remove(fullPath.c_str()) != 0) {
349                 ALOGW("SET: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
350             }
351             return;
352         }
353     }
354 
355     size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
356 
357     // If we're going to be over the cache limit, kick off a trim to clear space
358     if (getTotalSize() + fileSize > mMaxTotalSize || getTotalEntries() + 1 > mMaxTotalEntries) {
359         ALOGV("SET: Cache is full, calling trimCache to clear space");
360         trimCache();
361     }
362 
363     ALOGV("SET: Add %u to cache", entryHash);
364 
365     uint8_t* buffer = new uint8_t[fileSize];
366 
367     // Write placeholders for magic and CRC until deferred thread completes the write
368     android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
369     memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
370            sizeof(android::MultifileHeader));
371     // Write the key and value after the header
372     memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
373            keySize);
374     memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
375            static_cast<const void*>(value), valueSize);
376 
377     // Track the size and access time for quick recall and update the overall cache size
378     struct timespec time = {0, 0};
379     if (flags::multifile_blobcache_advanced_usage()) {
380         clock_gettime(CLOCK_REALTIME, &time);
381     }
382     trackEntry(entryHash, valueSize, fileSize, time);
383 
384     // Keep the entry in hot cache for quick retrieval
385     ALOGV("SET: Adding %u to hot cache.", entryHash);
386 
387     // Sending -1 as the fd indicates we don't have an fd for this
388     if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
389         ALOGE("SET: Failed to add %u to hot cache", entryHash);
390         delete[] buffer;
391         return;
392     }
393 
394     // Track that we're creating a pending write for this entry
395     // Include the buffer to handle the case when multiple writes are pending for an entry
396     {
397         // Synchronize access to deferred write status
398         std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
399         mDeferredWrites.insert(std::make_pair(entryHash, buffer));
400     }
401 
402     // Create deferred task to write to storage
403     ALOGV("SET: Adding task to queue.");
404     DeferredTask task(TaskCommand::WriteToDisk);
405     task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
406     queueTask(std::move(task));
407 }
408 
409 // Get will check the hot cache, then load it from disk if needed
get(const void * key,EGLsizeiANDROID keySize,void * value,EGLsizeiANDROID valueSize)410 EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
411                                         EGLsizeiANDROID valueSize) {
412     if (!mInitialized) {
413         return 0;
414     }
415 
416     // Ensure key and value are under their limits
417     if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
418         ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
419               valueSize, mMaxValueSize);
420         return 0;
421     }
422 
423     // Generate a hash of the key and use it to track this entry
424     uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
425 
426     // See if we have this file
427     if (!contains(entryHash)) {
428         ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
429         return 0;
430     }
431 
432     // Look up the data for this entry
433     MultifileEntryStats entryStats = getEntryStats(entryHash);
434 
435     size_t cachedValueSize = entryStats.valueSize;
436     if (cachedValueSize > valueSize) {
437         ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
438               "size (%zu)",
439               valueSize, entryHash, cachedValueSize);
440         return cachedValueSize;
441     }
442 
443     // We have the file and have enough room to write it out, return the entry
444     ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
445 
446     // Look up the size of the file
447     size_t fileSize = entryStats.fileSize;
448     if (keySize > fileSize) {
449         ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
450               "file",
451               keySize, fileSize);
452         return 0;
453     }
454 
455     std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
456 
457     // Open the hashed filename path
458     uint8_t* cacheEntry = 0;
459 
460     // Check hot cache
461     if (mHotCache.find(entryHash) != mHotCache.end()) {
462         ALOGV("GET: HotCache HIT for entry %u", entryHash);
463         cacheEntry = mHotCache[entryHash].entryBuffer;
464 
465         if (flags::multifile_blobcache_advanced_usage()) {
466             // Update last access time on disk
467             struct timespec times[2];
468             times[0].tv_nsec = UTIME_NOW;
469             times[1].tv_nsec = UTIME_OMIT;
470             utimensat(0, fullPath.c_str(), times, 0);
471         }
472     } else {
473         ALOGV("GET: HotCache MISS for entry: %u", entryHash);
474 
475         // Wait for writes to complete if there is an outstanding write for this entry
476         bool wait = false;
477         {
478             // Synchronize access to deferred write status
479             std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
480             wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end();
481         }
482 
483         if (wait) {
484             ALOGV("GET: Waiting for write to complete for %u", entryHash);
485             waitForWorkComplete();
486         }
487 
488         // Open the entry file
489         int fd = open(fullPath.c_str(), O_RDONLY);
490         if (fd == -1) {
491             ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
492                   std::strerror(errno));
493             return 0;
494         }
495 
496         // Memory map the file
497         cacheEntry =
498                 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
499 
500         if (flags::multifile_blobcache_advanced_usage()) {
501             // Update last access time and omit last modify time
502             struct timespec times[2];
503             times[0].tv_nsec = UTIME_NOW;
504             times[1].tv_nsec = UTIME_OMIT;
505             futimens(fd, times);
506         }
507 
508         // We can close the file now and the mmap will remain
509         close(fd);
510 
511         if (cacheEntry == MAP_FAILED) {
512             ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
513             return 0;
514         }
515 
516         ALOGV("GET: Adding %u to hot cache", entryHash);
517         if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
518             ALOGE("GET: Failed to add %u to hot cache", entryHash);
519             return 0;
520         }
521 
522         cacheEntry = mHotCache[entryHash].entryBuffer;
523     }
524 
525     // Ensure the header matches
526     MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
527     if (header->keySize != keySize || header->valueSize != valueSize) {
528         ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
529               "to cache header values for fullPath: %s",
530               keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
531         removeFromHotCache(entryHash);
532         return 0;
533     }
534 
535     // Compare the incoming key with our stored version (the beginning of the entry)
536     uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
537     int compare = memcmp(cachedKey, key, keySize);
538     if (compare != 0) {
539         ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
540         removeFromHotCache(entryHash);
541         return 0;
542     }
543 
544     if (flags::multifile_blobcache_advanced_usage()) {
545         // Update the entry time for this hash, so it reflects LRU
546         struct timespec time;
547         clock_gettime(CLOCK_REALTIME, &time);
548         updateEntryTime(entryHash, time);
549     }
550 
551     // Remaining entry following the key is the value
552     uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
553     memcpy(value, cachedValue, cachedValueSize);
554 
555     return cachedValueSize;
556 }
557 
finish()558 void MultifileBlobCache::finish() {
559     if (!mInitialized) {
560         return;
561     }
562 
563     // Wait for all deferred writes to complete
564     ALOGV("FINISH: Waiting for work to complete.");
565     waitForWorkComplete();
566 
567     // Close all entries in the hot cache
568     for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
569         uint32_t entryHash = hotCacheIter->first;
570         MultifileHotCache entry = hotCacheIter->second;
571 
572         ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
573         freeHotCacheEntry(entry);
574 
575         mHotCache.erase(hotCacheIter++);
576     }
577 }
578 
createStatus(const std::string & baseDir)579 bool MultifileBlobCache::createStatus(const std::string& baseDir) {
580     // Populate the status struct
581     struct MultifileStatus status;
582     memset(&status, 0, sizeof(status));
583     status.magic = kMultifileMagic;
584     status.cacheVersion = mCacheVersion;
585 
586     // Copy the buildId string in, up to our allocated space
587     strncpy(status.buildId, mBuildId.c_str(),
588             mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
589 
590     // Finally update the crc, using cacheVersion and everything the follows
591     status.crc = GenerateCRC32(
592         reinterpret_cast<uint8_t *>(&status) + offsetof(MultifileStatus, cacheVersion),
593         sizeof(status) - offsetof(MultifileStatus, cacheVersion));
594 
595     // Create the status file
596     std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
597     int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
598     if (fd == -1) {
599         ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
600               std::strerror(errno));
601         return false;
602     }
603 
604     // Write the buffer contents to disk
605     ssize_t result = write(fd, &status, sizeof(status));
606     close(fd);
607     if (result != sizeof(status)) {
608         ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
609               std::strerror(errno));
610         return false;
611     }
612 
613     ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
614     return true;
615 }
616 
checkStatus(const std::string & baseDir)617 bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
618     std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
619 
620     // Does status exist
621     struct stat st;
622     if (stat(cacheStatus.c_str(), &st) != 0) {
623         ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
624         return false;
625     }
626 
627     // If the status entry is damaged or no good, remove it
628     if (st.st_size <= 0 || st.st_atime <= 0) {
629         ALOGE("STATUS(CHECK): Cache status has invalid stats!");
630         return false;
631     }
632 
633     // Open the file so we can read its header
634     int fd = open(cacheStatus.c_str(), O_RDONLY);
635     if (fd == -1) {
636         ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
637               cacheStatus.c_str(), std::strerror(errno));
638         return false;
639     }
640 
641     // Read in the status header
642     MultifileStatus status;
643     size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
644     close(fd);
645     if (result != sizeof(MultifileStatus)) {
646         ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
647               std::strerror(errno));
648         return false;
649     }
650 
651     // Verify header magic
652     if (status.magic != kMultifileMagic) {
653         ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
654         return false;
655     }
656 
657     // Ensure we have a good CRC
658     if (status.crc != GenerateCRC32(reinterpret_cast<uint8_t *>(&status) +
659                                         offsetof(MultifileStatus, cacheVersion),
660                                     sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
661         ALOGE("STATUS(CHECK): Cache status failed CRC check!");
662         return false;
663     }
664 
665     // Check cacheVersion
666     if (status.cacheVersion != mCacheVersion) {
667         ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
668               mCacheVersion);
669         return false;
670     }
671 
672     // Check buildId
673     if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
674         ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
675               mBuildId.c_str());
676         return false;
677     }
678 
679     // All checks passed!
680     ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
681           status.cacheVersion, status.buildId, cacheStatus.c_str());
682     return true;
683 }
684 
trackEntry(uint32_t entryHash,EGLsizeiANDROID valueSize,size_t fileSize,const timespec & accessTime)685 void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
686                                     const timespec& accessTime) {
687 #if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
688     // When we add this entry to the map, it is sorted by accessTime
689     MultifileEntryStatsMapIter entryStatsIter =
690             mEntryStats.emplace(std::piecewise_construct, std::forward_as_tuple(accessTime),
691                                 std::forward_as_tuple(entryHash, valueSize, fileSize));
692 
693     // Track all entries with quick access to its stats
694     mEntries.emplace(entryHash, entryStatsIter);
695 #else
696     (void)accessTime;
697     mEntries.insert(entryHash);
698     mEntryStats[entryHash] = {entryHash, valueSize, fileSize};
699 #endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
700 
701     increaseTotalCacheSize(fileSize);
702 }
703 
removeEntry(uint32_t entryHash)704 bool MultifileBlobCache::removeEntry(uint32_t entryHash) {
705     auto entryIter = mEntries.find(entryHash);
706     if (entryIter == mEntries.end()) {
707         return false;
708     }
709 
710 #if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
711     MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
712     MultifileEntryStats entryStats = entryStatsIter->second;
713     decreaseTotalCacheSize(entryStats.fileSize);
714 #else
715     auto entryStatsIter = mEntryStats.find(entryHash);
716     if (entryStatsIter == mEntryStats.end()) {
717         ALOGE("Failed to remove entryHash (%u) from mEntryStats", entryHash);
718         return false;
719     }
720     decreaseTotalCacheSize(entryStatsIter->second.fileSize);
721 #endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
722 
723     mEntryStats.erase(entryStatsIter);
724     mEntries.erase(entryIter);
725 
726     return true;
727 }
728 
contains(uint32_t hashEntry) const729 bool MultifileBlobCache::contains(uint32_t hashEntry) const {
730     return mEntries.find(hashEntry) != mEntries.end();
731 }
732 
getEntryStats(uint32_t entryHash)733 MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
734 #if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
735     auto entryIter = mEntries.find(entryHash);
736     if (entryIter == mEntries.end()) {
737         return {};
738     }
739 
740     MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
741     MultifileEntryStats entryStats = entryStatsIter->second;
742     return entryStats;
743 #else
744     return mEntryStats[entryHash];
745 #endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
746 }
747 
updateEntryTime(uint32_t entryHash,const timespec & newTime)748 void MultifileBlobCache::updateEntryTime(uint32_t entryHash, const timespec& newTime) {
749 #if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
750     // This function updates the ordering of the map by removing the old iterators
751     // and re-adding them. If should be perforant as it does not perform a full re-sort.
752     // First, pull out the old entryStats
753     auto entryIter = mEntries.find(entryHash);
754     MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
755     MultifileEntryStats entryStats = std::move(entryStatsIter->second);
756 
757     // Remove the old iterators
758     mEntryStats.erase(entryStatsIter);
759     mEntries.erase(entryIter);
760 
761     // Insert the new with updated time
762     entryStatsIter = mEntryStats.emplace(std::make_pair(newTime, std::move(entryStats)));
763     mEntries.emplace(entryHash, entryStatsIter);
764 #else
765     (void)entryHash;
766     (void)newTime;
767 #endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
768 }
769 
increaseTotalCacheSize(size_t fileSize)770 void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
771     mTotalCacheSize += fileSize;
772     mTotalCacheEntries++;
773 }
774 
decreaseTotalCacheSize(size_t fileSize)775 void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
776     mTotalCacheSize -= fileSize;
777     mTotalCacheEntries--;
778 }
779 
addToHotCache(uint32_t newEntryHash,int newFd,uint8_t * newEntryBuffer,size_t newEntrySize)780 bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
781                                        size_t newEntrySize) {
782     ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
783 
784     // Clear space if we need to
785     if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
786         ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
787               "mHotCacheLimit "
788               "(%zu), freeing up space for %u",
789               mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
790 
791         // Wait for all the files to complete writing so our hot cache is accurate
792         ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
793         waitForWorkComplete();
794 
795         // Free up old entries until under the limit
796         for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
797             uint32_t oldEntryHash = hotCacheIter->first;
798             MultifileHotCache oldEntry = hotCacheIter->second;
799 
800             // Move our iterator before deleting the entry
801             hotCacheIter++;
802             if (!removeFromHotCache(oldEntryHash)) {
803                 ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
804                 return false;
805             }
806 
807             // Clear at least half the hot cache
808             if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
809                 ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
810                 break;
811             }
812         }
813     }
814 
815     // Track it
816     mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
817     mHotCacheSize += newEntrySize;
818 
819     ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
820 
821     return true;
822 }
823 
removeFromHotCache(uint32_t entryHash)824 bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
825     if (mHotCache.find(entryHash) != mHotCache.end()) {
826         ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
827 
828         // Wait for all the files to complete writing so our hot cache is accurate
829         ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
830         waitForWorkComplete();
831 
832         ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
833         MultifileHotCache entry = mHotCache[entryHash];
834         freeHotCacheEntry(entry);
835 
836         // Delete the entry from our tracking
837         mHotCacheSize -= entry.entrySize;
838         mHotCache.erase(entryHash);
839 
840         return true;
841     }
842 
843     return false;
844 }
845 
applyLRU(size_t cacheSizeLimit,size_t cacheEntryLimit)846 bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
847     // Walk through our map of sorted last access times and remove files until under the limit
848     for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
849 #if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
850         const MultifileEntryStats& entryStats = cacheEntryIter->second;
851         uint32_t entryHash = entryStats.entryHash;
852 #else
853         uint32_t entryHash = cacheEntryIter->first;
854         const MultifileEntryStats& entryStats = cacheEntryIter->second;
855 #endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
856 
857         ALOGV("LRU: Removing entryHash %u", entryHash);
858 
859         // Remove it from hot cache if present
860         removeFromHotCache(entryHash);
861 
862         // Remove it from the system
863         std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
864         if (remove(entryPath.c_str()) != 0) {
865             // Continue evicting invalid item (app's cache might be cleared)
866             ALOGW("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
867         }
868 
869         // Increment the iterator before clearing the entry
870         cacheEntryIter++;
871 
872         // Delete the entry from our tracking and update the overall cache size
873         if (!removeEntry(entryHash)) {
874             ALOGE("LRU: Failed to remove entryHash %u", entryHash);
875             return false;
876         }
877 
878         // See if it has been reduced enough
879         size_t totalCacheSize = getTotalSize();
880         size_t totalCacheEntries = getTotalEntries();
881         if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
882             // Success
883             ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
884             return true;
885         }
886     }
887 
888     ALOGV("LRU: Cache is empty");
889     return false;
890 }
891 
892 // Clear the cache by removing all entries and deleting the directory
clearCache()893 bool MultifileBlobCache::clearCache() {
894     DIR* dir;
895     struct dirent* entry;
896     dir = opendir(mMultifileDirName.c_str());
897     if (dir == nullptr) {
898         ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
899         return false;
900     }
901 
902     // Delete all entries and the status file
903     while ((entry = readdir(dir)) != nullptr) {
904         if (entry->d_name == "."s || entry->d_name == ".."s) {
905             continue;
906         }
907 
908         std::string entryName = entry->d_name;
909         std::string fullPath = mMultifileDirName + "/" + entryName;
910         if (remove(fullPath.c_str()) != 0) {
911             ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
912             return false;
913         }
914     }
915 
916     // Delete the directory
917     if (remove(mMultifileDirName.c_str()) != 0) {
918         ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
919         return false;
920     }
921 
922     ALOGV("CLEAR: Cleared the multifile blobcache");
923     return true;
924 }
925 
926 // Calculate the cache size and remove old entries until under the limit
trimCache()927 void MultifileBlobCache::trimCache() {
928     // Wait for all deferred writes to complete
929     ALOGV("TRIM: Waiting for work to complete.");
930     waitForWorkComplete();
931 
932     ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
933           mMaxTotalSize / mTotalCacheSizeDivisor, mMaxTotalEntries / mTotalCacheSizeDivisor);
934 
935     if (!applyLRU(mMaxTotalSize / mTotalCacheSizeDivisor,
936                   mMaxTotalEntries / mTotalCacheSizeDivisor)) {
937         ALOGE("Error when clearing multifile shader cache");
938         return;
939     }
940 }
941 
942 // This function performs a task.  It only knows how to write files to disk,
943 // but it could be expanded if needed.
processTask(DeferredTask & task)944 void MultifileBlobCache::processTask(DeferredTask& task) {
945     switch (task.getTaskCommand()) {
946         case TaskCommand::Exit: {
947             ALOGV("DEFERRED: Shutting down");
948             return;
949         }
950         case TaskCommand::WriteToDisk: {
951             uint32_t entryHash = task.getEntryHash();
952             std::string& fullPath = task.getFullPath();
953             uint8_t* buffer = task.getBuffer();
954             size_t bufferSize = task.getBufferSize();
955 
956             // Create the file or reset it if already present, read+write for user only
957             int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
958             if (fd == -1) {
959                 if (flags::multifile_blobcache_advanced_usage()) {
960                     struct stat st;
961                     if (stat(mMultifileDirName.c_str(), &st) == -1) {
962                         ALOGW("Cache directory missing (app's cache cleared?). Recreating...");
963 
964                         // Restore the multifile directory
965                         if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
966                             ALOGE("Cache error in SET - Unable to create directory (%s), errno "
967                                   "(%i)",
968                                   mMultifileDirName.c_str(), errno);
969                             return;
970                         }
971 
972                         // Create new status file
973                         if (!createStatus(mMultifileDirName.c_str())) {
974                             ALOGE("Cache error in SET - Failed to create status file!");
975                             return;
976                         }
977 
978                         // Try to open the file again
979                         fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
980                                   S_IRUSR | S_IWUSR);
981                     }
982                 }
983 
984                 if (fd == -1) {
985                     ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
986                           fullPath.c_str(), std::strerror(errno));
987                     return;
988                 }
989             }
990 
991             ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
992 
993             // Add CRC check to the header (always do this last!)
994             MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
995             header->crc             = GenerateCRC32(buffer + sizeof(MultifileHeader),
996                                                     bufferSize - sizeof(MultifileHeader));
997 
998             ssize_t result = write(fd, buffer, bufferSize);
999             if (result != bufferSize) {
1000                 ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
1001                       std::strerror(errno));
1002                 return;
1003             }
1004 
1005             if (flags::multifile_blobcache_advanced_usage()) {
1006                 // Update last access time and last modify time
1007                 struct timespec times[2];
1008                 times[0].tv_nsec = UTIME_NOW;
1009                 times[1].tv_nsec = UTIME_NOW;
1010                 futimens(fd, times);
1011             }
1012 
1013             ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
1014             close(fd);
1015 
1016             // Erase the entry from mDeferredWrites
1017             // Since there could be multiple outstanding writes for an entry, find the matching one
1018             {
1019                 // Synchronize access to deferred write status
1020                 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
1021                 typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
1022                 std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
1023                 for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
1024                     if (it->second == buffer) {
1025                         ALOGV("DEFERRED: Marking write complete for %u at %p", it->first,
1026                               it->second);
1027                         mDeferredWrites.erase(it);
1028                         break;
1029                     }
1030                 }
1031             }
1032 
1033             return;
1034         }
1035         default: {
1036             ALOGE("DEFERRED: Unhandled task type");
1037             return;
1038         }
1039     }
1040 }
1041 
1042 // This function will wait until tasks arrive, then execute them
1043 // If the exit command is submitted, the loop will terminate
processTasksImpl(bool * exitThread)1044 void MultifileBlobCache::processTasksImpl(bool* exitThread) {
1045     while (true) {
1046         std::unique_lock<std::mutex> lock(mWorkerMutex);
1047         if (mTasks.empty()) {
1048             ALOGV("WORKER: No tasks available, waiting");
1049             mWorkerThreadIdle = true;
1050             mWorkerIdleCondition.notify_all();
1051             // Only wake if notified and command queue is not empty
1052             mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
1053         }
1054 
1055         ALOGV("WORKER: Task available, waking up.");
1056         mWorkerThreadIdle = false;
1057         DeferredTask task = std::move(mTasks.front());
1058         mTasks.pop();
1059 
1060         if (task.getTaskCommand() == TaskCommand::Exit) {
1061             ALOGV("WORKER: Exiting work loop.");
1062             *exitThread = true;
1063             mWorkerThreadIdle = true;
1064             mWorkerIdleCondition.notify_one();
1065             return;
1066         }
1067 
1068         lock.unlock();
1069         processTask(task);
1070     }
1071 }
1072 
1073 // Process tasks until the exit task is submitted
processTasks()1074 void MultifileBlobCache::processTasks() {
1075     while (true) {
1076         bool exitThread = false;
1077         processTasksImpl(&exitThread);
1078         if (exitThread) {
1079             break;
1080         }
1081     }
1082 }
1083 
1084 // Add a task to the queue to be processed by the worker thread
queueTask(DeferredTask && task)1085 void MultifileBlobCache::queueTask(DeferredTask&& task) {
1086     std::lock_guard<std::mutex> queueLock(mWorkerMutex);
1087     mTasks.emplace(std::move(task));
1088     mWorkAvailableCondition.notify_one();
1089 }
1090 
1091 // Wait until all tasks have been completed
waitForWorkComplete()1092 void MultifileBlobCache::waitForWorkComplete() {
1093     std::unique_lock<std::mutex> lock(mWorkerMutex);
1094     mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
1095 }
1096 
1097 }; // namespace android
1098