]> git.wh0rd.org - fontconfig.git/blob - src/fccache.c
Verify that every font pattern loaded from cache has both FC_FILE and
[fontconfig.git] / src / fccache.c
1 /*
2 * $RCSId: xc/lib/fontconfig/src/fccache.c,v 1.12 2002/08/22 07:36:44 keithp Exp $
3 *
4 * Copyright © 2000 Keith Packard
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
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
42 #define FC_DBG_CACHE_REF 1024
43
44 static FcChar8 *
45 FcCacheReadString (FILE *f, FcChar8 *dest, int len)
46 {
47 int c;
48 FcBool escape;
49 FcChar8 *d;
50 int size;
51 int i;
52
53 while ((c = GETC (f)) != EOF)
54 if (c == '"')
55 break;
56 if (c == EOF)
57 return FcFalse;
58 if (len == 0)
59 return FcFalse;
60
61 size = len;
62 i = 0;
63 d = dest;
64 escape = FcFalse;
65 while ((c = GETC (f)) != EOF)
66 {
67 if (!escape)
68 {
69 switch (c) {
70 case '"':
71 c = '\0';
72 break;
73 case '\\':
74 escape = FcTrue;
75 continue;
76 }
77 }
78 if (i == size)
79 {
80 FcChar8 *new = malloc (size * 2); /* freed in caller */
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;
92 escape = FcFalse;
93 }
94 if (d != dest)
95 free (d);
96 return 0;
97 }
98
99 static FcBool
100 FcCacheReadUlong (FILE *f, unsigned long *dest)
101 {
102 unsigned long t;
103 int c;
104
105 while ((c = GETC (f)) != EOF)
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');
120 c = GETC (f);
121 }
122 *dest = t;
123 return FcTrue;
124 }
125
126 static FcBool
127 FcCacheReadInt (FILE *f, int *dest)
128 {
129 unsigned long t;
130 FcBool ret;
131
132 ret = FcCacheReadUlong (f, &t);
133 if (ret)
134 *dest = (int) t;
135 return ret;
136 }
137
138 static FcBool
139 FcCacheReadTime (FILE *f, time_t *dest)
140 {
141 unsigned long t;
142 FcBool ret;
143
144 ret = FcCacheReadUlong (f, &t);
145 if (ret)
146 *dest = (time_t) t;
147 return ret;
148 }
149
150 static FcBool
151 FcCacheWriteChars (FILE *f, const FcChar8 *chars)
152 {
153 FcChar8 c;
154 while ((c = *chars++))
155 {
156 switch (c) {
157 case '"':
158 case '\\':
159 if (PUTC ('\\', f) == EOF)
160 return FcFalse;
161 /* fall through */
162 default:
163 if (PUTC (c, f) == EOF)
164 return FcFalse;
165 }
166 }
167 return FcTrue;
168 }
169
170 static FcBool
171 FcCacheWriteString (FILE *f, const FcChar8 *string)
172 {
173
174 if (PUTC ('"', f) == EOF)
175 return FcFalse;
176 if (!FcCacheWriteChars (f, string))
177 return FcFalse;
178 if (PUTC ('"', f) == EOF)
179 return FcFalse;
180 return FcTrue;
181 }
182
183 static FcBool
184 FcCacheWritePath (FILE *f, const FcChar8 *dir, const FcChar8 *file)
185 {
186 if (PUTC ('"', f) == EOF)
187 return FcFalse;
188 if (dir)
189 if (!FcCacheWriteChars (f, dir))
190 return FcFalse;
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
200 if (dir && dir[strlen((const char *) dir) - 1] != '/')
201 if (PUTC ('/', f) == EOF)
202 return FcFalse;
203 #endif
204 if (!FcCacheWriteChars (f, file))
205 return FcFalse;
206 if (PUTC ('"', f) == EOF)
207 return FcFalse;
208 return FcTrue;
209 }
210
211 static FcBool
212 FcCacheWriteUlong (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)
220 {
221 temp /= 10;
222 pow *= 10;
223 }
224 temp = t;
225 while (pow)
226 {
227 digit = temp / pow;
228 if (PUTC ((char) digit + '0', f) == EOF)
229 return FcFalse;
230 temp = temp - pow * digit;
231 pow = pow / 10;
232 }
233 return FcTrue;
234 }
235
236 static FcBool
237 FcCacheWriteInt (FILE *f, int i)
238 {
239 return FcCacheWriteUlong (f, (unsigned long) i);
240 }
241
242 static FcBool
243 FcCacheWriteTime (FILE *f, time_t t)
244 {
245 return FcCacheWriteUlong (f, (unsigned long) t);
246 }
247
248 static FcBool
249 FcCacheFontSetAdd (FcFontSet *set,
250 FcStrSet *dirs,
251 const FcChar8 *dir,
252 int dir_len,
253 const FcChar8 *file,
254 const FcChar8 *name,
255 FcConfig *config)
256 {
257 FcChar8 path_buf[8192], *path;
258 int len;
259 FcBool ret = FcFalse;
260 FcPattern *font;
261 FcPattern *frozen;
262
263 path = path_buf;
264 len = (dir_len + 1 + strlen ((const char *) file) + 1);
265 if (len > sizeof (path_buf))
266 {
267 path = malloc (len); /* freed down below */
268 if (!path)
269 return FcFalse;
270 }
271 strncpy ((char *) path, (const char *) dir, dir_len);
272 #ifdef _WIN32
273 if (dir[dir_len - 1] != '/' && dir[dir_len - 1] != '\\' )
274 path[dir_len++] = '\\';
275 #else
276 if (dir[dir_len - 1] != '/')
277 path[dir_len++] = '/';
278 #endif
279 strcpy ((char *) path + dir_len, (const char *) file);
280 if (config && !FcConfigAcceptFilename (config, path))
281 ret = FcTrue;
282 else if (!FcStrCmp (name, FC_FONT_FILE_DIR))
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 {
297 FcChar8 *family;
298
299 if (FcDebug () & FC_DBG_CACHEV)
300 printf (" dir cache file \"%s\"\n", file);
301 ret = FcPatternAddString (font, FC_FILE, path);
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)))
309 {
310 frozen = FcPatternFreeze (font);
311 ret = (frozen != 0);
312 if (ret)
313 ret = FcFontSetAdd (set, frozen);
314 }
315 FcPatternDestroy (font);
316 }
317 }
318 if (path != path_buf) free (path);
319 return ret;
320
321 }
322
323 static unsigned int
324 FcCacheHash (const FcChar8 *string, int len)
325 {
326 unsigned int h = 0;
327 FcChar8 c;
328
329 while (len-- && (c = *string++))
330 h = (h << 1) ^ c;
331 return h;
332 }
333
334 /*
335 * Verify the saved timestamp for a file
336 */
337 FcBool
338 FcGlobalCacheCheckTime (const FcChar8 *file, FcGlobalCacheInfo *info)
339 {
340 struct stat statb;
341
342 if (stat ((char *) file, &statb) < 0)
343 {
344 if (FcDebug () & FC_DBG_CACHE)
345 printf (" file %s missing\n", file);
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
358 void
359 FcGlobalCacheReferenced (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
377 typedef struct _FcFilePathInfo {
378 const FcChar8 *dir;
379 int dir_len;
380 const FcChar8 *base;
381 unsigned int base_hash;
382 } FcFilePathInfo;
383
384 static FcFilePathInfo
385 FcFilePathInfoGet (const FcChar8 *path)
386 {
387 FcFilePathInfo i;
388 FcChar8 *slash;
389
390 slash = FcStrLastSlash (path);
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 }
405 i.base_hash = FcCacheHash (i.base, -1);
406 return i;
407 }
408
409 FcGlobalCacheDir *
410 FcGlobalCacheDirGet (FcGlobalCache *cache,
411 const FcChar8 *dir,
412 int len,
413 FcBool create_missing)
414 {
415 unsigned int hash = FcCacheHash (dir, len);
416 FcGlobalCacheDir *d, **prev;
417
418 for (prev = &cache->ents[hash % FC_GLOBAL_CACHE_DIR_HASH_SIZE];
419 (d = *prev);
420 prev = &(*prev)->next)
421 {
422 if (d->info.hash == hash && d->len == len &&
423 !strncmp ((const char *) d->info.file,
424 (const char *) dir, len))
425 break;
426 }
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;
435 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCacheDir) + len + 1);
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
452 static FcGlobalCacheInfo *
453 FcGlobalCacheDirAdd (FcGlobalCache *cache,
454 const FcChar8 *dir,
455 time_t time,
456 FcBool replace,
457 FcBool create_missing)
458 {
459 FcGlobalCacheDir *d;
460 FcFilePathInfo i;
461 FcGlobalCacheSubdir *subdir;
462 FcGlobalCacheDir *parent;
463
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;
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;
486 /*
487 * Add this directory to the subdirectory list of the parent
488 */
489 subdir = malloc (sizeof (FcGlobalCacheSubdir));
490 if (!subdir)
491 return 0;
492 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCacheSubdir));
493 subdir->ent = d;
494 subdir->next = parent->subdirs;
495 parent->subdirs = subdir;
496 return &d->info;
497 }
498
499 static void
500 FcGlobalCacheDirDestroy (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;
510 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheFile) +
511 strlen ((char *) f->info.file) + 1 +
512 strlen ((char *) f->name) + 1);
513 free (f);
514 }
515 for (s = d->subdirs; s; s = nexts)
516 {
517 nexts = s->next;
518 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheSubdir));
519 free (s);
520 }
521 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheDir) + d->len + 1);
522 free (d);
523 }
524
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
531 void
532 FcGlobalCacheReferenceSubdir (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
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 */
549 FcBool
550 FcGlobalCacheScanDir (FcFontSet *set,
551 FcStrSet *dirs,
552 FcGlobalCache *cache,
553 const FcChar8 *dir,
554 FcConfig *config)
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;
563 FcBool any_in_cache = FcFalse;
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
575 /*
576 * See if the timestamp recorded in the global cache
577 * matches the directory time, if not, return False
578 */
579 if (!FcGlobalCacheCheckTime (d->info.file, &d->info))
580 {
581 if (FcDebug () & FC_DBG_CACHE)
582 printf ("\tdir cache entry time mismatch\n");
583 return FcFalse;
584 }
585
586 /*
587 * Add files from 'dir' to the fontset
588 */
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);
595 any_in_cache = FcTrue;
596 if (!FcCacheFontSetAdd (set, dirs, dir, dir_len,
597 f->info.file, f->name, config))
598 {
599 cache->broken = FcTrue;
600 return FcFalse;
601 }
602 FcGlobalCacheReferenced (cache, &f->info);
603 }
604 /*
605 * Add directories in 'dir' to 'dirs'
606 */
607 for (subdir = d->subdirs; subdir; subdir = subdir->next)
608 {
609 FcFilePathInfo info = FcFilePathInfoGet (subdir->ent->info.file);
610
611 any_in_cache = FcTrue;
612 if (!FcCacheFontSetAdd (set, dirs, dir, dir_len,
613 info.base, FC_FONT_FILE_DIR, config))
614 {
615 cache->broken = FcTrue;
616 return FcFalse;
617 }
618 FcGlobalCacheReferenced (cache, &subdir->ent->info);
619 }
620
621 FcGlobalCacheReferenced (cache, &d->info);
622
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;
632 }
633
634 /*
635 * Locate the cache entry for a particular file
636 */
637 FcGlobalCacheFile *
638 FcGlobalCacheFileGet (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)
663 *count = max + 1;
664 return match;
665 }
666
667 /*
668 * Add a file entry to the cache
669 */
670 static FcGlobalCacheInfo *
671 FcGlobalCacheFileAdd (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;
682 int size;
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 }
697 if (*prev)
698 {
699 if (!replace)
700 return 0;
701
702 f = *prev;
703 if (f->info.referenced)
704 cache->referenced--;
705 *prev = f->next;
706 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheFile) +
707 strlen ((char *) f->info.file) + 1 +
708 strlen ((char *) f->name) + 1);
709 free (f);
710 }
711 size = (sizeof (FcGlobalCacheFile) +
712 strlen ((char *) i.base) + 1 +
713 strlen ((char *) name) + 1);
714 f = malloc (size);
715 if (!f)
716 return 0;
717 FcMemAlloc (FC_MEM_CACHE, size);
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;
729 }
730
731 FcGlobalCache *
732 FcGlobalCacheCreate (void)
733 {
734 FcGlobalCache *cache;
735 int h;
736
737 cache = malloc (sizeof (FcGlobalCache));
738 if (!cache)
739 return 0;
740 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCache));
741 for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
742 cache->ents[h] = 0;
743 cache->entries = 0;
744 cache->referenced = 0;
745 cache->updated = FcFalse;
746 cache->broken = FcFalse;
747 return cache;
748 }
749
750 void
751 FcGlobalCacheDestroy (FcGlobalCache *cache)
752 {
753 FcGlobalCacheDir *d, *next;
754 int h;
755
756 for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
757 {
758 for (d = cache->ents[h]; d; d = next)
759 {
760 next = d->next;
761 FcGlobalCacheDirDestroy (d);
762 }
763 }
764 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCache));
765 free (cache);
766 }
767
768 /*
769 * Cache file syntax is quite simple:
770 *
771 * "file_name" id time "font_name" \n
772 */
773
774 void
775 FcGlobalCacheLoad (FcGlobalCache *cache,
776 const FcChar8 *cache_file)
777 {
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;
784
785 f = fopen ((char *) cache_file, "r");
786 if (!f)
787 return;
788
789 cache->updated = FcFalse;
790 file = 0;
791 name = 0;
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))))
796 {
797 if (FcDebug () & FC_DBG_CACHEV)
798 printf ("FcGlobalCacheLoad \"%s\" \"%20.20s\"\n", file, name);
799 if (!FcStrCmp (name, FC_FONT_FILE_DIR))
800 info = FcGlobalCacheDirAdd (cache, file, time, FcFalse, FcTrue);
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);
810 if (file != file_buf)
811 free (file);
812 if (name != name_buf)
813 free (name);
814 file = 0;
815 name = 0;
816 }
817 if (file && file != file_buf)
818 free (file);
819 if (name && name != name_buf)
820 free (name);
821 fclose (f);
822 }
823
824 FcBool
825 FcGlobalCacheUpdate (FcGlobalCache *cache,
826 const FcChar8 *file,
827 int id,
828 const FcChar8 *name)
829 {
830 const FcChar8 *match;
831 struct stat statb;
832 FcGlobalCacheInfo *info;
833
834 match = file;
835
836 if (stat ((char *) file, &statb) < 0)
837 return FcFalse;
838 if (S_ISDIR (statb.st_mode))
839 info = FcGlobalCacheDirAdd (cache, file, statb.st_mtime,
840 FcTrue, FcTrue);
841 else
842 info = FcGlobalCacheFileAdd (cache, file, id, statb.st_mtime,
843 name, FcTrue);
844 if (info)
845 {
846 FcGlobalCacheReferenced (cache, info);
847 cache->updated = FcTrue;
848 }
849 else
850 cache->broken = FcTrue;
851 return info != 0;
852 }
853
854 FcBool
855 FcGlobalCacheSave (FcGlobalCache *cache,
856 const FcChar8 *cache_file)
857 {
858 FILE *f;
859 int dir_hash, file_hash;
860 FcGlobalCacheDir *dir;
861 FcGlobalCacheFile *file;
862 FcAtomic *atomic;
863
864 if (!cache->updated && cache->referenced == cache->entries)
865 return FcTrue;
866
867 if (cache->broken)
868 return FcFalse;
869
870 #if defined (HAVE_GETUID) && defined (HAVE_GETEUID)
871 /* Set-UID programs can't safely update the cache */
872 if (getuid () != geteuid ())
873 return FcFalse;
874 #endif
875
876 atomic = FcAtomicCreate (cache_file);
877 if (!atomic)
878 goto bail0;
879 if (!FcAtomicLock (atomic))
880 goto bail1;
881 f = fopen ((char *) FcAtomicNewFile(atomic), "w");
882 if (!f)
883 goto bail2;
884
885 for (dir_hash = 0; dir_hash < FC_GLOBAL_CACHE_DIR_HASH_SIZE; dir_hash++)
886 {
887 for (dir = cache->ents[dir_hash]; dir; dir = dir->next)
888 {
889 if (!dir->info.referenced)
890 continue;
891 if (!FcCacheWriteString (f, dir->info.file))
892 goto bail4;
893 if (PUTC (' ', f) == EOF)
894 goto bail4;
895 if (!FcCacheWriteInt (f, 0))
896 goto bail4;
897 if (PUTC (' ', f) == EOF)
898 goto bail4;
899 if (!FcCacheWriteTime (f, dir->info.time))
900 goto bail4;
901 if (PUTC (' ', f) == EOF)
902 goto bail4;
903 if (!FcCacheWriteString (f, (FcChar8 *) FC_FONT_FILE_DIR))
904 goto bail4;
905 if (PUTC ('\n', f) == EOF)
906 goto bail4;
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;
916 if (PUTC (' ', f) == EOF)
917 goto bail4;
918 if (!FcCacheWriteInt (f, file->id < 0 ? 0 : file->id))
919 goto bail4;
920 if (PUTC (' ', f) == EOF)
921 goto bail4;
922 if (!FcCacheWriteTime (f, file->info.time))
923 goto bail4;
924 if (PUTC (' ', f) == EOF)
925 goto bail4;
926 if (!FcCacheWriteString (f, file->name))
927 goto bail4;
928 if (PUTC ('\n', f) == EOF)
929 goto bail4;
930 }
931 }
932 }
933 }
934
935 if (fclose (f) == EOF)
936 goto bail3;
937
938 if (!FcAtomicReplaceOrig (atomic))
939 goto bail3;
940
941 FcAtomicUnlock (atomic);
942 FcAtomicDestroy (atomic);
943
944 cache->updated = FcFalse;
945 return FcTrue;
946
947 bail4:
948 fclose (f);
949 bail3:
950 FcAtomicDeleteNew (atomic);
951 bail2:
952 FcAtomicUnlock (atomic);
953 bail1:
954 FcAtomicDestroy (atomic);
955 bail0:
956 return FcFalse;
957 }
958
959 FcBool
960 FcDirCacheValid (const FcChar8 *dir)
961 {
962 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
963 struct stat file_stat, dir_stat;
964
965 if (stat ((char *) dir, &dir_stat) < 0)
966 {
967 FcStrFree (cache_file);
968 return FcFalse;
969 }
970 if (stat ((char *) cache_file, &file_stat) < 0)
971 {
972 FcStrFree (cache_file);
973 return FcFalse;
974 }
975 FcStrFree (cache_file);
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
985 FcBool
986 FcDirCacheReadDir (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, FcConfig *config)
987 {
988 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
989 FILE *f;
990 FcChar8 *base;
991 int id;
992 int dir_len;
993 FcChar8 file_buf[8192], *file;
994 FcChar8 name_buf[8192], *name;
995 FcBool ret = FcFalse;
996
997 if (!cache_file)
998 goto bail0;
999
1000 if (FcDebug () & FC_DBG_CACHE)
1001 printf ("FcDirCacheReadDir cache_file \"%s\"\n", cache_file);
1002
1003 f = fopen ((char *) cache_file, "r");
1004 if (!f)
1005 {
1006 if (FcDebug () & FC_DBG_CACHE)
1007 printf (" no cache file\n");
1008 goto bail1;
1009 }
1010
1011 if (!FcDirCacheValid (dir))
1012 {
1013 if (FcDebug () & FC_DBG_CACHE)
1014 printf (" cache file older than directory\n");
1015 goto bail2;
1016 }
1017
1018 base = (FcChar8 *) strrchr ((char *) cache_file, '/');
1019 if (!base)
1020 goto bail2;
1021 base++;
1022 dir_len = base - cache_file;
1023
1024 file = 0;
1025 name = 0;
1026 while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) &&
1027 FcCacheReadInt (f, &id) &&
1028 (name = FcCacheReadString (f, name_buf, sizeof (name_buf))))
1029 {
1030 if (!FcCacheFontSetAdd (set, dirs, cache_file, dir_len,
1031 file, name, config))
1032 goto bail3;
1033 if (file != file_buf)
1034 free (file);
1035 if (name != name_buf)
1036 free (name);
1037 file = name = 0;
1038 }
1039 if (FcDebug () & FC_DBG_CACHE)
1040 printf (" cache loaded\n");
1041
1042 ret = FcTrue;
1043 bail3:
1044 if (file && file != file_buf)
1045 free (file);
1046 if (name && name != name_buf)
1047 free (name);
1048 bail2:
1049 fclose (f);
1050 bail1:
1051 FcStrFree (cache_file);
1052 bail0:
1053 return ret;
1054 }
1055
1056 /*
1057 * return the path from the directory containing 'cache' to 'file'
1058 */
1059
1060 static const FcChar8 *
1061 FcFileBaseName (const FcChar8 *cache, const FcChar8 *file)
1062 {
1063 const FcChar8 *cache_slash;
1064
1065 cache_slash = FcStrLastSlash (cache);
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
1072 FcBool
1073 FcDirCacheWriteDir (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir)
1074 {
1075 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
1076 FcPattern *font;
1077 FILE *f;
1078 FcChar8 *name;
1079 const FcChar8 *file, *base;
1080 int n;
1081 int id;
1082 FcBool ret;
1083 FcStrList *list;
1084
1085 if (!cache_file)
1086 goto bail0;
1087 if (FcDebug () & FC_DBG_CACHE)
1088 printf ("FcDirCacheWriteDir cache_file \"%s\"\n", cache_file);
1089
1090 f = fopen ((char *) cache_file, "w");
1091 if (!f)
1092 {
1093 if (FcDebug () & FC_DBG_CACHE)
1094 printf (" can't create \"%s\"\n", cache_file);
1095 goto bail1;
1096 }
1097
1098 list = FcStrListCreate (dirs);
1099 if (!list)
1100 goto bail2;
1101
1102 while ((dir = FcStrListNext (list)))
1103 {
1104 base = FcFileBaseName (cache_file, dir);
1105 if (!FcCacheWriteString (f, base))
1106 goto bail3;
1107 if (PUTC (' ', f) == EOF)
1108 goto bail3;
1109 if (!FcCacheWriteInt (f, 0))
1110 goto bail3;
1111 if (PUTC (' ', f) == EOF)
1112 goto bail3;
1113 if (!FcCacheWriteString (f, FC_FONT_FILE_DIR))
1114 goto bail3;
1115 if (PUTC ('\n', f) == EOF)
1116 goto bail3;
1117 }
1118
1119 for (n = 0; n < set->nfont; n++)
1120 {
1121 font = set->fonts[n];
1122 if (FcPatternGetString (font, FC_FILE, 0, (FcChar8 **) &file) != FcResultMatch)
1123 goto bail3;
1124 base = FcFileBaseName (cache_file, file);
1125 if (FcPatternGetInteger (font, FC_INDEX, 0, &id) != FcResultMatch)
1126 goto bail3;
1127 if (FcDebug () & FC_DBG_CACHEV)
1128 printf (" write file \"%s\"\n", base);
1129 if (!FcCacheWriteString (f, base))
1130 goto bail3;
1131 if (PUTC (' ', f) == EOF)
1132 goto bail3;
1133 if (!FcCacheWriteInt (f, id))
1134 goto bail3;
1135 if (PUTC (' ', f) == EOF)
1136 goto bail3;
1137 name = FcNameUnparse (font);
1138 if (!name)
1139 goto bail3;
1140 ret = FcCacheWriteString (f, name);
1141 FcStrFree (name);
1142 if (!ret)
1143 goto bail3;
1144 if (PUTC ('\n', f) == EOF)
1145 goto bail3;
1146 }
1147
1148 FcStrListDone (list);
1149
1150 if (fclose (f) == EOF)
1151 goto bail1;
1152
1153 FcStrFree (cache_file);
1154
1155 if (FcDebug () & FC_DBG_CACHE)
1156 printf (" cache written\n");
1157 return FcTrue;
1158
1159 bail3:
1160 FcStrListDone (list);
1161 bail2:
1162 fclose (f);
1163 bail1:
1164 unlink ((char *) cache_file);
1165 FcStrFree (cache_file);
1166 bail0:
1167 return FcFalse;
1168 }