Coverage Report

Created: 2026-04-29 07:00

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
0
#  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
0
{
71
0
    FcBool ret = FcTrue;
72
0
#ifndef _WIN32
73
0
    const FcChar8 *sysroot;
74
0
    FcChar8       *target, *d;
75
0
    struct stat    statb;
76
0
    struct timeval times[2];
77
78
0
    config = FcConfigReference (config);
79
0
    if (!config)
80
0
  return FcFalse;
81
0
    sysroot = FcConfigGetSysRoot (config);
82
0
    if (sysroot)
83
0
  d = FcStrBuildFilename (sysroot, dir, NULL);
84
0
    else
85
0
  d = FcStrBuildFilename (dir, NULL);
86
0
    if (FcStat (d, &statb) != 0) {
87
0
  ret = FcFalse;
88
0
  goto bail;
89
0
    }
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
#  ifdef HAVE_STRUCT_STAT_ST_MTIM
96
  times[0].tv_usec = statb.st_atim.tv_nsec / 1000;
97
  times[1].tv_usec = statb.st_mtim.tv_nsec / 1000;
98
#  else
99
0
  times[0].tv_usec = 0;
100
0
  times[1].tv_usec = 0;
101
0
#  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
0
bail:
108
0
    FcStrFree (d);
109
0
    FcConfigDestroy (config);
110
0
#endif
111
112
0
    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
0
{
120
0
    enum {
121
0
  MMAP_NOT_INITIALIZED = 0,
122
0
  MMAP_USE,
123
0
  MMAP_DONT_USE,
124
0
  MMAP_CHECK_FS,
125
0
    } status;
126
0
    static void *static_status;
127
128
0
    status = (intptr_t)fc_atomic_ptr_get (&static_status);
129
130
0
    if (status == MMAP_NOT_INITIALIZED) {
131
0
  const char *env = getenv ("FONTCONFIG_USE_MMAP");
132
0
  FcBool      use;
133
0
  if (env && FcNameBool ((const FcChar8 *)env, &use))
134
0
      status = use ? MMAP_USE : MMAP_DONT_USE;
135
0
  else
136
0
      status = MMAP_CHECK_FS;
137
0
  (void)fc_atomic_ptr_cmpexch (&static_status, NULL, (void *)(intptr_t)status);
138
0
    }
139
140
0
    if (status == MMAP_CHECK_FS)
141
0
  return FcIsFsMmapSafe (fd);
142
0
    else
143
0
  return status == MMAP_USE;
144
0
}
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
0
{
154
0
    FcChar8          *mapped_dir = NULL;
155
0
    unsigned char     hash[16];
156
0
    FcChar8          *hex_hash, *key = NULL;
157
0
    int               cnt;
158
0
    struct MD5Context ctx;
159
0
    const FcChar8    *salt, *orig_dir = NULL;
160
161
0
    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
0
    mapped_dir = FcConfigMapFontPath (config, dir);
169
0
    if (mapped_dir) {
170
0
  orig_dir = dir;
171
0
  dir = mapped_dir;
172
0
    }
173
0
    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
0
    MD5Init (&ctx);
186
0
    MD5Update (&ctx, (const unsigned char *)dir, strlen ((const char *)dir));
187
188
0
    MD5Final (hash, &ctx);
189
190
0
    if (key)
191
0
  FcStrFree (key);
192
193
0
    hex_hash = cache_base;
194
0
    for (cnt = 0; cnt < 16; ++cnt) {
195
0
  hex_hash[2 * cnt] = bin2hex[hash[cnt] >> 4];
196
0
  hex_hash[2 * cnt + 1] = bin2hex[hash[cnt] & 0xf];
197
0
    }
198
0
    hex_hash[2 * cnt] = 0;
199
0
    strcat ((char *)cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
200
0
    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
0
    if (mapped_dir)
205
0
  FcStrFree (mapped_dir);
206
207
0
    return cache_base;
208
0
}
209
210
#ifndef _WIN32
211
static FcChar8 *
212
FcDirCacheBasenameUUID (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
213
0
{
214
0
    FcChar8       *target, *fuuid;
215
0
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
216
0
    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
0
    cache_base[0] = 0;
222
0
    if (sysroot)
223
0
  target = FcStrBuildFilename (sysroot, dir, NULL);
224
0
    else
225
0
  target = FcStrCopy (dir);
226
0
    fuuid = FcStrBuildFilename (target, ".uuid", NULL);
227
0
    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
0
bail:
245
0
    FcStrFree (fuuid);
246
0
    FcStrFree (target);
247
248
0
    return cache_base;
249
0
}
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
0
{
317
0
    int fd;
318
319
#ifdef _WIN32
320
    if (FcStat (cache_file, file_stat) < 0)
321
  return -1;
322
#endif
323
0
    fd = FcOpen ((char *)cache_file, O_RDONLY | O_BINARY);
324
0
    if (fd < 0)
325
0
  return fd;
326
0
#ifndef _WIN32
327
0
    if (fstat (fd, file_stat) < 0) {
328
0
  close (fd);
329
0
  return -1;
330
0
    }
331
0
#endif
332
0
    return fd;
333
0
}
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
0
{
346
0
    int            fd = -1;
347
0
    FcChar8        cache_base[CACHEBASE_LEN];
348
0
    FcStrList     *list;
349
0
    FcChar8       *cache_dir, *d;
350
0
    struct stat    file_stat, dir_stat;
351
0
    FcBool         ret = FcFalse;
352
0
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
353
0
    struct timeval latest_mtime = (struct timeval){ 0 };
354
355
0
    if (sysroot)
356
0
  d = FcStrBuildFilename (sysroot, dir, NULL);
357
0
    else
358
0
  d = FcStrCopy (dir);
359
0
    if (FcStatChecksum (d, &dir_stat) < 0) {
360
0
  FcStrFree (d);
361
0
  return FcFalse;
362
0
    }
363
0
    FcStrFree (d);
364
365
0
    FcDirCacheBasenameMD5 (config, dir, cache_base);
366
367
0
    list = FcStrListCreate (config->cacheDirs);
368
0
    if (!list)
369
0
  return FcFalse;
370
371
0
    while ((cache_dir = FcStrListNext (list))) {
372
0
  FcChar8 *cache_hashed;
373
0
#ifndef _WIN32
374
0
  FcBool retried = FcFalse;
375
0
#endif
376
377
0
  if (sysroot)
378
0
      cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
379
0
  else
380
0
      cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
381
0
  if (!cache_hashed)
382
0
      break;
383
0
#ifndef _WIN32
384
0
    retry:
385
0
#endif
386
0
  fd = FcDirCacheOpenFile (cache_hashed, &file_stat);
387
0
  if (fd >= 0) {
388
0
      ret = (*callback) (config, fd, &file_stat, &dir_stat, &latest_mtime, closure);
389
0
      close (fd);
390
0
      if (ret) {
391
0
    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
0
        FcStrFree (cache_hashed);
397
0
      } else
398
0
    FcStrFree (cache_hashed);
399
0
  }
400
0
#ifndef _WIN32
401
0
  else if (!retried) {
402
0
      FcChar8 uuid_cache_base[CACHEBASE_LEN];
403
404
0
      retried = FcTrue;
405
0
      FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
406
0
      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
0
    FcStrFree (cache_hashed);
417
0
  }
418
0
#endif
419
0
  else
420
0
      FcStrFree (cache_hashed);
421
0
    }
422
0
    FcStrListDone (list);
423
424
0
    if (closure)
425
0
  return !!(*((FcCache **)closure) != NULL);
426
0
    return ret;
427
0
}
428
429
0
#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
0
#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
0
{
467
0
    FcMutex *lock;
468
0
retry:
469
0
    lock = fc_atomic_ptr_get (&cache_lock);
470
0
    if (!lock) {
471
0
  lock = (FcMutex *)malloc (sizeof (FcMutex));
472
0
  FcMutexInit (lock);
473
0
  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
0
  FcMutexLock (lock);
480
  /* Initialize random state */
481
0
  FcRandom();
482
0
  return;
483
0
    }
484
0
    FcMutexLock (lock);
485
0
}
486
487
static void
488
unlock_cache (void)
489
0
{
490
0
    FcMutex *lock;
491
0
    lock = fc_atomic_ptr_get (&cache_lock);
492
0
    FcMutexUnlock (lock);
493
0
}
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
0
{
515
    /* tricky bit -- each bit is '1' 75% of the time */
516
0
    long int bits = FcRandom() | FcRandom();
517
0
    int      level = 0;
518
519
0
    while (++level < FC_CACHE_MAX_LEVEL) {
520
0
  if (bits & 1)
521
0
      break;
522
0
  bits >>= 1;
523
0
    }
524
0
    return level;
525
0
}
526
527
/*
528
 * Insert cache into the list
529
 */
530
static FcBool
531
FcCacheInsert (FcCache *cache, struct stat *cache_stat)
532
0
{
533
0
    FcCacheSkip **update[FC_CACHE_MAX_LEVEL];
534
0
    FcCacheSkip  *s, **next;
535
0
    int           i, level;
536
537
0
    lock_cache();
538
539
    /*
540
     * Find links along each chain
541
     */
542
0
    next = fcCacheChains;
543
0
    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
0
    level = random_level();
554
0
    if (level > fcCacheMaxLevel) {
555
0
  level = fcCacheMaxLevel + 1;
556
0
  update[fcCacheMaxLevel] = &fcCacheChains[fcCacheMaxLevel];
557
0
  fcCacheMaxLevel = level;
558
0
    }
559
560
0
    s = malloc (sizeof (FcCacheSkip) + (level - 1) * sizeof (FcCacheSkip *));
561
0
    if (!s) {
562
0
  unlock_cache();
563
0
  return FcFalse;
564
0
    }
565
566
0
    s->cache = cache;
567
0
    s->size = cache->size;
568
0
    s->allocated = NULL;
569
0
    FcRefInit (&s->ref, 1);
570
0
    if (cache_stat) {
571
0
  s->cache_dev = cache_stat->st_dev;
572
0
  s->cache_ino = cache_stat->st_ino;
573
0
  s->cache_mtime = cache_stat->st_mtime;
574
#ifdef HAVE_STRUCT_STAT_ST_MTIM
575
  s->cache_mtime_nano = cache_stat->st_mtim.tv_nsec;
576
#else
577
0
  s->cache_mtime_nano = 0;
578
0
#endif
579
0
    } 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
0
    for (i = 0; i < level; i++) {
590
0
  s->next[i] = *update[i];
591
0
  *update[i] = s;
592
0
    }
593
594
0
    unlock_cache();
595
0
    return FcTrue;
596
0
}
597
598
static FcCacheSkip *
599
FcCacheFindByAddrUnlocked (void *object)
600
0
{
601
0
    int           i;
602
0
    FcCacheSkip **next = fcCacheChains;
603
0
    FcCacheSkip  *s;
604
605
0
    if (!object)
606
0
  return NULL;
607
608
    /*
609
     * Walk chain pointers one level at a time
610
     */
611
0
    for (i = fcCacheMaxLevel; --i >= 0;)
612
0
  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
0
    s = next[0];
618
0
    if (s && (char *)object < ((char *)s->cache + s->size))
619
0
  return s;
620
0
    return NULL;
621
0
}
622
623
static FcCacheSkip *
624
FcCacheFindByAddr (void *object)
625
0
{
626
0
    FcCacheSkip *ret;
627
0
    lock_cache();
628
0
    ret = FcCacheFindByAddrUnlocked (object);
629
0
    unlock_cache();
630
0
    return ret;
631
0
}
632
633
static void
634
FcCacheRemoveUnlocked (FcCache *cache)
635
0
{
636
0
    FcCacheSkip **update[FC_CACHE_MAX_LEVEL];
637
0
    FcCacheSkip  *s, **next;
638
0
    int           i;
639
0
    void         *allocated;
640
641
    /*
642
     * Find links along each chain
643
     */
644
0
    next = fcCacheChains;
645
0
    for (i = fcCacheMaxLevel; --i >= 0;) {
646
0
  for (; (s = next[i]); next = s->next)
647
0
      if (s->cache >= cache)
648
0
    break;
649
0
  update[i] = &next[i];
650
0
    }
651
0
    s = next[0];
652
0
    for (i = 0; i < fcCacheMaxLevel && *update[i] == s; i++)
653
0
  *update[i] = s->next[i];
654
0
    while (fcCacheMaxLevel > 0 && fcCacheChains[fcCacheMaxLevel - 1] == NULL)
655
0
  fcCacheMaxLevel--;
656
657
0
    if (s) {
658
0
  allocated = s->allocated;
659
0
  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
0
  free (s);
666
0
    }
667
0
}
668
669
static FcCache *
670
FcCacheFindByStat (struct stat *cache_stat)
671
0
{
672
0
    FcCacheSkip *s;
673
674
0
    lock_cache();
675
0
    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
#ifdef HAVE_STRUCT_STAT_ST_MTIM
680
      if (s->cache_mtime_nano != cache_stat->st_mtim.tv_nsec)
681
    continue;
682
#endif
683
0
      FcRefInc (&s->ref);
684
0
      unlock_cache();
685
0
      return s->cache;
686
0
  }
687
0
    unlock_cache();
688
0
    return NULL;
689
0
}
690
691
static void
692
FcDirCacheDisposeUnlocked (FcCache *cache)
693
0
{
694
0
    FcCacheRemoveUnlocked (cache);
695
696
0
    switch (cache->magic) {
697
0
    case FC_CACHE_MAGIC_ALLOC:
698
0
  free (cache);
699
0
  break;
700
0
    case FC_CACHE_MAGIC_MMAP:
701
0
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
702
0
  munmap (cache, cache->size);
703
#elif defined(_WIN32)
704
  UnmapViewOfFile (cache);
705
#endif
706
0
  break;
707
0
    }
708
0
}
709
710
void
711
FcCacheObjectReference (void *object)
712
0
{
713
0
    FcCacheSkip *skip = FcCacheFindByAddr (object);
714
715
0
    if (skip)
716
0
  FcRefInc (&skip->ref);
717
0
}
718
719
void
720
FcCacheObjectDereference (void *object)
721
0
{
722
0
    FcCacheSkip *skip;
723
724
0
    lock_cache();
725
0
    skip = FcCacheFindByAddrUnlocked (object);
726
0
    if (skip) {
727
0
  if (FcRefDec (&skip->ref) == 1)
728
0
      FcDirCacheDisposeUnlocked (skip->cache);
729
0
    }
730
0
    unlock_cache();
731
0
}
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
0
{
804
0
    struct stat dir_static;
805
0
    FcBool      fnano = FcTrue;
806
807
0
    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 = FcStrCopy (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
#ifdef HAVE_STRUCT_STAT_ST_MTIM
823
    fnano = (cache->checksum_nano == dir_stat->st_mtim.tv_nsec);
824
    if (FcDebug() & FC_DBG_CACHE)
825
  printf ("FcCacheTimeValid dir \"%s\" cache checksum %d.%ld dir checksum %d.%ld\n",
826
          FcCacheDir (cache), cache->checksum, (long)cache->checksum_nano, (int)dir_stat->st_mtime, dir_stat->st_mtim.tv_nsec);
827
#else
828
0
    if (FcDebug() & FC_DBG_CACHE)
829
0
  printf ("FcCacheTimeValid dir \"%s\" cache checksum %d dir checksum %d\n",
830
0
          FcCacheDir (cache), cache->checksum, (int)dir_stat->st_mtime);
831
0
#endif
832
833
0
    return dir_stat->st_mtime == 0 || (cache->checksum == (int)dir_stat->st_mtime && fnano);
834
0
}
835
836
static FcBool
837
FcCacheOffsetsValid (FcCache *cache)
838
0
{
839
0
    char      *base = (char *)cache;
840
0
    char      *end = base + cache->size;
841
0
    intptr_t  *dirs;
842
0
    FcFontSet *fs;
843
0
    int        i, j;
844
845
0
    if (cache->dir < 0 || cache->dir > cache->size - sizeof (intptr_t) ||
846
0
        memchr (base + cache->dir, '\0', cache->size - cache->dir) == NULL)
847
0
  return FcFalse;
848
849
0
    if (cache->dirs < 0 || cache->dirs >= cache->size ||
850
0
        cache->dirs_count < 0 ||
851
0
        cache->dirs_count > (cache->size - cache->dirs) / sizeof (intptr_t))
852
0
  return FcFalse;
853
854
0
    dirs = FcCacheDirs (cache);
855
0
    if (dirs) {
856
0
  for (i = 0; i < cache->dirs_count; i++) {
857
0
      FcChar8 *dir;
858
859
0
      if (dirs[i] < 0 ||
860
0
          dirs[i] > end - (char *)dirs - sizeof (intptr_t))
861
0
    return FcFalse;
862
863
0
      dir = FcOffsetToPtr (dirs, dirs[i], FcChar8);
864
0
      if (memchr (dir, '\0', end - (char *)dir) == NULL)
865
0
    return FcFalse;
866
0
  }
867
0
    }
868
869
0
    if (cache->set < 0 || cache->set > cache->size - sizeof (FcFontSet))
870
0
  return FcFalse;
871
872
0
    fs = FcCacheSet (cache);
873
0
    if (fs) {
874
0
  if (fs->nfont > (end - (char *)fs) / sizeof (FcPattern))
875
0
      return FcFalse;
876
877
0
  if (!FcIsEncodedOffset (fs->fonts))
878
0
      return FcFalse;
879
880
0
  for (i = 0; i < fs->nfont; i++) {
881
0
      FcPattern       *font = FcFontSetFont (fs, i);
882
0
      FcPatternElt    *e;
883
0
      FcValueListPtr   l;
884
0
      char            *last_offset;
885
0
      const FcLangSet *ls;
886
0
      const FcRange   *r;
887
0
      const FcChar8   *s;
888
889
0
      if ((char *)font < base ||
890
0
          (char *)font > end - sizeof (FcFontSet) ||
891
0
          font->elts_offset < 0 ||
892
0
          font->elts_offset > end - (char *)font ||
893
0
          font->num > (end - (char *)font - font->elts_offset) / sizeof (FcPatternElt) ||
894
0
          !FcRefIsConst (&font->ref))
895
0
    return FcFalse;
896
897
0
      e = FcPatternElts (font);
898
0
      if (e->values != 0 && !FcIsEncodedOffset (e->values))
899
0
    return FcFalse;
900
901
0
      for (j = 0; j < font->num; j++) {
902
0
    last_offset = (char *)font + font->elts_offset;
903
0
    for (l = FcPatternEltValues (&e[j]); l; l = FcValueListNext (l)) {
904
0
        if ((char *)l < last_offset || (char *)l > end - sizeof (*l) ||
905
0
            (l->next != NULL && !FcIsEncodedOffset (l->next)))
906
0
      return FcFalse;
907
0
        switch (l->value.type) {
908
0
        case FcTypeVoid:
909
0
        case FcTypeInteger:
910
0
        case FcTypeDouble:
911
0
        case FcTypeBool:
912
0
        case FcTypeMatrix:
913
0
        case FcTypeFTFace:
914
0
      break; /* nop */
915
0
        case FcTypeString:
916
0
      s = FcValueString (&l->value);
917
0
      if ((intptr_t)s < (intptr_t)&e[j] ||
918
0
          (intptr_t)&l->value > (intptr_t)end - sizeof (*l) ||
919
0
          !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
0
      break;
926
0
        case FcTypeCharSet:
927
      /* FcCharSet might be re-used by FcCharSetFindFrozen
928
       * which would means a pointer might be out of Elts
929
       */
930
0
      if ((intptr_t)&l->value > (intptr_t)end - sizeof (*l) ||
931
0
          !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
0
      break;
938
0
        case FcTypeLangSet:
939
0
      ls = FcValueLangSet (&l->value);
940
0
      if ((intptr_t)ls < (intptr_t)&e[j] ||
941
0
          (intptr_t)&l->value > (intptr_t)end - sizeof (*l) ||
942
0
          !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
0
      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
0
      break;
956
0
        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
0
        }
974
0
        last_offset = (char *)l + 1;
975
0
    }
976
0
      }
977
0
  }
978
0
    }
979
980
0
    return FcTrue;
981
0
}
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
0
{
989
0
    FcCache *cache;
990
0
    FcBool   allocated = FcFalse;
991
992
0
    if (fd_stat->st_size > INTPTR_MAX ||
993
0
        fd_stat->st_size < (int)sizeof (FcCache))
994
0
  return NULL;
995
0
    cache = FcCacheFindByStat (fd_stat);
996
0
    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
0
    if (FcCacheIsMmapSafe (fd) && fd_stat->st_size >= FC_CACHE_MIN_MMAP) {
1014
0
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
1015
0
  cache = mmap (0, fd_stat->st_size, PROT_READ, MAP_SHARED, fd, 0);
1016
#  if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
1017
  posix_fadvise (fd, 0, fd_stat->st_size, POSIX_FADV_WILLNEED);
1018
#  endif
1019
0
  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
0
    }
1036
0
    if (!cache) {
1037
0
  cache = malloc (fd_stat->st_size);
1038
0
  if (!cache)
1039
0
      return NULL;
1040
1041
0
  if (read (fd, cache, fd_stat->st_size) != fd_stat->st_size) {
1042
0
      free (cache);
1043
0
      return NULL;
1044
0
  }
1045
0
  allocated = FcTrue;
1046
0
    }
1047
0
    if (cache->magic != FC_CACHE_MAGIC_MMAP ||
1048
0
        cache->version < FC_CACHE_VERSION_NUMBER ||
1049
0
        cache->size != (intptr_t)fd_stat->st_size ||
1050
0
        !FcCacheOffsetsValid (cache) ||
1051
0
        (!FcCacheTimeValid (config, cache, dir_stat) &&
1052
0
         !FcCacheIsNewVersion (config, cache)) ||
1053
0
        !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
0
    if (allocated)
1068
0
  cache->magic = FC_CACHE_MAGIC_ALLOC;
1069
1070
0
    return cache;
1071
0
}
1072
1073
void
1074
FcDirCacheReference (FcCache *cache, int nref)
1075
0
{
1076
0
    FcCacheSkip *skip = FcCacheFindByAddr (cache);
1077
1078
0
    if (skip)
1079
0
  FcRefAdd (&skip->ref, nref);
1080
0
}
1081
1082
void
1083
FcDirCacheUnload (FcCache *cache)
1084
0
{
1085
0
    FcCacheObjectDereference (cache);
1086
0
}
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
0
{
1091
0
    FcCache       *cache = FcDirCacheMapFd (config, fd, fd_stat, dir_stat);
1092
0
    struct timeval cache_mtime, zero_mtime = { 0, 0 }, dir_mtime;
1093
1094
0
    if (!cache)
1095
0
  return FcFalse;
1096
0
    cache_mtime.tv_sec = fd_stat->st_mtime;
1097
0
    dir_mtime.tv_sec = dir_stat->st_mtime;
1098
#ifdef HAVE_STRUCT_STAT_ST_MTIM
1099
    cache_mtime.tv_usec = fd_stat->st_mtim.tv_nsec / 1000;
1100
    dir_mtime.tv_usec = dir_stat->st_mtim.tv_nsec / 1000;
1101
#else
1102
0
    cache_mtime.tv_usec = 0;
1103
0
    dir_mtime.tv_usec = 0;
1104
0
#endif
1105
    /* special take care of OSTree */
1106
0
    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
0
    } else if (timercmp (latest_cache_mtime, &cache_mtime, <)) {
1118
0
  if (*((FcCache **)closure))
1119
0
      FcDirCacheUnload (*((FcCache **)closure));
1120
0
    } else {
1121
0
  FcDirCacheUnload (cache);
1122
0
  return FcFalse;
1123
0
    }
1124
0
    latest_cache_mtime->tv_sec = cache_mtime.tv_sec;
1125
0
    latest_cache_mtime->tv_usec = cache_mtime.tv_usec;
1126
0
    *((FcCache **)closure) = cache;
1127
0
    return FcTrue;
1128
0
}
1129
1130
FcCache *
1131
FcDirCacheLoad (const FcChar8 *dir, FcConfig *config, FcChar8 **cache_file)
1132
0
{
1133
0
    FcCache *cache = NULL;
1134
1135
0
    config = FcConfigReference (config);
1136
0
    if (!config)
1137
0
  return NULL;
1138
0
    if (!FcDirCacheProcess (config, dir,
1139
0
                            FcDirCacheMapHelper,
1140
0
                            &cache, cache_file))
1141
0
  cache = NULL;
1142
1143
0
    FcConfigDestroy (config);
1144
1145
0
    return cache;
1146
0
}
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
#ifdef HAVE_STRUCT_STAT_ST_MTIM
1210
    /* No nanosecond component to parse */
1211
    if (getenv ("SOURCE_DATE_EPOCH"))
1212
  return 0;
1213
    return statb->st_mtim.tv_nsec;
1214
#else
1215
0
    return 0;
1216
0
#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
#ifdef HAVE_STRUCT_STAT_ST_MTIM
1240
    else if (c.checksum_nano != FcDirChecksumNano (dir_stat))
1241
  ret = FcFalse;
1242
#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
#ifdef HAVE_STRUCT_STAT_ST_MTIM
1523
      skip->cache_mtime_nano = cache_stat.st_mtim.tv_nsec;
1524
#else
1525
0
      skip->cache_mtime_nano = 0;
1526
0
#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 = FcStrCopy (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__