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