1 /*
2 * Copyright © 2022 Collabora, Ltd.
3 *
4 * Based on Fossilize DB:
5 * Copyright © 2020 Valve Corporation
6 *
7 * SPDX-License-Identifier: MIT
8 */
9
10 #include "detect_os.h"
11
12 #if DETECT_OS_WINDOWS == 0
13
14 #include <fcntl.h>
15 #include <stddef.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/file.h>
19 #include <unistd.h>
20
21 #include "crc32.h"
22 #include "disk_cache.h"
23 #include "hash_table.h"
24 #include "mesa-sha1.h"
25 #include "mesa_cache_db.h"
26 #include "os_time.h"
27 #include "ralloc.h"
28 #include "u_debug.h"
29 #include "u_qsort.h"
30
31 #define MESA_CACHE_DB_VERSION 1
32 #define MESA_CACHE_DB_MAGIC "MESA_DB"
33
34 struct PACKED mesa_db_file_header {
35 char magic[8];
36 uint32_t version;
37 uint64_t uuid;
38 };
39
40 struct PACKED mesa_cache_db_file_entry {
41 cache_key key;
42 uint32_t crc;
43 uint32_t size;
44 };
45
46 struct PACKED mesa_index_db_file_entry {
47 uint64_t hash;
48 uint32_t size;
49 uint64_t last_access_time;
50 uint64_t cache_db_file_offset;
51 };
52
53 struct mesa_index_db_hash_entry {
54 uint64_t cache_db_file_offset;
55 uint64_t index_db_file_offset;
56 uint64_t last_access_time;
57 uint32_t size;
58 bool evicted;
59 };
60
mesa_db_seek_end(FILE * file)61 static inline bool mesa_db_seek_end(FILE *file)
62 {
63 return !fseek(file, 0, SEEK_END);
64 }
65
mesa_db_seek(FILE * file,long pos)66 static inline bool mesa_db_seek(FILE *file, long pos)
67 {
68 return !fseek(file, pos, SEEK_SET);
69 }
70
mesa_db_seek_cur(FILE * file,long pos)71 static inline bool mesa_db_seek_cur(FILE *file, long pos)
72 {
73 return !fseek(file, pos, SEEK_CUR);
74 }
75
mesa_db_read_data(FILE * file,void * data,size_t size)76 static inline bool mesa_db_read_data(FILE *file, void *data, size_t size)
77 {
78 return fread(data, 1, size, file) == size;
79 }
80 #define mesa_db_read(file, var) mesa_db_read_data(file, var, sizeof(*(var)))
81
mesa_db_write_data(FILE * file,const void * data,size_t size)82 static inline bool mesa_db_write_data(FILE *file, const void *data, size_t size)
83 {
84 return fwrite(data, 1, size, file) == size;
85 }
86 #define mesa_db_write(file, var) mesa_db_write_data(file, var, sizeof(*(var)))
87
mesa_db_truncate(FILE * file,long pos)88 static inline bool mesa_db_truncate(FILE *file, long pos)
89 {
90 return !ftruncate(fileno(file), pos);
91 }
92
93 static bool
mesa_db_lock(struct mesa_cache_db * db)94 mesa_db_lock(struct mesa_cache_db *db)
95 {
96 simple_mtx_lock(&db->flock_mtx);
97
98 if (flock(fileno(db->cache.file), LOCK_EX) == -1)
99 goto unlock_mtx;
100
101 if (flock(fileno(db->index.file), LOCK_EX) == -1)
102 goto unlock_cache;
103
104 return true;
105
106 unlock_cache:
107 flock(fileno(db->cache.file), LOCK_UN);
108 unlock_mtx:
109 simple_mtx_unlock(&db->flock_mtx);
110
111 return false;
112 }
113
114 static void
mesa_db_unlock(struct mesa_cache_db * db)115 mesa_db_unlock(struct mesa_cache_db *db)
116 {
117 flock(fileno(db->index.file), LOCK_UN);
118 flock(fileno(db->cache.file), LOCK_UN);
119 simple_mtx_unlock(&db->flock_mtx);
120 }
121
to_mesa_cache_db_hash(const uint8_t * cache_key_160bit)122 static uint64_t to_mesa_cache_db_hash(const uint8_t *cache_key_160bit)
123 {
124 uint64_t hash = 0;
125
126 for (unsigned i = 0; i < 8; i++)
127 hash |= ((uint64_t)cache_key_160bit[i]) << i * 8;
128
129 return hash;
130 }
131
132 static uint64_t
mesa_db_generate_uuid(void)133 mesa_db_generate_uuid(void)
134 {
135 /* This simple UUID implementation is sufficient for our needs
136 * because UUID is updated rarely. It's nice to make UUID meaningful
137 * and incremental by adding the timestamp to it, which also prevents
138 * the potential collisions. */
139 return ((os_time_get() / 1000000) << 32) | rand();
140 }
141
142 static bool
mesa_db_read_header(FILE * file,struct mesa_db_file_header * header)143 mesa_db_read_header(FILE *file, struct mesa_db_file_header *header)
144 {
145 rewind(file);
146 fflush(file);
147
148 if (!mesa_db_read(file, header))
149 return false;
150
151 if (strncmp(header->magic, MESA_CACHE_DB_MAGIC, sizeof(header->magic)) ||
152 header->version != MESA_CACHE_DB_VERSION || !header->uuid)
153 return false;
154
155 return true;
156 }
157
158 static bool
mesa_db_load_header(struct mesa_cache_db_file * db_file)159 mesa_db_load_header(struct mesa_cache_db_file *db_file)
160 {
161 struct mesa_db_file_header header;
162
163 if (!mesa_db_read_header(db_file->file, &header))
164 return false;
165
166 db_file->uuid = header.uuid;
167
168 return true;
169 }
170
mesa_db_uuid_changed(struct mesa_cache_db * db)171 static bool mesa_db_uuid_changed(struct mesa_cache_db *db)
172 {
173 struct mesa_db_file_header cache_header;
174 struct mesa_db_file_header index_header;
175
176 if (!mesa_db_read_header(db->cache.file, &cache_header) ||
177 !mesa_db_read_header(db->index.file, &index_header) ||
178 cache_header.uuid != index_header.uuid ||
179 cache_header.uuid != db->uuid)
180 return true;
181
182 return false;
183 }
184
185 static bool
mesa_db_write_header(struct mesa_cache_db_file * db_file,uint64_t uuid,bool reset)186 mesa_db_write_header(struct mesa_cache_db_file *db_file,
187 uint64_t uuid, bool reset)
188 {
189 struct mesa_db_file_header header;
190
191 rewind(db_file->file);
192
193 sprintf(header.magic, "MESA_DB");
194 header.version = MESA_CACHE_DB_VERSION;
195 header.uuid = uuid;
196
197 if (!mesa_db_write(db_file->file, &header))
198 return false;
199
200 if (reset) {
201 if (!mesa_db_truncate(db_file->file, ftell(db_file->file)))
202 return false;
203 }
204
205 fflush(db_file->file);
206
207 return true;
208 }
209
210 /* Wipe out all database cache files.
211 *
212 * Whenever we get an unmanageable error on reading or writing to the
213 * database file, wipe out the whole database and start over. All the
214 * cached entries will be lost, but the broken cache will be auto-repaired
215 * reliably. Normally cache shall never get corrupted and losing cache
216 * entries is acceptable, hence it's more practical to repair DB using
217 * the simplest method.
218 */
219 static bool
mesa_db_zap(struct mesa_cache_db * db)220 mesa_db_zap(struct mesa_cache_db *db)
221 {
222 /* Disable cache to prevent the recurring faults */
223 db->alive = false;
224
225 /* Zap corrupted database files to start over from a clean slate */
226 if (!mesa_db_truncate(db->cache.file, 0) ||
227 !mesa_db_truncate(db->index.file, 0))
228 return false;
229
230 fflush(db->cache.file);
231 fflush(db->index.file);
232
233 return true;
234 }
235
236 static bool
mesa_db_index_entry_valid(struct mesa_index_db_file_entry * entry)237 mesa_db_index_entry_valid(struct mesa_index_db_file_entry *entry)
238 {
239 return entry->size && entry->hash &&
240 (int64_t)entry->cache_db_file_offset >= sizeof(struct mesa_db_file_header);
241 }
242
243 static bool
mesa_db_cache_entry_valid(struct mesa_cache_db_file_entry * entry)244 mesa_db_cache_entry_valid(struct mesa_cache_db_file_entry *entry)
245 {
246 return entry->size && entry->crc;
247 }
248
249 static bool
mesa_db_update_index(struct mesa_cache_db * db)250 mesa_db_update_index(struct mesa_cache_db *db)
251 {
252 struct mesa_index_db_hash_entry *hash_entry;
253 struct mesa_index_db_file_entry index_entry;
254 size_t file_length;
255
256 if (!mesa_db_seek_end(db->index.file))
257 return false;
258
259 file_length = ftell(db->index.file);
260
261 if (!mesa_db_seek(db->index.file, db->index.offset))
262 return false;
263
264 while (db->index.offset < file_length) {
265 if (!mesa_db_read(db->index.file, &index_entry))
266 break;
267
268 /* Check whether the index entry looks valid or we have a corrupted DB */
269 if (!mesa_db_index_entry_valid(&index_entry))
270 break;
271
272 hash_entry = ralloc(db->mem_ctx, struct mesa_index_db_hash_entry);
273 if (!hash_entry)
274 break;
275
276 hash_entry->cache_db_file_offset = index_entry.cache_db_file_offset;
277 hash_entry->index_db_file_offset = db->index.offset;
278 hash_entry->last_access_time = index_entry.last_access_time;
279 hash_entry->size = index_entry.size;
280
281 _mesa_hash_table_u64_insert(db->index_db, index_entry.hash, hash_entry);
282
283 db->index.offset += sizeof(index_entry);
284 }
285
286 if (!mesa_db_seek(db->index.file, db->index.offset))
287 return false;
288
289 return db->index.offset == file_length;
290 }
291
292 static void
mesa_db_hash_table_reset(struct mesa_cache_db * db)293 mesa_db_hash_table_reset(struct mesa_cache_db *db)
294 {
295 _mesa_hash_table_u64_clear(db->index_db);
296 ralloc_free(db->mem_ctx);
297 db->mem_ctx = ralloc_context(NULL);
298 }
299
300 static bool
mesa_db_recreate_files(struct mesa_cache_db * db)301 mesa_db_recreate_files(struct mesa_cache_db *db)
302 {
303 db->uuid = mesa_db_generate_uuid();
304
305 if (!mesa_db_write_header(&db->cache, db->uuid, true) ||
306 !mesa_db_write_header(&db->index, db->uuid, true))
307 return false;
308
309 return true;
310 }
311
312 static bool
mesa_db_load(struct mesa_cache_db * db,bool reload)313 mesa_db_load(struct mesa_cache_db *db, bool reload)
314 {
315 /* reloading must be done under the held lock */
316 if (!reload) {
317 if (!mesa_db_lock(db))
318 return false;
319 }
320
321 /* If file headers are invalid, then zap database files and start over */
322 if (!mesa_db_load_header(&db->cache) ||
323 !mesa_db_load_header(&db->index) ||
324 db->cache.uuid != db->index.uuid) {
325
326 /* This is unexpected to happen on reload, bail out */
327 if (reload)
328 goto fail;
329
330 if (!mesa_db_recreate_files(db))
331 goto fail;
332 } else {
333 db->uuid = db->cache.uuid;
334 }
335
336 db->index.offset = ftell(db->index.file);
337
338 if (reload)
339 mesa_db_hash_table_reset(db);
340
341 if (!mesa_db_update_index(db))
342 goto fail;
343
344 if (!reload)
345 mesa_db_unlock(db);
346
347 db->alive = true;
348
349 return true;
350
351 fail:
352 if (!reload)
353 mesa_db_unlock(db);
354
355 return false;
356 }
357
358 static bool
mesa_db_reload(struct mesa_cache_db * db)359 mesa_db_reload(struct mesa_cache_db *db)
360 {
361 fflush(db->cache.file);
362 fflush(db->index.file);
363
364 return mesa_db_load(db, true);
365 }
366
367 static void
touch_file(const char * path)368 touch_file(const char* path)
369 {
370 close(open(path, O_CREAT | O_CLOEXEC, 0644));
371 }
372
373 static bool
mesa_db_open_file(struct mesa_cache_db_file * db_file,const char * cache_path,const char * filename)374 mesa_db_open_file(struct mesa_cache_db_file *db_file,
375 const char *cache_path,
376 const char *filename)
377 {
378 if (asprintf(&db_file->path, "%s/%s", cache_path, filename) == -1)
379 return false;
380
381 /* The fopen("r+b") mode doesn't auto-create new file, hence we need to
382 * explicitly create the file first.
383 */
384 touch_file(db_file->path);
385
386 db_file->file = fopen(db_file->path, "r+b");
387 if (!db_file->file) {
388 free(db_file->path);
389 return false;
390 }
391
392 return true;
393 }
394
395 static void
mesa_db_close_file(struct mesa_cache_db_file * db_file)396 mesa_db_close_file(struct mesa_cache_db_file *db_file)
397 {
398 fclose(db_file->file);
399 free(db_file->path);
400 }
401
402 static bool
mesa_db_remove_file(struct mesa_cache_db_file * db_file,const char * cache_path,const char * filename)403 mesa_db_remove_file(struct mesa_cache_db_file *db_file,
404 const char *cache_path,
405 const char *filename)
406 {
407 if (asprintf(&db_file->path, "%s/%s", cache_path, filename) == -1)
408 return false;
409
410 unlink(db_file->path);
411
412 return true;
413 }
414
415 static int
entry_sort_lru(const void * _a,const void * _b,void * arg)416 entry_sort_lru(const void *_a, const void *_b, void *arg)
417 {
418 const struct mesa_index_db_hash_entry *a = *((const struct mesa_index_db_hash_entry **)_a);
419 const struct mesa_index_db_hash_entry *b = *((const struct mesa_index_db_hash_entry **)_b);
420
421 /* In practice it's unlikely that we will get two entries with the
422 * same timestamp, but technically it's possible to happen if OS
423 * timer's resolution is low. */
424 if (a->last_access_time == b->last_access_time)
425 return 0;
426
427 return a->last_access_time > b->last_access_time ? 1 : -1;
428 }
429
430 static int
entry_sort_offset(const void * _a,const void * _b,void * arg)431 entry_sort_offset(const void *_a, const void *_b, void *arg)
432 {
433 const struct mesa_index_db_hash_entry *a = *((const struct mesa_index_db_hash_entry **)_a);
434 const struct mesa_index_db_hash_entry *b = *((const struct mesa_index_db_hash_entry **)_b);
435 struct mesa_cache_db *db = arg;
436
437 /* Two entries will never have the identical offset, otherwise DB is
438 * corrupted. */
439 if (a->cache_db_file_offset == b->cache_db_file_offset)
440 mesa_db_zap(db);
441
442 return a->cache_db_file_offset > b->cache_db_file_offset ? 1 : -1;
443 }
444
blob_file_size(uint32_t blob_size)445 static uint32_t blob_file_size(uint32_t blob_size)
446 {
447 return sizeof(struct mesa_cache_db_file_entry) + blob_size;
448 }
449
450 static bool
mesa_db_compact(struct mesa_cache_db * db,int64_t blob_size,struct mesa_index_db_hash_entry * remove_entry)451 mesa_db_compact(struct mesa_cache_db *db, int64_t blob_size,
452 struct mesa_index_db_hash_entry *remove_entry)
453 {
454 uint32_t num_entries, buffer_size = sizeof(struct mesa_index_db_file_entry);
455 struct mesa_db_file_header cache_header, index_header;
456 FILE *compacted_cache = NULL, *compacted_index = NULL;
457 struct mesa_index_db_file_entry index_entry;
458 struct mesa_index_db_hash_entry **entries;
459 bool success = false, compact = false;
460 void *buffer = NULL;
461 unsigned int i = 0;
462
463 /* reload index to sync the last access times */
464 if (!remove_entry && !mesa_db_reload(db))
465 return false;
466
467 num_entries = _mesa_hash_table_num_entries(db->index_db->table);
468 entries = calloc(num_entries, sizeof(*entries));
469 if (!entries)
470 return false;
471
472 compacted_cache = fopen(db->cache.path, "r+b");
473 compacted_index = fopen(db->index.path, "r+b");
474 if (!compacted_cache || !compacted_index)
475 goto cleanup;
476
477 /* The database file has been replaced if UUID changed. We opened
478 * some other cache, stop processing this database. */
479 if (!mesa_db_read_header(compacted_cache, &cache_header) ||
480 !mesa_db_read_header(compacted_index, &index_header) ||
481 cache_header.uuid != db->uuid ||
482 index_header.uuid != db->uuid)
483 goto cleanup;
484
485 hash_table_foreach(db->index_db->table, entry) {
486 entries[i] = entry->data;
487 entries[i]->evicted = (entries[i] == remove_entry);
488 buffer_size = MAX2(buffer_size, blob_file_size(entries[i]->size));
489 i++;
490 }
491
492 util_qsort_r(entries, num_entries, sizeof(*entries),
493 entry_sort_lru, db);
494
495 for (i = 0; blob_size > 0 && i < num_entries; i++) {
496 blob_size -= blob_file_size(entries[i]->size);
497 entries[i]->evicted = true;
498 }
499
500 util_qsort_r(entries, num_entries, sizeof(*entries),
501 entry_sort_offset, db);
502
503 /* entry_sort_offset() may zap the database */
504 if (!db->alive)
505 goto cleanup;
506
507 buffer = malloc(buffer_size);
508 if (!buffer)
509 goto cleanup;
510
511 /* Mark cache file invalid by writing zero-UUID header. If compaction will
512 * fail, then the file will remain to be invalid since we can't repair it. */
513 if (!mesa_db_write_header(&db->cache, 0, false) ||
514 !mesa_db_write_header(&db->index, 0, false))
515 goto cleanup;
516
517 /* Sync the file pointers */
518 if (!mesa_db_seek(compacted_cache, ftell(db->cache.file)) ||
519 !mesa_db_seek(compacted_index, ftell(db->index.file)))
520 goto cleanup;
521
522 /* Do the compaction */
523 for (i = 0; i < num_entries; i++) {
524 blob_size = blob_file_size(entries[i]->size);
525
526 /* Sanity-check the cache-read offset */
527 if (ftell(db->cache.file) != entries[i]->cache_db_file_offset)
528 goto cleanup;
529
530 if (entries[i]->evicted) {
531 /* Jump over the evicted entry */
532 if (!mesa_db_seek_cur(db->cache.file, blob_size) ||
533 !mesa_db_seek_cur(db->index.file, sizeof(index_entry)))
534 goto cleanup;
535
536 compact = true;
537 continue;
538 }
539
540 if (compact) {
541 /* Compact the cache file */
542 if (!mesa_db_read_data(db->cache.file, buffer, blob_size) ||
543 !mesa_db_cache_entry_valid(buffer) ||
544 !mesa_db_write_data(compacted_cache, buffer, blob_size))
545 goto cleanup;
546
547 /* Compact the index file */
548 if (!mesa_db_read(db->index.file, &index_entry) ||
549 !mesa_db_index_entry_valid(&index_entry) ||
550 index_entry.cache_db_file_offset != entries[i]->cache_db_file_offset ||
551 index_entry.size != entries[i]->size)
552 goto cleanup;
553
554 index_entry.cache_db_file_offset = ftell(compacted_cache) - blob_size;
555
556 if (!mesa_db_write(compacted_index, &index_entry))
557 goto cleanup;
558 } else {
559 /* Sanity-check the cache-write offset */
560 if (ftell(compacted_cache) != entries[i]->cache_db_file_offset)
561 goto cleanup;
562
563 /* Jump over the unchanged entry */
564 if (!mesa_db_seek_cur(db->index.file, sizeof(index_entry)) ||
565 !mesa_db_seek_cur(compacted_index, sizeof(index_entry)) ||
566 !mesa_db_seek_cur(db->cache.file, blob_size) ||
567 !mesa_db_seek_cur(compacted_cache, blob_size))
568 goto cleanup;
569 }
570 }
571
572 fflush(compacted_cache);
573 fflush(compacted_index);
574
575 /* Cut off the the freed space left after compaction */
576 if (!mesa_db_truncate(db->cache.file, ftell(compacted_cache)) ||
577 !mesa_db_truncate(db->index.file, ftell(compacted_index)))
578 goto cleanup;
579
580 /* Set the new UUID to let all cache readers know that the cache was changed */
581 db->uuid = mesa_db_generate_uuid();
582
583 if (!mesa_db_write_header(&db->cache, db->uuid, false) ||
584 !mesa_db_write_header(&db->index, db->uuid, false))
585 goto cleanup;
586
587 success = true;
588
589 cleanup:
590 free(buffer);
591 if (compacted_index)
592 fclose(compacted_index);
593 if (compacted_cache)
594 fclose(compacted_cache);
595 free(entries);
596
597 /* reload compacted index */
598 if (success && !mesa_db_reload(db))
599 success = false;
600
601 return success;
602 }
603
604 bool
mesa_cache_db_open(struct mesa_cache_db * db,const char * cache_path)605 mesa_cache_db_open(struct mesa_cache_db *db, const char *cache_path)
606 {
607 if (!mesa_db_open_file(&db->cache, cache_path, "mesa_cache.db"))
608 return false;
609
610 if (!mesa_db_open_file(&db->index, cache_path, "mesa_cache.idx"))
611 goto close_cache;
612
613 db->mem_ctx = ralloc_context(NULL);
614 if (!db->mem_ctx)
615 goto close_index;
616
617 simple_mtx_init(&db->flock_mtx, mtx_plain);
618
619 db->index_db = _mesa_hash_table_u64_create(NULL);
620 if (!db->index_db)
621 goto destroy_mtx;
622
623 if (!mesa_db_load(db, false))
624 goto destroy_hash;
625
626 return true;
627
628 destroy_hash:
629 _mesa_hash_table_u64_destroy(db->index_db);
630 destroy_mtx:
631 simple_mtx_destroy(&db->flock_mtx);
632
633 ralloc_free(db->mem_ctx);
634 close_index:
635 mesa_db_close_file(&db->index);
636 close_cache:
637 mesa_db_close_file(&db->cache);
638
639 return false;
640 }
641
642 bool
mesa_db_wipe_path(const char * cache_path)643 mesa_db_wipe_path(const char *cache_path)
644 {
645 struct mesa_cache_db db = {0};
646 bool success = true;
647
648 if (!mesa_db_remove_file(&db.cache, cache_path, "mesa_cache.db") ||
649 !mesa_db_remove_file(&db.index, cache_path, "mesa_cache.idx"))
650 success = false;
651
652 free(db.cache.path);
653 free(db.index.path);
654
655 return success;
656 }
657
658 void
mesa_cache_db_close(struct mesa_cache_db * db)659 mesa_cache_db_close(struct mesa_cache_db *db)
660 {
661 _mesa_hash_table_u64_destroy(db->index_db);
662 simple_mtx_destroy(&db->flock_mtx);
663 ralloc_free(db->mem_ctx);
664
665 mesa_db_close_file(&db->index);
666 mesa_db_close_file(&db->cache);
667 }
668
669 void
mesa_cache_db_set_size_limit(struct mesa_cache_db * db,uint64_t max_cache_size)670 mesa_cache_db_set_size_limit(struct mesa_cache_db *db,
671 uint64_t max_cache_size)
672 {
673 db->max_cache_size = max_cache_size;
674 }
675
676 unsigned int
mesa_cache_db_file_entry_size(void)677 mesa_cache_db_file_entry_size(void)
678 {
679 return sizeof(struct mesa_cache_db_file_entry);
680 }
681
682 void *
mesa_cache_db_read_entry(struct mesa_cache_db * db,const uint8_t * cache_key_160bit,size_t * size)683 mesa_cache_db_read_entry(struct mesa_cache_db *db,
684 const uint8_t *cache_key_160bit,
685 size_t *size)
686 {
687 uint64_t hash = to_mesa_cache_db_hash(cache_key_160bit);
688 struct mesa_cache_db_file_entry cache_entry;
689 struct mesa_index_db_file_entry index_entry;
690 struct mesa_index_db_hash_entry *hash_entry;
691 void *data = NULL;
692
693 if (!mesa_db_lock(db))
694 return NULL;
695
696 if (!db->alive)
697 goto fail;
698
699 if (mesa_db_uuid_changed(db) && !mesa_db_reload(db))
700 goto fail_fatal;
701
702 if (!mesa_db_update_index(db))
703 goto fail_fatal;
704
705 hash_entry = _mesa_hash_table_u64_search(db->index_db, hash);
706 if (!hash_entry)
707 goto fail;
708
709 if (!mesa_db_seek(db->cache.file, hash_entry->cache_db_file_offset) ||
710 !mesa_db_read(db->cache.file, &cache_entry) ||
711 !mesa_db_cache_entry_valid(&cache_entry))
712 goto fail_fatal;
713
714 if (memcmp(cache_entry.key, cache_key_160bit, sizeof(cache_entry.key)))
715 goto fail;
716
717 data = malloc(cache_entry.size);
718 if (!data)
719 goto fail;
720
721 if (!mesa_db_read_data(db->cache.file, data, cache_entry.size) ||
722 util_hash_crc32(data, cache_entry.size) != cache_entry.crc)
723 goto fail_fatal;
724
725 if (!mesa_db_seek(db->index.file, hash_entry->index_db_file_offset) ||
726 !mesa_db_read(db->index.file, &index_entry) ||
727 !mesa_db_index_entry_valid(&index_entry) ||
728 index_entry.cache_db_file_offset != hash_entry->cache_db_file_offset ||
729 index_entry.size != hash_entry->size)
730 goto fail_fatal;
731
732 index_entry.last_access_time = os_time_get_nano();
733 hash_entry->last_access_time = index_entry.last_access_time;
734
735 if (!mesa_db_seek(db->index.file, hash_entry->index_db_file_offset) ||
736 !mesa_db_write(db->index.file, &index_entry))
737 goto fail_fatal;
738
739 fflush(db->index.file);
740
741 mesa_db_unlock(db);
742
743 *size = cache_entry.size;
744
745 return data;
746
747 fail_fatal:
748 mesa_db_zap(db);
749 fail:
750 free(data);
751
752 mesa_db_unlock(db);
753
754 return NULL;
755 }
756
757 static bool
mesa_cache_db_has_space_locked(struct mesa_cache_db * db,size_t blob_size)758 mesa_cache_db_has_space_locked(struct mesa_cache_db *db, size_t blob_size)
759 {
760 return ftell(db->cache.file) + blob_file_size(blob_size) -
761 sizeof(struct mesa_db_file_header) <= db->max_cache_size;
762 }
763
764 static size_t
mesa_cache_db_eviction_size(struct mesa_cache_db * db)765 mesa_cache_db_eviction_size(struct mesa_cache_db *db)
766 {
767 return db->max_cache_size / 2 - sizeof(struct mesa_db_file_header);
768 }
769
770 bool
mesa_cache_db_entry_write(struct mesa_cache_db * db,const uint8_t * cache_key_160bit,const void * blob,size_t blob_size)771 mesa_cache_db_entry_write(struct mesa_cache_db *db,
772 const uint8_t *cache_key_160bit,
773 const void *blob, size_t blob_size)
774 {
775 uint64_t hash = to_mesa_cache_db_hash(cache_key_160bit);
776 struct mesa_index_db_hash_entry *hash_entry = NULL;
777 struct mesa_cache_db_file_entry cache_entry;
778 struct mesa_index_db_file_entry index_entry;
779
780 if (!mesa_db_lock(db))
781 return false;
782
783 if (!db->alive)
784 goto fail;
785
786 if (mesa_db_uuid_changed(db) && !mesa_db_reload(db))
787 goto fail_fatal;
788
789 if (!mesa_db_seek_end(db->cache.file))
790 goto fail_fatal;
791
792 if (!mesa_cache_db_has_space_locked(db, blob_size)) {
793 if (!mesa_db_compact(db, MAX2(blob_size, mesa_cache_db_eviction_size(db)),
794 NULL))
795 goto fail_fatal;
796 } else {
797 if (!mesa_db_update_index(db))
798 goto fail_fatal;
799 }
800
801 hash_entry = _mesa_hash_table_u64_search(db->index_db, hash);
802 if (hash_entry) {
803 hash_entry = NULL;
804 goto fail;
805 }
806
807 if (!mesa_db_seek_end(db->cache.file) ||
808 !mesa_db_seek_end(db->index.file))
809 goto fail_fatal;
810
811 memcpy(cache_entry.key, cache_key_160bit, sizeof(cache_entry.key));
812 cache_entry.crc = util_hash_crc32(blob, blob_size);
813 cache_entry.size = blob_size;
814
815 index_entry.hash = hash;
816 index_entry.size = blob_size;
817 index_entry.last_access_time = os_time_get_nano();
818 index_entry.cache_db_file_offset = ftell(db->cache.file);
819
820 hash_entry = ralloc(db->mem_ctx, struct mesa_index_db_hash_entry);
821 if (!hash_entry)
822 goto fail;
823
824 hash_entry->cache_db_file_offset = index_entry.cache_db_file_offset;
825 hash_entry->index_db_file_offset = ftell(db->index.file);
826 hash_entry->last_access_time = index_entry.last_access_time;
827 hash_entry->size = index_entry.size;
828
829 if (!mesa_db_write(db->cache.file, &cache_entry) ||
830 !mesa_db_write_data(db->cache.file, blob, blob_size) ||
831 !mesa_db_write(db->index.file, &index_entry))
832 goto fail_fatal;
833
834 fflush(db->cache.file);
835 fflush(db->index.file);
836
837 db->index.offset = ftell(db->index.file);
838
839 _mesa_hash_table_u64_insert(db->index_db, hash, hash_entry);
840
841 mesa_db_unlock(db);
842
843 return true;
844
845 fail_fatal:
846 mesa_db_zap(db);
847 fail:
848 mesa_db_unlock(db);
849
850 if (hash_entry)
851 ralloc_free(hash_entry);
852
853 return false;
854 }
855
856 bool
mesa_cache_db_entry_remove(struct mesa_cache_db * db,const uint8_t * cache_key_160bit)857 mesa_cache_db_entry_remove(struct mesa_cache_db *db,
858 const uint8_t *cache_key_160bit)
859 {
860 uint64_t hash = to_mesa_cache_db_hash(cache_key_160bit);
861 struct mesa_cache_db_file_entry cache_entry;
862 struct mesa_index_db_hash_entry *hash_entry;
863
864 if (!mesa_db_lock(db))
865 return NULL;
866
867 if (!db->alive)
868 goto fail;
869
870 if (mesa_db_uuid_changed(db) && !mesa_db_reload(db))
871 goto fail_fatal;
872
873 if (!mesa_db_update_index(db))
874 goto fail_fatal;
875
876 hash_entry = _mesa_hash_table_u64_search(db->index_db, hash);
877 if (!hash_entry)
878 goto fail;
879
880 if (!mesa_db_seek(db->cache.file, hash_entry->cache_db_file_offset) ||
881 !mesa_db_read(db->cache.file, &cache_entry) ||
882 !mesa_db_cache_entry_valid(&cache_entry))
883 goto fail_fatal;
884
885 if (memcmp(cache_entry.key, cache_key_160bit, sizeof(cache_entry.key)))
886 goto fail;
887
888 if (!mesa_db_compact(db, 0, hash_entry))
889 goto fail_fatal;
890
891 mesa_db_unlock(db);
892
893 return true;
894
895 fail_fatal:
896 mesa_db_zap(db);
897 fail:
898 mesa_db_unlock(db);
899
900 return false;
901 }
902
903 bool
mesa_cache_db_has_space(struct mesa_cache_db * db,size_t blob_size)904 mesa_cache_db_has_space(struct mesa_cache_db *db, size_t blob_size)
905 {
906 bool has_space;
907
908 if (!mesa_db_lock(db))
909 return false;
910
911 if (!mesa_db_seek_end(db->cache.file))
912 goto fail_fatal;
913
914 has_space = mesa_cache_db_has_space_locked(db, blob_size);
915
916 mesa_db_unlock(db);
917
918 return has_space;
919
920 fail_fatal:
921 mesa_db_zap(db);
922 mesa_db_unlock(db);
923
924 return false;
925 }
926
927 static uint64_t
mesa_cache_db_eviction_2x_score_period(void)928 mesa_cache_db_eviction_2x_score_period(void)
929 {
930 const uint64_t nsec_per_sec = 1000000000ull;
931 static uint64_t period = 0;
932
933 if (period)
934 return period;
935
936 period = debug_get_num_option("MESA_DISK_CACHE_DATABASE_EVICTION_SCORE_2X_PERIOD",
937 30 * 24 * 60 * 60) * nsec_per_sec;
938
939 return period;
940 }
941
942 double
mesa_cache_db_eviction_score(struct mesa_cache_db * db)943 mesa_cache_db_eviction_score(struct mesa_cache_db *db)
944 {
945 int64_t eviction_size = mesa_cache_db_eviction_size(db);
946 struct mesa_index_db_hash_entry **entries;
947 unsigned num_entries, i = 0;
948 double eviction_score = 0;
949
950 if (!mesa_db_lock(db))
951 return 0;
952
953 if (!db->alive)
954 goto fail;
955
956 if (!mesa_db_reload(db))
957 goto fail_fatal;
958
959 num_entries = _mesa_hash_table_num_entries(db->index_db->table);
960 entries = calloc(num_entries, sizeof(*entries));
961 if (!entries)
962 goto fail;
963
964 hash_table_foreach(db->index_db->table, entry)
965 entries[i++] = entry->data;
966
967 util_qsort_r(entries, num_entries, sizeof(*entries),
968 entry_sort_lru, db);
969
970 for (i = 0; eviction_size > 0 && i < num_entries; i++) {
971 uint64_t entry_age = os_time_get_nano() - entries[i]->last_access_time;
972 unsigned entry_size = blob_file_size(entries[i]->size);
973
974 /* Eviction score is a sum of weighted cache entry sizes,
975 * where weight doubles for each month of entry's age.
976 */
977 uint64_t period = mesa_cache_db_eviction_2x_score_period();
978 double entry_scale = 1 + (double)entry_age / period;
979 double entry_score = entry_size * entry_scale;
980
981 eviction_score += entry_score;
982 eviction_size -= entry_size;
983 }
984
985 free(entries);
986
987 mesa_db_unlock(db);
988
989 return eviction_score;
990
991 fail_fatal:
992 mesa_db_zap(db);
993 fail:
994 mesa_db_unlock(db);
995
996 return 0;
997 }
998
999 #endif /* DETECT_OS_WINDOWS */
1000