Coverage Report

Created: 2026-10-28 07:09

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