]> git.wh0rd.org - 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 }