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