X-Git-Url: https://git.wh0rd.org/?a=blobdiff_plain;f=src%2Ffccache.c;h=11f88a788b2dc84ab5361055aac44f638924ffd7;hb=8d779ce4b3cdac796e20ca568654c0ef1c576809;hp=9f35f297820025107daa6fe4894f3dd9198221a8;hpb=c02886485b293179e8492cad9a34eb431dd4bfc9;p=fontconfig.git diff --git a/src/fccache.c b/src/fccache.c index 9f35f29..11f88a7 100644 --- a/src/fccache.c +++ b/src/fccache.c @@ -28,6 +28,7 @@ #include #include #include +#include #if defined(HAVE_MMAP) || defined(__CYGWIN__) # include # include @@ -39,9 +40,6 @@ #define O_BINARY 0 #endif -static int -FcDirCacheOpen (FcConfig *config, const FcChar8 *dir, off_t *size); - struct MD5Context { FcChar32 buf[4]; FcChar32 bits[2]; @@ -53,23 +51,6 @@ static void MD5Update(struct MD5Context *ctx, unsigned char *buf, unsigned len); static void MD5Final(unsigned char digest[16], struct MD5Context *ctx); static void MD5Transform(FcChar32 buf[4], FcChar32 in[16]); -#define FC_DBG_CACHE_REF 1024 - -/* Does not check that the cache has the appropriate arch section. */ -FcBool -FcDirCacheValid (const FcChar8 *dir, FcConfig *config) -{ - int fd; - - fd = FcDirCacheOpen (config, dir, NULL); - - if (fd < 0) - return FcFalse; - close (fd); - - return FcTrue; -} - #define CACHEBASE_LEN (1 + 32 + 1 + sizeof (FC_ARCHITECTURE) + sizeof (FC_CACHE_SUFFIX)) static const char bin2hex[] = { '0', '1', '2', '3', @@ -132,191 +113,357 @@ FcDirCacheUnlink (const FcChar8 *dir, FcConfig *config) } static int -FcCacheReadDirs (FcConfig * config, - FcStrList *list, FcFontSet * set, FcStrSet *processed_dirs) +FcDirCacheOpenFile (const FcChar8 *cache_file, struct stat *file_stat) { - int ret = 0; - FcChar8 *dir; - FcStrSet *subdirs; - FcStrList *sublist; + int fd; - /* - * Read in the results from 'list'. - */ - while ((dir = FcStrListNext (list))) + fd = open((char *) cache_file, O_RDONLY | O_BINARY); + if (fd < 0) + return fd; + if (fstat (fd, file_stat) < 0) { - if (!FcConfigAcceptFilename (config, dir)) - continue; - - /* Skip this directory if already updated - * to avoid the looped directories via symlinks - * Clearly a dir not in fonts.conf shouldn't be globally cached. - */ - - if (FcStrSetMember (processed_dirs, dir)) - continue; - if (!FcStrSetAdd (processed_dirs, dir)) - continue; - - subdirs = FcStrSetCreate (); - if (!subdirs) - { - fprintf (stderr, "Can't create directory set\n"); - ret++; - continue; - } - - FcDirScanConfig (set, subdirs, - config->blanks, dir, FcFalse, config); - - sublist = FcStrListCreate (subdirs); - FcStrSetDestroy (subdirs); - if (!sublist) - { - fprintf (stderr, "Can't create subdir list in \"%s\"\n", dir); - ret++; - continue; - } - ret += FcCacheReadDirs (config, sublist, set, processed_dirs); + close (fd); + return -1; } - FcStrListDone (list); - return ret; -} - -FcFontSet * -FcCacheRead (FcConfig *config) -{ - FcFontSet *s = FcFontSetCreate(); - FcStrSet *processed_dirs; - - if (!s) - return 0; - - processed_dirs = FcStrSetCreate(); - if (!processed_dirs) - goto bail; - - if (FcCacheReadDirs (config, FcConfigGetConfigDirs (config), s, processed_dirs)) - goto bail1; - - FcStrSetDestroy (processed_dirs); - return s; - - bail1: - FcStrSetDestroy (processed_dirs); - bail: - FcFontSetDestroy (s); - return 0; + return fd; } -/* Opens the cache file for 'dir' for reading. - * This searches the list of cache dirs for the relevant cache file, - * returning the first one found. +/* + * Look for a cache file for the specified dir. Attempt + * to use each one we find, stopping when the callback + * indicates success */ -static int -FcDirCacheOpen (FcConfig *config, const FcChar8 *dir, off_t *size) +static FcBool +FcDirCacheProcess (FcConfig *config, const FcChar8 *dir, + FcBool (*callback) (int fd, struct stat *stat, void *closure), + void *closure, FcChar8 **cache_file_ret) { int fd = -1; FcChar8 cache_base[CACHEBASE_LEN]; FcStrList *list; FcChar8 *cache_dir; struct stat file_stat, dir_stat; + FcBool ret = FcFalse; if (stat ((char *) dir, &dir_stat) < 0) - return -1; + return FcFalse; FcDirCacheBasename (dir, cache_base); list = FcStrListCreate (config->cacheDirs); if (!list) - return -1; + return FcFalse; while ((cache_dir = FcStrListNext (list))) { FcChar8 *cache_hashed = FcStrPlus (cache_dir, cache_base); if (!cache_hashed) break; - fd = open((char *) cache_hashed, O_RDONLY | O_BINARY); - FcStrFree (cache_hashed); + fd = FcDirCacheOpenFile (cache_hashed, &file_stat); if (fd >= 0) { - if (fstat (fd, &file_stat) >= 0 && - dir_stat.st_mtime <= file_stat.st_mtime) + if (dir_stat.st_mtime <= file_stat.st_mtime) { - if (size) - *size = file_stat.st_size; - break; + ret = (*callback) (fd, &file_stat, closure); + if (ret) + { + if (cache_file_ret) + *cache_file_ret = cache_hashed; + else + FcStrFree (cache_hashed); + close (fd); + break; + } } close (fd); - fd = -1; } + FcStrFree (cache_hashed); } FcStrListDone (list); - return fd; + return ret; } -void -FcDirCacheUnmap (FcCache *cache) +#define FC_CACHE_MIN_MMAP 1024 + +/* + * Skip list element, make sure the 'next' pointer is the last thing + * in the structure, it will be allocated large enough to hold all + * of the necessary pointers + */ + +typedef struct _FcCacheSkip FcCacheSkip; + +struct _FcCacheSkip { + FcCache *cache; + int ref; + intptr_t size; + dev_t cache_dev; + ino_t cache_ino; + time_t cache_mtime; + FcCacheSkip *next[1]; +}; + +/* + * The head of the skip list; pointers for every possible level + * in the skip list, plus the largest level in the list + */ + +#define FC_CACHE_MAX_LEVEL 16 + +static FcCacheSkip *fcCacheChains[FC_CACHE_MAX_LEVEL]; +static int fcCacheMaxLevel; + +/* + * Generate a random level number, distributed + * so that each level is 1/4 as likely as the one before + * + * Note that level numbers run 1 <= level <= MAX_LEVEL + */ +static int +random_level (void) { - if (cache->magic == FC_CACHE_MAGIC_COPY) + /* tricky bit -- each bit is '1' 75% of the time */ + long int bits = random () | random (); + int level = 0; + + while (++level < FC_CACHE_MAX_LEVEL) { - free (cache); - return; + if (bits & 1) + break; + bits >>= 1; + } + return level; +} + +/* + * Insert cache into the list + */ +static FcBool +FcCacheInsert (FcCache *cache, struct stat *cache_stat) +{ + FcCacheSkip **update[FC_CACHE_MAX_LEVEL]; + FcCacheSkip *s, **next; + int i, level; + + /* + * Find links along each chain + */ + next = fcCacheChains; + for (i = fcCacheMaxLevel; --i >= 0; ) + { + for (; (s = next[i]); next = s->next) + if (s->cache > cache) + break; + update[i] = &next[i]; + } + + /* + * Create new list element + */ + level = random_level (); + if (level > fcCacheMaxLevel) + { + level = fcCacheMaxLevel + 1; + update[fcCacheMaxLevel] = &fcCacheChains[fcCacheMaxLevel]; + fcCacheMaxLevel = level; } + + s = malloc (sizeof (FcCacheSkip) + (level - 1) * sizeof (FcCacheSkip *)); + if (!s) + return FcFalse; + + s->cache = cache; + s->size = cache->size; + s->ref = 1; + s->cache_dev = cache_stat->st_dev; + s->cache_ino = cache_stat->st_ino; + s->cache_mtime = cache_stat->st_mtime; + + /* + * Insert into all fcCacheChains + */ + for (i = 0; i < level; i++) + { + s->next[i] = *update[i]; + *update[i] = s; + } + return FcTrue; +} + +static FcCacheSkip * +FcCacheFindByAddr (void *object) +{ + int i; + FcCacheSkip **next = fcCacheChains; + FcCacheSkip *s; + + /* + * Walk chain pointers one level at a time + */ + for (i = fcCacheMaxLevel; --i >= 0;) + while (next[i] && (char *) object >= ((char *) next[i]->cache + next[i]->size)) + next = next[i]->next; + /* + * Here we are + */ + s = next[0]; + if (s && (char *) object < ((char *) s->cache + s->size)) + return s; + return NULL; +} + +static void +FcCacheRemove (FcCache *cache) +{ + FcCacheSkip **update[FC_CACHE_MAX_LEVEL]; + FcCacheSkip *s, **next; + int i; + + /* + * Find links along each chain + */ + next = fcCacheChains; + for (i = fcCacheMaxLevel; --i >= 0; ) + { + for (; (s = next[i]); next = s->next) + if (s->cache >= cache) + break; + update[i] = &next[i]; + } + s = next[0]; + for (i = 0; i < fcCacheMaxLevel && *update[i] == s; i++) + *update[i] = s->next[i]; + while (fcCacheMaxLevel > 0 && fcCacheChains[fcCacheMaxLevel - 1] == NULL) + fcCacheMaxLevel--; + free (s); +} + +static FcCache * +FcCacheFindByStat (struct stat *cache_stat) +{ + FcCacheSkip *s; + + for (s = fcCacheChains[0]; s; s = s->next[0]) + if (s->cache_dev == cache_stat->st_dev && + s->cache_ino == cache_stat->st_ino && + s->cache_mtime == cache_stat->st_mtime) + { + s->ref++; + return s->cache; + } + return NULL; +} + +static void +FcDirCacheDispose (FcCache *cache) +{ + switch (cache->magic) { + case FC_CACHE_MAGIC_ALLOC: + free (cache); + break; + case FC_CACHE_MAGIC_MMAP: #if defined(HAVE_MMAP) || defined(__CYGWIN__) - munmap (cache, cache->size); + munmap (cache, cache->size); #elif defined(_WIN32) - UnmapViewOfFile (cache); + UnmapViewOfFile (cache); #endif + break; + } + FcCacheRemove (cache); } -/* read serialized state from the cache file */ -FcCache * -FcDirCacheMap (int fd, off_t size) +void +FcCacheObjectReference (void *object) +{ + FcCacheSkip *skip = FcCacheFindByAddr (object); + + if (skip) + skip->ref++; +} + +void +FcCacheObjectDereference (void *object) +{ + FcCacheSkip *skip = FcCacheFindByAddr (object); + + if (skip) + { + skip->ref--; + if (skip->ref <= 0) + FcDirCacheDispose (skip->cache); + } +} + +void +FcCacheFini (void) +{ + int i; + + for (i = 0; i < FC_CACHE_MAX_LEVEL; i++) + assert (fcCacheChains[i] == NULL); + assert (fcCacheMaxLevel == 0); +} + +/* + * Map a cache file into memory + */ +static FcCache * +FcDirCacheMapFd (int fd, struct stat *fd_stat) { FcCache *cache; FcBool allocated = FcFalse; - if (size < sizeof (FcCache)) + if (fd_stat->st_size < sizeof (FcCache)) return NULL; + cache = FcCacheFindByStat (fd_stat); + if (cache) + return cache; + /* + * For small cache files, just read them into memory + */ + if (fd_stat->st_size >= FC_CACHE_MIN_MMAP) + { #if defined(HAVE_MMAP) || defined(__CYGWIN__) - cache = mmap (0, size, PROT_READ, MAP_SHARED, fd, 0); + cache = mmap (0, fd_stat->st_size, PROT_READ, MAP_SHARED, fd, 0); #elif defined(_WIN32) - { - HANDLE hFileMap; - - cache = NULL; - hFileMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL, - PAGE_READONLY, 0, 0, NULL); - if (hFileMap != NULL) { - cache = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0, size); - CloseHandle (hFileMap); + HANDLE hFileMap; + + cache = NULL; + hFileMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL, + PAGE_READONLY, 0, 0, NULL); + if (hFileMap != NULL) + { + cache = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0, size); + CloseHandle (hFileMap); + } } - } #endif + } if (!cache) { - cache = malloc (size); + cache = malloc (fd_stat->st_size); if (!cache) return NULL; - if (read (fd, cache, size) != size) + if (read (fd, cache, fd_stat->st_size) != fd_stat->st_size) { free (cache); return NULL; } allocated = FcTrue; } - if (cache->magic != FC_CACHE_MAGIC || - cache->size != size) + if (cache->magic != FC_CACHE_MAGIC_MMAP || + cache->version < FC_CACHE_CONTENT_VERSION || + cache->size != fd_stat->st_size || + !FcCacheInsert (cache, fd_stat)) { if (allocated) free (cache); else { #if defined(HAVE_MMAP) || defined(__CYGWIN__) - munmap (cache, size); + munmap (cache, fd_stat->st_size); #elif defined(_WIN32) UnmapViewOfFile (cache); #endif @@ -326,78 +473,109 @@ FcDirCacheMap (int fd, off_t size) /* Mark allocated caches so they're freed rather than unmapped */ if (allocated) - cache->magic = FC_CACHE_MAGIC_COPY; + cache->magic = FC_CACHE_MAGIC_ALLOC; return cache; } -FcBool -FcDirCacheRead (FcFontSet * set, FcStrSet * dirs, - const FcChar8 *dir, FcConfig *config) +void +FcDirCacheReference (FcCache *cache, int nref) { - int fd; - FcCache *cache; - off_t size; - int i; - FcFontSet *cache_set; - intptr_t *cache_dirs; - FcPattern **cache_fonts; + FcCacheSkip *skip = FcCacheFindByAddr (cache); - fd = FcDirCacheOpen (config, dir, &size); - if (fd < 0) - return FcFalse; + if (skip) + skip->ref += nref; +} + +void +FcDirCacheUnload (FcCache *cache) +{ + FcCacheObjectDereference (cache); +} + +static FcBool +FcDirCacheMapHelper (int fd, struct stat *fd_stat, void *closure) +{ + FcCache *cache = FcDirCacheMapFd (fd, fd_stat); - cache = FcDirCacheMap (fd, size); - if (!cache) - { - if (FcDebug() & FC_DBG_CACHE) - printf ("FcDirCacheRead failed to map cache for %s\n", dir); - close (fd); return FcFalse; - } - - cache_set = FcCacheSet (cache); - cache_fonts = FcFontSetFonts(cache_set); - if (FcDebug() & FC_DBG_CACHE) - printf ("FcDirCacheRead mapped cache for %s (%d fonts %d subdirs)\n", - dir, cache_set->nfont, cache->dirs_count); - for (i = 0; i < cache_set->nfont; i++) - { - FcPattern *font = FcEncodedOffsetToPtr (cache_set, - cache_fonts[i], - FcPattern); - if (FcDebug() & FC_DBG_CACHEV) { - printf ("Mapped font %d\n", i); - FcPatternPrint (font); - } - FcFontSetAdd (set, font); - } - - cache_dirs = FcCacheDirs (cache); - for (i = 0; i < cache->dirs_count; i++) - FcStrSetAdd (dirs, FcOffsetToPtr (cache_dirs, - cache_dirs[i], - FcChar8)); - - if (config) - FcConfigAddFontDir (config, (FcChar8 *)dir); - - close (fd); + *((FcCache **) closure) = cache; return FcTrue; } - + +FcCache * +FcDirCacheLoad (const FcChar8 *dir, FcConfig *config, FcChar8 **cache_file) +{ + FcCache *cache = NULL; + + if (!FcDirCacheProcess (config, dir, + FcDirCacheMapHelper, + &cache, cache_file)) + return NULL; + return cache; +} + +FcCache * +FcDirCacheLoadFile (const FcChar8 *cache_file, struct stat *file_stat) +{ + int fd; + FcCache *cache; + + fd = FcDirCacheOpenFile (cache_file, file_stat); + if (fd < 0) + return NULL; + cache = FcDirCacheMapFd (fd, file_stat); + close (fd); + return cache; +} + /* - * Cache file is: - * - * FcCache - * dir name - * subdirs - * FcFontSet + * Validate a cache file by reading the header and checking + * the magic number and the size field */ +static FcBool +FcDirCacheValidateHelper (int fd, struct stat *fd_stat, void *closure) +{ + FcBool ret = FcTrue; + FcCache c; + + if (read (fd, &c, sizeof (FcCache)) != sizeof (FcCache)) + ret = FcFalse; + else if (c.magic != FC_CACHE_MAGIC_MMAP) + ret = FcFalse; + else if (c.version < FC_CACHE_CONTENT_VERSION) + ret = FcFalse; + else if (fd_stat->st_size != c.size) + ret = FcFalse; + return ret; +} -static FcCache * -FcDirCacheProduce (FcFontSet *set, const FcChar8 *dir, FcStrSet *dirs) +static FcBool +FcDirCacheValidConfig (const FcChar8 *dir, FcConfig *config) +{ + return FcDirCacheProcess (config, dir, + FcDirCacheValidateHelper, + NULL, NULL); +} + +FcBool +FcDirCacheValid (const FcChar8 *dir) +{ + FcConfig *config; + + config = FcConfigGetCurrent (); + if (!config) + return FcFalse; + + return FcDirCacheValidConfig (dir, config); +} + +/* + * Build a cache structure from the given contents + */ +FcCache * +FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, FcStrSet *dirs) { FcSerialize *serialize = FcSerializeCreate (); FcCache *cache; @@ -442,7 +620,8 @@ FcDirCacheProduce (FcFontSet *set, const FcChar8 *dir, FcStrSet *dirs) serialize->linear = cache; - cache->magic = FC_CACHE_MAGIC; + cache->magic = FC_CACHE_MAGIC_ALLOC; + cache->version = FC_CACHE_CONTENT_VERSION; cache->size = serialize->size; /* @@ -512,16 +691,18 @@ FcMakeDirectory (const FcChar8 *dir) /* write serialized state to the cache file */ FcBool -FcDirCacheWrite (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, FcConfig *config) +FcDirCacheWrite (FcCache *cache, FcConfig *config) { + FcChar8 *dir = FcCacheDir (cache); FcChar8 cache_base[CACHEBASE_LEN]; FcChar8 *cache_hashed; int fd; FcAtomic *atomic; - FcCache *cache; FcStrList *list; FcChar8 *cache_dir = NULL; FcChar8 *test_dir; + int magic; + int written; /* * Write it to the first directory in the list which is writable @@ -559,18 +740,13 @@ FcDirCacheWrite (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, FcConfig *c if (!cache_hashed) return FcFalse; - cache = FcDirCacheProduce (set, dir, dirs); - - if (!cache) - goto bail1; - if (FcDebug () & FC_DBG_CACHE) printf ("FcDirCacheWriteDir dir \"%s\" file \"%s\"\n", dir, cache_hashed); atomic = FcAtomicCreate ((FcChar8 *)cache_hashed); if (!atomic) - goto bail2; + goto bail1; if (!FcAtomicLock (atomic)) goto bail3; @@ -579,7 +755,21 @@ FcDirCacheWrite (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, FcConfig *c if (fd == -1) goto bail4; - if (write (fd, cache, cache->size) != cache->size) + /* Temporarily switch magic to MMAP while writing to file */ + magic = cache->magic; + if (magic != FC_CACHE_MAGIC_MMAP) + cache->magic = FC_CACHE_MAGIC_MMAP; + + /* + * Write cache contents to file + */ + written = write (fd, cache, cache->size); + + /* Switch magic back */ + if (magic != FC_CACHE_MAGIC_MMAP) + cache->magic = magic; + + if (written != cache->size) { perror ("write cache"); goto bail5; @@ -588,7 +778,7 @@ FcDirCacheWrite (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, FcConfig *c close(fd); if (!FcAtomicReplaceOrig(atomic)) goto bail4; - FcStrFree ((FcChar8 *)cache_hashed); + FcStrFree (cache_hashed); FcAtomicUnlock (atomic); FcAtomicDestroy (atomic); return FcTrue; @@ -599,13 +789,64 @@ FcDirCacheWrite (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, FcConfig *c FcAtomicUnlock (atomic); bail3: FcAtomicDestroy (atomic); - bail2: - free (cache); bail1: - FcStrFree ((FcChar8 *)cache_hashed); + FcStrFree (cache_hashed); return FcFalse; } +/* + * Hokey little macro trick to permit the definitions of C functions + * with the same name as CPP macros + */ +#define args(x...) (x) + +const FcChar8 * +FcCacheDir args(const FcCache *c) +{ + return FcCacheDir (c); +} + +FcFontSet * +FcCacheCopySet args(const FcCache *c) +{ + FcFontSet *old = FcCacheSet (c); + FcFontSet *new = FcFontSetCreate (); + int i; + + if (!new) + return NULL; + for (i = 0; i < old->nfont; i++) + { + FcPattern *font = FcFontSetFont (old, i); + + FcPatternReference (font); + if (!FcFontSetAdd (new, font)) + { + FcFontSetDestroy (new); + return NULL; + } + } + return new; +} + +const FcChar8 * +FcCacheSubdir args(const FcCache *c, int i) +{ + return FcCacheSubdir (c, i); +} + +int +FcCacheNumSubdir args(const FcCache *c) +{ + return c->dirs_count; +} + +int +FcCacheNumFont args(const FcCache *c) +{ + return FcCacheSet(c)->nfont; +} + /* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was @@ -849,3 +1090,6 @@ static void MD5Transform(FcChar32 buf[4], FcChar32 in[16]) buf[2] += c; buf[3] += d; } +#define __fccache__ +#include "fcaliastail.h" +#undef __fccache__