1 /*
2 * Copyright © 2000 Keith Packard
3 * Copyright © 2005 Patrick Lam
4 *
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation, and that the name of the author(s) not be used in
10 * advertising or publicity pertaining to distribution of the software without
11 * specific, written prior permission. The authors make no
12 * representations about the suitability of this software for any purpose. It
13 * is provided "as is" without express or implied warranty.
14 *
15 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21 * PERFORMANCE OF THIS SOFTWARE.
22 */
23 #include "fcint.h"
24 #include "fcarch.h"
25 #include "fcmd5.h"
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <fcntl.h>
29 #ifdef HAVE_DIRENT_H
30 #include <dirent.h>
31 #endif
32 #include <string.h>
33 #include <limits.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36
37 #ifndef _WIN32
38 #include <sys/time.h>
39 #else
40 #include <winsock2.h> /* for struct timeval */
41 #endif
42
43 #include <assert.h>
44 #if defined(HAVE_MMAP) || defined(__CYGWIN__)
45 # include <unistd.h>
46 # include <sys/mman.h>
47 #endif
48 #if defined(_WIN32)
49 #include <sys/locking.h>
50 #endif
51
52 #ifndef O_BINARY
53 #define O_BINARY 0
54 #endif
55
56 FcBool
57 FcDirCacheCreateUUID (FcChar8 *dir,
58 FcBool force,
59 FcConfig *config)
60 {
61 return FcTrue;
62 }
63
64 FcBool
65 FcDirCacheDeleteUUID (const FcChar8 *dir,
66 FcConfig *config)
67 {
68 FcBool ret = FcTrue;
69 #ifndef _WIN32
70 const FcChar8 *sysroot;
71 FcChar8 *target, *d;
72 struct stat statb;
73 struct timeval times[2];
74
75 config = FcConfigReference (config);
76 if (!config)
77 return FcFalse;
78 sysroot = FcConfigGetSysRoot (config);
79 if (sysroot)
80 d = FcStrBuildFilename (sysroot, dir, NULL);
81 else
82 d = FcStrBuildFilename (dir, NULL);
83 if (FcStat (d, &statb) != 0)
84 {
85 ret = FcFalse;
86 goto bail;
87 }
88 target = FcStrBuildFilename (d, ".uuid", NULL);
89 ret = unlink ((char *) target) == 0;
90 if (ret)
91 {
92 times[0].tv_sec = statb.st_atime;
93 times[1].tv_sec = statb.st_mtime;
94 #ifdef HAVE_STRUCT_STAT_ST_MTIM
95 times[0].tv_usec = statb.st_atim.tv_nsec / 1000;
96 times[1].tv_usec = statb.st_mtim.tv_nsec / 1000;
97 #else
98 times[0].tv_usec = 0;
99 times[1].tv_usec = 0;
100 #endif
101 if (utimes ((const char *) d, times) != 0)
102 {
103 fprintf (stderr, "Unable to revert mtime: %s\n", d);
104 }
105 }
106 FcStrFree (target);
107 bail:
108 FcStrFree (d);
109 #endif
110 FcConfigDestroy (config);
111
112 return ret;
113 }
114
115 #define CACHEBASE_LEN (1 + 36 + 1 + sizeof (FC_ARCHITECTURE) + sizeof (FC_CACHE_SUFFIX))
116
117 static FcBool
118 FcCacheIsMmapSafe (int fd)
119 {
120 enum {
121 MMAP_NOT_INITIALIZED = 0,
122 MMAP_USE,
123 MMAP_DONT_USE,
124 MMAP_CHECK_FS,
125 } status;
126 static void *static_status;
127
128 status = (intptr_t) fc_atomic_ptr_get (&static_status);
129
130 if (status == MMAP_NOT_INITIALIZED)
131 {
132 const char *env = getenv ("FONTCONFIG_USE_MMAP");
133 FcBool use;
134 if (env && FcNameBool ((const FcChar8 *) env, &use))
135 status = use ? MMAP_USE : MMAP_DONT_USE;
136 else
137 status = MMAP_CHECK_FS;
138 (void) fc_atomic_ptr_cmpexch (&static_status, NULL, (void *) (intptr_t) status);
139 }
140
141 if (status == MMAP_CHECK_FS)
142 return FcIsFsMmapSafe (fd);
143 else
144 return status == MMAP_USE;
145
146 }
147
148 static const char bin2hex[] = { '0', '1', '2', '3',
149 '4', '5', '6', '7',
150 '8', '9', 'a', 'b',
151 'c', 'd', 'e', 'f' };
152
153 static FcChar8 *
154 FcDirCacheBasenameMD5 (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
155 {
156 FcChar8 *mapped_dir = NULL;
157 unsigned char hash[16];
158 FcChar8 *hex_hash, *key = NULL;
159 int cnt;
160 struct MD5Context ctx;
161 const FcChar8 *salt, *orig_dir = NULL;
162
163 salt = FcConfigMapSalt (config, dir);
164 /* Obtain a path where "dir" is mapped to.
165 * In case:
166 * <remap-dir as-path="/usr/share/fonts">/run/host/fonts</remap-dir>
167 *
168 * FcConfigMapFontPath (config, "/run/host/fonts") will returns "/usr/share/fonts".
169 */
170 mapped_dir = FcConfigMapFontPath(config, dir);
171 if (mapped_dir)
172 {
173 orig_dir = dir;
174 dir = mapped_dir;
175 }
176 if (salt)
177 {
178 size_t dl = strlen ((const char *) dir);
179 size_t sl = strlen ((const char *) salt);
180
181 key = (FcChar8 *) malloc (dl + sl + 1);
182 memcpy (key, dir, dl);
183 memcpy (key + dl, salt, sl + 1);
184 key[dl + sl] = 0;
185 if (!orig_dir)
186 orig_dir = dir;
187 dir = key;
188 }
189 MD5Init (&ctx);
190 MD5Update (&ctx, (const unsigned char *)dir, strlen ((const char *) dir));
191
192 MD5Final (hash, &ctx);
193
194 if (key)
195 FcStrFree (key);
196
197 cache_base[0] = '/';
198 hex_hash = cache_base + 1;
199 for (cnt = 0; cnt < 16; ++cnt)
200 {
201 hex_hash[2*cnt ] = bin2hex[hash[cnt] >> 4];
202 hex_hash[2*cnt+1] = bin2hex[hash[cnt] & 0xf];
203 }
204 hex_hash[2*cnt] = 0;
205 strcat ((char *) cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
206 if (FcDebug() & FC_DBG_CACHE)
207 {
208 printf ("cache: %s (dir: %s%s%s%s%s%s)\n", cache_base, orig_dir ? orig_dir : dir, mapped_dir ? " (mapped to " : "", mapped_dir ? (char *)mapped_dir : "", mapped_dir ? ")" : "", salt ? ", salt: " : "", salt ? (char *)salt : "");
209 }
210
211 if (mapped_dir)
212 FcStrFree(mapped_dir);
213
214 return cache_base;
215 }
216
217 #ifndef _WIN32
218 static FcChar8 *
219 FcDirCacheBasenameUUID (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
220 {
221 FcChar8 *target, *fuuid;
222 const FcChar8 *sysroot = FcConfigGetSysRoot (config);
223 int fd;
224
225 /* We don't need to apply remapping here. because .uuid was created at that very directory
226 * to determine the cache name no matter where it was mapped to.
227 */
228 cache_base[0] = 0;
229 if (sysroot)
230 target = FcStrBuildFilename (sysroot, dir, NULL);
231 else
232 target = FcStrdup (dir);
233 fuuid = FcStrBuildFilename (target, ".uuid", NULL);
234 if ((fd = FcOpen ((char *) fuuid, O_RDONLY)) != -1)
235 {
236 char suuid[37];
237 ssize_t len;
238
239 memset (suuid, 0, sizeof (suuid));
240 len = read (fd, suuid, 36);
241 suuid[36] = 0;
242 close (fd);
243 if (len < 0)
244 goto bail;
245 cache_base[0] = '/';
246 strcpy ((char *)&cache_base[1], suuid);
247 strcat ((char *) cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
248 if (FcDebug () & FC_DBG_CACHE)
249 {
250 printf ("cache fallbacks to: %s (dir: %s)\n", cache_base, dir);
251 }
252 }
253 bail:
254 FcStrFree (fuuid);
255 FcStrFree (target);
256
257 return cache_base;
258 }
259 #endif
260
261 FcBool
262 FcDirCacheUnlink (const FcChar8 *dir, FcConfig *config)
263 {
264 FcChar8 *cache_hashed = NULL;
265 FcChar8 cache_base[CACHEBASE_LEN];
266 #ifndef _WIN32
267 FcChar8 uuid_cache_base[CACHEBASE_LEN];
268 #endif
269 FcStrList *list;
270 FcChar8 *cache_dir;
271 const FcChar8 *sysroot;
272 FcBool ret = FcTrue;
273
274 config = FcConfigReference (config);
275 if (!config)
276 return FcFalse;
277 sysroot = FcConfigGetSysRoot (config);
278
279 FcDirCacheBasenameMD5 (config, dir, cache_base);
280 #ifndef _WIN32
281 FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
282 #endif
283
284 list = FcStrListCreate (config->cacheDirs);
285 if (!list)
286 {
287 ret = FcFalse;
288 goto bail;
289 }
290
291 while ((cache_dir = FcStrListNext (list)))
292 {
293 if (sysroot)
294 cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
295 else
296 cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
297 if (!cache_hashed)
298 break;
299 (void) unlink ((char *) cache_hashed);
300 FcStrFree (cache_hashed);
301 #ifndef _WIN32
302 if (uuid_cache_base[0] != 0)
303 {
304 if (sysroot)
305 cache_hashed = FcStrBuildFilename (sysroot, cache_dir, uuid_cache_base, NULL);
306 else
307 cache_hashed = FcStrBuildFilename (cache_dir, uuid_cache_base, NULL);
308 if (!cache_hashed)
309 break;
310 (void) unlink ((char *) cache_hashed);
311 FcStrFree (cache_hashed);
312 }
313 #endif
314 }
315 FcStrListDone (list);
316 FcDirCacheDeleteUUID (dir, config);
317 /* return FcFalse if something went wrong */
318 if (cache_dir)
319 ret = FcFalse;
320 bail:
321 FcConfigDestroy (config);
322
323 return ret;
324 }
325
326 static int
327 FcDirCacheOpenFile (const FcChar8 *cache_file, struct stat *file_stat)
328 {
329 int fd;
330
331 #ifdef _WIN32
332 if (FcStat (cache_file, file_stat) < 0)
333 return -1;
334 #endif
335 fd = FcOpen((char *) cache_file, O_RDONLY | O_BINARY);
336 if (fd < 0)
337 return fd;
338 #ifndef _WIN32
339 if (fstat (fd, file_stat) < 0)
340 {
341 close (fd);
342 return -1;
343 }
344 #endif
345 return fd;
346 }
347
348 /*
349 * Look for a cache file for the specified dir. Attempt
350 * to use each one we find, stopping when the callback
351 * indicates success
352 */
353 static FcBool
354 FcDirCacheProcess (FcConfig *config, const FcChar8 *dir,
355 FcBool (*callback) (FcConfig *config, int fd, struct stat *fd_stat,
356 struct stat *dir_stat, struct timeval *cache_mtime, void *closure),
357 void *closure, FcChar8 **cache_file_ret)
358 {
359 int fd = -1;
360 FcChar8 cache_base[CACHEBASE_LEN];
361 FcStrList *list;
362 FcChar8 *cache_dir, *d;
363 struct stat file_stat, dir_stat;
364 FcBool ret = FcFalse;
365 const FcChar8 *sysroot = FcConfigGetSysRoot (config);
366 struct timeval latest_mtime = (struct timeval){ 0 };
367
368 if (sysroot)
369 d = FcStrBuildFilename (sysroot, dir, NULL);
370 else
371 d = FcStrdup (dir);
372 if (FcStatChecksum (d, &dir_stat) < 0)
373 {
374 FcStrFree (d);
375 return FcFalse;
376 }
377 FcStrFree (d);
378
379 FcDirCacheBasenameMD5 (config, dir, cache_base);
380
381 list = FcStrListCreate (config->cacheDirs);
382 if (!list)
383 return FcFalse;
384
385 while ((cache_dir = FcStrListNext (list)))
386 {
387 FcChar8 *cache_hashed;
388 #ifndef _WIN32
389 FcBool retried = FcFalse;
390 #endif
391
392 if (sysroot)
393 cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
394 else
395 cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
396 if (!cache_hashed)
397 break;
398 #ifndef _WIN32
399 retry:
400 #endif
401 fd = FcDirCacheOpenFile (cache_hashed, &file_stat);
402 if (fd >= 0) {
403 ret = (*callback) (config, fd, &file_stat, &dir_stat, &latest_mtime, closure);
404 close (fd);
405 if (ret)
406 {
407 if (cache_file_ret)
408 {
409 if (*cache_file_ret)
410 FcStrFree (*cache_file_ret);
411 *cache_file_ret = cache_hashed;
412 }
413 else
414 FcStrFree (cache_hashed);
415 }
416 else
417 FcStrFree (cache_hashed);
418 }
419 #ifndef _WIN32
420 else if (!retried)
421 {
422 FcChar8 uuid_cache_base[CACHEBASE_LEN];
423
424 retried = FcTrue;
425 FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
426 if (uuid_cache_base[0] != 0)
427 {
428 FcStrFree (cache_hashed);
429 if (sysroot)
430 cache_hashed = FcStrBuildFilename (sysroot, cache_dir, uuid_cache_base, NULL);
431 else
432 cache_hashed = FcStrBuildFilename (cache_dir, uuid_cache_base, NULL);
433 if (!cache_hashed)
434 break;
435 goto retry;
436 }
437 else
438 FcStrFree (cache_hashed);
439 }
440 #endif
441 else
442 FcStrFree (cache_hashed);
443 }
444 FcStrListDone (list);
445
446 if (closure)
447 return !!(*((FcCache **)closure) != NULL);
448 return ret;
449 }
450
451 #define FC_CACHE_MIN_MMAP 1024
452
453 /*
454 * Skip list element, make sure the 'next' pointer is the last thing
455 * in the structure, it will be allocated large enough to hold all
456 * of the necessary pointers
457 */
458
459 typedef struct _FcCacheSkip FcCacheSkip;
460
461 struct _FcCacheSkip {
462 FcCache *cache;
463 FcRef ref;
464 intptr_t size;
465 void *allocated;
466 dev_t cache_dev;
467 ino_t cache_ino;
468 time_t cache_mtime;
469 long cache_mtime_nano;
470 FcCacheSkip *next[1];
471 };
472
473 /*
474 * The head of the skip list; pointers for every possible level
475 * in the skip list, plus the largest level in the list
476 */
477
478 #define FC_CACHE_MAX_LEVEL 16
479
480 /* Protected by cache_lock below */
481 static FcCacheSkip *fcCacheChains[FC_CACHE_MAX_LEVEL];
482 static int fcCacheMaxLevel;
483
484
485 static FcMutex *cache_lock;
486
487 static void
488 lock_cache (void)
489 {
490 FcMutex *lock;
491 retry:
492 lock = fc_atomic_ptr_get (&cache_lock);
493 if (!lock) {
494 lock = (FcMutex *) malloc (sizeof (FcMutex));
495 FcMutexInit (lock);
496 if (!fc_atomic_ptr_cmpexch (&cache_lock, NULL, lock)) {
497 FcMutexFinish (lock);
498 free (lock);
499 goto retry;
500 }
501
502 FcMutexLock (lock);
503 /* Initialize random state */
504 FcRandom ();
505 return;
506 }
507 FcMutexLock (lock);
508 }
509
510 static void
511 unlock_cache (void)
512 {
513 FcMutex *lock;
514 lock = fc_atomic_ptr_get (&cache_lock);
515 FcMutexUnlock (lock);
516 }
517
518 static void
519 free_lock (void)
520 {
521 FcMutex *lock;
522 lock = fc_atomic_ptr_get (&cache_lock);
523 if (lock && fc_atomic_ptr_cmpexch (&cache_lock, lock, NULL)) {
524 FcMutexFinish (lock);
525 free (lock);
526 }
527 }
528
529
530
531 /*
532 * Generate a random level number, distributed
533 * so that each level is 1/4 as likely as the one before
534 *
535 * Note that level numbers run 1 <= level <= MAX_LEVEL
536 */
537 static int
538 random_level (void)
539 {
540 /* tricky bit -- each bit is '1' 75% of the time */
541 long int bits = FcRandom () | FcRandom ();
542 int level = 0;
543
544 while (++level < FC_CACHE_MAX_LEVEL)
545 {
546 if (bits & 1)
547 break;
548 bits >>= 1;
549 }
550 return level;
551 }
552
553 /*
554 * Insert cache into the list
555 */
556 static FcBool
557 FcCacheInsert (FcCache *cache, struct stat *cache_stat)
558 {
559 FcCacheSkip **update[FC_CACHE_MAX_LEVEL];
560 FcCacheSkip *s, **next;
561 int i, level;
562
563 lock_cache ();
564
565 /*
566 * Find links along each chain
567 */
568 next = fcCacheChains;
569 for (i = fcCacheMaxLevel; --i >= 0; )
570 {
571 for (; (s = next[i]); next = s->next)
572 if (s->cache > cache)
573 break;
574 update[i] = &next[i];
575 }
576
577 /*
578 * Create new list element
579 */
580 level = random_level ();
581 if (level > fcCacheMaxLevel)
582 {
583 level = fcCacheMaxLevel + 1;
584 update[fcCacheMaxLevel] = &fcCacheChains[fcCacheMaxLevel];
585 fcCacheMaxLevel = level;
586 }
587
588 s = malloc (sizeof (FcCacheSkip) + (level - 1) * sizeof (FcCacheSkip *));
589 if (!s)
590 return FcFalse;
591
592 s->cache = cache;
593 s->size = cache->size;
594 s->allocated = NULL;
595 FcRefInit (&s->ref, 1);
596 if (cache_stat)
597 {
598 s->cache_dev = cache_stat->st_dev;
599 s->cache_ino = cache_stat->st_ino;
600 s->cache_mtime = cache_stat->st_mtime;
601 #ifdef HAVE_STRUCT_STAT_ST_MTIM
602 s->cache_mtime_nano = cache_stat->st_mtim.tv_nsec;
603 #else
604 s->cache_mtime_nano = 0;
605 #endif
606 }
607 else
608 {
609 s->cache_dev = 0;
610 s->cache_ino = 0;
611 s->cache_mtime = 0;
612 s->cache_mtime_nano = 0;
613 }
614
615 /*
616 * Insert into all fcCacheChains
617 */
618 for (i = 0; i < level; i++)
619 {
620 s->next[i] = *update[i];
621 *update[i] = s;
622 }
623
624 unlock_cache ();
625 return FcTrue;
626 }
627
628 static FcCacheSkip *
629 FcCacheFindByAddrUnlocked (void *object)
630 {
631 int i;
632 FcCacheSkip **next = fcCacheChains;
633 FcCacheSkip *s;
634
635 if (!object)
636 return NULL;
637
638 /*
639 * Walk chain pointers one level at a time
640 */
641 for (i = fcCacheMaxLevel; --i >= 0;)
642 while (next[i] && (char *) object >= ((char *) next[i]->cache + next[i]->size))
643 next = next[i]->next;
644 /*
645 * Here we are
646 */
647 s = next[0];
648 if (s && (char *) object < ((char *) s->cache + s->size))
649 return s;
650 return NULL;
651 }
652
653 static FcCacheSkip *
654 FcCacheFindByAddr (void *object)
655 {
656 FcCacheSkip *ret;
657 lock_cache ();
658 ret = FcCacheFindByAddrUnlocked (object);
659 unlock_cache ();
660 return ret;
661 }
662
663 static void
664 FcCacheRemoveUnlocked (FcCache *cache)
665 {
666 FcCacheSkip **update[FC_CACHE_MAX_LEVEL];
667 FcCacheSkip *s, **next;
668 int i;
669 void *allocated;
670
671 /*
672 * Find links along each chain
673 */
674 next = fcCacheChains;
675 for (i = fcCacheMaxLevel; --i >= 0; )
676 {
677 for (; (s = next[i]); next = s->next)
678 if (s->cache >= cache)
679 break;
680 update[i] = &next[i];
681 }
682 s = next[0];
683 for (i = 0; i < fcCacheMaxLevel && *update[i] == s; i++)
684 *update[i] = s->next[i];
685 while (fcCacheMaxLevel > 0 && fcCacheChains[fcCacheMaxLevel - 1] == NULL)
686 fcCacheMaxLevel--;
687
688 if (s)
689 {
690 allocated = s->allocated;
691 while (allocated)
692 {
693 /* First element in allocated chunk is the free list */
694 next = *(void **)allocated;
695 free (allocated);
696 allocated = next;
697 }
698 free (s);
699 }
700 }
701
702 static FcCache *
703 FcCacheFindByStat (struct stat *cache_stat)
704 {
705 FcCacheSkip *s;
706
707 lock_cache ();
708 for (s = fcCacheChains[0]; s; s = s->next[0])
709 if (s->cache_dev == cache_stat->st_dev &&
710 s->cache_ino == cache_stat->st_ino &&
711 s->cache_mtime == cache_stat->st_mtime)
712 {
713 #ifdef HAVE_STRUCT_STAT_ST_MTIM
714 if (s->cache_mtime_nano != cache_stat->st_mtim.tv_nsec)
715 continue;
716 #endif
717 FcRefInc (&s->ref);
718 unlock_cache ();
719 return s->cache;
720 }
721 unlock_cache ();
722 return NULL;
723 }
724
725 static void
726 FcDirCacheDisposeUnlocked (FcCache *cache)
727 {
728 FcCacheRemoveUnlocked (cache);
729
730 switch (cache->magic) {
731 case FC_CACHE_MAGIC_ALLOC:
732 free (cache);
733 break;
734 case FC_CACHE_MAGIC_MMAP:
735 #if defined(HAVE_MMAP) || defined(__CYGWIN__)
736 munmap (cache, cache->size);
737 #elif defined(_WIN32)
738 UnmapViewOfFile (cache);
739 #endif
740 break;
741 }
742 }
743
744 void
745 FcCacheObjectReference (void *object)
746 {
747 FcCacheSkip *skip = FcCacheFindByAddr (object);
748
749 if (skip)
750 FcRefInc (&skip->ref);
751 }
752
753 void
754 FcCacheObjectDereference (void *object)
755 {
756 FcCacheSkip *skip;
757
758 lock_cache ();
759 skip = FcCacheFindByAddrUnlocked (object);
760 if (skip)
761 {
762 if (FcRefDec (&skip->ref) == 1)
763 FcDirCacheDisposeUnlocked (skip->cache);
764 }
765 unlock_cache ();
766 }
767
768 void *
769 FcCacheAllocate (FcCache *cache, size_t len)
770 {
771 FcCacheSkip *skip;
772 void *allocated = NULL;
773
774 lock_cache ();
775 skip = FcCacheFindByAddrUnlocked (cache);
776 if (skip)
777 {
778 void *chunk = malloc (sizeof (void *) + len);
779 if (chunk)
780 {
781 /* First element in allocated chunk is the free list */
782 *(void **)chunk = skip->allocated;
783 skip->allocated = chunk;
784 /* Return the rest */
785 allocated = ((FcChar8 *)chunk) + sizeof (void *);
786 }
787 }
788 unlock_cache ();
789 return allocated;
790 }
791
792 void
793 FcCacheFini (void)
794 {
795 int i;
796
797 if (FcDebug() & FC_DBG_CACHE)
798 {
799 for (i = 0; i < FC_CACHE_MAX_LEVEL; i++)
800 {
801 if (fcCacheChains[i] != NULL)
802 {
803 FcCacheSkip *s = fcCacheChains[i];
804 fprintf(stderr, "Fontconfig error: not freed %p (dir: %s, refcount %" FC_ATOMIC_INT_FORMAT ")\n", s->cache, FcCacheDir(s->cache), s->ref.count);
805 }
806 }
807 }
808
809 free_lock ();
810 }
811
812 static FcBool
813 FcCacheTimeValid (FcConfig *config, FcCache *cache, struct stat *dir_stat)
814 {
815 struct stat dir_static;
816 FcBool fnano = FcTrue;
817
818 if (!dir_stat)
819 {
820 const FcChar8 *sysroot = FcConfigGetSysRoot (config);
821 FcChar8 *d;
822
823 if (sysroot)
824 d = FcStrBuildFilename (sysroot, FcCacheDir (cache), NULL);
825 else
826 d = FcStrdup (FcCacheDir (cache));
827 if (FcStatChecksum (d, &dir_static) < 0)
828 {
829 FcStrFree (d);
830 return FcFalse;
831 }
832 FcStrFree (d);
833 dir_stat = &dir_static;
834 }
835 #ifdef HAVE_STRUCT_STAT_ST_MTIM
836 fnano = (cache->checksum_nano == dir_stat->st_mtim.tv_nsec);
837 if (FcDebug () & FC_DBG_CACHE)
838 printf ("FcCacheTimeValid dir \"%s\" cache checksum %d.%ld dir checksum %d.%ld\n",
839 FcCacheDir (cache), cache->checksum, (long)cache->checksum_nano, (int) dir_stat->st_mtime, dir_stat->st_mtim.tv_nsec);
840 #else
841 if (FcDebug () & FC_DBG_CACHE)
842 printf ("FcCacheTimeValid dir \"%s\" cache checksum %d dir checksum %d\n",
843 FcCacheDir (cache), cache->checksum, (int) dir_stat->st_mtime);
844 #endif
845
846 return dir_stat->st_mtime == 0 || (cache->checksum == (int) dir_stat->st_mtime && fnano);
847 }
848
849 static FcBool
850 FcCacheOffsetsValid (FcCache *cache)
851 {
852 char *base = (char *)cache;
853 char *end = base + cache->size;
854 intptr_t *dirs;
855 FcFontSet *fs;
856 int i, j;
857
858 if (cache->dir < 0 || cache->dir > cache->size - sizeof (intptr_t) ||
859 memchr (base + cache->dir, '\0', cache->size - cache->dir) == NULL)
860 return FcFalse;
861
862 if (cache->dirs < 0 || cache->dirs >= cache->size ||
863 cache->dirs_count < 0 ||
864 cache->dirs_count > (cache->size - cache->dirs) / sizeof (intptr_t))
865 return FcFalse;
866
867 dirs = FcCacheDirs (cache);
868 if (dirs)
869 {
870 for (i = 0; i < cache->dirs_count; i++)
871 {
872 FcChar8 *dir;
873
874 if (dirs[i] < 0 ||
875 dirs[i] > end - (char *) dirs - sizeof (intptr_t))
876 return FcFalse;
877
878 dir = FcOffsetToPtr (dirs, dirs[i], FcChar8);
879 if (memchr (dir, '\0', end - (char *) dir) == NULL)
880 return FcFalse;
881 }
882 }
883
884 if (cache->set < 0 || cache->set > cache->size - sizeof (FcFontSet))
885 return FcFalse;
886
887 fs = FcCacheSet (cache);
888 if (fs)
889 {
890 if (fs->nfont > (end - (char *) fs) / sizeof (FcPattern))
891 return FcFalse;
892
893 if (!FcIsEncodedOffset(fs->fonts))
894 return FcFalse;
895
896 for (i = 0; i < fs->nfont; i++)
897 {
898 FcPattern *font = FcFontSetFont (fs, i);
899 FcPatternElt *e;
900 FcValueListPtr l;
901 char *last_offset;
902
903 if ((char *) font < base ||
904 (char *) font > end - sizeof (FcFontSet) ||
905 font->elts_offset < 0 ||
906 font->elts_offset > end - (char *) font ||
907 font->num > (end - (char *) font - font->elts_offset) / sizeof (FcPatternElt) ||
908 !FcRefIsConst (&font->ref))
909 return FcFalse;
910
911
912 e = FcPatternElts(font);
913 if (e->values != 0 && !FcIsEncodedOffset(e->values))
914 return FcFalse;
915
916 for (j = 0; j < font->num; j++)
917 {
918 last_offset = (char *) font + font->elts_offset;
919 for (l = FcPatternEltValues(&e[j]); l; l = FcValueListNext(l))
920 {
921 if ((char *) l < last_offset || (char *) l > end - sizeof (*l) ||
922 (l->next != NULL && !FcIsEncodedOffset(l->next)))
923 return FcFalse;
924 last_offset = (char *) l + 1;
925 }
926 }
927 }
928 }
929
930 return FcTrue;
931 }
932
933 /*
934 * Map a cache file into memory
935 */
936 static FcCache *
937 FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat)
938 {
939 FcCache *cache;
940 FcBool allocated = FcFalse;
941
942 if (fd_stat->st_size > INTPTR_MAX ||
943 fd_stat->st_size < (int) sizeof (FcCache))
944 return NULL;
945 cache = FcCacheFindByStat (fd_stat);
946 if (cache)
947 {
948 if (FcCacheTimeValid (config, cache, dir_stat))
949 return cache;
950 FcDirCacheUnload (cache);
951 cache = NULL;
952 }
953
954 /*
955 * Large cache files are mmap'ed, smaller cache files are read. This
956 * balances the system cost of mmap against per-process memory usage.
957 */
958 if (FcCacheIsMmapSafe (fd) && fd_stat->st_size >= FC_CACHE_MIN_MMAP)
959 {
960 #if defined(HAVE_MMAP) || defined(__CYGWIN__)
961 cache = mmap (0, fd_stat->st_size, PROT_READ, MAP_SHARED, fd, 0);
962 #if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
963 posix_fadvise (fd, 0, fd_stat->st_size, POSIX_FADV_WILLNEED);
964 #endif
965 if (cache == MAP_FAILED)
966 cache = NULL;
967 #elif defined(_WIN32)
968 {
969 HANDLE hFileMap;
970
971 cache = NULL;
972 hFileMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL,
973 PAGE_READONLY, 0, 0, NULL);
974 if (hFileMap != NULL)
975 {
976 cache = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0,
977 fd_stat->st_size);
978 CloseHandle (hFileMap);
979 }
980 }
981 #endif
982 }
983 if (!cache)
984 {
985 cache = malloc (fd_stat->st_size);
986 if (!cache)
987 return NULL;
988
989 if (read (fd, cache, fd_stat->st_size) != fd_stat->st_size)
990 {
991 free (cache);
992 return NULL;
993 }
994 allocated = FcTrue;
995 }
996 if (cache->magic != FC_CACHE_MAGIC_MMAP ||
997 cache->version < FC_CACHE_VERSION_NUMBER ||
998 cache->size != (intptr_t) fd_stat->st_size ||
999 !FcCacheOffsetsValid (cache) ||
1000 !FcCacheTimeValid (config, cache, dir_stat) ||
1001 !FcCacheInsert (cache, fd_stat))
1002 {
1003 if (allocated)
1004 free (cache);
1005 else
1006 {
1007 #if defined(HAVE_MMAP) || defined(__CYGWIN__)
1008 munmap (cache, fd_stat->st_size);
1009 #elif defined(_WIN32)
1010 UnmapViewOfFile (cache);
1011 #endif
1012 }
1013 return NULL;
1014 }
1015
1016 /* Mark allocated caches so they're freed rather than unmapped */
1017 if (allocated)
1018 cache->magic = FC_CACHE_MAGIC_ALLOC;
1019
1020 return cache;
1021 }
1022
1023 void
1024 FcDirCacheReference (FcCache *cache, int nref)
1025 {
1026 FcCacheSkip *skip = FcCacheFindByAddr (cache);
1027
1028 if (skip)
1029 FcRefAdd (&skip->ref, nref);
1030 }
1031
1032 void
1033 FcDirCacheUnload (FcCache *cache)
1034 {
1035 FcCacheObjectDereference (cache);
1036 }
1037
1038 static FcBool
1039 FcDirCacheMapHelper (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat, struct timeval *latest_cache_mtime, void *closure)
1040 {
1041 FcCache *cache = FcDirCacheMapFd (config, fd, fd_stat, dir_stat);
1042 struct timeval cache_mtime, zero_mtime = { 0, 0}, dir_mtime;
1043
1044 if (!cache)
1045 return FcFalse;
1046 cache_mtime.tv_sec = fd_stat->st_mtime;
1047 dir_mtime.tv_sec = dir_stat->st_mtime;
1048 #ifdef HAVE_STRUCT_STAT_ST_MTIM
1049 cache_mtime.tv_usec = fd_stat->st_mtim.tv_nsec / 1000;
1050 dir_mtime.tv_usec = dir_stat->st_mtim.tv_nsec / 1000;
1051 #else
1052 cache_mtime.tv_usec = 0;
1053 dir_mtime.tv_usec = 0;
1054 #endif
1055 /* special take care of OSTree */
1056 if (!timercmp (&zero_mtime, &dir_mtime, !=))
1057 {
1058 if (!timercmp (&zero_mtime, &cache_mtime, !=))
1059 {
1060 if (*((FcCache **) closure))
1061 FcDirCacheUnload (*((FcCache **) closure));
1062 }
1063 else if (*((FcCache **) closure) && !timercmp (&zero_mtime, latest_cache_mtime, !=))
1064 {
1065 FcDirCacheUnload (cache);
1066 return FcFalse;
1067 }
1068 else if (timercmp (latest_cache_mtime, &cache_mtime, <))
1069 {
1070 if (*((FcCache **) closure))
1071 FcDirCacheUnload (*((FcCache **) closure));
1072 }
1073 }
1074 else if (timercmp (latest_cache_mtime, &cache_mtime, <))
1075 {
1076 if (*((FcCache **) closure))
1077 FcDirCacheUnload (*((FcCache **) closure));
1078 }
1079 else
1080 {
1081 FcDirCacheUnload (cache);
1082 return FcFalse;
1083 }
1084 latest_cache_mtime->tv_sec = cache_mtime.tv_sec;
1085 latest_cache_mtime->tv_usec = cache_mtime.tv_usec;
1086 *((FcCache **) closure) = cache;
1087 return FcTrue;
1088 }
1089
1090 FcCache *
1091 FcDirCacheLoad (const FcChar8 *dir, FcConfig *config, FcChar8 **cache_file)
1092 {
1093 FcCache *cache = NULL;
1094
1095 config = FcConfigReference (config);
1096 if (!config)
1097 return NULL;
1098 if (!FcDirCacheProcess (config, dir,
1099 FcDirCacheMapHelper,
1100 &cache, cache_file))
1101 cache = NULL;
1102
1103 FcConfigDestroy (config);
1104
1105 return cache;
1106 }
1107
1108 FcCache *
1109 FcDirCacheLoadFile (const FcChar8 *cache_file, struct stat *file_stat)
1110 {
1111 int fd;
1112 FcCache *cache = NULL;
1113 struct stat my_file_stat;
1114 FcConfig *config;
1115
1116 if (!file_stat)
1117 file_stat = &my_file_stat;
1118 config = FcConfigReference (NULL);
1119 if (!config)
1120 return NULL;
1121 fd = FcDirCacheOpenFile (cache_file, file_stat);
1122 if (fd >= 0)
1123 {
1124 cache = FcDirCacheMapFd (config, fd, file_stat, NULL);
1125 close (fd);
1126 }
1127 FcConfigDestroy (config);
1128
1129 return cache;
1130 }
1131
1132 static int
1133 FcDirChecksum (struct stat *statb)
1134 {
1135 int ret = (int) statb->st_mtime;
1136 char *endptr;
1137 char *source_date_epoch;
1138 unsigned long long epoch;
1139
1140 source_date_epoch = getenv("SOURCE_DATE_EPOCH");
1141 if (source_date_epoch)
1142 {
1143 errno = 0;
1144 epoch = strtoull(source_date_epoch, &endptr, 10);
1145
1146 if (endptr == source_date_epoch)
1147 fprintf (stderr,
1148 "Fontconfig: SOURCE_DATE_EPOCH invalid\n");
1149 else if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0))
1150 || (errno != 0 && epoch == 0))
1151 fprintf (stderr,
1152 "Fontconfig: SOURCE_DATE_EPOCH: strtoull: %s: %" FC_UINT64_FORMAT "\n",
1153 strerror(errno), epoch);
1154 else if (*endptr != '\0')
1155 fprintf (stderr,
1156 "Fontconfig: SOURCE_DATE_EPOCH has trailing garbage\n");
1157 else if (epoch > ULONG_MAX)
1158 fprintf (stderr,
1159 "Fontconfig: SOURCE_DATE_EPOCH must be <= %lu but saw: %" FC_UINT64_FORMAT "\n",
1160 ULONG_MAX, epoch);
1161 else if (epoch < ret)
1162 /* Only override if directory is newer */
1163 ret = (int) epoch;
1164 }
1165
1166 return ret;
1167 }
1168
1169 static int64_t
1170 FcDirChecksumNano (struct stat *statb)
1171 {
1172 #ifdef HAVE_STRUCT_STAT_ST_MTIM
1173 /* No nanosecond component to parse */
1174 if (getenv("SOURCE_DATE_EPOCH"))
1175 return 0;
1176 return statb->st_mtim.tv_nsec;
1177 #else
1178 return 0;
1179 #endif
1180 }
1181
1182 /*
1183 * Validate a cache file by reading the header and checking
1184 * the magic number and the size field
1185 */
1186 static FcBool
1187 FcDirCacheValidateHelper (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat, struct timeval *latest_cache_mtime, void *closure FC_UNUSED)
1188 {
1189 FcBool ret = FcTrue;
1190 FcCache c;
1191
1192 if (read (fd, &c, sizeof (FcCache)) != sizeof (FcCache))
1193 ret = FcFalse;
1194 else if (c.magic != FC_CACHE_MAGIC_MMAP)
1195 ret = FcFalse;
1196 else if (c.version < FC_CACHE_VERSION_NUMBER)
1197 ret = FcFalse;
1198 else if (fd_stat->st_size != c.size)
1199 ret = FcFalse;
1200 else if (c.checksum != FcDirChecksum (dir_stat))
1201 ret = FcFalse;
1202 #ifdef HAVE_STRUCT_STAT_ST_MTIM
1203 else if (c.checksum_nano != FcDirChecksumNano (dir_stat))
1204 ret = FcFalse;
1205 #endif
1206 return ret;
1207 }
1208
1209 static FcBool
1210 FcDirCacheValidConfig (const FcChar8 *dir, FcConfig *config)
1211 {
1212 return FcDirCacheProcess (config, dir,
1213 FcDirCacheValidateHelper,
1214 NULL, NULL);
1215 }
1216
1217 FcBool
1218 FcDirCacheValid (const FcChar8 *dir)
1219 {
1220 FcConfig *config;
1221 FcBool ret;
1222
1223 config = FcConfigReference (NULL);
1224 if (!config)
1225 return FcFalse;
1226
1227 ret = FcDirCacheValidConfig (dir, config);
1228 FcConfigDestroy (config);
1229
1230 return ret;
1231 }
1232
1233 /*
1234 * Build a cache structure from the given contents
1235 */
1236 FcCache *
1237 FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcStrSet *dirs)
1238 {
1239 FcSerialize *serialize = FcSerializeCreate ();
1240 FcCache *cache;
1241 int i;
1242 FcChar8 *dir_serialize;
1243 intptr_t *dirs_serialize;
1244 FcFontSet *set_serialize;
1245
1246 if (!serialize)
1247 return NULL;
1248 /*
1249 * Space for cache structure
1250 */
1251 FcSerializeReserve (serialize, sizeof (FcCache));
1252 /*
1253 * Directory name
1254 */
1255 if (!FcStrSerializeAlloc (serialize, dir))
1256 goto bail1;
1257 /*
1258 * Subdirs
1259 */
1260 FcSerializeAlloc (serialize, dirs, dirs->num * sizeof (FcChar8 *));
1261 for (i = 0; i < dirs->num; i++)
1262 if (!FcStrSerializeAlloc (serialize, dirs->strs[i]))
1263 goto bail1;
1264
1265 /*
1266 * Patterns
1267 */
1268 if (!FcFontSetSerializeAlloc (serialize, set))
1269 goto bail1;
1270
1271 /* Serialize layout complete. Now allocate space and fill it */
1272 cache = malloc (serialize->size);
1273 if (!cache)
1274 goto bail1;
1275 /* shut up valgrind */
1276 memset (cache, 0, serialize->size);
1277
1278 serialize->linear = cache;
1279
1280 cache->magic = FC_CACHE_MAGIC_ALLOC;
1281 cache->version = FC_CACHE_VERSION_NUMBER;
1282 cache->size = serialize->size;
1283 cache->checksum = FcDirChecksum (dir_stat);
1284 cache->checksum_nano = FcDirChecksumNano (dir_stat);
1285
1286 /*
1287 * Serialize directory name
1288 */
1289 dir_serialize = FcStrSerialize (serialize, dir);
1290 if (!dir_serialize)
1291 goto bail2;
1292 cache->dir = FcPtrToOffset (cache, dir_serialize);
1293
1294 /*
1295 * Serialize sub dirs
1296 */
1297 dirs_serialize = FcSerializePtr (serialize, dirs);
1298 if (!dirs_serialize)
1299 goto bail2;
1300 cache->dirs = FcPtrToOffset (cache, dirs_serialize);
1301 cache->dirs_count = dirs->num;
1302 for (i = 0; i < dirs->num; i++)
1303 {
1304 FcChar8 *d_serialize = FcStrSerialize (serialize, dirs->strs[i]);
1305 if (!d_serialize)
1306 goto bail2;
1307 dirs_serialize[i] = FcPtrToOffset (dirs_serialize, d_serialize);
1308 }
1309
1310 /*
1311 * Serialize font set
1312 */
1313 set_serialize = FcFontSetSerialize (serialize, set);
1314 if (!set_serialize)
1315 goto bail2;
1316 cache->set = FcPtrToOffset (cache, set_serialize);
1317
1318 FcSerializeDestroy (serialize);
1319
1320 FcCacheInsert (cache, NULL);
1321
1322 return cache;
1323
1324 bail2:
1325 free (cache);
1326 bail1:
1327 FcSerializeDestroy (serialize);
1328 return NULL;
1329 }
1330
1331 FcCache *
1332 FcDirCacheRebuild (FcCache *cache, struct stat *dir_stat, FcStrSet *dirs)
1333 {
1334 FcCache *new;
1335 FcFontSet *set = FcFontSetDeserialize (FcCacheSet (cache));
1336 const FcChar8 *dir = FcCacheDir (cache);
1337
1338 new = FcDirCacheBuild (set, dir, dir_stat, dirs);
1339 FcFontSetDestroy (set);
1340
1341 return new;
1342 }
1343
1344 /* write serialized state to the cache file */
1345 FcBool
1346 FcDirCacheWrite (FcCache *cache, FcConfig *config)
1347 {
1348 FcChar8 *dir = FcCacheDir (cache);
1349 FcChar8 cache_base[CACHEBASE_LEN];
1350 FcChar8 *cache_hashed;
1351 int fd;
1352 FcAtomic *atomic;
1353 FcStrList *list;
1354 FcChar8 *cache_dir = NULL;
1355 FcChar8 *test_dir, *d = NULL;
1356 FcCacheSkip *skip;
1357 struct stat cache_stat;
1358 unsigned int magic;
1359 int written;
1360 const FcChar8 *sysroot = FcConfigGetSysRoot (config);
1361
1362 /*
1363 * Write it to the first directory in the list which is writable
1364 */
1365
1366 list = FcStrListCreate (config->cacheDirs);
1367 if (!list)
1368 return FcFalse;
1369 while ((test_dir = FcStrListNext (list)))
1370 {
1371 if (d)
1372 FcStrFree (d);
1373 if (sysroot)
1374 d = FcStrBuildFilename (sysroot, test_dir, NULL);
1375 else
1376 d = FcStrCopyFilename (test_dir);
1377
1378 if (access ((char *) d, W_OK) == 0)
1379 {
1380 cache_dir = FcStrCopyFilename (d);
1381 break;
1382 }
1383 else
1384 {
1385 /*
1386 * If the directory doesn't exist, try to create it
1387 */
1388 if (access ((char *) d, F_OK) == -1) {
1389 if (FcMakeDirectory (d))
1390 {
1391 cache_dir = FcStrCopyFilename (d);
1392 /* Create CACHEDIR.TAG */
1393 FcDirCacheCreateTagFile (d);
1394 break;
1395 }
1396 }
1397 /*
1398 * Otherwise, try making it writable
1399 */
1400 else if (chmod ((char *) d, 0755) == 0)
1401 {
1402 cache_dir = FcStrCopyFilename (d);
1403 /* Try to create CACHEDIR.TAG too */
1404 FcDirCacheCreateTagFile (d);
1405 break;
1406 }
1407 }
1408 }
1409 if (!test_dir)
1410 fprintf (stderr, "Fontconfig error: No writable cache directories\n");
1411 if (d)
1412 FcStrFree (d);
1413 FcStrListDone (list);
1414 if (!cache_dir)
1415 return FcFalse;
1416
1417 FcDirCacheBasenameMD5 (config, dir, cache_base);
1418 cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
1419 FcStrFree (cache_dir);
1420 if (!cache_hashed)
1421 return FcFalse;
1422
1423 if (FcDebug () & FC_DBG_CACHE)
1424 printf ("FcDirCacheWriteDir dir \"%s\" file \"%s\"\n",
1425 dir, cache_hashed);
1426
1427 atomic = FcAtomicCreate ((FcChar8 *)cache_hashed);
1428 if (!atomic)
1429 goto bail1;
1430
1431 if (!FcAtomicLock (atomic))
1432 goto bail3;
1433
1434 fd = FcOpen((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT | O_BINARY, 0666);
1435 if (fd == -1)
1436 goto bail4;
1437
1438 /* Temporarily switch magic to MMAP while writing to file */
1439 magic = cache->magic;
1440 if (magic != FC_CACHE_MAGIC_MMAP)
1441 cache->magic = FC_CACHE_MAGIC_MMAP;
1442
1443 /*
1444 * Write cache contents to file
1445 */
1446 written = write (fd, cache, cache->size);
1447
1448 /* Switch magic back */
1449 if (magic != FC_CACHE_MAGIC_MMAP)
1450 cache->magic = magic;
1451
1452 if (written != cache->size)
1453 {
1454 perror ("write cache");
1455 goto bail5;
1456 }
1457
1458 close(fd);
1459 if (!FcAtomicReplaceOrig(atomic))
1460 goto bail4;
1461
1462 /* If the file is small, update the cache chain entry such that the
1463 * new cache file is not read again. If it's large, we don't do that
1464 * such that we reload it, using mmap, which is shared across processes.
1465 */
1466 if (cache->size < FC_CACHE_MIN_MMAP && FcStat (cache_hashed, &cache_stat))
1467 {
1468 lock_cache ();
1469 if ((skip = FcCacheFindByAddrUnlocked (cache)))
1470 {
1471 skip->cache_dev = cache_stat.st_dev;
1472 skip->cache_ino = cache_stat.st_ino;
1473 skip->cache_mtime = cache_stat.st_mtime;
1474 #ifdef HAVE_STRUCT_STAT_ST_MTIM
1475 skip->cache_mtime_nano = cache_stat.st_mtim.tv_nsec;
1476 #else
1477 skip->cache_mtime_nano = 0;
1478 #endif
1479 }
1480 unlock_cache ();
1481 }
1482
1483 FcStrFree (cache_hashed);
1484 FcAtomicUnlock (atomic);
1485 FcAtomicDestroy (atomic);
1486 return FcTrue;
1487
1488 bail5:
1489 close (fd);
1490 bail4:
1491 FcAtomicUnlock (atomic);
1492 bail3:
1493 FcAtomicDestroy (atomic);
1494 bail1:
1495 FcStrFree (cache_hashed);
1496 return FcFalse;
1497 }
1498
1499 FcBool
1500 FcDirCacheClean (const FcChar8 *cache_dir, FcBool verbose)
1501 {
1502 DIR *d;
1503 struct dirent *ent;
1504 FcChar8 *dir;
1505 FcBool ret = FcTrue;
1506 FcBool remove;
1507 FcCache *cache;
1508 struct stat target_stat;
1509 const FcChar8 *sysroot;
1510 FcConfig *config;
1511
1512 config = FcConfigReference (NULL);
1513 if (!config)
1514 return FcFalse;
1515 /* FIXME: this API needs to support non-current FcConfig */
1516 sysroot = FcConfigGetSysRoot (config);
1517 if (sysroot)
1518 dir = FcStrBuildFilename (sysroot, cache_dir, NULL);
1519 else
1520 dir = FcStrCopyFilename (cache_dir);
1521 if (!dir)
1522 {
1523 fprintf (stderr, "Fontconfig error: %s: out of memory\n", cache_dir);
1524 ret = FcFalse;
1525 goto bail;
1526 }
1527 if (access ((char *) dir, W_OK) != 0)
1528 {
1529 if (verbose || FcDebug () & FC_DBG_CACHE)
1530 printf ("%s: not cleaning %s cache directory\n", dir,
1531 access ((char *) dir, F_OK) == 0 ? "unwritable" : "non-existent");
1532 goto bail0;
1533 }
1534 if (verbose || FcDebug () & FC_DBG_CACHE)
1535 printf ("%s: cleaning cache directory\n", dir);
1536 d = opendir ((char *) dir);
1537 if (!d)
1538 {
1539 perror ((char *) dir);
1540 ret = FcFalse;
1541 goto bail0;
1542 }
1543 while ((ent = readdir (d)))
1544 {
1545 FcChar8 *file_name;
1546 const FcChar8 *target_dir;
1547
1548 if (ent->d_name[0] == '.')
1549 continue;
1550 /* skip cache files for different architectures and */
1551 /* files which are not cache files at all */
1552 if (strlen(ent->d_name) != 32 + strlen ("-" FC_ARCHITECTURE FC_CACHE_SUFFIX) ||
1553 strcmp(ent->d_name + 32, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX))
1554 continue;
1555
1556 file_name = FcStrBuildFilename (dir, (FcChar8 *)ent->d_name, NULL);
1557 if (!file_name)
1558 {
1559 fprintf (stderr, "Fontconfig error: %s: allocation failure\n", dir);
1560 ret = FcFalse;
1561 break;
1562 }
1563 remove = FcFalse;
1564 cache = FcDirCacheLoadFile (file_name, NULL);
1565 if (!cache)
1566 {
1567 if (verbose || FcDebug () & FC_DBG_CACHE)
1568 printf ("%s: invalid cache file: %s\n", dir, ent->d_name);
1569 remove = FcTrue;
1570 }
1571 else
1572 {
1573 FcChar8 *s;
1574
1575 target_dir = FcCacheDir (cache);
1576 if (sysroot)
1577 s = FcStrBuildFilename (sysroot, target_dir, NULL);
1578 else
1579 s = FcStrdup (target_dir);
1580 if (stat ((char *) s, &target_stat) < 0)
1581 {
1582 if (verbose || FcDebug () & FC_DBG_CACHE)
1583 printf ("%s: %s: missing directory: %s \n",
1584 dir, ent->d_name, s);
1585 remove = FcTrue;
1586 }
1587 FcDirCacheUnload (cache);
1588 FcStrFree (s);
1589 }
1590 if (remove)
1591 {
1592 if (unlink ((char *) file_name) < 0)
1593 {
1594 perror ((char *) file_name);
1595 ret = FcFalse;
1596 }
1597 }
1598 FcStrFree (file_name);
1599 }
1600
1601 closedir (d);
1602 bail0:
1603 FcStrFree (dir);
1604 bail:
1605 FcConfigDestroy (config);
1606
1607 return ret;
1608 }
1609
1610 int
1611 FcDirCacheLock (const FcChar8 *dir,
1612 FcConfig *config)
1613 {
1614 FcChar8 *cache_hashed = NULL;
1615 FcChar8 cache_base[CACHEBASE_LEN];
1616 FcStrList *list;
1617 FcChar8 *cache_dir;
1618 const FcChar8 *sysroot = FcConfigGetSysRoot (config);
1619 int fd = -1;
1620
1621 FcDirCacheBasenameMD5 (config, dir, cache_base);
1622 list = FcStrListCreate (config->cacheDirs);
1623 if (!list)
1624 return -1;
1625
1626 while ((cache_dir = FcStrListNext (list)))
1627 {
1628 if (sysroot)
1629 cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
1630 else
1631 cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
1632 if (!cache_hashed)
1633 break;
1634 fd = FcOpen ((const char *)cache_hashed, O_RDWR);
1635 FcStrFree (cache_hashed);
1636 /* No caches in that directory. simply retry with another one */
1637 if (fd != -1)
1638 {
1639 #if defined(_WIN32)
1640 if (_locking (fd, _LK_LOCK, 1) == -1)
1641 goto bail;
1642 #else
1643 struct flock fl;
1644
1645 fl.l_type = F_WRLCK;
1646 fl.l_whence = SEEK_SET;
1647 fl.l_start = 0;
1648 fl.l_len = 0;
1649 fl.l_pid = getpid ();
1650 if (fcntl (fd, F_SETLKW, &fl) == -1)
1651 goto bail;
1652 #endif
1653 break;
1654 }
1655 }
1656 FcStrListDone (list);
1657 return fd;
1658 bail:
1659 FcStrListDone (list);
1660 if (fd != -1)
1661 close (fd);
1662 return -1;
1663 }
1664
1665 void
1666 FcDirCacheUnlock (int fd)
1667 {
1668 if (fd != -1)
1669 {
1670 #if defined(_WIN32)
1671 _locking (fd, _LK_UNLCK, 1);
1672 #else
1673 struct flock fl;
1674
1675 fl.l_type = F_UNLCK;
1676 fl.l_whence = SEEK_SET;
1677 fl.l_start = 0;
1678 fl.l_len = 0;
1679 fl.l_pid = getpid ();
1680 fcntl (fd, F_SETLK, &fl);
1681 #endif
1682 close (fd);
1683 }
1684 }
1685
1686 /*
1687 * Hokey little macro trick to permit the definitions of C functions
1688 * with the same name as CPP macros
1689 */
1690 #define args1(x) (x)
1691 #define args2(x,y) (x,y)
1692
1693 const FcChar8 *
1694 FcCacheDir args1(const FcCache *c)
1695 {
1696 return FcCacheDir (c);
1697 }
1698
1699 FcFontSet *
1700 FcCacheCopySet args1(const FcCache *c)
1701 {
1702 FcFontSet *old = FcCacheSet (c);
1703 FcFontSet *new = FcFontSetCreate ();
1704 int i;
1705
1706 if (!new)
1707 return NULL;
1708 for (i = 0; i < old->nfont; i++)
1709 {
1710 FcPattern *font = FcFontSetFont (old, i);
1711
1712 FcPatternReference (font);
1713 if (!FcFontSetAdd (new, font))
1714 {
1715 FcFontSetDestroy (new);
1716 return NULL;
1717 }
1718 }
1719 return new;
1720 }
1721
1722 const FcChar8 *
1723 FcCacheSubdir args2(const FcCache *c, int i)
1724 {
1725 return FcCacheSubdir (c, i);
1726 }
1727
1728 int
1729 FcCacheNumSubdir args1(const FcCache *c)
1730 {
1731 return c->dirs_count;
1732 }
1733
1734 int
1735 FcCacheNumFont args1(const FcCache *c)
1736 {
1737 return FcCacheSet(c)->nfont;
1738 }
1739
1740 FcBool
1741 FcDirCacheCreateTagFile (const FcChar8 *cache_dir)
1742 {
1743 FcChar8 *cache_tag;
1744 int fd;
1745 FILE *fp;
1746 FcAtomic *atomic;
1747 static const FcChar8 cache_tag_contents[] =
1748 "Signature: 8a477f597d28d172789f06886806bc55\n"
1749 "# This file is a cache directory tag created by fontconfig.\n"
1750 "# For information about cache directory tags, see:\n"
1751 "# http://www.brynosaurus.com/cachedir/\n";
1752 static size_t cache_tag_contents_size = sizeof (cache_tag_contents) - 1;
1753 FcBool ret = FcFalse;
1754
1755 if (!cache_dir)
1756 return FcFalse;
1757
1758 if (access ((char *) cache_dir, W_OK) == 0)
1759 {
1760 /* Create CACHEDIR.TAG */
1761 cache_tag = FcStrBuildFilename (cache_dir, "CACHEDIR.TAG", NULL);
1762 if (!cache_tag)
1763 return FcFalse;
1764 atomic = FcAtomicCreate ((FcChar8 *)cache_tag);
1765 if (!atomic)
1766 goto bail1;
1767 if (!FcAtomicLock (atomic))
1768 goto bail2;
1769 fd = FcOpen((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT, 0644);
1770 if (fd == -1)
1771 goto bail3;
1772 fp = fdopen(fd, "wb");
1773 if (fp == NULL)
1774 goto bail3;
1775
1776 fwrite(cache_tag_contents, cache_tag_contents_size, sizeof (FcChar8), fp);
1777 fclose(fp);
1778
1779 if (!FcAtomicReplaceOrig(atomic))
1780 goto bail3;
1781
1782 ret = FcTrue;
1783 bail3:
1784 FcAtomicUnlock (atomic);
1785 bail2:
1786 FcAtomicDestroy (atomic);
1787 bail1:
1788 FcStrFree (cache_tag);
1789 }
1790
1791 if (FcDebug () & FC_DBG_CACHE)
1792 {
1793 if (ret)
1794 printf ("Created CACHEDIR.TAG at %s\n", cache_dir);
1795 else
1796 printf ("Unable to create CACHEDIR.TAG at %s\n", cache_dir);
1797 }
1798
1799 return ret;
1800 }
1801
1802 void
1803 FcCacheCreateTagFile (FcConfig *config)
1804 {
1805 FcChar8 *cache_dir = NULL, *d = NULL;
1806 FcStrList *list;
1807 const FcChar8 *sysroot;
1808
1809 config = FcConfigReference (config);
1810 if (!config)
1811 return;
1812 sysroot = FcConfigGetSysRoot (config);
1813
1814 list = FcConfigGetCacheDirs (config);
1815 if (!list)
1816 goto bail;
1817
1818 while ((cache_dir = FcStrListNext (list)))
1819 {
1820 if (d)
1821 FcStrFree (d);
1822 if (sysroot)
1823 d = FcStrBuildFilename (sysroot, cache_dir, NULL);
1824 else
1825 d = FcStrCopyFilename (cache_dir);
1826 if (FcDirCacheCreateTagFile (d))
1827 break;
1828 }
1829 if (d)
1830 FcStrFree (d);
1831 FcStrListDone (list);
1832 bail:
1833 FcConfigDestroy (config);
1834 }
1835
1836 #define __fccache__
1837 #include "fcaliastail.h"
1838 #undef __fccache__