fccache.c 50 KB
Newer Older
Keith Packard's avatar
Keith Packard committed
1
/*
2
 * Copyright © 2000 Keith Packard
3
 * Copyright © 2005 Patrick Lam
Keith Packard's avatar
Keith Packard committed
4
5
6
7
8
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
9
 * documentation, and that the name of the author(s) not be used in
Keith Packard's avatar
Keith Packard committed
10
 * advertising or publicity pertaining to distribution of the software without
11
 * specific, written prior permission.  The authors make no
Keith Packard's avatar
Keith Packard committed
12
13
14
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
15
 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
Keith Packard's avatar
Keith Packard committed
16
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17
 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
Keith Packard's avatar
Keith Packard committed
18
19
20
21
22
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
23
#include "fcint.h"
24
#include "fcarch.h"
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <fcntl.h>
28
#include <dirent.h>
29
#include <string.h>
30
#include <limits.h>
31
#include <sys/types.h>
32
#include <sys/stat.h>
33
34
35
36
37

#ifndef _WIN32
  #include <sys/time.h>
#endif

Keith Packard's avatar
Keith Packard committed
38
#include <assert.h>
Patrick Lam's avatar
Patrick Lam committed
39
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
40
#  include <unistd.h>
Patrick Lam's avatar
Patrick Lam committed
41
42
#  include <sys/mman.h>
#endif
Akira TAGOH's avatar
Akira TAGOH committed
43
44
#if defined(_WIN32)
#include <sys/locking.h>
45
#endif
Keith Packard's avatar
Keith Packard committed
46

47
48
49
50
#ifndef O_BINARY
#define O_BINARY 0
#endif

51
FcBool
Akira TAGOH's avatar
Akira TAGOH committed
52
53
54
FcDirCacheCreateUUID (FcChar8  *dir,
		      FcBool    force,
		      FcConfig *config)
55
{
56
    return FcTrue;
57
58
}

59
60
61
62
FcBool
FcDirCacheDeleteUUID (const FcChar8  *dir,
		      FcConfig       *config)
{
63
64
    FcBool ret = FcTrue;
#ifndef _WIN32
65
    const FcChar8 *sysroot;
66
67
68
    FcChar8 *target, *d;
    struct stat statb;
    struct timeval times[2];
69

70
71
72
73
    config = FcConfigReference (config);
    if (!config)
	return FcFalse;
    sysroot = FcConfigGetSysRoot (config);
74
    if (sysroot)
75
	d = FcStrBuildFilename (sysroot, dir, NULL);
76
    else
77
78
79
80
81
82
83
	d = FcStrBuildFilename (dir, NULL);
    if (FcStat (d, &statb) != 0)
    {
	ret = FcFalse;
	goto bail;
    }
    target = FcStrBuildFilename (d, ".uuid", NULL);
84
    ret = unlink ((char *) target) == 0;
85
86
87
88
    if (ret)
    {
	times[0].tv_sec = statb.st_atime;
	times[1].tv_sec = statb.st_mtime;
89
#ifdef HAVE_STRUCT_STAT_ST_MTIM
90
91
	times[0].tv_usec = statb.st_atim.tv_nsec / 1000;
	times[1].tv_usec = statb.st_mtim.tv_nsec / 1000;
92
#else
93
94
	times[0].tv_usec = 0;
	times[1].tv_usec = 0;
95
#endif
96
97
98
99
	if (utimes ((const char *) d, times) != 0)
	{
	    fprintf (stderr, "Unable to revert mtime: %s\n", d);
	}
100
101
102
103
    }
    FcStrFree (target);
bail:
    FcStrFree (d);
104
#endif
105
    FcConfigDestroy (config);
106
107
108
109

    return ret;
}

110
111
112
113
114
115
116
struct MD5Context {
        FcChar32 buf[4];
        FcChar32 bits[2];
        unsigned char in[64];
};

static void MD5Init(struct MD5Context *ctx);
117
static void MD5Update(struct MD5Context *ctx, const unsigned char *buf, unsigned len);
118
119
120
static void MD5Final(unsigned char digest[16], struct MD5Context *ctx);
static void MD5Transform(FcChar32 buf[4], FcChar32 in[16]);

121
#define CACHEBASE_LEN (1 + 36 + 1 + sizeof (FC_ARCHITECTURE) + sizeof (FC_CACHE_SUFFIX))
122

123
124
125
static FcBool
FcCacheIsMmapSafe (int fd)
{
126
127
128
129
130
131
132
    enum {
      MMAP_NOT_INITIALIZED = 0,
      MMAP_USE,
      MMAP_DONT_USE,
      MMAP_CHECK_FS,
    } status;
    static void *static_status;
133

134
    status = (intptr_t) fc_atomic_ptr_get (&static_status);
135

136
137
138
139
140
141
142
143
    if (status == MMAP_NOT_INITIALIZED)
    {
	const char *env = getenv ("FONTCONFIG_USE_MMAP");
	FcBool use;
	if (env && FcNameBool ((const FcChar8 *) env, &use))
	    status =  use ? MMAP_USE : MMAP_DONT_USE;
	else
	    status = MMAP_CHECK_FS;
144
	(void) fc_atomic_ptr_cmpexch (&static_status, NULL, (void *) status);
145
146
    }

147
148
149
150
151
    if (status == MMAP_CHECK_FS)
	return FcIsFsMmapSafe (fd);
    else
	return status == MMAP_USE;

152
153
}

154
155
156
157
158
159
static const char bin2hex[] = { '0', '1', '2', '3',
				'4', '5', '6', '7',
				'8', '9', 'a', 'b',
				'c', 'd', 'e', 'f' };

static FcChar8 *
160
FcDirCacheBasenameMD5 (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
161
{
Akira TAGOH's avatar
Akira TAGOH committed
162
    FcChar8		*mapped_dir = NULL;
163
    unsigned char 	hash[16];
164
    FcChar8		*hex_hash, *key = NULL;
165
166
    int			cnt;
    struct MD5Context 	ctx;
Akira TAGOH's avatar
Akira TAGOH committed
167
    const FcChar8	*salt, *orig_dir = NULL;
168

169
    salt = FcConfigMapSalt (config, dir);
Akira TAGOH's avatar
Akira TAGOH committed
170
171
172
173
174
175
176
177
    /* Obtain a path where "dir" is mapped to.
     * In case:
     * <remap-dir as-path="/usr/share/fonts">/run/host/fonts</remap-dir>
     *
     * FcConfigMapFontPath (config, "/run/host/fonts") will returns "/usr/share/fonts".
     */
    mapped_dir = FcConfigMapFontPath(config, dir);
    if (mapped_dir)
Akira TAGOH's avatar
Akira TAGOH committed
178
179
    {
	orig_dir = dir;
Akira TAGOH's avatar
Akira TAGOH committed
180
	dir = mapped_dir;
Akira TAGOH's avatar
Akira TAGOH committed
181
    }
182
183
184
185
    if (salt)
    {
	size_t dl = strlen ((const char *) dir);
	size_t sl = strlen ((const char *) salt);
186

187
188
189
	key = (FcChar8 *) malloc (dl + sl + 1);
	memcpy (key, dir, dl);
	memcpy (key + dl, salt, sl + 1);
Akira TAGOH's avatar
Akira TAGOH committed
190
	key[dl + sl] = 0;
Akira TAGOH's avatar
Akira TAGOH committed
191
192
	if (!orig_dir)
		orig_dir = dir;
193
194
	dir = key;
    }
195
    MD5Init (&ctx);
196
    MD5Update (&ctx, (const unsigned char *)dir, strlen ((const char *) dir));
Patrick Lam's avatar
Patrick Lam committed
197

198
199
    MD5Final (hash, &ctx);

200
201
    if (key)
	FcStrFree (key);
202

203
204
205
    cache_base[0] = '/';
    hex_hash = cache_base + 1;
    for (cnt = 0; cnt < 16; ++cnt)
206
    {
207
208
209
210
	hex_hash[2*cnt  ] = bin2hex[hash[cnt] >> 4];
	hex_hash[2*cnt+1] = bin2hex[hash[cnt] & 0xf];
    }
    hex_hash[2*cnt] = 0;
211
    strcat ((char *) cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
Akira TAGOH's avatar
Akira TAGOH committed
212
213
    if (FcDebug() & FC_DBG_CACHE)
    {
Akira TAGOH's avatar
Akira TAGOH committed
214
	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 : "");
Akira TAGOH's avatar
Akira TAGOH committed
215
216
    }

Akira TAGOH's avatar
Akira TAGOH committed
217
218
    if (mapped_dir)
	FcStrFree(mapped_dir);
219

220
221
    return cache_base;
}
Patrick Lam's avatar
Patrick Lam committed
222

223
224
225
226
227
228
229
230
#ifndef _WIN32
static FcChar8 *
FcDirCacheBasenameUUID (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
{
    FcChar8 *target, *fuuid;
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
    int fd;

Akira TAGOH's avatar
Akira TAGOH committed
231
232
233
    /* We don't need to apply remapping here. because .uuid was created at that very directory
     * to determine the cache name no matter where it was mapped to.
     */
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
    cache_base[0] = 0;
    if (sysroot)
	target = FcStrBuildFilename (sysroot, dir, NULL);
    else
	target = FcStrdup (dir);
    fuuid = FcStrBuildFilename (target, ".uuid", NULL);
    if ((fd = FcOpen ((char *) fuuid, O_RDONLY)) != -1)
    {
	char suuid[37];
	ssize_t len;

	memset (suuid, 0, sizeof (suuid));
	len = read (fd, suuid, 36);
	suuid[36] = 0;
	close (fd);
	if (len < 0)
	    goto bail;
	cache_base[0] = '/';
	strcpy ((char *)&cache_base[1], suuid);
	strcat ((char *) cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
	if (FcDebug () & FC_DBG_CACHE)
	{
Akira TAGOH's avatar
Akira TAGOH committed
256
	    printf ("cache fallbacks to: %s (dir: %s)\n", cache_base, dir);
257
258
259
260
261
262
263
264
265
266
	}
    }
bail:
    FcStrFree (fuuid);
    FcStrFree (target);

    return cache_base;
}
#endif

267
268
269
270
271
FcBool
FcDirCacheUnlink (const FcChar8 *dir, FcConfig *config)
{
    FcChar8	*cache_hashed = NULL;
    FcChar8	cache_base[CACHEBASE_LEN];
272
273
274
#ifndef _WIN32
    FcChar8     uuid_cache_base[CACHEBASE_LEN];
#endif
275
276
    FcStrList	*list;
    FcChar8	*cache_dir;
277
278
279
280
281
282
283
    const FcChar8 *sysroot;
    FcBool	ret = FcTrue;

    config = FcConfigReference (config);
    if (!config)
	return FcFalse;
    sysroot = FcConfigGetSysRoot (config);
Patrick Lam's avatar
Patrick Lam committed
284

285
    FcDirCacheBasenameMD5 (config, dir, cache_base);
286
287
288
#ifndef _WIN32
    FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
#endif
Patrick Lam's avatar
Patrick Lam committed
289

290
291
    list = FcStrListCreate (config->cacheDirs);
    if (!list)
292
293
294
295
    {
	ret = FcFalse;
	goto bail;
    }
296
297
	
    while ((cache_dir = FcStrListNext (list)))
Patrick Lam's avatar
Patrick Lam committed
298
    {
299
300
301
302
	if (sysroot)
	    cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
	else
	    cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
303
304
        if (!cache_hashed)
	    break;
305
	(void) unlink ((char *) cache_hashed);
306
	FcStrFree (cache_hashed);
307
308
309
310
311
312
313
314
315
316
317
318
319
#ifndef _WIN32
	if (uuid_cache_base[0] != 0)
	{
	    if (sysroot)
		cache_hashed = FcStrBuildFilename (sysroot, cache_dir, uuid_cache_base, NULL);
	    else
		cache_hashed = FcStrBuildFilename (cache_dir, uuid_cache_base, NULL);
	    if (!cache_hashed)
		break;
	    (void) unlink ((char *) cache_hashed);
	    FcStrFree (cache_hashed);
	}
#endif
320
    }
321
    FcStrListDone (list);
322
    FcDirCacheDeleteUUID (dir, config);
323
324
    /* return FcFalse if something went wrong */
    if (cache_dir)
325
326
327
328
329
	ret = FcFalse;
bail:
    FcConfigDestroy (config);

    return ret;
330
331
}

332
static int
333
FcDirCacheOpenFile (const FcChar8 *cache_file, struct stat *file_stat)
334
{
335
    int	fd;
336

337
338
339
340
#ifdef _WIN32
    if (FcStat (cache_file, file_stat) < 0)
        return -1;
#endif
Akira TAGOH's avatar
Akira TAGOH committed
341
    fd = FcOpen((char *) cache_file, O_RDONLY | O_BINARY);
342
343
    if (fd < 0)
	return fd;
344
#ifndef _WIN32
345
    if (fstat (fd, file_stat) < 0)
346
    {
347
348
	close (fd);
	return -1;
349
    }
350
#endif
351
    return fd;
352
353
}

354
/*
355
356
357
 * Look for a cache file for the specified dir. Attempt
 * to use each one we find, stopping when the callback
 * indicates success
358
 */
359
static FcBool
360
FcDirCacheProcess (FcConfig *config, const FcChar8 *dir,
361
		   FcBool (*callback) (FcConfig *config, int fd, struct stat *fd_stat,
Akira TAGOH's avatar
Akira TAGOH committed
362
				       struct stat *dir_stat, struct timeval *cache_mtime, void *closure),
363
		   void *closure, FcChar8 **cache_file_ret)
364
{
365
    int		fd = -1;
366
367
    FcChar8	cache_base[CACHEBASE_LEN];
    FcStrList	*list;
368
    FcChar8	*cache_dir, *d;
369
    struct stat file_stat, dir_stat;
370
    FcBool	ret = FcFalse;
371
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
372
    struct timeval latest_mtime = (struct timeval){ 0 };
373

374
375
376
377
378
379
380
    if (sysroot)
	d = FcStrBuildFilename (sysroot, dir, NULL);
    else
	d = FcStrdup (dir);
    if (FcStatChecksum (d, &dir_stat) < 0)
    {
	FcStrFree (d);
381
        return FcFalse;
382
383
    }
    FcStrFree (d);
384

385
    FcDirCacheBasenameMD5 (config, dir, cache_base);
386

387
388
    list = FcStrListCreate (config->cacheDirs);
    if (!list)
389
        return FcFalse;
390
391
	
    while ((cache_dir = FcStrListNext (list)))
392
    {
393
        FcChar8	*cache_hashed;
394
395
396
#ifndef _WIN32
	FcBool retried = FcFalse;
#endif
Akira TAGOH's avatar
Akira TAGOH committed
397

398
399
400
401
	if (sysroot)
	    cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
	else
	    cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
402
        if (!cache_hashed)
403
	    break;
Akira TAGOH's avatar
Akira TAGOH committed
404
405
406
#ifndef _WIN32
      retry:
#endif
407
        fd = FcDirCacheOpenFile (cache_hashed, &file_stat);
408
        if (fd >= 0) {
Akira TAGOH's avatar
Akira TAGOH committed
409
	    ret = (*callback) (config, fd, &file_stat, &dir_stat, &latest_mtime, closure);
410
411
	    close (fd);
	    if (ret)
412
	    {
413
		if (cache_file_ret)
Akira TAGOH's avatar
Akira TAGOH committed
414
415
416
		{
		    if (*cache_file_ret)
			FcStrFree (*cache_file_ret);
417
		    *cache_file_ret = cache_hashed;
Akira TAGOH's avatar
Akira TAGOH committed
418
		}
419
420
		else
		    FcStrFree (cache_hashed);
421
	    }
422
423
	    else
		FcStrFree (cache_hashed);
424
	}
425
426
427
#ifndef _WIN32
	else if (!retried)
	{
Akira TAGOH's avatar
Akira TAGOH committed
428
429
	    FcChar8	uuid_cache_base[CACHEBASE_LEN];

430
	    retried = FcTrue;
Akira TAGOH's avatar
Akira TAGOH committed
431
432
	    FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
	    if (uuid_cache_base[0] != 0)
433
434
	    {
		FcStrFree (cache_hashed);
Akira TAGOH's avatar
Akira TAGOH committed
435
436
437
438
439
440
		if (sysroot)
		    cache_hashed = FcStrBuildFilename (sysroot, cache_dir, uuid_cache_base, NULL);
		else
		    cache_hashed = FcStrBuildFilename (cache_dir, uuid_cache_base, NULL);
		if (!cache_hashed)
		    break;
441
442
		goto retry;
	    }
443
444
	    else
		FcStrFree (cache_hashed);
445
446
	}
#endif
Akira TAGOH's avatar
Akira TAGOH committed
447
448
	else
	    FcStrFree (cache_hashed);
449
450
    }
    FcStrListDone (list);
451

452
453
    if (closure)
	return !!(*((FcCache **)closure) != NULL);
454
    return ret;
455
456
}

Keith Packard's avatar
Keith Packard committed
457
458
459
460
461
462
463
464
465
466
467
468
#define FC_CACHE_MIN_MMAP   1024

/*
 * Skip list element, make sure the 'next' pointer is the last thing
 * in the structure, it will be allocated large enough to hold all
 * of the necessary pointers
 */

typedef struct _FcCacheSkip FcCacheSkip;

struct _FcCacheSkip {
    FcCache	    *cache;
469
    FcRef	    ref;
Keith Packard's avatar
Keith Packard committed
470
    intptr_t	    size;
471
    void	   *allocated;
Keith Packard's avatar
Keith Packard committed
472
473
474
    dev_t	    cache_dev;
    ino_t	    cache_ino;
    time_t	    cache_mtime;
Akira TAGOH's avatar
Akira TAGOH committed
475
    long	    cache_mtime_nano;
Keith Packard's avatar
Keith Packard committed
476
477
478
479
480
481
482
483
484
485
    FcCacheSkip	    *next[1];
};

/*
 * The head of the skip list; pointers for every possible level
 * in the skip list, plus the largest level in the list
 */

#define FC_CACHE_MAX_LEVEL  16

Behdad Esfahbod's avatar
Behdad Esfahbod committed
486
/* Protected by cache_lock below */
Keith Packard's avatar
Keith Packard committed
487
488
489
static FcCacheSkip	*fcCacheChains[FC_CACHE_MAX_LEVEL];
static int		fcCacheMaxLevel;

490

Behdad Esfahbod's avatar
Behdad Esfahbod committed
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
static FcMutex *cache_lock;

static void
lock_cache (void)
{
  FcMutex *lock;
retry:
  lock = fc_atomic_ptr_get (&cache_lock);
  if (!lock) {
    lock = (FcMutex *) malloc (sizeof (FcMutex));
    FcMutexInit (lock);
    if (!fc_atomic_ptr_cmpexch (&cache_lock, NULL, lock)) {
      FcMutexFinish (lock);
      goto retry;
    }
506
507
508
509
510

    FcMutexLock (lock);
    /* Initialize random state */
    FcRandom ();
    return;
Behdad Esfahbod's avatar
Behdad Esfahbod committed
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
  }
  FcMutexLock (lock);
}

static void
unlock_cache (void)
{
  FcMutexUnlock (cache_lock);
}

static void
free_lock (void)
{
  FcMutex *lock;
  lock = fc_atomic_ptr_get (&cache_lock);
  if (lock && fc_atomic_ptr_cmpexch (&cache_lock, lock, NULL)) {
    FcMutexFinish (lock);
    free (lock);
  }
}



Keith Packard's avatar
Keith Packard committed
534
535
536
537
538
539
540
541
542
543
/*
 * Generate a random level number, distributed
 * so that each level is 1/4 as likely as the one before
 *
 * Note that level numbers run 1 <= level <= MAX_LEVEL
 */
static int
random_level (void)
{
    /* tricky bit -- each bit is '1' 75% of the time */
544
    long int	bits = FcRandom () | FcRandom ();
Keith Packard's avatar
Keith Packard committed
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
    int	level = 0;

    while (++level < FC_CACHE_MAX_LEVEL)
    {
	if (bits & 1)
	    break;
	bits >>= 1;
    }
    return level;
}

/*
 * Insert cache into the list
 */
static FcBool
FcCacheInsert (FcCache *cache, struct stat *cache_stat)
{
    FcCacheSkip    **update[FC_CACHE_MAX_LEVEL];
    FcCacheSkip    *s, **next;
    int		    i, level;

Behdad Esfahbod's avatar
Behdad Esfahbod committed
566
567
    lock_cache ();

Keith Packard's avatar
Keith Packard committed
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
    /*
     * Find links along each chain
     */
    next = fcCacheChains;
    for (i = fcCacheMaxLevel; --i >= 0; )
    {
	for (; (s = next[i]); next = s->next)
	    if (s->cache > cache)
		break;
        update[i] = &next[i];
    }

    /*
     * Create new list element
     */
    level = random_level ();
    if (level > fcCacheMaxLevel)
    {
	level = fcCacheMaxLevel + 1;
	update[fcCacheMaxLevel] = &fcCacheChains[fcCacheMaxLevel];
	fcCacheMaxLevel = level;
    }
590

Keith Packard's avatar
Keith Packard committed
591
592
593
594
595
596
    s = malloc (sizeof (FcCacheSkip) + (level - 1) * sizeof (FcCacheSkip *));
    if (!s)
	return FcFalse;

    s->cache = cache;
    s->size = cache->size;
597
    s->allocated = NULL;
598
    FcRefInit (&s->ref, 1);
599
600
601
602
603
    if (cache_stat)
    {
	s->cache_dev = cache_stat->st_dev;
	s->cache_ino = cache_stat->st_ino;
	s->cache_mtime = cache_stat->st_mtime;
Akira TAGOH's avatar
Akira TAGOH committed
604
605
606
607
608
#ifdef HAVE_STRUCT_STAT_ST_MTIM
	s->cache_mtime_nano = cache_stat->st_mtim.tv_nsec;
#else
	s->cache_mtime_nano = 0;
#endif
609
610
611
612
613
614
    }
    else
    {
	s->cache_dev = 0;
	s->cache_ino = 0;
	s->cache_mtime = 0;
Akira TAGOH's avatar
Akira TAGOH committed
615
	s->cache_mtime_nano = 0;
616
    }
617

Keith Packard's avatar
Keith Packard committed
618
619
620
621
622
623
624
625
    /*
     * Insert into all fcCacheChains
     */
    for (i = 0; i < level; i++)
    {
	s->next[i] = *update[i];
	*update[i] = s;
    }
Behdad Esfahbod's avatar
Behdad Esfahbod committed
626
627

    unlock_cache ();
Keith Packard's avatar
Keith Packard committed
628
629
630
631
    return FcTrue;
}

static FcCacheSkip *
Behdad Esfahbod's avatar
Behdad Esfahbod committed
632
FcCacheFindByAddrUnlocked (void *object)
Keith Packard's avatar
Keith Packard committed
633
634
635
636
637
{
    int	    i;
    FcCacheSkip    **next = fcCacheChains;
    FcCacheSkip    *s;

638
639
640
    if (!object)
	return NULL;

Keith Packard's avatar
Keith Packard committed
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
    /*
     * Walk chain pointers one level at a time
     */
    for (i = fcCacheMaxLevel; --i >= 0;)
	while (next[i] && (char *) object >= ((char *) next[i]->cache + next[i]->size))
	    next = next[i]->next;
    /*
     * Here we are
     */
    s = next[0];
    if (s && (char *) object < ((char *) s->cache + s->size))
	return s;
    return NULL;
}

Behdad Esfahbod's avatar
Behdad Esfahbod committed
656
657
658
659
660
661
662
663
664
665
static FcCacheSkip *
FcCacheFindByAddr (void *object)
{
    FcCacheSkip *ret;
    lock_cache ();
    ret = FcCacheFindByAddrUnlocked (object);
    unlock_cache ();
    return ret;
}

Keith Packard's avatar
Keith Packard committed
666
static void
667
FcCacheRemoveUnlocked (FcCache *cache)
Keith Packard's avatar
Keith Packard committed
668
669
670
671
{
    FcCacheSkip	    **update[FC_CACHE_MAX_LEVEL];
    FcCacheSkip	    *s, **next;
    int		    i;
672
    void            *allocated;
Keith Packard's avatar
Keith Packard committed
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689

    /*
     * Find links along each chain
     */
    next = fcCacheChains;
    for (i = fcCacheMaxLevel; --i >= 0; )
    {
	for (; (s = next[i]); next = s->next)
	    if (s->cache >= cache)
		break;
        update[i] = &next[i];
    }
    s = next[0];
    for (i = 0; i < fcCacheMaxLevel && *update[i] == s; i++)
	*update[i] = s->next[i];
    while (fcCacheMaxLevel > 0 && fcCacheChains[fcCacheMaxLevel - 1] == NULL)
	fcCacheMaxLevel--;
690

691
    if (s)
692
    {
693
694
695
696
697
698
699
700
701
	allocated = s->allocated;
	while (allocated)
	{
	    /* First element in allocated chunk is the free list */
	    next = *(void **)allocated;
	    free (allocated);
	    allocated = next;
	}
	free (s);
702
    }
Keith Packard's avatar
Keith Packard committed
703
704
705
706
707
708
709
}

static FcCache *
FcCacheFindByStat (struct stat *cache_stat)
{
    FcCacheSkip	    *s;

Behdad Esfahbod's avatar
Behdad Esfahbod committed
710
    lock_cache ();
Keith Packard's avatar
Keith Packard committed
711
712
713
714
    for (s = fcCacheChains[0]; s; s = s->next[0])
	if (s->cache_dev == cache_stat->st_dev &&
	    s->cache_ino == cache_stat->st_ino &&
	    s->cache_mtime == cache_stat->st_mtime)
715
	{
Akira TAGOH's avatar
Akira TAGOH committed
716
#ifdef HAVE_STRUCT_STAT_ST_MTIM
717
	    if (s->cache_mtime_nano != cache_stat->st_mtim.tv_nsec)
Akira TAGOH's avatar
Akira TAGOH committed
718
719
		continue;
#endif
720
	    FcRefInc (&s->ref);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
721
	    unlock_cache ();
Keith Packard's avatar
Keith Packard committed
722
	    return s->cache;
723
	}
Behdad Esfahbod's avatar
Behdad Esfahbod committed
724
    unlock_cache ();
Keith Packard's avatar
Keith Packard committed
725
726
727
    return NULL;
}

728
static void
729
FcDirCacheDisposeUnlocked (FcCache *cache)
730
{
731
732
    FcCacheRemoveUnlocked (cache);

733
734
735
736
737
738
739
740
741
742
743
744
745
746
    switch (cache->magic) {
    case FC_CACHE_MAGIC_ALLOC:
	free (cache);
	break;
    case FC_CACHE_MAGIC_MMAP:
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
	munmap (cache, cache->size);
#elif defined(_WIN32)
	UnmapViewOfFile (cache);
#endif
	break;
    }
}

Keith Packard's avatar
Keith Packard committed
747
748
void
FcCacheObjectReference (void *object)
749
{
Keith Packard's avatar
Keith Packard committed
750
    FcCacheSkip *skip = FcCacheFindByAddr (object);
751

Keith Packard's avatar
Keith Packard committed
752
    if (skip)
753
	FcRefInc (&skip->ref);
754
755
}

Keith Packard's avatar
Keith Packard committed
756
757
void
FcCacheObjectDereference (void *object)
758
{
759
    FcCacheSkip	*skip;
760

761
762
    lock_cache ();
    skip = FcCacheFindByAddrUnlocked (object);
Keith Packard's avatar
Keith Packard committed
763
764
    if (skip)
    {
765
	if (FcRefDec (&skip->ref) == 1)
766
	    FcDirCacheDisposeUnlocked (skip->cache);
Keith Packard's avatar
Keith Packard committed
767
    }
768
    unlock_cache ();
769
770
}

771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
void *
FcCacheAllocate (FcCache *cache, size_t len)
{
    FcCacheSkip	*skip;
    void *allocated = NULL;

    lock_cache ();
    skip = FcCacheFindByAddrUnlocked (cache);
    if (skip)
    {
      void *chunk = malloc (sizeof (void *) + len);
      if (chunk)
      {
	  /* First element in allocated chunk is the free list */
	  *(void **)chunk = skip->allocated;
	  skip->allocated = chunk;
	  /* Return the rest */
	  allocated = ((FcChar8 *)chunk) + sizeof (void *);
      }
    }
    unlock_cache ();
    return allocated;
}

795
796
797
798
799
void
FcCacheFini (void)
{
    int		    i;

Keith Packard's avatar
Keith Packard committed
800
    for (i = 0; i < FC_CACHE_MAX_LEVEL; i++)
801
802
803
804
805
806
807
808
809
810
811
812
    {
	if (FcDebug() & FC_DBG_CACHE)
	{
	    if (fcCacheChains[i] != NULL)
	    {
		FcCacheSkip *s = fcCacheChains[i];
		printf("Fontconfig error: not freed %p (dir: %s, refcount %d)\n", s->cache, FcCacheDir(s->cache), s->ref.count);
	    }
	}
	else
	    assert (fcCacheChains[i] == NULL);
    }
Keith Packard's avatar
Keith Packard committed
813
    assert (fcCacheMaxLevel == 0);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
814
815

    free_lock ();
816
817
}

818
static FcBool
819
FcCacheTimeValid (FcConfig *config, FcCache *cache, struct stat *dir_stat)
820
821
{
    struct stat	dir_static;
Akira TAGOH's avatar
Akira TAGOH committed
822
    FcBool fnano = FcTrue;
823
824
825

    if (!dir_stat)
    {
826
827
828
829
830
831
832
833
834
835
	const FcChar8 *sysroot = FcConfigGetSysRoot (config);
	FcChar8 *d;

	if (sysroot)
	    d = FcStrBuildFilename (sysroot, FcCacheDir (cache), NULL);
	else
	    d = FcStrdup (FcCacheDir (cache));
	if (FcStatChecksum (d, &dir_static) < 0)
	{
	    FcStrFree (d);
836
	    return FcFalse;
837
838
	}
	FcStrFree (d);
839
840
	dir_stat = &dir_static;
    }
Akira TAGOH's avatar
Akira TAGOH committed
841
842
843
#ifdef HAVE_STRUCT_STAT_ST_MTIM
    fnano = (cache->checksum_nano == dir_stat->st_mtim.tv_nsec);
    if (FcDebug () & FC_DBG_CACHE)
Akira TAGOH's avatar
Akira TAGOH committed
844
845
	printf ("FcCacheTimeValid dir \"%s\" cache checksum %d.%ld dir checksum %d.%ld\n",
		FcCacheDir (cache), cache->checksum, (long)cache->checksum_nano, (int) dir_stat->st_mtime, dir_stat->st_mtim.tv_nsec);
Akira TAGOH's avatar
Akira TAGOH committed
846
#else
847
    if (FcDebug () & FC_DBG_CACHE)
848
849
	printf ("FcCacheTimeValid dir \"%s\" cache checksum %d dir checksum %d\n",
		FcCacheDir (cache), cache->checksum, (int) dir_stat->st_mtime);
Akira TAGOH's avatar
Akira TAGOH committed
850
851
#endif

852
    return dir_stat->st_mtime == 0 || (cache->checksum == (int) dir_stat->st_mtime && fnano);
853
854
}

855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
static FcBool
FcCacheOffsetsValid (FcCache *cache)
{
    char		*base = (char *)cache;
    char		*end = base + cache->size;
    intptr_t		*dirs;
    FcFontSet		*fs;
    int			 i, j;

    if (cache->dir < 0 || cache->dir > cache->size - sizeof (intptr_t) ||
        memchr (base + cache->dir, '\0', cache->size - cache->dir) == NULL)
        return FcFalse;

    if (cache->dirs < 0 || cache->dirs >= cache->size ||
        cache->dirs_count < 0 ||
        cache->dirs_count > (cache->size - cache->dirs) / sizeof (intptr_t))
        return FcFalse;

    dirs = FcCacheDirs (cache);
    if (dirs)
    {
        for (i = 0; i < cache->dirs_count; i++)
        {
            FcChar8	*dir;

            if (dirs[i] < 0 ||
                dirs[i] > end - (char *) dirs - sizeof (intptr_t))
                return FcFalse;

            dir = FcOffsetToPtr (dirs, dirs[i], FcChar8);
            if (memchr (dir, '\0', end - (char *) dir) == NULL)
                return FcFalse;
         }
    }

    if (cache->set < 0 || cache->set > cache->size - sizeof (FcFontSet))
        return FcFalse;

    fs = FcCacheSet (cache);
    if (fs)
    {
        if (fs->nfont > (end - (char *) fs) / sizeof (FcPattern))
            return FcFalse;

899
        if (!FcIsEncodedOffset(fs->fonts))
900
901
902
903
904
905
906
            return FcFalse;

        for (i = 0; i < fs->nfont; i++)
        {
            FcPattern		*font = FcFontSetFont (fs, i);
            FcPatternElt	*e;
            FcValueListPtr	 l;
Akira TAGOH's avatar
Akira TAGOH committed
907
	    char                *last_offset;
908
909
910
911
912

            if ((char *) font < base ||
                (char *) font > end - sizeof (FcFontSet) ||
                font->elts_offset < 0 ||
                font->elts_offset > end - (char *) font ||
Akira TAGOH's avatar
Akira TAGOH committed
913
914
                font->num > (end - (char *) font - font->elts_offset) / sizeof (FcPatternElt) ||
		!FcRefIsConst (&font->ref))
915
916
917
918
919
920
921
                return FcFalse;


            e = FcPatternElts(font);
            if (e->values != 0 && !FcIsEncodedOffset(e->values))
                return FcFalse;

Akira TAGOH's avatar
Akira TAGOH committed
922
923
924
925
926
927
928
929
930
931
932
	    for (j = 0; j < font->num; j++)
	    {
		last_offset = (char *) font + font->elts_offset;
		for (l = FcPatternEltValues(&e[j]); l; l = FcValueListNext(l))
		{
		    if ((char *) l < last_offset || (char *) l > end - sizeof (*l) ||
			(l->next != NULL && !FcIsEncodedOffset(l->next)))
			return FcFalse;
		    last_offset = (char *) l + 1;
		}
	    }
933
934
935
936
937
938
        }
    }

    return FcTrue;
}

939
940
941
942
/*
 * Map a cache file into memory
 */
static FcCache *
943
FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat)
944
{
945
    FcCache	*cache;
946
    FcBool	allocated = FcFalse;
947

948
949
    if (fd_stat->st_size > INTPTR_MAX ||
        fd_stat->st_size < (int) sizeof (FcCache))
950
	return NULL;
Keith Packard's avatar
Keith Packard committed
951
    cache = FcCacheFindByStat (fd_stat);
952
    if (cache)
953
    {
954
	if (FcCacheTimeValid (config, cache, dir_stat))
955
956
957
958
959
	    return cache;
	FcDirCacheUnload (cache);
	cache = NULL;
    }

960
    /*
961
     * Large cache files are mmap'ed, smaller cache files are read. This
962
     * balances the system cost of mmap against per-process memory usage.
963
     */
964
    if (FcCacheIsMmapSafe (fd) && fd_stat->st_size >= FC_CACHE_MIN_MMAP)
965
    {
Patrick Lam's avatar
Patrick Lam committed
966
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
967
	cache = mmap (0, fd_stat->st_size, PROT_READ, MAP_SHARED, fd, 0);
968
#if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
969
970
	posix_fadvise (fd, 0, fd_stat->st_size, POSIX_FADV_WILLNEED);
#endif
971
972
	if (cache == MAP_FAILED)
	    cache = NULL;
973
#elif defined(_WIN32)
974
	{
975
976
977
978
979
980
981
	    HANDLE hFileMap;

	    cache = NULL;
	    hFileMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL,
					 PAGE_READONLY, 0, 0, NULL);
	    if (hFileMap != NULL)
	    {
982
		cache = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0,
983
				       fd_stat->st_size);
984
985
		CloseHandle (hFileMap);
	    }
986
	}
987
#endif
988
    }
989
990
    if (!cache)
    {
991
	cache = malloc (fd_stat->st_size);
992
	if (!cache)
993
	    return NULL;
Patrick Lam's avatar
Patrick Lam committed
994

995
	if (read (fd, cache, fd_stat->st_size) != fd_stat->st_size)
996
	{
997
	    free (cache);
998
	    return NULL;
999
	}
1000
	allocated = FcTrue;
1001
1002
    }
    if (cache->magic != FC_CACHE_MAGIC_MMAP ||
1003
	cache->version < FC_CACHE_VERSION_NUMBER ||
Behdad Esfahbod's avatar
Behdad Esfahbod committed
1004
	cache->size != (intptr_t) fd_stat->st_size ||
1005
        !FcCacheOffsetsValid (cache) ||
1006
	!FcCacheTimeValid (config, cache, dir_stat) ||
1007
	!FcCacheInsert (cache, fd_stat))
1008
1009
1010
1011
1012
1013
    {
	if (allocated)
	    free (cache);
	else
	{
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
1014
	    munmap (cache, fd_stat->st_size);
1015
1016
#elif defined(_WIN32)
	    UnmapViewOfFile (cache);
1017
#endif
1018
	}
1019
	return NULL;
1020
    }
1021

1022
1023
    /* Mark allocated caches so they're freed rather than unmapped */
    if (allocated)
1024
	cache->magic = FC_CACHE_MAGIC_ALLOC;
1025
	
1026
1027
1028
    return cache;
}

1029
1030
1031
1032
1033
1034
void
FcDirCacheReference (FcCache *cache, int nref)
{
    FcCacheSkip *skip = FcCacheFindByAddr (cache);

    if (skip)
1035
	FcRefAdd (&skip->ref, nref);
1036
1037
}

1038
1039
1040
void
FcDirCacheUnload (FcCache *cache)
{
Keith Packard's avatar
Keith Packard committed
1041
    FcCacheObjectDereference (cache);
1042
1043
1044
}

static FcBool
Akira TAGOH's avatar
Akira TAGOH committed
1045
FcDirCacheMapHelper (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat, struct timeval *latest_cache_mtime, void *closure)
1046
{
1047
    FcCache *cache = FcDirCacheMapFd (config, fd, fd_stat, dir_stat);
1048
    struct timeval cache_mtime, zero_mtime = { 0, 0}, dir_mtime;
1049
1050
1051

    if (!cache)
	return FcFalse;
Akira TAGOH's avatar
Akira TAGOH committed
1052
    cache_mtime.tv_sec = fd_stat->st_mtime;
1053
    dir_mtime.tv_sec = dir_stat->st_mtime;
Akira TAGOH's avatar
Akira TAGOH committed
1054
1055
#ifdef HAVE_STRUCT_STAT_ST_MTIM
    cache_mtime.tv_usec = fd_stat->st_mtim.tv_nsec / 1000;
1056
    dir_mtime.tv_usec = dir_stat->st_mtim.tv_nsec / 1000;
Akira TAGOH's avatar
Akira TAGOH committed
1057
1058
#else
    cache_mtime.tv_usec = 0;
1059
    dir_mtime.tv_usec = 0;
Akira TAGOH's avatar
Akira TAGOH committed
1060
#endif
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
    /* special take care of OSTree */
    if (!timercmp (&zero_mtime, &dir_mtime, !=))
    {
	if (!timercmp (&zero_mtime, &cache_mtime, !=))
	{
	    if (*((FcCache **) closure))
		FcDirCacheUnload (*((FcCache **) closure));
	}
	else if (*((FcCache **) closure) && !timercmp (&zero_mtime, latest_cache_mtime, !=))
	{
	    FcDirCacheUnload (cache);
	    return FcFalse;
	}
	else if (timercmp (latest_cache_mtime, &cache_mtime, <))
	{
	    if (*((FcCache **) closure))
		FcDirCacheUnload (*((FcCache **) closure));
	}
    }
    else if (timercmp (latest_cache_mtime, &cache_mtime, <))
Akira TAGOH's avatar
Akira TAGOH committed
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
    {
	if (*((FcCache **) closure))
	    FcDirCacheUnload (*((FcCache **) closure));
    }
    else
    {
	FcDirCacheUnload (cache);
	return FcFalse;
    }
    latest_cache_mtime->tv_sec = cache_mtime.tv_sec;
    latest_cache_mtime->tv_usec = cache_mtime.tv_usec;
1092
1093
1094
1095
1096
    *((FcCache **) closure) = cache;
    return FcTrue;
}

FcCache *
1097
FcDirCacheLoad (const FcChar8 *dir, FcConfig *config, FcChar8 **cache_file)
1098
{
Keith Packard's avatar
Keith Packard committed
1099
    FcCache *cache = NULL;
1100

1101
1102
1103
    config = FcConfigReference (config);
    if (!config)
	return NULL;
Keith Packard's avatar
Keith Packard committed
1104
    if (!FcDirCacheProcess (config, dir,
1105
			    FcDirCacheMapHelper,
1106
			    &cache, cache_file))
1107
1108
1109
	cache = NULL;

    FcConfigDestroy (config);
1110

Keith Packard's avatar
Keith Packard committed
1111
    return cache;
1112
1113
}

1114
1115
FcCache *
FcDirCacheLoadFile (const FcChar8 *cache_file, struct stat *file_stat)
1116
{