Fix cache aging for fonts on FAT filesystem under Linux

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>
This commit is contained in:
Mikhail Gusarov 2012-05-28 14:52:21 +09:00 committed by Akira TAGOH
parent dc2da23e69
commit 0ac6c98294
4 changed files with 96 additions and 9 deletions

View File

@ -181,7 +181,7 @@ FcDirCacheProcess (FcConfig *config, const FcChar8 *dir,
struct stat file_stat, dir_stat; struct stat file_stat, dir_stat;
FcBool ret = FcFalse; FcBool ret = FcFalse;
if (FcStat (dir, &dir_stat) < 0) if (FcStatChecksum (dir, &dir_stat) < 0)
return FcFalse; return FcFalse;
FcDirCacheBasename (dir, cache_base); FcDirCacheBasename (dir, cache_base);
@ -508,14 +508,14 @@ FcCacheTimeValid (FcCache *cache, struct stat *dir_stat)
if (!dir_stat) if (!dir_stat)
{ {
if (FcStat (FcCacheDir (cache), &dir_static) < 0) if (FcStatChecksum (FcCacheDir (cache), &dir_static) < 0)
return FcFalse; return FcFalse;
dir_stat = &dir_static; dir_stat = &dir_static;
} }
if (FcDebug () & FC_DBG_CACHE) if (FcDebug () & FC_DBG_CACHE)
printf ("FcCacheTimeValid dir \"%s\" cache time %d dir time %d\n", printf ("FcCacheTimeValid dir \"%s\" cache checksum %d dir checksum %d\n",
FcCacheDir (cache), cache->mtime, (int) dir_stat->st_mtime); FcCacheDir (cache), cache->checksum, (int) dir_stat->st_mtime);
return cache->mtime == (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; ret = FcFalse;
else if (fd_stat->st_size != c.size) else if (fd_stat->st_size != c.size)
ret = FcFalse; ret = FcFalse;
else if (c.mtime != (int) dir_stat->st_mtime) else if (c.checksum != (int) dir_stat->st_mtime)
ret = FcFalse; ret = FcFalse;
return ret; return ret;
} }
@ -754,7 +754,7 @@ FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcSt
cache->magic = FC_CACHE_MAGIC_ALLOC; cache->magic = FC_CACHE_MAGIC_ALLOC;
cache->version = FC_CACHE_CONTENT_VERSION; cache->version = FC_CACHE_CONTENT_VERSION;
cache->size = serialize->size; cache->size = serialize->size;
cache->mtime = (int) dir_stat->st_mtime; cache->checksum = (int) dir_stat->st_mtime;
/* /*
* Serialize directory name * Serialize directory name

View File

@ -245,7 +245,7 @@ FcDirCacheScan (const FcChar8 *dir, FcConfig *config)
if (FcDebug () & FC_DBG_FONTSET) if (FcDebug () & FC_DBG_FONTSET)
printf ("cache scan dir %s\n", dir); printf ("cache scan dir %s\n", dir);
if (FcStat (dir, &dir_stat) < 0) if (FcStatChecksum (dir, &dir_stat) < 0)
goto bail; goto bail;
set = FcFontSetCreate(); set = FcFontSetCreate();

View File

@ -358,7 +358,7 @@ struct _FcCache {
intptr_t dirs; /* offset to subdirs */ intptr_t dirs; /* offset to subdirs */
int dirs_count; /* number of subdir strings */ int dirs_count; /* number of subdir strings */
intptr_t set; /* offset to font set */ intptr_t set; /* offset to font set */
int mtime; /* low bits of directory mtime */ int checksum; /* checksum of directory state */
}; };
#undef FcCacheDir #undef FcCacheDir
@ -1029,6 +1029,9 @@ FcMatrixFree (FcMatrix *mat);
FcPrivate int FcPrivate int
FcStat (const FcChar8 *file, struct stat *statb); FcStat (const FcChar8 *file, struct stat *statb);
FcPrivate int
FcStatChecksum (const FcChar8 *file, struct stat *statb);
FcPrivate FcBool FcPrivate FcBool
FcIsFsMmapSafe (int fd); FcIsFsMmapSafe (int fd);

View File

@ -130,6 +130,90 @@ FcStat (const FcChar8 *file, struct stat *statb)
#endif #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 static int
FcFStatFs (int fd, FcStatFS *statb) FcFStatFs (int fd, FcStatFS *statb)
{ {