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