]> git.wh0rd.org Git - fontconfig.git/blob - src/fccache.c
Reinstate basename patch, but keep a hash table linking FcPatterns to their
[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  * Copyright © 2005 Patrick Lam
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and its
8  * documentation for any purpose is hereby granted without fee, provided that
9  * the above copyright notice appear in all copies and that both that
10  * copyright notice and this permission notice appear in supporting
11  * documentation, and that the name of Keith Packard not be used in
12  * advertising or publicity pertaining to distribution of the software without
13  * specific, written prior permission.  Keith Packard makes no
14  * representations about the suitability of this software for any purpose.  It
15  * is provided "as is" without express or implied warranty.
16  *
17  * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
18  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
19  * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
20  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
22  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
23  * PERFORMANCE OF THIS SOFTWARE.
24  */
25
26 #include <fcntl.h>
27 #include <dirent.h>
28 #include <sys/mman.h>
29 #include <sys/utsname.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32 #include "fcint.h"
33
34 #define ENDIAN_TEST 0x12345678
35 #define MACHINE_SIGNATURE_SIZE 9 + 5*19 + 1
36
37 static off_t
38 FcCacheSkipToArch (int fd, const char * arch);
39
40 static FcBool 
41 FcCacheCopyOld (int fd, int fd_orig, off_t start);
42
43 static void *
44 FcDirCacheProduce (FcFontSet *set, FcCache * metadata);
45
46 static FcBool
47 FcDirCacheConsume (int fd, const char * dir, FcFontSet *set);
48
49 FcBool
50 FcDirCacheRead (FcFontSet * set, FcStrSet * dirs, const FcChar8 *dir);
51
52 static int
53 FcCacheNextOffset(off_t w);
54
55 static char *
56 FcCacheMachineSignature (void);
57
58 static FcBool
59 FcCacheHaveBank (int bank);
60
61 static void
62 FcCacheAddBankDir (int bank, const char * dir);
63
64 #define FC_DBG_CACHE_REF    1024
65
66 static char *
67 FcCacheReadString (int fd, char *dest, int len)
68 {
69     FcChar8     c;
70     FcBool      escape;
71     int         size;
72     int         i;
73
74     if (len == 0)
75         return 0;
76     
77     size = len;
78     i = 0;
79     escape = FcFalse;
80     while (read (fd, &c, 1) == 1)
81     {
82         if (!escape)
83         {
84             switch (c) {
85             case '"':
86                 c = '\0';
87                 break;
88             case '\\':
89                 escape = FcTrue;
90                 continue;
91             }
92         }
93         if (i == size)
94         {
95             dest[i++] = 0;
96             return dest;
97         }
98         dest[i++] = c;
99         if (c == '\0')
100             return dest;
101         escape = FcFalse;
102     }
103     return 0;
104 }
105
106 static FcBool
107 FcCacheWriteString (int fd, const char *chars)
108 {
109     if (write (fd, chars, strlen(chars)+1) != strlen(chars)+1)
110         return FcFalse;
111     return FcTrue;
112 }
113
114 static void
115 FcGlobalCacheDirDestroy (FcGlobalCacheDir *d)
116 {
117     FcMemFree (FC_MEM_STRING, strlen (d->name)+1);
118     free (d->name);
119     FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheDir));
120     free (d);
121 }
122
123 FcGlobalCache *
124 FcGlobalCacheCreate (void)
125 {
126     FcGlobalCache   *cache;
127
128     cache = malloc (sizeof (FcGlobalCache));
129     if (!cache)
130         return 0;
131     FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCache));
132     cache->dirs = 0;
133     cache->updated = FcFalse;
134     cache->fd = -1;
135     return cache;
136 }
137
138 void
139 FcGlobalCacheDestroy (FcGlobalCache *cache)
140 {
141     FcGlobalCacheDir    *d, *next;
142
143     for (d = cache->dirs; d; d = next)
144     {
145         next = d->next;
146         FcGlobalCacheDirDestroy (d);
147     }
148     FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCache));
149     free (cache);
150 }
151
152 void
153 FcGlobalCacheLoad (FcGlobalCache    *cache,
154                    FcStrSet         *staleDirs,
155                    const FcChar8    *cache_file)
156 {
157     char                name_buf[8192];
158     FcGlobalCacheDir    *d, *next;
159     char                * current_arch_machine_name;
160     char                candidate_arch_machine_name[MACHINE_SIGNATURE_SIZE + 9];
161     off_t               current_arch_start;
162
163     struct stat         cache_stat, dir_stat;
164
165     if (stat ((char *) cache_file, &cache_stat) < 0)
166         return;
167
168     cache->fd = open ((char *) cache_file, O_RDONLY);
169     if (cache->fd == -1)
170         return;
171
172     cache->updated = FcFalse;
173
174     current_arch_machine_name = FcCacheMachineSignature ();
175     current_arch_start = FcCacheSkipToArch(cache->fd, 
176                                            current_arch_machine_name);
177     if (current_arch_start < 0)
178         goto bail_and_destroy;
179
180     lseek (cache->fd, current_arch_start, SEEK_SET);
181     FcCacheReadString (cache->fd, candidate_arch_machine_name, 
182                        sizeof (candidate_arch_machine_name));
183     if (strlen(candidate_arch_machine_name) == 0)
184         goto bail_and_destroy;
185
186     while (1) 
187     {
188         off_t targ;
189
190         FcCacheReadString (cache->fd, name_buf, sizeof (name_buf));
191         if (!strlen(name_buf))
192             break;
193
194         if (stat ((char *) name_buf, &dir_stat) < 0 || 
195             dir_stat.st_mtime > cache_stat.st_mtime)
196         {
197             FcCache md;
198
199             FcStrSetAdd (staleDirs, FcStrCopy ((FcChar8 *)name_buf));
200             read (cache->fd, &md, sizeof (FcCache));
201             lseek (cache->fd, FcCacheNextOffset (lseek(cache->fd, 0, SEEK_CUR)) + md.count, SEEK_SET);
202             continue;
203         }
204
205         d = malloc (sizeof (FcGlobalCacheDir));
206         if (!d)
207             goto bail1;
208
209         d->next = cache->dirs;
210         cache->dirs = d;
211
212         d->name = (char *)FcStrCopy ((FcChar8 *)name_buf);
213         d->ent = 0;
214         d->offset = lseek (cache->fd, 0, SEEK_CUR);
215         if (read (cache->fd, &d->metadata, sizeof (FcCache)) != sizeof (FcCache))
216             goto bail1;
217         targ = FcCacheNextOffset (lseek(cache->fd, 0, SEEK_CUR)) + d->metadata.count;
218         if (lseek (cache->fd, targ, SEEK_SET) != targ)
219             goto bail1;
220     }
221     return;
222
223  bail1:
224     for (d = cache->dirs; d; d = next)
225     {
226         next = d->next;
227         free (d);
228     }
229     cache->dirs = 0;
230
231     close (cache->fd);
232     cache->fd = -1;
233     return;
234
235  bail_and_destroy:
236     close (cache->fd);
237     cache->fd = -1;
238
239     if (stat ((char *) cache_file, &cache_stat) == 0)
240         unlink ((char *)cache_file);
241
242     return;
243
244 }
245
246 FcBool
247 FcGlobalCacheReadDir (FcFontSet *set, FcStrSet *dirs, FcGlobalCache * cache, const char *dir, FcConfig *config)
248 {
249     FcGlobalCacheDir *d;
250     FcBool ret = FcFalse;
251
252     if (cache->fd == -1)
253         return FcFalse;
254
255     for (d = cache->dirs; d; d = d->next)
256     {
257         if (strncmp (d->name, dir, strlen(dir)) == 0)
258         {
259             lseek (cache->fd, d->offset, SEEK_SET);
260             if (!FcDirCacheConsume (cache->fd, dir, set))
261                 return FcFalse;
262             if (strcmp (d->name, dir) == 0)
263                 ret = FcTrue;
264         }
265     }
266
267     return ret;
268 }
269
270 FcBool
271 FcGlobalCacheUpdate (FcGlobalCache  *cache,
272                      const char     *name,
273                      FcFontSet      *set)
274 {
275     FcGlobalCacheDir * d;
276
277     if (!set->nfont)
278         return FcTrue;
279
280     for (d = cache->dirs; d; d = d->next)
281     {
282         if (strcmp(d->name, name) == 0)
283             break;
284     }
285
286     if (!d)
287     {
288         d = malloc (sizeof (FcGlobalCacheDir));
289         if (!d)
290             return FcFalse;
291         d->next = cache->dirs;
292         cache->dirs = d;
293     }
294
295     cache->updated = FcTrue;
296
297     d->name = (char *)FcStrCopy ((FcChar8 *)name);
298     d->ent = FcDirCacheProduce (set, &d->metadata);
299     d->offset = 0;
300     return FcTrue;
301 }
302
303 FcBool
304 FcGlobalCacheSave (FcGlobalCache    *cache,
305                    const FcChar8    *cache_file)
306 {
307     int                 fd, fd_orig;
308     FcGlobalCacheDir    *dir;
309     FcAtomic            *atomic;
310     off_t               current_arch_start = 0, truncate_to;
311     char                * current_arch_machine_name, * header;
312
313     if (!cache->updated)
314         return FcTrue;
315     
316 #if defined (HAVE_GETUID) && defined (HAVE_GETEUID)
317     /* Set-UID programs can't safely update the cache */
318     if (getuid () != geteuid ())
319         return FcFalse;
320 #endif
321     
322     atomic = FcAtomicCreate (cache_file);
323     if (!atomic)
324         return FcFalse;
325
326     if (!FcAtomicLock (atomic))
327         goto bail1;
328     fd = open ((char *) FcAtomicNewFile(atomic), O_RDWR | O_CREAT, 
329                S_IRUSR | S_IWUSR);
330     if (fd == -1)
331         goto bail2;
332
333     fd_orig = open ((char *) FcAtomicOrigFile(atomic), O_RDONLY);
334
335     current_arch_machine_name = FcCacheMachineSignature ();
336     if (fd_orig == -1)
337         current_arch_start = 0;
338     else
339         current_arch_start = FcCacheSkipToArch (fd_orig, 
340                                                 current_arch_machine_name);
341
342     if (current_arch_start < 0)
343         current_arch_start = FcCacheNextOffset (lseek(fd_orig, 0, SEEK_END));
344
345     if (!FcCacheCopyOld(fd, fd_orig, current_arch_start))
346         goto bail3;
347
348     close (fd_orig);
349     fd_orig = -1;
350
351     current_arch_start = lseek(fd, 0, SEEK_CUR);
352     if (ftruncate (fd, current_arch_start) == -1)
353         goto bail3;
354
355     header = malloc (10 + strlen (current_arch_machine_name));
356     if (!header)
357         goto bail3;
358
359     truncate_to = current_arch_start + strlen(current_arch_machine_name) + 11;
360     for (dir = cache->dirs; dir; dir = dir->next)
361     {
362         truncate_to += strlen(dir->name) + 1;
363         truncate_to += sizeof (FcCache);
364         truncate_to = FcCacheNextOffset (current_arch_start + truncate_to);
365         truncate_to += dir->metadata.count;
366     }
367     truncate_to -= current_arch_start;
368
369     sprintf (header, "%8x ", (int)truncate_to);
370     strcat (header, current_arch_machine_name);
371     if (!FcCacheWriteString (fd, header))
372         goto bail4;
373
374     for (dir = cache->dirs; dir; dir = dir->next)
375     {
376         if (dir->ent)
377         {
378             FcCacheWriteString (fd, dir->name);
379             write (fd, &dir->metadata, sizeof(FcCache));
380             lseek (fd, FcCacheNextOffset (lseek(fd, 0, SEEK_CUR)), SEEK_SET);
381             write (fd, dir->ent, dir->metadata.count);
382             free (dir->ent);
383         }
384     }
385     FcCacheWriteString (fd, "");
386
387     if (close (fd) == -1)
388         goto bail25;
389     
390     if (!FcAtomicReplaceOrig (atomic))
391         goto bail25;
392     
393     FcAtomicUnlock (atomic);
394     FcAtomicDestroy (atomic);
395
396     cache->updated = FcFalse;
397     return FcTrue;
398
399  bail4:
400     free (header);
401  bail3:
402     if (fd_orig != -1)
403         close (fd_orig);
404
405     close (fd);
406  bail25:
407     FcAtomicDeleteNew (atomic);
408  bail2:
409     FcAtomicUnlock (atomic);
410  bail1:
411     FcAtomicDestroy (atomic);
412     return FcFalse;
413 }
414
415 #define PAGESIZE 8192
416 /* 
417  * Find the next presumably-mmapable offset after the supplied file
418  * position.
419  */
420 static int
421 FcCacheNextOffset(off_t w)
422 {
423     if (w % PAGESIZE == 0) 
424         return w;
425     else
426         return ((w / PAGESIZE)+1)*PAGESIZE;
427 }
428
429 /* return the address of the segment for the provided arch,
430  * or -1 if arch not found */
431 static off_t
432 FcCacheSkipToArch (int fd, const char * arch)
433 {
434     char candidate_arch_machine_name_count[MACHINE_SIGNATURE_SIZE + 9];
435     char * candidate_arch;
436     off_t current_arch_start = 0;
437
438     /* skip arches that are not the current arch */
439     while (1)
440     {
441         long bs;
442
443         if (lseek (fd, current_arch_start, SEEK_SET) != current_arch_start)
444             return -1;
445
446         if (FcCacheReadString (fd, candidate_arch_machine_name_count, 
447                                 sizeof (candidate_arch_machine_name_count)) == 0)
448             return -1;
449         if (!strlen(candidate_arch_machine_name_count))
450             return -1;
451         bs = strtol(candidate_arch_machine_name_count, &candidate_arch, 16);
452
453         // count = 0 should probably be distinguished from the !bs condition
454         if (!bs || bs < strlen (candidate_arch_machine_name_count))
455             return -1;
456
457         candidate_arch++; /* skip leading space */
458
459         if (strcmp (candidate_arch, arch)==0)
460             return current_arch_start;
461         current_arch_start += bs;
462     }
463
464     return -1;
465 }
466
467 /* Cuts out the segment at the file pointer (moves everything else
468  * down to cover it), and leaves the file pointer at the end of the
469  * file. */
470 static FcBool 
471 FcCacheCopyOld (int fd, int fd_orig, off_t start)
472 {
473     char * buf = malloc (8192);
474     char candidate_arch_machine_name[MACHINE_SIGNATURE_SIZE + 9];
475     long bs;
476     int c, bytes_skipped;
477     off_t loc;
478
479     if (!buf)
480         return FcFalse;
481
482     loc = 0;
483     lseek (fd, 0, SEEK_SET); lseek (fd_orig, 0, SEEK_SET);
484     do
485     {
486         int b = 8192;
487         if (loc + b > start)
488             b = start - loc;
489
490         if ((c = read (fd_orig, buf, b)) <= 0)
491             break;
492         if (write (fd, buf, c) < 0)
493             goto bail;
494
495         loc += c;
496     }
497     while (c > 0);
498
499     lseek (fd, start, SEEK_SET);
500     if (FcCacheReadString (fd, candidate_arch_machine_name, 
501                            sizeof (candidate_arch_machine_name)) == 0)
502         goto done;
503     if (!strlen(candidate_arch_machine_name))
504         goto done;
505
506     bs = strtol(candidate_arch_machine_name, 0, 16);
507     if (bs == 0)
508         goto done;
509
510     bytes_skipped = 0;
511     do
512     {
513         lseek (fd, start+bs+bytes_skipped, SEEK_SET);
514         if ((c = read (fd, buf, 8192)) <= 0)
515             break;
516         lseek (fd, start+bytes_skipped, SEEK_SET);
517         if (write (fd, buf, c) < 0)
518             goto bail;
519         bytes_skipped += c;
520     }
521     while (c > 0);
522     lseek (fd, start+bytes_skipped, SEEK_SET);
523
524  done:
525     free (buf);
526     return FcTrue;
527
528  bail:
529     free (buf);
530     return FcFalse;
531 }
532
533 /* Does not check that the cache has the appropriate arch section. */
534 FcBool
535 FcDirCacheValid (const FcChar8 *dir)
536 {
537     FcChar8     *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
538     struct stat file_stat, dir_stat;
539
540     if (stat ((char *) dir, &dir_stat) < 0)
541     {
542         FcStrFree (cache_file);
543         return FcFalse;
544     }
545     if (stat ((char *) cache_file, &file_stat) < 0)
546     {
547         FcStrFree (cache_file);
548         return FcFalse;
549     }
550
551     FcStrFree (cache_file);
552     /*
553      * If the directory has been modified more recently than
554      * the cache file, the cache is not valid
555      */
556     if (dir_stat.st_mtime - file_stat.st_mtime > 0)
557         return FcFalse;
558     return FcTrue;
559 }
560
561 /* Assumes that the cache file in 'dir' exists.
562  * Checks that the cache has the appropriate arch section. */
563 FcBool
564 FcDirCacheHasCurrentArch (const FcChar8 *dir)
565 {
566     FcChar8     *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
567     int         fd;
568     off_t       current_arch_start;
569     char        *current_arch_machine_name;
570
571     current_arch_machine_name = FcCacheMachineSignature();
572     fd = open ((char *)cache_file, O_RDONLY);
573     if (fd == -1)
574         return FcFalse;
575
576     current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
577     close (fd);
578
579     if (current_arch_start < 0)
580         return FcFalse;
581     
582     return FcTrue;
583 }
584
585 FcBool
586 FcDirCacheUnlink (const FcChar8 *dir)
587 {
588     FcChar8     *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
589     struct stat cache_stat;
590
591     if (stat ((char *) cache_file, &cache_stat) == 0 &&
592         unlink ((char *)cache_file) != 0)
593     {
594         FcStrFree (cache_file);
595         return FcFalse;
596     }
597
598     FcStrFree (cache_file);
599     return FcTrue;
600 }
601
602 static int
603 FcCacheReadDirs (FcConfig * config, FcGlobalCache * cache, 
604                  FcStrList *list, FcFontSet * set)
605 {
606     int                 ret = 0;
607     FcChar8             *dir;
608     FcChar8             *file, *base;
609     FcStrSet            *subdirs;
610     FcStrList           *sublist;
611     struct stat         statb;
612
613     /*
614      * Read in the results from 'list'.
615      */
616     while ((dir = FcStrListNext (list)))
617     {
618         /* freed below */
619         file = (FcChar8 *) malloc (strlen ((char *) dir) + 1 + FC_MAX_FILE_LEN + 1);
620         if (!file)
621             return FcFalse;
622
623         strcpy ((char *) file, (char *) dir);
624         strcat ((char *) file, "/");
625         base = file + strlen ((char *) file);
626
627         subdirs = FcStrSetCreate ();
628         if (!subdirs)
629         {
630             fprintf (stderr, "Can't create directory set\n");
631             ret++;
632             free (file);
633             continue;
634         }
635         
636         if (access ((char *) dir, X_OK) < 0)
637         {
638             switch (errno) {
639             case ENOENT:
640             case ENOTDIR:
641             case EACCES:
642                 break;
643             default:
644                 fprintf (stderr, "\"%s\": ", dir);
645                 perror ("");
646                 ret++;
647             }
648             FcStrSetDestroy (subdirs);
649             free (file);
650             continue;
651         }
652         if (stat ((char *) dir, &statb) == -1)
653         {
654             fprintf (stderr, "\"%s\": ", dir);
655             perror ("");
656             FcStrSetDestroy (subdirs);
657             ret++;
658             free (file);
659             continue;
660         }
661         if (!S_ISDIR (statb.st_mode))
662         {
663             fprintf (stderr, "\"%s\": not a directory, skipping\n", dir);
664             FcStrSetDestroy (subdirs);
665             free (file);
666             continue;
667         }
668         if (!FcDirCacheValid (dir) || !FcDirCacheRead (set, subdirs, dir))
669         {
670             if (FcDebug () & FC_DBG_FONTSET)
671                 printf ("cache scan dir %s\n", dir);
672
673             FcDirScanConfig (set, subdirs, cache, 
674                              config->blanks, dir, FcFalse, config);
675         }
676         sublist = FcStrListCreate (subdirs);
677         FcStrSetDestroy (subdirs);
678         if (!sublist)
679         {
680             fprintf (stderr, "Can't create subdir list in \"%s\"\n", dir);
681             ret++;
682             free (file);
683             continue;
684         }
685         ret += FcCacheReadDirs (config, cache, sublist, set);
686         free (file);
687     }
688     FcStrListDone (list);
689     return ret;
690 }
691
692 FcFontSet *
693 FcCacheRead (FcConfig *config, FcGlobalCache * cache)
694 {
695     FcFontSet * s = FcFontSetCreate();
696     if (!s) 
697         return 0;
698
699     if (FcCacheReadDirs (config, cache, FcConfigGetConfigDirs (config), s))
700         goto bail;
701
702     return s;
703
704  bail:
705     FcFontSetDestroy (s);
706     return 0;
707 }
708
709 /* read serialized state from the cache file */
710 FcBool
711 FcDirCacheRead (FcFontSet * set, FcStrSet * dirs, const FcChar8 *dir)
712 {
713     char *cache_file = (char *)FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
714     int fd;
715     char * current_arch_machine_name;
716     char candidate_arch_machine_name[9+MACHINE_SIGNATURE_SIZE];
717     off_t current_arch_start = 0;
718     char subdirName[FC_MAX_FILE_LEN + 1 + 12 + 1];
719
720     if (!cache_file)
721         goto bail;
722
723     current_arch_machine_name = FcCacheMachineSignature();
724     fd = open(cache_file, O_RDONLY);
725     if (fd == -1)
726         goto bail;
727
728     current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
729     if (current_arch_start < 0)
730         goto bail1;
731
732     lseek (fd, current_arch_start, SEEK_SET);
733     if (FcCacheReadString (fd, candidate_arch_machine_name, 
734                            sizeof (candidate_arch_machine_name)) == 0)
735         goto bail1;
736
737     while (strlen(FcCacheReadString (fd, subdirName, sizeof (subdirName))) > 0)
738         FcStrSetAdd (dirs, (FcChar8 *)subdirName);
739
740     if (!FcDirCacheConsume (fd, (const char *)dir, set))
741         goto bail1;
742         
743     close(fd);
744     free (cache_file);
745     return FcTrue;
746
747  bail1:
748     close (fd);
749  bail:
750     free (cache_file);
751     return FcFalse;
752 }
753
754 static FcBool
755 FcDirCacheConsume (int fd, const char * dir, FcFontSet *set)
756 {
757     FcCache metadata;
758     void * current_dir_block;
759     off_t pos;
760
761     read(fd, &metadata, sizeof(FcCache));
762     if (metadata.magic != FC_CACHE_MAGIC)
763         return FcFalse;
764
765     if (!metadata.count)
766         return FcTrue;
767
768     pos = FcCacheNextOffset (lseek(fd, 0, SEEK_CUR));
769     current_dir_block = mmap (0, metadata.count, 
770                               PROT_READ, MAP_SHARED, fd, pos);
771     if (current_dir_block == MAP_FAILED)
772         return FcFalse;
773     
774     if (!FcFontSetUnserialize (metadata, set, current_dir_block))
775         return FcFalse;
776
777     FcCacheAddBankDir (metadata.bank, dir);
778
779     return FcTrue;
780 }
781
782 static void *
783 FcDirCacheProduce (FcFontSet *set, FcCache *metadata)
784 {
785     void * current_dir_block, * final_dir_block;
786     static unsigned int rand_state = 0;
787     int bank;
788
789     if (!rand_state) 
790         rand_state = time(0L);
791     bank = rand_r(&rand_state);
792
793     while (FcCacheHaveBank(bank))
794         bank = rand_r(&rand_state);
795
796     memset (metadata, 0, sizeof(FcCache));
797     FcFontSetNewBank();
798     metadata->count = FcFontSetNeededBytes (set);
799     metadata->magic = FC_CACHE_MAGIC;
800     metadata->bank = bank;
801
802     if (!metadata->count) /* not a failure, no fonts to write */
803         return 0;
804
805     current_dir_block = malloc (metadata->count);
806     if (!current_dir_block)
807         goto bail;
808     final_dir_block = FcFontSetDistributeBytes (metadata, current_dir_block);
809
810     if ((char *)current_dir_block + metadata->count != final_dir_block)
811         goto bail;
812                               
813     if (!FcFontSetSerialize (bank, set))
814         goto bail;
815
816     return current_dir_block;
817
818  bail:
819     free (current_dir_block);
820     return 0;
821 }
822
823 /* write serialized state to the cache file */
824 FcBool
825 FcDirCacheWrite (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir)
826 {
827     FcChar8         *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
828     int             fd, fd_orig, i, dirs_count;
829     FcAtomic        *atomic;
830     FcCache         metadata;
831     off_t           current_arch_start = 0, truncate_to;
832
833     char            *current_arch_machine_name, * header;
834     void            *current_dir_block;
835
836     if (!cache_file)
837         goto bail;
838
839     current_dir_block = FcDirCacheProduce (set, &metadata);
840
841     if (metadata.count && !current_dir_block)
842         goto bail0;
843
844     if (FcDebug () & FC_DBG_CACHE)
845         printf ("FcDirCacheWriteDir cache_file \"%s\"\n", cache_file);
846
847     atomic = FcAtomicCreate (cache_file);
848     if (!atomic)
849         goto bail0;
850
851     if (!FcAtomicLock (atomic))
852         goto bail1;
853
854     fd_orig = open((char *)FcAtomicOrigFile (atomic), O_RDONLY, 0666);
855
856     fd = open((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT, 0666);
857     if (fd == -1)
858         goto bail2;
859
860     current_arch_machine_name = FcCacheMachineSignature ();
861     current_arch_start = 0;
862
863     if (fd_orig != -1)
864         current_arch_start = 
865             FcCacheSkipToArch(fd_orig, current_arch_machine_name);
866
867     if (current_arch_start < 0)
868         current_arch_start = FcCacheNextOffset (lseek(fd_orig, 0, SEEK_END));
869
870     if (fd_orig != -1 && !FcCacheCopyOld(fd, fd_orig, current_arch_start))
871         goto bail3;
872
873     if (fd_orig != -1)
874         close (fd_orig);
875
876     current_arch_start = lseek(fd, 0, SEEK_CUR);
877     if (ftruncate (fd, current_arch_start) == -1)
878         goto bail3;
879
880     /* allocate space for subdir names in this block */
881     dirs_count = 0;
882     for (i = 0; i < dirs->size; i++)
883         dirs_count += strlen((char *)dirs->strs[i]) + 1;
884     dirs_count ++;
885
886     /* now write the address of the next offset */
887     truncate_to = FcCacheNextOffset (FcCacheNextOffset (current_arch_start + sizeof (FcCache) + dirs_count) + metadata.count) - current_arch_start;
888     header = malloc (10 + strlen (current_arch_machine_name));
889     if (!header)
890         goto bail3;
891     sprintf (header, "%8x ", (int)truncate_to);
892     strcat (header, current_arch_machine_name);
893     if (!FcCacheWriteString (fd, header))
894         goto bail4;
895
896     for (i = 0; i < dirs->size; i++)
897         FcCacheWriteString (fd, (char *)dirs->strs[i]);
898     FcCacheWriteString (fd, "");
899
900     write (fd, &metadata, sizeof(FcCache));
901     if (metadata.count)
902     {
903         lseek (fd, FcCacheNextOffset (lseek(fd, 0, SEEK_END)), SEEK_SET);
904         write (fd, current_dir_block, metadata.count);
905         free (current_dir_block);
906     }
907
908     /* this actually serves to pad out the cache file, if needed */
909     if (ftruncate (fd, current_arch_start + truncate_to) == -1)
910         goto bail4;
911
912     close(fd);
913     if (!FcAtomicReplaceOrig(atomic))
914         goto bail4;
915     FcAtomicUnlock (atomic);
916     FcAtomicDestroy (atomic);
917     return FcTrue;
918
919  bail4:
920     free (header);
921  bail3:
922     close (fd);
923  bail2:
924     FcAtomicUnlock (atomic);
925  bail1:
926     FcAtomicDestroy (atomic);
927  bail0:
928     unlink ((char *)cache_file);
929     free (cache_file);
930     if (current_dir_block)
931         free (current_dir_block);
932  bail:
933     return FcFalse;
934 }
935
936 static char *
937 FcCacheMachineSignature ()
938 {
939     static char buf[MACHINE_SIGNATURE_SIZE];
940     int magic = ENDIAN_TEST;
941     char * m = (char *)&magic;
942
943     sprintf (buf, "%2x%2x%2x%2x "
944              "%4x %4x %4x %4x %4x %4x %4x %4x %4x %4x %4x %4x "
945              "%4x %4x %4x %4x %4x %4x %4x\n", 
946              m[0], m[1], m[2], m[3],
947              (unsigned int)sizeof (char),
948              (unsigned int)sizeof (char *),
949              (unsigned int)sizeof (int),
950              (unsigned int)sizeof (FcPattern),
951              (unsigned int)sizeof (FcPatternEltPtr),
952              (unsigned int)sizeof (struct _FcPatternElt *),
953              (unsigned int)sizeof (FcPatternElt),
954              (unsigned int)sizeof (FcObjectPtr),
955              (unsigned int)sizeof (FcValueListPtr),
956              (unsigned int)sizeof (FcValue),
957              (unsigned int)sizeof (FcValueBinding),
958              (unsigned int)sizeof (struct _FcValueList *),
959              (unsigned int)sizeof (FcCharSet),
960              (unsigned int)sizeof (FcCharLeaf **),
961              (unsigned int)sizeof (FcChar16 *),
962              (unsigned int)sizeof (FcChar16),
963              (unsigned int)sizeof (FcCharLeaf),
964              (unsigned int)sizeof (FcChar32),
965              (unsigned int)sizeof (FcCache));
966
967     return buf;
968 }
969
970 static int banks_ptr = 0, banks_alloc = 0;
971 static int * bankId = 0, * bankIdx = 0;
972 static const char ** bankDirs = 0;
973
974 static FcBool
975 FcCacheHaveBank (int bank)
976 {
977     int i;
978
979     if (bank < FC_BANK_FIRST)
980         return FcTrue;
981
982     for (i = 0; i < banks_ptr; i++)
983         if (bankId[i] == bank)
984             return FcTrue;
985
986     return FcFalse;
987 }
988
989 int
990 FcCacheBankToIndex (int bank)
991 {
992     int i, j;
993
994     for (i = 0; i < banks_ptr; i++)
995         if (bankId[bankIdx[i]] == bank)
996         {
997             int t = bankIdx[i];
998
999             for (j = i; j > 0; j--)
1000                 bankIdx[j] = bankIdx[j-1];
1001             bankIdx[0] = t;
1002             return t;
1003         }
1004
1005     if (banks_ptr >= banks_alloc)
1006     {
1007         int * b, * bidx;
1008         const char ** bds;
1009
1010         b = realloc (bankId, (banks_alloc + 4) * sizeof(int));
1011         if (!b)
1012             return -1;
1013         bankId = b;
1014
1015         bidx = realloc (bankIdx, (banks_alloc + 4) * sizeof(int));
1016         if (!bidx)
1017             return -1;
1018         bankIdx = bidx;
1019
1020         bds = realloc (bankDirs, (banks_alloc + 4) * sizeof (char *));
1021         if (!bds)
1022             return -1;
1023         bankDirs = bds;
1024
1025         banks_alloc += 4;
1026     }
1027
1028     i = banks_ptr++;
1029     bankId[i] = bank;
1030     bankIdx[i] = i;
1031     return i;
1032 }
1033
1034 static void
1035 FcCacheAddBankDir (int bank, const char * dir)
1036 {
1037     int bi = FcCacheBankToIndex (bank);
1038
1039     if (bi < 0)
1040         return;
1041
1042     bankDirs[bi] = (const char *)FcStrCopy ((FcChar8 *)dir);
1043 }
1044
1045 const char *
1046 FcCacheFindBankDir (int bank)
1047 {
1048     int bi = FcCacheBankToIndex (bank);
1049     return bankDirs[bi];
1050 }
1051