1 /*
2  * Copyright © 2020 Valve Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23 
24 /* This is a basic c implementation of a fossilize db like format intended for
25  * use with the Mesa shader cache.
26  *
27  * The format is compatible enough to allow the fossilize db tools to be used
28  * to do things like merge db collections.
29  */
30 
31 #include "fossilize_db.h"
32 
33 #ifdef FOZ_DB_UTIL
34 
35 #include <assert.h>
36 #include <stddef.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sys/file.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <unistd.h>
43 
44 #ifdef FOZ_DB_UTIL_DYNAMIC_LIST
45 #include <sys/inotify.h>
46 #endif
47 
48 #include "util/u_debug.h"
49 
50 #include "crc32.h"
51 #include "hash_table.h"
52 #include "mesa-sha1.h"
53 #include "ralloc.h"
54 
55 #define FOZ_REF_MAGIC_SIZE 16
56 
57 static const uint8_t stream_reference_magic_and_version[FOZ_REF_MAGIC_SIZE] = {
58    0x81, 'F', 'O', 'S',
59    'S', 'I', 'L', 'I',
60    'Z', 'E', 'D', 'B',
61    0, 0, 0, FOSSILIZE_FORMAT_VERSION, /* 4 bytes to use for versioning. */
62 };
63 
64 /* Mesa uses 160bit hashes to identify cache entries, a hash of this size
65  * makes collisions virtually impossible for our use case. However the foz db
66  * format uses a 64bit hash table to lookup file offsets for reading cache
67  * entries so we must shorten our hash.
68  */
69 static uint64_t
truncate_hash_to_64bits(const uint8_t * cache_key)70 truncate_hash_to_64bits(const uint8_t *cache_key)
71 {
72    uint64_t hash = 0;
73    unsigned shift = 7;
74    for (unsigned i = 0; i < 8; i++) {
75       hash |= ((uint64_t)cache_key[i]) << shift * 8;
76       shift--;
77    }
78    return hash;
79 }
80 
81 static bool
check_files_opened_successfully(FILE * file,FILE * db_idx)82 check_files_opened_successfully(FILE *file, FILE *db_idx)
83 {
84    if (!file) {
85       if (db_idx)
86          fclose(db_idx);
87       return false;
88    }
89 
90    if (!db_idx) {
91       if (file)
92          fclose(file);
93       return false;
94    }
95 
96    return true;
97 }
98 
99 static bool
create_foz_db_filenames(const char * cache_path,char * name,char ** filename,char ** idx_filename)100 create_foz_db_filenames(const char *cache_path,
101                         char *name,
102                         char **filename,
103                         char **idx_filename)
104 {
105    if (asprintf(filename, "%s/%s.foz", cache_path, name) == -1)
106       return false;
107 
108    if (asprintf(idx_filename, "%s/%s_idx.foz", cache_path, name) == -1) {
109       free(*filename);
110       return false;
111    }
112 
113    return true;
114 }
115 
116 
117 /* This looks at stuff that was added to the index since the last time we looked at it. This is safe
118  * to do without locking the file as we assume the file is append only */
119 static void
update_foz_index(struct foz_db * foz_db,FILE * db_idx,unsigned file_idx)120 update_foz_index(struct foz_db *foz_db, FILE *db_idx, unsigned file_idx)
121 {
122    uint64_t offset = ftell(db_idx);
123    fseek(db_idx, 0, SEEK_END);
124    uint64_t len = ftell(db_idx);
125    uint64_t parsed_offset = offset;
126 
127    if (offset == len)
128       return;
129 
130    fseek(db_idx, offset, SEEK_SET);
131    while (offset < len) {
132       char bytes_to_read[FOSSILIZE_BLOB_HASH_LENGTH + sizeof(struct foz_payload_header)];
133       struct foz_payload_header *header;
134 
135       /* Corrupt entry. Our process might have been killed before we
136        * could write all data.
137        */
138       if (offset + sizeof(bytes_to_read) > len)
139          break;
140 
141       /* NAME + HEADER in one read */
142       if (fread(bytes_to_read, 1, sizeof(bytes_to_read), db_idx) !=
143           sizeof(bytes_to_read))
144          break;
145 
146       offset += sizeof(bytes_to_read);
147       header = (struct foz_payload_header*)&bytes_to_read[FOSSILIZE_BLOB_HASH_LENGTH];
148 
149       /* Corrupt entry. Our process might have been killed before we
150        * could write all data.
151        */
152       if (offset + header->payload_size > len ||
153           header->payload_size != sizeof(uint64_t))
154          break;
155 
156       char hash_str[FOSSILIZE_BLOB_HASH_LENGTH + 1] = {0};
157       memcpy(hash_str, bytes_to_read, FOSSILIZE_BLOB_HASH_LENGTH);
158 
159       /* read cache item offset from index file */
160       uint64_t cache_offset;
161       if (fread(&cache_offset, 1, sizeof(cache_offset), db_idx) !=
162           sizeof(cache_offset))
163          break;
164 
165       offset += header->payload_size;
166       parsed_offset = offset;
167 
168       struct foz_db_entry *entry = ralloc(foz_db->mem_ctx,
169                                           struct foz_db_entry);
170       entry->header = *header;
171       entry->file_idx = file_idx;
172       _mesa_sha1_hex_to_sha1(entry->key, hash_str);
173 
174       /* Truncate the entry's hash string to a 64bit hash for use with a
175        * 64bit hash table for looking up file offsets.
176        */
177       hash_str[16] = '\0';
178       uint64_t key = strtoull(hash_str, NULL, 16);
179 
180       entry->offset = cache_offset;
181 
182       _mesa_hash_table_u64_insert(foz_db->index_db, key, entry);
183    }
184 
185 
186    fseek(db_idx, parsed_offset, SEEK_SET);
187 }
188 
189 /* exclusive flock with timeout. timeout is in nanoseconds */
lock_file_with_timeout(FILE * f,int64_t timeout)190 static int lock_file_with_timeout(FILE *f, int64_t timeout)
191 {
192    int err;
193    int fd = fileno(f);
194    int64_t iterations = MAX2(DIV_ROUND_UP(timeout, 1000000), 1);
195 
196    /* Since there is no blocking flock with timeout and we don't want to totally spin on getting the
197     * lock, use a nonblocking method and retry every millisecond. */
198    for (int64_t iter = 0; iter < iterations; ++iter) {
199       err = flock(fd, LOCK_EX | LOCK_NB);
200       if (err == 0 || errno != EAGAIN)
201          break;
202       usleep(1000);
203    }
204    return err;
205 }
206 
207 static bool
load_foz_dbs(struct foz_db * foz_db,FILE * db_idx,uint8_t file_idx,bool read_only)208 load_foz_dbs(struct foz_db *foz_db, FILE *db_idx, uint8_t file_idx,
209              bool read_only)
210 {
211    /* Scan through the archive and get the list of cache entries. */
212    fseek(db_idx, 0, SEEK_END);
213    size_t len = ftell(db_idx);
214    rewind(db_idx);
215 
216    /* Try not to take the lock if len >= the size of the header, but if it is smaller we take the
217     * lock to potentially initialize the files. */
218    if (len < sizeof(stream_reference_magic_and_version)) {
219       /* Wait for 100 ms in case of contention, after that we prioritize getting the app started. */
220       int err = lock_file_with_timeout(foz_db->file[file_idx], 100000000);
221       if (err == -1)
222          goto fail;
223 
224       /* Compute length again so we know nobody else did it in the meantime */
225       fseek(db_idx, 0, SEEK_END);
226       len = ftell(db_idx);
227       rewind(db_idx);
228    }
229 
230    if (len != 0) {
231       uint8_t magic[FOZ_REF_MAGIC_SIZE];
232       if (fread(magic, 1, FOZ_REF_MAGIC_SIZE, db_idx) != FOZ_REF_MAGIC_SIZE)
233          goto fail;
234 
235       if (memcmp(magic, stream_reference_magic_and_version,
236                  FOZ_REF_MAGIC_SIZE - 1))
237          goto fail;
238 
239       int version = magic[FOZ_REF_MAGIC_SIZE - 1];
240       if (version > FOSSILIZE_FORMAT_VERSION ||
241           version < FOSSILIZE_FORMAT_MIN_COMPAT_VERSION)
242          goto fail;
243 
244    } else {
245       /* Appending to a fresh file. Make sure we have the magic. */
246       if (fwrite(stream_reference_magic_and_version, 1,
247                  sizeof(stream_reference_magic_and_version), foz_db->file[file_idx]) !=
248           sizeof(stream_reference_magic_and_version))
249          goto fail;
250 
251       if (fwrite(stream_reference_magic_and_version, 1,
252                  sizeof(stream_reference_magic_and_version), db_idx) !=
253           sizeof(stream_reference_magic_and_version))
254          goto fail;
255 
256       fflush(foz_db->file[file_idx]);
257       fflush(db_idx);
258    }
259 
260    flock(fileno(foz_db->file[file_idx]), LOCK_UN);
261 
262    if (foz_db->updater.thrd) {
263    /* If MESA_DISK_CACHE_READ_ONLY_FOZ_DBS_DYNAMIC_LIST is enabled, access to
264     * the foz_db hash table requires locking to prevent racing between this
265     * updated thread loading DBs at runtime and cache entry read/writes. */
266       simple_mtx_lock(&foz_db->mtx);
267       update_foz_index(foz_db, db_idx, file_idx);
268       simple_mtx_unlock(&foz_db->mtx);
269    } else {
270       update_foz_index(foz_db, db_idx, file_idx);
271    }
272 
273    foz_db->alive = true;
274    return true;
275 
276 fail:
277    flock(fileno(foz_db->file[file_idx]), LOCK_UN);
278    return false;
279 }
280 
281 static void
load_foz_dbs_ro(struct foz_db * foz_db,char * foz_dbs_ro)282 load_foz_dbs_ro(struct foz_db *foz_db, char *foz_dbs_ro)
283 {
284    uint8_t file_idx = 1;
285    char *filename = NULL;
286    char *idx_filename = NULL;
287 
288    for (unsigned n; n = strcspn(foz_dbs_ro, ","), *foz_dbs_ro;
289         foz_dbs_ro += MAX2(1, n)) {
290       char *foz_db_filename = strndup(foz_dbs_ro, n);
291 
292       filename = NULL;
293       idx_filename = NULL;
294       if (!create_foz_db_filenames(foz_db->cache_path, foz_db_filename,
295                                    &filename, &idx_filename)) {
296          free(foz_db_filename);
297          continue; /* Ignore invalid user provided filename and continue */
298       }
299       free(foz_db_filename);
300 
301       /* Open files as read only */
302       foz_db->file[file_idx] = fopen(filename, "rb");
303       FILE *db_idx = fopen(idx_filename, "rb");
304 
305       free(filename);
306       free(idx_filename);
307 
308       if (!check_files_opened_successfully(foz_db->file[file_idx], db_idx)) {
309          /* Prevent foz_destroy from destroying it a second time. */
310          foz_db->file[file_idx] = NULL;
311 
312          continue; /* Ignore invalid user provided filename and continue */
313       }
314 
315       if (!load_foz_dbs(foz_db, db_idx, file_idx, true)) {
316          fclose(db_idx);
317          fclose(foz_db->file[file_idx]);
318          foz_db->file[file_idx] = NULL;
319 
320          continue; /* Ignore invalid user provided foz db */
321       }
322 
323       fclose(db_idx);
324       file_idx++;
325 
326       if (file_idx >= FOZ_MAX_DBS)
327          break;
328    }
329 }
330 
331 #ifdef FOZ_DB_UTIL_DYNAMIC_LIST
332 static bool
check_file_already_loaded(struct foz_db * foz_db,FILE * db_file,uint8_t max_file_idx)333 check_file_already_loaded(struct foz_db *foz_db,
334                           FILE *db_file,
335                           uint8_t max_file_idx)
336 {
337    struct stat new_file_stat;
338 
339    if (fstat(fileno(db_file), &new_file_stat) == -1)
340       return false;
341 
342    for (int i = 0; i < max_file_idx; i++) {
343       struct stat loaded_file_stat;
344 
345       if (fstat(fileno(foz_db->file[i]), &loaded_file_stat) == -1)
346          continue;
347 
348       if ((loaded_file_stat.st_dev == new_file_stat.st_dev) &&
349           (loaded_file_stat.st_ino == new_file_stat.st_ino))
350          return true;
351    }
352 
353    return false;
354 }
355 
356 static bool
load_from_list_file(struct foz_db * foz_db,const char * foz_dbs_list_filename)357 load_from_list_file(struct foz_db *foz_db, const char *foz_dbs_list_filename)
358 {
359    uint8_t file_idx;
360    char list_entry[PATH_MAX];
361 
362    /* Find the first empty file idx slot */
363    for (file_idx = 0; file_idx < FOZ_MAX_DBS; file_idx++) {
364       if (!foz_db->file[file_idx])
365          break;
366    }
367 
368    if (file_idx >= FOZ_MAX_DBS)
369       return false;
370 
371    FILE *foz_dbs_list_file = fopen(foz_dbs_list_filename, "rb");
372    if (!foz_dbs_list_file)
373       return false;
374 
375    while (fgets(list_entry, sizeof(list_entry), foz_dbs_list_file)) {
376       char *db_filename = NULL;
377       char *idx_filename = NULL;
378       FILE *db_file = NULL;
379       FILE *idx_file = NULL;
380 
381       list_entry[strcspn(list_entry, "\n")] = '\0';
382 
383       if (!create_foz_db_filenames(foz_db->cache_path, list_entry,
384                                    &db_filename, &idx_filename))
385          continue;
386 
387       db_file = fopen(db_filename, "rb");
388       idx_file = fopen(idx_filename, "rb");
389 
390       free(db_filename);
391       free(idx_filename);
392 
393       if (!check_files_opened_successfully(db_file, idx_file))
394          continue;
395 
396       if (check_file_already_loaded(foz_db, db_file, file_idx)) {
397          fclose(db_file);
398          fclose(idx_file);
399 
400          continue;
401       }
402 
403       /* Must be set before calling load_foz_dbs() */
404       foz_db->file[file_idx] = db_file;
405 
406       if (!load_foz_dbs(foz_db, idx_file, file_idx, true)) {
407          fclose(db_file);
408          fclose(idx_file);
409          foz_db->file[file_idx] = NULL;
410 
411          continue;
412       }
413 
414       fclose(idx_file);
415       file_idx++;
416 
417       if (file_idx >= FOZ_MAX_DBS)
418          break;
419    }
420 
421    fclose(foz_dbs_list_file);
422    return true;
423 }
424 
425 static int
foz_dbs_list_updater_thrd(void * data)426 foz_dbs_list_updater_thrd(void *data)
427 {
428    char buf[10 * (sizeof(struct inotify_event) + NAME_MAX + 1)];
429    struct foz_db *foz_db = data;
430    struct foz_dbs_list_updater *updater = &foz_db->updater;
431 
432    while (1) {
433       int len = read(updater->inotify_fd, buf, sizeof(buf));
434 
435       if (len == -1 && errno != EAGAIN)
436          return errno;
437 
438       int i = 0;
439       while (i < len) {
440          struct inotify_event *event = (struct inotify_event *)&buf[i];
441 
442          i += sizeof(struct inotify_event) + event->len;
443 
444          if (event->mask & IN_CLOSE_WRITE)
445             load_from_list_file(foz_db, foz_db->updater.list_filename);
446 
447          /* List file deleted or watch removed by foz destroy */
448          if ((event->mask & IN_DELETE_SELF) || (event->mask & IN_IGNORED))
449             return 0;
450       }
451    }
452 
453    return 0;
454 }
455 
456 static bool
foz_dbs_list_updater_init(struct foz_db * foz_db,char * list_filename)457 foz_dbs_list_updater_init(struct foz_db *foz_db, char *list_filename)
458 {
459    struct foz_dbs_list_updater *updater = &foz_db->updater;
460 
461    /* Initial load */
462    if (!load_from_list_file(foz_db, list_filename))
463       return false;
464 
465    updater->list_filename = list_filename;
466 
467    int fd = inotify_init1(IN_CLOEXEC);
468    if (fd < 0)
469       return false;
470 
471    int wd = inotify_add_watch(fd, foz_db->updater.list_filename,
472                               IN_CLOSE_WRITE | IN_DELETE_SELF);
473    if (wd < 0) {
474       close(fd);
475       return false;
476    }
477 
478    updater->inotify_fd = fd;
479    updater->inotify_wd = wd;
480 
481    if (thrd_create(&updater->thrd, foz_dbs_list_updater_thrd, foz_db)) {
482       inotify_rm_watch(fd, wd);
483       close(fd);
484 
485       return false;
486    }
487 
488    return true;
489 }
490 #endif
491 
492 /* Here we open mesa cache foz dbs files. If the files exist we load the index
493  * db into a hash table. The index db contains the offsets needed to later
494  * read cache entries from the foz db containing the actual cache entries.
495  */
496 bool
foz_prepare(struct foz_db * foz_db,char * cache_path)497 foz_prepare(struct foz_db *foz_db, char *cache_path)
498 {
499    char *filename = NULL;
500    char *idx_filename = NULL;
501 
502    simple_mtx_init(&foz_db->mtx, mtx_plain);
503    simple_mtx_init(&foz_db->flock_mtx, mtx_plain);
504    foz_db->mem_ctx = ralloc_context(NULL);
505    foz_db->index_db = _mesa_hash_table_u64_create(NULL);
506    foz_db->cache_path = cache_path;
507 
508    /* Open the default foz dbs for read/write. If the files didn't already exist
509     * create them.
510     */
511    if (debug_get_bool_option("MESA_DISK_CACHE_SINGLE_FILE", false)) {
512       if (!create_foz_db_filenames(cache_path, "foz_cache",
513                                    &filename, &idx_filename))
514          goto fail;
515 
516       foz_db->file[0] = fopen(filename, "a+b");
517       foz_db->db_idx = fopen(idx_filename, "a+b");
518 
519       free(filename);
520       free(idx_filename);
521 
522       if (!check_files_opened_successfully(foz_db->file[0], foz_db->db_idx))
523          goto fail;
524 
525       if (!load_foz_dbs(foz_db, foz_db->db_idx, 0, false))
526          goto fail;
527    }
528 
529    char *foz_dbs_ro = getenv("MESA_DISK_CACHE_READ_ONLY_FOZ_DBS");
530    if (foz_dbs_ro)
531       load_foz_dbs_ro(foz_db, foz_dbs_ro);
532 
533 #ifdef FOZ_DB_UTIL_DYNAMIC_LIST
534    char *foz_dbs_list =
535       getenv("MESA_DISK_CACHE_READ_ONLY_FOZ_DBS_DYNAMIC_LIST");
536    if (foz_dbs_list)
537       foz_dbs_list_updater_init(foz_db, foz_dbs_list);
538 #endif
539 
540    return true;
541 
542 fail:
543    foz_destroy(foz_db);
544 
545    return false;
546 }
547 
548 void
foz_destroy(struct foz_db * foz_db)549 foz_destroy(struct foz_db *foz_db)
550 {
551 #ifdef FOZ_DB_UTIL_DYNAMIC_LIST
552    struct foz_dbs_list_updater *updater = &foz_db->updater;
553    if (updater->thrd) {
554       inotify_rm_watch(updater->inotify_fd, updater->inotify_wd);
555       /* inotify_rm_watch() triggers the IN_IGNORE event for the thread
556        * to exit.
557        */
558       thrd_join(updater->thrd, NULL);
559       close(updater->inotify_fd);
560    }
561 #endif
562 
563    if (foz_db->db_idx)
564       fclose(foz_db->db_idx);
565    for (unsigned i = 0; i < FOZ_MAX_DBS; i++) {
566       if (foz_db->file[i])
567          fclose(foz_db->file[i]);
568    }
569 
570    if (foz_db->mem_ctx) {
571       _mesa_hash_table_u64_destroy(foz_db->index_db);
572       ralloc_free(foz_db->mem_ctx);
573       simple_mtx_destroy(&foz_db->flock_mtx);
574       simple_mtx_destroy(&foz_db->mtx);
575    }
576 
577    memset(foz_db, 0, sizeof(*foz_db));
578 }
579 
580 /* Here we lookup a cache entry in the index hash table. If an entry is found
581  * we use the retrieved offset to read the cache entry from disk.
582  */
583 void *
foz_read_entry(struct foz_db * foz_db,const uint8_t * cache_key_160bit,size_t * size)584 foz_read_entry(struct foz_db *foz_db, const uint8_t *cache_key_160bit,
585                size_t *size)
586 {
587    uint64_t hash = truncate_hash_to_64bits(cache_key_160bit);
588 
589    void *data = NULL;
590 
591    if (!foz_db->alive)
592       return NULL;
593 
594    simple_mtx_lock(&foz_db->mtx);
595 
596    struct foz_db_entry *entry =
597       _mesa_hash_table_u64_search(foz_db->index_db, hash);
598    if (!entry && foz_db->db_idx) {
599       update_foz_index(foz_db, foz_db->db_idx, 0);
600       entry = _mesa_hash_table_u64_search(foz_db->index_db, hash);
601    }
602    if (!entry) {
603       simple_mtx_unlock(&foz_db->mtx);
604       return NULL;
605    }
606 
607    uint8_t file_idx = entry->file_idx;
608    if (fseek(foz_db->file[file_idx], entry->offset, SEEK_SET) < 0)
609       goto fail;
610 
611    uint32_t header_size = sizeof(struct foz_payload_header);
612    if (fread(&entry->header, 1, header_size, foz_db->file[file_idx]) !=
613        header_size)
614       goto fail;
615 
616    /* Check for collision using full 160bit hash for increased assurance
617     * against potential collisions.
618     */
619    for (int i = 0; i < 20; i++) {
620       if (cache_key_160bit[i] != entry->key[i])
621          goto fail;
622    }
623 
624    uint32_t data_sz = entry->header.payload_size;
625    data = malloc(data_sz);
626    if (fread(data, 1, data_sz, foz_db->file[file_idx]) != data_sz)
627       goto fail;
628 
629    /* verify checksum */
630    if (entry->header.crc != 0) {
631       if (util_hash_crc32(data, data_sz) != entry->header.crc)
632          goto fail;
633    }
634 
635    simple_mtx_unlock(&foz_db->mtx);
636 
637    if (size)
638       *size = data_sz;
639 
640    return data;
641 
642 fail:
643    free(data);
644 
645    /* reading db entry failed. reset the file offset */
646    simple_mtx_unlock(&foz_db->mtx);
647 
648    return NULL;
649 }
650 
651 /* Here we write the cache entry to disk and store its offset in the index db.
652  */
653 bool
foz_write_entry(struct foz_db * foz_db,const uint8_t * cache_key_160bit,const void * blob,size_t blob_size)654 foz_write_entry(struct foz_db *foz_db, const uint8_t *cache_key_160bit,
655                 const void *blob, size_t blob_size)
656 {
657    uint64_t hash = truncate_hash_to_64bits(cache_key_160bit);
658 
659    if (!foz_db->alive || !foz_db->file[0])
660       return false;
661 
662    /* The flock is per-fd, not per thread, we do it outside of the main mutex to avoid having to
663     * wait in the mutex potentially blocking reads. We use the secondary flock_mtx to stop race
664     * conditions between the write threads sharing the same file descriptor. */
665    simple_mtx_lock(&foz_db->flock_mtx);
666 
667    /* Wait for 1 second. This is done outside of the main mutex as I believe there is more potential
668     * for file contention than mtx contention of significant length. */
669    int err = lock_file_with_timeout(foz_db->file[0], 1000000000);
670    if (err == -1)
671       goto fail_file;
672 
673    simple_mtx_lock(&foz_db->mtx);
674 
675    update_foz_index(foz_db, foz_db->db_idx, 0);
676 
677    struct foz_db_entry *entry =
678       _mesa_hash_table_u64_search(foz_db->index_db, hash);
679    if (entry) {
680       simple_mtx_unlock(&foz_db->mtx);
681       flock(fileno(foz_db->file[0]), LOCK_UN);
682       simple_mtx_unlock(&foz_db->flock_mtx);
683       return NULL;
684    }
685 
686    /* Prepare db entry header and blob ready for writing */
687    struct foz_payload_header header;
688    header.uncompressed_size = blob_size;
689    header.format = FOSSILIZE_COMPRESSION_NONE;
690    header.payload_size = blob_size;
691    header.crc = util_hash_crc32(blob, blob_size);
692 
693    fseek(foz_db->file[0], 0, SEEK_END);
694 
695    /* Write hash header to db */
696    char hash_str[FOSSILIZE_BLOB_HASH_LENGTH + 1]; /* 40 digits + null */
697    _mesa_sha1_format(hash_str, cache_key_160bit);
698    if (fwrite(hash_str, 1, FOSSILIZE_BLOB_HASH_LENGTH, foz_db->file[0]) !=
699        FOSSILIZE_BLOB_HASH_LENGTH)
700       goto fail;
701 
702    off_t offset = ftell(foz_db->file[0]);
703 
704    /* Write db entry header */
705    if (fwrite(&header, 1, sizeof(header), foz_db->file[0]) != sizeof(header))
706       goto fail;
707 
708    /* Now write the db entry blob */
709    if (fwrite(blob, 1, blob_size, foz_db->file[0]) != blob_size)
710       goto fail;
711 
712    /* Flush everything to file to reduce chance of cache corruption */
713    fflush(foz_db->file[0]);
714 
715    /* Write hash header to index db */
716    if (fwrite(hash_str, 1, FOSSILIZE_BLOB_HASH_LENGTH, foz_db->db_idx) !=
717        FOSSILIZE_BLOB_HASH_LENGTH)
718       goto fail;
719 
720    header.uncompressed_size = sizeof(uint64_t);
721    header.format = FOSSILIZE_COMPRESSION_NONE;
722    header.payload_size = sizeof(uint64_t);
723    header.crc = 0;
724 
725    if (fwrite(&header, 1, sizeof(header), foz_db->db_idx) !=
726        sizeof(header))
727       goto fail;
728 
729    if (fwrite(&offset, 1, sizeof(uint64_t), foz_db->db_idx) !=
730        sizeof(uint64_t))
731       goto fail;
732 
733    /* Flush everything to file to reduce chance of cache corruption */
734    fflush(foz_db->db_idx);
735 
736    entry = ralloc(foz_db->mem_ctx, struct foz_db_entry);
737    entry->header = header;
738    entry->offset = offset;
739    entry->file_idx = 0;
740    _mesa_sha1_hex_to_sha1(entry->key, hash_str);
741    _mesa_hash_table_u64_insert(foz_db->index_db, hash, entry);
742 
743    simple_mtx_unlock(&foz_db->mtx);
744    flock(fileno(foz_db->file[0]), LOCK_UN);
745    simple_mtx_unlock(&foz_db->flock_mtx);
746 
747    return true;
748 
749 fail:
750    simple_mtx_unlock(&foz_db->mtx);
751 fail_file:
752    flock(fileno(foz_db->file[0]), LOCK_UN);
753    simple_mtx_unlock(&foz_db->flock_mtx);
754    return false;
755 }
756 #else
757 
758 bool
foz_prepare(struct foz_db * foz_db,char * filename)759 foz_prepare(struct foz_db *foz_db, char *filename)
760 {
761    fprintf(stderr, "Warning: Mesa single file cache selected but Mesa wasn't "
762            "built with single cache file support. Shader cache will be disabled"
763            "!\n");
764    return false;
765 }
766 
767 void
foz_destroy(struct foz_db * foz_db)768 foz_destroy(struct foz_db *foz_db)
769 {
770 }
771 
772 void *
foz_read_entry(struct foz_db * foz_db,const uint8_t * cache_key_160bit,size_t * size)773 foz_read_entry(struct foz_db *foz_db, const uint8_t *cache_key_160bit,
774                size_t *size)
775 {
776    return false;
777 }
778 
779 bool
foz_write_entry(struct foz_db * foz_db,const uint8_t * cache_key_160bit,const void * blob,size_t size)780 foz_write_entry(struct foz_db *foz_db, const uint8_t *cache_key_160bit,
781                 const void *blob, size_t size)
782 {
783    return false;
784 }
785 
786 #endif
787