fccache.c 43.5 KB
Newer Older
Keith Packard's avatar
Keith Packard committed
1
/*
Keith Packard's avatar
Keith Packard committed
2
 * $RCSId: xc/lib/fontconfig/src/fccache.c,v 1.12 2002/08/22 07:36:44 keithp Exp $
Keith Packard's avatar
Keith Packard committed
3
 *
4
 * Copyright © 2000 Keith Packard
5
 * Copyright © 2005 Patrick Lam
Keith Packard's avatar
Keith Packard committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 *
 * 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
 * documentation, and that the name of Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * 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.
 */

26
#include <fcntl.h>
27
#include <dirent.h>
28
#include <string.h>
29
30
#include <sys/mman.h>
#include <sys/utsname.h>
31
32
#include <sys/types.h>
#include <unistd.h>
Keith Packard's avatar
Keith Packard committed
33
#include "fcint.h"
34
#include <unistd.h>
Keith Packard's avatar
Keith Packard committed
35

36
#define ENDIAN_TEST 0x12345678
37
#define MACHINE_SIGNATURE_SIZE 9 + 5*20 + 1
38

39
40
41
42
#ifndef O_BINARY
#define O_BINARY 0
#endif

43
static int
44
FcDirCacheOpen (const FcChar8 * dir);
45
46
47
48

static char *
FcDirCacheHashName (char * cache_file, int collisions);

Patrick Lam's avatar
Patrick Lam committed
49
static off_t
Patrick Lam's avatar
Patrick Lam committed
50
FcCacheSkipToArch (int fd, const char * arch);
51

Patrick Lam's avatar
Patrick Lam committed
52
static FcBool 
53
FcCacheCopyOld (int fd, int fd_orig, off_t start);
54

Patrick Lam's avatar
Patrick Lam committed
55
56
static void *
FcDirCacheProduce (FcFontSet *set, FcCache * metadata);
57
58

static FcBool
59
FcDirCacheConsume (int fd, const char * dir, FcFontSet *set, FcConfig *config);
60

Patrick Lam's avatar
Patrick Lam committed
61
62
static int
FcCacheNextOffset(off_t w);
63

Patrick Lam's avatar
Patrick Lam committed
64
65
static char *
FcCacheMachineSignature (void);
66
67

static FcBool
Patrick Lam's avatar
Patrick Lam committed
68
FcCacheHaveBank (int bank);
69

70
71
72
static void
FcCacheAddBankDir (int bank, const char * dir);

73
74
75
76
77
78
79
80
81
82
83
struct MD5Context {
        FcChar32 buf[4];
        FcChar32 bits[2];
        unsigned char in[64];
};

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

Patrick Lam's avatar
Patrick Lam committed
84
#define FC_DBG_CACHE_REF    1024
85

86
87
static char *
FcCacheReadString (int fd, char *dest, int len)
Keith Packard's avatar
Keith Packard committed
88
{
89
90
    int    size;
    int    slen;
Keith Packard's avatar
Keith Packard committed
91
92

    if (len == 0)
93
	return 0;
94
95
96
97

    size = read (fd, dest, len-1);

    if (size > 0)
Keith Packard's avatar
Keith Packard committed
98
    {
99
100
101
102
103
	dest[size] = '\0';
	slen = strlen (dest);

	lseek (fd, slen - size + 1, SEEK_CUR);
	return slen < len ? dest : 0;
Keith Packard's avatar
Keith Packard committed
104
    }
105

106
    return 0;
Keith Packard's avatar
Keith Packard committed
107
108
}

109
110
111
static void
FcCacheSkipString (int fd)
{
112
113
114
    char buf[256];
    int  size;
    int  slen;
115

116
    while ( (size = read (fd, buf, sizeof (buf)-1)) > 0) 
117
    {
118
119
120
121
122
123
124
        buf [size] = '\0';
        slen = strlen (buf);
        if (slen < size) 
        {
            lseek (fd, slen - size + 1, SEEK_CUR);
            return;
        }
125
126
127
    }
}

Keith Packard's avatar
Keith Packard committed
128
static FcBool
129
FcCacheWriteString (int fd, const char *chars)
130
{
131
    if (write (fd, chars, strlen(chars)+1) != strlen(chars)+1)
132
133
134
135
	return FcFalse;
    return FcTrue;
}

Patrick Lam's avatar
Patrick Lam committed
136
137
static void
FcGlobalCacheDirDestroy (FcGlobalCacheDir *d)
138
{
139
    FcStrSetDestroy (d->subdirs);
140
141
142
143
    FcMemFree (FC_MEM_STRING, strlen (d->name)+1);
    free (d->name);
    FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheDir));
    free (d);
144
145
}

Patrick Lam's avatar
Patrick Lam committed
146
147
FcGlobalCache *
FcGlobalCacheCreate (void)
148
{
Patrick Lam's avatar
Patrick Lam committed
149
    FcGlobalCache   *cache;
150

Patrick Lam's avatar
Patrick Lam committed
151
152
153
154
155
156
157
158
    cache = malloc (sizeof (FcGlobalCache));
    if (!cache)
	return 0;
    FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCache));
    cache->dirs = 0;
    cache->updated = FcFalse;
    cache->fd = -1;
    return cache;
159
160
}

Patrick Lam's avatar
Patrick Lam committed
161
162
void
FcGlobalCacheDestroy (FcGlobalCache *cache)
163
{
Patrick Lam's avatar
Patrick Lam committed
164
    FcGlobalCacheDir	*d, *next;
165

Patrick Lam's avatar
Patrick Lam committed
166
    for (d = cache->dirs; d; d = next)
167
    {
Patrick Lam's avatar
Patrick Lam committed
168
169
	next = d->next;
	FcGlobalCacheDirDestroy (d);
170
    }
Patrick Lam's avatar
Patrick Lam committed
171
172
    FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCache));
    free (cache);
173
174
175
}

void
Patrick Lam's avatar
Patrick Lam committed
176
FcGlobalCacheLoad (FcGlobalCache    *cache,
177
                   FcStrSet	    *staleDirs,
178
179
		   const FcChar8    *cache_file,
		   FcConfig	    *config)
180
{
181
    char		name_buf[FC_MAX_FILE_LEN];
Patrick Lam's avatar
Patrick Lam committed
182
    FcGlobalCacheDir	*d, *next;
183
    FcFileTime		config_time = FcConfigModifiedTime (config);
Patrick Lam's avatar
Patrick Lam committed
184
185
186
    char 		* current_arch_machine_name;
    char 		candidate_arch_machine_name[MACHINE_SIGNATURE_SIZE + 9];
    off_t		current_arch_start;
187

188
    struct stat 	cache_stat, dir_stat;
189
    char 		subdirName[FC_MAX_FILE_LEN + 1 + 12 + 1];
190
191
192
193

    if (stat ((char *) cache_file, &cache_stat) < 0)
        return;

194
    cache->fd = open ((char *) cache_file, O_RDONLY | O_BINARY);
Patrick Lam's avatar
Patrick Lam committed
195
196
    if (cache->fd == -1)
	return;
197

Patrick Lam's avatar
Patrick Lam committed
198
    cache->updated = FcFalse;
199

200
    if (!FcCacheReadString (cache->fd, name_buf, sizeof (name_buf)))
Patrick Lam's avatar
Patrick Lam committed
201
        goto bail_and_destroy;
Patrick Lam's avatar
Patrick Lam committed
202
    if (strcmp (name_buf, FC_GLOBAL_MAGIC_COOKIE) != 0)
Patrick Lam's avatar
Patrick Lam committed
203
        goto bail_and_destroy;
Patrick Lam's avatar
Patrick Lam committed
204

Patrick Lam's avatar
Patrick Lam committed
205
206
    current_arch_machine_name = FcCacheMachineSignature ();
    current_arch_start = FcCacheSkipToArch(cache->fd, 
Patrick Lam's avatar
Patrick Lam committed
207
					   current_arch_machine_name);
Patrick Lam's avatar
Patrick Lam committed
208
    if (current_arch_start < 0)
209
        goto bail_and_destroy;
210

Patrick Lam's avatar
Patrick Lam committed
211
    lseek (cache->fd, current_arch_start, SEEK_SET);
212
213
214
    if (!FcCacheReadString (cache->fd, candidate_arch_machine_name, 
			    sizeof (candidate_arch_machine_name)))
	goto bail_and_destroy;
215
    if (strlen(candidate_arch_machine_name) == 0)
216
	goto bail_and_destroy;
217

Patrick Lam's avatar
Patrick Lam committed
218
    while (1) 
219
    {
220
221
	off_t targ;

222
223
	if (!FcCacheReadString (cache->fd, name_buf, sizeof (name_buf)) || 
	    !strlen(name_buf))
224
225
	    break;

226
227
	/* Directory must be older than the global cache file; also
	   cache must be newer than the config file. */
228
        if (stat ((char *) name_buf, &dir_stat) < 0 || 
229
            dir_stat.st_mtime > cache_stat.st_mtime ||
230
	    (config_time.set && cache_stat.st_mtime < config_time.time))
231
232
        {
            FcCache md;
233
	    off_t off;
234

235
	    FcStrSetAdd (staleDirs, FcStrCopy ((FcChar8 *)name_buf));
236
237
238
239
240
241
242

	    /* skip subdirs */
	    while (FcCacheReadString (cache->fd, subdirName, 
				      sizeof (subdirName)) &&
		   strlen (subdirName))
		;

243
244
245
246
247
248
	    if (read (cache->fd, &md, sizeof (FcCache)) != sizeof(FcCache)) 
	    {
		perror ("read metadata");
		goto bail1;
	    }
	    off = FcCacheNextOffset (lseek(cache->fd, 0, SEEK_CUR)) + md.count;
249
250
	    if (lseek (cache->fd, off, SEEK_SET) != off) 
	    {
251
252
253
254
		perror ("lseek");
		goto bail1;
	    }
	    continue;
255
256
        }

Patrick Lam's avatar
Patrick Lam committed
257
258
259
	d = malloc (sizeof (FcGlobalCacheDir));
	if (!d)
	    goto bail1;
260

Patrick Lam's avatar
Patrick Lam committed
261
262
	d->next = cache->dirs;
	cache->dirs = d;
263

264
	d->name = (char *)FcStrCopy ((FcChar8 *)name_buf);
Patrick Lam's avatar
Patrick Lam committed
265
	d->ent = 0;
266
	d->state = FcGCDirFileRead;
267
268

	d->subdirs = FcStrSetCreate();
269
270
271
272
273
274
	do
	{
	    if (!FcCacheReadString (cache->fd, subdirName, 
				    sizeof (subdirName)) ||
		!strlen (subdirName))
		break;
275
	    FcStrSetAdd (d->subdirs, (FcChar8 *)subdirName);
276
	} while (1);
277

278
	d->offset = lseek (cache->fd, 0, SEEK_CUR);
279
280
281
282
283
	if (read (cache->fd, &d->metadata, sizeof (FcCache)) != sizeof (FcCache))
	    goto bail1;
	targ = FcCacheNextOffset (lseek(cache->fd, 0, SEEK_CUR)) + d->metadata.count;
	if (lseek (cache->fd, targ, SEEK_SET) != targ)
	    goto bail1;
284
    }
Patrick Lam's avatar
Patrick Lam committed
285
    return;
286

Patrick Lam's avatar
Patrick Lam committed
287
288
 bail1:
    for (d = cache->dirs; d; d = next)
289
    {
Patrick Lam's avatar
Patrick Lam committed
290
291
	next = d->next;
	free (d);
292
    }
Patrick Lam's avatar
Patrick Lam committed
293
    cache->dirs = 0;
294
295
296
297
298
299

    close (cache->fd);
    cache->fd = -1;
    return;

 bail_and_destroy:
Patrick Lam's avatar
Patrick Lam committed
300
301
    close (cache->fd);
    cache->fd = -1;
302
303
304
305

    if (stat ((char *) cache_file, &cache_stat) == 0)
        unlink ((char *)cache_file);

Patrick Lam's avatar
Patrick Lam committed
306
    return;
307

308
309
310
}

FcBool
311
FcGlobalCacheReadDir (FcFontSet *set, FcStrSet *dirs, FcGlobalCache * cache, const char *dir, FcConfig *config)
312
{
313
314
    FcGlobalCacheDir 	*d;
    int			i;
315

Patrick Lam's avatar
Patrick Lam committed
316
    if (cache->fd == -1)
317
318
	return FcFalse;

319
320
321
    if (!(dir = (char *)FcConfigNormalizeFontDir (config, (FcChar8 *)dir)))
	return FcFalse; /* non-existing directory */

Patrick Lam's avatar
Patrick Lam committed
322
    for (d = cache->dirs; d; d = d->next)
323
    {
324
	if (strcmp (d->name, dir) == 0)
325
	{
326
	    if (d->state == FcGCDirDisabled)
Patrick Lam's avatar
Patrick Lam committed
327
		return FcFalse;
328

329
330
331
332
333
334
	    if (d->state == FcGCDirFileRead) 
	    {
		lseek (cache->fd, d->offset, SEEK_SET);
		if (!FcDirCacheConsume (cache->fd, d->name, set, config))
		    return FcFalse;

335
336
337
		for (i = 0; i < d->subdirs->num; i++)
		    FcStrSetAdd (dirs, (FcChar8 *)d->subdirs->strs[i]);

338
339
340
		d->state = FcGCDirConsumed;
	    }
	    return FcTrue;
341
342
343
	}
    }

344
    return FcFalse;
345
346
}

347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
static FcGlobalCacheDir *
FcGlobalCacheDirFind (FcGlobalCache *cache, const char *name)
{
    FcGlobalCacheDir * d;

    if (!cache || !name)
	return NULL;

    for (d = cache->dirs; d; d = d->next)
	if (strcmp((const char *)d->name, (const char *)name) == 0)
	    return d;

    return NULL;
 }

Patrick Lam's avatar
Patrick Lam committed
362
363
FcBool
FcGlobalCacheUpdate (FcGlobalCache  *cache,
364
		     FcStrSet	    *dirs,
365
		     const char     *orig_name,
366
367
		     FcFontSet	    *set,
		     FcConfig	    *config)
368
{
369
370
    FcGlobalCacheDir    *d;
    int		       	i;
371
    const char *name;
372

373
374
    name = (char *)FcConfigNormalizeFontDir (config, (FcChar8 *)orig_name);
    if (!name) 
375
    {
376
377
	fprintf(stderr, "Invalid directory name %s\n", orig_name);
	return FcFalse;
Keith Packard's avatar
Keith Packard committed
378
379
    }

380
381
    d = FcGlobalCacheDirFind (cache, name);

Patrick Lam's avatar
Patrick Lam committed
382
    if (!d)
Keith Packard's avatar
Keith Packard committed
383
    {
Patrick Lam's avatar
Patrick Lam committed
384
385
386
387
388
	d = malloc (sizeof (FcGlobalCacheDir));
	if (!d)
	    return FcFalse;
	d->next = cache->dirs;
	cache->dirs = d;
389
390
391
392
393
    } else {
	/* free old resources */
	FcStrFree ((FcChar8 *)d->name);
	free (d->ent);
	FcStrSetDestroy (d->subdirs);
Keith Packard's avatar
Keith Packard committed
394
395
    }

Patrick Lam's avatar
Patrick Lam committed
396
    cache->updated = FcTrue;
Keith Packard's avatar
Keith Packard committed
397

398
    d->name = (char *)FcStrCopy ((FcChar8 *)name);
Patrick Lam's avatar
Patrick Lam committed
399
400
    d->ent = FcDirCacheProduce (set, &d->metadata);
    d->offset = 0;
401
    d->subdirs = FcStrSetCreate();
402
    d->state = FcGCDirUpdated;
403
404
    for (i = 0; i < dirs->num; i++)
	FcStrSetAdd (d->subdirs, dirs->strs[i]);
Patrick Lam's avatar
Patrick Lam committed
405
    return FcTrue;
Keith Packard's avatar
Keith Packard committed
406
407
408
}

FcBool
409
FcGlobalCacheSave (FcGlobalCache    *cache,
410
411
		   const FcChar8    *cache_file,
		   FcConfig	    *config)
Keith Packard's avatar
Keith Packard committed
412
{
413
    int			fd, fd_orig, i;
414
415
    FcGlobalCacheDir	*dir;
    FcAtomic		*atomic;
Patrick Lam's avatar
Patrick Lam committed
416
417
    off_t 		current_arch_start = 0, truncate_to;
    char 		* current_arch_machine_name, * header;
Keith Packard's avatar
Keith Packard committed
418

Patrick Lam's avatar
Patrick Lam committed
419
    if (!cache->updated)
Keith Packard's avatar
Keith Packard committed
420
421
	return FcTrue;
    
Tor Lillqvist's avatar
Tor Lillqvist committed
422
#if defined (HAVE_GETUID) && defined (HAVE_GETEUID)
Keith Packard's avatar
Keith Packard committed
423
424
425
    /* Set-UID programs can't safely update the cache */
    if (getuid () != geteuid ())
	return FcFalse;
Tor Lillqvist's avatar
Tor Lillqvist committed
426
#endif
Keith Packard's avatar
Keith Packard committed
427
    
428
429
    atomic = FcAtomicCreate (cache_file);
    if (!atomic)
430
431
	return FcFalse;

432
    if (!FcAtomicLock (atomic))
Keith Packard's avatar
Keith Packard committed
433
	goto bail1;
434
    fd = open ((char *) FcAtomicNewFile(atomic), O_RDWR | O_CREAT | O_BINARY, 
Patrick Lam's avatar
Patrick Lam committed
435
436
	       S_IRUSR | S_IWUSR);
    if (fd == -1)
Keith Packard's avatar
Keith Packard committed
437
	goto bail2;
438
    FcCacheWriteString (fd, FC_GLOBAL_MAGIC_COOKIE);
Keith Packard's avatar
Keith Packard committed
439

440
    fd_orig = open ((char *) FcAtomicOrigFile(atomic), O_RDONLY | O_BINARY);
441

Patrick Lam's avatar
Patrick Lam committed
442
    current_arch_machine_name = FcCacheMachineSignature ();
443
444
445
446
    if (fd_orig == -1)
        current_arch_start = 0;
    else
        current_arch_start = FcCacheSkipToArch (fd_orig, 
Patrick Lam's avatar
Patrick Lam committed
447
                                                current_arch_machine_name);
448

Patrick Lam's avatar
Patrick Lam committed
449
    if (current_arch_start < 0)
450
451
452
453
454
455
    {
	off_t i = lseek(fd_orig, 0, SEEK_END);
	if (i < strlen (FC_GLOBAL_MAGIC_COOKIE)+1)
	    i = strlen (FC_GLOBAL_MAGIC_COOKIE)+1;
	current_arch_start = FcCacheNextOffset (i);
    }
Patrick Lam's avatar
Patrick Lam committed
456

457
458
459
    if (!FcCacheCopyOld(fd, fd_orig, current_arch_start))
	goto bail3;

Patrick Lam's avatar
Patrick Lam committed
460
461
    current_arch_start = lseek(fd, 0, SEEK_CUR);
    if (ftruncate (fd, current_arch_start) == -1)
462
	goto bail3;
Patrick Lam's avatar
Patrick Lam committed
463

464
465
    header = malloc (10 + strlen (current_arch_machine_name));
    if (!header)
466
	goto bail3;
467

468
    truncate_to = current_arch_start + strlen(current_arch_machine_name) + 11;
Patrick Lam's avatar
Patrick Lam committed
469
    for (dir = cache->dirs; dir; dir = dir->next)
Keith Packard's avatar
Keith Packard committed
470
    {
471
472
	if (dir->state == FcGCDirDisabled)
	    continue;
Patrick Lam's avatar
Patrick Lam committed
473
474
	truncate_to += strlen(dir->name) + 1;
	truncate_to += sizeof (FcCache);
475
	truncate_to = FcCacheNextOffset (truncate_to);
Patrick Lam's avatar
Patrick Lam committed
476
	truncate_to += dir->metadata.count;
477
478
479
480

	for (i = 0; i < dir->subdirs->size; i++)
	    truncate_to += strlen((char *)dir->subdirs->strs[i]) + 1;
	truncate_to ++;
Patrick Lam's avatar
Patrick Lam committed
481
482
483
    }
    truncate_to -= current_arch_start;

484
485
486
    sprintf (header, "%8x ", (int)truncate_to);
    strcat (header, current_arch_machine_name);
    if (!FcCacheWriteString (fd, header))
487
	goto bail4;
488

Patrick Lam's avatar
Patrick Lam committed
489
490
    for (dir = cache->dirs; dir; dir = dir->next)
    {
491
492
	const char * d;
	off_t off;
493

494
495
496
497
498
	if (!dir->name || dir->state == FcGCDirDisabled)
	    continue;
	d = (const char *)FcConfigNormalizeFontDir (config, (const FcChar8 *)dir->name);
	if (!d) 
	    continue;
499
	    
500
501
502
	if (dir->metadata.count && !dir->ent) 
	{
	    if (dir->state == FcGCDirUpdated || fd_orig < 0) 
503
	    {
504
		fprintf(stderr, "Invalid metadata entry for %s, skipping...\n", d);
505
506
		continue;
	    }
507
508
509
	    /* copy the old content */
	    dir->ent = malloc (dir->metadata.count);
	    if (!dir->ent) 
510
	    {
511
		perror("malloc error");
512
513
		continue;
	    }
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
	    off = FcCacheNextOffset (dir->offset + sizeof(FcCache));
	    if (lseek (fd_orig, off, SEEK_SET) != off) 
	    {
		perror("lseek");
		free(dir->ent);
		continue;
	    }
	    if (read (fd_orig, dir->ent, dir->metadata.count)
		!= dir->metadata.count) 
	    {
		perror("read");
		free(dir->ent);
		continue;
	    }
	}
	
	FcCacheWriteString (fd, d);

	for (i = 0; i < dir->subdirs->size; i++)
	    FcCacheWriteString (fd, (char *)dir->subdirs->strs[i]);
	FcCacheWriteString (fd, "");
	
	if (write (fd, &dir->metadata, sizeof(FcCache)) != sizeof(FcCache))
	{
	    perror ("write metadata");
	    free (dir->ent);
	    continue;
	}
	off = FcCacheNextOffset (lseek(fd, 0, SEEK_CUR));
	if (lseek (fd, off, SEEK_SET) != off)
	{
	    perror ("lseek");
	    free (dir->ent);
	    continue;
	}
	if (dir->metadata.count)
	{
551
552
553
554
555
556
557
	    if (write (fd, dir->ent, dir->metadata.count) != dir->metadata.count)
	    {
		perror ("write dirent");
		free (dir->ent);
		continue;
	    }
	}
558
	free (dir->ent);
Keith Packard's avatar
Keith Packard committed
559
    }
Patrick Lam's avatar
Patrick Lam committed
560
    FcCacheWriteString (fd, "");
Keith Packard's avatar
Keith Packard committed
561

Patrick Lam's avatar
Patrick Lam committed
562
    if (close (fd) == -1)
563
	goto bail25;
564
565
566

    close (fd_orig);
    fd_orig = -1;
Keith Packard's avatar
Keith Packard committed
567
    
568
    if (!FcAtomicReplaceOrig (atomic))
569
	goto bail25;
Keith Packard's avatar
Keith Packard committed
570
    
571
572
573
    FcAtomicUnlock (atomic);
    FcAtomicDestroy (atomic);

Keith Packard's avatar
Keith Packard committed
574
575
576
    cache->updated = FcFalse;
    return FcTrue;

577
578
579
580
581
582
583
584
 bail4:
    free (header);
 bail3:
    if (fd_orig != -1)
        close (fd_orig);

    close (fd);
 bail25:
585
    FcAtomicDeleteNew (atomic);
586
 bail2:
587
    FcAtomicUnlock (atomic);
588
 bail1:
589
    FcAtomicDestroy (atomic);
Keith Packard's avatar
Keith Packard committed
590
591
592
    return FcFalse;
}

593
/* 
Patrick Lam's avatar
Patrick Lam committed
594
595
 * Find the next presumably-mmapable offset after the supplied file
 * position.
596
 */
597
598
static int
FcCacheNextOffset(off_t w)
599
{
600
601
602
603
    static long pagesize = -1;
    if (pagesize == -1)
	pagesize = sysconf(_SC_PAGESIZE);
    if (w % pagesize == 0) 
604
605
	return w;
    else
606
	return ((w / pagesize)+1)*pagesize;
607
608
}

609
610
611
/* return the address of the segment for the provided arch,
 * or -1 if arch not found */
static off_t
Patrick Lam's avatar
Patrick Lam committed
612
FcCacheSkipToArch (int fd, const char * arch)
613
{
614
615
    char candidate_arch_machine_name_count[MACHINE_SIGNATURE_SIZE + 9];
    char * candidate_arch;
616
617
    off_t current_arch_start = 0;

618
    lseek (fd, 0, SEEK_SET);
Patrick Lam's avatar
Patrick Lam committed
619
    FcCacheSkipString (fd);
620
621
    current_arch_start = lseek (fd, 0, SEEK_CUR);

622
623
624
625
626
    /* skip arches that are not the current arch */
    while (1)
    {
	long bs;

627
628
629
	if (lseek (fd, current_arch_start, SEEK_SET) != current_arch_start)
            return -1;

Patrick Lam's avatar
Patrick Lam committed
630
	if (FcCacheReadString (fd, candidate_arch_machine_name_count, 
631
				sizeof (candidate_arch_machine_name_count)) == 0)
632
            return -1;
633
634
635
	if (!strlen(candidate_arch_machine_name_count))
	    return -1;
	bs = strtol(candidate_arch_machine_name_count, &candidate_arch, 16);
636

637
	// count = 0 should probably be distinguished from the !bs condition
638
639
640
	if (!bs || bs < strlen (candidate_arch_machine_name_count))
	    return -1;

641
	candidate_arch++; /* skip leading space */
642

643
	if (strcmp (candidate_arch, arch)==0)
644
	    return current_arch_start;
645
646
647
	current_arch_start += bs;
    }

648
    return -1;
649
650
651
652
653
654
}

/* Cuts out the segment at the file pointer (moves everything else
 * down to cover it), and leaves the file pointer at the end of the
 * file. */
static FcBool 
655
FcCacheCopyOld (int fd, int fd_orig, off_t start)
656
{
Patrick Lam's avatar
Patrick Lam committed
657
    char * buf = malloc (8192);
658
    char candidate_arch_machine_name[MACHINE_SIGNATURE_SIZE + 9];
659
    long bs;
660
    int c, bytes_skipped;
661
    off_t loc;
662
663
664
665

    if (!buf)
	return FcFalse;

666
667
    loc = 0;
    lseek (fd, 0, SEEK_SET); lseek (fd_orig, 0, SEEK_SET);
668
    FcCacheSkipString (fd); FcCacheSkipString (fd_orig);
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
    do
    {
        int b = 8192;
        if (loc + b > start)
            b = start - loc;

	if ((c = read (fd_orig, buf, b)) <= 0)
	    break;
	if (write (fd, buf, c) < 0)
	    goto bail;

        loc += c;
    }
    while (c > 0);

684
    lseek (fd, start, SEEK_SET);
Patrick Lam's avatar
Patrick Lam committed
685
    if (FcCacheReadString (fd, candidate_arch_machine_name, 
686
687
			   sizeof (candidate_arch_machine_name)) == 0)
	goto done;
688
    if (!strlen(candidate_arch_machine_name))
689
690
	goto done;

691
    bs = strtol(candidate_arch_machine_name, 0, 16);
692
693
694
695
696
697
698
    if (bs == 0)
	goto done;

    bytes_skipped = 0;
    do
    {
	lseek (fd, start+bs+bytes_skipped, SEEK_SET);
Patrick Lam's avatar
Patrick Lam committed
699
	if ((c = read (fd, buf, 8192)) <= 0)
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
	    break;
	lseek (fd, start+bytes_skipped, SEEK_SET);
	if (write (fd, buf, c) < 0)
	    goto bail;
	bytes_skipped += c;
    }
    while (c > 0);
    lseek (fd, start+bytes_skipped, SEEK_SET);

 done:
    free (buf);
    return FcTrue;

 bail:
    free (buf);
    return FcFalse;
}

718
/* Does not check that the cache has the appropriate arch section. */
719
720
721
/* Also, this can be fooled if the original location has a stale
 * cache, and the hashed location has an up-to-date cache.  Oh well,
 * sucks to be you in that case! */
722
723
724
725
FcBool
FcDirCacheValid (const FcChar8 *dir)
{
    struct stat file_stat, dir_stat;
726
    int 	fd;
727
728
729

    if (stat ((char *) dir, &dir_stat) < 0)
        return FcFalse;
730

731
    fd = FcDirCacheOpen (dir);
732
733

    if (fd < 0)
734
	return FcFalse;
735
    if (fstat (fd, &file_stat) < 0)
736
	goto bail;
737
738
739

    close (fd);

740
741
742
743
    /*
     * If the directory has been modified more recently than
     * the cache file, the cache is not valid
     */
744
    if (dir_stat.st_mtime > file_stat.st_mtime)
745
        return FcFalse;
746

747
    return FcTrue;
748
749

 bail:
750
    close (fd);
751
    return FcFalse;
752
753
754
755
756
757
758
759
760
761
}

/* Assumes that the cache file in 'dir' exists.
 * Checks that the cache has the appropriate arch section. */
FcBool
FcDirCacheHasCurrentArch (const FcChar8 *dir)
{
    int 	fd;
    off_t	current_arch_start;
    char 	*current_arch_machine_name;
762
    FcCache	metadata;
763

764
    fd = FcDirCacheOpen (dir);
765
766
767
768
    if (fd < 0)
	goto bail;

    current_arch_machine_name = FcCacheMachineSignature();
Patrick Lam's avatar
Patrick Lam committed
769
    current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785

    if (current_arch_start >= 0)
    {
        if (read(fd, &metadata, sizeof(FcCache)) != sizeof(FcCache))
        {
            close (fd);
            return FcFalse;
        }

        if (metadata.magic != FC_CACHE_MAGIC)
        {
            close (fd);
            return FcFalse;
        }
    }

786
787
788
789
    close (fd);

    if (current_arch_start < 0)
        return FcFalse;
790
791
    
    return FcTrue;
792
793
794

 bail:
    return FcFalse;
795
}
796

797
FcBool
798
FcDirCacheUnlink (const FcChar8 *dir, FcConfig *config)
799
{
800
    char	*cache_file;
801
    char	*cache_hashed = 0;
Patrick Lam's avatar
Patrick Lam committed
802
    int		fd, collisions;
803
    struct stat	cache_stat;
Patrick Lam's avatar
Patrick Lam committed
804
    char	name_buf[FC_MAX_FILE_LEN];
805

806
807
    dir = FcConfigNormalizeFontDir (config, dir);
    cache_file = (char *)FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
808
809
810
    if (!cache_file)
	return FcFalse;

Patrick Lam's avatar
Patrick Lam committed
811
    /* First remove normal cache file. */
812
813
    if (stat ((char *) cache_file, &cache_stat) == 0)
        unlink ((char *)cache_file);
Patrick Lam's avatar
Patrick Lam committed
814
815
816
817

    /* Next remove any applicable hashed files. */
    fd = -1; collisions = 0;
    do
818
    {
819
820
821
	if (cache_hashed)
	    FcStrFree ((FcChar8 *)cache_hashed);

Patrick Lam's avatar
Patrick Lam committed
822
823
824
825
826
827
	cache_hashed = FcDirCacheHashName (cache_file, collisions++);
	if (!cache_hashed)
	    goto bail;

	if (fd > 0)
	    close (fd);
828
	fd = open(cache_hashed, O_RDONLY | O_BINARY);
Patrick Lam's avatar
Patrick Lam committed
829
830
831
832
833
834
	if (fd == -1)
	{
	    FcStrFree ((FcChar8 *)cache_file);
	    return FcTrue;
	}

835
	if (!FcCacheReadString (fd, name_buf, sizeof (name_buf)) || !strlen(name_buf))
836
837
	{
	    FcStrFree ((FcChar8 *)cache_hashed);
Patrick Lam's avatar
Patrick Lam committed
838
	    goto bail;
839
	}
Patrick Lam's avatar
Patrick Lam committed
840
841
842
843
844
845
846
847
848
    } while (strcmp (name_buf, cache_file) != 0);

    close (fd);

    if (stat ((char *) cache_hashed, &cache_stat) == 0 &&
	unlink ((char *)cache_hashed) != 0)
    {
	FcStrFree ((FcChar8 *)cache_hashed);
	goto bail;
849
    }
850

Patrick Lam's avatar
Patrick Lam committed
851
    FcStrFree ((FcChar8 *)cache_file);
Patrick Lam's avatar
Patrick Lam committed
852
    FcStrFree ((FcChar8 *)cache_hashed);
853
    return FcTrue;
Patrick Lam's avatar
Patrick Lam committed
854
855
856
857

 bail:
    FcStrFree ((FcChar8 *)cache_file);
    return FcFalse;
858
859
}

860
static int
861
FcCacheReadDirs (FcConfig * config, FcGlobalCache * cache, 
Patrick Lam's avatar
Patrick Lam committed
862
		 FcStrList *list, FcFontSet * set, FcStrSet *processed_dirs)
863
864
865
866
867
868
{
    int			ret = 0;
    FcChar8		*dir;
    FcStrSet		*subdirs;
    FcStrList		*sublist;
    struct stat		statb;
869
    FcGlobalCacheDir   *d;
870
871

    /*
872
     * Read in the results from 'list'.
873
874
875
     */
    while ((dir = FcStrListNext (list)))
    {
876
877
878
	if (!FcConfigAcceptFilename (config, dir))
	    continue;

879
880
	/* Skip this directory if already updated
	 * to avoid the looped directories via symlinks
881
	 * Clearly a dir not in fonts.conf shouldn't be globally cached.
882
	 */
Patrick Lam's avatar
Patrick Lam committed
883
	dir = (FcChar8 *)FcConfigNormalizeFontDir (config, dir);
884
885
886
887
888
889
890
	if (!dir)
	    continue;

	if (FcStrSetMember (processed_dirs, dir))
	    continue;
	if (!FcStrSetAdd (processed_dirs, dir))
	    continue;
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928

	subdirs = FcStrSetCreate ();
	if (!subdirs)
	{
	    fprintf (stderr, "Can't create directory set\n");
	    ret++;
	    continue;
	}
	
	if (access ((char *) dir, X_OK) < 0)
	{
	    switch (errno) {
	    case ENOENT:
	    case ENOTDIR:
	    case EACCES:
		break;
	    default:
		fprintf (stderr, "\"%s\": ", dir);
		perror ("");
		ret++;
	    }
	    FcStrSetDestroy (subdirs);
	    continue;
	}
	if (stat ((char *) dir, &statb) == -1)
	{
	    fprintf (stderr, "\"%s\": ", dir);
	    perror ("");
	    FcStrSetDestroy (subdirs);
	    ret++;
	    continue;
	}
	if (!S_ISDIR (statb.st_mode))
	{
	    fprintf (stderr, "\"%s\": not a directory, skipping\n", dir);
	    FcStrSetDestroy (subdirs);
	    continue;
	}
929
930
931
	if (FcDirCacheValid (dir) && FcDirCacheRead (set, subdirs, dir, config))
	{
	    /* if an old entry is found in the global cache, disable it */
932
	    if ((d = FcGlobalCacheDirFind (cache, (const char *)dir)) != NULL)
933
934
935
936
937
938
939
	    {
		d->state = FcGCDirDisabled;
		/* save the updated config later without this entry */
		cache->updated = FcTrue;
	    }
	}
	else
940
	{
941
	    if (FcDebug () & FC_DBG_FONTSET)
942
		printf ("cache scan dir %s\n", dir);
Patrick Lam's avatar
:    
Patrick Lam committed
943

944
945
	    FcDirScanConfig (set, subdirs, cache, 
			     config->blanks, dir, FcFalse, config);
946
947
948
949
950
951
952
953
954
	}
	sublist = FcStrListCreate (subdirs);
	FcStrSetDestroy (subdirs);
	if (!sublist)
	{
	    fprintf (stderr, "Can't create subdir list in \"%s\"\n", dir);
	    ret++;
	    continue;
	}
Patrick Lam's avatar
Patrick Lam committed
955
	ret += FcCacheReadDirs (config, cache, sublist, set, processed_dirs);
956
957
958
959
960
961
    }
    FcStrListDone (list);
    return ret;
}

FcFontSet *
962
FcCacheRead (FcConfig *config, FcGlobalCache * cache)
963
{
Patrick Lam's avatar
Patrick Lam committed
964
965
966
    FcFontSet 	*s = FcFontSetCreate();
    FcStrSet 	*processed_dirs;

967
968
969
    if (!s) 
	return 0;

Patrick Lam's avatar
Patrick Lam committed
970
971
    processed_dirs = FcStrSetCreate();
    if (!processed_dirs)
972
973
	goto bail;

Patrick Lam's avatar
Patrick Lam committed
974
975
976
977
    if (FcCacheReadDirs (config, cache, FcConfigGetConfigDirs (config), s, processed_dirs))
	goto bail1;

    FcStrSetDestroy (processed_dirs);
978
979
    return s;

Patrick Lam's avatar
Patrick Lam committed
980
981
 bail1:
    FcStrSetDestroy (processed_dirs);
982
983
984
985
986
 bail:
    FcFontSetDestroy (s);
    return 0;
}

987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
static const char bin2hex[] = { '0', '1', '2', '3',
				'4', '5', '6', '7',
				'8', '9', 'a', 'b',
				'c', 'd', 'e', 'f' };

static char *
FcDirCacheHashName (char * cache_file, int collisions)
{
    unsigned char 	hash[16], hex_hash[33];
    char 		*cache_hashed;
    unsigned char	uscore = '_';
    int			cnt, i;
    FcChar8 		*tmp;
    struct MD5Context 	ctx;

    MD5Init (&ctx);
    MD5Update (&ctx, (unsigned char *)cache_file, strlen (cache_file));

    for (i = 0; i < collisions; i++)
	MD5Update (&ctx, &uscore, 1);

    MD5Final (hash, &ctx);

    for (cnt = 0; cnt < 16; ++cnt)
    {
	hex_hash[2*cnt] = bin2hex[hash[cnt] >> 4];
	hex_hash[2*cnt+1] = bin2hex[hash[cnt] & 0xf];
    }
    hex_hash[32] = 0;

    tmp = FcStrPlus ((FcChar8 *)hex_hash, (FcChar8 *)FC_CACHE_SUFFIX);
    if (!tmp)
	return 0;

1021
    cache_hashed = (char *)FcStrPlus ((FcChar8 *)PKGCACHEDIR"/", tmp);
1022
1023
1024
1025
1026
1027
1028
1029
1030
    free (tmp);

    return cache_hashed;
}

/* Opens the hashed name for cache_file.
 * This would fail in the unlikely event of a collision and subsequent
 * removal of the file which originally caused the collision. */
static int
1031
FcDirCacheOpen (const FcChar8 *dir)