]> git.wh0rd.org - fontconfig.git/blame - src/fccache.c
Forward port cworth's patch to branch.
[fontconfig.git] / src / fccache.c
CommitLineData
24330d27 1/*
4bd4418a 2 * $RCSId: xc/lib/fontconfig/src/fccache.c,v 1.12 2002/08/22 07:36:44 keithp Exp $
24330d27 3 *
46b51147 4 * Copyright © 2000 Keith Packard
24330d27
KP
5 *
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation, and that the name of Keith Packard not be used in
11 * advertising or publicity pertaining to distribution of the software without
12 * specific, written prior permission. Keith Packard makes no
13 * representations about the suitability of this software for any purpose. It
14 * is provided "as is" without express or implied warranty.
15 *
16 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
20 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
21 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22 * PERFORMANCE OF THIS SOFTWARE.
23 */
24
25#include "fcint.h"
26
fa244f3d
KP
27/*
28 * POSIX has broken stdio so that getc must do thread-safe locking,
29 * this is a serious performance problem for applications doing large
30 * amounts of IO with getc (as is done here). If available, use
31 * the getc_unlocked varient instead.
32 */
33
34#if defined(getc_unlocked) || defined(_IO_getc_unlocked)
35#define GETC(f) getc_unlocked(f)
36#define PUTC(c,f) putc_unlocked(c,f)
37#else
38#define GETC(f) getc(f)
39#define PUTC(c,f) putc(c,f)
40#endif
41
327a7fd4 42#define FC_DBG_CACHE_REF 1024
24330d27 43
ccb3e93b 44static FcChar8 *
327a7fd4 45FcCacheReadString (FILE *f, FcChar8 *dest, int len)
24330d27 46{
ccb3e93b
KP
47 int c;
48 FcBool escape;
49 FcChar8 *d;
50 int size;
51 int i;
24330d27 52
fa244f3d 53 while ((c = GETC (f)) != EOF)
24330d27
KP
54 if (c == '"')
55 break;
56 if (c == EOF)
57 return FcFalse;
58 if (len == 0)
59 return FcFalse;
60
ccb3e93b
KP
61 size = len;
62 i = 0;
63 d = dest;
24330d27 64 escape = FcFalse;
fa244f3d 65 while ((c = GETC (f)) != EOF)
24330d27
KP
66 {
67 if (!escape)
68 {
69 switch (c) {
70 case '"':
ccb3e93b
KP
71 c = '\0';
72 break;
24330d27
KP
73 case '\\':
74 escape = FcTrue;
75 continue;
76 }
77 }
ccb3e93b
KP
78 if (i == size)
79 {
9dac3c59 80 FcChar8 *new = malloc (size * 2); /* freed in caller */
ccb3e93b
KP
81 if (!new)
82 break;
83 memcpy (new, d, size);
84 size *= 2;
85 if (d != dest)
86 free (d);
87 d = new;
88 }
89 d[i++] = c;
90 if (c == '\0')
91 return d;
24330d27
KP
92 escape = FcFalse;
93 }
ccb3e93b
KP
94 if (d != dest)
95 free (d);
96 return 0;
24330d27
KP
97}
98
99static FcBool
327a7fd4 100FcCacheReadUlong (FILE *f, unsigned long *dest)
24330d27
KP
101{
102 unsigned long t;
103 int c;
104
fa244f3d 105 while ((c = GETC (f)) != EOF)
24330d27
KP
106 {
107 if (!isspace (c))
108 break;
109 }
110 if (c == EOF)
111 return FcFalse;
112 t = 0;
113 for (;;)
114 {
115 if (c == EOF || isspace (c))
116 break;
117 if (!isdigit (c))
118 return FcFalse;
119 t = t * 10 + (c - '0');
fa244f3d 120 c = GETC (f);
24330d27
KP
121 }
122 *dest = t;
123 return FcTrue;
124}
125
126static FcBool
327a7fd4 127FcCacheReadInt (FILE *f, int *dest)
24330d27
KP
128{
129 unsigned long t;
130 FcBool ret;
131
327a7fd4 132 ret = FcCacheReadUlong (f, &t);
24330d27
KP
133 if (ret)
134 *dest = (int) t;
135 return ret;
136}
137
138static FcBool
327a7fd4 139FcCacheReadTime (FILE *f, time_t *dest)
24330d27
KP
140{
141 unsigned long t;
142 FcBool ret;
143
327a7fd4 144 ret = FcCacheReadUlong (f, &t);
24330d27
KP
145 if (ret)
146 *dest = (time_t) t;
147 return ret;
148}
149
150static FcBool
327a7fd4 151FcCacheWriteChars (FILE *f, const FcChar8 *chars)
24330d27 152{
327a7fd4
KP
153 FcChar8 c;
154 while ((c = *chars++))
155 {
156 switch (c) {
157 case '"':
158 case '\\':
fa244f3d 159 if (PUTC ('\\', f) == EOF)
327a7fd4
KP
160 return FcFalse;
161 /* fall through */
162 default:
fa244f3d 163 if (PUTC (c, f) == EOF)
327a7fd4
KP
164 return FcFalse;
165 }
166 }
167 return FcTrue;
168}
24330d27 169
327a7fd4
KP
170static FcBool
171FcCacheWriteString (FILE *f, const FcChar8 *string)
172{
173
fa244f3d 174 if (PUTC ('"', f) == EOF)
327a7fd4
KP
175 return FcFalse;
176 if (!FcCacheWriteChars (f, string))
177 return FcFalse;
fa244f3d 178 if (PUTC ('"', f) == EOF)
327a7fd4
KP
179 return FcFalse;
180 return FcTrue;
181}
182
183static FcBool
184FcCacheWritePath (FILE *f, const FcChar8 *dir, const FcChar8 *file)
185{
fa244f3d 186 if (PUTC ('"', f) == EOF)
327a7fd4
KP
187 return FcFalse;
188 if (dir)
189 if (!FcCacheWriteChars (f, dir))
190 return FcFalse;
daeed6e0
TL
191#ifdef _WIN32
192 if (dir &&
193 dir[strlen((const char *) dir) - 1] != '/' &&
194 dir[strlen((const char *) dir) - 1] != '\\')
195 {
196 if (!FcCacheWriteChars (f, "\\"))
197 return FcFalse;
198 }
199#else
327a7fd4 200 if (dir && dir[strlen((const char *) dir) - 1] != '/')
fa244f3d 201 if (PUTC ('/', f) == EOF)
327a7fd4 202 return FcFalse;
daeed6e0 203#endif
327a7fd4
KP
204 if (!FcCacheWriteChars (f, file))
205 return FcFalse;
fa244f3d 206 if (PUTC ('"', f) == EOF)
327a7fd4
KP
207 return FcFalse;
208 return FcTrue;
209}
210
211static FcBool
212FcCacheWriteUlong (FILE *f, unsigned long t)
213{
214 int pow;
215 unsigned long temp, digit;
216
217 temp = t;
218 pow = 1;
219 while (temp >= 10)
24330d27 220 {
327a7fd4
KP
221 temp /= 10;
222 pow *= 10;
24330d27 223 }
327a7fd4
KP
224 temp = t;
225 while (pow)
226 {
227 digit = temp / pow;
fa244f3d 228 if (PUTC ((char) digit + '0', f) == EOF)
327a7fd4
KP
229 return FcFalse;
230 temp = temp - pow * digit;
231 pow = pow / 10;
232 }
233 return FcTrue;
234}
235
236static FcBool
237FcCacheWriteInt (FILE *f, int i)
238{
239 return FcCacheWriteUlong (f, (unsigned long) i);
240}
241
242static FcBool
243FcCacheWriteTime (FILE *f, time_t t)
244{
245 return FcCacheWriteUlong (f, (unsigned long) t);
246}
247
248static FcBool
249FcCacheFontSetAdd (FcFontSet *set,
250 FcStrSet *dirs,
251 const FcChar8 *dir,
252 int dir_len,
253 const FcChar8 *file,
d47c9d6e
KP
254 const FcChar8 *name,
255 FcConfig *config)
327a7fd4
KP
256{
257 FcChar8 path_buf[8192], *path;
258 int len;
259 FcBool ret = FcFalse;
260 FcPattern *font;
d8d73958 261 FcPattern *frozen;
327a7fd4
KP
262
263 path = path_buf;
264 len = (dir_len + 1 + strlen ((const char *) file) + 1);
265 if (len > sizeof (path_buf))
266 {
9dac3c59 267 path = malloc (len); /* freed down below */
327a7fd4
KP
268 if (!path)
269 return FcFalse;
270 }
271 strncpy ((char *) path, (const char *) dir, dir_len);
daeed6e0
TL
272#ifdef _WIN32
273 if (dir[dir_len - 1] != '/' && dir[dir_len - 1] != '\\' )
274 path[dir_len++] = '\\';
275#else
327a7fd4
KP
276 if (dir[dir_len - 1] != '/')
277 path[dir_len++] = '/';
daeed6e0 278#endif
327a7fd4 279 strcpy ((char *) path + dir_len, (const char *) file);
d47c9d6e
KP
280 if (config && !FcConfigAcceptFilename (config, path))
281 ret = FcTrue;
282 else if (!FcStrCmp (name, FC_FONT_FILE_DIR))
327a7fd4
KP
283 {
284 if (FcDebug () & FC_DBG_CACHEV)
285 printf (" dir cache dir \"%s\"\n", path);
286 ret = FcStrSetAdd (dirs, path);
287 }
288 else if (!FcStrCmp (name, FC_FONT_FILE_INVALID))
289 {
290 ret = FcTrue;
291 }
292 else
293 {
294 font = FcNameParse (name);
295 if (font)
296 {
fce87a18
KP
297 FcChar8 *family;
298
327a7fd4
KP
299 if (FcDebug () & FC_DBG_CACHEV)
300 printf (" dir cache file \"%s\"\n", file);
d8d73958 301 ret = FcPatternAddString (font, FC_FILE, path);
fce87a18
KP
302 /*
303 * Make sure the pattern has the file name as well as
304 * already containing at least one family name.
305 */
306 if (ret &&
307 FcPatternGetString (font, FC_FAMILY, 0, &family) == FcResultMatch &&
308 (!config || FcConfigAcceptFont (config, font)))
d8d73958
KP
309 {
310 frozen = FcPatternFreeze (font);
311 ret = (frozen != 0);
312 if (ret)
313 ret = FcFontSetAdd (set, frozen);
314 }
315 FcPatternDestroy (font);
327a7fd4
KP
316 }
317 }
318 if (path != path_buf) free (path);
319 return ret;
320
321}
322
323static unsigned int
c8d5753c 324FcCacheHash (const FcChar8 *string, int len)
327a7fd4
KP
325{
326 unsigned int h = 0;
327 FcChar8 c;
328
c8d5753c 329 while (len-- && (c = *string++))
327a7fd4 330 h = (h << 1) ^ c;
c8d5753c 331 return h;
327a7fd4
KP
332}
333
334/*
335 * Verify the saved timestamp for a file
336 */
337FcBool
a8386abc 338FcGlobalCacheCheckTime (const FcChar8 *file, FcGlobalCacheInfo *info)
327a7fd4
KP
339{
340 struct stat statb;
341
a8386abc 342 if (stat ((char *) file, &statb) < 0)
327a7fd4
KP
343 {
344 if (FcDebug () & FC_DBG_CACHE)
a8386abc 345 printf (" file %s missing\n", file);
327a7fd4
KP
346 return FcFalse;
347 }
348 if (statb.st_mtime != info->time)
349 {
350 if (FcDebug () & FC_DBG_CACHE)
351 printf (" timestamp mismatch (was %d is %d)\n",
352 (int) info->time, (int) statb.st_mtime);
353 return FcFalse;
354 }
355 return FcTrue;
356}
357
358void
359FcGlobalCacheReferenced (FcGlobalCache *cache,
360 FcGlobalCacheInfo *info)
361{
362 if (!info->referenced)
363 {
364 info->referenced = FcTrue;
365 cache->referenced++;
366 if (FcDebug () & FC_DBG_CACHE_REF)
367 printf ("Reference %d %s\n", cache->referenced, info->file);
368 }
369}
370
371/*
372 * Break a path into dir/base elements and compute the base hash
373 * and the dir length. This is shared between the functions
374 * which walk the file caches
375 */
376
377typedef struct _FcFilePathInfo {
378 const FcChar8 *dir;
379 int dir_len;
380 const FcChar8 *base;
381 unsigned int base_hash;
382} FcFilePathInfo;
383
384static FcFilePathInfo
385FcFilePathInfoGet (const FcChar8 *path)
386{
387 FcFilePathInfo i;
388 FcChar8 *slash;
389
daeed6e0 390 slash = FcStrLastSlash (path);
327a7fd4
KP
391 if (slash)
392 {
393 i.dir = path;
394 i.dir_len = slash - path;
395 if (!i.dir_len)
396 i.dir_len = 1;
397 i.base = slash + 1;
398 }
399 else
400 {
401 i.dir = (const FcChar8 *) ".";
402 i.dir_len = 1;
403 i.base = path;
404 }
c8d5753c 405 i.base_hash = FcCacheHash (i.base, -1);
327a7fd4
KP
406 return i;
407}
408
409FcGlobalCacheDir *
410FcGlobalCacheDirGet (FcGlobalCache *cache,
411 const FcChar8 *dir,
412 int len,
413 FcBool create_missing)
414{
c8d5753c 415 unsigned int hash = FcCacheHash (dir, len);
327a7fd4
KP
416 FcGlobalCacheDir *d, **prev;
417
418 for (prev = &cache->ents[hash % FC_GLOBAL_CACHE_DIR_HASH_SIZE];
419 (d = *prev);
24330d27
KP
420 prev = &(*prev)->next)
421 {
327a7fd4
KP
422 if (d->info.hash == hash && d->len == len &&
423 !strncmp ((const char *) d->info.file,
424 (const char *) dir, len))
24330d27
KP
425 break;
426 }
327a7fd4
KP
427 if (!(d = *prev))
428 {
429 int i;
430 if (!create_missing)
431 return 0;
432 d = malloc (sizeof (FcGlobalCacheDir) + len + 1);
433 if (!d)
434 return 0;
9dac3c59 435 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCacheDir) + len + 1);
327a7fd4
KP
436 d->next = *prev;
437 *prev = d;
438 d->info.hash = hash;
439 d->info.file = (FcChar8 *) (d + 1);
440 strncpy ((char *) d->info.file, (const char *) dir, len);
441 d->info.file[len] = '\0';
442 d->info.time = 0;
443 d->info.referenced = FcFalse;
444 d->len = len;
445 for (i = 0; i < FC_GLOBAL_CACHE_FILE_HASH_SIZE; i++)
446 d->ents[i] = 0;
447 d->subdirs = 0;
448 }
449 return d;
450}
451
452static FcGlobalCacheInfo *
453FcGlobalCacheDirAdd (FcGlobalCache *cache,
454 const FcChar8 *dir,
455 time_t time,
c4ab52dc
KP
456 FcBool replace,
457 FcBool create_missing)
327a7fd4
KP
458{
459 FcGlobalCacheDir *d;
460 FcFilePathInfo i;
461 FcGlobalCacheSubdir *subdir;
462 FcGlobalCacheDir *parent;
463
c4ab52dc
KP
464 i = FcFilePathInfoGet (dir);
465 parent = FcGlobalCacheDirGet (cache, i.dir, i.dir_len, create_missing);
466 /*
467 * Tricky here -- directories containing fonts.cache-1 files
468 * need entries only when the parent doesn't have a cache file.
469 * That is, when the parent already exists in the cache, is
470 * referenced and has a "real" timestamp. The time of 0 is
471 * special and marks directories which got stuck in the
472 * global cache for this very reason. Yes, it could
473 * use a separate boolean field, and probably should.
474 */
475 if (!parent || (!create_missing &&
476 (!parent->info.referenced ||
477 (parent->info.time == 0))))
478 return 0;
327a7fd4
KP
479 /*
480 * Add this directory to the cache
481 */
482 d = FcGlobalCacheDirGet (cache, dir, strlen ((const char *) dir), FcTrue);
483 if (!d)
484 return 0;
485 d->info.time = time;
327a7fd4
KP
486 /*
487 * Add this directory to the subdirectory list of the parent
488 */
e712133c 489 subdir = malloc (sizeof (FcGlobalCacheSubdir));
327a7fd4
KP
490 if (!subdir)
491 return 0;
e712133c
KP
492 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCacheSubdir));
493 subdir->ent = d;
327a7fd4
KP
494 subdir->next = parent->subdirs;
495 parent->subdirs = subdir;
496 return &d->info;
497}
498
499static void
500FcGlobalCacheDirDestroy (FcGlobalCacheDir *d)
501{
502 FcGlobalCacheFile *f, *next;
503 int h;
504 FcGlobalCacheSubdir *s, *nexts;
505
506 for (h = 0; h < FC_GLOBAL_CACHE_FILE_HASH_SIZE; h++)
507 for (f = d->ents[h]; f; f = next)
508 {
509 next = f->next;
9dac3c59
KP
510 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheFile) +
511 strlen ((char *) f->info.file) + 1 +
512 strlen ((char *) f->name) + 1);
327a7fd4
KP
513 free (f);
514 }
515 for (s = d->subdirs; s; s = nexts)
516 {
517 nexts = s->next;
e712133c 518 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheSubdir));
327a7fd4
KP
519 free (s);
520 }
9dac3c59 521 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheDir) + d->len + 1);
327a7fd4
KP
522 free (d);
523}
524
c4ab52dc
KP
525/*
526 * If the parent is in the global cache and referenced, add
527 * an entry for 'dir' to the global cache. This is used
528 * for directories with fonts.cache files
529 */
530
531void
532FcGlobalCacheReferenceSubdir (FcGlobalCache *cache,
533 const FcChar8 *dir)
534{
535 FcGlobalCacheInfo *info;
536 info = FcGlobalCacheDirAdd (cache, dir, 0, FcFalse, FcFalse);
537 if (info && !info->referenced)
538 {
539 info->referenced = FcTrue;
540 cache->referenced++;
541 }
542}
543
c8d5753c
KP
544/*
545 * Check to see if the global cache contains valid data for 'dir'.
546 * If so, scan the global cache for files and directories in 'dir'.
547 * else, return False.
548 */
327a7fd4
KP
549FcBool
550FcGlobalCacheScanDir (FcFontSet *set,
551 FcStrSet *dirs,
552 FcGlobalCache *cache,
d47c9d6e
KP
553 const FcChar8 *dir,
554 FcConfig *config)
327a7fd4
KP
555{
556 FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, dir,
557 strlen ((const char *) dir),
558 FcFalse);
559 FcGlobalCacheFile *f;
560 int h;
561 int dir_len;
562 FcGlobalCacheSubdir *subdir;
c8d5753c 563 FcBool any_in_cache = FcFalse;
327a7fd4
KP
564
565 if (FcDebug() & FC_DBG_CACHE)
566 printf ("FcGlobalCacheScanDir %s\n", dir);
567
568 if (!d)
569 {
570 if (FcDebug () & FC_DBG_CACHE)
571 printf ("\tNo dir cache entry\n");
572 return FcFalse;
573 }
574
c8d5753c
KP
575 /*
576 * See if the timestamp recorded in the global cache
577 * matches the directory time, if not, return False
578 */
a8386abc 579 if (!FcGlobalCacheCheckTime (d->info.file, &d->info))
327a7fd4
KP
580 {
581 if (FcDebug () & FC_DBG_CACHE)
582 printf ("\tdir cache entry time mismatch\n");
583 return FcFalse;
584 }
585
c8d5753c
KP
586 /*
587 * Add files from 'dir' to the fontset
588 */
327a7fd4
KP
589 dir_len = strlen ((const char *) dir);
590 for (h = 0; h < FC_GLOBAL_CACHE_FILE_HASH_SIZE; h++)
591 for (f = d->ents[h]; f; f = f->next)
592 {
593 if (FcDebug() & FC_DBG_CACHEV)
594 printf ("FcGlobalCacheScanDir add file %s\n", f->info.file);
c8d5753c 595 any_in_cache = FcTrue;
327a7fd4 596 if (!FcCacheFontSetAdd (set, dirs, dir, dir_len,
d47c9d6e 597 f->info.file, f->name, config))
327a7fd4
KP
598 {
599 cache->broken = FcTrue;
600 return FcFalse;
601 }
602 FcGlobalCacheReferenced (cache, &f->info);
603 }
c8d5753c
KP
604 /*
605 * Add directories in 'dir' to 'dirs'
606 */
327a7fd4
KP
607 for (subdir = d->subdirs; subdir; subdir = subdir->next)
608 {
e712133c
KP
609 FcFilePathInfo info = FcFilePathInfoGet (subdir->ent->info.file);
610
c8d5753c 611 any_in_cache = FcTrue;
327a7fd4 612 if (!FcCacheFontSetAdd (set, dirs, dir, dir_len,
d47c9d6e 613 info.base, FC_FONT_FILE_DIR, config))
327a7fd4
KP
614 {
615 cache->broken = FcTrue;
616 return FcFalse;
617 }
e712133c 618 FcGlobalCacheReferenced (cache, &subdir->ent->info);
327a7fd4
KP
619 }
620
621 FcGlobalCacheReferenced (cache, &d->info);
622
c8d5753c
KP
623 /*
624 * To recover from a bug in previous versions of fontconfig,
625 * return FcFalse if no entries in the cache were found
626 * for this directory. This will cause any empty directories
627 * to get rescanned every time fontconfig is initialized. This
628 * might get removed at some point when the older cache files are
629 * presumably fixed.
630 */
631 return any_in_cache;
327a7fd4
KP
632}
633
634/*
635 * Locate the cache entry for a particular file
636 */
637FcGlobalCacheFile *
638FcGlobalCacheFileGet (FcGlobalCache *cache,
639 const FcChar8 *file,
640 int id,
641 int *count)
642{
643 FcFilePathInfo i = FcFilePathInfoGet (file);
644 FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, i.dir,
645 i.dir_len, FcFalse);
646 FcGlobalCacheFile *f, *match = 0;
647 int max = -1;
648
649 if (!d)
650 return 0;
651 for (f = d->ents[i.base_hash % FC_GLOBAL_CACHE_FILE_HASH_SIZE]; f; f = f->next)
652 {
653 if (f->info.hash == i.base_hash &&
654 !strcmp ((const char *) f->info.file, (const char *) i.base))
655 {
656 if (f->id == id)
657 match = f;
658 if (f->id > max)
659 max = f->id;
660 }
661 }
662 if (count)
a8386abc 663 *count = max + 1;
327a7fd4
KP
664 return match;
665}
666
667/*
668 * Add a file entry to the cache
669 */
670static FcGlobalCacheInfo *
671FcGlobalCacheFileAdd (FcGlobalCache *cache,
672 const FcChar8 *path,
673 int id,
674 time_t time,
675 const FcChar8 *name,
676 FcBool replace)
677{
678 FcFilePathInfo i = FcFilePathInfoGet (path);
679 FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, i.dir,
680 i.dir_len, FcTrue);
681 FcGlobalCacheFile *f, **prev;
9dac3c59 682 int size;
327a7fd4
KP
683
684 if (!d)
685 return 0;
686 for (prev = &d->ents[i.base_hash % FC_GLOBAL_CACHE_FILE_HASH_SIZE];
687 (f = *prev);
688 prev = &(*prev)->next)
689 {
690 if (f->info.hash == i.base_hash &&
691 f->id == id &&
692 !strcmp ((const char *) f->info.file, (const char *) i.base))
693 {
694 break;
695 }
696 }
24330d27
KP
697 if (*prev)
698 {
699 if (!replace)
327a7fd4 700 return 0;
24330d27 701
327a7fd4
KP
702 f = *prev;
703 if (f->info.referenced)
24330d27 704 cache->referenced--;
327a7fd4 705 *prev = f->next;
9dac3c59
KP
706 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheFile) +
707 strlen ((char *) f->info.file) + 1 +
708 strlen ((char *) f->name) + 1);
327a7fd4 709 free (f);
24330d27 710 }
9dac3c59
KP
711 size = (sizeof (FcGlobalCacheFile) +
712 strlen ((char *) i.base) + 1 +
713 strlen ((char *) name) + 1);
714 f = malloc (size);
327a7fd4
KP
715 if (!f)
716 return 0;
9dac3c59 717 FcMemAlloc (FC_MEM_CACHE, size);
327a7fd4
KP
718 f->next = *prev;
719 *prev = f;
720 f->info.hash = i.base_hash;
721 f->info.file = (FcChar8 *) (f + 1);
722 f->info.time = time;
723 f->info.referenced = FcFalse;
724 f->id = id;
725 f->name = f->info.file + strlen ((char *) i.base) + 1;
726 strcpy ((char *) f->info.file, (const char *) i.base);
727 strcpy ((char *) f->name, (const char *) name);
728 return &f->info;
24330d27
KP
729}
730
327a7fd4
KP
731FcGlobalCache *
732FcGlobalCacheCreate (void)
24330d27 733{
327a7fd4
KP
734 FcGlobalCache *cache;
735 int h;
24330d27 736
327a7fd4 737 cache = malloc (sizeof (FcGlobalCache));
24330d27
KP
738 if (!cache)
739 return 0;
9dac3c59 740 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCache));
327a7fd4 741 for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
24330d27
KP
742 cache->ents[h] = 0;
743 cache->entries = 0;
744 cache->referenced = 0;
745 cache->updated = FcFalse;
bb356b68 746 cache->broken = FcFalse;
24330d27
KP
747 return cache;
748}
749
750void
327a7fd4 751FcGlobalCacheDestroy (FcGlobalCache *cache)
24330d27 752{
327a7fd4
KP
753 FcGlobalCacheDir *d, *next;
754 int h;
24330d27 755
327a7fd4 756 for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
24330d27 757 {
327a7fd4 758 for (d = cache->ents[h]; d; d = next)
24330d27 759 {
327a7fd4
KP
760 next = d->next;
761 FcGlobalCacheDirDestroy (d);
24330d27
KP
762 }
763 }
9dac3c59 764 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCache));
24330d27
KP
765 free (cache);
766}
767
327a7fd4
KP
768/*
769 * Cache file syntax is quite simple:
770 *
771 * "file_name" id time "font_name" \n
772 */
773
24330d27 774void
327a7fd4
KP
775FcGlobalCacheLoad (FcGlobalCache *cache,
776 const FcChar8 *cache_file)
24330d27 777{
327a7fd4
KP
778 FILE *f;
779 FcChar8 file_buf[8192], *file;
780 int id;
781 time_t time;
782 FcChar8 name_buf[8192], *name;
783 FcGlobalCacheInfo *info;
24330d27 784
ccb3e93b 785 f = fopen ((char *) cache_file, "r");
24330d27
KP
786 if (!f)
787 return;
788
789 cache->updated = FcFalse;
ccb3e93b
KP
790 file = 0;
791 name = 0;
327a7fd4
KP
792 while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) &&
793 FcCacheReadInt (f, &id) &&
794 FcCacheReadTime (f, &time) &&
795 (name = FcCacheReadString (f, name_buf, sizeof (name_buf))))
24330d27 796 {
327a7fd4
KP
797 if (FcDebug () & FC_DBG_CACHEV)
798 printf ("FcGlobalCacheLoad \"%s\" \"%20.20s\"\n", file, name);
799 if (!FcStrCmp (name, FC_FONT_FILE_DIR))
c4ab52dc 800 info = FcGlobalCacheDirAdd (cache, file, time, FcFalse, FcTrue);
327a7fd4
KP
801 else
802 info = FcGlobalCacheFileAdd (cache, file, id, time, name, FcFalse);
803 if (!info)
804 cache->broken = FcTrue;
805 else
806 cache->entries++;
807 if (FcDebug () & FC_DBG_CACHE_REF)
808 printf ("FcGlobalCacheLoad entry %d %s\n",
809 cache->entries, file);
ccb3e93b
KP
810 if (file != file_buf)
811 free (file);
812 if (name != name_buf)
813 free (name);
814 file = 0;
815 name = 0;
24330d27 816 }
ccb3e93b
KP
817 if (file && file != file_buf)
818 free (file);
819 if (name && name != name_buf)
820 free (name);
24330d27
KP
821 fclose (f);
822}
823
824FcBool
327a7fd4
KP
825FcGlobalCacheUpdate (FcGlobalCache *cache,
826 const FcChar8 *file,
827 int id,
828 const FcChar8 *name)
24330d27 829{
327a7fd4
KP
830 const FcChar8 *match;
831 struct stat statb;
832 FcGlobalCacheInfo *info;
24330d27
KP
833
834 match = file;
835
ccb3e93b 836 if (stat ((char *) file, &statb) < 0)
24330d27 837 return FcFalse;
327a7fd4
KP
838 if (S_ISDIR (statb.st_mode))
839 info = FcGlobalCacheDirAdd (cache, file, statb.st_mtime,
c4ab52dc 840 FcTrue, FcTrue);
327a7fd4
KP
841 else
842 info = FcGlobalCacheFileAdd (cache, file, id, statb.st_mtime,
843 name, FcTrue);
844 if (info)
24330d27 845 {
327a7fd4
KP
846 FcGlobalCacheReferenced (cache, info);
847 cache->updated = FcTrue;
24330d27 848 }
327a7fd4
KP
849 else
850 cache->broken = FcTrue;
851 return info != 0;
24330d27
KP
852}
853
854FcBool
327a7fd4
KP
855FcGlobalCacheSave (FcGlobalCache *cache,
856 const FcChar8 *cache_file)
24330d27 857{
327a7fd4
KP
858 FILE *f;
859 int dir_hash, file_hash;
860 FcGlobalCacheDir *dir;
861 FcGlobalCacheFile *file;
862 FcAtomic *atomic;
24330d27
KP
863
864 if (!cache->updated && cache->referenced == cache->entries)
865 return FcTrue;
866
327a7fd4
KP
867 if (cache->broken)
868 return FcFalse;
869
daeed6e0 870#if defined (HAVE_GETUID) && defined (HAVE_GETEUID)
a391da8f
KP
871 /* Set-UID programs can't safely update the cache */
872 if (getuid () != geteuid ())
873 return FcFalse;
daeed6e0 874#endif
a391da8f 875
134f6011
KP
876 atomic = FcAtomicCreate (cache_file);
877 if (!atomic)
24330d27 878 goto bail0;
134f6011 879 if (!FcAtomicLock (atomic))
24330d27 880 goto bail1;
134f6011 881 f = fopen ((char *) FcAtomicNewFile(atomic), "w");
24330d27
KP
882 if (!f)
883 goto bail2;
884
327a7fd4 885 for (dir_hash = 0; dir_hash < FC_GLOBAL_CACHE_DIR_HASH_SIZE; dir_hash++)
24330d27 886 {
327a7fd4 887 for (dir = cache->ents[dir_hash]; dir; dir = dir->next)
24330d27 888 {
327a7fd4 889 if (!dir->info.referenced)
24330d27 890 continue;
327a7fd4 891 if (!FcCacheWriteString (f, dir->info.file))
24330d27 892 goto bail4;
fa244f3d 893 if (PUTC (' ', f) == EOF)
24330d27 894 goto bail4;
327a7fd4 895 if (!FcCacheWriteInt (f, 0))
24330d27 896 goto bail4;
fa244f3d 897 if (PUTC (' ', f) == EOF)
24330d27 898 goto bail4;
327a7fd4 899 if (!FcCacheWriteTime (f, dir->info.time))
24330d27 900 goto bail4;
fa244f3d 901 if (PUTC (' ', f) == EOF)
24330d27 902 goto bail4;
327a7fd4 903 if (!FcCacheWriteString (f, (FcChar8 *) FC_FONT_FILE_DIR))
24330d27 904 goto bail4;
fa244f3d 905 if (PUTC ('\n', f) == EOF)
24330d27 906 goto bail4;
327a7fd4
KP
907
908 for (file_hash = 0; file_hash < FC_GLOBAL_CACHE_FILE_HASH_SIZE; file_hash++)
909 {
910 for (file = dir->ents[file_hash]; file; file = file->next)
911 {
912 if (!file->info.referenced)
913 continue;
914 if (!FcCacheWritePath (f, dir->info.file, file->info.file))
915 goto bail4;
fa244f3d 916 if (PUTC (' ', f) == EOF)
327a7fd4
KP
917 goto bail4;
918 if (!FcCacheWriteInt (f, file->id < 0 ? 0 : file->id))
919 goto bail4;
fa244f3d 920 if (PUTC (' ', f) == EOF)
327a7fd4
KP
921 goto bail4;
922 if (!FcCacheWriteTime (f, file->info.time))
923 goto bail4;
fa244f3d 924 if (PUTC (' ', f) == EOF)
327a7fd4
KP
925 goto bail4;
926 if (!FcCacheWriteString (f, file->name))
927 goto bail4;
fa244f3d 928 if (PUTC ('\n', f) == EOF)
327a7fd4
KP
929 goto bail4;
930 }
931 }
24330d27
KP
932 }
933 }
934
935 if (fclose (f) == EOF)
936 goto bail3;
937
134f6011 938 if (!FcAtomicReplaceOrig (atomic))
24330d27
KP
939 goto bail3;
940
134f6011
KP
941 FcAtomicUnlock (atomic);
942 FcAtomicDestroy (atomic);
943
24330d27
KP
944 cache->updated = FcFalse;
945 return FcTrue;
946
947bail4:
948 fclose (f);
949bail3:
134f6011 950 FcAtomicDeleteNew (atomic);
24330d27 951bail2:
134f6011 952 FcAtomicUnlock (atomic);
24330d27 953bail1:
134f6011 954 FcAtomicDestroy (atomic);
24330d27
KP
955bail0:
956 return FcFalse;
957}
958
959FcBool
327a7fd4 960FcDirCacheValid (const FcChar8 *dir)
179c3995 961{
327a7fd4 962 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
179c3995
KP
963 struct stat file_stat, dir_stat;
964
179c3995
KP
965 if (stat ((char *) dir, &dir_stat) < 0)
966 {
327a7fd4 967 FcStrFree (cache_file);
179c3995
KP
968 return FcFalse;
969 }
179c3995 970 if (stat ((char *) cache_file, &file_stat) < 0)
327a7fd4
KP
971 {
972 FcStrFree (cache_file);
179c3995 973 return FcFalse;
327a7fd4
KP
974 }
975 FcStrFree (cache_file);
179c3995
KP
976 /*
977 * If the directory has been modified more recently than
978 * the cache file, the cache is not valid
979 */
980 if (dir_stat.st_mtime - file_stat.st_mtime > 0)
981 return FcFalse;
982 return FcTrue;
983}
984
985FcBool
d47c9d6e 986FcDirCacheReadDir (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, FcConfig *config)
24330d27 987{
327a7fd4 988 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
24330d27 989 FILE *f;
ccb3e93b 990 FcChar8 *base;
24330d27 991 int id;
179c3995 992 int dir_len;
179c3995 993 FcChar8 file_buf[8192], *file;
ccb3e93b 994 FcChar8 name_buf[8192], *name;
24330d27
KP
995 FcBool ret = FcFalse;
996
327a7fd4
KP
997 if (!cache_file)
998 goto bail0;
999
24330d27 1000 if (FcDebug () & FC_DBG_CACHE)
327a7fd4 1001 printf ("FcDirCacheReadDir cache_file \"%s\"\n", cache_file);
24330d27 1002
ccb3e93b 1003 f = fopen ((char *) cache_file, "r");
24330d27
KP
1004 if (!f)
1005 {
1006 if (FcDebug () & FC_DBG_CACHE)
24330d27 1007 printf (" no cache file\n");
327a7fd4 1008 goto bail1;
24330d27
KP
1009 }
1010
327a7fd4 1011 if (!FcDirCacheValid (dir))
179c3995
KP
1012 {
1013 if (FcDebug () & FC_DBG_CACHE)
179c3995 1014 printf (" cache file older than directory\n");
327a7fd4 1015 goto bail2;
179c3995
KP
1016 }
1017
ccb3e93b 1018 base = (FcChar8 *) strrchr ((char *) cache_file, '/');
24330d27 1019 if (!base)
327a7fd4 1020 goto bail2;
24330d27 1021 base++;
179c3995 1022 dir_len = base - cache_file;
24330d27 1023
ccb3e93b
KP
1024 file = 0;
1025 name = 0;
327a7fd4
KP
1026 while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) &&
1027 FcCacheReadInt (f, &id) &&
1028 (name = FcCacheReadString (f, name_buf, sizeof (name_buf))))
24330d27 1029 {
327a7fd4 1030 if (!FcCacheFontSetAdd (set, dirs, cache_file, dir_len,
d47c9d6e 1031 file, name, config))
327a7fd4 1032 goto bail3;
ccb3e93b
KP
1033 if (file != file_buf)
1034 free (file);
1035 if (name != name_buf)
1036 free (name);
327a7fd4 1037 file = name = 0;
24330d27
KP
1038 }
1039 if (FcDebug () & FC_DBG_CACHE)
24330d27 1040 printf (" cache loaded\n");
24330d27
KP
1041
1042 ret = FcTrue;
327a7fd4 1043bail3:
ccb3e93b
KP
1044 if (file && file != file_buf)
1045 free (file);
1046 if (name && name != name_buf)
1047 free (name);
327a7fd4 1048bail2:
24330d27 1049 fclose (f);
327a7fd4 1050bail1:
9dac3c59 1051 FcStrFree (cache_file);
24330d27
KP
1052bail0:
1053 return ret;
1054}
1055
179c3995
KP
1056/*
1057 * return the path from the directory containing 'cache' to 'file'
1058 */
1059
1060static const FcChar8 *
1061FcFileBaseName (const FcChar8 *cache, const FcChar8 *file)
1062{
1063 const FcChar8 *cache_slash;
1064
daeed6e0 1065 cache_slash = FcStrLastSlash (cache);
179c3995
KP
1066 if (cache_slash && !strncmp ((const char *) cache, (const char *) file,
1067 (cache_slash + 1) - cache))
1068 return file + ((cache_slash + 1) - cache);
1069 return file;
1070}
1071
24330d27 1072FcBool
327a7fd4 1073FcDirCacheWriteDir (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir)
24330d27 1074{
327a7fd4 1075 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
24330d27
KP
1076 FcPattern *font;
1077 FILE *f;
ccb3e93b
KP
1078 FcChar8 *name;
1079 const FcChar8 *file, *base;
24330d27
KP
1080 int n;
1081 int id;
1082 FcBool ret;
179c3995 1083 FcStrList *list;
24330d27 1084
327a7fd4
KP
1085 if (!cache_file)
1086 goto bail0;
24330d27 1087 if (FcDebug () & FC_DBG_CACHE)
327a7fd4 1088 printf ("FcDirCacheWriteDir cache_file \"%s\"\n", cache_file);
24330d27 1089
ccb3e93b 1090 f = fopen ((char *) cache_file, "w");
24330d27
KP
1091 if (!f)
1092 {
1093 if (FcDebug () & FC_DBG_CACHE)
1094 printf (" can't create \"%s\"\n", cache_file);
327a7fd4 1095 goto bail1;
24330d27 1096 }
179c3995
KP
1097
1098 list = FcStrListCreate (dirs);
1099 if (!list)
327a7fd4 1100 goto bail2;
179c3995
KP
1101
1102 while ((dir = FcStrListNext (list)))
1103 {
1104 base = FcFileBaseName (cache_file, dir);
327a7fd4
KP
1105 if (!FcCacheWriteString (f, base))
1106 goto bail3;
fa244f3d 1107 if (PUTC (' ', f) == EOF)
327a7fd4
KP
1108 goto bail3;
1109 if (!FcCacheWriteInt (f, 0))
1110 goto bail3;
fa244f3d 1111 if (PUTC (' ', f) == EOF)
327a7fd4
KP
1112 goto bail3;
1113 if (!FcCacheWriteString (f, FC_FONT_FILE_DIR))
1114 goto bail3;
fa244f3d 1115 if (PUTC ('\n', f) == EOF)
327a7fd4 1116 goto bail3;
179c3995
KP
1117 }
1118
24330d27
KP
1119 for (n = 0; n < set->nfont; n++)
1120 {
1121 font = set->fonts[n];
aae6f7d4 1122 if (FcPatternGetString (font, FC_FILE, 0, (FcChar8 **) &file) != FcResultMatch)
327a7fd4 1123 goto bail3;
179c3995 1124 base = FcFileBaseName (cache_file, file);
24330d27 1125 if (FcPatternGetInteger (font, FC_INDEX, 0, &id) != FcResultMatch)
327a7fd4 1126 goto bail3;
24330d27
KP
1127 if (FcDebug () & FC_DBG_CACHEV)
1128 printf (" write file \"%s\"\n", base);
327a7fd4
KP
1129 if (!FcCacheWriteString (f, base))
1130 goto bail3;
fa244f3d 1131 if (PUTC (' ', f) == EOF)
327a7fd4
KP
1132 goto bail3;
1133 if (!FcCacheWriteInt (f, id))
1134 goto bail3;
fa244f3d 1135 if (PUTC (' ', f) == EOF)
327a7fd4 1136 goto bail3;
24330d27
KP
1137 name = FcNameUnparse (font);
1138 if (!name)
327a7fd4
KP
1139 goto bail3;
1140 ret = FcCacheWriteString (f, name);
9dac3c59 1141 FcStrFree (name);
24330d27 1142 if (!ret)
327a7fd4 1143 goto bail3;
fa244f3d 1144 if (PUTC ('\n', f) == EOF)
327a7fd4 1145 goto bail3;
24330d27 1146 }
179c3995
KP
1147
1148 FcStrListDone (list);
1149
24330d27 1150 if (fclose (f) == EOF)
327a7fd4 1151 goto bail1;
24330d27 1152
9dac3c59 1153 FcStrFree (cache_file);
327a7fd4 1154
24330d27
KP
1155 if (FcDebug () & FC_DBG_CACHE)
1156 printf (" cache written\n");
1157 return FcTrue;
1158
327a7fd4 1159bail3:
179c3995 1160 FcStrListDone (list);
327a7fd4 1161bail2:
24330d27 1162 fclose (f);
327a7fd4 1163bail1:
ccb3e93b 1164 unlink ((char *) cache_file);
9dac3c59 1165 FcStrFree (cache_file);
327a7fd4 1166bail0:
24330d27
KP
1167 return FcFalse;
1168}
cd2ec1a9
PL
1169
1170void
1171FcCacheClearStatic()
1172{
1173 FcFontSetClearStatic();
1174 FcPatternClearStatic();
1175 FcValueListClearStatic();
1176 FcObjectClearStatic();
1177 FcMatrixClearStatic();
1178 FcCharSetClearStatic();
1179 FcLangSetClearStatic();
1180}
1181
1182FcBool
1183FcCachePrepareSerialize (FcConfig * config)
1184{
1185 int i;
1186 for (i = FcSetSystem; i <= FcSetApplication; i++)
1187 if (config->fonts[i] && !FcFontSetPrepareSerialize(config->fonts[i]))
1188 return FcFalse;
1189 return FcTrue;
1190}
1191
1192FcBool
1193FcCacheSerialize (FcConfig * config)
1194{
1195 int i;
1196 for (i = FcSetSystem; i <= FcSetApplication; i++)
1197 if (config->fonts[i] && !FcFontSetSerialize(config->fonts[i]))
1198 return FcFalse;
1199 return FcTrue;
1200}