1 /*
2  * fontconfig/fc-cache/fc-cache.c
3  *
4  * Copyright © 2002 Keith Packard
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of the author(s) not be used in
11  * advertising or publicity pertaining to distribution of the software without
12  * specific, written prior permission.  The authors make no
13  * representations about the suitability of this software for any purpose.  It
14  * is provided "as is" without express or implied warranty.
15  *
16  * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18  * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
20  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
21  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22  * PERFORMANCE OF THIS SOFTWARE.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #else
28 #ifdef linux
29 #define HAVE_GETOPT_LONG 1
30 #endif
31 #define HAVE_GETOPT 1
32 #endif
33 
34 #include <fontconfig/fontconfig.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <dirent.h>
43 #include <string.h>
44 #include <locale.h>
45 
46 #if defined (_WIN32)
47 #define STRICT
48 #include <windows.h>
49 #define sleep(x) Sleep((x) * 1000)
50 #undef STRICT
51 #endif
52 
53 #ifdef ENABLE_NLS
54 #include <libintl.h>
55 #define _(x)		(dgettext(GETTEXT_PACKAGE, x))
56 #else
57 #define dgettext(d, s)	(s)
58 #define _(x)		(x)
59 #endif
60 
61 #ifndef O_BINARY
62 #define O_BINARY 0
63 #endif
64 
65 #ifndef HAVE_GETOPT
66 #define HAVE_GETOPT 0
67 #endif
68 #ifndef HAVE_GETOPT_LONG
69 #define HAVE_GETOPT_LONG 0
70 #endif
71 
72 #if HAVE_GETOPT_LONG
73 #undef  _GNU_SOURCE
74 #define _GNU_SOURCE
75 #include <getopt.h>
76 const struct option longopts[] = {
77     {"error-on-no-fonts", 0, 0, 'E'},
78     {"force", 0, 0, 'f'},
79     {"really-force", 0, 0, 'r'},
80     {"sysroot", required_argument, 0, 'y'},
81     {"system-only", 0, 0, 's'},
82     {"version", 0, 0, 'V'},
83     {"verbose", 0, 0, 'v'},
84     {"help", 0, 0, 'h'},
85     {NULL,0,0,0},
86 };
87 #else
88 #if HAVE_GETOPT
89 extern char *optarg;
90 extern int optind, opterr, optopt;
91 #endif
92 #endif
93 
94 static void
usage(char * program,int error)95 usage (char *program, int error)
96 {
97     FILE *file = error ? stderr : stdout;
98 #if HAVE_GETOPT_LONG
99     fprintf (file, _("usage: %s [-EfrsvVh] [-y SYSROOT] [--error-on-no-fonts] [--force|--really-force] [--sysroot=SYSROOT] [--system-only] [--verbose] [--version] [--help] [dirs]\n"),
100 	     program);
101 #else
102     fprintf (file, _("usage: %s [-EfrsvVh] [-y SYSROOT] [dirs]\n"),
103 	     program);
104 #endif
105     fprintf (file, _("Build font information caches in [dirs]\n"
106 		     "(all directories in font configuration by default).\n"));
107     fprintf (file, "\n");
108 #if HAVE_GETOPT_LONG
109     fprintf (file, _("  -E, --error-on-no-fonts  raise an error if no fonts in a directory\n"));
110     fprintf (file, _("  -f, --force              scan directories with apparently valid caches\n"));
111     fprintf (file, _("  -r, --really-force       erase all existing caches, then rescan\n"));
112     fprintf (file, _("  -s, --system-only        scan system-wide directories only\n"));
113     fprintf (file, _("  -y, --sysroot=SYSROOT    prepend SYSROOT to all paths for scanning\n"));
114     fprintf (file, _("  -v, --verbose            display status information while busy\n"));
115     fprintf (file, _("  -V, --version            display font config version and exit\n"));
116     fprintf (file, _("  -h, --help               display this help and exit\n"));
117 #else
118     fprintf (file, _("  -E         (error-on-no-fonts)\n"));
119     fprintf (file, _("                       raise an error if no fonts in a directory\n"));
120     fprintf (file, _("  -f         (force)   scan directories with apparently valid caches\n"));
121     fprintf (file, _("  -r,   (really force) erase all existing caches, then rescan\n"));
122     fprintf (file, _("  -s         (system)  scan system-wide directories only\n"));
123     fprintf (file, _("  -y SYSROOT (sysroot) prepend SYSROOT to all paths for scanning\n"));
124     fprintf (file, _("  -v         (verbose) display status information while busy\n"));
125     fprintf (file, _("  -V         (version) display font config version and exit\n"));
126     fprintf (file, _("  -h         (help)    display this help and exit\n"));
127 #endif
128     exit (error);
129 }
130 
131 static FcStrSet *processed_dirs;
132 
133 static int
scanDirs(FcStrList * list,FcConfig * config,FcBool force,FcBool really_force,FcBool verbose,FcBool error_on_no_fonts,int * changed)134 scanDirs (FcStrList *list, FcConfig *config, FcBool force, FcBool really_force, FcBool verbose, FcBool error_on_no_fonts, int *changed)
135 {
136     int		    ret = 0;
137     const FcChar8   *dir;
138     FcStrSet	    *subdirs;
139     FcStrList	    *sublist;
140     FcCache	    *cache;
141     struct stat	    statb;
142     FcBool	    was_valid, was_processed = FcFalse;
143     int		    i;
144     const FcChar8   *sysroot = FcConfigGetSysRoot (config);
145 
146     /*
147      * Now scan all of the directories into separate databases
148      * and write out the results
149      */
150     while ((dir = FcStrListNext (list)))
151     {
152 	if (verbose)
153 	{
154 	    if (sysroot)
155 		printf ("[%s]", sysroot);
156 	    printf ("%s: ", dir);
157 	    fflush (stdout);
158 	}
159 
160 	if (FcStrSetMember (processed_dirs, dir))
161 	{
162 	    if (verbose)
163 		printf (_("skipping, looped directory detected\n"));
164 	    continue;
165 	}
166 
167     FcChar8 *rooted_dir = NULL;
168     if (sysroot)
169     {
170         rooted_dir = FcStrPlus(sysroot, dir);
171     }
172     else {
173         rooted_dir = FcStrCopy(dir);
174     }
175 
176 	if (stat ((char *) rooted_dir, &statb) == -1)
177 	{
178 	    switch (errno) {
179 	    case ENOENT:
180 	    case ENOTDIR:
181 		if (verbose)
182 		    printf (_("skipping, no such directory\n"));
183 		break;
184 	    default:
185 		fprintf (stderr, "\"%s\": ", dir);
186 		perror ("");
187 		ret++;
188 		break;
189 	    }
190 	    FcStrFree (rooted_dir);
191 	    rooted_dir = NULL;
192 	    continue;
193 	}
194 
195     FcStrFree(rooted_dir);
196     rooted_dir = NULL;
197 
198 	if (!S_ISDIR (statb.st_mode))
199 	{
200 	    fprintf (stderr, _("\"%s\": not a directory, skipping\n"), dir);
201 	    continue;
202 	}
203 	was_processed = FcTrue;
204 
205 	if (really_force)
206 	{
207 	    FcDirCacheUnlink (dir, config);
208 	}
209 
210 	cache = NULL;
211 	was_valid = FcFalse;
212 	if (!force) {
213 	    cache = FcDirCacheLoad (dir, config, NULL);
214 	    if (cache)
215 		was_valid = FcTrue;
216 	}
217 
218 	if (!cache)
219 	{
220 	    (*changed)++;
221 	    cache = FcDirCacheRead (dir, FcTrue, config);
222 	    if (!cache)
223 	    {
224 		fprintf (stderr, _("\"%s\": scanning error\n"), dir);
225 		ret++;
226 		continue;
227 	    }
228 	}
229 
230 	if (was_valid)
231 	{
232 	    if (verbose)
233 		printf (_("skipping, existing cache is valid: %d fonts, %d dirs\n"),
234 			FcCacheNumFont (cache), FcCacheNumSubdir (cache));
235 	}
236 	else
237 	{
238 	    if (verbose)
239 		printf (_("caching, new cache contents: %d fonts, %d dirs\n"),
240 			FcCacheNumFont (cache), FcCacheNumSubdir (cache));
241 
242 	    if (!FcDirCacheValid (dir))
243 	    {
244 		fprintf (stderr, _("%s: failed to write cache\n"), dir);
245 		(void) FcDirCacheUnlink (dir, config);
246 		ret++;
247 	    }
248 	}
249 
250 	subdirs = FcStrSetCreate ();
251 	if (!subdirs)
252 	{
253 	    fprintf (stderr, _("%s: Can't create subdir set\n"), dir);
254 	    ret++;
255 	    FcDirCacheUnload (cache);
256 	    continue;
257 	}
258 	for (i = 0; i < FcCacheNumSubdir (cache); i++)
259 	    FcStrSetAdd (subdirs, FcCacheSubdir (cache, i));
260 
261 	FcDirCacheUnload (cache);
262 
263 	sublist = FcStrListCreate (subdirs);
264 	FcStrSetDestroy (subdirs);
265 	if (!sublist)
266 	{
267 	    fprintf (stderr, _("%s: Can't create subdir list\n"), dir);
268 	    ret++;
269 	    continue;
270 	}
271 	FcStrSetAdd (processed_dirs, dir);
272 	ret += scanDirs (sublist, config, force, really_force, verbose, error_on_no_fonts, changed);
273 	FcStrListDone (sublist);
274     }
275 
276     if (error_on_no_fonts && !was_processed)
277 	ret++;
278     return ret;
279 }
280 
281 static FcBool
cleanCacheDirectories(FcConfig * config,FcBool verbose)282 cleanCacheDirectories (FcConfig *config, FcBool verbose)
283 {
284     FcStrList	*cache_dirs = FcConfigGetCacheDirs (config);
285     FcChar8	*cache_dir;
286     FcBool	ret = FcTrue;
287 
288     if (!cache_dirs)
289 	return FcFalse;
290     while ((cache_dir = FcStrListNext (cache_dirs)))
291     {
292 	if (!FcDirCacheClean (cache_dir, verbose))
293 	{
294 	    ret = FcFalse;
295 	    break;
296 	}
297     }
298     FcStrListDone (cache_dirs);
299     return ret;
300 }
301 
302 int
main(int argc,char ** argv)303 main (int argc, char **argv)
304 {
305     FcStrSet	*dirs;
306     FcStrList	*list;
307     FcBool    	verbose = FcFalse;
308     FcBool	force = FcFalse;
309     FcBool	really_force = FcFalse;
310     FcBool	systemOnly = FcFalse;
311     FcBool	error_on_no_fonts = FcFalse;
312     FcConfig	*config;
313     FcChar8     *sysroot = NULL;
314     int		i;
315     int		changed;
316     int		ret;
317 #if HAVE_GETOPT_LONG || HAVE_GETOPT
318     int		c;
319 
320     setlocale (LC_ALL, "");
321 #if HAVE_GETOPT_LONG
322     while ((c = getopt_long (argc, argv, "Efrsy:Vvh", longopts, NULL)) != -1)
323 #else
324     while ((c = getopt (argc, argv, "Efrsy:Vvh")) != -1)
325 #endif
326     {
327 	switch (c) {
328 	case 'E':
329 	    error_on_no_fonts = FcTrue;
330 	    break;
331 	case 'r':
332 	    really_force = FcTrue;
333 	    /* fall through */
334 	case 'f':
335 	    force = FcTrue;
336 	    break;
337 	case 's':
338 	    systemOnly = FcTrue;
339 	    break;
340 	case 'y':
341 	    sysroot = FcStrCopy ((const FcChar8 *)optarg);
342 	    break;
343 	case 'V':
344 	    fprintf (stderr, "fontconfig version %d.%d.%d\n",
345 		     FC_MAJOR, FC_MINOR, FC_REVISION);
346 	    exit (0);
347 	case 'v':
348 	    verbose = FcTrue;
349 	    break;
350 	case 'h':
351 	    usage (argv[0], 0);
352 	default:
353 	    usage (argv[0], 1);
354 	}
355     }
356     i = optind;
357 #else
358     i = 1;
359 #endif
360 
361     if (systemOnly)
362 	FcConfigEnableHome (FcFalse);
363     if (sysroot)
364     {
365 	FcConfigSetSysRoot (NULL, sysroot);
366 	FcStrFree (sysroot);
367 	config = FcConfigGetCurrent();
368     }
369     else
370     {
371 	config = FcInitLoadConfig ();
372     }
373     if (!config)
374     {
375 	fprintf (stderr, _("%s: Can't initialize font config library\n"), argv[0]);
376 	return 1;
377     }
378     FcConfigSetCurrent (config);
379 
380     if (argv[i])
381     {
382 	dirs = FcStrSetCreate ();
383 	if (!dirs)
384 	{
385 	    fprintf (stderr, _("%s: Can't create list of directories\n"),
386 		     argv[0]);
387 	    return 1;
388 	}
389 	while (argv[i])
390 	{
391 	    if (!FcStrSetAddFilename (dirs, (FcChar8 *) argv[i]))
392 	    {
393 		fprintf (stderr, _("%s: Can't add directory\n"), argv[0]);
394 		return 1;
395 	    }
396 	    i++;
397 	}
398 	list = FcStrListCreate (dirs);
399 	FcStrSetDestroy (dirs);
400     }
401     else
402 	list = FcConfigGetFontDirs (config);
403 
404     if ((processed_dirs = FcStrSetCreate()) == NULL) {
405 	fprintf(stderr, _("Out of Memory\n"));
406 	return 1;
407     }
408 
409     if (verbose)
410     {
411 	const FcChar8 *dir;
412 
413 	printf ("Font directories:\n");
414 	while ((dir = FcStrListNext (list)))
415 	{
416 	    printf ("\t%s\n", dir);
417 	}
418 	FcStrListFirst(list);
419     }
420     changed = 0;
421     ret = scanDirs (list, config, force, really_force, verbose, error_on_no_fonts, &changed);
422     FcStrListDone (list);
423 
424     /*
425      * Try to create CACHEDIR.TAG anyway.
426      * This expects the fontconfig cache directory already exists.
427      * If it doesn't, it won't be simply created.
428      */
429     FcCacheCreateTagFile (config);
430 
431     FcStrSetDestroy (processed_dirs);
432 
433     cleanCacheDirectories (config, verbose);
434 
435     FcConfigDestroy (config);
436     FcFini ();
437     /*
438      * Now we need to sleep a second  (or two, to be extra sure), to make
439      * sure that timestamps for changes after this run of fc-cache are later
440      * then any timestamps we wrote.  We don't use gettimeofday() because
441      * sleep(3) can't be interrupted by a signal here -- this isn't in the
442      * library, and there aren't any signals flying around here.
443      */
444     /* the resolution of mtime on FAT is 2 seconds */
445     if (changed)
446 	sleep (2);
447     if (verbose)
448 	printf ("%s: %s\n", argv[0], ret ? _("failed") : _("succeeded"));
449     return ret;
450 }
451