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