]> git.wh0rd.org Git - fontconfig.git/blob - src/fccache.c
Various config changes plus a couple of optimizations from Owen
[fontconfig.git] / src / fccache.c
1 /*
2  * $XFree86: xc/lib/fontconfig/src/fccache.c,v 1.10 2002/08/06 19:00:43 keithp Exp $
3  *
4  * Copyright © 2000 Keith Packard, member of The XFree86 Project, Inc.
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);
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     if (dir && dir[strlen((const char *) dir) - 1] != '/')
192         if (PUTC ('/', f) == EOF)
193             return FcFalse;
194     if (!FcCacheWriteChars (f, file))
195         return FcFalse;
196     if (PUTC ('"', f) == EOF)
197         return FcFalse;
198     return FcTrue;
199 }
200
201 static FcBool
202 FcCacheWriteUlong (FILE *f, unsigned long t)
203 {
204     int     pow;
205     unsigned long   temp, digit;
206
207     temp = t;
208     pow = 1;
209     while (temp >= 10)
210     {
211         temp /= 10;
212         pow *= 10;
213     }
214     temp = t;
215     while (pow)
216     {
217         digit = temp / pow;
218         if (PUTC ((char) digit + '0', f) == EOF)
219             return FcFalse;
220         temp = temp - pow * digit;
221         pow = pow / 10;
222     }
223     return FcTrue;
224 }
225
226 static FcBool
227 FcCacheWriteInt (FILE *f, int i)
228 {
229     return FcCacheWriteUlong (f, (unsigned long) i);
230 }
231
232 static FcBool
233 FcCacheWriteTime (FILE *f, time_t t)
234 {
235     return FcCacheWriteUlong (f, (unsigned long) t);
236 }
237
238 static FcBool
239 FcCacheFontSetAdd (FcFontSet        *set,
240                    FcStrSet         *dirs,
241                    const FcChar8    *dir,
242                    int              dir_len,
243                    const FcChar8    *file,
244                    const FcChar8    *name)
245 {
246     FcChar8     path_buf[8192], *path;
247     int         len;
248     FcBool      ret = FcFalse;
249     FcPattern   *font;
250
251     path = path_buf;
252     len = (dir_len + 1 + strlen ((const char *) file) + 1);
253     if (len > sizeof (path_buf))
254     {
255         path = malloc (len);
256         if (!path)
257             return FcFalse;
258     }
259     strncpy ((char *) path, (const char *) dir, dir_len);
260     if (dir[dir_len - 1] != '/')
261         path[dir_len++] = '/';
262     strcpy ((char *) path + dir_len, (const char *) file);
263     if (!FcStrCmp (name, FC_FONT_FILE_DIR))
264     {
265         if (FcDebug () & FC_DBG_CACHEV)
266             printf (" dir cache dir \"%s\"\n", path);
267         ret = FcStrSetAdd (dirs, path);
268     }
269     else if (!FcStrCmp (name, FC_FONT_FILE_INVALID))
270     {
271         ret = FcTrue;
272     }
273     else
274     {
275         font = FcNameParse (name);
276         if (font)
277         {
278             if (FcDebug () & FC_DBG_CACHEV)
279                 printf (" dir cache file \"%s\"\n", file);
280             ret = (FcPatternAddString (font, FC_FILE, path) &&
281                    FcFontSetAdd (set, font));
282             if (!ret)
283                 FcPatternDestroy (font);
284         }
285     }
286     if (path != path_buf) free (path);
287     return ret;
288     
289 }
290
291 static unsigned int
292 FcCacheHash (const FcChar8 *string)
293 {
294     unsigned int    h = 0;
295     FcChar8         c;
296
297     while ((c = *string++))
298         h = (h << 1) ^ c;
299     return 0;
300 }
301
302 /*
303  * Verify the saved timestamp for a file
304  */
305 FcBool
306 FcGlobalCacheCheckTime (FcGlobalCacheInfo *info)
307 {
308     struct stat     statb;
309
310     if (stat ((char *) info->file, &statb) < 0)
311     {
312         if (FcDebug () & FC_DBG_CACHE)
313             printf (" file missing\n");
314         return FcFalse;
315     }
316     if (statb.st_mtime != info->time)
317     {
318         if (FcDebug () & FC_DBG_CACHE)
319             printf (" timestamp mismatch (was %d is %d)\n",
320                     (int) info->time, (int) statb.st_mtime);
321         return FcFalse;
322     }
323     return FcTrue;
324 }
325
326 void
327 FcGlobalCacheReferenced (FcGlobalCache      *cache,
328                          FcGlobalCacheInfo  *info)
329 {
330     if (!info->referenced)
331     {
332         info->referenced = FcTrue;
333         cache->referenced++;
334         if (FcDebug () & FC_DBG_CACHE_REF)
335             printf ("Reference %d %s\n", cache->referenced, info->file);
336     }
337 }
338
339 /*
340  * Break a path into dir/base elements and compute the base hash
341  * and the dir length.  This is shared between the functions
342  * which walk the file caches
343  */
344
345 typedef struct _FcFilePathInfo {
346     const FcChar8   *dir;
347     int             dir_len;
348     const FcChar8   *base;
349     unsigned int    base_hash;
350 } FcFilePathInfo;
351
352 static FcFilePathInfo
353 FcFilePathInfoGet (const FcChar8    *path)
354 {
355     FcFilePathInfo  i;
356     FcChar8         *slash;
357
358     slash = (FcChar8 *) strrchr ((const char *) path, '/');
359     if (slash)
360     {
361         i.dir = path;
362         i.dir_len = slash - path;
363         if (!i.dir_len)
364             i.dir_len = 1;
365         i.base = slash + 1;
366     }
367     else
368     {
369         i.dir = (const FcChar8 *) ".";
370         i.dir_len = 1;
371         i.base = path;
372     }
373     i.base_hash = FcCacheHash (i.base);
374     return i;
375 }
376
377 FcGlobalCacheDir *
378 FcGlobalCacheDirGet (FcGlobalCache  *cache,
379                      const FcChar8  *dir,
380                      int            len,
381                      FcBool         create_missing)
382 {
383     unsigned int        hash = FcCacheHash (dir);
384     FcGlobalCacheDir    *d, **prev;
385
386     for (prev = &cache->ents[hash % FC_GLOBAL_CACHE_DIR_HASH_SIZE];
387          (d = *prev);
388          prev = &(*prev)->next)
389     {
390         if (d->info.hash == hash && d->len == len &&
391             !strncmp ((const char *) d->info.file,
392                       (const char *) dir, len))
393             break;
394     }
395     if (!(d = *prev))
396     {
397         int     i;
398         if (!create_missing)
399             return 0;
400         d = malloc (sizeof (FcGlobalCacheDir) + len + 1);
401         if (!d)
402             return 0;
403         d->next = *prev;
404         *prev = d;
405         d->info.hash = hash;
406         d->info.file = (FcChar8 *) (d + 1);
407         strncpy ((char *) d->info.file, (const char *) dir, len);
408         d->info.file[len] = '\0';
409         d->info.time = 0;
410         d->info.referenced = FcFalse;
411         d->len = len;
412         for (i = 0; i < FC_GLOBAL_CACHE_FILE_HASH_SIZE; i++)
413             d->ents[i] = 0;
414         d->subdirs = 0;
415     }
416     return d;
417 }
418
419 static FcGlobalCacheInfo *
420 FcGlobalCacheDirAdd (FcGlobalCache  *cache,
421                      const FcChar8  *dir,
422                      time_t         time,
423                      FcBool         replace)
424 {
425     FcGlobalCacheDir    *d;
426     FcFilePathInfo      i;
427     FcGlobalCacheSubdir *subdir;
428     FcGlobalCacheDir    *parent;
429
430     /*
431      * Add this directory to the cache
432      */
433     d = FcGlobalCacheDirGet (cache, dir, strlen ((const char *) dir), FcTrue);
434     if (!d)
435         return 0;
436     d->info.time = time;
437     i = FcFilePathInfoGet (dir);
438     /*
439      * Add this directory to the subdirectory list of the parent
440      */
441     parent = FcGlobalCacheDirGet (cache, i.dir, i.dir_len, FcTrue);
442     if (!parent)
443         return 0;
444     subdir = malloc (sizeof (FcGlobalCacheSubdir) + 
445                      strlen ((const char *) i.base) + 1);
446     if (!subdir)
447         return 0;
448     subdir->file = (FcChar8 *) (subdir + 1);
449     strcpy ((char *) subdir->file, (const char *) i.base);
450     subdir->next = parent->subdirs;
451     parent->subdirs = subdir;
452     return &d->info;
453 }
454
455 static void
456 FcGlobalCacheDirDestroy (FcGlobalCacheDir *d)
457 {
458     FcGlobalCacheFile   *f, *next;
459     int                 h;
460     FcGlobalCacheSubdir *s, *nexts;
461
462     for (h = 0; h < FC_GLOBAL_CACHE_FILE_HASH_SIZE; h++)
463         for (f = d->ents[h]; f; f = next)
464         {
465             next = f->next;
466             free (f);
467         }
468     for (s = d->subdirs; s; s = nexts)
469     {
470         nexts = s->next;
471         free (s);
472     }
473     free (d);
474 }
475
476 FcBool
477 FcGlobalCacheScanDir (FcFontSet         *set,
478                       FcStrSet          *dirs,
479                       FcGlobalCache     *cache,
480                       const FcChar8     *dir)
481 {
482     FcGlobalCacheDir    *d = FcGlobalCacheDirGet (cache, dir,
483                                                   strlen ((const char *) dir),
484                                                   FcFalse);
485     FcGlobalCacheFile   *f;
486     int                 h;
487     int                 dir_len;
488     FcGlobalCacheSubdir *subdir;
489
490     if (FcDebug() & FC_DBG_CACHE)
491         printf ("FcGlobalCacheScanDir %s\n", dir);
492     
493     if (!d)
494     {
495         if (FcDebug () & FC_DBG_CACHE)
496             printf ("\tNo dir cache entry\n");
497         return FcFalse;
498     }
499
500     if (!FcGlobalCacheCheckTime (&d->info))
501     {
502         if (FcDebug () & FC_DBG_CACHE)
503             printf ("\tdir cache entry time mismatch\n");
504         return FcFalse;
505     }
506
507     dir_len = strlen ((const char *) dir);
508     for (h = 0; h < FC_GLOBAL_CACHE_FILE_HASH_SIZE; h++)
509         for (f = d->ents[h]; f; f = f->next)
510         {
511             if (FcDebug() & FC_DBG_CACHEV)
512                 printf ("FcGlobalCacheScanDir add file %s\n", f->info.file);
513             if (!FcCacheFontSetAdd (set, dirs, dir, dir_len,
514                                     f->info.file, f->name))
515             {
516                 cache->broken = FcTrue;
517                 return FcFalse;
518             }
519             FcGlobalCacheReferenced (cache, &f->info);
520         }
521     for (subdir = d->subdirs; subdir; subdir = subdir->next)
522     {
523         if (!FcCacheFontSetAdd (set, dirs, dir, dir_len,
524                                 subdir->file, FC_FONT_FILE_DIR))
525         {
526             cache->broken = FcTrue;
527             return FcFalse;
528         }
529     }
530     
531     FcGlobalCacheReferenced (cache, &d->info);
532
533     return FcTrue;
534 }
535
536 /*
537  * Locate the cache entry for a particular file
538  */
539 FcGlobalCacheFile *
540 FcGlobalCacheFileGet (FcGlobalCache *cache,
541                       const FcChar8 *file,
542                       int           id,
543                       int           *count)
544 {
545     FcFilePathInfo      i = FcFilePathInfoGet (file);
546     FcGlobalCacheDir    *d = FcGlobalCacheDirGet (cache, i.dir, 
547                                                   i.dir_len, FcFalse);
548     FcGlobalCacheFile   *f, *match = 0;
549     int                 max = -1;
550
551     if (!d)
552         return 0;
553     for (f = d->ents[i.base_hash % FC_GLOBAL_CACHE_FILE_HASH_SIZE]; f; f = f->next)
554     {
555         if (f->info.hash == i.base_hash &&
556             !strcmp ((const char *) f->info.file, (const char *) i.base))
557         {
558             if (f->id == id)
559                 match = f;
560             if (f->id > max)
561                 max = f->id;
562         }
563     }
564     if (count)
565         *count = max;
566     return match;
567 }
568     
569 /*
570  * Add a file entry to the cache
571  */
572 static FcGlobalCacheInfo *
573 FcGlobalCacheFileAdd (FcGlobalCache *cache,
574                       const FcChar8 *path,
575                       int           id,
576                       time_t        time,
577                       const FcChar8 *name,
578                       FcBool        replace)
579 {
580     FcFilePathInfo      i = FcFilePathInfoGet (path);
581     FcGlobalCacheDir    *d = FcGlobalCacheDirGet (cache, i.dir, 
582                                                   i.dir_len, FcTrue);
583     FcGlobalCacheFile   *f, **prev;
584
585     if (!d)
586         return 0;
587     for (prev = &d->ents[i.base_hash % FC_GLOBAL_CACHE_FILE_HASH_SIZE];
588          (f = *prev);
589          prev = &(*prev)->next)
590     {
591         if (f->info.hash == i.base_hash && 
592             f->id == id &&
593             !strcmp ((const char *) f->info.file, (const char *) i.base))
594         {
595             break;
596         }
597     }
598     if (*prev)
599     {
600         if (!replace)
601             return 0;
602
603         f = *prev;
604         if (f->info.referenced)
605             cache->referenced--;
606         *prev = f->next;
607         free (f);
608     }
609     f = malloc (sizeof (FcGlobalCacheFile) +
610                 strlen ((char *) i.base) + 1 +
611                 strlen ((char *) name) + 1);
612     if (!f)
613         return 0;
614     f->next = *prev;
615     *prev = f;
616     f->info.hash = i.base_hash;
617     f->info.file = (FcChar8 *) (f + 1);
618     f->info.time = time;
619     f->info.referenced = FcFalse;
620     f->id = id;
621     f->name = f->info.file + strlen ((char *) i.base) + 1;
622     strcpy ((char *) f->info.file, (const char *) i.base);
623     strcpy ((char *) f->name, (const char *) name);
624     return &f->info;
625 }
626
627 FcGlobalCache *
628 FcGlobalCacheCreate (void)
629 {
630     FcGlobalCache   *cache;
631     int             h;
632
633     cache = malloc (sizeof (FcGlobalCache));
634     if (!cache)
635         return 0;
636     for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
637         cache->ents[h] = 0;
638     cache->entries = 0;
639     cache->referenced = 0;
640     cache->updated = FcFalse;
641     cache->broken = FcFalse;
642     return cache;
643 }
644
645 void
646 FcGlobalCacheDestroy (FcGlobalCache *cache)
647 {
648     FcGlobalCacheDir    *d, *next;
649     int                 h;
650
651     for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
652     {
653         for (d = cache->ents[h]; d; d = next)
654         {
655             next = d->next;
656             FcGlobalCacheDirDestroy (d);
657         }
658     }
659     free (cache);
660 }
661
662 /*
663  * Cache file syntax is quite simple:
664  *
665  * "file_name" id time "font_name" \n
666  */
667  
668 void
669 FcGlobalCacheLoad (FcGlobalCache    *cache,
670                    const FcChar8    *cache_file)
671 {
672     FILE                *f;
673     FcChar8             file_buf[8192], *file;
674     int                 id;
675     time_t              time;
676     FcChar8             name_buf[8192], *name;
677     FcGlobalCacheInfo   *info;
678
679     f = fopen ((char *) cache_file, "r");
680     if (!f)
681         return;
682
683     cache->updated = FcFalse;
684     file = 0;
685     name = 0;
686     while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) &&
687            FcCacheReadInt (f, &id) &&
688            FcCacheReadTime (f, &time) &&
689            (name = FcCacheReadString (f, name_buf, sizeof (name_buf))))
690     {
691         if (FcDebug () & FC_DBG_CACHEV)
692             printf ("FcGlobalCacheLoad \"%s\" \"%20.20s\"\n", file, name);
693         if (!FcStrCmp (name, FC_FONT_FILE_DIR))
694             info = FcGlobalCacheDirAdd (cache, file, time, FcFalse);
695         else
696             info = FcGlobalCacheFileAdd (cache, file, id, time, name, FcFalse);
697         if (!info)
698             cache->broken = FcTrue;
699         else
700             cache->entries++;
701         if (FcDebug () & FC_DBG_CACHE_REF)
702             printf ("FcGlobalCacheLoad entry %d %s\n",
703                     cache->entries, file);
704         if (file != file_buf)
705             free (file);
706         if (name != name_buf)
707             free (name);
708         file = 0;
709         name = 0;
710     }
711     if (file && file != file_buf)
712         free (file);
713     if (name && name != name_buf)
714         free (name);
715     fclose (f);
716 }
717
718 FcBool
719 FcGlobalCacheUpdate (FcGlobalCache  *cache,
720                      const FcChar8  *file,
721                      int            id,
722                      const FcChar8  *name)
723 {
724     const FcChar8       *match;
725     struct stat         statb;
726     FcGlobalCacheInfo   *info;
727
728     match = file;
729
730     if (stat ((char *) file, &statb) < 0)
731         return FcFalse;
732     if (S_ISDIR (statb.st_mode))
733         info = FcGlobalCacheDirAdd (cache, file, statb.st_mtime, 
734                                    FcTrue);
735     else
736         info = FcGlobalCacheFileAdd (cache, file, id, statb.st_mtime, 
737                                     name, FcTrue);
738     if (info)
739     {
740         FcGlobalCacheReferenced (cache, info);
741         cache->updated = FcTrue;
742     }
743     else
744         cache->broken = FcTrue;
745     return info != 0;
746 }
747
748 FcBool
749 FcGlobalCacheSave (FcGlobalCache    *cache,
750                    const FcChar8    *cache_file)
751 {
752     FILE                *f;
753     int                 dir_hash, file_hash;
754     FcGlobalCacheDir    *dir;
755     FcGlobalCacheFile   *file;
756     FcAtomic            *atomic;
757
758     if (!cache->updated && cache->referenced == cache->entries)
759         return FcTrue;
760     
761     if (cache->broken)
762         return FcFalse;
763
764     /* Set-UID programs can't safely update the cache */
765     if (getuid () != geteuid ())
766         return FcFalse;
767     
768     atomic = FcAtomicCreate (cache_file);
769     if (!atomic)
770         goto bail0;
771     if (!FcAtomicLock (atomic))
772         goto bail1;
773     f = fopen ((char *) FcAtomicNewFile(atomic), "w");
774     if (!f)
775         goto bail2;
776
777     for (dir_hash = 0; dir_hash < FC_GLOBAL_CACHE_DIR_HASH_SIZE; dir_hash++)
778     {
779         for (dir = cache->ents[dir_hash]; dir; dir = dir->next)
780         {
781             if (!dir->info.referenced)
782                 continue;
783             if (!FcCacheWriteString (f, dir->info.file))
784                 goto bail4;
785             if (PUTC (' ', f) == EOF)
786                 goto bail4;
787             if (!FcCacheWriteInt (f, 0))
788                 goto bail4;
789             if (PUTC (' ', f) == EOF)
790                 goto bail4;
791             if (!FcCacheWriteTime (f, dir->info.time))
792                 goto bail4;
793             if (PUTC (' ', f) == EOF)
794                 goto bail4;
795             if (!FcCacheWriteString (f, (FcChar8 *) FC_FONT_FILE_DIR))
796                 goto bail4;
797             if (PUTC ('\n', f) == EOF)
798                 goto bail4;
799             
800             for (file_hash = 0; file_hash < FC_GLOBAL_CACHE_FILE_HASH_SIZE; file_hash++)
801             {
802                 for (file = dir->ents[file_hash]; file; file = file->next)
803                 {
804                     if (!file->info.referenced)
805                         continue;
806                     if (!FcCacheWritePath (f, dir->info.file, file->info.file))
807                         goto bail4;
808                     if (PUTC (' ', f) == EOF)
809                         goto bail4;
810                     if (!FcCacheWriteInt (f, file->id < 0 ? 0 : file->id))
811                         goto bail4;
812                     if (PUTC (' ', f) == EOF)
813                         goto bail4;
814                     if (!FcCacheWriteTime (f, file->info.time))
815                         goto bail4;
816                     if (PUTC (' ', f) == EOF)
817                         goto bail4;
818                     if (!FcCacheWriteString (f, file->name))
819                         goto bail4;
820                     if (PUTC ('\n', f) == EOF)
821                         goto bail4;
822                 }
823             }
824         }
825     }
826
827     if (fclose (f) == EOF)
828         goto bail3;
829     
830     if (!FcAtomicReplaceOrig (atomic))
831         goto bail3;
832     
833     FcAtomicUnlock (atomic);
834     FcAtomicDestroy (atomic);
835
836     cache->updated = FcFalse;
837     return FcTrue;
838
839 bail4:
840     fclose (f);
841 bail3:
842     FcAtomicDeleteNew (atomic);
843 bail2:
844     FcAtomicUnlock (atomic);
845 bail1:
846     FcAtomicDestroy (atomic);
847 bail0:
848     return FcFalse;
849 }
850
851 FcBool
852 FcDirCacheValid (const FcChar8 *dir)
853 {
854     FcChar8     *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
855     struct stat file_stat, dir_stat;
856
857     if (stat ((char *) dir, &dir_stat) < 0)
858     {
859         FcStrFree (cache_file);
860         return FcFalse;
861     }
862     if (stat ((char *) cache_file, &file_stat) < 0)
863     {
864         FcStrFree (cache_file);
865         return FcFalse;
866     }
867     FcStrFree (cache_file);
868     /*
869      * If the directory has been modified more recently than
870      * the cache file, the cache is not valid
871      */
872     if (dir_stat.st_mtime - file_stat.st_mtime > 0)
873         return FcFalse;
874     return FcTrue;
875 }
876
877 FcBool
878 FcDirCacheReadDir (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir)
879 {
880     FcChar8         *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
881     FILE            *f;
882     FcChar8         *base;
883     int             id;
884     int             dir_len;
885     FcChar8         file_buf[8192], *file;
886     FcChar8         name_buf[8192], *name;
887     FcBool          ret = FcFalse;
888
889     if (!cache_file)
890         goto bail0;
891     
892     if (FcDebug () & FC_DBG_CACHE)
893         printf ("FcDirCacheReadDir cache_file \"%s\"\n", cache_file);
894     
895     f = fopen ((char *) cache_file, "r");
896     if (!f)
897     {
898         if (FcDebug () & FC_DBG_CACHE)
899             printf (" no cache file\n");
900         goto bail1;
901     }
902
903     if (!FcDirCacheValid (dir))
904     {
905         if (FcDebug () & FC_DBG_CACHE)
906             printf (" cache file older than directory\n");
907         goto bail2;
908     }
909     
910     base = (FcChar8 *) strrchr ((char *) cache_file, '/');
911     if (!base)
912         goto bail2;
913     base++;
914     dir_len = base - cache_file;
915     
916     file = 0;
917     name = 0;
918     while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) &&
919            FcCacheReadInt (f, &id) &&
920            (name = FcCacheReadString (f, name_buf, sizeof (name_buf))))
921     {
922         if (!FcCacheFontSetAdd (set, dirs, cache_file, dir_len,
923                                 file, name))
924             goto bail3;
925         if (file != file_buf)
926             free (file);
927         if (name != name_buf)
928             free (name);
929         file = name = 0;
930     }
931     if (FcDebug () & FC_DBG_CACHE)
932         printf (" cache loaded\n");
933     
934     ret = FcTrue;
935 bail3:
936     if (file && file != file_buf)
937         free (file);
938     if (name && name != name_buf)
939         free (name);
940 bail2:
941     fclose (f);
942 bail1:
943     free (cache_file);
944 bail0:
945     return ret;
946 }
947
948 /*
949  * return the path from the directory containing 'cache' to 'file'
950  */
951
952 static const FcChar8 *
953 FcFileBaseName (const FcChar8 *cache, const FcChar8 *file)
954 {
955     const FcChar8   *cache_slash;
956
957     cache_slash = (const FcChar8 *) strrchr ((const char *) cache, '/');
958     if (cache_slash && !strncmp ((const char *) cache, (const char *) file,
959                                  (cache_slash + 1) - cache))
960         return file + ((cache_slash + 1) - cache);
961     return file;
962 }
963
964 FcBool
965 FcDirCacheWriteDir (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir)
966 {
967     FcChar8         *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
968     FcPattern       *font;
969     FILE            *f;
970     FcChar8         *name;
971     const FcChar8   *file, *base;
972     int             n;
973     int             id;
974     FcBool          ret;
975     FcStrList       *list;
976
977     if (!cache_file)
978         goto bail0;
979     if (FcDebug () & FC_DBG_CACHE)
980         printf ("FcDirCacheWriteDir cache_file \"%s\"\n", cache_file);
981     
982     f = fopen ((char *) cache_file, "w");
983     if (!f)
984     {
985         if (FcDebug () & FC_DBG_CACHE)
986             printf (" can't create \"%s\"\n", cache_file);
987         goto bail1;
988     }
989     
990     list = FcStrListCreate (dirs);
991     if (!list)
992         goto bail2;
993     
994     while ((dir = FcStrListNext (list)))
995     {
996         base = FcFileBaseName (cache_file, dir);
997         if (!FcCacheWriteString (f, base))
998             goto bail3;
999         if (PUTC (' ', f) == EOF)
1000             goto bail3;
1001         if (!FcCacheWriteInt (f, 0))
1002             goto bail3;
1003         if (PUTC (' ', f) == EOF)
1004             goto bail3;
1005         if (!FcCacheWriteString (f, FC_FONT_FILE_DIR))
1006             goto bail3;
1007         if (PUTC ('\n', f) == EOF)
1008             goto bail3;
1009     }
1010     
1011     for (n = 0; n < set->nfont; n++)
1012     {
1013         font = set->fonts[n];
1014         if (FcPatternGetString (font, FC_FILE, 0, (FcChar8 **) &file) != FcResultMatch)
1015             goto bail3;
1016         base = FcFileBaseName (cache_file, file);
1017         if (FcPatternGetInteger (font, FC_INDEX, 0, &id) != FcResultMatch)
1018             goto bail3;
1019         if (FcDebug () & FC_DBG_CACHEV)
1020             printf (" write file \"%s\"\n", base);
1021         if (!FcCacheWriteString (f, base))
1022             goto bail3;
1023         if (PUTC (' ', f) == EOF)
1024             goto bail3;
1025         if (!FcCacheWriteInt (f, id))
1026             goto bail3;
1027         if (PUTC (' ', f) == EOF)
1028             goto bail3;
1029         name = FcNameUnparse (font);
1030         if (!name)
1031             goto bail3;
1032         ret = FcCacheWriteString (f, name);
1033         free (name);
1034         if (!ret)
1035             goto bail3;
1036         if (PUTC ('\n', f) == EOF)
1037             goto bail3;
1038     }
1039     
1040     FcStrListDone (list);
1041
1042     if (fclose (f) == EOF)
1043         goto bail1;
1044     
1045     free (cache_file);
1046
1047     if (FcDebug () & FC_DBG_CACHE)
1048         printf (" cache written\n");
1049     return FcTrue;
1050     
1051 bail3:
1052     FcStrListDone (list);
1053 bail2:
1054     fclose (f);
1055 bail1:
1056     unlink ((char *) cache_file);
1057     free (cache_file);
1058 bail0:
1059     return FcFalse;
1060 }