fccache.c 47.6 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
#include <sys/time.h>
Keith Packard's avatar
Keith Packard committed
34
#include <assert.h>
Patrick Lam's avatar
Patrick Lam committed
35
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
36
#  include <unistd.h>
Patrick Lam's avatar
Patrick Lam committed
37
38
#  include <sys/mman.h>
#endif
Akira TAGOH's avatar
Akira TAGOH committed
39
40
#if defined(_WIN32)
#include <sys/locking.h>
41
#else
42
#include <uuid/uuid.h>
43
#endif
Keith Packard's avatar
Keith Packard committed
44

45
46
47
48
#ifndef O_BINARY
#define O_BINARY 0
#endif

49
FcBool
Akira TAGOH's avatar
Akira TAGOH committed
50
51
52
FcDirCacheCreateUUID (FcChar8  *dir,
		      FcBool    force,
		      FcConfig *config)
53
{
54
55
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
    FcChar8 *target;
56
    FcBool ret = FcTrue;
57
#ifndef _WIN32
58
59
    FcChar8 *uuidname;

60
61
62
63
64
65
    if (sysroot)
	target = FcStrBuildFilename (sysroot, dir, NULL);
    else
	target = FcStrdup (dir);
    uuidname = FcStrBuildFilename (target, ".uuid", NULL);

66
    if (!uuidname)
67
68
    {
	FcStrFree (target);
69
	return FcFalse;
70
    }
71
72
73
74
75
76
77

    if (force || access ((const char *) uuidname, F_OK) < 0)
    {
	FcAtomic *atomic;
	int fd;
	uuid_t uuid;
	char out[37];
78
	FcBool (* hash_add) (FcHashTable *, void*, void*);
79
80
	struct stat statb;
	struct timeval times[2];
81

82
	if (FcStat (target, &statb) != 0)
83
84
85
86
	{
	    ret = FcFalse;
	    goto bail1;
	}
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
	atomic = FcAtomicCreate (uuidname);
	if (!atomic)
	{
	    ret = FcFalse;
	    goto bail1;
	}
	if (!FcAtomicLock (atomic))
	{
	    ret = FcFalse;
	    goto bail2;
	}
	fd = FcOpen ((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT, 0644);
	if (fd == -1)
	{
	    ret = FcFalse;
	    goto bail3;
	}
	uuid_generate_random (uuid);
105
106
107
108
	if (force)
	    hash_add = FcHashTableReplace;
	else
	    hash_add = FcHashTableAdd;
109
	if (!hash_add (config->uuid_table, target, uuid))
Akira TAGOH's avatar
Akira TAGOH committed
110
111
	{
	    ret = FcFalse;
112
	    FcAtomicDeleteNew (atomic);
Akira TAGOH's avatar
Akira TAGOH committed
113
	    close (fd);
Akira TAGOH's avatar
Akira TAGOH committed
114
115
	    goto bail3;
	}
116
117
118
119
120
121
122
123
124
125
	uuid_unparse (uuid, out);
	if (FcDebug () & FC_DBG_CACHE)
	    printf ("FcDirCacheCreateUUID %s: %s\n", uuidname, out);
	write (fd, out, strlen (out));
	close (fd);
	FcAtomicReplaceOrig (atomic);
    bail3:
	FcAtomicUnlock (atomic);
    bail2:
	FcAtomicDestroy (atomic);
126
127
128
129
130
131
132
133
134
135
136
137
138

	if (ret)
	{
	    /* revert mtime of the directory */
	    times[0].tv_sec = statb.st_atime;
	    times[1].tv_sec = statb.st_mtime;
#ifdef HAVE_STRUCT_STAT_ST_MTIM
	    times[0].tv_usec = statb.st_atim.tv_nsec / 1000;
	    times[1].tv_usec = statb.st_mtim.tv_nsec / 1000;
#else
	    times[0].tv_usec = 0;
	    times[1].tv_usec = 0;
#endif
139
	    if (utimes ((const  char *) target, times) != 0)
140
	    {
141
		fprintf (stderr, "Unable to revert mtime: %s\n", target);
142
143
	    }
	}
144
    }
145
bail1:
146
    FcStrFree (uuidname);
147
    FcStrFree (target);
148
#endif
149
150
151
152

    return ret;
}

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
FcBool
FcDirCacheDeleteUUID (const FcChar8  *dir,
		      FcConfig       *config)
{
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
    FcChar8 *target;
    FcBool ret = FcTrue;

    if (sysroot)
	target = FcStrBuildFilename (sysroot, dir, ".uuid", NULL);
    else
	target = FcStrBuildFilename (dir, ".uuid", NULL);

    ret = unlink ((char *) target) == 0;
    FcHashTableRemove (config->uuid_table, target);
Tom Anderson's avatar
Tom Anderson committed
168
    FcStrFree(target);
169
170
171
172

    return ret;
}

173
#ifndef _WIN32
174
static void
Akira TAGOH's avatar
Akira TAGOH committed
175
176
FcDirCacheReadUUID (FcChar8  *dir,
		    FcConfig *config)
177
{
Akira TAGOH's avatar
Akira TAGOH committed
178
    void *u;
179
    uuid_t uuid;
180
181
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
    FcChar8 *target;
182

183
184
185
186
187
188
    if (sysroot)
	target = FcStrBuildFilename (sysroot, dir, NULL);
    else
	target = FcStrdup (dir);

    if (!FcHashTableFind (config->uuid_table, target, &u))
189
    {
190
	FcChar8 *uuidname = FcStrBuildFilename (target, ".uuid", NULL);
191
192
193
194
195
	int fd;

	if ((fd = FcOpen ((char *) uuidname, O_RDONLY)) >= 0)
	{
	    char suuid[37];
Akira TAGOH's avatar
Akira TAGOH committed
196
	    ssize_t len;
197
198

	    memset (suuid, 0, sizeof (suuid));
Akira TAGOH's avatar
Akira TAGOH committed
199
200
	    len = read (fd, suuid, 36);
	    if (len != -1)
201
	    {
Akira TAGOH's avatar
Akira TAGOH committed
202
		suuid[len] = 0;
203
204
205
206
207
		memset (uuid, 0, sizeof (uuid));
		if (uuid_parse (suuid, uuid) == 0)
		{
		    if (FcDebug () & FC_DBG_CACHE)
			printf ("FcDirCacheReadUUID %s -> %s\n", uuidname, suuid);
208
		    FcHashTableAdd (config->uuid_table, target, uuid);
209
210
211
212
213
214
215
216
217
218
219
		}
	    }
	    close (fd);
	}
	else
	{
	    if (FcDebug () & FC_DBG_CACHE)
		printf ("FcDirCacheReadUUID Unable to read %s\n", uuidname);
	}
	FcStrFree (uuidname);
    }
Akira TAGOH's avatar
Akira TAGOH committed
220
221
    else
	FcHashUuidFree (u);
222
    FcStrFree (target);
223
}
224
#endif
225

226
227
228
229
230
231
232
struct MD5Context {
        FcChar32 buf[4];
        FcChar32 bits[2];
        unsigned char in[64];
};

static void MD5Init(struct MD5Context *ctx);
233
static void MD5Update(struct MD5Context *ctx, const unsigned char *buf, unsigned len);
234
235
236
static void MD5Final(unsigned char digest[16], struct MD5Context *ctx);
static void MD5Transform(FcChar32 buf[4], FcChar32 in[16]);

237
#define CACHEBASE_LEN (1 + 36 + 1 + sizeof (FC_ARCHITECTURE) + sizeof (FC_CACHE_SUFFIX))
238

239
240
241
static FcBool
FcCacheIsMmapSafe (int fd)
{
242
243
244
245
246
247
248
    enum {
      MMAP_NOT_INITIALIZED = 0,
      MMAP_USE,
      MMAP_DONT_USE,
      MMAP_CHECK_FS,
    } status;
    static void *static_status;
249

250
    status = (intptr_t) fc_atomic_ptr_get (&static_status);
251

252
253
254
255
256
257
258
259
    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;
260
	(void) fc_atomic_ptr_cmpexch (&static_status, NULL, (void *) status);
261
262
    }

263
264
265
266
267
    if (status == MMAP_CHECK_FS)
	return FcIsFsMmapSafe (fd);
    else
	return status == MMAP_USE;

268
269
}

270
271
272
273
274
275
static const char bin2hex[] = { '0', '1', '2', '3',
				'4', '5', '6', '7',
				'8', '9', 'a', 'b',
				'c', 'd', 'e', 'f' };

static FcChar8 *
276
FcDirCacheBasenameMD5 (const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
277
{
278
279
280
281
    unsigned char 	hash[16];
    FcChar8		*hex_hash;
    int			cnt;
    struct MD5Context 	ctx;
282

283
    MD5Init (&ctx);
284
    MD5Update (&ctx, (const unsigned char *)dir, strlen ((const char *) dir));
Patrick Lam's avatar
Patrick Lam committed
285

286
287
288
289
290
    MD5Final (hash, &ctx);

    cache_base[0] = '/';
    hex_hash = cache_base + 1;
    for (cnt = 0; cnt < 16; ++cnt)
291
    {
292
293
294
295
	hex_hash[2*cnt  ] = bin2hex[hash[cnt] >> 4];
	hex_hash[2*cnt+1] = bin2hex[hash[cnt] & 0xf];
    }
    hex_hash[2*cnt] = 0;
296
    strcat ((char *) cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
297

298
299
    return cache_base;
}
Patrick Lam's avatar
Patrick Lam committed
300

301
#ifndef _WIN32
302
static FcChar8 *
Akira TAGOH's avatar
Akira TAGOH committed
303
FcDirCacheBasenameUUID (const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN], FcConfig *config)
304
{
Akira TAGOH's avatar
Akira TAGOH committed
305
    void *u;
Alexander Larsson's avatar
Alexander Larsson committed
306
    FcChar8 *target;
307
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
308

309
    if (sysroot)
Alexander Larsson's avatar
Alexander Larsson committed
310
	target = FcStrBuildFilename (sysroot, dir, NULL);
311
    else
Alexander Larsson's avatar
Alexander Larsson committed
312
	target = FcStrdup (dir);
313
    if (FcHashTableFind (config->uuid_table, target, &u))
314
    {
Akira TAGOH's avatar
Akira TAGOH committed
315
	uuid_unparse (u, (char *) cache_base);
316
	strcat ((char *) cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
Akira TAGOH's avatar
Akira TAGOH committed
317
	FcHashUuidFree (u);
318
	FcStrFree (target);
319
320
	return cache_base;
    }
321
    FcStrFree (target);
322
323
    return NULL;
}
324
#endif
325

326
327
328
329
330
331
332
FcBool
FcDirCacheUnlink (const FcChar8 *dir, FcConfig *config)
{
    FcChar8	*cache_hashed = NULL;
    FcChar8	cache_base[CACHEBASE_LEN];
    FcStrList	*list;
    FcChar8	*cache_dir;
333
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
Patrick Lam's avatar
Patrick Lam committed
334

335
#ifndef _WIN32
Akira TAGOH's avatar
Akira TAGOH committed
336
    if (!FcDirCacheBasenameUUID (dir, cache_base, config))
337
#endif
338
	FcDirCacheBasenameMD5 (dir, cache_base);
Patrick Lam's avatar
Patrick Lam committed
339

340
341
342
343
344
    list = FcStrListCreate (config->cacheDirs);
    if (!list)
        return FcFalse;
	
    while ((cache_dir = FcStrListNext (list)))
Patrick Lam's avatar
Patrick Lam committed
345
    {
346
347
348
349
	if (sysroot)
	    cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
	else
	    cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
350
351
        if (!cache_hashed)
	    break;
352
	(void) unlink ((char *) cache_hashed);
353
	FcDirCacheDeleteUUID (dir, config);
354
	FcStrFree (cache_hashed);
355
    }
356
357
358
359
    FcStrListDone (list);
    /* return FcFalse if something went wrong */
    if (cache_dir)
	return FcFalse;
360
361
362
    return FcTrue;
}

363
static int
364
FcDirCacheOpenFile (const FcChar8 *cache_file, struct stat *file_stat)
365
{
366
    int	fd;
367

368
369
370
371
#ifdef _WIN32
    if (FcStat (cache_file, file_stat) < 0)
        return -1;
#endif
Akira TAGOH's avatar
Akira TAGOH committed
372
    fd = FcOpen((char *) cache_file, O_RDONLY | O_BINARY);
373
374
    if (fd < 0)
	return fd;
375
#ifndef _WIN32
376
    if (fstat (fd, file_stat) < 0)
377
    {
378
379
	close (fd);
	return -1;
380
    }
381
#endif
382
    return fd;
383
384
}

385
/*
386
387
388
 * Look for a cache file for the specified dir. Attempt
 * to use each one we find, stopping when the callback
 * indicates success
389
 */
390
static FcBool
391
FcDirCacheProcess (FcConfig *config, const FcChar8 *dir,
392
		   FcBool (*callback) (FcConfig *config, int fd, struct stat *fd_stat,
393
				       struct stat *dir_stat, void *closure),
394
		   void *closure, FcChar8 **cache_file_ret)
395
{
396
    int		fd = -1;
397
398
    FcChar8	cache_base[CACHEBASE_LEN];
    FcStrList	*list;
399
    FcChar8	*cache_dir, *d;
400
    struct stat file_stat, dir_stat;
401
    FcBool	ret = FcFalse;
402
    const FcChar8 *sysroot = FcConfigGetSysRoot (config);
403

404
405
406
407
408
409
410
    if (sysroot)
	d = FcStrBuildFilename (sysroot, dir, NULL);
    else
	d = FcStrdup (dir);
    if (FcStatChecksum (d, &dir_stat) < 0)
    {
	FcStrFree (d);
411
        return FcFalse;
412
413
    }
    FcStrFree (d);
414

415
#ifndef _WIN32
Akira TAGOH's avatar
Akira TAGOH committed
416
    if (!FcDirCacheBasenameUUID (dir, cache_base, config))
417
#endif
418
	FcDirCacheBasenameMD5 (dir, cache_base);
419

420
421
    list = FcStrListCreate (config->cacheDirs);
    if (!list)
422
        return FcFalse;
423
424
	
    while ((cache_dir = FcStrListNext (list)))
425
    {
426
427
428
429
430
431
        FcChar8	*cache_hashed;

	if (sysroot)
	    cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
	else
	    cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
432
        if (!cache_hashed)
433
	    break;
434
        fd = FcDirCacheOpenFile (cache_hashed, &file_stat);
435
        if (fd >= 0) {
436
	    ret = (*callback) (config, fd, &file_stat, &dir_stat, closure);
437
438
	    close (fd);
	    if (ret)
439
	    {
440
441
442
443
444
		if (cache_file_ret)
		    *cache_file_ret = cache_hashed;
		else
		    FcStrFree (cache_hashed);
		break;
445
446
	    }
	}
447
    	FcStrFree (cache_hashed);
448
449
    }
    FcStrListDone (list);
450

451
    return ret;
452
453
}

Keith Packard's avatar
Keith Packard committed
454
455
456
457
458
459
460
461
462
463
464
465
#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;
466
    FcRef	    ref;
Keith Packard's avatar
Keith Packard committed
467
    intptr_t	    size;
468
    void	   *allocated;
Keith Packard's avatar
Keith Packard committed
469
470
471
    dev_t	    cache_dev;
    ino_t	    cache_ino;
    time_t	    cache_mtime;
Akira TAGOH's avatar
Akira TAGOH committed
472
    long	    cache_mtime_nano;
Keith Packard's avatar
Keith Packard committed
473
474
475
476
477
478
479
480
481
482
    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
483
/* Protected by cache_lock below */
Keith Packard's avatar
Keith Packard committed
484
485
486
static FcCacheSkip	*fcCacheChains[FC_CACHE_MAX_LEVEL];
static int		fcCacheMaxLevel;

487

Behdad Esfahbod's avatar
Behdad Esfahbod committed
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
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;
    }
503
504
505
506
507

    FcMutexLock (lock);
    /* Initialize random state */
    FcRandom ();
    return;
Behdad Esfahbod's avatar
Behdad Esfahbod committed
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
  }
  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
531
532
533
534
535
536
537
538
539
540
/*
 * 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 */
541
    long int	bits = FcRandom () | FcRandom ();
Keith Packard's avatar
Keith Packard committed
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
    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
563
564
    lock_cache ();

Keith Packard's avatar
Keith Packard committed
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
    /*
     * 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;
    }
587

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

    s->cache = cache;
    s->size = cache->size;
594
    s->allocated = NULL;
595
    FcRefInit (&s->ref, 1);
596
597
598
599
600
    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
601
602
603
604
605
#ifdef HAVE_STRUCT_STAT_ST_MTIM
	s->cache_mtime_nano = cache_stat->st_mtim.tv_nsec;
#else
	s->cache_mtime_nano = 0;
#endif
606
607
608
609
610
611
    }
    else
    {
	s->cache_dev = 0;
	s->cache_ino = 0;
	s->cache_mtime = 0;
Akira TAGOH's avatar
Akira TAGOH committed
612
	s->cache_mtime_nano = 0;
613
    }
614

Keith Packard's avatar
Keith Packard committed
615
616
617
618
619
620
621
622
    /*
     * 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
623
624

    unlock_cache ();
Keith Packard's avatar
Keith Packard committed
625
626
627
628
    return FcTrue;
}

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

635
636
637
    if (!object)
	return NULL;

Keith Packard's avatar
Keith Packard committed
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
    /*
     * 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
653
654
655
656
657
658
659
660
661
662
static FcCacheSkip *
FcCacheFindByAddr (void *object)
{
    FcCacheSkip *ret;
    lock_cache ();
    ret = FcCacheFindByAddrUnlocked (object);
    unlock_cache ();
    return ret;
}

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

    /*
     * 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--;
687
688
689
690
691
692
693
694
695

    allocated = s->allocated;
    while (allocated)
    {
	/* First element in allocated chunk is the free list */
	next = *(void **)allocated;
	free (allocated);
	allocated = next;
    }
Keith Packard's avatar
Keith Packard committed
696
697
698
699
700
701
702
703
    free (s);
}

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

Behdad Esfahbod's avatar
Behdad Esfahbod committed
704
    lock_cache ();
Keith Packard's avatar
Keith Packard committed
705
706
707
708
    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)
709
	{
Akira TAGOH's avatar
Akira TAGOH committed
710
#ifdef HAVE_STRUCT_STAT_ST_MTIM
711
	    if (s->cache_mtime_nano != cache_stat->st_mtim.tv_nsec)
Akira TAGOH's avatar
Akira TAGOH committed
712
713
		continue;
#endif
714
	    FcRefInc (&s->ref);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
715
	    unlock_cache ();
Keith Packard's avatar
Keith Packard committed
716
	    return s->cache;
717
	}
Behdad Esfahbod's avatar
Behdad Esfahbod committed
718
    unlock_cache ();
Keith Packard's avatar
Keith Packard committed
719
720
721
    return NULL;
}

722
static void
723
FcDirCacheDisposeUnlocked (FcCache *cache)
724
{
725
726
    FcCacheRemoveUnlocked (cache);

727
728
729
730
731
732
733
734
735
736
737
738
739
740
    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
741
742
void
FcCacheObjectReference (void *object)
743
{
Keith Packard's avatar
Keith Packard committed
744
    FcCacheSkip *skip = FcCacheFindByAddr (object);
745

Keith Packard's avatar
Keith Packard committed
746
    if (skip)
747
	FcRefInc (&skip->ref);
748
749
}

Keith Packard's avatar
Keith Packard committed
750
751
void
FcCacheObjectDereference (void *object)
752
{
753
    FcCacheSkip	*skip;
754

755
756
    lock_cache ();
    skip = FcCacheFindByAddrUnlocked (object);
Keith Packard's avatar
Keith Packard committed
757
758
    if (skip)
    {
759
	if (FcRefDec (&skip->ref) == 1)
760
	    FcDirCacheDisposeUnlocked (skip->cache);
Keith Packard's avatar
Keith Packard committed
761
    }
762
    unlock_cache ();
763
764
}

765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
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;
}

789
790
791
792
793
void
FcCacheFini (void)
{
    int		    i;

Keith Packard's avatar
Keith Packard committed
794
795
796
    for (i = 0; i < FC_CACHE_MAX_LEVEL; i++)
	assert (fcCacheChains[i] == NULL);
    assert (fcCacheMaxLevel == 0);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
797
798

    free_lock ();
799
800
}

801
static FcBool
802
FcCacheTimeValid (FcConfig *config, FcCache *cache, struct stat *dir_stat)
803
804
{
    struct stat	dir_static;
Akira TAGOH's avatar
Akira TAGOH committed
805
    FcBool fnano = FcTrue;
806
807
808

    if (!dir_stat)
    {
809
810
811
812
813
814
815
816
817
818
	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);
819
	    return FcFalse;
820
821
	}
	FcStrFree (d);
822
823
	dir_stat = &dir_static;
    }
Akira TAGOH's avatar
Akira TAGOH committed
824
825
826
#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
827
828
	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
829
#else
830
    if (FcDebug () & FC_DBG_CACHE)
831
832
	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
833
834
835
#endif

    return cache->checksum == (int) dir_stat->st_mtime && fnano;
836
837
}

838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
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
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;

        if (fs->fonts != 0 && !FcIsEncodedOffset(fs->fonts))
            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
890
	    char                *last_offset;
891
892
893
894
895

            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
896
897
                font->num > (end - (char *) font - font->elts_offset) / sizeof (FcPatternElt) ||
		!FcRefIsConst (&font->ref))
898
899
900
901
902
903
904
                return FcFalse;


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

Akira TAGOH's avatar
Akira TAGOH committed
905
906
907
908
909
910
911
912
913
914
915
	    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;
		}
	    }
916
917
918
919
920
921
        }
    }

    return FcTrue;
}

922
923
924
925
/*
 * Map a cache file into memory
 */
static FcCache *
926
FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat)
927
{
928
    FcCache	*cache;
929
    FcBool	allocated = FcFalse;
930

931
932
    if (fd_stat->st_size > INTPTR_MAX ||
        fd_stat->st_size < (int) sizeof (FcCache))
933
	return NULL;
Keith Packard's avatar
Keith Packard committed
934
    cache = FcCacheFindByStat (fd_stat);
935
    if (cache)
936
    {
937
	if (FcCacheTimeValid (config, cache, dir_stat))
938
939
940
941
942
	    return cache;
	FcDirCacheUnload (cache);
	cache = NULL;
    }

943
    /*
944
     * Large cache files are mmap'ed, smaller cache files are read. This
945
     * balances the system cost of mmap against per-process memory usage.
946
     */
947
    if (FcCacheIsMmapSafe (fd) && fd_stat->st_size >= FC_CACHE_MIN_MMAP)
948
    {
Patrick Lam's avatar
Patrick Lam committed
949
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
950
	cache = mmap (0, fd_stat->st_size, PROT_READ, MAP_SHARED, fd, 0);
951
#if (HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
952
953
	posix_fadvise (fd, 0, fd_stat->st_size, POSIX_FADV_WILLNEED);
#endif
954
955
	if (cache == MAP_FAILED)
	    cache = NULL;
956
#elif defined(_WIN32)
957
	{
958
959
960
961
962
963
964
	    HANDLE hFileMap;

	    cache = NULL;
	    hFileMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL,
					 PAGE_READONLY, 0, 0, NULL);
	    if (hFileMap != NULL)
	    {
965
		cache = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0,
966
				       fd_stat->st_size);
967
968
		CloseHandle (hFileMap);
	    }
969
	}
970
#endif
971
    }
972
973
    if (!cache)
    {
974
	cache = malloc (fd_stat->st_size);
975
	if (!cache)
976
	    return NULL;
Patrick Lam's avatar
Patrick Lam committed
977

978
	if (read (fd, cache, fd_stat->st_size) != fd_stat->st_size)
979
	{
980
	    free (cache);
981
	    return NULL;
982
	}
983
	allocated = FcTrue;
984
985
    }
    if (cache->magic != FC_CACHE_MAGIC_MMAP ||
986
	cache->version < FC_CACHE_VERSION_NUMBER ||
Behdad Esfahbod's avatar
Behdad Esfahbod committed
987
	cache->size != (intptr_t) fd_stat->st_size ||
988
        !FcCacheOffsetsValid (cache) ||
989
	!FcCacheTimeValid (config, cache, dir_stat) ||
990
	!FcCacheInsert (cache, fd_stat))
991
992
993
994
995
996
    {
	if (allocated)
	    free (cache);
	else
	{
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
997
	    munmap (cache, fd_stat->st_size);
998
999
#elif defined(_WIN32)
	    UnmapViewOfFile (cache);
1000
#endif
1001
	}
1002
	return NULL;
1003
    }
1004

1005
1006
    /* Mark allocated caches so they're freed rather than unmapped */
    if (allocated)
1007
	cache->magic = FC_CACHE_MAGIC_ALLOC;
1008
	
1009
1010
1011
    return cache;
}

1012
1013
1014
1015
1016
1017
void
FcDirCacheReference (FcCache *cache, int nref)
{
    FcCacheSkip *skip = FcCacheFindByAddr (cache);

    if (skip)
1018
	FcRefAdd (&skip->ref, nref);
1019
1020
}

1021
1022
1023
void
FcDirCacheUnload (FcCache *cache)
{
Keith Packard's avatar
Keith Packard committed
1024
    FcCacheObjectDereference (cache);
1025
1026
1027
}

static FcBool
1028
FcDirCacheMapHelper (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat, void *closure)
1029
{
1030
    FcCache *cache = FcDirCacheMapFd (config, fd, fd_stat, dir_stat);
1031
1032
1033

    if (!cache)
	return FcFalse;
1034
1035
1036
1037
1038
    *((FcCache **) closure) = cache;
    return FcTrue;
}

FcCache *
1039
FcDirCacheLoad (const FcChar8 *dir, FcConfig *config, FcChar8 **cache_file)
1040
{
Keith Packard's avatar
Keith Packard committed
1041
    FcCache *cache = NULL;
1042

1043
#ifndef _WIN32
Akira TAGOH's avatar
Akira TAGOH committed
1044
    FcDirCacheReadUUID ((FcChar8 *) dir, config);
1045
#endif
Keith Packard's avatar
Keith Packard committed
1046
    if (!FcDirCacheProcess (config, dir,
1047
			    FcDirCacheMapHelper,
1048
			    &cache, cache_file))
Keith Packard's avatar
Keith Packard committed
1049
	return NULL;
1050

Keith Packard's avatar
Keith Packard committed
1051
    return cache;
1052
1053
}

1054
1055
FcCache *
FcDirCacheLoadFile (const FcChar8 *cache_file, struct stat *file_stat)
1056
{