]> git.wh0rd.org - fontconfig.git/blobdiff - src/fccache.c
Fix flipped return value on unlink. (Reported by Mike Fabian)
[fontconfig.git] / src / fccache.c
index 2251286cce4c88ae3db48560143aad96d2f496b0..7ccb5295013f3a946146d0af273406faca06855b 100644 (file)
@@ -1,7 +1,8 @@
 /*
- * $XFree86: $
+ * $RCSId: xc/lib/fontconfig/src/fccache.c,v 1.12 2002/08/22 07:36:44 keithp Exp $
  *
- * Copyright © 2000 Keith Packard, member of The XFree86 Project, Inc.
+ * Copyright © 2000 Keith Packard
+ * Copyright © 2005 Patrick Lam
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <unistd.h>
 #include "fcint.h"
 
-static unsigned int
-FcFileCacheHash (const char *string)
-{
-    unsigned int    h = 0;
-    char           c;
+#define ENDIAN_TEST 0x12345678
+#define MACHINE_SIGNATURE_SIZE 9 + 5*19 + 1
 
-    while ((c = *string++))
-       h = (h << 1) ^ c;
-    return h;
-}
+static off_t
+FcCacheSkipToArch (int fd, const char * arch);
 
-char *
-FcFileCacheFind (FcFileCache   *cache,
-                const char     *file,
-                int            id,
-                int            *count)
-{
-    unsigned int    hash;
-    const char     *match;
-    FcFileCacheEnt *c, *name;
-    int                    maxid;
-    struct stat            statb;
-    
-    match = file;
-    
-    hash = FcFileCacheHash (match);
-    name = 0;
-    maxid = -1;
-    for (c = cache->ents[hash % FC_FILE_CACHE_HASH_SIZE]; c; c = c->next)
-    {
-       if (c->hash == hash && !strcmp (match, c->file))
-       {
-           if (c->id > maxid)
-               maxid = c->id;
-           if (c->id == id)
-           {
-               if (stat (file, &statb) < 0)
-               {
-                   if (FcDebug () & FC_DBG_CACHE)
-                       printf (" file missing\n");
-                   return 0;
-               }
-               if (statb.st_mtime != c->time)
-               {
-                   if (FcDebug () & FC_DBG_CACHE)
-                       printf (" timestamp mismatch (was %d is %d)\n",
-                               (int) c->time, (int) statb.st_mtime);
-                   return 0;
-               }
-               if (!c->referenced)
-               {
-                   cache->referenced++;
-                   c->referenced = FcTrue;
-               }
-               name = c;
-           }
-       }
-    }
-    if (!name)
-       return 0;
-    *count = maxid + 1;
-    return name->name;
-}
+static FcBool 
+FcCacheCopyOld (int fd, int fd_orig, off_t start);
+
+static void *
+FcDirCacheProduce (FcFontSet *set, FcCache * metadata);
+
+static FcBool
+FcDirCacheConsume (int fd, FcFontSet *set);
+
+FcBool
+FcDirCacheRead (FcFontSet * set, FcStrSet * dirs, const FcChar8 *dir);
+
+static int
+FcCacheNextOffset(off_t w);
+
+static char *
+FcCacheMachineSignature (void);
 
-/*
- * Cache file syntax is quite simple:
- *
- * "file_name" id time "font_name" \n
- */
 static FcBool
-FcFileCacheReadString (FILE *f, char *dest, int len)
+FcCacheHaveBank (int bank);
+
+#define FC_DBG_CACHE_REF    1024
+
+static char *
+FcCacheReadString (int fd, char *dest, int len)
 {
-    int            c;
-    FcBool    escape;
+    FcChar8    c;
+    FcBool     escape;
+    int                size;
+    int                i;
 
-    while ((c = getc (f)) != EOF)
-       if (c == '"')
-           break;
-    if (c == EOF)
-       return FcFalse;
     if (len == 0)
-       return FcFalse;
+       return 0;
     
+    size = len;
+    i = 0;
     escape = FcFalse;
-    while ((c = getc (f)) != EOF)
+    while (read (fd, &c, 1) == 1)
     {
        if (!escape)
        {
            switch (c) {
            case '"':
-               *dest++ = '\0';
-               return FcTrue;
+               c = '\0';
+               break;
            case '\\':
                escape = FcTrue;
                continue;
            }
        }
-        if (--len <= 1)
-           return FcFalse;
-       *dest++ = c;
+       if (i == size)
+       {
+           dest[i++] = 0;
+           return dest;
+       }
+       dest[i++] = c;
+       if (c == '\0')
+           return dest;
        escape = FcFalse;
     }
-    return FcFalse;
+    return 0;
 }
 
 static FcBool
-FcFileCacheReadUlong (FILE *f, unsigned long *dest)
+FcCacheWriteString (int fd, const char *chars)
 {
-    unsigned long   t;
-    int                    c;
-
-    while ((c = getc (f)) != EOF)
-    {
-       if (!isspace (c))
-           break;
-    }
-    if (c == EOF)
+    if (write (fd, chars, strlen(chars)+1) != strlen(chars)+1)
        return FcFalse;
-    t = 0;
-    for (;;)
-    {
-       if (c == EOF || isspace (c))
-           break;
-       if (!isdigit (c))
-           return FcFalse;
-       t = t * 10 + (c - '0');
-       c = getc (f);
-    }
-    *dest = t;
     return FcTrue;
 }
 
-static FcBool
-FcFileCacheReadInt (FILE *f, int *dest)
-{
-    unsigned long   t;
-    FcBool         ret;
-
-    ret = FcFileCacheReadUlong (f, &t);
-    if (ret)
-       *dest = (int) t;
-    return ret;
-}
-
-static FcBool
-FcFileCacheReadTime (FILE *f, time_t *dest)
-{
-    unsigned long   t;
-    FcBool         ret;
-
-    ret = FcFileCacheReadUlong (f, &t);
-    if (ret)
-       *dest = (time_t) t;
-    return ret;
-}
-
-static FcBool
-FcFileCacheAdd (FcFileCache    *cache,
-                const char     *file,
-                int            id,
-                time_t         time,
-                const char     *name,
-                FcBool         replace)
+static void
+FcGlobalCacheDirDestroy (FcGlobalCacheDir *d)
 {
-    FcFileCacheEnt    *c;
-    FcFileCacheEnt    **prev, *old;
-    unsigned int    hash;
-
-    if (FcDebug () & FC_DBG_CACHE)
-    {
-       printf ("%s face %s/%d as %s\n", replace ? "Replace" : "Add",
-               file, id, name);
-    }
-    hash = FcFileCacheHash (file);
-    for (prev = &cache->ents[hash % FC_FILE_CACHE_HASH_SIZE]; 
-        (old = *prev);
-        prev = &(*prev)->next)
-    {
-       if (old->hash == hash && old->id == id && !strcmp (old->file, file))
-           break;
-    }
-    if (*prev)
-    {
-       if (!replace)
-           return FcFalse;
-
-       old = *prev;
-       if (old->referenced)
-           cache->referenced--;
-       *prev = old->next;
-       free (old);
-       cache->entries--;
-    }
-       
-    c = malloc (sizeof (FcFileCacheEnt) +
-               strlen (file) + 1 +
-               strlen (name) + 1);
-    if (!c)
-       return FcFalse;
-    c->next = *prev;
-    *prev = c;
-    c->hash = hash;
-    c->file = (char *) (c + 1);
-    c->id = id;
-    c->name = c->file + strlen (file) + 1;
-    strcpy (c->file, file);
-    c->time = time;
-    c->referenced = replace;
-    strcpy (c->name, name);
-    cache->entries++;
-    return FcTrue;
+    FcMemFree (FC_MEM_STRING, strlen (d->name)+1);
+    free (d->name);
+    FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCacheDir));
+    free (d);
 }
 
-FcFileCache *
-FcFileCacheCreate (void)
+FcGlobalCache *
+FcGlobalCacheCreate (void)
 {
-    FcFileCache        *cache;
-    int                h;
+    FcGlobalCache   *cache;
 
-    cache = malloc (sizeof (FcFileCache));
+    cache = malloc (sizeof (FcGlobalCache));
     if (!cache)
        return 0;
-    for (h = 0; h < FC_FILE_CACHE_HASH_SIZE; h++)
-       cache->ents[h] = 0;
-    cache->entries = 0;
-    cache->referenced = 0;
+    FcMemAlloc (FC_MEM_CACHE, sizeof (FcGlobalCache));
+    cache->dirs = 0;
     cache->updated = FcFalse;
+    cache->fd = -1;
     return cache;
 }
 
 void
-FcFileCacheDestroy (FcFileCache *cache)
+FcGlobalCacheDestroy (FcGlobalCache *cache)
 {
-    FcFileCacheEnt *c, *next;
-    int                    h;
+    FcGlobalCacheDir   *d, *next;
 
-    for (h = 0; h < FC_FILE_CACHE_HASH_SIZE; h++)
+    for (d = cache->dirs; d; d = next)
     {
-       for (c = cache->ents[h]; c; c = next)
-       {
-           next = c->next;
-           free (c);
-       }
+       next = d->next;
+       FcGlobalCacheDirDestroy (d);
     }
+    FcMemFree (FC_MEM_CACHE, sizeof (FcGlobalCache));
     free (cache);
 }
 
 void
-FcFileCacheLoad (FcFileCache   *cache,
-                const char     *cache_file)
+FcGlobalCacheLoad (FcGlobalCache    *cache,
+                   FcStrSet        *staleDirs,
+                  const FcChar8    *cache_file)
 {
-    FILE           *f;
-    char           file[8192];
-    int                    id;
-    time_t         time;
-    char           name[8192];
-
-    f = fopen (cache_file, "r");
-    if (!f)
+    char               name_buf[8192];
+    FcGlobalCacheDir   *d, *next;
+    char               * current_arch_machine_name;
+    char               candidate_arch_machine_name[MACHINE_SIGNATURE_SIZE + 9];
+    off_t              current_arch_start;
+
+    struct stat        cache_stat, dir_stat;
+
+    if (stat ((char *) cache_file, &cache_stat) < 0)
+        return;
+
+    cache->fd = open ((char *) cache_file, O_RDONLY);
+    if (cache->fd == -1)
        return;
 
     cache->updated = FcFalse;
-    while (FcFileCacheReadString (f, file, sizeof (file)) &&
-          FcFileCacheReadInt (f, &id) &&
-          FcFileCacheReadTime (f, &time) &&
-          FcFileCacheReadString (f, name, sizeof (name)))
+
+    current_arch_machine_name = FcCacheMachineSignature ();
+    current_arch_start = FcCacheSkipToArch(cache->fd, 
+                                          current_arch_machine_name);
+    if (current_arch_start < 0)
+        goto bail0;
+
+    lseek (cache->fd, current_arch_start, SEEK_SET);
+    FcCacheReadString (cache->fd, candidate_arch_machine_name, 
+                       sizeof (candidate_arch_machine_name));
+    if (strlen(candidate_arch_machine_name) == 0)
+       goto bail0;
+
+    while (1) 
     {
-       (void) FcFileCacheAdd (cache, file, id, time, name, FcFalse);
-    }
-    fclose (f);
-}
+       off_t targ;
 
-FcBool
-FcFileCacheUpdate (FcFileCache *cache,
-                  const char   *file,
-                  int          id,
-                  const char   *name)
-{
-    const char     *match;
-    struct stat            statb;
-    FcBool         ret;
+       FcCacheReadString (cache->fd, name_buf, sizeof (name_buf));
+       if (!strlen(name_buf))
+           break;
 
-    match = file;
+        if (stat ((char *) name_buf, &dir_stat) < 0 || 
+            dir_stat.st_mtime > cache_stat.st_mtime)
+        {
+            FcCache md;
 
-    if (stat (file, &statb) < 0)
-       return FcFalse;
-    ret = FcFileCacheAdd (cache, match, id, 
-                           statb.st_mtime, name, FcTrue);
-    if (ret)
-       cache->updated = FcTrue;
-    return ret;
+            FcStrSetAdd (staleDirs, FcStrCopy ((FcChar8 *)name_buf));
+            read (cache->fd, &md, sizeof (FcCache));
+            lseek (cache->fd, FcCacheNextOffset (lseek(cache->fd, 0, SEEK_CUR)) + md.count, SEEK_SET);
+            continue;
+        }
+
+       d = malloc (sizeof (FcGlobalCacheDir));
+       if (!d)
+           goto bail1;
+
+       d->next = cache->dirs;
+       cache->dirs = d;
+
+       d->name = (char *)FcStrCopy ((FcChar8 *)name_buf);
+       d->ent = 0;
+       d->offset = lseek (cache->fd, 0, SEEK_CUR);
+       if (read (cache->fd, &d->metadata, sizeof (FcCache)) != sizeof (FcCache))
+           goto bail1;
+       targ = FcCacheNextOffset (lseek(cache->fd, 0, SEEK_CUR)) + d->metadata.count;
+       if (lseek (cache->fd, targ, SEEK_SET) != targ)
+           goto bail1;
+    }
+    return;
+
+ bail1:
+    for (d = cache->dirs; d; d = next)
+    {
+       next = d->next;
+       free (d);
+    }
+    cache->dirs = 0;
+ bail0:
+    close (cache->fd);
+    cache->fd = -1;
+    return;
 }
 
-static FcBool
-FcFileCacheWriteString (FILE *f, char *string)
+FcBool
+FcGlobalCacheReadDir (FcFontSet *set, FcStrSet *dirs, FcGlobalCache * cache, const char *dir, FcConfig *config)
 {
-    char    c;
+    FcGlobalCacheDir *d;
+    FcBool ret = FcFalse;
 
-    if (putc ('"', f) == EOF)
+    if (cache->fd == -1)
        return FcFalse;
-    while ((c = *string++))
+
+    for (d = cache->dirs; d; d = d->next)
     {
-       switch (c) {
-       case '"':
-       case '\\':
-           if (putc ('\\', f) == EOF)
-               return FcFalse;
-           /* fall through */
-       default:
-           if (putc (c, f) == EOF)
+       if (strncmp (d->name, dir, strlen(dir)) == 0)
+       {
+           lseek (cache->fd, d->offset, SEEK_SET);
+           if (!FcDirCacheConsume (cache->fd, set))
                return FcFalse;
+            if (strcmp (d->name, dir) == 0)
+               ret = FcTrue;
        }
     }
-    if (putc ('"', f) == EOF)
-       return FcFalse;
-    return FcTrue;
+
+    return ret;
 }
 
-static FcBool
-FcFileCacheWriteUlong (FILE *f, unsigned long t)
+FcBool
+FcGlobalCacheUpdate (FcGlobalCache  *cache,
+                    const char     *name,
+                    FcFontSet      *set)
 {
-    int            pow;
-    unsigned long   temp, digit;
+    FcGlobalCacheDir * d;
+
+    if (!set->nfont)
+       return FcTrue;
 
-    temp = t;
-    pow = 1;
-    while (temp >= 10)
+    for (d = cache->dirs; d; d = d->next)
     {
-       temp /= 10;
-       pow *= 10;
+       if (strcmp(d->name, name) == 0)
+           break;
     }
-    temp = t;
-    while (pow)
+
+    if (!d)
     {
-       digit = temp / pow;
-       if (putc ((char) digit + '0', f) == EOF)
+       d = malloc (sizeof (FcGlobalCacheDir));
+       if (!d)
            return FcFalse;
-       temp = temp - pow * digit;
-       pow = pow / 10;
+       d->next = cache->dirs;
+       cache->dirs = d;
     }
-    return FcTrue;
-}
 
-static FcBool
-FcFileCacheWriteInt (FILE *f, int i)
-{
-    return FcFileCacheWriteUlong (f, (unsigned long) i);
-}
+    cache->updated = FcTrue;
 
-static FcBool
-FcFileCacheWriteTime (FILE *f, time_t t)
-{
-    return FcFileCacheWriteUlong (f, (unsigned long) t);
+    d->name = (char *)FcStrCopy ((FcChar8 *)name);
+    d->ent = FcDirCacheProduce (set, &d->metadata);
+    d->offset = 0;
+    return FcTrue;
 }
 
 FcBool
-FcFileCacheSave (FcFileCache   *cache,
-                const char     *cache_file)
+FcGlobalCacheSave (FcGlobalCache    *cache,
+                  const FcChar8    *cache_file)
 {
-    char           *lck;
-    char           *tmp;
-    FILE           *f;
-    int                    h;
-    FcFileCacheEnt *c;
+    int                        fd, fd_orig;
+    FcGlobalCacheDir   *dir;
+    FcAtomic           *atomic;
+    off_t              current_arch_start = 0, truncate_to;
+    char               * current_arch_machine_name, * header;
 
-    if (!cache->updated && cache->referenced == cache->entries)
+    if (!cache->updated)
        return FcTrue;
     
-    lck = malloc (strlen (cache_file)*2 + 4);
-    if (!lck)
-       goto bail0;
-    tmp = lck + strlen (cache_file) + 2;
-    strcpy (lck, cache_file);
-    strcat (lck, "L");
-    strcpy (tmp, cache_file);
-    strcat (tmp, "T");
-    if (link (lck, cache_file) < 0 && errno != ENOENT)
+#if defined (HAVE_GETUID) && defined (HAVE_GETEUID)
+    /* Set-UID programs can't safely update the cache */
+    if (getuid () != geteuid ())
+       return FcFalse;
+#endif
+    
+    atomic = FcAtomicCreate (cache_file);
+    if (!atomic)
+       return FcFalse;
+
+    if (!FcAtomicLock (atomic))
        goto bail1;
-    if (access (tmp, F_OK) == 0)
-       goto bail2;
-    f = fopen (tmp, "w");
-    if (!f)
+    fd = open ((char *) FcAtomicNewFile(atomic), O_RDWR | O_CREAT, 
+              S_IRUSR | S_IWUSR);
+    if (fd == -1)
        goto bail2;
 
-    for (h = 0; h < FC_FILE_CACHE_HASH_SIZE; h++)
+    fd_orig = open ((char *) FcAtomicOrigFile(atomic), O_RDONLY);
+
+    current_arch_machine_name = FcCacheMachineSignature ();
+    if (fd_orig == -1)
+        current_arch_start = 0;
+    else
+        current_arch_start = FcCacheSkipToArch (fd_orig, 
+                                                current_arch_machine_name);
+
+    if (current_arch_start < 0)
+       current_arch_start = FcCacheNextOffset (lseek(fd_orig, 0, SEEK_END));
+
+    if (!FcCacheCopyOld(fd, fd_orig, current_arch_start))
+       goto bail3;
+
+    close (fd_orig);
+    fd_orig = -1;
+
+    current_arch_start = lseek(fd, 0, SEEK_CUR);
+    if (ftruncate (fd, current_arch_start) == -1)
+       goto bail3;
+
+    header = malloc (10 + strlen (current_arch_machine_name));
+    if (!header)
+       goto bail3;
+
+    truncate_to = current_arch_start + strlen(current_arch_machine_name) + 11;
+    for (dir = cache->dirs; dir; dir = dir->next)
     {
-       for (c = cache->ents[h]; c; c = c->next)
-       {
-           if (!c->referenced)
-               continue;
-           if (!FcFileCacheWriteString (f, c->file))
-               goto bail4;
-           if (putc (' ', f) == EOF)
-               goto bail4;
-           if (!FcFileCacheWriteInt (f, c->id))
-               goto bail4;
-           if (putc (' ', f) == EOF)
-               goto bail4;
-           if (!FcFileCacheWriteTime (f, c->time))
-               goto bail4;
-           if (putc (' ', f) == EOF)
-               goto bail4;
-           if (!FcFileCacheWriteString (f, c->name))
-               goto bail4;
-           if (putc ('\n', f) == EOF)
-               goto bail4;
-       }
+       truncate_to += strlen(dir->name) + 1;
+       truncate_to += sizeof (FcCache);
+       truncate_to = FcCacheNextOffset (current_arch_start + truncate_to);
+       truncate_to += dir->metadata.count;
     }
+    truncate_to -= current_arch_start;
 
-    if (fclose (f) == EOF)
-       goto bail3;
+    sprintf (header, "%8x ", (int)truncate_to);
+    strcat (header, current_arch_machine_name);
+    if (!FcCacheWriteString (fd, header))
+       goto bail4;
+
+    for (dir = cache->dirs; dir; dir = dir->next)
+    {
+        if (dir->ent)
+        {
+            FcCacheWriteString (fd, dir->name);
+            write (fd, &dir->metadata, sizeof(FcCache));
+            lseek (fd, FcCacheNextOffset (lseek(fd, 0, SEEK_CUR)), SEEK_SET);
+            write (fd, dir->ent, dir->metadata.count);
+            free (dir->ent);
+        }
+    }
+    FcCacheWriteString (fd, "");
+
+    if (close (fd) == -1)
+       goto bail25;
     
-    if (rename (tmp, cache_file) < 0)
-       goto bail3;
+    if (!FcAtomicReplaceOrig (atomic))
+       goto bail25;
     
-    unlink (lck);
+    FcAtomicUnlock (atomic);
+    FcAtomicDestroy (atomic);
+
     cache->updated = FcFalse;
     return FcTrue;
 
-bail4:
-    fclose (f);
-bail3:
-    unlink (tmp);
-bail2:
-    unlink (lck);
-bail1:
-    free (lck);
-bail0:
+ bail4:
+    free (header);
+ bail3:
+    if (fd_orig != -1)
+        close (fd_orig);
+
+    close (fd);
+ bail25:
+    FcAtomicDeleteNew (atomic);
+ bail2:
+    FcAtomicUnlock (atomic);
+ bail1:
+    FcAtomicDestroy (atomic);
+    return FcFalse;
+}
+
+#define PAGESIZE 8192
+/* 
+ * Find the next presumably-mmapable offset after the supplied file
+ * position.
+ */
+static int
+FcCacheNextOffset(off_t w)
+{
+    if (w % PAGESIZE == 0) 
+       return w;
+    else
+       return ((w / PAGESIZE)+1)*PAGESIZE;
+}
+
+/* return the address of the segment for the provided arch,
+ * or -1 if arch not found */
+static off_t
+FcCacheSkipToArch (int fd, const char * arch)
+{
+    char candidate_arch_machine_name_count[MACHINE_SIGNATURE_SIZE + 9];
+    char * candidate_arch;
+    off_t current_arch_start = 0;
+
+    /* skip arches that are not the current arch */
+    while (1)
+    {
+       long bs;
+
+       if (lseek (fd, current_arch_start, SEEK_SET) != current_arch_start)
+            return -1;
+
+       if (FcCacheReadString (fd, candidate_arch_machine_name_count, 
+                               sizeof (candidate_arch_machine_name_count)) == 0)
+            return -1;
+       if (!strlen(candidate_arch_machine_name_count))
+           return -1;
+       bs = strtol(candidate_arch_machine_name_count, &candidate_arch, 16);
+
+       // count = 0 should probably be distinguished from the !bs condition
+       if (!bs || bs < strlen (candidate_arch_machine_name_count))
+           return -1;
+
+       candidate_arch++; /* skip leading space */
+
+       if (strcmp (candidate_arch, arch)==0)
+           return current_arch_start;
+       current_arch_start += bs;
+    }
+
+    return -1;
+}
+
+/* Cuts out the segment at the file pointer (moves everything else
+ * down to cover it), and leaves the file pointer at the end of the
+ * file. */
+static FcBool 
+FcCacheCopyOld (int fd, int fd_orig, off_t start)
+{
+    char * buf = malloc (8192);
+    char candidate_arch_machine_name[MACHINE_SIGNATURE_SIZE + 9];
+    long bs;
+    int c, bytes_skipped;
+    off_t loc;
+
+    if (!buf)
+       return FcFalse;
+
+    loc = 0;
+    lseek (fd, 0, SEEK_SET); lseek (fd_orig, 0, SEEK_SET);
+    do
+    {
+        int b = 8192;
+        if (loc + b > start)
+            b = start - loc;
+
+       if ((c = read (fd_orig, buf, b)) <= 0)
+           break;
+       if (write (fd, buf, c) < 0)
+           goto bail;
+
+        loc += c;
+    }
+    while (c > 0);
+
+    lseek (fd, start, SEEK_SET);
+    if (FcCacheReadString (fd, candidate_arch_machine_name, 
+                          sizeof (candidate_arch_machine_name)) == 0)
+       goto done;
+    if (!strlen(candidate_arch_machine_name))
+       goto done;
+
+    bs = strtol(candidate_arch_machine_name, 0, 16);
+    if (bs == 0)
+       goto done;
+
+    bytes_skipped = 0;
+    do
+    {
+       lseek (fd, start+bs+bytes_skipped, SEEK_SET);
+       if ((c = read (fd, buf, 8192)) <= 0)
+           break;
+       lseek (fd, start+bytes_skipped, SEEK_SET);
+       if (write (fd, buf, c) < 0)
+           goto bail;
+       bytes_skipped += c;
+    }
+    while (c > 0);
+    lseek (fd, start+bytes_skipped, SEEK_SET);
+
+ done:
+    free (buf);
+    return FcTrue;
+
+ bail:
+    free (buf);
     return FcFalse;
 }
 
+/* Does not check that the cache has the appropriate arch section. */
 FcBool
-FcFileCacheReadDir (FcFontSet *set, const char *cache_file)
+FcDirCacheValid (const FcChar8 *dir)
 {
-    FcPattern      *font;
-    FILE           *f;
-    char           *path;
-    char           *base;
-    char           file[8192];
-    int                    id;
-    char           name[8192];
-    FcBool         ret = FcFalse;
+    FcChar8     *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
+    struct stat file_stat, dir_stat;
 
-    if (FcDebug () & FC_DBG_CACHE)
+    if (stat ((char *) dir, &dir_stat) < 0)
     {
-       printf ("FcFileCacheReadDir cache_file \"%s\"\n", cache_file);
+        FcStrFree (cache_file);
+        return FcFalse;
     }
-    
-    f = fopen (cache_file, "r");
-    if (!f)
+    if (stat ((char *) cache_file, &file_stat) < 0)
     {
-       if (FcDebug () & FC_DBG_CACHE)
-       {
-           printf (" no cache file\n");
-       }
-       goto bail0;
+        FcStrFree (cache_file);
+        return FcFalse;
     }
 
-    base = strrchr (cache_file, '/');
-    if (!base)
-       goto bail1;
-    base++;
-    path = malloc (base - cache_file + 8192 + 1);
-    if (!path)
-       goto bail1;
-    memcpy (path, cache_file, base - cache_file);
-    base = path + (base - cache_file);
+    FcStrFree (cache_file);
+    /*
+     * If the directory has been modified more recently than
+     * the cache file, the cache is not valid
+     */
+    if (dir_stat.st_mtime - file_stat.st_mtime > 0)
+        return FcFalse;
+    return FcTrue;
+}
+
+/* Assumes that the cache file in 'dir' exists.
+ * Checks that the cache has the appropriate arch section. */
+FcBool
+FcDirCacheHasCurrentArch (const FcChar8 *dir)
+{
+    FcChar8     *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
+    int        fd;
+    off_t      current_arch_start;
+    char       *current_arch_machine_name;
+
+    current_arch_machine_name = FcCacheMachineSignature();
+    fd = open ((char *)cache_file, O_RDONLY);
+    if (fd == -1)
+        return FcFalse;
+
+    current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
+    close (fd);
+
+    if (current_arch_start < 0)
+        return FcFalse;
     
-    while (FcFileCacheReadString (f, file, sizeof (file)) &&
-          FcFileCacheReadInt (f, &id) &&
-          FcFileCacheReadString (f, name, sizeof (name)))
+    return FcTrue;
+}
+
+FcBool
+FcDirCacheUnlink (const FcChar8 *dir)
+{
+    FcChar8     *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
+
+    if (unlink ((char *)cache_file) != 0)
+    {
+       FcStrFree (cache_file);
+        return FcFalse;
+    }
+
+    FcStrFree (cache_file);
+    return FcTrue;
+}
+
+static int
+FcCacheReadDirs (FcConfig * config, FcGlobalCache * cache, 
+                FcStrList *list, FcFontSet * set)
+{
+    int                        ret = 0;
+    FcChar8            *dir;
+    FcChar8            *file, *base;
+    FcStrSet           *subdirs;
+    FcStrList          *sublist;
+    struct stat                statb;
+
+    /*
+     * Read in the results from 'list'.
+     */
+    while ((dir = FcStrListNext (list)))
     {
-       font = FcNameParse (name);
-       if (font)
+       /* freed below */
+       file = (FcChar8 *) malloc (strlen ((char *) dir) + 1 + FC_MAX_FILE_LEN + 1);
+       if (!file)
+           return FcFalse;
+
+       strcpy ((char *) file, (char *) dir);
+       strcat ((char *) file, "/");
+       base = file + strlen ((char *) file);
+
+       subdirs = FcStrSetCreate ();
+       if (!subdirs)
+       {
+           fprintf (stderr, "Can't create directory set\n");
+           ret++;
+           free (file);
+           continue;
+       }
+       
+       if (access ((char *) dir, X_OK) < 0)
        {
-           strcpy (base, file);
-           if (FcDebug () & FC_DBG_CACHEV)
-           {
-               printf (" dir cache file \"%s\"\n", file);
+           switch (errno) {
+           case ENOENT:
+           case ENOTDIR:
+           case EACCES:
+               break;
+           default:
+               fprintf (stderr, "\"%s\": ", dir);
+               perror ("");
+               ret++;
            }
-           FcPatternAddString (font, FC_FILE, path);
-           if (!FcFontSetAdd (set, font))
-               goto bail2;
+           FcStrSetDestroy (subdirs);
+           free (file);
+           continue;
        }
+       if (stat ((char *) dir, &statb) == -1)
+       {
+           fprintf (stderr, "\"%s\": ", dir);
+           perror ("");
+           FcStrSetDestroy (subdirs);
+           ret++;
+           free (file);
+           continue;
+       }
+       if (!S_ISDIR (statb.st_mode))
+       {
+           fprintf (stderr, "\"%s\": not a directory, skipping\n", dir);
+           FcStrSetDestroy (subdirs);
+           free (file);
+           continue;
+       }
+       if (!FcDirCacheValid (dir) || !FcDirCacheRead (set, subdirs, dir))
+       {
+           if (FcDebug () & FC_DBG_FONTSET)
+               printf ("cache scan dir %s\n", dir);
+
+           FcDirScanConfig (set, subdirs, cache, 
+                            config->blanks, dir, FcFalse, config);
+       }
+       sublist = FcStrListCreate (subdirs);
+       FcStrSetDestroy (subdirs);
+       if (!sublist)
+       {
+           fprintf (stderr, "Can't create subdir list in \"%s\"\n", dir);
+           ret++;
+           free (file);
+           continue;
+       }
+       ret += FcCacheReadDirs (config, cache, sublist, set);
+       free (file);
     }
-    if (FcDebug () & FC_DBG_CACHE)
-    {
-       printf (" cache loaded\n");
-    }
-    
-    ret = FcTrue;
-bail2:
-    free (path);
-bail1:
-    fclose (f);
-bail0:
+    FcStrListDone (list);
     return ret;
 }
 
+FcFontSet *
+FcCacheRead (FcConfig *config, FcGlobalCache * cache)
+{
+    FcFontSet * s = FcFontSetCreate();
+    if (!s) 
+       return 0;
+
+    if (FcCacheReadDirs (config, cache, FcConfigGetConfigDirs (config), s))
+       goto bail;
+
+    return s;
+
+ bail:
+    FcFontSetDestroy (s);
+    return 0;
+}
+
+/* read serialized state from the cache file */
 FcBool
-FcFileCacheWriteDir (FcFontSet *set, const char *cache_file)
+FcDirCacheRead (FcFontSet * set, FcStrSet * dirs, const FcChar8 *dir)
 {
-    FcPattern      *font;
-    FILE           *f;
-    char           *name;
-    char           *file, *base;
-    int                    n;
-    int                    id;
-    FcBool         ret;
+    char *cache_file = (char *)FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
+    int fd;
+    char * current_arch_machine_name;
+    char candidate_arch_machine_name[9+MACHINE_SIGNATURE_SIZE];
+    off_t current_arch_start = 0;
+    char subdirName[FC_MAX_FILE_LEN + 1 + 12 + 1];
+
+    if (!cache_file)
+        goto bail;
+
+    current_arch_machine_name = FcCacheMachineSignature();
+    fd = open(cache_file, O_RDONLY);
+    if (fd == -1)
+        goto bail;
+
+    current_arch_start = FcCacheSkipToArch(fd, current_arch_machine_name);
+    if (current_arch_start < 0)
+        goto bail1;
+
+    lseek (fd, current_arch_start, SEEK_SET);
+    if (FcCacheReadString (fd, candidate_arch_machine_name, 
+                          sizeof (candidate_arch_machine_name)) == 0)
+       goto bail1;
 
-    if (FcDebug () & FC_DBG_CACHE)
-       printf ("FcFileCacheWriteDir cache_file \"%s\"\n", cache_file);
+    while (strlen(FcCacheReadString (fd, subdirName, sizeof (subdirName))) > 0)
+        FcStrSetAdd (dirs, (FcChar8 *)subdirName);
+
+    if (!FcDirCacheConsume (fd, set))
+       goto bail1;
+       
+    close(fd);
+    free (cache_file);
+    return FcTrue;
+
+ bail1:
+    close (fd);
+ bail:
+    free (cache_file);
+    return FcFalse;
+}
+
+static FcBool
+FcDirCacheConsume (int fd, FcFontSet *set)
+{
+    FcCache metadata;
+    void * current_dir_block;
+    off_t pos;
+
+    read(fd, &metadata, sizeof(FcCache));
+    if (metadata.magic != FC_CACHE_MAGIC)
+        return FcFalse;
+
+    if (!metadata.count)
+       return FcTrue;
+
+    pos = FcCacheNextOffset (lseek(fd, 0, SEEK_CUR));
+    current_dir_block = mmap (0, metadata.count, 
+                             PROT_READ, MAP_SHARED, fd, pos);
+    if (current_dir_block == MAP_FAILED)
+       return FcFalse;
     
-    f = fopen (cache_file, "w");
-    if (!f)
-    {
-       if (FcDebug () & FC_DBG_CACHE)
-           printf (" can't create \"%s\"\n", cache_file);
+    if (!FcFontSetUnserialize (metadata, set, current_dir_block))
+       return FcFalse;
+
+    return FcTrue;
+}
+
+static void *
+FcDirCacheProduce (FcFontSet *set, FcCache *metadata)
+{
+    void * current_dir_block, * final_dir_block;
+    static unsigned int rand_state = 0;
+    int bank;
+
+    if (!rand_state) 
+       rand_state = time(0L);
+    bank = rand_r(&rand_state);
+
+    while (FcCacheHaveBank(bank))
+       bank = rand_r(&rand_state);
+
+    memset (metadata, 0, sizeof(FcCache));
+    FcFontSetNewBank();
+    metadata->count = FcFontSetNeededBytes (set);
+    metadata->magic = FC_CACHE_MAGIC;
+    metadata->bank = bank;
+
+    if (!metadata->count) /* not a failure, no fonts to write */
+       return 0;
+
+    current_dir_block = malloc (metadata->count);
+    if (!current_dir_block)
+       goto bail;
+    final_dir_block = FcFontSetDistributeBytes (metadata, current_dir_block);
+
+    if ((char *)current_dir_block + metadata->count != final_dir_block)
+       goto bail;
+                             
+    if (!FcFontSetSerialize (bank, set))
+       goto bail;
+
+    return current_dir_block;
+
+ bail:
+    free (current_dir_block);
+    return 0;
+}
+
+/* write serialized state to the cache file */
+FcBool
+FcDirCacheWrite (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir)
+{
+    FcChar8         *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE);
+    int            fd, fd_orig, i, dirs_count;
+    FcAtomic       *atomic;
+    FcCache        metadata;
+    off_t          current_arch_start = 0, truncate_to;
+
+    char            *current_arch_machine_name, * header;
+    void           *current_dir_block;
+
+    if (!cache_file)
+        goto bail;
+
+    current_dir_block = FcDirCacheProduce (set, &metadata);
+
+    if (metadata.count && !current_dir_block)
        goto bail0;
-    }
-    for (n = 0; n < set->nfont; n++)
+
+    if (FcDebug () & FC_DBG_CACHE)
+        printf ("FcDirCacheWriteDir cache_file \"%s\"\n", cache_file);
+
+    atomic = FcAtomicCreate (cache_file);
+    if (!atomic)
+        goto bail0;
+
+    if (!FcAtomicLock (atomic))
+        goto bail1;
+
+    fd_orig = open((char *)FcAtomicOrigFile (atomic), O_RDONLY, 0666);
+
+    fd = open((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT, 0666);
+    if (fd == -1)
+        goto bail2;
+
+    current_arch_machine_name = FcCacheMachineSignature ();
+    current_arch_start = 0;
+
+    if (fd_orig != -1)
+        current_arch_start = 
+            FcCacheSkipToArch(fd_orig, current_arch_machine_name);
+
+    if (current_arch_start < 0)
+       current_arch_start = FcCacheNextOffset (lseek(fd_orig, 0, SEEK_END));
+
+    if (fd_orig != -1 && !FcCacheCopyOld(fd, fd_orig, current_arch_start))
+       goto bail3;
+
+    if (fd_orig != -1)
+        close (fd_orig);
+
+    current_arch_start = lseek(fd, 0, SEEK_CUR);
+    if (ftruncate (fd, current_arch_start) == -1)
+       goto bail3;
+
+    /* allocate space for subdir names in this block */
+    dirs_count = 0;
+    for (i = 0; i < dirs->size; i++)
+        dirs_count += strlen((char *)dirs->strs[i]) + 1;
+    dirs_count ++;
+
+    /* now write the address of the next offset */
+    truncate_to = FcCacheNextOffset (FcCacheNextOffset (current_arch_start + sizeof (FcCache) + dirs_count) + metadata.count) - current_arch_start;
+    header = malloc (10 + strlen (current_arch_machine_name));
+    if (!header)
+       goto bail3;
+    sprintf (header, "%8x ", (int)truncate_to);
+    strcat (header, current_arch_machine_name);
+    if (!FcCacheWriteString (fd, header))
+       goto bail4;
+
+    for (i = 0; i < dirs->size; i++)
+        FcCacheWriteString (fd, (char *)dirs->strs[i]);
+    FcCacheWriteString (fd, "");
+
+    write (fd, &metadata, sizeof(FcCache));
+    if (metadata.count)
     {
-       font = set->fonts[n];
-       if (FcPatternGetString (font, FC_FILE, 0, &file) != FcResultMatch)
-           goto bail1;
-       base = strrchr (file, '/');
-       if (base)
-           base = base + 1;
-       else
-           base = file;
-       if (FcPatternGetInteger (font, FC_INDEX, 0, &id) != FcResultMatch)
-           goto bail1;
-       if (FcDebug () & FC_DBG_CACHEV)
-           printf (" write file \"%s\"\n", base);
-       if (!FcFileCacheWriteString (f, base))
-           goto bail1;
-       if (putc (' ', f) == EOF)
-           goto bail1;
-       if (!FcFileCacheWriteInt (f, id))
-           goto bail1;
-        if (putc (' ', f) == EOF)
-           goto bail1;
-       name = FcNameUnparse (font);
-       if (!name)
-           goto bail1;
-       ret = FcFileCacheWriteString (f, name);
-       free (name);
-       if (!ret)
-           goto bail1;
-       if (putc ('\n', f) == EOF)
-           goto bail1;
+       lseek (fd, FcCacheNextOffset (lseek(fd, 0, SEEK_END)), SEEK_SET);
+       write (fd, current_dir_block, metadata.count);
+       free (current_dir_block);
     }
-    if (fclose (f) == EOF)
-       goto bail0;
-    
-    if (FcDebug () & FC_DBG_CACHE)
-       printf (" cache written\n");
+
+    /* this actually serves to pad out the cache file, if needed */
+    if (ftruncate (fd, current_arch_start + truncate_to) == -1)
+       goto bail4;
+
+    close(fd);
+    if (!FcAtomicReplaceOrig(atomic))
+        goto bail4;
+    FcAtomicUnlock (atomic);
+    FcAtomicDestroy (atomic);
     return FcTrue;
-    
-bail1:
-    fclose (f);
-bail0:
-    unlink (cache_file);
+
+ bail4:
+    free (header);
+ bail3:
+    close (fd);
+ bail2:
+    FcAtomicUnlock (atomic);
+ bail1:
+    FcAtomicDestroy (atomic);
+ bail0:
+    unlink ((char *)cache_file);
+    free (cache_file);
+    if (current_dir_block)
+        free (current_dir_block);
+ bail:
     return FcFalse;
 }
+
+static char *
+FcCacheMachineSignature ()
+{
+    static char buf[MACHINE_SIGNATURE_SIZE];
+    int magic = ENDIAN_TEST;
+    char * m = (char *)&magic;
+
+    sprintf (buf, "%2x%2x%2x%2x "
+            "%4x %4x %4x %4x %4x %4x %4x %4x %4x %4x %4x %4x "
+            "%4x %4x %4x %4x %4x %4x %4x\n", 
+            m[0], m[1], m[2], m[3],
+            (unsigned int)sizeof (char),
+            (unsigned int)sizeof (char *),
+            (unsigned int)sizeof (int),
+            (unsigned int)sizeof (FcPattern),
+            (unsigned int)sizeof (FcPatternEltPtr),
+            (unsigned int)sizeof (struct _FcPatternElt *),
+            (unsigned int)sizeof (FcPatternElt),
+            (unsigned int)sizeof (FcObjectPtr),
+            (unsigned int)sizeof (FcValueListPtr),
+            (unsigned int)sizeof (FcValue),
+            (unsigned int)sizeof (FcValueBinding),
+            (unsigned int)sizeof (struct _FcValueList *),
+            (unsigned int)sizeof (FcCharSet),
+            (unsigned int)sizeof (FcCharLeaf **),
+            (unsigned int)sizeof (FcChar16 *),
+            (unsigned int)sizeof (FcChar16),
+            (unsigned int)sizeof (FcCharLeaf),
+            (unsigned int)sizeof (FcChar32),
+            (unsigned int)sizeof (FcCache));
+
+    return buf;
+}
+
+static int banks_ptr = 0, banks_alloc = 0;
+static int * bankId = 0, * bankIdx = 0;
+
+static FcBool
+FcCacheHaveBank (int bank)
+{
+    int i;
+
+    if (bank < FC_BANK_FIRST)
+       return FcTrue;
+
+    for (i = 0; i < banks_ptr; i++)
+       if (bankId[i] == bank)
+           return FcTrue;
+
+    return FcFalse;
+}
+
+int
+FcCacheBankToIndex (int bank)
+{
+    int i, j;
+
+    for (i = 0; i < banks_ptr; i++)
+       if (bankId[bankIdx[i]] == bank)
+       {
+           int t = bankIdx[i];
+
+           for (j = i; j > 0; j--)
+               bankIdx[j] = bankIdx[j-1];
+           bankIdx[0] = t;
+           return t;
+       }
+
+    if (banks_ptr >= banks_alloc)
+    {
+       int * b, * bidx;
+       b = realloc (bankId, (banks_alloc + 4) * sizeof(int));
+       if (!b)
+           return -1;
+       bankId = b;
+
+       bidx = realloc (bankIdx, (banks_alloc + 4) * sizeof(int));
+       if (!bidx)
+           return -1;
+       bankIdx = bidx;
+
+       banks_alloc += 4;
+    }
+
+    i = banks_ptr++;
+    bankId[i] = bank;
+    bankIdx[i] = i;
+    return i;
+}