Fix cache aging for fonts on FAT filesystem under Linux
authorMikhail Gusarov <dottedmag@dottedmag.net>
Mon, 28 May 2012 05:52:21 +0000 (14:52 +0900)
committerAkira TAGOH <akira@tagoh.org>
Mon, 28 May 2012 07:46:04 +0000 (16:46 +0900)
Windows does not update mtime of directory on FAT filesystem when
file is added to it or removed from it. Fontconfig uses mtime of
directory to check cache file aging and hence fails to detect
newly added or recently removed files.

This changeset detects FAT filesystem (currently implemented for
Linux) and adds generating checksum of directory entries instead
of using mtime which guarantees proper cache rebuild.

For non-FAT filesystems this patch adds single syscall per directory
which is negligeable overhead.

This fixes bug https://bugs.freedesktop.org/show_bug.cgi?id=25535

Signed-off-by: Mikhail Gusarov <dottedmag@dottedmag.net>
src/fccache.c
src/fcdir.c
src/fcint.h
src/fcstat.c

index fddce94..9e582b9 100644 (file)
@@ -181,7 +181,7 @@ FcDirCacheProcess (FcConfig *config, const FcChar8 *dir,
     struct stat file_stat, dir_stat;
     FcBool     ret = FcFalse;
 
-    if (FcStat (dir, &dir_stat) < 0)
+    if (FcStatChecksum (dir, &dir_stat) < 0)
         return FcFalse;
 
     FcDirCacheBasename (dir, cache_base);
@@ -508,14 +508,14 @@ FcCacheTimeValid (FcCache *cache, struct stat *dir_stat)
 
     if (!dir_stat)
     {
-       if (FcStat (FcCacheDir (cache), &dir_static) < 0)
+       if (FcStatChecksum (FcCacheDir (cache), &dir_static) < 0)
            return FcFalse;
        dir_stat = &dir_static;
     }
     if (FcDebug () & FC_DBG_CACHE)
-       printf ("FcCacheTimeValid dir \"%s\" cache time %d dir time %d\n",
-               FcCacheDir (cache), cache->mtime, (int) dir_stat->st_mtime);
-    return cache->mtime == (int) dir_stat->st_mtime;
+       printf ("FcCacheTimeValid dir \"%s\" cache checksum %d dir checksum %d\n",
+               FcCacheDir (cache), cache->checksum, (int) dir_stat->st_mtime);
+    return cache->checksum == (int) dir_stat->st_mtime;
 }
 
 /*
@@ -679,7 +679,7 @@ FcDirCacheValidateHelper (int fd, struct stat *fd_stat, struct stat *dir_stat, v
        ret = FcFalse;
     else if (fd_stat->st_size != c.size)
        ret = FcFalse;
-    else if (c.mtime != (int) dir_stat->st_mtime)
+    else if (c.checksum != (int) dir_stat->st_mtime)
        ret = FcFalse;
     return ret;
 }
@@ -754,7 +754,7 @@ FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcSt
     cache->magic = FC_CACHE_MAGIC_ALLOC;
     cache->version = FC_CACHE_CONTENT_VERSION;
     cache->size = serialize->size;
-    cache->mtime = (int) dir_stat->st_mtime;
+    cache->checksum = (int) dir_stat->st_mtime;
 
     /*
      * Serialize directory name
index 4399afc..2b476e8 100644 (file)
@@ -245,7 +245,7 @@ FcDirCacheScan (const FcChar8 *dir, FcConfig *config)
     if (FcDebug () & FC_DBG_FONTSET)
        printf ("cache scan dir %s\n", dir);
 
-    if (FcStat (dir, &dir_stat) < 0)
+    if (FcStatChecksum (dir, &dir_stat) < 0)
        goto bail;
 
     set = FcFontSetCreate();
index 3d0e7bb..3d06fc6 100644 (file)
@@ -358,7 +358,7 @@ struct _FcCache {
     intptr_t   dirs;               /* offset to subdirs */
     int                dirs_count;         /* number of subdir strings */
     intptr_t   set;                /* offset to font set */
-    int                mtime;              /* low bits of directory mtime */
+    int                checksum;           /* checksum of directory state */
 };
 
 #undef FcCacheDir
@@ -1029,6 +1029,9 @@ FcMatrixFree (FcMatrix *mat);
 FcPrivate int
 FcStat (const FcChar8 *file, struct stat *statb);
 
+FcPrivate int
+FcStatChecksum (const FcChar8 *file, struct stat *statb);
+
 FcPrivate FcBool
 FcIsFsMmapSafe (int fd);
 
index 013e275..c2d9fe9 100644 (file)
@@ -130,6 +130,90 @@ FcStat (const FcChar8 *file, struct stat *statb)
 
 #endif
 
+/* Adler-32 checksum implementation */
+struct Adler32 {
+    int a;
+    int b;
+};
+
+static void
+Adler32Init (struct Adler32 *ctx)
+{
+    ctx->a = 1;
+    ctx->b = 0;
+}
+
+static void
+Adler32Update (struct Adler32 *ctx, const char *data, int data_len)
+{
+    while (data_len--)
+    {
+       ctx->a = (ctx->a + *data++) % 65521;
+       ctx->b = (ctx->b + ctx->a) % 65521;
+    }
+}
+
+static int
+Adler32Finish (struct Adler32 *ctx)
+{
+    return ctx->a + (ctx->b << 16);
+}
+
+/* dirent.d_type can be relied upon on FAT filesystem */
+static FcBool
+FcDirChecksumScandirFilter(const struct dirent *entry)
+{
+    return entry->d_type != DT_DIR;
+}
+
+static int
+FcDirChecksumScandirSorter(const struct dirent **lhs, const struct dirent **rhs)
+{
+    return strcmp((*lhs)->d_name, (*rhs)->d_name);
+}
+
+static int
+FcDirChecksum (const FcChar8 *dir, time_t *checksum)
+{
+    struct Adler32 ctx;
+    struct dirent **files;
+    int n;
+
+    Adler32Init (&ctx);
+
+    n = scandir ((const char *)dir, &files,
+                 &FcDirChecksumScandirFilter,
+                 &FcDirChecksumScandirSorter);
+    if (n == -1)
+        return -1;
+
+    while (n--)
+    {
+        Adler32Update (&ctx, files[n]->d_name, strlen(files[n]->d_name) + 1);
+        Adler32Update (&ctx, (char *)&files[n]->d_type, sizeof(files[n]->d_type));
+        free(files[n]);
+    }
+    free(files);
+
+    *checksum = Adler32Finish (&ctx);
+    return 0;
+}
+
+int
+FcStatChecksum (const FcChar8 *file, struct stat *statb)
+{
+    if (FcStat (file, statb) == -1)
+        return -1;
+
+    if (FcIsFsMtimeBroken (file))
+    {
+        if (FcDirChecksum (file, &statb->st_mtime) == -1)
+            return -1;
+    }
+
+    return 0;
+}
+
 static int
 FcFStatFs (int fd, FcStatFS *statb)
 {