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