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