]> git.wh0rd.org - fontconfig.git/blob - src/fccache.c
Emit and verify machine signature (sizeof (stuff) + endianness) in cache
[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 <fcntl.h>
26 #include <dirent.h>
27 #include <sys/mman.h>
28 #include <sys/utsname.h>
29 #include "fcint.h"
30
31 #define ENDIAN_TEST 0x12345678
32 #define MACHINE_SIGNATURE_SIZE 9 + 5*19 + 1
33
34 static char *
35 FcCacheProduceMachineSignature (void);
36
37 /*
38 * POSIX has broken stdio so that getc must do thread-safe locking,
39 * this is a serious performance problem for applications doing large
40 * amounts of IO with getc (as is done here). If available, use
41 * the getc_unlocked varient instead.
42 */
43
44 #if defined(getc_unlocked) || defined(_IO_getc_unlocked)
45 #define GETC(f) getc_unlocked(f)
46 #define PUTC(c,f) putc_unlocked(c,f)
47 #else
48 #define GETC(f) getc(f)
49 #define PUTC(c,f) putc(c,f)
50 #endif
51
52 #define FC_DBG_CACHE_REF 1024
53
54 #define PAGESIZE 8192
55
56 static FcBool force;
57
58 static FcChar8 *
59 FcCacheReadString (FILE *f, FcChar8 *dest, int len)
60 {
61 int c;
62 FcBool escape;
63 FcChar8 *d;
64 int size;
65 int i;
66
67 while ((c = GETC (f)) != EOF)
68 if (c == '"')
69 break;
70 if (c == EOF)
71 return FcFalse;
72 if (len == 0)
73 return FcFalse;
74
75 size = len;
76 i = 0;
77 d = dest;
78 escape = FcFalse;
79 while ((c = GETC (f)) != EOF)
80 {
81 if (!escape)
82 {
83 switch (c) {
84 case '"':
85 c = '\0';
86 break;
87 case '\\':
88 escape = FcTrue;
89 continue;
90 }
91 }
92 if (i == size)
93 {
94 FcChar8 *new = malloc (size * 2); /* freed in caller */
95 if (!new)
96 break;
97 memcpy (new, d, size);
98 size *= 2;
99 if (d != dest)
100 free (d);
101 d = new;
102 }
103 d[i++] = c;
104 if (c == '\0')
105 return d;
106 escape = FcFalse;
107 }
108 if (d != dest)
109 free (d);
110 return 0;
111 }
112
113 static FcBool
114 FcCacheReadUlong (FILE *f, unsigned long *dest)
115 {
116 unsigned long t;
117 int c;
118
119 while ((c = GETC (f)) != EOF)
120 {
121 if (!isspace (c))
122 break;
123 }
124 if (c == EOF)
125 return FcFalse;
126 t = 0;
127 for (;;)
128 {
129 if (c == EOF || isspace (c))
130 break;
131 if (!isdigit (c))
132 return FcFalse;
133 t = t * 10 + (c - '0');
134 c = GETC (f);
135 }
136 *dest = t;
137 return FcTrue;
138 }
139
140 static FcBool
141 FcCacheReadInt (FILE *f, int *dest)
142 {
143 unsigned long t;
144 FcBool ret;
145
146 ret = FcCacheReadUlong (f, &t);
147 if (ret)
148 *dest = (int) t;
149 return ret;
150 }
151
152 static FcBool
153 FcCacheReadTime (FILE *f, time_t *dest)
154 {
155 unsigned long t;
156 FcBool ret;
157
158 ret = FcCacheReadUlong (f, &t);
159 if (ret)
160 *dest = (time_t) t;
161 return ret;
162 }
163
164 static FcBool
165 FcCacheWriteChars (FILE *f, const FcChar8 *chars)
166 {
167 FcChar8 c;
168 while ((c = *chars++))
169 {
170 switch (c) {
171 case '"':
172 case '\\':
173 if (PUTC ('\\', f) == EOF)
174 return FcFalse;
175 /* fall through */
176 default:
177 if (PUTC (c, f) == EOF)
178 return FcFalse;
179 }
180 }
181 return FcTrue;
182 }
183
184 static FcBool
185 FcCacheWriteString (FILE *f, const FcChar8 *string)
186 {
187
188 if (PUTC ('"', f) == EOF)
189 return FcFalse;
190 if (!FcCacheWriteChars (f, string))
191 return FcFalse;
192 if (PUTC ('"', f) == EOF)
193 return FcFalse;
194 return FcTrue;
195 }
196
197 static FcBool
198 FcCacheWritePath (FILE *f, const FcChar8 *dir, const FcChar8 *file)
199 {
200 if (PUTC ('"', f) == EOF)
201 return FcFalse;
202 if (dir)
203 if (!FcCacheWriteChars (f, dir))
204 return FcFalse;
205 #ifdef _WIN32
206 if (dir &&
207 dir[strlen((const char *) dir) - 1] != '/' &&
208 dir[strlen((const char *) dir) - 1] != '\\')
209 {
210 if (!FcCacheWriteChars (f, "\\"))
211 return FcFalse;
212 }
213 #else
214 if (dir && dir[strlen((const char *) dir) - 1] != '/')
215 if (PUTC ('/', f) == EOF)
216 return FcFalse;
217 #endif
218 if (!FcCacheWriteChars (f, file))
219 return FcFalse;
220 if (PUTC ('"', f) == EOF)
221 return FcFalse;
222 return FcTrue;
223 }
224
225 static FcBool
226 FcCacheWriteUlong (FILE *f, unsigned long t)
227 {
228 int pow;
229 unsigned long temp, digit;
230
231 temp = t;
232 pow = 1;
233 while (temp >= 10)
234 {
235 temp /= 10;
236 pow *= 10;
237 }
238 temp = t;
239 while (pow)
240 {
241 digit = temp / pow;
242 if (PUTC ((char) digit + '0', f) == EOF)
243 return FcFalse;
244 temp = temp - pow * digit;
245 pow = pow / 10;
246 }
247 return FcTrue;
248 }
249
250 static FcBool
251 FcCacheWriteInt (FILE *f, int i)
252 {
253 return FcCacheWriteUlong (f, (unsigned long) i);
254 }
255
256 static FcBool
257 FcCacheWriteTime (FILE *f, time_t t)
258 {
259 return FcCacheWriteUlong (f, (unsigned long) t);
260 }
261
262 static FcChar8 *
263 FcCacheReadString2 (int fd, FcChar8 *dest, int len)
264 {
265 FcChar8 c;
266 FcBool escape;
267 int size;
268 int i;
269
270 if (len == 0)
271 return FcFalse;
272
273 size = len;
274 i = 0;
275 escape = FcFalse;
276 while (read (fd, &c, 1) == 1)
277 {
278 if (!escape)
279 {
280 switch (c) {
281 case '"':
282 c = '\0';
283 break;
284 case '\\':
285 escape = FcTrue;
286 continue;
287 }
288 }
289 if (i == size)
290 {
291 dest[i++] = 0;
292 return dest;
293 }
294 dest[i++] = c;
295 if (c == '\0')
296 return dest;
297 escape = FcFalse;
298 }
299 return 0;
300 }
301
302 static FcBool
303 FcCacheWriteString2 (int fd, const FcChar8 *chars)
304 {
305 if (write (fd, chars, strlen(chars)+1) != strlen(chars)+1)
306 return FcFalse;
307 return FcTrue;
308 }
309
310 static FcBool
311 FcCacheFontSetAdd (FcFontSet *set,
312 FcStrSet *dirs,
313 const FcChar8 *dir,
314 int dir_len,
315 const FcChar8 *file,
316 const FcChar8 *name,
317 FcConfig *config)
318 {
319 FcChar8 path_buf[8192], *path;
320 int len;
321 FcBool ret = FcFalse;
322 FcPattern *font;
323 FcPattern *frozen;
324
325 path = path_buf;
326 len = (dir_len + 1 + strlen ((const char *) file) + 1);
327 if (len > sizeof (path_buf))
328 {
329 path = malloc (len); /* freed down below */
330 if (!path)
331 return FcFalse;
332 }
333 strncpy ((char *) path, (const char *) dir, dir_len);
334 #ifdef _WIN32
335 if (dir[dir_len - 1] != '/' && dir[dir_len - 1] != '\\' )
336 path[dir_len++] = '\\';
337 #else
338 if (dir[dir_len - 1] != '/')
339 path[dir_len++] = '/';
340 #endif
341 strcpy ((char *) path + dir_len, (const char *) file);
342 if (config && !FcConfigAcceptFilename (config, path))
343 ret = FcTrue;
344 else if (!FcStrCmp (name, FC_FONT_FILE_DIR))
345 {
346 if (FcDebug () & FC_DBG_CACHEV)
347 printf (" dir cache dir \"%s\"\n", path);
348 ret = FcStrSetAdd (dirs, path);
349 }
350 else if (!FcStrCmp (name, FC_FONT_FILE_INVALID))
351 {
352 ret = FcTrue;
353 }
354 else
355 {
356 font = FcNameParse (name);
357 if (font)
358 {
359 FcChar8 *family;
360
361 if (FcDebug () & FC_DBG_CACHEV)
362 printf (" dir cache file \"%s\"\n", file);
363 ret = FcPatternAddString (font, FC_FILE, path);
364 /*
365 * Make sure the pattern has the file name as well as
366 * already containing at least one family name.
367 */
368 if (ret &&
369 FcPatternGetString (font, FC_FAMILY, 0, &family) == FcResultMatch &&
370 (!config || FcConfigAcceptFont (config, font)))
371 {
372 frozen = FcPatternFreeze (font);
373 ret = (frozen != 0);
374 if (ret)
375 ret = FcFontSetAdd (set, frozen);
376 }
377 FcPatternDestroy (font);
378 }
379 }
380 if (path != path_buf) free (path);
381 return ret;
382
383 }
384
385 static unsigned int
386 FcCacheHash (const FcChar8 *string, int len)
387 {
388 unsigned int h = 0;
389 FcChar8 c;
390
391 while (len-- && (c = *string++))
392 h = (h << 1) ^ c;
393 return h;
394 }
395
396 /*
397 * Verify the saved timestamp for a file
398 */
399 FcBool
400 FcGlobalCacheCheckTime (const FcChar8 *file, FcGlobalCacheInfo *info)
401 {
402 struct stat statb;
403
404 if (stat ((char *) file, &statb) < 0)
405 {
406 if (FcDebug () & FC_DBG_CACHE)
407 printf (" file %s missing\n", file);
408 return FcFalse;
409 }
410 if (statb.st_mtime != info->time)
411 {
412 if (FcDebug () & FC_DBG_CACHE)
413 printf (" timestamp mismatch (was %d is %d)\n",
414 (int) info->time, (int) statb.st_mtime);
415 return FcFalse;
416 }
417 return FcTrue;
418 }
419
420 void
421 FcGlobalCacheReferenced (FcGlobalCache *cache,
422 FcGlobalCacheInfo *info)
423 {
424 if (!info->referenced)
425 {
426 info->referenced = FcTrue;
427 cache->referenced++;
428 if (FcDebug () & FC_DBG_CACHE_REF)
429 printf ("Reference %d %s\n", cache->referenced, info->file);
430 }
431 }
432
433 /*
434 * Break a path into dir/base elements and compute the base hash
435 * and the dir length. This is shared between the functions
436 * which walk the file caches
437 */
438
439 typedef struct _FcFilePathInfo {
440 const FcChar8 *dir;
441 int dir_len;
442 const FcChar8 *base;
443 unsigned int base_hash;
444 } FcFilePathInfo;
445
446 static FcFilePathInfo
447 FcFilePathInfoGet (const FcChar8 *path)
448 {
449 FcFilePathInfo i;
450 FcChar8 *slash;
451
452 slash = FcStrLastSlash (path);
453 if (slash)
454 {
455 i.dir = path;
456 i.dir_len = slash - path;
457 if (!i.dir_len)
458 i.dir_len = 1;
459 i.base = slash + 1;
460 }
461 else
462 {
463 i.dir = (const FcChar8 *) ".";
464 i.dir_len = 1;
465 i.base = path;
466 }
467 i.base_hash = FcCacheHash (i.base, -1);
468 return i;
469 }
470
471 FcGlobalCacheDir *
472 FcGlobalCacheDirGet (FcGlobalCache *cache,
473 const FcChar8 *dir,
474 int len,
475 FcBool create_missing)
476 {
477 unsigned int hash = FcCacheHash (dir, len);
478 FcGlobalCacheDir *d, **prev;
479
480 for (prev = &cache->ents[hash % FC_GLOBAL_CACHE_DIR_HASH_SIZE];
481 (d = *prev);
482 prev = &(*prev)->next)
483 {
484 if (d->info.hash == hash && d->len == len &&
485 !strncmp ((const char *) d->info.file,
486 (const char *) dir, len))
487 break;
488 }
489 if (!(d = *prev))
490 {
491 int i;
492 if (!create_missing)
493 return 0;
494 d = malloc (sizeof (FcGlobalCacheDir) + len + 1);
495 if (!d)
496 return 0;
497 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCacheDir) + len + 1);
498 d->next = *prev;
499 *prev = d;
500 d->info.hash = hash;
501 d->info.file = (FcChar8 *) (d + 1);
502 strncpy ((char *) d->info.file, (const char *) dir, len);
503 d->info.file[len] = '\0';
504 d->info.time = 0;
505 d->info.referenced = FcFalse;
506 d->len = len;
507 for (i = 0; i < FC_GLOBAL_CACHE_FILE_HASH_SIZE; i++)
508 d->ents[i] = 0;
509 d->subdirs = 0;
510 }
511 return d;
512 }
513
514 static FcGlobalCacheInfo *
515 FcGlobalCacheDirAdd (FcGlobalCache *cache,
516 const FcChar8 *dir,
517 time_t time,
518 FcBool replace,
519 FcBool create_missing)
520 {
521 FcGlobalCacheDir *d;
522 FcFilePathInfo i;
523 FcGlobalCacheSubdir *subdir;
524 FcGlobalCacheDir *parent;
525
526 i = FcFilePathInfoGet (dir);
527 parent = FcGlobalCacheDirGet (cache, i.dir, i.dir_len, create_missing);
528 /*
529 * Tricky here -- directories containing fonts.cache-1 files
530 * need entries only when the parent doesn't have a cache file.
531 * That is, when the parent already exists in the cache, is
532 * referenced and has a "real" timestamp. The time of 0 is
533 * special and marks directories which got stuck in the
534 * global cache for this very reason. Yes, it could
535 * use a separate boolean field, and probably should.
536 */
537 if (!parent || (!create_missing &&
538 (!parent->info.referenced ||
539 (parent->info.time == 0))))
540 return 0;
541 /*
542 * Add this directory to the cache
543 */
544 d = FcGlobalCacheDirGet (cache, dir, strlen ((const char *) dir), FcTrue);
545 if (!d)
546 return 0;
547 d->info.time = time;
548 /*
549 * Add this directory to the subdirectory list of the parent
550 */
551 subdir = malloc (sizeof (FcGlobalCacheSubdir));
552 if (!subdir)
553 return 0;
554 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCacheSubdir));
555 subdir->ent = d;
556 subdir->next = parent->subdirs;
557 parent->subdirs = subdir;
558 return &d->info;
559 }
560
561 static void
562 FcGlobalCacheDirDestroy (FcGlobalCacheDir *d)
563 {
564 FcGlobalCacheFile *f, *next;
565 int h;
566 FcGlobalCacheSubdir *s, *nexts;
567
568 for (h = 0; h < FC_GLOBAL_CACHE_FILE_HASH_SIZE; h++)
569 for (f = d->ents[h]; f; f = next)
570 {
571 next = f->next;
572 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheFile) +
573 strlen ((char *) f->info.file) + 1 +
574 strlen ((char *) f->name) + 1);
575 free (f);
576 }
577 for (s = d->subdirs; s; s = nexts)
578 {
579 nexts = s->next;
580 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheSubdir));
581 free (s);
582 }
583 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheDir) + d->len + 1);
584 free (d);
585 }
586
587 /*
588 * If the parent is in the global cache and referenced, add
589 * an entry for 'dir' to the global cache. This is used
590 * for directories with fonts.cache files
591 */
592
593 void
594 FcGlobalCacheReferenceSubdir (FcGlobalCache *cache,
595 const FcChar8 *dir)
596 {
597 FcGlobalCacheInfo *info;
598 info = FcGlobalCacheDirAdd (cache, dir, 0, FcFalse, FcFalse);
599 if (info && !info->referenced)
600 {
601 info->referenced = FcTrue;
602 cache->referenced++;
603 }
604 }
605
606 /*
607 * Check to see if the global cache contains valid data for 'dir'.
608 * If so, scan the global cache for files and directories in 'dir'.
609 * else, return False.
610 */
611 FcBool
612 FcGlobalCacheScanDir (FcFontSet *set,
613 FcStrSet *dirs,
614 FcGlobalCache *cache,
615 const FcChar8 *dir,
616 FcConfig *config)
617 {
618 FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, dir,
619 strlen ((const char *) dir),
620 FcFalse);
621 FcGlobalCacheFile *f;
622 int h;
623 int dir_len;
624 FcGlobalCacheSubdir *subdir;
625 FcBool any_in_cache = FcFalse;
626
627 if (FcDebug() & FC_DBG_CACHE)
628 printf ("FcGlobalCacheScanDir %s\n", dir);
629
630 if (!d)
631 {
632 if (FcDebug () & FC_DBG_CACHE)
633 printf ("\tNo dir cache entry\n");
634 return FcFalse;
635 }
636
637 /*
638 * See if the timestamp recorded in the global cache
639 * matches the directory time, if not, return False
640 */
641 if (!FcGlobalCacheCheckTime (d->info.file, &d->info))
642 {
643 if (FcDebug () & FC_DBG_CACHE)
644 printf ("\tdir cache entry time mismatch\n");
645 return FcFalse;
646 }
647
648 /*
649 * Add files from 'dir' to the fontset
650 */
651 dir_len = strlen ((const char *) dir);
652 for (h = 0; h < FC_GLOBAL_CACHE_FILE_HASH_SIZE; h++)
653 for (f = d->ents[h]; f; f = f->next)
654 {
655 if (FcDebug() & FC_DBG_CACHEV)
656 printf ("FcGlobalCacheScanDir add file %s\n", f->info.file);
657 any_in_cache = FcTrue;
658 if (!FcCacheFontSetAdd (set, dirs, dir, dir_len,
659 f->info.file, f->name, config))
660 {
661 cache->broken = FcTrue;
662 return FcFalse;
663 }
664 FcGlobalCacheReferenced (cache, &f->info);
665 }
666 /*
667 * Add directories in 'dir' to 'dirs'
668 */
669 for (subdir = d->subdirs; subdir; subdir = subdir->next)
670 {
671 FcFilePathInfo info = FcFilePathInfoGet (subdir->ent->info.file);
672
673 any_in_cache = FcTrue;
674 if (!FcCacheFontSetAdd (set, dirs, dir, dir_len,
675 info.base, FC_FONT_FILE_DIR, config))
676 {
677 cache->broken = FcTrue;
678 return FcFalse;
679 }
680 FcGlobalCacheReferenced (cache, &subdir->ent->info);
681 }
682
683 FcGlobalCacheReferenced (cache, &d->info);
684
685 /*
686 * To recover from a bug in previous versions of fontconfig,
687 * return FcFalse if no entries in the cache were found
688 * for this directory. This will cause any empty directories
689 * to get rescanned every time fontconfig is initialized. This
690 * might get removed at some point when the older cache files are
691 * presumably fixed.
692 */
693 return any_in_cache;
694 }
695
696 /*
697 * Locate the cache entry for a particular file
698 */
699 FcGlobalCacheFile *
700 FcGlobalCacheFileGet (FcGlobalCache *cache,
701 const FcChar8 *file,
702 int id,
703 int *count)
704 {
705 FcFilePathInfo i = FcFilePathInfoGet (file);
706 FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, i.dir,
707 i.dir_len, FcFalse);
708 FcGlobalCacheFile *f, *match = 0;
709 int max = -1;
710
711 if (!d)
712 return 0;
713 for (f = d->ents[i.base_hash % FC_GLOBAL_CACHE_FILE_HASH_SIZE]; f; f = f->next)
714 {
715 if (f->info.hash == i.base_hash &&
716 !strcmp ((const char *) f->info.file, (const char *) i.base))
717 {
718 if (f->id == id)
719 match = f;
720 if (f->id > max)
721 max = f->id;
722 }
723 }
724 if (count)
725 *count = max + 1;
726 return match;
727 }
728
729 /*
730 * Add a file entry to the cache
731 */
732 static FcGlobalCacheInfo *
733 FcGlobalCacheFileAdd (FcGlobalCache *cache,
734 const FcChar8 *path,
735 int id,
736 time_t time,
737 const FcChar8 *name,
738 FcBool replace)
739 {
740 FcFilePathInfo i = FcFilePathInfoGet (path);
741 FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, i.dir,
742 i.dir_len, FcTrue);
743 FcGlobalCacheFile *f, **prev;
744 int size;
745
746 if (!d)
747 return 0;
748 for (prev = &d->ents[i.base_hash % FC_GLOBAL_CACHE_FILE_HASH_SIZE];
749 (f = *prev);
750 prev = &(*prev)->next)
751 {
752 if (f->info.hash == i.base_hash &&
753 f->id == id &&
754 !strcmp ((const char *) f->info.file, (const char *) i.base))
755 {
756 break;
757 }
758 }
759 if (*prev)
760 {
761 if (!replace)
762 return 0;
763
764 f = *prev;
765 if (f->info.referenced)
766 cache->referenced--;
767 *prev = f->next;
768 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheFile) +
769 strlen ((char *) f->info.file) + 1 +
770 strlen ((char *) f->name) + 1);
771 free (f);
772 }
773 size = (sizeof (FcGlobalCacheFile) +
774 strlen ((char *) i.base) + 1 +
775 strlen ((char *) name) + 1);
776 f = malloc (size);
777 if (!f)
778 return 0;
779 FcMemAlloc (FC_MEM_CACHE, size);
780 f->next = *prev;
781 *prev = f;
782 f->info.hash = i.base_hash;
783 f->info.file = (FcChar8 *) (f + 1);
784 f->info.time = time;
785 f->info.referenced = FcFalse;
786 f->id = id;
787 f->name = f->info.file + strlen ((char *) i.base) + 1;
788 strcpy ((char *) f->info.file, (const char *) i.base);
789 strcpy ((char *) f->name, (const char *) name);
790 return &f->info;
791 }
792
793 FcGlobalCache *
794 FcGlobalCacheCreate (void)
795 {
796 FcGlobalCache *cache;
797 int h;
798
799 cache = malloc (sizeof (FcGlobalCache));
800 if (!cache)
801 return 0;
802 FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCache));
803 for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
804 cache->ents[h] = 0;
805 cache->entries = 0;
806 cache->referenced = 0;
807 cache->updated = FcFalse;
808 cache->broken = FcFalse;
809 return cache;
810 }
811
812 void
813 FcGlobalCacheDestroy (FcGlobalCache *cache)
814 {
815 FcGlobalCacheDir *d, *next;
816 int h;
817
818 for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
819 {
820 for (d = cache->ents[h]; d; d = next)
821 {
822 next = d->next;
823 FcGlobalCacheDirDestroy (d);
824 }
825 }
826 FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCache));
827 free (cache);
828 }
829
830 /*
831 * Cache file syntax is quite simple:
832 *
833 * "file_name" id time "font_name" \n
834 */
835
836 void
837 FcGlobalCacheLoad (FcGlobalCache *cache,
838 const FcChar8 *cache_file)
839 {
840 FILE *f;
841 FcChar8 file_buf[8192], *file;
842 int id;
843 time_t time;
844 FcChar8 name_buf[8192], *name;
845 FcGlobalCacheInfo *info;
846
847 f = fopen ((char *) cache_file, "r");
848 if (!f)
849 return;
850
851 cache->updated = FcFalse;
852 file = 0;
853 name = 0;
854 while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) &&
855 FcCacheReadInt (f, &id) &&
856 FcCacheReadTime (f, &time) &&
857 (name = FcCacheReadString (f, name_buf, sizeof (name_buf))))
858 {
859 if (FcDebug () & FC_DBG_CACHEV)
860 printf ("FcGlobalCacheLoad \"%s\" \"%20.20s\"\n", file, name);
861 if (!FcStrCmp (name, FC_FONT_FILE_DIR))
862 info = FcGlobalCacheDirAdd (cache, file, time, FcFalse, FcTrue);
863 else
864 info = FcGlobalCacheFileAdd (cache, file, id, time, name, FcFalse);
865 if (!info)
866 cache->broken = FcTrue;
867 else
868 cache->entries++;
869 if (FcDebug () & FC_DBG_CACHE_REF)
870 printf ("FcGlobalCacheLoad entry %d %s\n",
871 cache->entries, file);
872 if (file != file_buf)
873 free (file);
874 if (name != name_buf)
875 free (name);
876 file = 0;
877 name = 0;
878 }
879 if (file && file != file_buf)
880 free (file);
881 if (name && name != name_buf)
882 free (name);
883 fclose (f);
884 }
885
886 FcBool
887 FcGlobalCacheUpdate (FcGlobalCache *cache,
888 const FcChar8 *file,
889 int id,
890 const FcChar8 *name)
891 {
892 const FcChar8 *match;
893 struct stat statb;
894 FcGlobalCacheInfo *info;
895
896 match = file;
897
898 if (stat ((char *) file, &statb) < 0)
899 return FcFalse;
900 if (S_ISDIR (statb.st_mode))
901 info = FcGlobalCacheDirAdd (cache, file, statb.st_mtime,
902 FcTrue, FcTrue);
903 else
904 info = FcGlobalCacheFileAdd (cache, file, id, statb.st_mtime,
905 name, FcTrue);
906 if (info)
907 {
908 FcGlobalCacheReferenced (cache, info);
909 cache->updated = FcTrue;
910 }
911 else
912 cache->broken = FcTrue;
913 return info != 0;
914 }
915
916 FcBool
917 FcGlobalCacheSave (FcGlobalCache *cache,
918 const FcChar8 *cache_file)
919 {
920 FILE *f;
921 int dir_hash, file_hash;
922 FcGlobalCacheDir *dir;
923 FcGlobalCacheFile *file;
924 FcAtomic *atomic;
925
926 if (!cache->updated && cache->referenced == cache->entries)
927 return FcTrue;
928
929 if (cache->broken)
930 return FcFalse;
931
932 #if defined (HAVE_GETUID) && defined (HAVE_GETEUID)
933 /* Set-UID programs can't safely update the cache */
934 if (getuid () != geteuid ())
935 return FcFalse;
936 #endif
937
938 atomic = FcAtomicCreate (cache_file);
939 if (!atomic)
940 goto bail0;
941 if (!FcAtomicLock (atomic))
942 goto bail1;
943 f = fopen ((char *) FcAtomicNewFile(atomic), "w");
944 if (!f)
945 goto bail2;
946
947 for (dir_hash = 0; dir_hash < FC_GLOBAL_CACHE_DIR_HASH_SIZE; dir_hash++)
948 {
949 for (dir = cache->ents[dir_hash]; dir; dir = dir->next)
950 {
951 if (!dir->info.referenced)
952 continue;
953 if (!FcCacheWriteString (f, dir->info.file))
954 goto bail4;
955 if (PUTC (' ', f) == EOF)
956 goto bail4;
957 if (!FcCacheWriteInt (f, 0))
958 goto bail4;
959 if (PUTC (' ', f) == EOF)
960 goto bail4;
961 if (!FcCacheWriteTime (f, dir->info.time))
962 goto bail4;
963 if (PUTC (' ', f) == EOF)
964 goto bail4;
965 if (!FcCacheWriteString (f, (FcChar8 *) FC_FONT_FILE_DIR))
966 goto bail4;
967 if (PUTC ('\n', f) == EOF)
968 goto bail4;
969
970 for (file_hash = 0; file_hash < FC_GLOBAL_CACHE_FILE_HASH_SIZE; file_hash++)
971 {
972 for (file = dir->ents[file_hash]; file; file = file->next)
973 {
974 if (!file->info.referenced)
975 continue;
976 if (!FcCacheWritePath (f, dir->info.file, file->info.file))
977 goto bail4;
978 if (PUTC (' ', f) == EOF)
979 goto bail4;
980 if (!FcCacheWriteInt (f, file->id < 0 ? 0 : file->id))
981 goto bail4;
982 if (PUTC (' ', f) == EOF)
983 goto bail4;
984 if (!FcCacheWriteTime (f, file->info.time))
985 goto bail4;
986 if (PUTC (' ', f) == EOF)
987 goto bail4;
988 if (!FcCacheWriteString (f, file->name))
989 goto bail4;
990 if (PUTC ('\n', f) == EOF)
991 goto bail4;
992 }
993 }
994 }
995 }
996
997 if (fclose (f) == EOF)
998 goto bail3;
999
1000 if (!FcAtomicReplaceOrig (atomic))
1001 goto bail3;
1002
1003 FcAtomicUnlock (atomic);
1004 FcAtomicDestroy (atomic);
1005
1006 cache->updated = FcFalse;
1007 return FcTrue;
1008
1009 bail4:
1010 fclose (f);
1011 bail3:
1012 FcAtomicDeleteNew (atomic);
1013 bail2:
1014 FcAtomicUnlock (atomic);
1015 bail1:
1016 FcAtomicDestroy (atomic);
1017 bail0:
1018 return FcFalse;
1019 }
1020
1021 /*
1022 * Find the next presumably-mmapable offset after the current file
1023 * pointer.
1024 */
1025 static int
1026 FcCacheNextOffset(off_t w)
1027 {
1028 if (w % PAGESIZE == 0)
1029 return w;
1030 else
1031 return ((w / PAGESIZE)+1)*PAGESIZE;
1032 }
1033
1034 /* return the address of the segment for the provided arch,
1035 * or -1 if arch not found */
1036 static off_t
1037 FcCacheSkipToArch (int fd, const char * arch)
1038 {
1039 char candidate_arch_machine_name_count[MACHINE_SIGNATURE_SIZE + 9];
1040 char * candidate_arch;
1041 off_t current_arch_start = 0;
1042
1043 /* skip arches that are not the current arch */
1044 while (1)
1045 {
1046 long bs;
1047
1048 lseek (fd, current_arch_start, SEEK_SET);
1049 if (FcCacheReadString2 (fd, candidate_arch_machine_name_count,
1050 sizeof (candidate_arch_machine_name_count)) == 0)
1051 break;
1052 if (!strlen(candidate_arch_machine_name_count))
1053 return -1;
1054 bs = strtol(candidate_arch_machine_name_count, &candidate_arch, 16);
1055 candidate_arch++; /* skip leading space */
1056
1057 if (strcmp (candidate_arch, arch)==0)
1058 break;
1059 current_arch_start += bs;
1060 }
1061
1062 if (strcmp (candidate_arch, arch)!=0)
1063 return -1;
1064
1065 return current_arch_start;
1066 }
1067
1068 /* Cuts out the segment at the file pointer (moves everything else
1069 * down to cover it), and leaves the file pointer at the end of the
1070 * file. */
1071 #define BUF_SIZE 8192
1072
1073 static FcBool
1074 FcCacheMoveDown (int fd, off_t start)
1075 {
1076 char * buf = malloc (BUF_SIZE);
1077 char candidate_arch_machine_name[MACHINE_SIGNATURE_SIZE + 9];
1078 long bs;
1079 int c, bytes_skipped;
1080
1081 if (!buf)
1082 return FcFalse;
1083
1084 lseek (fd, start, SEEK_SET);
1085 if (FcCacheReadString2 (fd, candidate_arch_machine_name,
1086 sizeof (candidate_arch_machine_name)) == 0)
1087 goto done;
1088 if (!strlen(candidate_arch_machine_name))
1089 goto done;
1090
1091 bs = strtol(candidate_arch_machine_name, 0, 16);
1092 if (bs == 0)
1093 goto done;
1094
1095 bytes_skipped = 0;
1096 do
1097 {
1098 lseek (fd, start+bs+bytes_skipped, SEEK_SET);
1099 if ((c = read (fd, buf, BUF_SIZE)) <= 0)
1100 break;
1101 lseek (fd, start+bytes_skipped, SEEK_SET);
1102 if (write (fd, buf, c) < 0)
1103 goto bail;
1104 bytes_skipped += c;
1105 }
1106 while (c > 0);
1107 lseek (fd, start+bytes_skipped, SEEK_SET);
1108
1109 done:
1110 free (buf);
1111 return FcTrue;
1112
1113 bail:
1114 free (buf);
1115 return FcFalse;
1116 }
1117
1118 FcBool
1119 FcDirCacheValid (const FcChar8 *dir)
1120 {
1121 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
1122 struct stat file_stat, dir_stat;
1123
1124 if (stat ((char *) dir, &dir_stat) < 0)
1125 {
1126 FcStrFree (cache_file);
1127 return FcFalse;
1128 }
1129 if (stat ((char *) cache_file, &file_stat) < 0)
1130 {
1131 FcStrFree (cache_file);
1132 return FcFalse;
1133 }
1134 FcStrFree (cache_file);
1135 /*
1136 * If the directory has been modified more recently than
1137 * the cache file, the cache is not valid
1138 */
1139 if (dir_stat.st_mtime - file_stat.st_mtime > 0)
1140 return FcFalse;
1141 return FcTrue;
1142 }
1143
1144 static int
1145 FcCacheReadDirs (FcConfig * config, FcGlobalCache * cache,
1146 FcStrList *list, FcFontSet * set)
1147 {
1148 DIR *d;
1149 struct dirent *e;
1150 int ret = 0;
1151 FcChar8 *dir;
1152 FcChar8 *file, *base;
1153 FcStrSet *subdirs;
1154 FcStrList *sublist;
1155 struct stat statb;
1156
1157 /*
1158 * Now scan all of the directories into separate databases
1159 * and write out the results
1160 */
1161 while ((dir = FcStrListNext (list)))
1162 {
1163 /* freed below */
1164 file = (FcChar8 *) malloc (strlen ((char *) dir) + 1 + FC_MAX_FILE_LEN + 1);
1165 if (!file)
1166 return FcFalse;
1167
1168 strcpy ((char *) file, (char *) dir);
1169 strcat ((char *) file, "/");
1170 base = file + strlen ((char *) file);
1171
1172 subdirs = FcStrSetCreate ();
1173 if (!subdirs)
1174 {
1175 fprintf (stderr, "Can't create directory set\n");
1176 ret++;
1177 free (file);
1178 continue;
1179 }
1180
1181 if (access ((char *) dir, X_OK) < 0)
1182 {
1183 switch (errno) {
1184 case ENOENT:
1185 case ENOTDIR:
1186 case EACCES:
1187 break;
1188 default:
1189 fprintf (stderr, "\"%s\": ", dir);
1190 perror ("");
1191 ret++;
1192 }
1193 FcStrSetDestroy (subdirs);
1194 free (file);
1195 continue;
1196 }
1197 if (stat ((char *) dir, &statb) == -1)
1198 {
1199 fprintf (stderr, "\"%s\": ", dir);
1200 perror ("");
1201 FcStrSetDestroy (subdirs);
1202 ret++;
1203 free (file);
1204 continue;
1205 }
1206 if (!S_ISDIR (statb.st_mode))
1207 {
1208 fprintf (stderr, "\"%s\": not a directory, skipping\n", dir);
1209 FcStrSetDestroy (subdirs);
1210 free (file);
1211 continue;
1212 }
1213 d = opendir ((char *) dir);
1214 if (!d)
1215 {
1216 FcStrSetDestroy (subdirs);
1217 free (file);
1218 continue;
1219 }
1220 while ((e = readdir (d)))
1221 {
1222 if (e->d_name[0] != '.' && strlen (e->d_name) < FC_MAX_FILE_LEN)
1223 {
1224 strcpy ((char *) base, (char *) e->d_name);
1225 if (FcFileIsDir (file) && !FcStrSetAdd (subdirs, file))
1226 ret++;
1227 }
1228 }
1229 closedir (d);
1230 if (!FcDirCacheValid (dir) || !FcDirCacheRead (set, dir))
1231 {
1232 if (FcDebug () & FC_DBG_FONTSET)
1233 printf ("scan dir %s\n", dir);
1234 FcDirScanConfig (set, subdirs, cache,
1235 config->blanks, dir, FcFalse, config);
1236 }
1237 sublist = FcStrListCreate (subdirs);
1238 FcStrSetDestroy (subdirs);
1239 if (!sublist)
1240 {
1241 fprintf (stderr, "Can't create subdir list in \"%s\"\n", dir);
1242 ret++;
1243 free (file);
1244 continue;
1245 }
1246 ret += FcCacheReadDirs (config, cache, sublist, set);
1247 free (file);
1248 }
1249 FcStrListDone (list);
1250 return ret;
1251 }
1252
1253 FcFontSet *
1254 FcCacheRead (FcConfig *config, FcGlobalCache * cache)
1255 {
1256 FcFontSet * s = FcFontSetCreate();
1257 if (!s)
1258 return 0;
1259
1260 if (force)
1261 goto bail;
1262
1263 if (FcCacheReadDirs (config, cache, FcConfigGetConfigDirs (config), s))
1264 goto bail;
1265
1266 return s;
1267
1268 bail:
1269 FcFontSetDestroy (s);
1270 return 0;
1271 }
1272
1273 /* read serialized state from the cache file */
1274 FcBool
1275 FcDirCacheRead (FcFontSet * set, const FcChar8 *dir)
1276 {
1277 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
1278 int fd;
1279 FcCache metadata;
1280 void * current_dir_block;
1281 char * current_arch_machine_name;
1282 char candidate_arch_machine_name[9+MACHINE_SIGNATURE_SIZE];
1283 off_t current_arch_start = 0;
1284
1285 if (force)
1286 goto bail;
1287 if (!cache_file)
1288 goto bail;
1289
1290 current_arch_machine_name = FcCacheProduceMachineSignature();
1291 fd = open(cache_file, O_RDONLY);
1292 if (fd == -1)
1293 goto bail;
1294
1295 current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
1296 if (current_arch_start < 0)
1297 goto bail1;
1298
1299 lseek (fd, current_arch_start, SEEK_SET);
1300 if (FcCacheReadString2 (fd, candidate_arch_machine_name,
1301 sizeof (candidate_arch_machine_name)) == 0)
1302 goto bail1;
1303
1304 // sanity check for endianness issues
1305 read(fd, &metadata, sizeof(FcCache));
1306 if (metadata.magic != FC_CACHE_MAGIC)
1307 goto bail1;
1308
1309 if (!metadata.count)
1310 goto bail1;
1311
1312 off_t pos = FcCacheNextOffset (lseek(fd, 0, SEEK_CUR));
1313 current_dir_block = mmap (0, metadata.count,
1314 PROT_READ, MAP_SHARED, fd, pos);
1315 if (current_dir_block == MAP_FAILED)
1316 perror("");
1317
1318 if (!FcFontSetUnserialize (metadata, set, current_dir_block))
1319 goto bail1;
1320
1321 close(fd);
1322 free (cache_file);
1323 return FcTrue;
1324
1325 bail1:
1326 close(fd);
1327 bail:
1328 free (cache_file);
1329 return FcFalse;
1330 }
1331
1332 /* write serialized state to the cache file */
1333 FcBool
1334 FcDirCacheWrite (int bank, FcFontSet *set, const FcChar8 *dir)
1335 {
1336 FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
1337 int fd, bytes_to_write, metadata_bytes;
1338 FcCache metadata;
1339 off_t current_arch_start = 0, truncate_to;
1340 char * current_arch_machine_name, * header;
1341 void * current_dir_block, *final_dir_block;
1342
1343 if (!cache_file)
1344 goto bail;
1345
1346 FcFontSetNewBank();
1347 bytes_to_write = FcFontSetNeededBytes (set);
1348 metadata_bytes = FcCacheNextOffset (sizeof (FcCache));
1349
1350 if (!bytes_to_write)
1351 {
1352 unlink (cache_file);
1353 free (cache_file);
1354 return FcTrue;
1355 }
1356
1357 current_dir_block = malloc (bytes_to_write);
1358 memset (&metadata, 0, sizeof(FcCache));
1359 metadata.count = bytes_to_write;
1360 metadata.bank = bank;
1361 if (!current_dir_block)
1362 goto bail;
1363 final_dir_block = FcFontSetDistributeBytes (&metadata, current_dir_block);
1364
1365 if ((char *)current_dir_block + bytes_to_write != final_dir_block)
1366 goto bail;
1367
1368 if (!FcFontSetSerialize (bank, set))
1369 goto bail;
1370
1371 if (FcDebug () & FC_DBG_CACHE)
1372 printf ("FcDirCacheWriteDir cache_file \"%s\"\n", cache_file);
1373
1374 fd = open(cache_file, O_RDWR | O_CREAT, 0666);
1375 if (fd == -1)
1376 goto bail;
1377
1378 current_arch_machine_name = FcCacheProduceMachineSignature ();
1379 current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
1380 if (current_arch_start < 0)
1381 current_arch_start = FcCacheNextOffset (lseek(fd, 0, SEEK_END));
1382
1383 if (!FcCacheMoveDown(fd, current_arch_start))
1384 goto bail1;
1385
1386 current_arch_start = lseek(fd, 0, SEEK_CUR);
1387 if (ftruncate (fd, current_arch_start) == -1)
1388 goto bail1;
1389
1390 /* now write the address of the next offset */
1391 truncate_to = FcCacheNextOffset (FcCacheNextOffset (current_arch_start + metadata_bytes) + bytes_to_write) - current_arch_start;
1392
1393 header = malloc (10 + strlen (current_arch_machine_name));
1394 sprintf (header, "%8x ", (int)truncate_to);
1395 strcat (header, current_arch_machine_name);
1396 if (!FcCacheWriteString2 (fd, header))
1397 goto bail1;
1398
1399 metadata.magic = FC_CACHE_MAGIC;
1400 write (fd, &metadata, sizeof(FcCache));
1401 lseek (fd, FcCacheNextOffset (lseek(fd, 0, SEEK_END)), SEEK_SET);
1402 write (fd, current_dir_block, bytes_to_write);
1403
1404 /* this actually serves to pad out the cache file, if needed */
1405 if (ftruncate (fd, current_arch_start + truncate_to) == -1)
1406 goto bail1;
1407
1408 close(fd);
1409 return FcTrue;
1410
1411 bail1:
1412 free (current_dir_block);
1413 free (current_arch_machine_name);
1414 bail:
1415 unlink (cache_file);
1416 free (cache_file);
1417 return FcFalse;
1418 }
1419
1420 static char *
1421 FcCacheProduceMachineSignature ()
1422 {
1423 static char buf[MACHINE_SIGNATURE_SIZE];
1424 int magic = ENDIAN_TEST;
1425 char * m = (char *)&magic;
1426
1427 sprintf (buf, "%2x%2x%2x%2x "
1428 "%4x %4x %4x %4x %4x %4x %4x %4x %4x %4x %4x %4x "
1429 "%4x %4x %4x %4x %4x %4x %4x\n",
1430 m[0], m[1], m[2], m[3],
1431 sizeof (char),
1432 sizeof (char *),
1433 sizeof (int),
1434 sizeof (FcPattern),
1435 sizeof (FcPatternEltPtr),
1436 sizeof (struct _FcPatternElt *),
1437 sizeof (FcPatternElt),
1438 sizeof (FcObjectPtr),
1439 sizeof (FcValueListPtr),
1440 sizeof (FcValue),
1441 sizeof (FcValueBinding),
1442 sizeof (struct _FcValueList *),
1443 sizeof (FcCharSet),
1444 sizeof (FcCharLeaf **),
1445 sizeof (FcChar16 *),
1446 sizeof (FcChar16),
1447 sizeof (FcCharLeaf),
1448 sizeof (FcChar32),
1449 sizeof (FcCache));
1450
1451 return buf;
1452 }
1453
1454 /* if true, ignore the cache file */
1455 void
1456 FcCacheForce (FcBool f)
1457 {
1458 force = f;
1459 }
1460
1461 static int banks_ptr = 0, banks_alloc = 0;
1462 static int * bankId = 0;
1463
1464 int
1465 FcCacheBankCount (void)
1466 {
1467 return banks_ptr;
1468 }
1469
1470 FcBool
1471 FcCacheHaveBank (int bank)
1472 {
1473 int i;
1474
1475 if (bank < FC_BANK_FIRST)
1476 return FcTrue;
1477
1478 for (i = 0; i < banks_ptr; i++)
1479 if (bankId[i] == bank)
1480 return FcTrue;
1481
1482 return FcFalse;
1483 }
1484
1485 int
1486 FcCacheBankToIndex (int bank)
1487 {
1488 static int lastBank = FC_BANK_DYNAMIC, lastIndex = -1;
1489 int i;
1490 int * b;
1491
1492 if (bank == lastBank)
1493 return lastIndex;
1494
1495 for (i = 0; i < banks_ptr; i++)
1496 if (bankId[i] == bank)
1497 return i;
1498
1499 if (banks_ptr >= banks_alloc)
1500 {
1501 b = realloc (bankId, (banks_alloc + 4) * sizeof(int));
1502 if (!b)
1503 return -1;
1504
1505 bankId = b;
1506 banks_alloc += 4;
1507 }
1508
1509 i = banks_ptr++;
1510 bankId[i] = bank;
1511 return i;
1512 }