1bf215546Sopenharmony_ci/*
2bf215546Sopenharmony_ci * Copyright © 2020 Valve Corporation
3bf215546Sopenharmony_ci *
4bf215546Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a
5bf215546Sopenharmony_ci * copy of this software and associated documentation files (the "Software"),
6bf215546Sopenharmony_ci * to deal in the Software without restriction, including without limitation
7bf215546Sopenharmony_ci * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8bf215546Sopenharmony_ci * and/or sell copies of the Software, and to permit persons to whom the
9bf215546Sopenharmony_ci * Software is furnished to do so, subject to the following conditions:
10bf215546Sopenharmony_ci *
11bf215546Sopenharmony_ci * The above copyright notice and this permission notice (including the next
12bf215546Sopenharmony_ci * paragraph) shall be included in all copies or substantial portions of the
13bf215546Sopenharmony_ci * Software.
14bf215546Sopenharmony_ci *
15bf215546Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16bf215546Sopenharmony_ci * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17bf215546Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18bf215546Sopenharmony_ci * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19bf215546Sopenharmony_ci * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20bf215546Sopenharmony_ci * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21bf215546Sopenharmony_ci * IN THE SOFTWARE.
22bf215546Sopenharmony_ci */
23bf215546Sopenharmony_ci
24bf215546Sopenharmony_ci/* This is a basic c implementation of a fossilize db like format intended for
25bf215546Sopenharmony_ci * use with the Mesa shader cache.
26bf215546Sopenharmony_ci *
27bf215546Sopenharmony_ci * The format is compatible enough to allow the fossilize db tools to be used
28bf215546Sopenharmony_ci * to do things like merge db collections.
29bf215546Sopenharmony_ci */
30bf215546Sopenharmony_ci
31bf215546Sopenharmony_ci#include "fossilize_db.h"
32bf215546Sopenharmony_ci
33bf215546Sopenharmony_ci#ifdef FOZ_DB_UTIL
34bf215546Sopenharmony_ci
35bf215546Sopenharmony_ci#include <assert.h>
36bf215546Sopenharmony_ci#include <stddef.h>
37bf215546Sopenharmony_ci#include <stdlib.h>
38bf215546Sopenharmony_ci#include <string.h>
39bf215546Sopenharmony_ci#include <sys/file.h>
40bf215546Sopenharmony_ci#include <sys/types.h>
41bf215546Sopenharmony_ci#include <unistd.h>
42bf215546Sopenharmony_ci
43bf215546Sopenharmony_ci#include "crc32.h"
44bf215546Sopenharmony_ci#include "hash_table.h"
45bf215546Sopenharmony_ci#include "mesa-sha1.h"
46bf215546Sopenharmony_ci#include "ralloc.h"
47bf215546Sopenharmony_ci
48bf215546Sopenharmony_ci#define FOZ_REF_MAGIC_SIZE 16
49bf215546Sopenharmony_ci
50bf215546Sopenharmony_cistatic const uint8_t stream_reference_magic_and_version[FOZ_REF_MAGIC_SIZE] = {
51bf215546Sopenharmony_ci   0x81, 'F', 'O', 'S',
52bf215546Sopenharmony_ci   'S', 'I', 'L', 'I',
53bf215546Sopenharmony_ci   'Z', 'E', 'D', 'B',
54bf215546Sopenharmony_ci   0, 0, 0, FOSSILIZE_FORMAT_VERSION, /* 4 bytes to use for versioning. */
55bf215546Sopenharmony_ci};
56bf215546Sopenharmony_ci
57bf215546Sopenharmony_ci/* Mesa uses 160bit hashes to identify cache entries, a hash of this size
58bf215546Sopenharmony_ci * makes collisions virtually impossible for our use case. However the foz db
59bf215546Sopenharmony_ci * format uses a 64bit hash table to lookup file offsets for reading cache
60bf215546Sopenharmony_ci * entries so we must shorten our hash.
61bf215546Sopenharmony_ci */
62bf215546Sopenharmony_cistatic uint64_t
63bf215546Sopenharmony_citruncate_hash_to_64bits(const uint8_t *cache_key)
64bf215546Sopenharmony_ci{
65bf215546Sopenharmony_ci   uint64_t hash = 0;
66bf215546Sopenharmony_ci   unsigned shift = 7;
67bf215546Sopenharmony_ci   for (unsigned i = 0; i < 8; i++) {
68bf215546Sopenharmony_ci      hash |= ((uint64_t)cache_key[i]) << shift * 8;
69bf215546Sopenharmony_ci      shift--;
70bf215546Sopenharmony_ci   }
71bf215546Sopenharmony_ci   return hash;
72bf215546Sopenharmony_ci}
73bf215546Sopenharmony_ci
74bf215546Sopenharmony_cistatic bool
75bf215546Sopenharmony_cicheck_files_opened_successfully(FILE *file, FILE *db_idx)
76bf215546Sopenharmony_ci{
77bf215546Sopenharmony_ci   if (!file) {
78bf215546Sopenharmony_ci      if (db_idx)
79bf215546Sopenharmony_ci         fclose(db_idx);
80bf215546Sopenharmony_ci      return false;
81bf215546Sopenharmony_ci   }
82bf215546Sopenharmony_ci
83bf215546Sopenharmony_ci   if (!db_idx) {
84bf215546Sopenharmony_ci      if (file)
85bf215546Sopenharmony_ci         fclose(file);
86bf215546Sopenharmony_ci      return false;
87bf215546Sopenharmony_ci   }
88bf215546Sopenharmony_ci
89bf215546Sopenharmony_ci   return true;
90bf215546Sopenharmony_ci}
91bf215546Sopenharmony_ci
92bf215546Sopenharmony_cistatic bool
93bf215546Sopenharmony_cicreate_foz_db_filenames(char *cache_path, char *name, char **filename,
94bf215546Sopenharmony_ci                        char **idx_filename)
95bf215546Sopenharmony_ci{
96bf215546Sopenharmony_ci   if (asprintf(filename, "%s/%s.foz", cache_path, name) == -1)
97bf215546Sopenharmony_ci      return false;
98bf215546Sopenharmony_ci
99bf215546Sopenharmony_ci   if (asprintf(idx_filename, "%s/%s_idx.foz", cache_path, name) == -1) {
100bf215546Sopenharmony_ci      free(*filename);
101bf215546Sopenharmony_ci      return false;
102bf215546Sopenharmony_ci   }
103bf215546Sopenharmony_ci
104bf215546Sopenharmony_ci   return true;
105bf215546Sopenharmony_ci}
106bf215546Sopenharmony_ci
107bf215546Sopenharmony_ci
108bf215546Sopenharmony_ci/* This looks at stuff that was added to the index since the last time we looked at it. This is safe
109bf215546Sopenharmony_ci * to do without locking the file as we assume the file is append only */
110bf215546Sopenharmony_cistatic void
111bf215546Sopenharmony_ciupdate_foz_index(struct foz_db *foz_db, FILE *db_idx, unsigned file_idx)
112bf215546Sopenharmony_ci{
113bf215546Sopenharmony_ci   uint64_t offset = ftell(db_idx);
114bf215546Sopenharmony_ci   fseek(db_idx, 0, SEEK_END);
115bf215546Sopenharmony_ci   uint64_t len = ftell(db_idx);
116bf215546Sopenharmony_ci   uint64_t parsed_offset = offset;
117bf215546Sopenharmony_ci
118bf215546Sopenharmony_ci   if (offset == len)
119bf215546Sopenharmony_ci      return;
120bf215546Sopenharmony_ci
121bf215546Sopenharmony_ci   fseek(db_idx, offset, SEEK_SET);
122bf215546Sopenharmony_ci   while (offset < len) {
123bf215546Sopenharmony_ci      char bytes_to_read[FOSSILIZE_BLOB_HASH_LENGTH + sizeof(struct foz_payload_header)];
124bf215546Sopenharmony_ci      struct foz_payload_header *header;
125bf215546Sopenharmony_ci
126bf215546Sopenharmony_ci      /* Corrupt entry. Our process might have been killed before we
127bf215546Sopenharmony_ci       * could write all data.
128bf215546Sopenharmony_ci       */
129bf215546Sopenharmony_ci      if (offset + sizeof(bytes_to_read) > len)
130bf215546Sopenharmony_ci         break;
131bf215546Sopenharmony_ci
132bf215546Sopenharmony_ci      /* NAME + HEADER in one read */
133bf215546Sopenharmony_ci      if (fread(bytes_to_read, 1, sizeof(bytes_to_read), db_idx) !=
134bf215546Sopenharmony_ci          sizeof(bytes_to_read))
135bf215546Sopenharmony_ci         break;
136bf215546Sopenharmony_ci
137bf215546Sopenharmony_ci      offset += sizeof(bytes_to_read);
138bf215546Sopenharmony_ci      header = (struct foz_payload_header*)&bytes_to_read[FOSSILIZE_BLOB_HASH_LENGTH];
139bf215546Sopenharmony_ci
140bf215546Sopenharmony_ci      /* Corrupt entry. Our process might have been killed before we
141bf215546Sopenharmony_ci       * could write all data.
142bf215546Sopenharmony_ci       */
143bf215546Sopenharmony_ci      if (offset + header->payload_size > len ||
144bf215546Sopenharmony_ci          header->payload_size != sizeof(uint64_t))
145bf215546Sopenharmony_ci         break;
146bf215546Sopenharmony_ci
147bf215546Sopenharmony_ci      char hash_str[FOSSILIZE_BLOB_HASH_LENGTH + 1] = {0};
148bf215546Sopenharmony_ci      memcpy(hash_str, bytes_to_read, FOSSILIZE_BLOB_HASH_LENGTH);
149bf215546Sopenharmony_ci
150bf215546Sopenharmony_ci      /* read cache item offset from index file */
151bf215546Sopenharmony_ci      uint64_t cache_offset;
152bf215546Sopenharmony_ci      if (fread(&cache_offset, 1, sizeof(cache_offset), db_idx) !=
153bf215546Sopenharmony_ci          sizeof(cache_offset))
154bf215546Sopenharmony_ci         break;
155bf215546Sopenharmony_ci
156bf215546Sopenharmony_ci      offset += header->payload_size;
157bf215546Sopenharmony_ci      parsed_offset = offset;
158bf215546Sopenharmony_ci
159bf215546Sopenharmony_ci      struct foz_db_entry *entry = ralloc(foz_db->mem_ctx,
160bf215546Sopenharmony_ci                                          struct foz_db_entry);
161bf215546Sopenharmony_ci      entry->header = *header;
162bf215546Sopenharmony_ci      entry->file_idx = file_idx;
163bf215546Sopenharmony_ci      _mesa_sha1_hex_to_sha1(entry->key, hash_str);
164bf215546Sopenharmony_ci
165bf215546Sopenharmony_ci      /* Truncate the entry's hash string to a 64bit hash for use with a
166bf215546Sopenharmony_ci       * 64bit hash table for looking up file offsets.
167bf215546Sopenharmony_ci       */
168bf215546Sopenharmony_ci      hash_str[16] = '\0';
169bf215546Sopenharmony_ci      uint64_t key = strtoull(hash_str, NULL, 16);
170bf215546Sopenharmony_ci
171bf215546Sopenharmony_ci      entry->offset = cache_offset;
172bf215546Sopenharmony_ci
173bf215546Sopenharmony_ci      _mesa_hash_table_u64_insert(foz_db->index_db, key, entry);
174bf215546Sopenharmony_ci   }
175bf215546Sopenharmony_ci
176bf215546Sopenharmony_ci
177bf215546Sopenharmony_ci   fseek(db_idx, parsed_offset, SEEK_SET);
178bf215546Sopenharmony_ci}
179bf215546Sopenharmony_ci
180bf215546Sopenharmony_ci/* exclusive flock with timeout. timeout is in nanoseconds */
181bf215546Sopenharmony_cistatic int lock_file_with_timeout(FILE *f, int64_t timeout)
182bf215546Sopenharmony_ci{
183bf215546Sopenharmony_ci   int err;
184bf215546Sopenharmony_ci   int fd = fileno(f);
185bf215546Sopenharmony_ci   int64_t iterations = MAX2(DIV_ROUND_UP(timeout, 1000000), 1);
186bf215546Sopenharmony_ci
187bf215546Sopenharmony_ci   /* Since there is no blocking flock with timeout and we don't want to totally spin on getting the
188bf215546Sopenharmony_ci    * lock, use a nonblocking method and retry every millisecond. */
189bf215546Sopenharmony_ci   for (int64_t iter = 0; iter < iterations; ++iter) {
190bf215546Sopenharmony_ci      err = flock(fd, LOCK_EX | LOCK_NB);
191bf215546Sopenharmony_ci      if (err == 0 || errno != EAGAIN)
192bf215546Sopenharmony_ci         break;
193bf215546Sopenharmony_ci      usleep(1000);
194bf215546Sopenharmony_ci   }
195bf215546Sopenharmony_ci   return err;
196bf215546Sopenharmony_ci}
197bf215546Sopenharmony_ci
198bf215546Sopenharmony_cistatic bool
199bf215546Sopenharmony_ciload_foz_dbs(struct foz_db *foz_db, FILE *db_idx, uint8_t file_idx,
200bf215546Sopenharmony_ci             bool read_only)
201bf215546Sopenharmony_ci{
202bf215546Sopenharmony_ci   /* Scan through the archive and get the list of cache entries. */
203bf215546Sopenharmony_ci   fseek(db_idx, 0, SEEK_END);
204bf215546Sopenharmony_ci   size_t len = ftell(db_idx);
205bf215546Sopenharmony_ci   rewind(db_idx);
206bf215546Sopenharmony_ci
207bf215546Sopenharmony_ci   /* Try not to take the lock if len >= the size of the header, but if it is smaller we take the
208bf215546Sopenharmony_ci    * lock to potentially initialize the files. */
209bf215546Sopenharmony_ci   if (len < sizeof(stream_reference_magic_and_version)) {
210bf215546Sopenharmony_ci      /* Wait for 100 ms in case of contention, after that we prioritize getting the app started. */
211bf215546Sopenharmony_ci      int err = lock_file_with_timeout(foz_db->file[file_idx], 100000000);
212bf215546Sopenharmony_ci      if (err == -1)
213bf215546Sopenharmony_ci         goto fail;
214bf215546Sopenharmony_ci
215bf215546Sopenharmony_ci      /* Compute length again so we know nobody else did it in the meantime */
216bf215546Sopenharmony_ci      fseek(db_idx, 0, SEEK_END);
217bf215546Sopenharmony_ci      len = ftell(db_idx);
218bf215546Sopenharmony_ci      rewind(db_idx);
219bf215546Sopenharmony_ci   }
220bf215546Sopenharmony_ci
221bf215546Sopenharmony_ci   if (len != 0) {
222bf215546Sopenharmony_ci      uint8_t magic[FOZ_REF_MAGIC_SIZE];
223bf215546Sopenharmony_ci      if (fread(magic, 1, FOZ_REF_MAGIC_SIZE, db_idx) != FOZ_REF_MAGIC_SIZE)
224bf215546Sopenharmony_ci         goto fail;
225bf215546Sopenharmony_ci
226bf215546Sopenharmony_ci      if (memcmp(magic, stream_reference_magic_and_version,
227bf215546Sopenharmony_ci                 FOZ_REF_MAGIC_SIZE - 1))
228bf215546Sopenharmony_ci         goto fail;
229bf215546Sopenharmony_ci
230bf215546Sopenharmony_ci      int version = magic[FOZ_REF_MAGIC_SIZE - 1];
231bf215546Sopenharmony_ci      if (version > FOSSILIZE_FORMAT_VERSION ||
232bf215546Sopenharmony_ci          version < FOSSILIZE_FORMAT_MIN_COMPAT_VERSION)
233bf215546Sopenharmony_ci         goto fail;
234bf215546Sopenharmony_ci
235bf215546Sopenharmony_ci   } else {
236bf215546Sopenharmony_ci      /* Appending to a fresh file. Make sure we have the magic. */
237bf215546Sopenharmony_ci      if (fwrite(stream_reference_magic_and_version, 1,
238bf215546Sopenharmony_ci                 sizeof(stream_reference_magic_and_version), foz_db->file[file_idx]) !=
239bf215546Sopenharmony_ci          sizeof(stream_reference_magic_and_version))
240bf215546Sopenharmony_ci         goto fail;
241bf215546Sopenharmony_ci
242bf215546Sopenharmony_ci      if (fwrite(stream_reference_magic_and_version, 1,
243bf215546Sopenharmony_ci                 sizeof(stream_reference_magic_and_version), db_idx) !=
244bf215546Sopenharmony_ci          sizeof(stream_reference_magic_and_version))
245bf215546Sopenharmony_ci         goto fail;
246bf215546Sopenharmony_ci
247bf215546Sopenharmony_ci      fflush(foz_db->file[file_idx]);
248bf215546Sopenharmony_ci      fflush(db_idx);
249bf215546Sopenharmony_ci   }
250bf215546Sopenharmony_ci
251bf215546Sopenharmony_ci   flock(fileno(foz_db->file[file_idx]), LOCK_UN);
252bf215546Sopenharmony_ci
253bf215546Sopenharmony_ci   update_foz_index(foz_db, db_idx, file_idx);
254bf215546Sopenharmony_ci
255bf215546Sopenharmony_ci   foz_db->alive = true;
256bf215546Sopenharmony_ci   return true;
257bf215546Sopenharmony_ci
258bf215546Sopenharmony_cifail:
259bf215546Sopenharmony_ci   flock(fileno(foz_db->file[file_idx]), LOCK_UN);
260bf215546Sopenharmony_ci   foz_destroy(foz_db);
261bf215546Sopenharmony_ci   return false;
262bf215546Sopenharmony_ci}
263bf215546Sopenharmony_ci
264bf215546Sopenharmony_ci/* Here we open mesa cache foz dbs files. If the files exist we load the index
265bf215546Sopenharmony_ci * db into a hash table. The index db contains the offsets needed to later
266bf215546Sopenharmony_ci * read cache entries from the foz db containing the actual cache entries.
267bf215546Sopenharmony_ci */
268bf215546Sopenharmony_cibool
269bf215546Sopenharmony_cifoz_prepare(struct foz_db *foz_db, char *cache_path)
270bf215546Sopenharmony_ci{
271bf215546Sopenharmony_ci   char *filename = NULL;
272bf215546Sopenharmony_ci   char *idx_filename = NULL;
273bf215546Sopenharmony_ci   if (!create_foz_db_filenames(cache_path, "foz_cache", &filename, &idx_filename))
274bf215546Sopenharmony_ci      return false;
275bf215546Sopenharmony_ci
276bf215546Sopenharmony_ci   /* Open the default foz dbs for read/write. If the files didn't already exist
277bf215546Sopenharmony_ci    * create them.
278bf215546Sopenharmony_ci    */
279bf215546Sopenharmony_ci   foz_db->file[0] = fopen(filename, "a+b");
280bf215546Sopenharmony_ci   foz_db->db_idx = fopen(idx_filename, "a+b");
281bf215546Sopenharmony_ci
282bf215546Sopenharmony_ci   free(filename);
283bf215546Sopenharmony_ci   free(idx_filename);
284bf215546Sopenharmony_ci
285bf215546Sopenharmony_ci   if (!check_files_opened_successfully(foz_db->file[0], foz_db->db_idx))
286bf215546Sopenharmony_ci      return false;
287bf215546Sopenharmony_ci
288bf215546Sopenharmony_ci   simple_mtx_init(&foz_db->mtx, mtx_plain);
289bf215546Sopenharmony_ci   simple_mtx_init(&foz_db->flock_mtx, mtx_plain);
290bf215546Sopenharmony_ci   foz_db->mem_ctx = ralloc_context(NULL);
291bf215546Sopenharmony_ci   foz_db->index_db = _mesa_hash_table_u64_create(NULL);
292bf215546Sopenharmony_ci
293bf215546Sopenharmony_ci   if (!load_foz_dbs(foz_db, foz_db->db_idx, 0, false))
294bf215546Sopenharmony_ci      return false;
295bf215546Sopenharmony_ci
296bf215546Sopenharmony_ci   uint8_t file_idx = 1;
297bf215546Sopenharmony_ci   char *foz_dbs = getenv("MESA_DISK_CACHE_READ_ONLY_FOZ_DBS");
298bf215546Sopenharmony_ci   if (!foz_dbs)
299bf215546Sopenharmony_ci      return true;
300bf215546Sopenharmony_ci
301bf215546Sopenharmony_ci   for (unsigned n; n = strcspn(foz_dbs, ","), *foz_dbs;
302bf215546Sopenharmony_ci        foz_dbs += MAX2(1, n)) {
303bf215546Sopenharmony_ci      char *foz_db_filename = strndup(foz_dbs, n);
304bf215546Sopenharmony_ci
305bf215546Sopenharmony_ci      filename = NULL;
306bf215546Sopenharmony_ci      idx_filename = NULL;
307bf215546Sopenharmony_ci      if (!create_foz_db_filenames(cache_path, foz_db_filename, &filename,
308bf215546Sopenharmony_ci                                   &idx_filename)) {
309bf215546Sopenharmony_ci         free(foz_db_filename);
310bf215546Sopenharmony_ci         continue; /* Ignore invalid user provided filename and continue */
311bf215546Sopenharmony_ci      }
312bf215546Sopenharmony_ci      free(foz_db_filename);
313bf215546Sopenharmony_ci
314bf215546Sopenharmony_ci      /* Open files as read only */
315bf215546Sopenharmony_ci      foz_db->file[file_idx] = fopen(filename, "rb");
316bf215546Sopenharmony_ci      FILE *db_idx = fopen(idx_filename, "rb");
317bf215546Sopenharmony_ci
318bf215546Sopenharmony_ci      free(filename);
319bf215546Sopenharmony_ci      free(idx_filename);
320bf215546Sopenharmony_ci
321bf215546Sopenharmony_ci      if (!check_files_opened_successfully(foz_db->file[file_idx], db_idx)) {
322bf215546Sopenharmony_ci         /* Prevent foz_destroy from destroying it a second time. */
323bf215546Sopenharmony_ci         foz_db->file[file_idx] = NULL;
324bf215546Sopenharmony_ci
325bf215546Sopenharmony_ci         continue; /* Ignore invalid user provided filename and continue */
326bf215546Sopenharmony_ci      }
327bf215546Sopenharmony_ci
328bf215546Sopenharmony_ci      if (!load_foz_dbs(foz_db, db_idx, file_idx, true)) {
329bf215546Sopenharmony_ci         fclose(db_idx);
330bf215546Sopenharmony_ci         return false;
331bf215546Sopenharmony_ci      }
332bf215546Sopenharmony_ci
333bf215546Sopenharmony_ci      fclose(db_idx);
334bf215546Sopenharmony_ci      file_idx++;
335bf215546Sopenharmony_ci
336bf215546Sopenharmony_ci      if (file_idx >= FOZ_MAX_DBS)
337bf215546Sopenharmony_ci         break;
338bf215546Sopenharmony_ci   }
339bf215546Sopenharmony_ci
340bf215546Sopenharmony_ci   return true;
341bf215546Sopenharmony_ci}
342bf215546Sopenharmony_ci
343bf215546Sopenharmony_civoid
344bf215546Sopenharmony_cifoz_destroy(struct foz_db *foz_db)
345bf215546Sopenharmony_ci{
346bf215546Sopenharmony_ci   if (foz_db->db_idx)
347bf215546Sopenharmony_ci      fclose(foz_db->db_idx);
348bf215546Sopenharmony_ci   for (unsigned i = 0; i < FOZ_MAX_DBS; i++) {
349bf215546Sopenharmony_ci      if (foz_db->file[i])
350bf215546Sopenharmony_ci         fclose(foz_db->file[i]);
351bf215546Sopenharmony_ci   }
352bf215546Sopenharmony_ci
353bf215546Sopenharmony_ci   if (foz_db->mem_ctx) {
354bf215546Sopenharmony_ci      _mesa_hash_table_u64_destroy(foz_db->index_db);
355bf215546Sopenharmony_ci      ralloc_free(foz_db->mem_ctx);
356bf215546Sopenharmony_ci      simple_mtx_destroy(&foz_db->flock_mtx);
357bf215546Sopenharmony_ci      simple_mtx_destroy(&foz_db->mtx);
358bf215546Sopenharmony_ci   }
359bf215546Sopenharmony_ci}
360bf215546Sopenharmony_ci
361bf215546Sopenharmony_ci/* Here we lookup a cache entry in the index hash table. If an entry is found
362bf215546Sopenharmony_ci * we use the retrieved offset to read the cache entry from disk.
363bf215546Sopenharmony_ci */
364bf215546Sopenharmony_civoid *
365bf215546Sopenharmony_cifoz_read_entry(struct foz_db *foz_db, const uint8_t *cache_key_160bit,
366bf215546Sopenharmony_ci               size_t *size)
367bf215546Sopenharmony_ci{
368bf215546Sopenharmony_ci   uint64_t hash = truncate_hash_to_64bits(cache_key_160bit);
369bf215546Sopenharmony_ci
370bf215546Sopenharmony_ci   void *data = NULL;
371bf215546Sopenharmony_ci
372bf215546Sopenharmony_ci   if (!foz_db->alive)
373bf215546Sopenharmony_ci      return NULL;
374bf215546Sopenharmony_ci
375bf215546Sopenharmony_ci   simple_mtx_lock(&foz_db->mtx);
376bf215546Sopenharmony_ci
377bf215546Sopenharmony_ci   struct foz_db_entry *entry =
378bf215546Sopenharmony_ci      _mesa_hash_table_u64_search(foz_db->index_db, hash);
379bf215546Sopenharmony_ci   if (!entry) {
380bf215546Sopenharmony_ci      update_foz_index(foz_db, foz_db->db_idx, 0);
381bf215546Sopenharmony_ci      entry = _mesa_hash_table_u64_search(foz_db->index_db, hash);
382bf215546Sopenharmony_ci   }
383bf215546Sopenharmony_ci   if (!entry) {
384bf215546Sopenharmony_ci      simple_mtx_unlock(&foz_db->mtx);
385bf215546Sopenharmony_ci      return NULL;
386bf215546Sopenharmony_ci   }
387bf215546Sopenharmony_ci
388bf215546Sopenharmony_ci   uint8_t file_idx = entry->file_idx;
389bf215546Sopenharmony_ci   if (fseek(foz_db->file[file_idx], entry->offset, SEEK_SET) < 0)
390bf215546Sopenharmony_ci      goto fail;
391bf215546Sopenharmony_ci
392bf215546Sopenharmony_ci   uint32_t header_size = sizeof(struct foz_payload_header);
393bf215546Sopenharmony_ci   if (fread(&entry->header, 1, header_size, foz_db->file[file_idx]) !=
394bf215546Sopenharmony_ci       header_size)
395bf215546Sopenharmony_ci      goto fail;
396bf215546Sopenharmony_ci
397bf215546Sopenharmony_ci   /* Check for collision using full 160bit hash for increased assurance
398bf215546Sopenharmony_ci    * against potential collisions.
399bf215546Sopenharmony_ci    */
400bf215546Sopenharmony_ci   for (int i = 0; i < 20; i++) {
401bf215546Sopenharmony_ci      if (cache_key_160bit[i] != entry->key[i])
402bf215546Sopenharmony_ci         goto fail;
403bf215546Sopenharmony_ci   }
404bf215546Sopenharmony_ci
405bf215546Sopenharmony_ci   uint32_t data_sz = entry->header.payload_size;
406bf215546Sopenharmony_ci   data = malloc(data_sz);
407bf215546Sopenharmony_ci   if (fread(data, 1, data_sz, foz_db->file[file_idx]) != data_sz)
408bf215546Sopenharmony_ci      goto fail;
409bf215546Sopenharmony_ci
410bf215546Sopenharmony_ci   /* verify checksum */
411bf215546Sopenharmony_ci   if (entry->header.crc != 0) {
412bf215546Sopenharmony_ci      if (util_hash_crc32(data, data_sz) != entry->header.crc)
413bf215546Sopenharmony_ci         goto fail;
414bf215546Sopenharmony_ci   }
415bf215546Sopenharmony_ci
416bf215546Sopenharmony_ci   simple_mtx_unlock(&foz_db->mtx);
417bf215546Sopenharmony_ci
418bf215546Sopenharmony_ci   if (size)
419bf215546Sopenharmony_ci      *size = data_sz;
420bf215546Sopenharmony_ci
421bf215546Sopenharmony_ci   return data;
422bf215546Sopenharmony_ci
423bf215546Sopenharmony_cifail:
424bf215546Sopenharmony_ci   free(data);
425bf215546Sopenharmony_ci
426bf215546Sopenharmony_ci   /* reading db entry failed. reset the file offset */
427bf215546Sopenharmony_ci   simple_mtx_unlock(&foz_db->mtx);
428bf215546Sopenharmony_ci
429bf215546Sopenharmony_ci   return NULL;
430bf215546Sopenharmony_ci}
431bf215546Sopenharmony_ci
432bf215546Sopenharmony_ci/* Here we write the cache entry to disk and store its offset in the index db.
433bf215546Sopenharmony_ci */
434bf215546Sopenharmony_cibool
435bf215546Sopenharmony_cifoz_write_entry(struct foz_db *foz_db, const uint8_t *cache_key_160bit,
436bf215546Sopenharmony_ci                const void *blob, size_t blob_size)
437bf215546Sopenharmony_ci{
438bf215546Sopenharmony_ci   uint64_t hash = truncate_hash_to_64bits(cache_key_160bit);
439bf215546Sopenharmony_ci
440bf215546Sopenharmony_ci   if (!foz_db->alive)
441bf215546Sopenharmony_ci      return false;
442bf215546Sopenharmony_ci
443bf215546Sopenharmony_ci   /* The flock is per-fd, not per thread, we do it outside of the main mutex to avoid having to
444bf215546Sopenharmony_ci    * wait in the mutex potentially blocking reads. We use the secondary flock_mtx to stop race
445bf215546Sopenharmony_ci    * conditions between the write threads sharing the same file descriptor. */
446bf215546Sopenharmony_ci   simple_mtx_lock(&foz_db->flock_mtx);
447bf215546Sopenharmony_ci
448bf215546Sopenharmony_ci   /* Wait for 1 second. This is done outside of the main mutex as I believe there is more potential
449bf215546Sopenharmony_ci    * for file contention than mtx contention of significant length. */
450bf215546Sopenharmony_ci   int err = lock_file_with_timeout(foz_db->file[0], 1000000000);
451bf215546Sopenharmony_ci   if (err == -1)
452bf215546Sopenharmony_ci      goto fail_file;
453bf215546Sopenharmony_ci
454bf215546Sopenharmony_ci   simple_mtx_lock(&foz_db->mtx);
455bf215546Sopenharmony_ci
456bf215546Sopenharmony_ci   update_foz_index(foz_db, foz_db->db_idx, 0);
457bf215546Sopenharmony_ci
458bf215546Sopenharmony_ci   struct foz_db_entry *entry =
459bf215546Sopenharmony_ci      _mesa_hash_table_u64_search(foz_db->index_db, hash);
460bf215546Sopenharmony_ci   if (entry) {
461bf215546Sopenharmony_ci      simple_mtx_unlock(&foz_db->mtx);
462bf215546Sopenharmony_ci      flock(fileno(foz_db->file[0]), LOCK_UN);
463bf215546Sopenharmony_ci      simple_mtx_unlock(&foz_db->flock_mtx);
464bf215546Sopenharmony_ci      return NULL;
465bf215546Sopenharmony_ci   }
466bf215546Sopenharmony_ci
467bf215546Sopenharmony_ci   /* Prepare db entry header and blob ready for writing */
468bf215546Sopenharmony_ci   struct foz_payload_header header;
469bf215546Sopenharmony_ci   header.uncompressed_size = blob_size;
470bf215546Sopenharmony_ci   header.format = FOSSILIZE_COMPRESSION_NONE;
471bf215546Sopenharmony_ci   header.payload_size = blob_size;
472bf215546Sopenharmony_ci   header.crc = util_hash_crc32(blob, blob_size);
473bf215546Sopenharmony_ci
474bf215546Sopenharmony_ci   fseek(foz_db->file[0], 0, SEEK_END);
475bf215546Sopenharmony_ci
476bf215546Sopenharmony_ci   /* Write hash header to db */
477bf215546Sopenharmony_ci   char hash_str[FOSSILIZE_BLOB_HASH_LENGTH + 1]; /* 40 digits + null */
478bf215546Sopenharmony_ci   _mesa_sha1_format(hash_str, cache_key_160bit);
479bf215546Sopenharmony_ci   if (fwrite(hash_str, 1, FOSSILIZE_BLOB_HASH_LENGTH, foz_db->file[0]) !=
480bf215546Sopenharmony_ci       FOSSILIZE_BLOB_HASH_LENGTH)
481bf215546Sopenharmony_ci      goto fail;
482bf215546Sopenharmony_ci
483bf215546Sopenharmony_ci   off_t offset = ftell(foz_db->file[0]);
484bf215546Sopenharmony_ci
485bf215546Sopenharmony_ci   /* Write db entry header */
486bf215546Sopenharmony_ci   if (fwrite(&header, 1, sizeof(header), foz_db->file[0]) != sizeof(header))
487bf215546Sopenharmony_ci      goto fail;
488bf215546Sopenharmony_ci
489bf215546Sopenharmony_ci   /* Now write the db entry blob */
490bf215546Sopenharmony_ci   if (fwrite(blob, 1, blob_size, foz_db->file[0]) != blob_size)
491bf215546Sopenharmony_ci      goto fail;
492bf215546Sopenharmony_ci
493bf215546Sopenharmony_ci   /* Flush everything to file to reduce chance of cache corruption */
494bf215546Sopenharmony_ci   fflush(foz_db->file[0]);
495bf215546Sopenharmony_ci
496bf215546Sopenharmony_ci   /* Write hash header to index db */
497bf215546Sopenharmony_ci   if (fwrite(hash_str, 1, FOSSILIZE_BLOB_HASH_LENGTH, foz_db->db_idx) !=
498bf215546Sopenharmony_ci       FOSSILIZE_BLOB_HASH_LENGTH)
499bf215546Sopenharmony_ci      goto fail;
500bf215546Sopenharmony_ci
501bf215546Sopenharmony_ci   header.uncompressed_size = sizeof(uint64_t);
502bf215546Sopenharmony_ci   header.format = FOSSILIZE_COMPRESSION_NONE;
503bf215546Sopenharmony_ci   header.payload_size = sizeof(uint64_t);
504bf215546Sopenharmony_ci   header.crc = 0;
505bf215546Sopenharmony_ci
506bf215546Sopenharmony_ci   if (fwrite(&header, 1, sizeof(header), foz_db->db_idx) !=
507bf215546Sopenharmony_ci       sizeof(header))
508bf215546Sopenharmony_ci      goto fail;
509bf215546Sopenharmony_ci
510bf215546Sopenharmony_ci   if (fwrite(&offset, 1, sizeof(uint64_t), foz_db->db_idx) !=
511bf215546Sopenharmony_ci       sizeof(uint64_t))
512bf215546Sopenharmony_ci      goto fail;
513bf215546Sopenharmony_ci
514bf215546Sopenharmony_ci   /* Flush everything to file to reduce chance of cache corruption */
515bf215546Sopenharmony_ci   fflush(foz_db->db_idx);
516bf215546Sopenharmony_ci
517bf215546Sopenharmony_ci   entry = ralloc(foz_db->mem_ctx, struct foz_db_entry);
518bf215546Sopenharmony_ci   entry->header = header;
519bf215546Sopenharmony_ci   entry->offset = offset;
520bf215546Sopenharmony_ci   entry->file_idx = 0;
521bf215546Sopenharmony_ci   _mesa_sha1_hex_to_sha1(entry->key, hash_str);
522bf215546Sopenharmony_ci   _mesa_hash_table_u64_insert(foz_db->index_db, hash, entry);
523bf215546Sopenharmony_ci
524bf215546Sopenharmony_ci   simple_mtx_unlock(&foz_db->mtx);
525bf215546Sopenharmony_ci   flock(fileno(foz_db->file[0]), LOCK_UN);
526bf215546Sopenharmony_ci   simple_mtx_unlock(&foz_db->flock_mtx);
527bf215546Sopenharmony_ci
528bf215546Sopenharmony_ci   return true;
529bf215546Sopenharmony_ci
530bf215546Sopenharmony_cifail:
531bf215546Sopenharmony_ci   simple_mtx_unlock(&foz_db->mtx);
532bf215546Sopenharmony_cifail_file:
533bf215546Sopenharmony_ci   flock(fileno(foz_db->file[0]), LOCK_UN);
534bf215546Sopenharmony_ci   simple_mtx_unlock(&foz_db->flock_mtx);
535bf215546Sopenharmony_ci   return false;
536bf215546Sopenharmony_ci}
537bf215546Sopenharmony_ci#else
538bf215546Sopenharmony_ci
539bf215546Sopenharmony_cibool
540bf215546Sopenharmony_cifoz_prepare(struct foz_db *foz_db, char *filename)
541bf215546Sopenharmony_ci{
542bf215546Sopenharmony_ci   fprintf(stderr, "Warning: Mesa single file cache selected but Mesa wasn't "
543bf215546Sopenharmony_ci           "built with single cache file support. Shader cache will be disabled"
544bf215546Sopenharmony_ci           "!\n");
545bf215546Sopenharmony_ci   return false;
546bf215546Sopenharmony_ci}
547bf215546Sopenharmony_ci
548bf215546Sopenharmony_civoid
549bf215546Sopenharmony_cifoz_destroy(struct foz_db *foz_db)
550bf215546Sopenharmony_ci{
551bf215546Sopenharmony_ci}
552bf215546Sopenharmony_ci
553bf215546Sopenharmony_civoid *
554bf215546Sopenharmony_cifoz_read_entry(struct foz_db *foz_db, const uint8_t *cache_key_160bit,
555bf215546Sopenharmony_ci               size_t *size)
556bf215546Sopenharmony_ci{
557bf215546Sopenharmony_ci   return false;
558bf215546Sopenharmony_ci}
559bf215546Sopenharmony_ci
560bf215546Sopenharmony_cibool
561bf215546Sopenharmony_cifoz_write_entry(struct foz_db *foz_db, const uint8_t *cache_key_160bit,
562bf215546Sopenharmony_ci                const void *blob, size_t size)
563bf215546Sopenharmony_ci{
564bf215546Sopenharmony_ci   return false;
565bf215546Sopenharmony_ci}
566bf215546Sopenharmony_ci
567bf215546Sopenharmony_ci#endif
568