Coverage Report

Created: 2025-09-27 07:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fontconfig/src/fccache.c
Line
Count
Source
1
/*
2
 * Copyright © 2000 Keith Packard
3
 * Copyright © 2005 Patrick Lam
4
 *
5
 * Permission to use, copy, modify, distribute, and sell this software and its
6
 * documentation for any purpose is hereby granted without fee, provided that
7
 * the above copyright notice appear in all copies and that both that
8
 * copyright notice and this permission notice appear in supporting
9
 * documentation, and that the name of the author(s) not be used in
10
 * advertising or publicity pertaining to distribution of the software without
11
 * specific, written prior permission.  The authors make no
12
 * representations about the suitability of this software for any purpose.  It
13
 * is provided "as is" without express or implied warranty.
14
 *
15
 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17
 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21
 * PERFORMANCE OF THIS SOFTWARE.
22
 */
23
#include "fcint.h"
24
25
#include "fcarch.h"
26
#include "fcmd5.h"
27
28
#include <fcntl.h>
29
#include <stdio.h>
30
#include <stdlib.h>
31
#ifdef HAVE_DIRENT_H
32
#  include <dirent.h>
33
#endif
34
#include <inttypes.h>
35
#include <limits.h>
36
#include <string.h>
37
#include <sys/stat.h>
38
#include <sys/types.h>
39
40
#ifndef _WIN32
41
#  include <sys/time.h>
42
#else
43
#  include <winsock2.h> /* for struct timeval */
44
#endif
45
46
#include <assert.h>
47
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
48
#  include <sys/mman.h>
49
#  include <unistd.h>
50
#endif
51
#if defined(_WIN32)
52
#  include <sys/locking.h>
53
#endif
54
55
#ifndef O_BINARY
56
132
#  define O_BINARY 0
57
#endif
58
59
FcBool
60
FcDirCacheCreateUUID (FcChar8  *dir,
61
                      FcBool    force,
62
                      FcConfig *config)
63
0
{
64
0
    return FcTrue;
65
0
}
66
67
FcBool
68
FcDirCacheDeleteUUID (const FcChar8 *dir,
69
                      FcConfig      *config)
70
44
{
71
44
    FcBool ret = FcTrue;
72
44
#ifndef _WIN32
73
44
    const FcChar8 *sysroot;
74
44
    FcChar8       *target, *d;
75
44
    struct stat    statb;
76
44
    struct timeval times[2];
77
78
44
    config = FcConfigReference (config);
79
44
    if (!config)
80
0
  return FcFalse;
81
44
    sysroot = FcConfigGetSysRoot (config);
82
44
    if (sysroot)
83
0
  d = FcStrBuildFilename (sysroot, dir, NULL);
84
44
    else
85
44
  d = FcStrBuildFilename (dir, NULL);
86
44
    if (FcStat (d, &statb) != 0) {
87
44
  ret = FcFalse;
88
44
  goto bail;
89
44
    }
90
0
    target = FcStrBuildFilename (d, ".uuid", NULL);
91
0
    ret = unlink ((char *)target) == 0;
92
0
    if (ret) {
93
0
  times[0].tv_sec = statb.st_atime;
94
0
  times[1].tv_sec = statb.st_mtime;
95
0
#  ifdef HAVE_STRUCT_STAT_ST_MTIM
96
0
  times[0].tv_usec = statb.st_atim.tv_nsec / 1000;
97
0
  times[1].tv_usec = statb.st_mtim.tv_nsec / 1000;
98
#  else
99
  times[0].tv_usec = 0;
100
  times[1].tv_usec = 0;
101
#  endif
102
0
  if (utimes ((const char *)d, times) != 0) {
103
0
      fprintf (stderr, "Unable to revert mtime: %s\n", d);
104
0
  }
105
0
    }
106
0
    FcStrFree (target);
107
44
bail:
108
44
    FcStrFree (d);
109
44
    FcConfigDestroy (config);
110
44
#endif
111
112
44
    return ret;
113
0
}
114
115
#define CACHEBASE_LEN (1 + 36 + 1 + sizeof (FC_ARCHITECTURE) + sizeof (FC_CACHE_SUFFIX))
116
117
static FcBool
118
FcCacheIsMmapSafe (int fd)
119
66
{
120
66
    enum {
121
66
  MMAP_NOT_INITIALIZED = 0,
122
66
  MMAP_USE,
123
66
  MMAP_DONT_USE,
124
66
  MMAP_CHECK_FS,
125
66
    } status;
126
66
    static void *static_status;
127
128
66
    status = (intptr_t)fc_atomic_ptr_get (&static_status);
129
130
66
    if (status == MMAP_NOT_INITIALIZED) {
131
11
  const char *env = getenv ("FONTCONFIG_USE_MMAP");
132
11
  FcBool      use;
133
11
  if (env && FcNameBool ((const FcChar8 *)env, &use))
134
0
      status = use ? MMAP_USE : MMAP_DONT_USE;
135
11
  else
136
11
      status = MMAP_CHECK_FS;
137
11
  (void)fc_atomic_ptr_cmpexch (&static_status, NULL, (void *)(intptr_t)status);
138
11
    }
139
140
66
    if (status == MMAP_CHECK_FS)
141
66
  return FcIsFsMmapSafe (fd);
142
0
    else
143
0
  return status == MMAP_USE;
144
66
}
145
146
static const char bin2hex[] = { '0', '1', '2', '3',
147
                                '4', '5', '6', '7',
148
                                '8', '9', 'a', 'b',
149
                                'c', 'd', 'e', 'f' };
150
151
static FcChar8 *
152
FcDirCacheBasenameMD5 (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
153
66
{
154
66
    FcChar8          *mapped_dir = NULL;
155
66
    unsigned char     hash[16];
156
66
    FcChar8          *hex_hash, *key = NULL;
157
66
    int               cnt;
158
66
    struct MD5Context ctx;
159
66
    const FcChar8    *salt, *orig_dir = NULL;
160
161
66
    salt = FcConfigMapSalt (config, dir);
162
    /* Obtain a path where "dir" is mapped to.
163
     * In case:
164
     * <remap-dir as-path="/usr/share/fonts">/run/host/fonts</remap-dir>
165
     *
166
     * FcConfigMapFontPath (config, "/run/host/fonts") will returns "/usr/share/fonts".
167
     */
168
66
    mapped_dir = FcConfigMapFontPath (config, dir);
169
66
    if (mapped_dir) {
170
0
  orig_dir = dir;
171
0
  dir = mapped_dir;
172
0
    }
173
66
    if (salt) {
174
0
  size_t dl = strlen ((const char *)dir);
175
0
  size_t sl = strlen ((const char *)salt);
176
177
0
  key = (FcChar8 *)malloc (dl + sl + 1);
178
0
  memcpy (key, dir, dl);
179
0
  memcpy (key + dl, salt, sl + 1);
180
0
  key[dl + sl] = 0;
181
0
  if (!orig_dir)
182
0
      orig_dir = dir;
183
0
  dir = key;
184
0
    }
185
66
    MD5Init (&ctx);
186
66
    MD5Update (&ctx, (const unsigned char *)dir, strlen ((const char *)dir));
187
188
66
    MD5Final (hash, &ctx);
189
190
66
    if (key)
191
0
  FcStrFree (key);
192
193
66
    hex_hash = cache_base;
194
1.12k
    for (cnt = 0; cnt < 16; ++cnt) {
195
1.05k
  hex_hash[2 * cnt] = bin2hex[hash[cnt] >> 4];
196
1.05k
  hex_hash[2 * cnt + 1] = bin2hex[hash[cnt] & 0xf];
197
1.05k
    }
198
66
    hex_hash[2 * cnt] = 0;
199
66
    strcat ((char *)cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
200
66
    if (FcDebug() & FC_DBG_CACHE) {
201
0
  printf ("cache: %s (dir: %s%s%s%s%s%s)\n", cache_base, orig_dir ? orig_dir : dir, mapped_dir ? " (mapped to " : "", mapped_dir ? (char *)mapped_dir : "", mapped_dir ? ")" : "", salt ? ", salt: " : "", salt ? (char *)salt : "");
202
0
    }
203
204
66
    if (mapped_dir)
205
0
  FcStrFree (mapped_dir);
206
207
66
    return cache_base;
208
66
}
209
210
#ifndef _WIN32
211
static FcChar8 *
212
FcDirCacheBasenameUUID (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
213
66
{
214
66
    FcChar8       *target, *fuuid;
215
66
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
216
66
    int            fd;
217
218
    /* We don't need to apply remapping here. because .uuid was created at that very directory
219
     * to determine the cache name no matter where it was mapped to.
220
     */
221
66
    cache_base[0] = 0;
222
66
    if (sysroot)
223
0
  target = FcStrBuildFilename (sysroot, dir, NULL);
224
66
    else
225
66
  target = FcStrdup (dir);
226
66
    fuuid = FcStrBuildFilename (target, ".uuid", NULL);
227
66
    if ((fd = FcOpen ((char *)fuuid, O_RDONLY)) != -1) {
228
0
  char    suuid[37];
229
0
  ssize_t len;
230
231
0
  memset (suuid, 0, sizeof (suuid));
232
0
  len = read (fd, suuid, 36);
233
0
  suuid[36] = 0;
234
0
  close (fd);
235
0
  if (len < 0)
236
0
      goto bail;
237
0
  cache_base[0] = '/';
238
0
  strcpy ((char *)&cache_base[1], suuid);
239
0
  strcat ((char *)cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
240
0
  if (FcDebug() & FC_DBG_CACHE) {
241
0
      printf ("cache fallbacks to: %s (dir: %s)\n", cache_base, dir);
242
0
  }
243
0
    }
244
66
bail:
245
66
    FcStrFree (fuuid);
246
66
    FcStrFree (target);
247
248
66
    return cache_base;
249
66
}
250
#endif
251
252
FcBool
253
FcDirCacheUnlink (const FcChar8 *dir, FcConfig *config)
254
0
{
255
0
    FcChar8 *cache_hashed = NULL;
256
0
    FcChar8  cache_base[CACHEBASE_LEN];
257
0
#ifndef _WIN32
258
0
    FcChar8 uuid_cache_base[CACHEBASE_LEN];
259
0
#endif
260
0
    FcStrList     *list;
261
0
    FcChar8       *cache_dir;
262
0
    const FcChar8 *sysroot;
263
0
    FcBool         ret = FcTrue;
264
265
0
    config = FcConfigReference (config);
266
0
    if (!config)
267
0
  return FcFalse;
268
0
    sysroot = FcConfigGetSysRoot (config);
269
270
0
    FcDirCacheBasenameMD5 (config, dir, cache_base);
271
0
#ifndef _WIN32
272
0
    FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
273
0
#endif
274
275
0
    list = FcStrListCreate (config->cacheDirs);
276
0
    if (!list) {
277
0
  ret = FcFalse;
278
0
  goto bail;
279
0
    }
280
281
0
    while ((cache_dir = FcStrListNext (list))) {
282
0
  if (sysroot)
283
0
      cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
284
0
  else
285
0
      cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
286
0
  if (!cache_hashed)
287
0
      break;
288
0
  (void)unlink ((char *)cache_hashed);
289
0
  FcStrFree (cache_hashed);
290
0
#ifndef _WIN32
291
0
  if (uuid_cache_base[0] != 0) {
292
0
      if (sysroot)
293
0
    cache_hashed = FcStrBuildFilename (sysroot, cache_dir, uuid_cache_base, NULL);
294
0
      else
295
0
    cache_hashed = FcStrBuildFilename (cache_dir, uuid_cache_base, NULL);
296
0
      if (!cache_hashed)
297
0
    break;
298
0
      (void)unlink ((char *)cache_hashed);
299
0
      FcStrFree (cache_hashed);
300
0
  }
301
0
#endif
302
0
    }
303
0
    FcStrListDone (list);
304
0
    FcDirCacheDeleteUUID (dir, config);
305
    /* return FcFalse if something went wrong */
306
0
    if (cache_dir)
307
0
  ret = FcFalse;
308
0
bail:
309
0
    FcConfigDestroy (config);
310
311
0
    return ret;
312
0
}
313
314
static int
315
FcDirCacheOpenFile (const FcChar8 *cache_file, struct stat *file_stat)
316
132
{
317
132
    int fd;
318
319
#ifdef _WIN32
320
    if (FcStat (cache_file, file_stat) < 0)
321
  return -1;
322
#endif
323
132
    fd = FcOpen ((char *)cache_file, O_RDONLY | O_BINARY);
324
132
    if (fd < 0)
325
66
  return fd;
326
66
#ifndef _WIN32
327
66
    if (fstat (fd, file_stat) < 0) {
328
0
  close (fd);
329
0
  return -1;
330
0
    }
331
66
#endif
332
66
    return fd;
333
66
}
334
335
/*
336
 * Look for a cache file for the specified dir. Attempt
337
 * to use each one we find, stopping when the callback
338
 * indicates success
339
 */
340
static FcBool
341
FcDirCacheProcess (FcConfig *config, const FcChar8 *dir,
342
                   FcBool (*callback) (FcConfig *config, int fd, struct stat *fd_stat,
343
                                       struct stat *dir_stat, struct timeval *cache_mtime, void *closure),
344
                   void *closure, FcChar8 **cache_file_ret)
345
110
{
346
110
    int            fd = -1;
347
110
    FcChar8        cache_base[CACHEBASE_LEN];
348
110
    FcStrList     *list;
349
110
    FcChar8       *cache_dir, *d;
350
110
    struct stat    file_stat, dir_stat;
351
110
    FcBool         ret = FcFalse;
352
110
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
353
110
    struct timeval latest_mtime = (struct timeval){ 0 };
354
355
110
    if (sysroot)
356
0
  d = FcStrBuildFilename (sysroot, dir, NULL);
357
110
    else
358
110
  d = FcStrdup (dir);
359
110
    if (FcStatChecksum (d, &dir_stat) < 0) {
360
44
  FcStrFree (d);
361
44
  return FcFalse;
362
44
    }
363
66
    FcStrFree (d);
364
365
66
    FcDirCacheBasenameMD5 (config, dir, cache_base);
366
367
66
    list = FcStrListCreate (config->cacheDirs);
368
66
    if (!list)
369
0
  return FcFalse;
370
371
198
    while ((cache_dir = FcStrListNext (list))) {
372
132
  FcChar8 *cache_hashed;
373
132
#ifndef _WIN32
374
132
  FcBool retried = FcFalse;
375
132
#endif
376
377
132
  if (sysroot)
378
0
      cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
379
132
  else
380
132
      cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
381
132
  if (!cache_hashed)
382
0
      break;
383
132
#ifndef _WIN32
384
132
    retry:
385
132
#endif
386
132
  fd = FcDirCacheOpenFile (cache_hashed, &file_stat);
387
132
  if (fd >= 0) {
388
66
      ret = (*callback) (config, fd, &file_stat, &dir_stat, &latest_mtime, closure);
389
66
      close (fd);
390
66
      if (ret) {
391
66
    if (cache_file_ret) {
392
0
        if (*cache_file_ret)
393
0
      FcStrFree (*cache_file_ret);
394
0
        *cache_file_ret = cache_hashed;
395
0
    } else
396
66
        FcStrFree (cache_hashed);
397
66
      } else
398
0
    FcStrFree (cache_hashed);
399
66
  }
400
66
#ifndef _WIN32
401
66
  else if (!retried) {
402
66
      FcChar8 uuid_cache_base[CACHEBASE_LEN];
403
404
66
      retried = FcTrue;
405
66
      FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
406
66
      if (uuid_cache_base[0] != 0) {
407
0
    FcStrFree (cache_hashed);
408
0
    if (sysroot)
409
0
        cache_hashed = FcStrBuildFilename (sysroot, cache_dir, uuid_cache_base, NULL);
410
0
    else
411
0
        cache_hashed = FcStrBuildFilename (cache_dir, uuid_cache_base, NULL);
412
0
    if (!cache_hashed)
413
0
        break;
414
0
    goto retry;
415
0
      } else
416
66
    FcStrFree (cache_hashed);
417
66
  }
418
0
#endif
419
0
  else
420
0
      FcStrFree (cache_hashed);
421
132
    }
422
66
    FcStrListDone (list);
423
424
66
    if (closure)
425
66
  return !!(*((FcCache **)closure) != NULL);
426
0
    return ret;
427
66
}
428
429
66
#define FC_CACHE_MIN_MMAP 1024
430
431
/*
432
 * Skip list element, make sure the 'next' pointer is the last thing
433
 * in the structure, it will be allocated large enough to hold all
434
 * of the necessary pointers
435
 */
436
437
typedef struct _FcCacheSkip FcCacheSkip;
438
439
struct _FcCacheSkip {
440
    FcCache     *cache;
441
    FcRef        ref;
442
    intptr_t     size;
443
    void        *allocated;
444
    dev_t        cache_dev;
445
    ino_t        cache_ino;
446
    time_t       cache_mtime;
447
    long         cache_mtime_nano;
448
    FcCacheSkip *next[1];
449
};
450
451
/*
452
 * The head of the skip list; pointers for every possible level
453
 * in the skip list, plus the largest level in the list
454
 */
455
456
84
#define FC_CACHE_MAX_LEVEL 16
457
458
/* Protected by cache_lock below */
459
static FcCacheSkip *fcCacheChains[FC_CACHE_MAX_LEVEL];
460
static int          fcCacheMaxLevel;
461
462
static FcMutex *cache_lock;
463
464
static void
465
lock_cache (void)
466
8.73M
{
467
8.73M
    FcMutex *lock;
468
8.73M
retry:
469
8.73M
    lock = fc_atomic_ptr_get (&cache_lock);
470
8.73M
    if (!lock) {
471
11
  lock = (FcMutex *)malloc (sizeof (FcMutex));
472
11
  FcMutexInit (lock);
473
11
  if (!fc_atomic_ptr_cmpexch (&cache_lock, NULL, lock)) {
474
0
      FcMutexFinish (lock);
475
0
      free (lock);
476
0
      goto retry;
477
0
  }
478
479
11
  FcMutexLock (lock);
480
  /* Initialize random state */
481
11
  FcRandom();
482
11
  return;
483
11
    }
484
8.73M
    FcMutexLock (lock);
485
8.73M
}
486
487
static void
488
unlock_cache (void)
489
8.73M
{
490
8.73M
    FcMutex *lock;
491
8.73M
    lock = fc_atomic_ptr_get (&cache_lock);
492
8.73M
    FcMutexUnlock (lock);
493
8.73M
}
494
495
static void
496
free_lock (void)
497
0
{
498
0
    FcMutex *lock;
499
0
    lock = fc_atomic_ptr_get (&cache_lock);
500
0
    if (lock && fc_atomic_ptr_cmpexch (&cache_lock, lock, NULL)) {
501
0
  FcMutexFinish (lock);
502
0
  free (lock);
503
0
    }
504
0
}
505
506
/*
507
 * Generate a random level number, distributed
508
 * so that each level is 1/4 as likely as the one before
509
 *
510
 * Note that level numbers run 1 <= level <= MAX_LEVEL
511
 */
512
static int
513
random_level (void)
514
66
{
515
    /* tricky bit -- each bit is '1' 75% of the time */
516
66
    long int bits = FcRandom() | FcRandom();
517
66
    int      level = 0;
518
519
84
    while (++level < FC_CACHE_MAX_LEVEL) {
520
84
  if (bits & 1)
521
66
      break;
522
18
  bits >>= 1;
523
18
    }
524
66
    return level;
525
66
}
526
527
/*
528
 * Insert cache into the list
529
 */
530
static FcBool
531
FcCacheInsert (FcCache *cache, struct stat *cache_stat)
532
66
{
533
66
    FcCacheSkip **update[FC_CACHE_MAX_LEVEL];
534
66
    FcCacheSkip  *s, **next;
535
66
    int           i, level;
536
537
66
    lock_cache();
538
539
    /*
540
     * Find links along each chain
541
     */
542
66
    next = fcCacheChains;
543
66
    for (i = fcCacheMaxLevel; --i >= 0;) {
544
0
  for (; (s = next[i]); next = s->next)
545
0
      if (s->cache > cache)
546
0
    break;
547
0
  update[i] = &next[i];
548
0
    }
549
550
    /*
551
     * Create new list element
552
     */
553
66
    level = random_level();
554
66
    if (level > fcCacheMaxLevel) {
555
66
  level = fcCacheMaxLevel + 1;
556
66
  update[fcCacheMaxLevel] = &fcCacheChains[fcCacheMaxLevel];
557
66
  fcCacheMaxLevel = level;
558
66
    }
559
560
66
    s = malloc (sizeof (FcCacheSkip) + (level - 1) * sizeof (FcCacheSkip *));
561
66
    if (!s) {
562
0
  unlock_cache();
563
0
  return FcFalse;
564
0
    }
565
566
66
    s->cache = cache;
567
66
    s->size = cache->size;
568
66
    s->allocated = NULL;
569
66
    FcRefInit (&s->ref, 1);
570
66
    if (cache_stat) {
571
66
  s->cache_dev = cache_stat->st_dev;
572
66
  s->cache_ino = cache_stat->st_ino;
573
66
  s->cache_mtime = cache_stat->st_mtime;
574
66
#ifdef HAVE_STRUCT_STAT_ST_MTIM
575
66
  s->cache_mtime_nano = cache_stat->st_mtim.tv_nsec;
576
#else
577
  s->cache_mtime_nano = 0;
578
#endif
579
66
    } else {
580
0
  s->cache_dev = 0;
581
0
  s->cache_ino = 0;
582
0
  s->cache_mtime = 0;
583
0
  s->cache_mtime_nano = 0;
584
0
    }
585
586
    /*
587
     * Insert into all fcCacheChains
588
     */
589
132
    for (i = 0; i < level; i++) {
590
66
  s->next[i] = *update[i];
591
66
  *update[i] = s;
592
66
    }
593
594
66
    unlock_cache();
595
66
    return FcTrue;
596
66
}
597
598
static FcCacheSkip *
599
FcCacheFindByAddrUnlocked (void *object)
600
8.73M
{
601
8.73M
    int           i;
602
8.73M
    FcCacheSkip **next = fcCacheChains;
603
8.73M
    FcCacheSkip  *s;
604
605
8.73M
    if (!object)
606
0
  return NULL;
607
608
    /*
609
     * Walk chain pointers one level at a time
610
     */
611
17.4M
    for (i = fcCacheMaxLevel; --i >= 0;)
612
8.73M
  while (next[i] && (char *)object >= ((char *)next[i]->cache + next[i]->size))
613
0
      next = next[i]->next;
614
    /*
615
     * Here we are
616
     */
617
8.73M
    s = next[0];
618
8.73M
    if (s && (char *)object < ((char *)s->cache + s->size))
619
8.73M
  return s;
620
0
    return NULL;
621
8.73M
}
622
623
static FcCacheSkip *
624
FcCacheFindByAddr (void *object)
625
4.36M
{
626
4.36M
    FcCacheSkip *ret;
627
4.36M
    lock_cache();
628
4.36M
    ret = FcCacheFindByAddrUnlocked (object);
629
4.36M
    unlock_cache();
630
4.36M
    return ret;
631
4.36M
}
632
633
static void
634
FcCacheRemoveUnlocked (FcCache *cache)
635
55
{
636
55
    FcCacheSkip **update[FC_CACHE_MAX_LEVEL];
637
55
    FcCacheSkip  *s, **next;
638
55
    int           i;
639
55
    void         *allocated;
640
641
    /*
642
     * Find links along each chain
643
     */
644
55
    next = fcCacheChains;
645
110
    for (i = fcCacheMaxLevel; --i >= 0;) {
646
55
  for (; (s = next[i]); next = s->next)
647
55
      if (s->cache >= cache)
648
55
    break;
649
55
  update[i] = &next[i];
650
55
    }
651
55
    s = next[0];
652
110
    for (i = 0; i < fcCacheMaxLevel && *update[i] == s; i++)
653
55
  *update[i] = s->next[i];
654
110
    while (fcCacheMaxLevel > 0 && fcCacheChains[fcCacheMaxLevel - 1] == NULL)
655
55
  fcCacheMaxLevel--;
656
657
55
    if (s) {
658
55
  allocated = s->allocated;
659
55
  while (allocated) {
660
      /* First element in allocated chunk is the free list */
661
0
      next = *(void **)allocated;
662
0
      free (allocated);
663
0
      allocated = next;
664
0
  }
665
55
  free (s);
666
55
    }
667
55
}
668
669
static FcCache *
670
FcCacheFindByStat (struct stat *cache_stat)
671
66
{
672
66
    FcCacheSkip *s;
673
674
66
    lock_cache();
675
66
    for (s = fcCacheChains[0]; s; s = s->next[0])
676
0
  if (s->cache_dev == cache_stat->st_dev &&
677
0
      s->cache_ino == cache_stat->st_ino &&
678
0
      s->cache_mtime == cache_stat->st_mtime) {
679
0
#ifdef HAVE_STRUCT_STAT_ST_MTIM
680
0
      if (s->cache_mtime_nano != cache_stat->st_mtim.tv_nsec)
681
0
    continue;
682
0
#endif
683
0
      FcRefInc (&s->ref);
684
0
      unlock_cache();
685
0
      return s->cache;
686
0
  }
687
66
    unlock_cache();
688
66
    return NULL;
689
66
}
690
691
static void
692
FcDirCacheDisposeUnlocked (FcCache *cache)
693
55
{
694
55
    FcCacheRemoveUnlocked (cache);
695
696
55
    switch (cache->magic) {
697
44
    case FC_CACHE_MAGIC_ALLOC:
698
44
  free (cache);
699
44
  break;
700
11
    case FC_CACHE_MAGIC_MMAP:
701
11
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
702
11
  munmap (cache, cache->size);
703
#elif defined(_WIN32)
704
  UnmapViewOfFile (cache);
705
#endif
706
11
  break;
707
55
    }
708
55
}
709
710
void
711
FcCacheObjectReference (void *object)
712
4.36M
{
713
4.36M
    FcCacheSkip *skip = FcCacheFindByAddr (object);
714
715
4.36M
    if (skip)
716
4.36M
  FcRefInc (&skip->ref);
717
4.36M
}
718
719
void
720
FcCacheObjectDereference (void *object)
721
4.36M
{
722
4.36M
    FcCacheSkip *skip;
723
724
4.36M
    lock_cache();
725
4.36M
    skip = FcCacheFindByAddrUnlocked (object);
726
4.36M
    if (skip) {
727
4.36M
  if (FcRefDec (&skip->ref) == 1)
728
55
      FcDirCacheDisposeUnlocked (skip->cache);
729
4.36M
    }
730
4.36M
    unlock_cache();
731
4.36M
}
732
733
void *
734
FcCacheAllocate (FcCache *cache, size_t len)
735
0
{
736
0
    FcCacheSkip *skip;
737
0
    void        *allocated = NULL;
738
739
0
    lock_cache();
740
0
    skip = FcCacheFindByAddrUnlocked (cache);
741
0
    if (skip) {
742
0
  void *chunk = malloc (sizeof (void *) + len);
743
0
  if (chunk) {
744
      /* First element in allocated chunk is the free list */
745
0
      *(void **)chunk = skip->allocated;
746
0
      skip->allocated = chunk;
747
      /* Return the rest */
748
0
      allocated = ((FcChar8 *)chunk) + sizeof (void *);
749
0
  }
750
0
    }
751
0
    unlock_cache();
752
0
    return allocated;
753
0
}
754
755
void
756
FcCacheFini (void)
757
0
{
758
0
    int    i;
759
0
    FcBool res = FcTrue;
760
761
0
    for (i = 0; i < FC_CACHE_MAX_LEVEL; i++) {
762
0
  if (fcCacheChains[i] != NULL) {
763
0
      res = FcFalse;
764
0
      if (FcDebug() & FC_DBG_CACHE) {
765
0
    FcCacheSkip *s = fcCacheChains[i];
766
0
    fprintf (stderr, "Fontconfig error: not freed %p (dir: %s, refcount %" FC_ATOMIC_INT_FORMAT ")\n", s->cache, FcCacheDir (s->cache), s->ref.count);
767
0
      }
768
0
  }
769
0
    }
770
0
    if (res)
771
0
  free_lock();
772
0
}
773
774
static FcBool
775
FcCacheIsNewVersion (FcConfig *config, FcCache *cache)
776
0
{
777
0
    int64_t version = (FC_VERSION_MAJOR << 24) +
778
0
                      (FC_VERSION_MINOR << 12) +
779
0
                      FC_VERSION_MICRO;
780
0
    static FcBool warn = FcFalse;
781
0
    static FcBool flag = FcFalse, is_initialized = FcFalse;
782
783
    /* To revert the behavior for some emergency. This will be removed in the future */
784
0
    if (!is_initialized) {
785
0
  const char *env = getenv ("FONTCONFIG_NO_CHECK_CACHE_VERSION");
786
787
0
  is_initialized = FcTrue;
788
0
  if (env)
789
0
      FcNameBool ((const FcChar8 *)env, &flag);
790
0
    }
791
0
    if (flag)
792
0
  return !flag;
793
0
    if (cache->fc_version > version && !warn) {
794
0
  warn = FcTrue;
795
0
  fprintf (stderr, "Fontconfig warning: We will not regenerate the cache because some cache files were generated by a newer version (0x%" PRIx64 ") of Fontconfig. Please regenerate the cache with the latest version of Fontconfig to avoid any unexpected behavior. (current version: 0x%" PRIx64 ")\n", cache->fc_version, version);
796
0
    }
797
798
0
    return cache->fc_version > version;
799
0
}
800
801
static FcBool
802
FcCacheTimeValid (FcConfig *config, FcCache *cache, struct stat *dir_stat)
803
66
{
804
66
    struct stat dir_static;
805
66
    FcBool      fnano = FcTrue;
806
807
66
    if (!dir_stat) {
808
0
  const FcChar8 *sysroot = FcConfigGetSysRoot (config);
809
0
  FcChar8       *d;
810
811
0
  if (sysroot)
812
0
      d = FcStrBuildFilename (sysroot, FcCacheDir (cache), NULL);
813
0
  else
814
0
      d = FcStrdup (FcCacheDir (cache));
815
0
  if (FcStatChecksum (d, &dir_static) < 0) {
816
0
      FcStrFree (d);
817
0
      return FcFalse;
818
0
  }
819
0
  FcStrFree (d);
820
0
  dir_stat = &dir_static;
821
0
    }
822
66
#ifdef HAVE_STRUCT_STAT_ST_MTIM
823
66
    fnano = (cache->checksum_nano == dir_stat->st_mtim.tv_nsec);
824
66
    if (FcDebug() & FC_DBG_CACHE)
825
0
  printf ("FcCacheTimeValid dir \"%s\" cache checksum %d.%ld dir checksum %d.%ld\n",
826
0
          FcCacheDir (cache), cache->checksum, (long)cache->checksum_nano, (int)dir_stat->st_mtime, dir_stat->st_mtim.tv_nsec);
827
#else
828
    if (FcDebug() & FC_DBG_CACHE)
829
  printf ("FcCacheTimeValid dir \"%s\" cache checksum %d dir checksum %d\n",
830
          FcCacheDir (cache), cache->checksum, (int)dir_stat->st_mtime);
831
#endif
832
833
66
    return dir_stat->st_mtime == 0 || (cache->checksum == (int)dir_stat->st_mtime && fnano);
834
66
}
835
836
static FcBool
837
FcCacheOffsetsValid (FcCache *cache)
838
66
{
839
66
    char      *base = (char *)cache;
840
66
    char      *end = base + cache->size;
841
66
    intptr_t  *dirs;
842
66
    FcFontSet *fs;
843
66
    int        i, j;
844
845
66
    if (cache->dir < 0 || cache->dir > cache->size - sizeof (intptr_t) ||
846
66
        memchr (base + cache->dir, '\0', cache->size - cache->dir) == NULL)
847
0
  return FcFalse;
848
849
66
    if (cache->dirs < 0 || cache->dirs >= cache->size ||
850
66
        cache->dirs_count < 0 ||
851
66
        cache->dirs_count > (cache->size - cache->dirs) / sizeof (intptr_t))
852
0
  return FcFalse;
853
854
66
    dirs = FcCacheDirs (cache);
855
66
    if (dirs) {
856
110
  for (i = 0; i < cache->dirs_count; i++) {
857
44
      FcChar8 *dir;
858
859
44
      if (dirs[i] < 0 ||
860
44
          dirs[i] > end - (char *)dirs - sizeof (intptr_t))
861
0
    return FcFalse;
862
863
44
      dir = FcOffsetToPtr (dirs, dirs[i], FcChar8);
864
44
      if (memchr (dir, '\0', end - (char *)dir) == NULL)
865
0
    return FcFalse;
866
44
  }
867
66
    }
868
869
66
    if (cache->set < 0 || cache->set > cache->size - sizeof (FcFontSet))
870
0
  return FcFalse;
871
872
66
    fs = FcCacheSet (cache);
873
66
    if (fs) {
874
66
  if (fs->nfont > (end - (char *)fs) / sizeof (FcPattern))
875
0
      return FcFalse;
876
877
66
  if (!FcIsEncodedOffset (fs->fonts))
878
0
      return FcFalse;
879
880
550
  for (i = 0; i < fs->nfont; i++) {
881
484
      FcPattern       *font = FcFontSetFont (fs, i);
882
484
      FcPatternElt    *e;
883
484
      FcValueListPtr   l;
884
484
      char            *last_offset;
885
484
      const FcLangSet *ls;
886
484
      const FcRange   *r;
887
484
      const FcChar8   *s;
888
889
484
      if ((char *)font < base ||
890
484
          (char *)font > end - sizeof (FcFontSet) ||
891
484
          font->elts_offset < 0 ||
892
484
          font->elts_offset > end - (char *)font ||
893
484
          font->num > (end - (char *)font - font->elts_offset) / sizeof (FcPatternElt) ||
894
484
          !FcRefIsConst (&font->ref))
895
0
    return FcFalse;
896
897
484
      e = FcPatternElts (font);
898
484
      if (e->values != 0 && !FcIsEncodedOffset (e->values))
899
0
    return FcFalse;
900
901
14.6k
      for (j = 0; j < font->num; j++) {
902
14.1k
    last_offset = (char *)font + font->elts_offset;
903
28.9k
    for (l = FcPatternEltValues (&e[j]); l; l = FcValueListNext (l)) {
904
14.8k
        if ((char *)l < last_offset || (char *)l > end - sizeof (*l) ||
905
14.8k
            (l->next != NULL && !FcIsEncodedOffset (l->next)))
906
0
      return FcFalse;
907
14.8k
        switch (l->value.type) {
908
0
        case FcTypeVoid:
909
2.50k
        case FcTypeInteger:
910
3.47k
        case FcTypeDouble:
911
7.34k
        case FcTypeBool:
912
7.34k
        case FcTypeMatrix:
913
7.34k
        case FcTypeFTFace:
914
7.34k
      break; /* nop */
915
6.55k
        case FcTypeString:
916
6.55k
      s = FcValueString (&l->value);
917
6.55k
      if ((intptr_t)s < (intptr_t)&e[j] ||
918
6.55k
          (intptr_t)&l->value > (intptr_t)end - sizeof (*l) ||
919
6.55k
          !FcIsEncodedOffset (l->value.u.s)) {
920
0
          if (FcDebug() & FC_DBG_CACHE) {
921
0
        fprintf (stderr, "Fontconfig warning: invalid cache: broken string pointer\n");
922
0
          }
923
0
          return FcFalse;
924
0
      }
925
6.55k
      break;
926
6.55k
        case FcTypeCharSet:
927
      /* FcCharSet might be re-used by FcCharSetFindFrozen
928
       * which would means a pointer might be out of Elts
929
       */
930
484
      if ((intptr_t)&l->value > (intptr_t)end - sizeof (*l) ||
931
484
          !FcIsEncodedOffset (l->value.u.c)) {
932
0
          if (FcDebug() & FC_DBG_CACHE) {
933
0
        fprintf (stderr, "Fontconfig warning: invalid cache: broken charset\n");
934
0
          }
935
0
          return FcFalse;
936
0
      }
937
484
      break;
938
484
        case FcTypeLangSet:
939
484
      ls = FcValueLangSet (&l->value);
940
484
      if ((intptr_t)ls < (intptr_t)&e[j] ||
941
484
          (intptr_t)&l->value > (intptr_t)end - sizeof (*l) ||
942
484
          !FcIsEncodedOffset (l->value.u.l)) {
943
0
          if (FcDebug() & FC_DBG_CACHE) {
944
0
        fprintf (stderr, "Fontconfig warning: invalid cache: broken langset\n");
945
0
          }
946
0
          return FcFalse;
947
0
      }
948
      /* ls->extra isn't serialized. it must be null */
949
484
      if (ls->extra != NULL) {
950
0
          if (FcDebug() & FC_DBG_CACHE) {
951
0
        fprintf (stderr, "Fontconfig warning: invalid cache: broken langset\n");
952
0
          }
953
0
          return FcFalse;
954
0
      }
955
484
      break;
956
484
        case FcTypeRange:
957
0
      r = FcValueRange (&l->value);
958
0
      if ((intptr_t)r < (intptr_t)&e[j] ||
959
0
          (intptr_t)&l->value > (intptr_t)end - sizeof (*l) ||
960
0
          !FcIsEncodedOffset (l->value.u.r)) {
961
0
          if (FcDebug() & FC_DBG_CACHE) {
962
0
        fprintf (stderr, "Fontconfig warning: invalid cache: broken range\n");
963
0
          }
964
0
          return FcFalse;
965
0
      }
966
0
      break;
967
0
        default:
968
      /* just ignore unknown object type for upper-compatible in the future */
969
0
      if (FcDebug() & FC_DBG_CACHEV) {
970
0
          fprintf (stderr, "Fontconfig warning: invalid cache: unknown object type in pattern: %d\n", l->value.type);
971
0
      }
972
0
      break;
973
14.8k
        }
974
14.8k
        last_offset = (char *)l + 1;
975
14.8k
    }
976
14.1k
      }
977
484
  }
978
66
    }
979
980
66
    return FcTrue;
981
66
}
982
983
/*
984
 * Map a cache file into memory
985
 */
986
static FcCache *
987
FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat)
988
66
{
989
66
    FcCache *cache;
990
66
    FcBool   allocated = FcFalse;
991
992
66
    if (fd_stat->st_size > INTPTR_MAX ||
993
66
        fd_stat->st_size < (int)sizeof (FcCache))
994
0
  return NULL;
995
66
    cache = FcCacheFindByStat (fd_stat);
996
66
    if (cache) {
997
0
  if (FcCacheTimeValid (config, cache, dir_stat))
998
0
      return cache;
999
0
  else if (FcCacheIsNewVersion (config, cache)) {
1000
      /* Re-use if cache was generated by newer version of fontconfig
1001
       * Do not trigger regenerating
1002
       */
1003
0
      return cache;
1004
0
  }
1005
0
  FcDirCacheUnload (cache);
1006
0
  cache = NULL;
1007
0
    }
1008
1009
    /*
1010
     * Large cache files are mmap'ed, smaller cache files are read. This
1011
     * balances the system cost of mmap against per-process memory usage.
1012
     */
1013
66
    if (FcCacheIsMmapSafe (fd) && fd_stat->st_size >= FC_CACHE_MIN_MMAP) {
1014
22
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
1015
22
  cache = mmap (0, fd_stat->st_size, PROT_READ, MAP_SHARED, fd, 0);
1016
22
#  if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
1017
22
  posix_fadvise (fd, 0, fd_stat->st_size, POSIX_FADV_WILLNEED);
1018
22
#  endif
1019
22
  if (cache == MAP_FAILED)
1020
0
      cache = NULL;
1021
#elif defined(_WIN32)
1022
  {
1023
      HANDLE hFileMap;
1024
1025
      cache = NULL;
1026
      hFileMap = CreateFileMapping ((HANDLE)_get_osfhandle (fd), NULL,
1027
                                    PAGE_READONLY, 0, 0, NULL);
1028
      if (hFileMap != NULL) {
1029
    cache = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0,
1030
                           fd_stat->st_size);
1031
    CloseHandle (hFileMap);
1032
      }
1033
  }
1034
#endif
1035
22
    }
1036
66
    if (!cache) {
1037
44
  cache = malloc (fd_stat->st_size);
1038
44
  if (!cache)
1039
0
      return NULL;
1040
1041
44
  if (read (fd, cache, fd_stat->st_size) != fd_stat->st_size) {
1042
0
      free (cache);
1043
0
      return NULL;
1044
0
  }
1045
44
  allocated = FcTrue;
1046
44
    }
1047
66
    if (cache->magic != FC_CACHE_MAGIC_MMAP ||
1048
66
        cache->version < FC_CACHE_VERSION_NUMBER ||
1049
66
        cache->size != (intptr_t)fd_stat->st_size ||
1050
66
        !FcCacheOffsetsValid (cache) ||
1051
66
        (!FcCacheTimeValid (config, cache, dir_stat) &&
1052
0
         !FcCacheIsNewVersion (config, cache)) ||
1053
66
        !FcCacheInsert (cache, fd_stat)) {
1054
0
  if (allocated)
1055
0
      free (cache);
1056
0
  else {
1057
0
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
1058
0
      munmap (cache, fd_stat->st_size);
1059
#elif defined(_WIN32)
1060
      UnmapViewOfFile (cache);
1061
#endif
1062
0
  }
1063
0
  return NULL;
1064
0
    }
1065
1066
    /* Mark allocated caches so they're freed rather than unmapped */
1067
66
    if (allocated)
1068
44
  cache->magic = FC_CACHE_MAGIC_ALLOC;
1069
1070
66
    return cache;
1071
66
}
1072
1073
void
1074
FcDirCacheReference (FcCache *cache, int nref)
1075
66
{
1076
66
    FcCacheSkip *skip = FcCacheFindByAddr (cache);
1077
1078
66
    if (skip)
1079
66
  FcRefAdd (&skip->ref, nref);
1080
66
}
1081
1082
void
1083
FcDirCacheUnload (FcCache *cache)
1084
66
{
1085
66
    FcCacheObjectDereference (cache);
1086
66
}
1087
1088
static FcBool
1089
FcDirCacheMapHelper (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat, struct timeval *latest_cache_mtime, void *closure)
1090
66
{
1091
66
    FcCache       *cache = FcDirCacheMapFd (config, fd, fd_stat, dir_stat);
1092
66
    struct timeval cache_mtime, zero_mtime = { 0, 0 }, dir_mtime;
1093
1094
66
    if (!cache)
1095
0
  return FcFalse;
1096
66
    cache_mtime.tv_sec = fd_stat->st_mtime;
1097
66
    dir_mtime.tv_sec = dir_stat->st_mtime;
1098
66
#ifdef HAVE_STRUCT_STAT_ST_MTIM
1099
66
    cache_mtime.tv_usec = fd_stat->st_mtim.tv_nsec / 1000;
1100
66
    dir_mtime.tv_usec = dir_stat->st_mtim.tv_nsec / 1000;
1101
#else
1102
    cache_mtime.tv_usec = 0;
1103
    dir_mtime.tv_usec = 0;
1104
#endif
1105
    /* special take care of OSTree */
1106
66
    if (!timercmp (&zero_mtime, &dir_mtime, !=)) {
1107
0
  if (!timercmp (&zero_mtime, &cache_mtime, !=)) {
1108
0
      if (*((FcCache **)closure))
1109
0
    FcDirCacheUnload (*((FcCache **)closure));
1110
0
  } else if (*((FcCache **)closure) && !timercmp (&zero_mtime, latest_cache_mtime, !=)) {
1111
0
      FcDirCacheUnload (cache);
1112
0
      return FcFalse;
1113
0
  } else if (timercmp (latest_cache_mtime, &cache_mtime, <)) {
1114
0
      if (*((FcCache **)closure))
1115
0
    FcDirCacheUnload (*((FcCache **)closure));
1116
0
  }
1117
66
    } else if (timercmp (latest_cache_mtime, &cache_mtime, <)) {
1118
66
  if (*((FcCache **)closure))
1119
0
      FcDirCacheUnload (*((FcCache **)closure));
1120
66
    } else {
1121
0
  FcDirCacheUnload (cache);
1122
0
  return FcFalse;
1123
0
    }
1124
66
    latest_cache_mtime->tv_sec = cache_mtime.tv_sec;
1125
66
    latest_cache_mtime->tv_usec = cache_mtime.tv_usec;
1126
66
    *((FcCache **)closure) = cache;
1127
66
    return FcTrue;
1128
66
}
1129
1130
FcCache *
1131
FcDirCacheLoad (const FcChar8 *dir, FcConfig *config, FcChar8 **cache_file)
1132
110
{
1133
110
    FcCache *cache = NULL;
1134
1135
110
    config = FcConfigReference (config);
1136
110
    if (!config)
1137
0
  return NULL;
1138
110
    if (!FcDirCacheProcess (config, dir,
1139
110
                            FcDirCacheMapHelper,
1140
110
                            &cache, cache_file))
1141
44
  cache = NULL;
1142
1143
110
    FcConfigDestroy (config);
1144
1145
110
    return cache;
1146
110
}
1147
1148
FcCache *
1149
FcDirCacheLoadFile (const FcChar8 *cache_file, struct stat *file_stat)
1150
0
{
1151
0
    int         fd;
1152
0
    FcCache    *cache = NULL;
1153
0
    struct stat my_file_stat;
1154
0
    FcConfig   *config;
1155
1156
0
    if (!file_stat)
1157
0
  file_stat = &my_file_stat;
1158
0
    config = FcConfigReference (NULL);
1159
0
    if (!config)
1160
0
  return NULL;
1161
0
    fd = FcDirCacheOpenFile (cache_file, file_stat);
1162
0
    if (fd >= 0) {
1163
0
  cache = FcDirCacheMapFd (config, fd, file_stat, NULL);
1164
0
  close (fd);
1165
0
    }
1166
0
    FcConfigDestroy (config);
1167
1168
0
    return cache;
1169
0
}
1170
1171
static int
1172
FcDirChecksum (struct stat *statb)
1173
0
{
1174
0
    int                ret = (int)statb->st_mtime;
1175
0
    char              *endptr;
1176
0
    char              *source_date_epoch;
1177
0
    unsigned long long epoch;
1178
1179
0
    source_date_epoch = getenv ("SOURCE_DATE_EPOCH");
1180
0
    if (source_date_epoch) {
1181
0
  errno = 0;
1182
0
  epoch = strtoull (source_date_epoch, &endptr, 10);
1183
1184
0
  if (endptr == source_date_epoch)
1185
0
      fprintf (stderr,
1186
0
               "Fontconfig: SOURCE_DATE_EPOCH invalid\n");
1187
0
  else if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0)) || (errno != 0 && epoch == 0))
1188
0
      fprintf (stderr,
1189
0
               "Fontconfig: SOURCE_DATE_EPOCH: strtoull: %s: %" FC_UINT64_FORMAT "\n",
1190
0
               strerror (errno), epoch);
1191
0
  else if (*endptr != '\0')
1192
0
      fprintf (stderr,
1193
0
               "Fontconfig: SOURCE_DATE_EPOCH has trailing garbage\n");
1194
0
  else if (epoch > ULONG_MAX)
1195
0
      fprintf (stderr,
1196
0
               "Fontconfig: SOURCE_DATE_EPOCH must be <= %lu but saw: %" FC_UINT64_FORMAT "\n",
1197
0
               ULONG_MAX, epoch);
1198
0
  else if (epoch < ret)
1199
      /* Only override if directory is newer */
1200
0
      ret = (int)epoch;
1201
0
    }
1202
1203
0
    return ret;
1204
0
}
1205
1206
static int64_t
1207
FcDirChecksumNano (struct stat *statb)
1208
0
{
1209
0
#ifdef HAVE_STRUCT_STAT_ST_MTIM
1210
    /* No nanosecond component to parse */
1211
0
    if (getenv ("SOURCE_DATE_EPOCH"))
1212
0
  return 0;
1213
0
    return statb->st_mtim.tv_nsec;
1214
#else
1215
    return 0;
1216
#endif
1217
0
}
1218
1219
/*
1220
 * Validate a cache file by reading the header and checking
1221
 * the magic number and the size field
1222
 */
1223
static FcBool
1224
FcDirCacheValidateHelper (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat, struct timeval *latest_cache_mtime, void *closure FC_UNUSED)
1225
0
{
1226
0
    FcBool  ret = FcTrue;
1227
0
    FcCache c;
1228
1229
0
    if (read (fd, &c, sizeof (FcCache)) != sizeof (FcCache))
1230
0
  ret = FcFalse;
1231
0
    else if (c.magic != FC_CACHE_MAGIC_MMAP)
1232
0
  ret = FcFalse;
1233
0
    else if (c.version < FC_CACHE_VERSION_NUMBER)
1234
0
  ret = FcFalse;
1235
0
    else if (fd_stat->st_size != c.size)
1236
0
  ret = FcFalse;
1237
0
    else if (c.checksum != FcDirChecksum (dir_stat))
1238
0
  ret = FcFalse;
1239
0
#ifdef HAVE_STRUCT_STAT_ST_MTIM
1240
0
    else if (c.checksum_nano != FcDirChecksumNano (dir_stat))
1241
0
  ret = FcFalse;
1242
0
#endif
1243
0
    return ret;
1244
0
}
1245
1246
static FcBool
1247
FcDirCacheValidConfig (const FcChar8 *dir, FcConfig *config)
1248
0
{
1249
0
    return FcDirCacheProcess (config, dir,
1250
0
                              FcDirCacheValidateHelper,
1251
0
                              NULL, NULL);
1252
0
}
1253
1254
FcBool
1255
FcDirCacheValid (const FcChar8 *dir)
1256
0
{
1257
0
    FcConfig *config;
1258
0
    FcBool    ret;
1259
1260
0
    config = FcConfigReference (NULL);
1261
0
    if (!config)
1262
0
  return FcFalse;
1263
1264
0
    ret = FcDirCacheValidConfig (dir, config);
1265
0
    FcConfigDestroy (config);
1266
1267
0
    return ret;
1268
0
}
1269
1270
/*
1271
 * Build a cache structure from the given contents
1272
 */
1273
FcCache *
1274
FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcStrSet *dirs)
1275
0
{
1276
0
    FcSerialize *serialize = FcSerializeCreate();
1277
0
    FcCache     *cache;
1278
0
    int          i;
1279
0
    FcChar8     *dir_serialize;
1280
0
    intptr_t    *dirs_serialize;
1281
0
    FcFontSet   *set_serialize;
1282
1283
0
    if (!serialize)
1284
0
  return NULL;
1285
    /*
1286
     * Space for cache structure
1287
     */
1288
0
    FcSerializeReserve (serialize, sizeof (FcCache));
1289
    /*
1290
     * Directory name
1291
     */
1292
0
    if (!FcStrSerializeAlloc (serialize, dir))
1293
0
  goto bail1;
1294
    /*
1295
     * Subdirs
1296
     */
1297
0
    FcSerializeAlloc (serialize, dirs, dirs->num * sizeof (FcChar8 *));
1298
0
    for (i = 0; i < dirs->num; i++)
1299
0
  if (!FcStrSerializeAlloc (serialize, dirs->strs[i]))
1300
0
      goto bail1;
1301
1302
    /*
1303
     * Patterns
1304
     */
1305
0
    if (!FcFontSetSerializeAlloc (serialize, set))
1306
0
  goto bail1;
1307
1308
    /* Serialize layout complete. Now allocate space and fill it */
1309
0
    cache = malloc (serialize->size);
1310
0
    if (!cache)
1311
0
  goto bail1;
1312
    /* shut up valgrind */
1313
0
    memset (cache, 0, serialize->size);
1314
1315
0
    serialize->linear = cache;
1316
1317
0
    cache->magic = FC_CACHE_MAGIC_ALLOC;
1318
0
    cache->version = FC_CACHE_VERSION_NUMBER;
1319
0
    cache->size = serialize->size;
1320
0
    cache->checksum = FcDirChecksum (dir_stat);
1321
0
    cache->checksum_nano = FcDirChecksumNano (dir_stat);
1322
0
    cache->fc_version = (FC_VERSION_MAJOR << 24) +
1323
0
                 (FC_VERSION_MINOR << 12) +
1324
0
                 FC_VERSION_MICRO;
1325
1326
    /*
1327
     * Serialize directory name
1328
     */
1329
0
    dir_serialize = FcStrSerialize (serialize, dir);
1330
0
    if (!dir_serialize)
1331
0
  goto bail2;
1332
0
    cache->dir = FcPtrToOffset (cache, dir_serialize);
1333
1334
    /*
1335
     * Serialize sub dirs
1336
     */
1337
0
    dirs_serialize = FcSerializePtr (serialize, dirs);
1338
0
    if (!dirs_serialize)
1339
0
  goto bail2;
1340
0
    cache->dirs = FcPtrToOffset (cache, dirs_serialize);
1341
0
    cache->dirs_count = dirs->num;
1342
0
    for (i = 0; i < dirs->num; i++) {
1343
0
  FcChar8 *d_serialize = FcStrSerialize (serialize, dirs->strs[i]);
1344
0
  if (!d_serialize)
1345
0
      goto bail2;
1346
0
  dirs_serialize[i] = FcPtrToOffset (dirs_serialize, d_serialize);
1347
0
    }
1348
1349
    /*
1350
     * Serialize font set
1351
     */
1352
0
    set_serialize = FcFontSetSerialize (serialize, set);
1353
0
    if (!set_serialize)
1354
0
  goto bail2;
1355
0
    cache->set = FcPtrToOffset (cache, set_serialize);
1356
1357
0
    FcSerializeDestroy (serialize);
1358
1359
0
    FcCacheInsert (cache, NULL);
1360
1361
0
    return cache;
1362
1363
0
bail2:
1364
0
    free (cache);
1365
0
bail1:
1366
0
    FcSerializeDestroy (serialize);
1367
0
    return NULL;
1368
0
}
1369
1370
FcCache *
1371
FcDirCacheRebuild (FcCache *cache, struct stat *dir_stat, FcStrSet *dirs)
1372
0
{
1373
0
    FcCache       *newp;
1374
0
    FcFontSet     *set = FcFontSetDeserialize (FcCacheSet (cache));
1375
0
    const FcChar8 *dir = FcCacheDir (cache);
1376
1377
0
    newp = FcDirCacheBuild (set, dir, dir_stat, dirs);
1378
0
    FcFontSetDestroy (set);
1379
1380
0
    return newp;
1381
0
}
1382
1383
/* write serialized state to the cache file */
1384
FcBool
1385
FcDirCacheWrite (FcCache *cache, FcConfig *config)
1386
0
{
1387
0
    FcChar8       *dir = FcCacheDir (cache);
1388
0
    FcChar8        cache_base[CACHEBASE_LEN];
1389
0
    FcChar8       *cache_hashed;
1390
0
    int            fd;
1391
0
    FcAtomic      *atomic;
1392
0
    FcStrList     *list;
1393
0
    FcChar8       *cache_dir = NULL;
1394
0
    FcChar8       *test_dir, *d = NULL;
1395
0
    FcCacheSkip   *skip;
1396
0
    struct stat    cache_stat;
1397
0
    unsigned int   magic;
1398
0
    int            written;
1399
0
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
1400
0
    FcStrSet      *cpath;
1401
1402
    /*
1403
     * Write it to the first directory in the list which is writable
1404
     */
1405
1406
0
    cpath = FcStrSetCreateEx (FCSS_GROW_BY_64);
1407
0
    if (!cpath)
1408
0
  return FcFalse;
1409
0
    list = FcStrListCreate (config->cacheDirs);
1410
0
    if (!list) {
1411
0
  FcStrSetDestroy (cpath);
1412
0
  return FcFalse;
1413
0
    }
1414
0
    while ((test_dir = FcStrListNext (list))) {
1415
0
  if (d)
1416
0
      FcStrFree (d);
1417
0
  if (sysroot)
1418
0
      d = FcStrBuildFilename (sysroot, test_dir, NULL);
1419
0
  else
1420
0
      d = FcStrCopyFilename (test_dir);
1421
1422
0
  if (access ((char *)d, W_OK) == 0) {
1423
0
      cache_dir = FcStrCopyFilename (d);
1424
0
      break;
1425
0
  } else {
1426
      /*
1427
       * If the directory doesn't exist, try to create it
1428
       */
1429
0
      if (access ((char *)d, F_OK) == -1) {
1430
0
    if (FcMakeDirectory (d)) {
1431
0
        cache_dir = FcStrCopyFilename (d);
1432
        /* Create CACHEDIR.TAG */
1433
0
        FcDirCacheCreateTagFile (d);
1434
0
        break;
1435
0
    }
1436
0
      }
1437
      /*
1438
       * Otherwise, try making it writable
1439
       */
1440
0
      else if (chmod ((char *)d, 0755) == 0) {
1441
0
    cache_dir = FcStrCopyFilename (d);
1442
    /* Try to create CACHEDIR.TAG too */
1443
0
    FcDirCacheCreateTagFile (d);
1444
0
    break;
1445
0
      }
1446
      /* Record a path that was supposed to be a cache directory */
1447
0
      FcStrSetAdd (cpath, d);
1448
0
  }
1449
0
    }
1450
0
    if (!test_dir) {
1451
0
  FcStrList *l;
1452
0
  FcChar8   *s;
1453
1454
0
  l = FcStrListCreate (cpath);
1455
0
  fprintf (stderr, "\nFontconfig error: No writable cache directories\n");
1456
0
  while ((s = FcStrListNext (l))) {
1457
0
      fprintf (stderr, "\t%s\n", s);
1458
0
  }
1459
0
  FcStrListDone (l);
1460
0
    }
1461
0
    if (d)
1462
0
  FcStrFree (d);
1463
0
    FcStrSetDestroy (cpath);
1464
0
    FcStrListDone (list);
1465
0
    if (!cache_dir)
1466
0
  return FcFalse;
1467
1468
0
    FcDirCacheBasenameMD5 (config, dir, cache_base);
1469
0
    cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
1470
0
    FcStrFree (cache_dir);
1471
0
    if (!cache_hashed)
1472
0
  return FcFalse;
1473
1474
0
    if (FcDebug() & FC_DBG_CACHE)
1475
0
  printf ("FcDirCacheWriteDir dir \"%s\" file \"%s\"\n",
1476
0
          dir, cache_hashed);
1477
1478
0
    atomic = FcAtomicCreate ((FcChar8 *)cache_hashed);
1479
0
    if (!atomic)
1480
0
  goto bail1;
1481
1482
0
    if (!FcAtomicLock (atomic))
1483
0
  goto bail3;
1484
1485
0
    fd = FcOpen ((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT | O_BINARY, 0666);
1486
0
    if (fd == -1)
1487
0
  goto bail4;
1488
1489
    /* Temporarily switch magic to MMAP while writing to file */
1490
0
    magic = cache->magic;
1491
0
    if (magic != FC_CACHE_MAGIC_MMAP)
1492
0
  cache->magic = FC_CACHE_MAGIC_MMAP;
1493
1494
    /*
1495
     * Write cache contents to file
1496
     */
1497
0
    written = write (fd, cache, cache->size);
1498
1499
    /* Switch magic back */
1500
0
    if (magic != FC_CACHE_MAGIC_MMAP)
1501
0
  cache->magic = magic;
1502
1503
0
    if (written != cache->size) {
1504
0
  perror ("write cache");
1505
0
  goto bail5;
1506
0
    }
1507
1508
0
    close (fd);
1509
0
    if (!FcAtomicReplaceOrig (atomic))
1510
0
  goto bail4;
1511
1512
    /* If the file is small, update the cache chain entry such that the
1513
     * new cache file is not read again.  If it's large, we don't do that
1514
     * such that we reload it, using mmap, which is shared across processes.
1515
     */
1516
0
    if (cache->size < FC_CACHE_MIN_MMAP && FcStat (cache_hashed, &cache_stat)) {
1517
0
  lock_cache();
1518
0
  if ((skip = FcCacheFindByAddrUnlocked (cache))) {
1519
0
      skip->cache_dev = cache_stat.st_dev;
1520
0
      skip->cache_ino = cache_stat.st_ino;
1521
0
      skip->cache_mtime = cache_stat.st_mtime;
1522
0
#ifdef HAVE_STRUCT_STAT_ST_MTIM
1523
0
      skip->cache_mtime_nano = cache_stat.st_mtim.tv_nsec;
1524
#else
1525
      skip->cache_mtime_nano = 0;
1526
#endif
1527
0
  }
1528
0
  unlock_cache();
1529
0
    }
1530
1531
0
    FcStrFree (cache_hashed);
1532
0
    FcAtomicUnlock (atomic);
1533
0
    FcAtomicDestroy (atomic);
1534
0
    return FcTrue;
1535
1536
0
bail5:
1537
0
    close (fd);
1538
0
bail4:
1539
0
    FcAtomicUnlock (atomic);
1540
0
bail3:
1541
0
    FcAtomicDestroy (atomic);
1542
0
bail1:
1543
0
    FcStrFree (cache_hashed);
1544
0
    return FcFalse;
1545
0
}
1546
1547
FcBool
1548
FcDirCacheClean (const FcChar8 *cache_dir, FcBool verbose)
1549
0
{
1550
0
    DIR           *d;
1551
0
    struct dirent *ent;
1552
0
    FcChar8       *dir;
1553
0
    FcBool         ret = FcTrue;
1554
0
    FcBool         remove;
1555
0
    FcCache       *cache;
1556
0
    struct stat    target_stat;
1557
0
    const FcChar8 *sysroot;
1558
0
    FcConfig      *config;
1559
1560
0
    config = FcConfigReference (NULL);
1561
0
    if (!config)
1562
0
  return FcFalse;
1563
    /* FIXME: this API needs to support non-current FcConfig */
1564
0
    sysroot = FcConfigGetSysRoot (config);
1565
0
    if (sysroot)
1566
0
  dir = FcStrBuildFilename (sysroot, cache_dir, NULL);
1567
0
    else
1568
0
  dir = FcStrCopyFilename (cache_dir);
1569
0
    if (!dir) {
1570
0
  fprintf (stderr, "Fontconfig error: %s: out of memory\n", cache_dir);
1571
0
  ret = FcFalse;
1572
0
  goto bail;
1573
0
    }
1574
0
    if (access ((char *)dir, W_OK) != 0) {
1575
0
  if (verbose || FcDebug() & FC_DBG_CACHE)
1576
0
      printf ("%s: not cleaning %s cache directory\n", dir,
1577
0
              access ((char *)dir, F_OK) == 0 ? "unwritable" : "non-existent");
1578
0
  goto bail0;
1579
0
    }
1580
0
    if (verbose || FcDebug() & FC_DBG_CACHE)
1581
0
  printf ("%s: cleaning cache directory\n", dir);
1582
0
    d = opendir ((char *)dir);
1583
0
    if (!d) {
1584
0
  perror ((char *)dir);
1585
0
  ret = FcFalse;
1586
0
  goto bail0;
1587
0
    }
1588
0
    while ((ent = readdir (d))) {
1589
0
  FcChar8       *file_name;
1590
0
  const FcChar8 *target_dir;
1591
1592
0
  if (ent->d_name[0] == '.')
1593
0
      continue;
1594
  /* skip cache files for different architectures and */
1595
  /* files which are not cache files at all */
1596
0
  if (strlen (ent->d_name) != 32 + strlen ("-" FC_ARCHITECTURE FC_CACHE_SUFFIX) ||
1597
0
      strcmp (ent->d_name + 32, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX))
1598
0
      continue;
1599
1600
0
  file_name = FcStrBuildFilename (dir, (FcChar8 *)ent->d_name, NULL);
1601
0
  if (!file_name) {
1602
0
      fprintf (stderr, "Fontconfig error: %s: allocation failure\n", dir);
1603
0
      ret = FcFalse;
1604
0
      break;
1605
0
  }
1606
0
  remove = FcFalse;
1607
0
  cache = FcDirCacheLoadFile (file_name, NULL);
1608
0
  if (!cache) {
1609
0
      if (verbose || FcDebug() & FC_DBG_CACHE)
1610
0
    printf ("%s: invalid cache file: %s\n", dir, ent->d_name);
1611
0
      remove = FcTrue;
1612
0
  } else {
1613
0
      FcChar8 *s;
1614
1615
0
      target_dir = FcCacheDir (cache);
1616
0
      if (sysroot)
1617
0
    s = FcStrBuildFilename (sysroot, target_dir, NULL);
1618
0
      else
1619
0
    s = FcStrdup (target_dir);
1620
0
      if (stat ((char *)s, &target_stat) < 0) {
1621
0
    if (verbose || FcDebug() & FC_DBG_CACHE)
1622
0
        printf ("%s: %s: missing directory: %s \n",
1623
0
                dir, ent->d_name, s);
1624
0
    remove = FcTrue;
1625
0
      }
1626
0
      FcDirCacheUnload (cache);
1627
0
      FcStrFree (s);
1628
0
  }
1629
0
  if (remove) {
1630
0
      if (unlink ((char *)file_name) < 0) {
1631
0
    perror ((char *)file_name);
1632
0
    ret = FcFalse;
1633
0
      }
1634
0
  }
1635
0
  FcStrFree (file_name);
1636
0
    }
1637
1638
0
    closedir (d);
1639
0
bail0:
1640
0
    FcStrFree (dir);
1641
0
bail:
1642
0
    FcConfigDestroy (config);
1643
1644
0
    return ret;
1645
0
}
1646
1647
int
1648
FcDirCacheLock (const FcChar8 *dir,
1649
                FcConfig      *config)
1650
0
{
1651
0
    FcChar8       *cache_hashed = NULL;
1652
0
    FcChar8        cache_base[CACHEBASE_LEN];
1653
0
    FcStrList     *list;
1654
0
    FcChar8       *cache_dir;
1655
0
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
1656
0
    int            fd = -1;
1657
1658
0
    FcDirCacheBasenameMD5 (config, dir, cache_base);
1659
0
    list = FcStrListCreate (config->cacheDirs);
1660
0
    if (!list)
1661
0
  return -1;
1662
1663
0
    while ((cache_dir = FcStrListNext (list))) {
1664
0
  if (sysroot)
1665
0
      cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
1666
0
  else
1667
0
      cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
1668
0
  if (!cache_hashed)
1669
0
      break;
1670
0
  fd = FcOpen ((const char *)cache_hashed, O_RDWR);
1671
0
  FcStrFree (cache_hashed);
1672
  /* No caches in that directory. simply retry with another one */
1673
0
  if (fd != -1) {
1674
#if defined(_WIN32)
1675
      if (_locking (fd, _LK_LOCK, 1) == -1)
1676
    goto bail;
1677
#else
1678
0
      struct flock fl;
1679
1680
0
      fl.l_type = F_WRLCK;
1681
0
      fl.l_whence = SEEK_SET;
1682
0
      fl.l_start = 0;
1683
0
      fl.l_len = 0;
1684
0
      fl.l_pid = getpid();
1685
0
      if (fcntl (fd, F_SETLKW, &fl) == -1)
1686
0
    goto bail;
1687
0
#endif
1688
0
      break;
1689
0
  }
1690
0
    }
1691
0
    FcStrListDone (list);
1692
0
    return fd;
1693
0
bail:
1694
0
    FcStrListDone (list);
1695
0
    if (fd != -1)
1696
0
  close (fd);
1697
0
    return -1;
1698
0
}
1699
1700
void
1701
FcDirCacheUnlock (int fd)
1702
0
{
1703
0
    if (fd != -1) {
1704
#if defined(_WIN32)
1705
  _locking (fd, _LK_UNLCK, 1);
1706
#else
1707
0
  struct flock fl;
1708
1709
0
  fl.l_type = F_UNLCK;
1710
0
  fl.l_whence = SEEK_SET;
1711
0
  fl.l_start = 0;
1712
0
  fl.l_len = 0;
1713
0
  fl.l_pid = getpid();
1714
0
  fcntl (fd, F_SETLK, &fl);
1715
0
#endif
1716
0
  close (fd);
1717
0
    }
1718
0
}
1719
1720
/*
1721
 * Hokey little macro trick to permit the definitions of C functions
1722
 * with the same name as CPP macros
1723
 */
1724
#define args1(x)    (x)
1725
#define args2(x, y) (x, y)
1726
1727
const FcChar8 *
1728
FcCacheDir args1 (const FcCache *c)
1729
0
{
1730
0
    return FcCacheDir (c);
1731
0
}
1732
1733
FcFontSet *
1734
FcCacheCopySet args1 (const FcCache *c)
1735
0
{
1736
0
    FcFontSet *old = FcCacheSet (c);
1737
0
    FcFontSet *newp = FcFontSetCreate();
1738
0
    int        i;
1739
1740
0
    if (!newp)
1741
0
  return NULL;
1742
0
    for (i = 0; i < old->nfont; i++) {
1743
0
  FcPattern *font = FcFontSetFont (old, i);
1744
1745
0
  FcPatternReference (font);
1746
0
  if (!FcFontSetAdd (newp, font)) {
1747
0
      FcFontSetDestroy (newp);
1748
0
      return NULL;
1749
0
  }
1750
0
    }
1751
0
    return newp;
1752
0
}
1753
1754
const FcChar8 *
1755
FcCacheSubdir args2 (const FcCache *c, int i)
1756
0
{
1757
0
    return FcCacheSubdir (c, i);
1758
0
}
1759
1760
int
1761
FcCacheNumSubdir args1 (const FcCache *c)
1762
0
{
1763
0
    return c->dirs_count;
1764
0
}
1765
1766
int
1767
FcCacheNumFont args1 (const FcCache *c)
1768
0
{
1769
0
    return FcCacheSet (c)->nfont;
1770
0
}
1771
1772
FcBool
1773
FcDirCacheCreateTagFile (const FcChar8 *cache_dir)
1774
0
{
1775
0
    FcChar8             *cache_tag;
1776
0
    int                  fd;
1777
0
    FILE                *fp;
1778
0
    FcAtomic            *atomic;
1779
0
    static const FcChar8 cache_tag_contents[] =
1780
0
  "Signature: 8a477f597d28d172789f06886806bc55\n"
1781
0
  "# This file is a cache directory tag created by fontconfig.\n"
1782
0
  "# For information about cache directory tags, see:\n"
1783
0
  "#       http://www.brynosaurus.com/cachedir/\n";
1784
0
    static size_t cache_tag_contents_size = sizeof (cache_tag_contents) - 1;
1785
0
    FcBool        ret = FcFalse;
1786
1787
0
    if (!cache_dir)
1788
0
  return FcFalse;
1789
1790
0
    if (access ((char *)cache_dir, W_OK) == 0) {
1791
  /* Create CACHEDIR.TAG */
1792
0
  cache_tag = FcStrBuildFilename (cache_dir, "CACHEDIR.TAG", NULL);
1793
0
  if (!cache_tag)
1794
0
      return FcFalse;
1795
0
  atomic = FcAtomicCreate ((FcChar8 *)cache_tag);
1796
0
  if (!atomic)
1797
0
      goto bail1;
1798
0
  if (!FcAtomicLock (atomic))
1799
0
      goto bail2;
1800
0
  fd = FcOpen ((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT, 0644);
1801
0
  if (fd == -1)
1802
0
      goto bail3;
1803
0
  fp = fdopen (fd, "wb");
1804
0
  if (fp == NULL)
1805
0
      goto bail3;
1806
1807
0
  fwrite (cache_tag_contents, cache_tag_contents_size, sizeof (FcChar8), fp);
1808
0
  fclose (fp);
1809
1810
0
  if (!FcAtomicReplaceOrig (atomic))
1811
0
      goto bail3;
1812
1813
0
  ret = FcTrue;
1814
0
    bail3:
1815
0
  FcAtomicUnlock (atomic);
1816
0
    bail2:
1817
0
  FcAtomicDestroy (atomic);
1818
0
    bail1:
1819
0
  FcStrFree (cache_tag);
1820
0
    }
1821
1822
0
    if (FcDebug() & FC_DBG_CACHE) {
1823
0
  if (ret)
1824
0
      printf ("Created CACHEDIR.TAG at %s\n", cache_dir);
1825
0
  else
1826
0
      printf ("Unable to create CACHEDIR.TAG at %s\n", cache_dir);
1827
0
    }
1828
1829
0
    return ret;
1830
0
}
1831
1832
void
1833
FcCacheCreateTagFile (FcConfig *config)
1834
0
{
1835
0
    FcChar8       *cache_dir = NULL, *d = NULL;
1836
0
    FcStrList     *list;
1837
0
    const FcChar8 *sysroot;
1838
1839
0
    config = FcConfigReference (config);
1840
0
    if (!config)
1841
0
  return;
1842
0
    sysroot = FcConfigGetSysRoot (config);
1843
1844
0
    list = FcConfigGetCacheDirs (config);
1845
0
    if (!list)
1846
0
  goto bail;
1847
1848
0
    while ((cache_dir = FcStrListNext (list))) {
1849
0
  if (d)
1850
0
      FcStrFree (d);
1851
0
  if (sysroot)
1852
0
      d = FcStrBuildFilename (sysroot, cache_dir, NULL);
1853
0
  else
1854
0
      d = FcStrCopyFilename (cache_dir);
1855
0
  if (FcDirCacheCreateTagFile (d))
1856
0
      break;
1857
0
    }
1858
0
    if (d)
1859
0
  FcStrFree (d);
1860
0
    FcStrListDone (list);
1861
0
bail:
1862
0
    FcConfigDestroy (config);
1863
0
}
1864
1865
#define __fccache__
1866
#include "fcaliastail.h"
1867
#undef __fccache__