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