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