]> git.wh0rd.org Git - fontconfig.git/blob - src/fccache.c
#ifdef out old cache stuff, replace with first version of new mmapping
[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 <fcntl.h>
26 #include <sys/mman.h>
27 #include <sys/utsname.h>
28 #include "fcint.h"
29
30 #define PAGESIZE 8192
31
32 static FcBool force;
33
34 static FcChar8 *
35 FcCacheReadString (int fd, FcChar8 *dest, int len)
36 {
37     FcChar8     c;
38     FcBool      escape;
39     int         size;
40     int         i;
41
42     if (len == 0)
43         return FcFalse;
44     
45     size = len;
46     i = 0;
47     escape = FcFalse;
48     while (read (fd, &c, 1) == 1)
49     {
50         if (!escape)
51         {
52             switch (c) {
53             case '"':
54                 c = '\0';
55                 break;
56             case '\\':
57                 escape = FcTrue;
58                 continue;
59             }
60         }
61         if (i == size)
62         {
63             dest[i++] = 0;
64             return dest;
65         }
66         dest[i++] = c;
67         if (c == '\0')
68             return dest;
69         escape = FcFalse;
70     }
71     return 0;
72 }
73
74 static FcBool
75 FcCacheWriteString (int fd, const FcChar8 *chars)
76 {
77     if (write (fd, chars, strlen(chars)+1) != strlen(chars)+1)
78         return FcFalse;
79     return FcTrue;
80 }
81
82 #if 0
83 /*
84  * Verify the saved timestamp for a file
85  */
86 FcBool
87 FcGlobalCacheCheckTime (const FcChar8 *file, FcGlobalCacheInfo *info)
88 {
89     struct stat     statb;
90
91     if (stat ((char *) file, &statb) < 0)
92     {
93         if (FcDebug () & FC_DBG_CACHE)
94             printf (" file %s missing\n", file);
95         return FcFalse;
96     }
97     if (statb.st_mtime != info->time)
98     {
99         if (FcDebug () & FC_DBG_CACHE)
100             printf (" timestamp mismatch (was %d is %d)\n",
101                     (int) info->time, (int) statb.st_mtime);
102         return FcFalse;
103     }
104     return FcTrue;
105 }
106
107 void
108 FcGlobalCacheReferenced (FcGlobalCache      *cache,
109                          FcGlobalCacheInfo  *info)
110 {
111     if (!info->referenced)
112     {
113         info->referenced = FcTrue;
114         cache->referenced++;
115         if (FcDebug () & FC_DBG_CACHE_REF)
116             printf ("Reference %d %s\n", cache->referenced, info->file);
117     }
118 }
119
120 /*
121  * Break a path into dir/base elements and compute the base hash
122  * and the dir length.  This is shared between the functions
123  * which walk the file caches
124  */
125
126 typedef struct _FcFilePathInfo {
127     const FcChar8   *dir;
128     int             dir_len;
129     const FcChar8   *base;
130     unsigned int    base_hash;
131 } FcFilePathInfo;
132
133 static FcFilePathInfo
134 FcFilePathInfoGet (const FcChar8    *path)
135 {
136     FcFilePathInfo  i;
137     FcChar8         *slash;
138
139     slash = FcStrLastSlash (path);
140     if (slash)
141     {
142         i.dir = path;
143         i.dir_len = slash - path;
144         if (!i.dir_len)
145             i.dir_len = 1;
146         i.base = slash + 1;
147     }
148     else
149     {
150         i.dir = (const FcChar8 *) ".";
151         i.dir_len = 1;
152         i.base = path;
153     }
154     i.base_hash = FcCacheHash (i.base, -1);
155     return i;
156 }
157
158 FcGlobalCache *
159 FcGlobalCacheCreate (void)
160 {
161     FcGlobalCache   *cache;
162     int             h;
163
164     cache = malloc (sizeof (FcGlobalCache));
165     if (!cache)
166         return 0;
167     FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCache));
168     for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
169         cache->ents[h] = 0;
170     cache->entries = 0;
171     cache->referenced = 0;
172     cache->updated = FcFalse;
173     cache->broken = FcFalse;
174     return cache;
175 }
176
177 void
178 FcGlobalCacheDestroy (FcGlobalCache *cache)
179 {
180     FcGlobalCacheDir    *d, *next;
181     int                 h;
182
183     for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++)
184     {
185         for (d = cache->ents[h]; d; d = next)
186         {
187             next = d->next;
188             FcGlobalCacheDirDestroy (d);
189         }
190     }
191     FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCache));
192     free (cache);
193 }
194
195 /*
196  * Cache file syntax is quite simple:
197  *
198  * "file_name" id time "font_name" \n
199  */
200  
201 void
202 FcGlobalCacheLoad (FcGlobalCache    *cache,
203                    const FcChar8    *cache_file)
204 {
205     FILE                *f;
206     FcChar8             file_buf[8192], *file;
207     int                 id;
208     time_t              time;
209     FcChar8             name_buf[8192], *name;
210     FcGlobalCacheInfo   *info;
211
212     f = fopen ((char *) cache_file, "r");
213     if (!f)
214         return;
215
216     cache->updated = FcFalse;
217     file = 0;
218     name = 0;
219     while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) &&
220            FcCacheReadInt (f, &id) &&
221            FcCacheReadTime (f, &time) &&
222            (name = FcCacheReadString (f, name_buf, sizeof (name_buf))))
223     {
224         if (FcDebug () & FC_DBG_CACHEV)
225             printf ("FcGlobalCacheLoad \"%s\" \"%20.20s\"\n", file, name);
226         if (!FcStrCmp (name, FC_FONT_FILE_DIR))
227             info = FcGlobalCacheDirAdd (cache, file, time, FcFalse, FcTrue);
228         else
229             info = FcGlobalCacheFileAdd (cache, file, id, time, name, FcFalse);
230         if (!info)
231             cache->broken = FcTrue;
232         else
233             cache->entries++;
234         if (FcDebug () & FC_DBG_CACHE_REF)
235             printf ("FcGlobalCacheLoad entry %d %s\n",
236                     cache->entries, file);
237         if (file != file_buf)
238             free (file);
239         if (name != name_buf)
240             free (name);
241         file = 0;
242         name = 0;
243     }
244     if (file && file != file_buf)
245         free (file);
246     if (name && name != name_buf)
247         free (name);
248     fclose (f);
249 }
250
251 FcBool
252 FcGlobalCacheUpdate (FcGlobalCache  *cache,
253                      const FcChar8  *file,
254                      int            id,
255                      const FcChar8  *name)
256 {
257     const FcChar8       *match;
258     struct stat         statb;
259     FcGlobalCacheInfo   *info;
260
261     match = file;
262
263     if (stat ((char *) file, &statb) < 0)
264         return FcFalse;
265     if (S_ISDIR (statb.st_mode))
266         info = FcGlobalCacheDirAdd (cache, file, statb.st_mtime, 
267                                     FcTrue, FcTrue);
268     else
269         info = FcGlobalCacheFileAdd (cache, file, id, statb.st_mtime, 
270                                     name, FcTrue);
271     if (info)
272     {
273         FcGlobalCacheReferenced (cache, info);
274         cache->updated = FcTrue;
275     }
276     else
277         cache->broken = FcTrue;
278     return info != 0;
279 }
280
281 FcBool
282 FcGlobalCacheSave (FcGlobalCache    *cache,
283                    const FcChar8    *cache_file)
284 {
285     FILE                *f;
286     int                 dir_hash, file_hash;
287     FcGlobalCacheDir    *dir;
288     FcGlobalCacheFile   *file;
289     FcAtomic            *atomic;
290
291     if (!cache->updated && cache->referenced == cache->entries)
292         return FcTrue;
293     
294     if (cache->broken)
295         return FcFalse;
296
297 #if defined (HAVE_GETUID) && defined (HAVE_GETEUID)
298     /* Set-UID programs can't safely update the cache */
299     if (getuid () != geteuid ())
300         return FcFalse;
301 #endif
302     
303     atomic = FcAtomicCreate (cache_file);
304     if (!atomic)
305         goto bail0;
306     if (!FcAtomicLock (atomic))
307         goto bail1;
308     f = fopen ((char *) FcAtomicNewFile(atomic), "w");
309     if (!f)
310         goto bail2;
311
312     for (dir_hash = 0; dir_hash < FC_GLOBAL_CACHE_DIR_HASH_SIZE; dir_hash++)
313     {
314         for (dir = cache->ents[dir_hash]; dir; dir = dir->next)
315         {
316             if (!dir->info.referenced)
317                 continue;
318             if (!FcCacheWriteString (f, dir->info.file))
319                 goto bail4;
320             if (PUTC (' ', f) == EOF)
321                 goto bail4;
322             if (!FcCacheWriteInt (f, 0))
323                 goto bail4;
324             if (PUTC (' ', f) == EOF)
325                 goto bail4;
326             if (!FcCacheWriteTime (f, dir->info.time))
327                 goto bail4;
328             if (PUTC (' ', f) == EOF)
329                 goto bail4;
330             if (!FcCacheWriteString (f, (FcChar8 *) FC_FONT_FILE_DIR))
331                 goto bail4;
332             if (PUTC ('\n', f) == EOF)
333                 goto bail4;
334             
335             for (file_hash = 0; file_hash < FC_GLOBAL_CACHE_FILE_HASH_SIZE; file_hash++)
336             {
337                 for (file = dir->ents[file_hash]; file; file = file->next)
338                 {
339                     if (!file->info.referenced)
340                         continue;
341                     if (!FcCacheWritePath (f, dir->info.file, file->info.file))
342                         goto bail4;
343                     if (PUTC (' ', f) == EOF)
344                         goto bail4;
345                     if (!FcCacheWriteInt (f, file->id < 0 ? 0 : file->id))
346                         goto bail4;
347                     if (PUTC (' ', f) == EOF)
348                         goto bail4;
349                     if (!FcCacheWriteTime (f, file->info.time))
350                         goto bail4;
351                     if (PUTC (' ', f) == EOF)
352                         goto bail4;
353                     if (!FcCacheWriteString (f, file->name))
354                         goto bail4;
355                     if (PUTC ('\n', f) == EOF)
356                         goto bail4;
357                 }
358             }
359         }
360     }
361
362     if (fclose (f) == EOF)
363         goto bail3;
364     
365     if (!FcAtomicReplaceOrig (atomic))
366         goto bail3;
367     
368     FcAtomicUnlock (atomic);
369     FcAtomicDestroy (atomic);
370
371     cache->updated = FcFalse;
372     return FcTrue;
373
374 bail4:
375     fclose (f);
376 bail3:
377     FcAtomicDeleteNew (atomic);
378 bail2:
379     FcAtomicUnlock (atomic);
380 bail1:
381     FcAtomicDestroy (atomic);
382 bail0:
383     return FcFalse;
384 }
385 #endif
386
387 /* 
388  * Find the next presumably-mmapable offset after the current file
389  * pointer.
390  */
391 int
392 FcCacheNextOffset(int fd)
393 {
394     off_t w;
395     w = lseek(fd, 0, SEEK_END);
396
397     if (w % PAGESIZE == 0) 
398         return w;
399     else
400         return ((w / PAGESIZE)+1)*PAGESIZE;
401 }
402
403 /* will go away once we use config->cache */
404 #define CACHE_DEFAULT_TMPDIR "/tmp"
405 #define CACHE_DEFAULT_NAME "/fontconfig-mmap"
406 static char *
407 FcCacheFilename(void)
408 {
409     struct utsname b;
410     static char * name = 0;
411
412     if (name)
413         return name;
414
415     if (uname(&b) == -1)
416         name = CACHE_DEFAULT_NAME;
417     else
418     {
419         char * tmpname = getenv("TMPDIR");
420         char * logname = getenv("LOGNAME");
421         if (!tmpname)
422             tmpname = CACHE_DEFAULT_TMPDIR;
423
424         name = malloc(strlen(CACHE_DEFAULT_NAME) +
425                       strlen(tmpname) +
426                       (logname ? strlen(logname) : 0) + 5);
427         strcpy(name, tmpname);
428         strcat(name, CACHE_DEFAULT_NAME);
429         strcat(name, "-");
430         strcat(name, logname ? logname : "");
431     }
432     return name;
433 }
434
435 /* 
436  * Wipe out static state.
437  */
438 void
439 FcCacheClearStatic()
440 {
441     FcFontSetClearStatic();
442     FcPatternClearStatic();
443     FcValueListClearStatic();
444     FcObjectClearStatic();
445     FcMatrixClearStatic();
446     FcCharSetClearStatic();
447     FcLangSetClearStatic();
448 }
449
450 /*
451  * Trigger the counting phase: this tells us how much to allocate.
452  */
453 FcBool
454 FcCachePrepareSerialize (FcConfig * config)
455 {
456     int i;
457     for (i = FcSetSystem; i <= FcSetApplication; i++)
458         if (config->fonts[i] && !FcFontSetPrepareSerialize(config->fonts[i]))
459             return FcFalse;
460     return FcTrue;
461 }
462
463 /* allocate and populate static structures */
464 FcBool
465 FcCacheSerialize (FcConfig * config)
466 {
467     int i;
468     for (i = FcSetSystem; i <= FcSetApplication; i++)
469         if (config->fonts[i] && !FcFontSetSerialize(config->fonts[i]))
470             return FcFalse;
471     return FcTrue;
472 }
473
474 /* get the current arch name */
475 /* caller is responsible for freeing returned pointer */
476 static char *
477 FcCacheGetCurrentArch (void)
478 {
479     struct utsname b;
480     char * current_arch_machine_name;
481
482     if (uname(&b) == -1)
483         return FcFalse;
484     current_arch_machine_name = strdup(b.machine);
485     /* if (getenv ("FAKE_ARCH")) // testing purposes
486        current_arch_machine_name = strdup(getenv("FAKE_ARCH")); */
487     return current_arch_machine_name;
488 }
489
490 /* return the address of the segment for the provided arch,
491  * or -1 if arch not found */
492 static off_t
493 FcCacheSkipToArch (int fd, const char * arch)
494 {
495     char candidate_arch_machine_name[64], bytes_to_skip[7];
496     off_t current_arch_start = 0;
497
498     /* skip arches that are not the current arch */
499     while (1)
500     {
501         long bs;
502
503         lseek (fd, current_arch_start, SEEK_SET);
504         if (FcCacheReadString (fd, candidate_arch_machine_name, 
505                                sizeof (candidate_arch_machine_name)) == 0)
506             break;
507         if (FcCacheReadString (fd, bytes_to_skip, 7) == 0)
508             break;
509         bs = a64l(bytes_to_skip);
510         if (bs == 0)
511             break;
512
513         if (strcmp (candidate_arch_machine_name, arch)==0)
514             break;
515         current_arch_start += bs;
516     }
517
518     if (strcmp (candidate_arch_machine_name, arch)!=0)
519         return -1;
520
521     return current_arch_start;
522 }
523
524 /* Cuts out the segment at the file pointer (moves everything else
525  * down to cover it), and leaves the file pointer at the end of the
526  * file. */
527 #define BUF_SIZE 8192
528
529 static FcBool 
530 FcCacheMoveDown (int fd, off_t start)
531 {
532     char * buf = malloc (BUF_SIZE);
533     char candidate_arch_machine_name[64], bytes_to_skip[7];
534     long bs; off_t pos;
535     int c, bytes_skipped;
536
537     if (!buf)
538         return FcFalse;
539
540     lseek (fd, start, SEEK_SET);
541     if (FcCacheReadString (fd, candidate_arch_machine_name, 
542                            sizeof (candidate_arch_machine_name)) == 0)
543         goto done;
544     if (FcCacheReadString (fd, bytes_to_skip, 7) == 0)
545         goto done;
546
547     bs = a64l(bytes_to_skip);
548     if (bs == 0)
549         goto done;
550
551     bytes_skipped = 0;
552     do
553     {
554         lseek (fd, start+bs+bytes_skipped, SEEK_SET);
555         if ((c = read (fd, buf, BUF_SIZE)) <= 0)
556             break;
557         lseek (fd, start+bytes_skipped, SEEK_SET);
558         if (write (fd, buf, c) < 0)
559             goto bail;
560         bytes_skipped += c;
561     }
562     while (c > 0);
563     lseek (fd, start+bytes_skipped, SEEK_SET);
564
565  done:
566     free (buf);
567     return FcTrue;
568
569  bail:
570     free (buf);
571     return FcFalse;
572 }
573
574 /* read serialized state from the cache file */
575 FcBool
576 FcCacheRead (FcConfig *config)
577 {
578     int fd, i;
579     FcCache metadata;
580     char * current_arch_machine_name;
581     char candidate_arch_machine_name[64], bytes_in_block[7];
582     off_t current_arch_start = 0;
583
584     if (force)
585         return FcFalse;
586
587     fd = open(FcCacheFilename(), O_RDONLY);
588     if (fd == -1)
589         return FcFalse;
590
591     current_arch_machine_name = FcCacheGetCurrentArch();
592     current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
593     if (current_arch_start < 0)
594         goto bail;
595
596     lseek (fd, current_arch_start, SEEK_SET);
597     if (FcCacheReadString (fd, candidate_arch_machine_name, 
598                            sizeof (candidate_arch_machine_name)) == 0)
599         goto bail;
600     if (FcCacheReadString (fd, bytes_in_block, 7) == 0)
601         goto bail;
602
603     // sanity check for endianness issues
604     read(fd, &metadata, sizeof(FcCache));
605     if (metadata.magic != FC_CACHE_MAGIC)
606         goto bail;
607
608     if (!FcObjectRead(fd, metadata)) goto bail1;
609     if (!FcStrSetRead(fd, metadata)) goto bail1;
610     if (!FcCharSetRead(fd, metadata)) goto bail1;
611     if (!FcMatrixRead(fd, metadata)) goto bail1;
612     if (!FcLangSetRead(fd, metadata)) goto bail1;
613     if (!FcValueListRead(fd, metadata)) goto bail1;
614     if (!FcPatternEltRead(fd, metadata)) goto bail1;
615     if (!FcPatternRead(fd, metadata)) goto bail1;
616     if (!FcFontSetRead(fd, config, metadata)) goto bail1;
617     close(fd);
618     free (current_arch_machine_name);
619     return FcTrue;
620
621  bail1:
622     for (i = FcSetSystem; i <= FcSetApplication; i++)
623         config->fonts[i] = 0;
624     close(fd);
625  bail:
626     free (current_arch_machine_name);
627     return FcFalse;
628 }
629
630 /* write serialized state to the cache file */
631 FcBool
632 FcCacheWrite (FcConfig * config)
633 {
634     int fd;
635     FcCache metadata;
636     off_t current_arch_start = 0, truncate_to;
637     char * current_arch_machine_name, bytes_written[7] = "dedbef";
638
639     if (!FcCachePrepareSerialize (config))
640         return FcFalse;
641
642     if (!FcCacheSerialize (config))
643         return FcFalse;
644
645     fd = open(FcCacheFilename(), O_RDWR | O_CREAT, 0666);
646     if (fd == -1)
647         return FcFalse;
648
649     current_arch_machine_name = FcCacheGetCurrentArch();
650     current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
651     if (current_arch_start < 0)
652         current_arch_start = FcCacheNextOffset (fd);
653
654     if (!FcCacheMoveDown(fd, current_arch_start))
655         goto bail;
656
657     current_arch_start = lseek(fd, 0, SEEK_CUR);
658     if (ftruncate (fd, current_arch_start) == -1)
659         goto bail;
660
661     /* reserve space for arch, count & metadata */
662     if (!FcCacheWriteString (fd, current_arch_machine_name))
663         goto bail;
664     if (!FcCacheWriteString (fd, bytes_written))
665         goto bail;
666     memset (&metadata, 0, sizeof(FcCache));
667     metadata.magic = FC_CACHE_MAGIC;
668     write(fd, &metadata, sizeof(FcCache));
669
670     if (!FcFontSetWrite(fd, config, &metadata)) goto bail;
671     if (!FcPatternWrite(fd, &metadata)) goto bail;
672     if (!FcPatternEltWrite(fd, &metadata)) goto bail;
673     if (!FcValueListWrite(fd, &metadata)) goto bail;
674     if (!FcLangSetWrite(fd, &metadata)) goto bail;
675     if (!FcCharSetWrite(fd, &metadata)) goto bail;
676     if (!FcMatrixWrite(fd, &metadata)) goto bail;
677     if (!FcStrSetWrite(fd, &metadata)) goto bail;
678     if (!FcObjectWrite(fd, &metadata)) goto bail;
679
680     /* now write the address of the next offset */
681     truncate_to = FcCacheNextOffset(fd) - current_arch_start;
682     lseek(fd, current_arch_start + strlen(current_arch_machine_name)+1, 
683           SEEK_SET);
684     strcpy (bytes_written, l64a(truncate_to));
685     if (!FcCacheWriteString (fd, bytes_written))
686         goto bail;
687
688     /* now rewrite metadata & truncate file */
689     if (write(fd, &metadata, sizeof(FcCache)) != sizeof (FcCache)) 
690         goto bail;
691     if (ftruncate (fd, current_arch_start + truncate_to) == -1)
692         goto bail;
693
694     close(fd);
695     return FcTrue;
696
697  bail:
698     free (current_arch_machine_name);
699     return FcFalse;
700 }
701
702 /* if true, ignore the cache file */
703 void
704 FcCacheForce (FcBool f)
705 {
706     force = f;
707 }