fc-cache.c revision 2c393a42
1/*
2 * $RCSId: xc/lib/fontconfig/fc-cache/fc-cache.c,v 1.8tsi Exp $
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 Keith Packard not be used in
11 * advertising or publicity pertaining to distribution of the software without
12 * specific, written prior permission.  Keith Packard makes 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 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18 * EVENT SHALL KEITH PACKARD 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#include "../fc-arch/fcarch.h"
26
27#ifdef HAVE_CONFIG_H
28#include <config.h>
29#else
30#ifdef linux
31#define HAVE_GETOPT_LONG 1
32#endif
33#define HAVE_GETOPT 1
34#endif
35
36#include <fontconfig/fontconfig.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <unistd.h>
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <errno.h>
43#include <fcntl.h>
44#include <dirent.h>
45#include <string.h>
46
47#if defined (_WIN32)
48#define STRICT
49#include <windows.h>
50#define sleep(x) Sleep((x) * 1000)
51#undef STRICT
52#endif
53
54#ifndef O_BINARY
55#define O_BINARY 0
56#endif
57
58#ifndef HAVE_GETOPT
59#define HAVE_GETOPT 0
60#endif
61#ifndef HAVE_GETOPT_LONG
62#define HAVE_GETOPT_LONG 0
63#endif
64
65#if HAVE_GETOPT_LONG
66#undef  _GNU_SOURCE
67#define _GNU_SOURCE
68#include <getopt.h>
69const struct option longopts[] = {
70    {"force", 0, 0, 'f'},
71    {"really-force", 0, 0, 'r'},
72    {"system-only", 0, 0, 's'},
73    {"version", 0, 0, 'V'},
74    {"verbose", 0, 0, 'v'},
75    {"help", 0, 0, '?'},
76    {NULL,0,0,0},
77};
78#else
79#if HAVE_GETOPT
80extern char *optarg;
81extern int optind, opterr, optopt;
82#endif
83#endif
84
85static void
86usage (char *program)
87{
88#if HAVE_GETOPT_LONG
89    fprintf (stderr, "usage: %s [-frsvV?] [--force|--really-force] [--system-only] [--verbose] [--version] [--help] [dirs]\n",
90	     program);
91#else
92    fprintf (stderr, "usage: %s [-frsvV?] [dirs]\n",
93	     program);
94#endif
95    fprintf (stderr, "Build font information caches in [dirs]\n"
96	     "(all directories in font configuration by default).\n");
97    fprintf (stderr, "\n");
98#if HAVE_GETOPT_LONG
99    fprintf (stderr, "  -f, --force          scan directories with apparently valid caches\n");
100    fprintf (stderr, "  -r, --really-force   erase all existing caches, then rescan\n");
101    fprintf (stderr, "  -s, --system-only    scan system-wide directories only\n");
102    fprintf (stderr, "  -v, --verbose        display status information while busy\n");
103    fprintf (stderr, "  -V, --version        display font config version and exit\n");
104    fprintf (stderr, "  -?, --help           display this help and exit\n");
105#else
106    fprintf (stderr, "  -f         (force)   scan directories with apparently valid caches\n");
107    fprintf (stderr, "  -r,   (really force) erase all existing caches, then rescan\n");
108    fprintf (stderr, "  -s         (system)  scan system-wide directories only\n");
109    fprintf (stderr, "  -v         (verbose) display status information while busy\n");
110    fprintf (stderr, "  -V         (version) display font config version and exit\n");
111    fprintf (stderr, "  -?         (help)    display this help and exit\n");
112#endif
113    exit (1);
114}
115
116static FcStrSet *processed_dirs;
117
118static int
119scanDirs (FcStrList *list, FcConfig *config, FcBool force, FcBool really_force, FcBool verbose)
120{
121    int		    ret = 0;
122    const FcChar8   *dir;
123    FcStrSet	    *subdirs;
124    FcStrList	    *sublist;
125    FcCache	    *cache;
126    struct stat	    statb;
127    FcBool	    was_valid;
128    int		    i;
129
130    /*
131     * Now scan all of the directories into separate databases
132     * and write out the results
133     */
134    while ((dir = FcStrListNext (list)))
135    {
136	if (verbose)
137	{
138	    printf ("%s: ", dir);
139	    fflush (stdout);
140	}
141
142	if (!dir)
143	{
144	    if (verbose)
145		printf ("skipping, no such directory\n");
146	    continue;
147	}
148
149	if (FcStrSetMember (processed_dirs, dir))
150	{
151	    if (verbose)
152		printf ("skipping, looped directory detected\n");
153	    continue;
154	}
155
156	if (stat ((char *) dir, &statb) == -1)
157	{
158	    switch (errno) {
159	    case ENOENT:
160	    case ENOTDIR:
161		if (verbose)
162		    printf ("skipping, no such directory\n");
163		break;
164	    default:
165		fprintf (stderr, "\"%s\": ", dir);
166		perror ("");
167		ret++;
168		break;
169	    }
170	    continue;
171	}
172
173	if (!S_ISDIR (statb.st_mode))
174	{
175	    fprintf (stderr, "\"%s\": not a directory, skipping\n", dir);
176	    continue;
177	}
178
179	if (really_force)
180	    FcDirCacheUnlink (dir, config);
181
182	cache = NULL;
183	was_valid = FcFalse;
184	if (!force) {
185	    cache = FcDirCacheLoad (dir, config, NULL);
186	    if (cache)
187		was_valid = FcTrue;
188	}
189
190	if (!cache)
191	{
192	    cache = FcDirCacheRead (dir, FcTrue, config);
193	    if (!cache)
194	    {
195		fprintf (stderr, "%s: error scanning\n", dir);
196		ret++;
197		continue;
198	    }
199	}
200
201	if (was_valid)
202	{
203	    if (verbose)
204		printf ("skipping, existing cache is valid: %d fonts, %d dirs\n",
205			FcCacheNumFont (cache), FcCacheNumSubdir (cache));
206	}
207	else
208	{
209	    if (verbose)
210		printf ("caching, new cache contents: %d fonts, %d dirs\n",
211			FcCacheNumFont (cache), FcCacheNumSubdir (cache));
212
213	    if (!FcDirCacheValid (dir))
214	    {
215		fprintf (stderr, "%s: failed to write cache\n", dir);
216		(void) FcDirCacheUnlink (dir, config);
217		ret++;
218	    }
219	}
220
221	subdirs = FcStrSetCreate ();
222	if (!subdirs)
223	{
224	    fprintf (stderr, "%s: Can't create subdir set\n", dir);
225	    ret++;
226	    FcDirCacheUnload (cache);
227	    continue;
228	}
229	for (i = 0; i < FcCacheNumSubdir (cache); i++)
230	    FcStrSetAdd (subdirs, FcCacheSubdir (cache, i));
231
232	FcDirCacheUnload (cache);
233
234	sublist = FcStrListCreate (subdirs);
235	FcStrSetDestroy (subdirs);
236	if (!sublist)
237	{
238	    fprintf (stderr, "%s: Can't create subdir list\n", dir);
239	    ret++;
240	    continue;
241	}
242	FcStrSetAdd (processed_dirs, dir);
243	ret += scanDirs (sublist, config, force, really_force, verbose);
244    }
245    FcStrListDone (list);
246    return ret;
247}
248
249static FcBool
250cleanCacheDirectory (FcConfig *config, FcChar8 *dir, FcBool verbose)
251{
252    DIR		*d;
253    struct dirent *ent;
254    FcChar8	*dir_base;
255    FcBool	ret = FcTrue;
256    FcBool	remove;
257    FcCache	*cache;
258    struct stat	target_stat;
259
260    dir_base = FcStrPlus (dir, (FcChar8 *) "/");
261    if (!dir_base)
262    {
263	fprintf (stderr, "%s: out of memory\n", dir);
264	return FcFalse;
265    }
266    if (access ((char *) dir, W_OK) != 0)
267    {
268	if (verbose)
269	    printf ("%s: not cleaning %s cache directory\n", dir,
270		    access ((char *) dir, F_OK) == 0 ? "unwritable" : "non-existent");
271	FcStrFree (dir_base);
272	return FcTrue;
273    }
274    if (verbose)
275	printf ("%s: cleaning cache directory\n", dir);
276    d = opendir ((char *) dir);
277    if (!d)
278    {
279	perror ((char *) dir);
280	FcStrFree (dir_base);
281	return FcFalse;
282    }
283    while ((ent = readdir (d)))
284    {
285	FcChar8	*file_name;
286	const FcChar8	*target_dir;
287
288	if (ent->d_name[0] == '.')
289	    continue;
290	/* skip cache files for different architectures and */
291	/* files which are not cache files at all */
292	if (strlen(ent->d_name) != 32 + strlen ("-" FC_ARCHITECTURE FC_CACHE_SUFFIX) ||
293	    strcmp(ent->d_name + 32, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX))
294	    continue;
295
296	file_name = FcStrPlus (dir_base, (FcChar8 *) ent->d_name);
297	if (!file_name)
298	{
299	    fprintf (stderr, "%s: allocation failure\n", dir);
300	    ret = FcFalse;
301	    break;
302	}
303	remove = FcFalse;
304	cache = FcDirCacheLoadFile (file_name, NULL);
305	if (!cache)
306	{
307	    if (verbose)
308		printf ("%s: invalid cache file: %s\n", dir, ent->d_name);
309	    remove = FcTrue;
310	}
311	else
312	{
313	    target_dir = FcCacheDir (cache);
314	    if (stat ((char *) target_dir, &target_stat) < 0)
315	    {
316		if (verbose)
317		    printf ("%s: %s: missing directory: %s \n",
318			    dir, ent->d_name, target_dir);
319		remove = FcTrue;
320	    }
321	}
322	if (remove)
323	{
324	    if (unlink ((char *) file_name) < 0)
325	    {
326		perror ((char *) file_name);
327		ret = FcFalse;
328	    }
329	}
330	FcDirCacheUnload (cache);
331        FcStrFree (file_name);
332    }
333
334    closedir (d);
335    FcStrFree (dir_base);
336    return ret;
337}
338
339static FcBool
340cleanCacheDirectories (FcConfig *config, FcBool verbose)
341{
342    FcStrList	*cache_dirs = FcConfigGetCacheDirs (config);
343    FcChar8	*cache_dir;
344    FcBool	ret = FcTrue;
345
346    if (!cache_dirs)
347	return FcFalse;
348    while ((cache_dir = FcStrListNext (cache_dirs)))
349    {
350	if (!cleanCacheDirectory (config, cache_dir, verbose))
351	{
352	    ret = FcFalse;
353	    break;
354	}
355    }
356    FcStrListDone (cache_dirs);
357    return ret;
358}
359
360int
361main (int argc, char **argv)
362{
363    FcStrSet	*dirs;
364    FcStrList	*list;
365    FcBool    	verbose = FcFalse;
366    FcBool	force = FcFalse;
367    FcBool	really_force = FcFalse;
368    FcBool	systemOnly = FcFalse;
369    FcConfig	*config;
370    int		i;
371    int		ret;
372#if HAVE_GETOPT_LONG || HAVE_GETOPT
373    int		c;
374
375#if HAVE_GETOPT_LONG
376    while ((c = getopt_long (argc, argv, "frsVv?", longopts, NULL)) != -1)
377#else
378    while ((c = getopt (argc, argv, "frsVv?")) != -1)
379#endif
380    {
381	switch (c) {
382	case 'r':
383	    really_force = FcTrue;
384	    /* fall through */
385	case 'f':
386	    force = FcTrue;
387	    break;
388	case 's':
389	    systemOnly = FcTrue;
390	    break;
391	case 'V':
392	    fprintf (stderr, "fontconfig version %d.%d.%d\n",
393		     FC_MAJOR, FC_MINOR, FC_REVISION);
394	    exit (0);
395	case 'v':
396	    verbose = FcTrue;
397	    break;
398	default:
399	    usage (argv[0]);
400	}
401    }
402    i = optind;
403#else
404    i = 1;
405#endif
406
407    if (systemOnly)
408	FcConfigEnableHome (FcFalse);
409    config = FcInitLoadConfig ();
410    if (!config)
411    {
412	fprintf (stderr, "%s: Can't init font config library\n", argv[0]);
413	return 1;
414    }
415    FcConfigSetCurrent (config);
416
417    if (argv[i])
418    {
419	dirs = FcStrSetCreate ();
420	if (!dirs)
421	{
422	    fprintf (stderr, "%s: Can't create list of directories\n",
423		     argv[0]);
424	    return 1;
425	}
426	while (argv[i])
427	{
428	    if (!FcStrSetAddFilename (dirs, (FcChar8 *) argv[i]))
429	    {
430		fprintf (stderr, "%s: Can't add directory\n", argv[0]);
431		return 1;
432	    }
433	    i++;
434	}
435	list = FcStrListCreate (dirs);
436	FcStrSetDestroy (dirs);
437    }
438    else
439	list = FcConfigGetConfigDirs (config);
440
441    if ((processed_dirs = FcStrSetCreate()) == NULL) {
442	fprintf(stderr, "Cannot malloc\n");
443	return 1;
444    }
445
446    ret = scanDirs (list, config, force, really_force, verbose);
447
448    FcStrSetDestroy (processed_dirs);
449
450    cleanCacheDirectories (config, verbose);
451
452    /*
453     * Now we need to sleep a second  (or two, to be extra sure), to make
454     * sure that timestamps for changes after this run of fc-cache are later
455     * then any timestamps we wrote.  We don't use gettimeofday() because
456     * sleep(3) can't be interrupted by a signal here -- this isn't in the
457     * library, and there aren't any signals flying around here.
458     */
459    FcConfigDestroy (config);
460    FcFini ();
461    sleep (2);
462    if (verbose)
463	printf ("%s: %s\n", argv[0], ret ? "failed" : "succeeded");
464    return ret;
465}
466