From f5dd8512bdf9fd8e01c30ae36f593758b29385cf Mon Sep 17 00:00:00 2001 From: Akira TAGOH Date: Mon, 11 Jun 2018 17:03:17 +0900 Subject: [PATCH] Remove .uuid when no font files exists on a directory https://bugs.freedesktop.org/show_bug.cgi?id=106632 --- doc/fccache.fncs | 12 +- fontconfig/fontconfig.h | 4 + src/fccache.c | 22 +++ src/fcdir.c | 7 + src/fchash.c | 29 ++++ src/fcint.h | 4 + test/Makefile.am | 17 +++ test/run-test.sh | 15 ++ test/test-bz106632.c | 316 ++++++++++++++++++++++++++++++++++++++++ test/test-hash.c | 187 ++++++++++++++++++++++++ 10 files changed, 612 insertions(+), 1 deletion(-) create mode 100644 test/test-bz106632.c create mode 100644 test/test-hash.c diff --git a/doc/fccache.fncs b/doc/fccache.fncs index 17e74fe..972b642 100644 --- a/doc/fccache.fncs +++ b/doc/fccache.fncs @@ -92,7 +92,7 @@ to config. @FUNC@ FcDirCacheCreateUUID @TYPE1@ FcChar8 * @ARG1@ dir @TYPE2@ FcBool @ARG2@ force -@TYPE3@ FcConfig @ARG3@ config +@TYPE3@ FcConfig * @ARG3@ config @PURPOSE@ Create .uuid file at a directory @DESC@ This is to create .uuid file containing an UUID at a font directory of @@ -102,3 +102,13 @@ the cache filename if available. @SINCE@ 2.12.92 @@ +@RET@ FcBool +@FUNC@ FcDirCacheDeleteUUID +@TYPE1@ const FcChar8 * @ARG1@ dir +@TYPE2@ FcConfig * @ARG2@ config +@PURPOSE@ Delete .uuid file +@DESC@ +This is to delete .uuid file containing an UUID at a font directory of +dir. +@SINCE@ 2.13.1 +@@ diff --git a/fontconfig/fontconfig.h b/fontconfig/fontconfig.h index 5c04219..045814f 100644 --- a/fontconfig/fontconfig.h +++ b/fontconfig/fontconfig.h @@ -381,6 +381,10 @@ FcDirCacheCreateUUID (FcChar8 *dir, FcBool force, FcConfig *config); +FcPublic FcBool +FcDirCacheDeleteUUID (const FcChar8 *dir, + FcConfig *config); + /* fccfg.c */ FcPublic FcChar8 * FcConfigHome (void); diff --git a/src/fccache.c b/src/fccache.c index 667d4e7..ada91d1 100644 --- a/src/fccache.c +++ b/src/fccache.c @@ -109,6 +109,7 @@ FcDirCacheCreateUUID (FcChar8 *dir, if (!hash_add (config->uuid_table, target, uuid)) { ret = FcFalse; + FcAtomicDeleteNew (atomic); goto bail3; } uuid_unparse (uuid, out); @@ -148,6 +149,26 @@ bail1: return ret; } +FcBool +FcDirCacheDeleteUUID (const FcChar8 *dir, + FcConfig *config) +{ + const FcChar8 *sysroot = FcConfigGetSysRoot (config); + FcChar8 *target; + FcBool ret = FcTrue; + + if (sysroot) + target = FcStrBuildFilename (sysroot, dir, ".uuid", NULL); + else + target = FcStrBuildFilename (dir, ".uuid", NULL); + + ret = unlink ((char *) target) == 0; + FcStrFree (target); + FcHashTableRemove (config->uuid_table, target); + + return ret; +} + #ifndef _WIN32 static void FcDirCacheReadUUID (FcChar8 *dir, @@ -325,6 +346,7 @@ FcDirCacheUnlink (const FcChar8 *dir, FcConfig *config) if (!cache_hashed) break; (void) unlink ((char *) cache_hashed); + FcDirCacheDeleteUUID (dir, config); FcStrFree (cache_hashed); } FcStrListDone (list); diff --git a/src/fcdir.c b/src/fcdir.c index bfcdf95..93f220c 100644 --- a/src/fcdir.c +++ b/src/fcdir.c @@ -421,6 +421,13 @@ FcDirCacheRead (const FcChar8 *dir, FcBool force, FcConfig *config) /* Not using existing cache file, construct new cache */ if (!cache) cache = FcDirCacheScan (dir, config); + if (cache) + { + FcFontSet *fs = FcCacheSet (cache); + + if (cache->dirs_count == 0 && (!fs || fs->nfont == 0)) + FcDirCacheDeleteUUID (dir, config); + } return cache; } diff --git a/src/fchash.c b/src/fchash.c index 396f452..2f06f1a 100644 --- a/src/fchash.c +++ b/src/fchash.c @@ -214,3 +214,32 @@ FcHashTableReplace (FcHashTable *table, { return FcHashTableAddInternal (table, key, value, FcTrue); } + +FcBool +FcHashTableRemove (FcHashTable *table, + void *key) +{ + FcHashBucket **prev, *bucket; + FcChar32 hash = table->hash_func (key); + FcBool ret = FcFalse; + +retry: + for (prev = &table->buckets[hash % FC_HASH_SIZE]; + (bucket = fc_atomic_ptr_get (prev)); prev = &(bucket->next)) + { + if (!table->compare_func (bucket->key, key)) + { + if (!fc_atomic_ptr_cmpexch (prev, bucket, bucket->next)) + goto retry; + if (table->key_destroy_func) + table->key_destroy_func (bucket->key); + if (table->value_destroy_func) + table->value_destroy_func (bucket->value); + free (bucket); + ret = FcTrue; + break; + } + } + + return ret; +} diff --git a/src/fcint.h b/src/fcint.h index b688aa4..598b76c 100644 --- a/src/fcint.h +++ b/src/fcint.h @@ -1356,4 +1356,8 @@ FcHashTableReplace (FcHashTable *table, void *key, void *value); +FcPrivate FcBool +FcHashTableRemove (FcHashTable *table, + void *key); + #endif /* _FC_INT_H_ */ diff --git a/test/Makefile.am b/test/Makefile.am index f104cf0..f9f043c 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -74,6 +74,23 @@ endif check_PROGRAMS += test-bz106618 test_bz106618_LDADD = $(top_builddir)/src/libfontconfig.la +check_PROGRAMS += test-hash +test_hash_CFLAGS = -I$(top_builddir) -I$(top_builddir)/src $(UUID_CFLAGS) +test_hash_LDADD = $(UUID_LIBS) +TESTS += test-hash + +check_PROGRAMS += test-bz106632 +test_bz106632_CFLAGS = \ + -I$(top_builddir) \ + -I$(top_builddir)/src \ + -I$(top_srcdir) \ + -I$(top_srcdir)/src \ + -DFONTFILE='"$(abs_top_srcdir)/test/4x6.pcf"' \ + -DHAVE_CONFIG_H \ + $(NULL) +test_bz106632_LDADD = $(top_builddir)/src/libfontconfig.la +TESTS += test-bz106632 + EXTRA_DIST=run-test.sh run-test-conf.sh $(TESTDATA) out.expected-long-family-names out.expected-no-long-family-names CLEANFILES=out fonts.conf out.expected diff --git a/test/run-test.sh b/test/run-test.sh index b75b764..e932601 100644 --- a/test/run-test.sh +++ b/test/run-test.sh @@ -239,4 +239,19 @@ fi rm -rf $MyPWD/sysroot +dotest "deleting .uuid file on empty dir" +prep +cp $FONT1 $FONT2 $FONTDIR +$FCCACHE $FONTDIR +sleep 1 +rm $FONTDIR/*pcf +$FCCACHE $FONTDIR +rmdir $FONTDIR > /dev/null 2>&1 +if [ $? != 0 ]; then + echo "*** Test failed: $TEST" + echo "$FONTDIR isn't empty" + ls -al $FONTDIR + exit 1 +fi + rm -rf $FONTDIR $CACHEFILE $CACHEDIR $FONTCONFIG_FILE out diff --git a/test/test-bz106632.c b/test/test-bz106632.c new file mode 100644 index 0000000..0736b97 --- /dev/null +++ b/test/test-bz106632.c @@ -0,0 +1,316 @@ +/* + * fontconfig/test/test-bz89617.c + * + * Copyright © 2000 Keith Packard + * Copyright © 2018 Akira TAGOH + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the author(s) not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. The authors make no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#ifndef HAVE_STRUCT_DIRENT_D_TYPE +#include +#include +#include +#endif +#include "fcstr.c" +#undef FcConfigBuildFonts +#undef FcConfigCreate +#undef FcConfigGetCurrent +#undef FcConfigParseAndLoadFromMemory +#undef FcConfigUptoDate +#undef FcFontList +#undef FcInitReinitialize +#undef FcPatternCreate +#undef FcPatternDestroy +#include + +#ifdef HAVE_MKDTEMP +#define fc_mkdtemp mkdtemp +#else +char * +fc_mkdtemp (char *template) +{ + if (!mktemp (template) || mkdir (template, 0700)) + return NULL; + + return template; +} +#endif + +FcBool +mkdir_p (const char *dir) +{ + char *parent; + FcBool ret; + + if (strlen (dir) == 0) + return FcFalse; + parent = (char *) FcStrDirname ((const FcChar8 *) dir); + if (!parent) + return FcFalse; + if (access (parent, F_OK) == 0) + ret = mkdir (dir, 0755) == 0 && chmod (dir, 0755) == 0; + else if (access (parent, F_OK) == -1) + ret = mkdir_p (parent) && (mkdir (dir, 0755) == 0) && chmod (dir, 0755) == 0; + else + ret = FcFalse; + free (parent); + + return ret; +} + +FcBool +unlink_dirs (const char *dir) +{ + DIR *d = opendir (dir); + struct dirent *e; + size_t len = strlen (dir); + char *n = NULL; + FcBool ret = FcTrue; +#ifndef HAVE_STRUCT_DIRENT_D_TYPE + struct stat statb; +#endif + + if (!d) + return FcFalse; + while ((e = readdir (d)) != NULL) + { + size_t l; + + if (strcmp (e->d_name, ".") == 0 || + strcmp (e->d_name, "..") == 0) + continue; + l = strlen (e->d_name) + 1; + if (n) + free (n); + n = malloc (l + len + 1); + if (!n) + { + ret = FcFalse; + break; + } + strcpy (n, dir); + n[len] = FC_DIR_SEPARATOR; + strcpy (&n[len + 1], e->d_name); +#ifdef HAVE_STRUCT_DIRENT_D_TYPE + if (e->d_type == DT_DIR) +#else + if (stat (n, &statb) == -1) + { + fprintf (stderr, "E: %s\n", n); + ret = FcFalse; + break; + } + if (S_ISDIR (statb.st_mode)) +#endif + { + if (!unlink_dirs (n)) + { + fprintf (stderr, "E: %s\n", n); + ret = FcFalse; + break; + } + } + else + { + if (unlink (n) == -1) + { + fprintf (stderr, "E: %s\n", n); + ret = FcFalse; + break; + } + } + } + if (n) + free (n); + closedir (d); + + if (rmdir (dir) == -1) + { + fprintf (stderr, "E: %s\n", dir); + return FcFalse; + } + + return ret; +} + +FcChar8 * +FcLangNormalize (const FcChar8 *lang) +{ + return NULL; +} + +FcChar8 * +FcConfigHome (void) +{ + return NULL; +} + +int +main (void) +{ + FcChar8 *fontdir = NULL, *cachedir = NULL, *fontname; + char *basedir, template[512] = "/tmp/bz106632-XXXXXX"; + char cmd[512]; + FcConfig *config; + const FcChar8 *tconf = "\n" + " %s\n" + " %s\n" + "\n"; + char conf[1024]; + int ret = 0; + FcFontSet *fs; + FcPattern *pat; + + fprintf (stderr, "D: Creating tmp dir\n"); + basedir = fc_mkdtemp (template); + if (!basedir) + { + fprintf (stderr, "%s: %s\n", template, strerror (errno)); + goto bail; + } + fontdir = FcStrBuildFilename (basedir, "fonts", NULL); + cachedir = FcStrBuildFilename (basedir, "cache", NULL); + fprintf (stderr, "D: Creating %s\n", fontdir); + mkdir_p (fontdir); + fprintf (stderr, "D: Creating %s\n", cachedir); + mkdir_p (cachedir); + + fprintf (stderr, "D: Copying %s to %s\n", FONTFILE, fontdir); + snprintf (cmd, 512, "cp -a %s %s", FONTFILE, fontdir); + system (cmd); + + fprintf (stderr, "D: Loading a config\n"); + snprintf (conf, 1024, tconf, fontdir, cachedir); + config = FcConfigCreate (); + if (!FcConfigParseAndLoadFromMemory (config, conf, FcTrue)) + { + printf ("E: Unable to load config\n"); + ret = 1; + goto bail; + } + if (!FcConfigBuildFonts (config)) + { + printf ("E: unable to build fonts\n"); + ret = 1; + goto bail; + } + fprintf (stderr, "D: Obtaining fonts information\n"); + pat = FcPatternCreate (); + fs = FcFontList (config, pat, NULL); + FcPatternDestroy (pat); + if (!fs || fs->nfont != 1) + { + printf ("E: Unexpected the number of fonts: %d\n", !fs ? -1 : fs->nfont); + ret = 1; + goto bail; + } + fprintf (stderr, "D: Removing %s\n", fontdir); + snprintf (cmd, 512, "rm %s%s*", fontdir, FC_DIR_SEPARATOR_S); + system (cmd); + fprintf (stderr, "D: Reinitializing\n"); + if (!FcConfigUptoDate (config) || !FcInitReinitialize ()) + { + fprintf (stderr, "E: Unable to reinitialize\n"); + ret = 2; + goto bail; + } + if (FcConfigGetCurrent () == config) + { + fprintf (stderr, "E: config wasn't reloaded\n"); + ret = 3; + goto bail; + } + config = FcConfigCreate (); + if (!FcConfigParseAndLoadFromMemory (config, conf, FcTrue)) + { + printf ("E: Unable to load config again\n"); + ret = 4; + goto bail; + } + if (!FcConfigBuildFonts (config)) + { + printf ("E: unable to build fonts again\n"); + ret = 5; + goto bail; + } + fprintf (stderr, "D: Obtaining fonts information again\n"); + pat = FcPatternCreate (); + fs = FcFontList (config, pat, NULL); + FcPatternDestroy (pat); + if (!fs || fs->nfont != 0) + { + printf ("E: Unexpected the number of fonts: %d\n", !fs ? -1 : fs->nfont); + ret = 1; + goto bail; + } + fprintf (stderr, "D: Copying %s to %s\n", FONTFILE, fontdir); + snprintf (cmd, 512, "cp -a %s %s", FONTFILE, fontdir); + system (cmd); + fprintf (stderr, "D: Reinitializing\n"); + if (!FcConfigUptoDate (config) || !FcInitReinitialize ()) + { + fprintf (stderr, "E: Unable to reinitialize\n"); + ret = 2; + goto bail; + } + if (FcConfigGetCurrent () == config) + { + fprintf (stderr, "E: config wasn't reloaded\n"); + ret = 3; + goto bail; + } + config = FcConfigCreate (); + if (!FcConfigParseAndLoadFromMemory (config, conf, FcTrue)) + { + printf ("E: Unable to load config again\n"); + ret = 4; + goto bail; + } + if (!FcConfigBuildFonts (config)) + { + printf ("E: unable to build fonts again\n"); + ret = 5; + goto bail; + } + fprintf (stderr, "D: Obtaining fonts information\n"); + pat = FcPatternCreate (); + fs = FcFontList (config, pat, NULL); + FcPatternDestroy (pat); + if (!fs || fs->nfont != 1) + { + printf ("E: Unexpected the number of fonts: %d\n", !fs ? -1 : fs->nfont); + ret = 1; + goto bail; + } + +bail: + fprintf (stderr, "Cleaning up\n"); + unlink_dirs (basedir); + if (fontdir) + FcStrFree (fontdir); + if (cachedir) + FcStrFree (cachedir); + + return ret; +} diff --git a/test/test-hash.c b/test/test-hash.c new file mode 100644 index 0000000..7530e82 --- /dev/null +++ b/test/test-hash.c @@ -0,0 +1,187 @@ +#include "../src/fchash.c" +#include "../src/fcstr.c" + +FcChar8 * +FcLangNormalize (const FcChar8 *lang) +{ + return NULL; +} + +FcChar8 * +FcConfigHome (void) +{ + return NULL; +} + +typedef struct _Test +{ + FcHashTable *table; +} Test; + +static Test * +init (void) +{ + Test *ret; + + ret = malloc (sizeof (Test)); + if (ret) + { + ret->table = FcHashTableCreate ((FcHashFunc) FcStrHashIgnoreCase, + (FcCompareFunc) FcStrCmp, + FcHashStrCopy, + FcHashUuidCopy, + (FcDestroyFunc) FcStrFree, + FcHashUuidFree); + } + + return ret; +} + +static void +fini (Test *test) +{ + FcHashTableDestroy (test->table); + free (test); +} + +static FcBool +test_add (Test *test, FcChar8 *key, FcBool replace) +{ + uuid_t uuid; + void *u; + FcBool (*hash_add) (FcHashTable *, void *, void *); + FcBool ret = FcFalse; + + uuid_generate_random (uuid); + if (replace) + hash_add = FcHashTableReplace; + else + hash_add = FcHashTableAdd; + if (!hash_add (test->table, key, uuid)) + return FcFalse; + if (!FcHashTableFind (test->table, key, &u)) + return FcFalse; + ret = (uuid_compare (uuid, u) == 0); + FcHashUuidFree (u); + + return ret; +} + +static FcBool +test_remove (Test *test, FcChar8 *key) +{ + void *u; + + if (!FcHashTableFind (test->table, key, &u)) + return FcFalse; + FcHashUuidFree (u); + if (!FcHashTableRemove (test->table, key)) + return FcFalse; + if (FcHashTableFind (test->table, key, &u)) + return FcFalse; + + return FcTrue; +} + +int +main (void) +{ + Test *test; + uuid_t uuid; + int ret = 0; + + test = init (); + /* first op to add */ + if (!test_add (test, "foo", FcFalse)) + { + ret = 1; + goto bail; + } + /* second op to add */ + if (!test_add (test, "bar", FcFalse)) + { + ret = 1; + goto bail; + } + /* dup not allowed */ + if (test_add (test, "foo", FcFalse)) + { + ret = 1; + goto bail; + } + /* replacement */ + if (!test_add (test, "foo", FcTrue)) + { + ret = 1; + goto bail; + } + /* removal */ + if (!test_remove (test, "foo")) + { + ret = 1; + goto bail; + } + /* not found to remove */ + if (test_remove (test, "foo")) + { + ret = 1; + goto bail; + } + /* complex op in pointer */ + if (!test_add (test, "foo", FcFalse)) + { + ret = 1; + goto bail; + } + if (test_add (test, "foo", FcFalse)) + { + ret = 1; + goto bail; + } + if (!test_remove (test, "foo")) + { + ret = 1; + goto bail; + } + if (!test_add (test, "foo", FcFalse)) + { + ret = 1; + goto bail; + } + if (!test_remove (test, "bar")) + { + ret = 1; + goto bail; + } + /* completely remove */ + if (!test_remove (test, "foo")) + { + ret = 1; + goto bail; + } + /* completely remove from the last one */ + if (!test_add (test, "foo", FcFalse)) + { + ret = 1; + goto bail; + } + if (!test_add (test, "bar", FcFalse)) + { + ret = 1; + goto bail; + } + if (!test_remove (test, "bar")) + { + ret = 1; + goto bail; + } + if (!test_remove (test, "foo")) + { + ret = 1; + goto bail; + } +bail: + fini (test); + + return ret; +}