1/* updateiconcache.c
2 * Copyright (C) 2004 Anders Carlsson <andersca@gnome.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include <locale.h>
21#include <stdlib.h>
22#include <stdio.h>
23#include <string.h>
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <fcntl.h>
27#ifdef HAVE_UNISTD_H
28#include <unistd.h>
29#endif
30#include <errno.h>
31#ifdef _MSC_VER
32#include <io.h>
33#include <sys/utime.h>
34#else
35#include <utime.h>
36#endif
37
38#include <glib.h>
39#include <glib/gstdio.h>
40#include <gdk-pixbuf/gdk-pixdata.h>
41#include <glib/gi18n.h>
42#include "gtkiconcachevalidator.h"
43
44static gboolean force_update = FALSE;
45static gboolean ignore_theme_index = FALSE;
46static gboolean quiet = FALSE;
47static gboolean index_only = TRUE;
48static gboolean validate = FALSE;
49static gchar *var_name = "-";
50
51/* Quite ugly - if we just add the c file to the
52 * list of sources in Makefile.am, libtool complains.
53 */
54#include "gtkiconcachevalidator.c"
55
56#define CACHE_NAME "icon-theme.cache"
57
58#define HAS_SUFFIX_XPM (1 << 0)
59#define HAS_SUFFIX_SVG (1 << 1)
60#define HAS_SUFFIX_PNG (1 << 2)
61#define HAS_ICON_FILE (1 << 3)
62
63#define MAJOR_VERSION 1
64#define MINOR_VERSION 0
65#define HASH_OFFSET 12
66
67#define ALIGN_VALUE(this, boundary) \
68 (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
69
70#ifdef HAVE_FTW_H
71
72#include <ftw.h>
73
74static GStatBuf cache_dir_stat;
75static gboolean cache_up_to_date;
76
77static int check_dir_mtime (const char *dir,
78 const GStatBuf *sb,
79 int tf)
80{
81 if (tf != FTW_NS && sb->st_mtime > cache_dir_stat.st_mtime)
82 {
83 cache_up_to_date = FALSE;
84 /* stop tree walk */
85 return 1;
86 }
87
88 return 0;
89}
90
91static gboolean
92is_cache_up_to_date (const gchar *path)
93{
94 gchar *cache_path;
95 gint retval;
96
97 cache_path = g_build_filename (path, CACHE_NAME, NULL);
98 retval = g_stat (cache_path, &cache_dir_stat);
99 g_free (cache_path);
100
101 if (retval < 0)
102 {
103 /* Cache file not found */
104 return FALSE;
105 }
106
107 cache_up_to_date = TRUE;
108
109 ftw (path, check_dir_mtime, 20);
110
111 return cache_up_to_date;
112}
113
114#else /* !HAVE_FTW_H */
115
116gboolean
117is_cache_up_to_date (const gchar *path)
118{
119 GStatBuf path_stat, cache_stat;
120 gchar *cache_path;
121 int retval;
122
123 retval = g_stat (path, &path_stat);
124
125 if (retval < 0)
126 {
127 /* We can't stat the path,
128 * assume we have a updated cache */
129 return TRUE;
130 }
131
132 cache_path = g_build_filename (path, CACHE_NAME, NULL);
133 retval = g_stat (cache_path, &cache_stat);
134 g_free (cache_path);
135
136 if (retval < 0)
137 {
138 /* Cache file not found */
139 return FALSE;
140 }
141
142 /* Check mtime */
143 return cache_stat.st_mtime >= path_stat.st_mtime;
144}
145
146#endif /* !HAVE_FTW_H */
147
148static gboolean
149has_theme_index (const gchar *path)
150{
151 gboolean result;
152 gchar *index_path;
153
154 index_path = g_build_filename (path, "index.theme", NULL);
155
156 result = g_file_test (index_path, G_FILE_TEST_IS_REGULAR);
157
158 g_free (index_path);
159
160 return result;
161}
162
163
164typedef struct
165{
166 GdkPixdata pixdata;
167 gboolean has_pixdata;
168 guint32 offset;
169 guint size;
170} ImageData;
171
172typedef struct
173{
174 int has_embedded_rect;
175 int x0, y0, x1, y1;
176
177 int n_attach_points;
178 int *attach_points;
179
180 int n_display_names;
181 char **display_names;
182
183 guint32 offset;
184 gint size;
185} IconData;
186
187static GHashTable *image_data_hash = NULL;
188static GHashTable *icon_data_hash = NULL;
189
190typedef struct
191{
192 int flags;
193 int dir_index;
194
195 ImageData *image_data;
196 guint pixel_data_size;
197
198 IconData *icon_data;
199 guint icon_data_size;
200} Image;
201
202
203static gboolean
204foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
205{
206 Image *image = (Image *)value;
207 GHashTable *files = user_data;
208 GList *list;
209 gboolean free_key = FALSE;
210
211 if (image->flags == HAS_ICON_FILE)
212 {
213 /* just a .icon file, throw away */
214 g_free (key);
215 g_free (image);
216
217 return TRUE;
218 }
219
220 list = g_hash_table_lookup (files, key);
221 if (list)
222 free_key = TRUE;
223
224 list = g_list_prepend (list, value);
225 g_hash_table_insert (files, key, list);
226
227 if (free_key)
228 g_free (key);
229
230 return TRUE;
231}
232
233static IconData *
234load_icon_data (const char *path)
235{
236 GKeyFile *icon_file;
237 char **split;
238 gsize length;
239 char *str;
240 char *split_point;
241 int i;
242 gint *ivalues;
243 GError *error = NULL;
244 gchar **keys;
245 gsize n_keys;
246 IconData *data;
247
248 icon_file = g_key_file_new ();
249 g_key_file_set_list_separator (icon_file, ',');
250 g_key_file_load_from_file (icon_file, path, G_KEY_FILE_KEEP_TRANSLATIONS, &error);
251 if (error)
252 {
253 g_error_free (error);
254 g_key_file_free (icon_file);
255
256 return NULL;
257 }
258
259 data = g_new0 (IconData, 1);
260
261 ivalues = g_key_file_get_integer_list (icon_file,
262 "Icon Data", "EmbeddedTextRectangle",
263 &length, NULL);
264 if (ivalues)
265 {
266 if (length == 4)
267 {
268 data->has_embedded_rect = TRUE;
269 data->x0 = ivalues[0];
270 data->y0 = ivalues[1];
271 data->x1 = ivalues[2];
272 data->y1 = ivalues[3];
273 }
274
275 g_free (ivalues);
276 }
277
278 str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
279 if (str)
280 {
281 split = g_strsplit (str, "|", -1);
282
283 data->n_attach_points = g_strv_length (split);
284 data->attach_points = g_new (int, 2 * data->n_attach_points);
285
286 i = 0;
287 while (split[i] != NULL && i < data->n_attach_points)
288 {
289 split_point = strchr (split[i], ',');
290 if (split_point)
291 {
292 *split_point = 0;
293 split_point++;
294 data->attach_points[2 * i] = atoi (split[i]);
295 data->attach_points[2 * i + 1] = atoi (split_point);
296 }
297 i++;
298 }
299
300 g_strfreev (split);
301 g_free (str);
302 }
303
304 keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error);
305 data->display_names = g_new0 (gchar *, 2 * n_keys + 1);
306 data->n_display_names = 0;
307
308 for (i = 0; i < n_keys; i++)
309 {
310 gchar *lang, *name;
311
312 if (g_str_has_prefix (keys[i], "DisplayName"))
313 {
314 gchar *open, *close = NULL;
315
316 open = strchr (keys[i], '[');
317
318 if (open)
319 close = strchr (open, ']');
320
321 if (open && close)
322 {
323 lang = g_strndup (open + 1, close - open - 1);
324 name = g_key_file_get_locale_string (icon_file,
325 "Icon Data", "DisplayName",
326 lang, NULL);
327 }
328 else
329 {
330 lang = g_strdup ("C");
331 name = g_key_file_get_string (icon_file,
332 "Icon Data", "DisplayName",
333 NULL);
334 }
335
336 data->display_names[2 * data->n_display_names] = lang;
337 data->display_names[2 * data->n_display_names + 1] = name;
338 data->n_display_names++;
339 }
340 }
341
342 g_strfreev (keys);
343
344 g_key_file_free (icon_file);
345
346 /* -1 means not computed yet, the real value depends
347 * on string pool state, and will be computed
348 * later
349 */
350 data->size = -1;
351
352 return data;
353}
354
355/*
356 * This function was copied from gtkfilesystemunix.c, it should
357 * probably go to GLib
358 */
359static void
360canonicalize_filename (gchar *filename)
361{
362 gchar *p, *q;
363 gboolean last_was_slash = FALSE;
364
365 p = filename;
366 q = filename;
367
368 while (*p)
369 {
370 if (*p == G_DIR_SEPARATOR)
371 {
372 if (!last_was_slash)
373 *q++ = G_DIR_SEPARATOR;
374
375 last_was_slash = TRUE;
376 }
377 else
378 {
379 if (last_was_slash && *p == '.')
380 {
381 if (*(p + 1) == G_DIR_SEPARATOR ||
382 *(p + 1) == '\0')
383 {
384 if (*(p + 1) == '\0')
385 break;
386
387 p += 1;
388 }
389 else if (*(p + 1) == '.' &&
390 (*(p + 2) == G_DIR_SEPARATOR ||
391 *(p + 2) == '\0'))
392 {
393 if (q > filename + 1)
394 {
395 q--;
396 while (q > filename + 1 &&
397 *(q - 1) != G_DIR_SEPARATOR)
398 q--;
399 }
400
401 if (*(p + 2) == '\0')
402 break;
403
404 p += 2;
405 }
406 else
407 {
408 *q++ = *p;
409 last_was_slash = FALSE;
410 }
411 }
412 else
413 {
414 *q++ = *p;
415 last_was_slash = FALSE;
416 }
417 }
418
419 p++;
420 }
421
422 if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
423 q--;
424
425 *q = '\0';
426}
427
428static gchar *
429follow_links (const gchar *path)
430{
431 gchar *target;
432 gchar *d, *s;
433 gchar *path2 = NULL;
434
435 path2 = g_strdup (path);
436 while (g_file_test (path2, G_FILE_TEST_IS_SYMLINK))
437 {
438 target = g_file_read_link (path2, NULL);
439
440 if (target)
441 {
442 if (g_path_is_absolute (target))
443 path2 = target;
444 else
445 {
446 d = g_path_get_dirname (path2);
447 s = g_build_filename (d, target, NULL);
448 g_free (d);
449 g_free (target);
450 g_free (path2);
451 path2 = s;
452 }
453 }
454 else
455 break;
456 }
457
458 if (strcmp (path, path2) == 0)
459 {
460 g_free (path2);
461 path2 = NULL;
462 }
463
464 return path2;
465}
466
467static void
468maybe_cache_image_data (Image *image,
469 const gchar *path)
470{
471 if (!index_only && !image->image_data &&
472 (g_str_has_suffix (path, ".png") || g_str_has_suffix (path, ".xpm")))
473 {
474 GdkPixbuf *pixbuf;
475 ImageData *idata;
476 gchar *path2;
477
478 idata = g_hash_table_lookup (image_data_hash, path);
479 path2 = follow_links (path);
480
481 if (path2)
482 {
483 ImageData *idata2;
484
485 canonicalize_filename (path2);
486
487 idata2 = g_hash_table_lookup (image_data_hash, path2);
488
489 if (idata && idata2 && idata != idata2)
490 g_error ("different idatas found for symlinked '%s' and '%s'\n",
491 path, path2);
492
493 if (idata && !idata2)
494 g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
495
496 if (!idata && idata2)
497 {
498 g_hash_table_insert (image_data_hash, g_strdup (path), idata2);
499 idata = idata2;
500 }
501 }
502
503 if (!idata)
504 {
505 idata = g_new0 (ImageData, 1);
506 g_hash_table_insert (image_data_hash, g_strdup (path), idata);
507 if (path2)
508 g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
509 }
510
511 if (!idata->has_pixdata)
512 {
513 pixbuf = gdk_pixbuf_new_from_file (path, NULL);
514
515 if (pixbuf)
516 {
517G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
518 gdk_pixdata_from_pixbuf (&idata->pixdata, pixbuf, FALSE);
519G_GNUC_END_IGNORE_DEPRECATIONS;
520 idata->size = idata->pixdata.length + 8;
521 idata->has_pixdata = TRUE;
522 }
523 }
524
525 image->image_data = idata;
526
527 g_free (path2);
528 }
529}
530
531static void
532maybe_cache_icon_data (Image *image,
533 const gchar *path)
534{
535 if (g_str_has_suffix (path, ".icon"))
536 {
537 IconData *idata = NULL;
538 gchar *path2 = NULL;
539
540 idata = g_hash_table_lookup (icon_data_hash, path);
541 path2 = follow_links (path);
542
543 if (path2)
544 {
545 IconData *idata2;
546
547 canonicalize_filename (path2);
548
549 idata2 = g_hash_table_lookup (icon_data_hash, path2);
550
551 if (idata && idata2 && idata != idata2)
552 g_error ("different idatas found for symlinked '%s' and '%s'\n",
553 path, path2);
554
555 if (idata && !idata2)
556 g_hash_table_insert (icon_data_hash, g_strdup (path2), idata);
557
558 if (!idata && idata2)
559 {
560 g_hash_table_insert (icon_data_hash, g_strdup (path), idata2);
561 idata = idata2;
562 }
563 }
564
565 if (!idata)
566 {
567 idata = load_icon_data (path);
568 g_hash_table_insert (icon_data_hash, g_strdup (path), idata);
569 if (path2)
570 g_hash_table_insert (icon_data_hash, g_strdup (path2), idata);
571 }
572
573 image->icon_data = idata;
574
575 g_free (path2);
576 }
577}
578
579/*
580 * Finds all dir separators and replaces them with “/”.
581 * This makes sure that only /-separated paths are written in cache files,
582 * maintaining compatibility with theme index files that use slashes as
583 * directory separators on all platforms.
584 */
585static void
586replace_backslashes_with_slashes (gchar *path)
587{
588 size_t i;
589 if (path == NULL)
590 return;
591 for (i = 0; path[i]; i++)
592 if (G_IS_DIR_SEPARATOR (path[i]))
593 path[i] = '/';
594}
595
596static GList *
597scan_directory (const gchar *base_path,
598 const gchar *subdir,
599 GHashTable *files,
600 GList *directories,
601 gint depth)
602{
603 GHashTable *dir_hash;
604 GDir *dir;
605 const gchar *name;
606 gchar *dir_path;
607 gboolean dir_added = FALSE;
608 guint dir_index = 0xffff;
609
610 dir_path = g_build_path ("/", base_path, subdir, NULL);
611
612 /* FIXME: Use the gerror */
613 dir = g_dir_open (dir_path, 0, NULL);
614
615 if (!dir)
616 return directories;
617
618 dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
619
620 while ((name = g_dir_read_name (dir)))
621 {
622 gchar *path;
623 gboolean retval;
624 int flags = 0;
625 Image *image;
626 gchar *basename, *dot;
627
628 path = g_build_filename (dir_path, name, NULL);
629
630 retval = g_file_test (path, G_FILE_TEST_IS_DIR);
631 if (retval)
632 {
633 gchar *subsubdir;
634
635 if (subdir)
636 subsubdir = g_build_path ("/", subdir, name, NULL);
637 else
638 subsubdir = g_strdup (name);
639 directories = scan_directory (base_path, subsubdir, files,
640 directories, depth + 1);
641 g_free (subsubdir);
642
643 continue;
644 }
645
646 /* ignore images in the toplevel directory */
647 if (subdir == NULL)
648 continue;
649
650 retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
651 if (retval)
652 {
653 if (g_str_has_suffix (name, ".png"))
654 flags |= HAS_SUFFIX_PNG;
655 else if (g_str_has_suffix (name, ".svg"))
656 flags |= HAS_SUFFIX_SVG;
657 else if (g_str_has_suffix (name, ".xpm"))
658 flags |= HAS_SUFFIX_XPM;
659 else if (g_str_has_suffix (name, ".icon"))
660 flags |= HAS_ICON_FILE;
661
662 if (flags == 0)
663 continue;
664
665 basename = g_strdup (name);
666 dot = strrchr (basename, '.');
667 *dot = '\0';
668
669 image = g_hash_table_lookup (dir_hash, basename);
670 if (!image)
671 {
672 if (!dir_added)
673 {
674 dir_added = TRUE;
675 if (subdir)
676 {
677 dir_index = g_list_length (directories);
678 directories = g_list_append (directories, g_strdup (subdir));
679 }
680 else
681 dir_index = 0xffff;
682 }
683
684 image = g_new0 (Image, 1);
685 image->dir_index = dir_index;
686 g_hash_table_insert (dir_hash, g_strdup (basename), image);
687 }
688
689 image->flags |= flags;
690
691 maybe_cache_image_data (image, path);
692 maybe_cache_icon_data (image, path);
693
694 g_free (basename);
695 }
696
697 g_free (path);
698 }
699
700 g_dir_close (dir);
701
702 /* Move dir into the big file hash */
703 g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
704
705 g_hash_table_destroy (dir_hash);
706
707 return directories;
708}
709
710typedef struct _HashNode HashNode;
711
712struct _HashNode
713{
714 HashNode *next;
715 gchar *name;
716 GList *image_list;
717 gint offset;
718};
719
720static guint
721icon_name_hash (gconstpointer key)
722{
723 const signed char *p = key;
724 guint32 h = *p;
725
726 if (h)
727 for (p += 1; *p != '\0'; p++)
728 h = (h << 5) - h + *p;
729
730 return h;
731}
732
733typedef struct {
734 gint size;
735 HashNode **nodes;
736} HashContext;
737
738static gboolean
739convert_to_hash (gpointer key, gpointer value, gpointer user_data)
740{
741 HashContext *context = user_data;
742 guint hash;
743 HashNode *node;
744
745 hash = icon_name_hash (key) % context->size;
746
747 node = g_new0 (HashNode, 1);
748 node->next = NULL;
749 node->name = key;
750 node->image_list = value;
751
752 if (context->nodes[hash] != NULL)
753 node->next = context->nodes[hash];
754
755 context->nodes[hash] = node;
756
757 return TRUE;
758}
759
760static GHashTable *string_pool = NULL;
761
762static int
763find_string (const gchar *n)
764{
765 return GPOINTER_TO_INT (g_hash_table_lookup (string_pool, n));
766}
767
768static void
769add_string (const gchar *n, int offset)
770{
771 g_hash_table_insert (string_pool, (gpointer) n, GINT_TO_POINTER (offset));
772}
773
774static gboolean
775write_string (FILE *cache, const gchar *n)
776{
777 gchar *s;
778 int i, l;
779
780 l = ALIGN_VALUE (strlen (n) + 1, 4);
781
782 s = g_malloc0 (l);
783 strcpy (s, n);
784
785 i = fwrite (s, l, 1, cache);
786
787 g_free (s);
788
789 return i == 1;
790
791}
792
793static gboolean
794write_card16 (FILE *cache, guint16 n)
795{
796 int i;
797
798 n = GUINT16_TO_BE (n);
799
800 i = fwrite ((char *)&n, 2, 1, cache);
801
802 return i == 1;
803}
804
805static gboolean
806write_card32 (FILE *cache, guint32 n)
807{
808 int i;
809
810 n = GUINT32_TO_BE (n);
811
812 i = fwrite ((char *)&n, 4, 1, cache);
813
814 return i == 1;
815}
816
817
818static gboolean
819write_image_data (FILE *cache, ImageData *image_data, int offset)
820{
821 guint8 *s;
822 guint len;
823 gint i;
824 GdkPixdata *pixdata = &image_data->pixdata;
825
826 /* Type 0 is GdkPixdata */
827 if (!write_card32 (cache, 0))
828 return FALSE;
829
830G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
831 s = gdk_pixdata_serialize (pixdata, &len);
832G_GNUC_END_IGNORE_DEPRECATIONS;
833
834 if (!write_card32 (cache, len))
835 {
836 g_free (s);
837 return FALSE;
838 }
839
840 i = fwrite (s, len, 1, cache);
841
842 g_free (s);
843
844 return i == 1;
845}
846
847static gboolean
848write_icon_data (FILE *cache, IconData *icon_data, int offset)
849{
850 int ofs = offset + 12;
851 int j;
852 int tmp, tmp2;
853
854 if (icon_data->has_embedded_rect)
855 {
856 if (!write_card32 (cache, ofs))
857 return FALSE;
858
859 ofs += 8;
860 }
861 else
862 {
863 if (!write_card32 (cache, 0))
864 return FALSE;
865 }
866
867 if (icon_data->n_attach_points > 0)
868 {
869 if (!write_card32 (cache, ofs))
870 return FALSE;
871
872 ofs += 4 + 4 * icon_data->n_attach_points;
873 }
874 else
875 {
876 if (!write_card32 (cache, 0))
877 return FALSE;
878 }
879
880 if (icon_data->n_display_names > 0)
881 {
882 if (!write_card32 (cache, ofs))
883 return FALSE;
884 }
885 else
886 {
887 if (!write_card32 (cache, 0))
888 return FALSE;
889 }
890
891 if (icon_data->has_embedded_rect)
892 {
893 if (!write_card16 (cache, icon_data->x0) ||
894 !write_card16 (cache, icon_data->y0) ||
895 !write_card16 (cache, icon_data->x1) ||
896 !write_card16 (cache, icon_data->y1))
897 return FALSE;
898 }
899
900 if (icon_data->n_attach_points > 0)
901 {
902 if (!write_card32 (cache, icon_data->n_attach_points))
903 return FALSE;
904
905 for (j = 0; j < 2 * icon_data->n_attach_points; j++)
906 {
907 if (!write_card16 (cache, icon_data->attach_points[j]))
908 return FALSE;
909 }
910 }
911
912 if (icon_data->n_display_names > 0)
913 {
914 if (!write_card32 (cache, icon_data->n_display_names))
915 return FALSE;
916
917 ofs += 4 + 8 * icon_data->n_display_names;
918
919 tmp = ofs;
920 for (j = 0; j < 2 * icon_data->n_display_names; j++)
921 {
922 tmp2 = find_string (icon_data->display_names[j]);
923 if (tmp2 == 0 || tmp2 == -1)
924 {
925 tmp2 = tmp;
926 tmp += ALIGN_VALUE (strlen (icon_data->display_names[j]) + 1, 4);
927 /* We're playing a little game with negative
928 * offsets here to handle duplicate strings in
929 * the array.
930 */
931 add_string (icon_data->display_names[j], -tmp2);
932 }
933 else if (tmp2 < 0)
934 {
935 tmp2 = -tmp2;
936 }
937
938 if (!write_card32 (cache, tmp2))
939 return FALSE;
940
941 }
942
943 g_assert (ofs == ftell (cache));
944 for (j = 0; j < 2 * icon_data->n_display_names; j++)
945 {
946 tmp2 = find_string (icon_data->display_names[j]);
947 g_assert (tmp2 != 0 && tmp2 != -1);
948 if (tmp2 < 0)
949 {
950 tmp2 = -tmp2;
951 g_assert (tmp2 == ftell (cache));
952 add_string (icon_data->display_names[j], tmp2);
953 if (!write_string (cache, icon_data->display_names[j]))
954 return FALSE;
955 }
956 }
957 }
958
959 return TRUE;
960}
961
962static gboolean
963write_header (FILE *cache, guint32 dir_list_offset)
964{
965 return (write_card16 (cache, MAJOR_VERSION) &&
966 write_card16 (cache, MINOR_VERSION) &&
967 write_card32 (cache, HASH_OFFSET) &&
968 write_card32 (cache, dir_list_offset));
969}
970
971static gint
972get_image_meta_data_size (Image *image)
973{
974 gint i;
975
976 /* The complication with storing the size in both
977 * IconData and Image is necessary since we attribute
978 * the size of the IconData only to the first Image
979 * using it (at which time it is written out in the
980 * cache). Later Images just refer to the written out
981 * IconData via the offset.
982 */
983 if (image->icon_data_size == 0)
984 {
985 if (image->icon_data && image->icon_data->size < 0)
986 {
987 IconData *data = image->icon_data;
988
989 data->size = 0;
990
991 if (data->has_embedded_rect ||
992 data->n_attach_points > 0 ||
993 data->n_display_names > 0)
994 data->size += 12;
995
996 if (data->has_embedded_rect)
997 data->size += 8;
998
999 if (data->n_attach_points > 0)
1000 data->size += 4 + data->n_attach_points * 4;
1001
1002 if (data->n_display_names > 0)
1003 {
1004 data->size += 4 + 8 * data->n_display_names;
1005
1006 for (i = 0; data->display_names[i]; i++)
1007 {
1008 int poolv;
1009 if ((poolv = find_string (data->display_names[i])) == 0)
1010 {
1011 data->size += ALIGN_VALUE (strlen (data->display_names[i]) + 1, 4);
1012 /* Adding the string to the pool with -1
1013 * to indicate that it hasn't been written out
1014 * to the cache yet. We still need it in the
1015 * pool in case the same string occurs twice
1016 * during a get_single_node_size() calculation.
1017 */
1018 add_string (data->display_names[i], -1);
1019 }
1020 }
1021 }
1022
1023 image->icon_data_size = data->size;
1024 data->size = 0;
1025 }
1026 }
1027
1028 g_assert (image->icon_data_size % 4 == 0);
1029
1030 return image->icon_data_size;
1031}
1032
1033static gint
1034get_image_pixel_data_size (Image *image)
1035{
1036 /* The complication with storing the size in both
1037 * ImageData and Image is necessary since we attribute
1038 * the size of the ImageData only to the first Image
1039 * using it (at which time it is written out in the
1040 * cache). Later Images just refer to the written out
1041 * ImageData via the offset.
1042 */
1043 if (image->pixel_data_size == 0)
1044 {
1045 if (image->image_data &&
1046 image->image_data->has_pixdata)
1047 {
1048 image->pixel_data_size = image->image_data->size;
1049 image->image_data->size = 0;
1050 }
1051 }
1052
1053 g_assert (image->pixel_data_size % 4 == 0);
1054
1055 return image->pixel_data_size;
1056}
1057
1058static gint
1059get_image_data_size (Image *image)
1060{
1061 gint len;
1062
1063 len = 0;
1064
1065 len += get_image_pixel_data_size (image);
1066 len += get_image_meta_data_size (image);
1067
1068 /* Even if len is zero, we need to reserve space to
1069 * write the ImageData, unless this is an .svg without
1070 * .icon, in which case both image_data and icon_data
1071 * are NULL.
1072 */
1073 if (len > 0 || image->image_data || image->icon_data)
1074 len += 8;
1075
1076 return len;
1077}
1078
1079static void
1080get_single_node_size (HashNode *node, int *node_size, int *image_data_size)
1081{
1082 GList *list;
1083
1084 /* Node pointers */
1085 *node_size = 12;
1086
1087 /* Name */
1088 if (find_string (node->name) == 0)
1089 {
1090 *node_size += ALIGN_VALUE (strlen (node->name) + 1, 4);
1091 add_string (node->name, -1);
1092 }
1093
1094 /* Image list */
1095 *node_size += 4 + g_list_length (node->image_list) * 8;
1096
1097 /* Image data */
1098 *image_data_size = 0;
1099 for (list = node->image_list; list; list = list->next)
1100 {
1101 Image *image = list->data;
1102
1103 *image_data_size += get_image_data_size (image);
1104 }
1105}
1106
1107static gboolean
1108write_bucket (FILE *cache, HashNode *node, int *offset)
1109{
1110 while (node != NULL)
1111 {
1112 int node_size, image_data_size;
1113 int next_offset, image_data_offset;
1114 int data_offset;
1115 int name_offset;
1116 int name_size;
1117 int image_list_offset;
1118 int i, len;
1119 GList *list;
1120
1121 g_assert (*offset == ftell (cache));
1122
1123 node->offset = *offset;
1124
1125 get_single_node_size (node, &node_size, &image_data_size);
1126 g_assert (node_size % 4 == 0);
1127 g_assert (image_data_size % 4 == 0);
1128 image_data_offset = *offset + node_size;
1129 next_offset = *offset + node_size + image_data_size;
1130 /* Chain offset */
1131 if (node->next != NULL)
1132 {
1133 if (!write_card32 (cache, next_offset))
1134 return FALSE;
1135 }
1136 else
1137 {
1138 if (!write_card32 (cache, 0xffffffff))
1139 return FALSE;
1140 }
1141
1142 name_size = 0;
1143 name_offset = find_string (node->name);
1144 if (name_offset <= 0)
1145 {
1146 name_offset = *offset + 12;
1147 name_size = ALIGN_VALUE (strlen (node->name) + 1, 4);
1148 add_string (node->name, name_offset);
1149 }
1150 if (!write_card32 (cache, name_offset))
1151 return FALSE;
1152
1153 image_list_offset = *offset + 12 + name_size;
1154 if (!write_card32 (cache, image_list_offset))
1155 return FALSE;
1156
1157 /* Icon name */
1158 if (name_size > 0)
1159 {
1160 if (!write_string (cache, node->name))
1161 return FALSE;
1162 }
1163
1164 /* Image list */
1165 len = g_list_length (node->image_list);
1166 if (!write_card32 (cache, len))
1167 return FALSE;
1168
1169 list = node->image_list;
1170 data_offset = image_data_offset;
1171 for (i = 0; i < len; i++)
1172 {
1173 Image *image = list->data;
1174 int image_size = get_image_data_size (image);
1175
1176 /* Directory index */
1177 if (!write_card16 (cache, image->dir_index))
1178 return FALSE;
1179
1180 /* Flags */
1181 if (!write_card16 (cache, image->flags))
1182 return FALSE;
1183
1184 /* Image data offset */
1185 if (image_size > 0)
1186 {
1187 if (!write_card32 (cache, data_offset))
1188 return FALSE;
1189 data_offset += image_size;
1190 }
1191 else
1192 {
1193 if (!write_card32 (cache, 0))
1194 return FALSE;
1195 }
1196
1197 list = list->next;
1198 }
1199
1200 /* Now write the image data */
1201 list = node->image_list;
1202 for (i = 0; i < len; i++, list = list->next)
1203 {
1204 Image *image = list->data;
1205 int pixel_data_size = get_image_pixel_data_size (image);
1206 int meta_data_size = get_image_meta_data_size (image);
1207
1208 if (get_image_data_size (image) == 0)
1209 continue;
1210
1211 /* Pixel data */
1212 if (pixel_data_size > 0)
1213 {
1214 image->image_data->offset = image_data_offset + 8;
1215 if (!write_card32 (cache, image->image_data->offset))
1216 return FALSE;
1217 }
1218 else
1219 {
1220 if (!write_card32 (cache, (guint32) (image->image_data ? image->image_data->offset : 0)))
1221 return FALSE;
1222 }
1223
1224 if (meta_data_size > 0)
1225 {
1226 image->icon_data->offset = image_data_offset + pixel_data_size + 8;
1227 if (!write_card32 (cache, image->icon_data->offset))
1228 return FALSE;
1229 }
1230 else
1231 {
1232 if (!write_card32 (cache, image->icon_data ? image->icon_data->offset : 0))
1233 return FALSE;
1234 }
1235
1236 if (pixel_data_size > 0)
1237 {
1238 if (!write_image_data (cache, image->image_data, image->image_data->offset))
1239 return FALSE;
1240 }
1241
1242 if (meta_data_size > 0)
1243 {
1244 if (!write_icon_data (cache, image->icon_data, image->icon_data->offset))
1245 return FALSE;
1246 }
1247
1248 image_data_offset += pixel_data_size + meta_data_size + 8;
1249 }
1250
1251 *offset = next_offset;
1252 node = node->next;
1253 }
1254
1255 return TRUE;
1256}
1257
1258static gboolean
1259write_hash_table (FILE *cache, HashContext *context, int *new_offset)
1260{
1261 int offset = HASH_OFFSET;
1262 int node_offset;
1263 int i;
1264
1265 if (!(write_card32 (cache, context->size)))
1266 return FALSE;
1267
1268 offset += 4;
1269 node_offset = offset + context->size * 4;
1270 /* Just write zeros here, we will rewrite this later */
1271 for (i = 0; i < context->size; i++)
1272 {
1273 if (!write_card32 (cache, 0))
1274 return FALSE;
1275 }
1276
1277 /* Now write the buckets */
1278 for (i = 0; i < context->size; i++)
1279 {
1280 if (!context->nodes[i])
1281 continue;
1282
1283 g_assert (node_offset % 4 == 0);
1284 if (!write_bucket (cache, context->nodes[i], &node_offset))
1285 return FALSE;
1286 }
1287
1288 *new_offset = node_offset;
1289
1290 /* Now write out the bucket offsets */
1291
1292 fseek (cache, offset, SEEK_SET);
1293
1294 for (i = 0; i < context->size; i++)
1295 {
1296 if (context->nodes[i] != NULL)
1297 node_offset = context->nodes[i]->offset;
1298 else
1299 node_offset = 0xffffffff;
1300 if (!write_card32 (cache, node_offset))
1301 return FALSE;
1302 }
1303
1304 fseek (cache, 0, SEEK_END);
1305
1306 return TRUE;
1307}
1308
1309static gboolean
1310write_dir_index (FILE *cache, int offset, GList *directories)
1311{
1312 int n_dirs;
1313 GList *d;
1314 char *dir;
1315 int tmp, tmp2;
1316
1317 n_dirs = g_list_length (directories);
1318
1319 if (!write_card32 (cache, n_dirs))
1320 return FALSE;
1321
1322 offset += 4 + n_dirs * 4;
1323
1324 tmp = offset;
1325 for (d = directories; d; d = d->next)
1326 {
1327 dir = d->data;
1328
1329 tmp2 = find_string (dir);
1330
1331 if (tmp2 == 0 || tmp2 == -1)
1332 {
1333 tmp2 = tmp;
1334 tmp += ALIGN_VALUE (strlen (dir) + 1, 4);
1335 /* We're playing a little game with negative
1336 * offsets here to handle duplicate strings in
1337 * the array, even though that should not
1338 * really happen for the directory index.
1339 */
1340 add_string (dir, -tmp2);
1341 }
1342 else if (tmp2 < 0)
1343 {
1344 tmp2 = -tmp2;
1345 }
1346
1347 if (!write_card32 (cache, tmp2))
1348 return FALSE;
1349 }
1350
1351 g_assert (offset == ftell (cache));
1352 for (d = directories; d; d = d->next)
1353 {
1354 dir = d->data;
1355
1356 tmp2 = find_string (dir);
1357 g_assert (tmp2 != 0 && tmp2 != -1);
1358 if (tmp2 < 0)
1359 {
1360 tmp2 = -tmp2;
1361 g_assert (tmp2 == ftell (cache));
1362 add_string (dir, tmp2);
1363 if (!write_string (cache, dir))
1364 return FALSE;
1365 }
1366 }
1367
1368 return TRUE;
1369}
1370
1371static gboolean
1372write_file (FILE *cache, GHashTable *files, GList *directories)
1373{
1374 HashContext context;
1375 int new_offset;
1376
1377 /* Convert the hash table into something looking a bit more
1378 * like what we want to write to disk.
1379 */
1380 context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
1381 context.nodes = g_new0 (HashNode *, context.size);
1382
1383 g_hash_table_foreach_remove (files, convert_to_hash, &context);
1384
1385 /* Now write the file */
1386 /* We write 0 as the directory list offset and go
1387 * back and change it later */
1388 if (!write_header (cache, 0))
1389 {
1390 g_printerr (_("Failed to write header\n"));
1391 return FALSE;
1392 }
1393
1394 if (!write_hash_table (cache, &context, &new_offset))
1395 {
1396 g_printerr (_("Failed to write hash table\n"));
1397 return FALSE;
1398 }
1399
1400 if (!write_dir_index (cache, new_offset, directories))
1401 {
1402 g_printerr (_("Failed to write folder index\n"));
1403 return FALSE;
1404 }
1405
1406 rewind (cache);
1407
1408 if (!write_header (cache, new_offset))
1409 {
1410 g_printerr (_("Failed to rewrite header\n"));
1411 return FALSE;
1412 }
1413
1414 return TRUE;
1415}
1416
1417static gboolean
1418validate_file (const gchar *file)
1419{
1420 GMappedFile *map;
1421 CacheInfo info;
1422
1423 map = g_mapped_file_new (file, FALSE, NULL);
1424 if (!map)
1425 return FALSE;
1426
1427 info.cache = g_mapped_file_get_contents (map);
1428 info.cache_size = g_mapped_file_get_length (map);
1429 info.n_directories = 0;
1430 info.flags = CHECK_OFFSETS|CHECK_STRINGS|CHECK_PIXBUFS;
1431
1432 if (!_gtk_icon_cache_validate (&info))
1433 {
1434 g_mapped_file_unref (map);
1435 return FALSE;
1436 }
1437
1438 g_mapped_file_unref (map);
1439
1440 return TRUE;
1441}
1442
1443/**
1444 * safe_fclose:
1445 * @f: A FILE* stream, must have underlying fd
1446 *
1447 * Unix defaults for data preservation after system crash
1448 * are unspecified, and many systems will eat your data
1449 * in this situation unless you explicitly fsync().
1450 *
1451 * Returns: %TRUE on success, %FALSE on failure, and will set errno()
1452 */
1453static gboolean
1454safe_fclose (FILE *f)
1455{
1456 int fd = fileno (f);
1457 g_assert (fd >= 0);
1458 if (fflush (f) == EOF)
1459 return FALSE;
1460#ifndef G_OS_WIN32
1461 if (fsync (fd) < 0)
1462 return FALSE;
1463#endif
1464 if (fclose (f) == EOF)
1465 return FALSE;
1466 return TRUE;
1467}
1468
1469static void
1470build_cache (const gchar *path)
1471{
1472 gchar *cache_path, *tmp_cache_path;
1473#ifdef G_OS_WIN32
1474 gchar *bak_cache_path = NULL;
1475#endif
1476 GHashTable *files;
1477 FILE *cache;
1478 GStatBuf path_stat, cache_stat;
1479 struct utimbuf utime_buf;
1480 GList *directories = NULL;
1481 int fd;
1482 int retry_count = 0;
1483#ifndef G_OS_WIN32
1484 mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
1485#else
1486 int mode = _S_IWRITE | _S_IREAD;
1487#endif
1488#ifndef _O_BINARY
1489#define _O_BINARY 0
1490#endif
1491
1492 tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
1493 cache_path = g_build_filename (path, CACHE_NAME, NULL);
1494
1495opentmp:
1496 if ((fd = g_open (tmp_cache_path, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | _O_BINARY, mode)) == -1)
1497 {
1498 if (retry_count == 0)
1499 {
1500 retry_count++;
1501 g_remove (tmp_cache_path);
1502 goto opentmp;
1503 }
1504 g_printerr (_("Failed to open file %s : %s\n"), tmp_cache_path, g_strerror (errno));
1505 exit (1);
1506 }
1507
1508 cache = fdopen (fd, "wb");
1509
1510 if (!cache)
1511 {
1512 g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno));
1513 exit (1);
1514 }
1515
1516 files = g_hash_table_new (g_str_hash, g_str_equal);
1517 image_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
1518 icon_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
1519 string_pool = g_hash_table_new (g_str_hash, g_str_equal);
1520
1521 directories = scan_directory (path, NULL, files, NULL, 0);
1522
1523 if (g_hash_table_size (files) == 0)
1524 {
1525 /* Empty table, just close and remove the file */
1526
1527 fclose (cache);
1528 g_unlink (tmp_cache_path);
1529 g_unlink (cache_path);
1530 exit (0);
1531 }
1532
1533 /* FIXME: Handle failure */
1534 if (!write_file (cache, files, directories))
1535 {
1536 g_unlink (tmp_cache_path);
1537 exit (1);
1538 }
1539
1540 if (!safe_fclose (cache))
1541 {
1542 g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno));
1543 g_unlink (tmp_cache_path);
1544 exit (1);
1545 }
1546 cache = NULL;
1547
1548 g_list_free_full (directories, g_free);
1549
1550 if (!validate_file (tmp_cache_path))
1551 {
1552 g_printerr (_("The generated cache was invalid.\n"));
1553 /*g_unlink (tmp_cache_path);*/
1554 exit (1);
1555 }
1556
1557#ifdef G_OS_WIN32
1558 if (g_file_test (cache_path, G_FILE_TEST_EXISTS))
1559 {
1560 bak_cache_path = g_strconcat (cache_path, ".bak", NULL);
1561 g_unlink (bak_cache_path);
1562 if (g_rename (cache_path, bak_cache_path) == -1)
1563 {
1564 int errsv = errno;
1565
1566 g_printerr (_("Could not rename %s to %s: %s, removing %s then.\n"),
1567 cache_path, bak_cache_path,
1568 g_strerror (errsv),
1569 cache_path);
1570 g_unlink (cache_path);
1571 bak_cache_path = NULL;
1572 }
1573 }
1574#endif
1575
1576 if (g_rename (tmp_cache_path, cache_path) == -1)
1577 {
1578 int errsv = errno;
1579
1580 g_printerr (_("Could not rename %s to %s: %s\n"),
1581 tmp_cache_path, cache_path,
1582 g_strerror (errsv));
1583 g_unlink (tmp_cache_path);
1584#ifdef G_OS_WIN32
1585 if (bak_cache_path != NULL)
1586 if (g_rename (bak_cache_path, cache_path) == -1)
1587 {
1588 errsv = errno;
1589
1590 g_printerr (_("Could not rename %s back to %s: %s.\n"),
1591 bak_cache_path, cache_path,
1592 g_strerror (errsv));
1593 }
1594#endif
1595 exit (1);
1596 }
1597#ifdef G_OS_WIN32
1598 if (bak_cache_path != NULL)
1599 g_unlink (bak_cache_path);
1600#endif
1601
1602 /* Update time */
1603 /* FIXME: What do do if an error occurs here? */
1604 if (g_stat (path, &path_stat) < 0 ||
1605 g_stat (cache_path, &cache_stat))
1606 exit (1);
1607
1608 utime_buf.actime = path_stat.st_atime;
1609 utime_buf.modtime = cache_stat.st_mtime;
1610#if GLIB_CHECK_VERSION (2, 17, 1)
1611 g_utime (path, &utime_buf);
1612#else
1613 utime (path, &utime_buf);
1614#endif
1615
1616 if (!quiet)
1617 g_printerr (_("Cache file created successfully.\n"));
1618}
1619
1620static void
1621write_csource (const gchar *path)
1622{
1623 gchar *cache_path;
1624 gchar *data;
1625 gsize len;
1626 gint i;
1627
1628 cache_path = g_build_filename (path, CACHE_NAME, NULL);
1629 if (!g_file_get_contents (cache_path, &data, &len, NULL))
1630 exit (1);
1631
1632 g_printf ("#ifdef __SUNPRO_C\n");
1633 g_printf ("#pragma align 4 (%s)\n", var_name);
1634 g_printf ("#endif\n");
1635
1636 g_printf ("#ifdef __GNUC__\n");
1637 g_printf ("static const guint8 %s[] __attribute__ ((__aligned__ (4))) = \n", var_name);
1638 g_printf ("#else\n");
1639 g_printf ("static const guint8 %s[] = \n", var_name);
1640 g_printf ("#endif\n");
1641
1642 g_printf ("{\n");
1643 for (i = 0; i < len - 1; i++)
1644 {
1645 if (i %12 == 0)
1646 g_printf (" ");
1647 g_printf ("0x%02x, ", (guint8)data[i]);
1648 if (i % 12 == 11)
1649 g_printf ("\n");
1650 }
1651
1652 g_printf ("0x%02x\n};\n", (guint8)data[i]);
1653}
1654
1655static GOptionEntry args[] = {
1656 { "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, N_("Overwrite an existing cache, even if up to date"), NULL },
1657 { "ignore-theme-index", 't', 0, G_OPTION_ARG_NONE, &ignore_theme_index, N_("Don't check for the existence of index.theme"), NULL },
1658 { "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Don't include image data in the cache"), NULL },
1659 { "include-image-data", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &index_only, N_("Include image data in the cache"), NULL },
1660 { "source", 'c', 0, G_OPTION_ARG_STRING, &var_name, N_("Output a C header file"), "NAME" },
1661 { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, N_("Turn off verbose output"), NULL },
1662 { "validate", 'v', 0, G_OPTION_ARG_NONE, &validate, N_("Validate existing icon cache"), NULL },
1663 { NULL }
1664};
1665
1666static void
1667printerr_handler (const gchar *string)
1668{
1669 const gchar *charset;
1670
1671 fputs (g_get_prgname (), stderr);
1672 fputs (": ", stderr);
1673 if (g_get_charset (&charset))
1674 fputs (string, stderr); /* charset is UTF-8 already */
1675 else
1676 {
1677 gchar *result;
1678
1679 result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, NULL);
1680
1681 if (result)
1682 {
1683 fputs (result, stderr);
1684 g_free (result);
1685 }
1686
1687 fflush (stderr);
1688 }
1689}
1690
1691
1692int
1693main (int argc, char **argv)
1694{
1695 gchar *path;
1696 GOptionContext *context;
1697
1698 if (argc < 2)
1699 return 0;
1700
1701 g_set_printerr_handler (printerr_handler);
1702
1703 setlocale (LC_ALL, "");
1704
1705#ifdef ENABLE_NLS
1706 bindtextdomain (GETTEXT_PACKAGE, GTK_LOCALEDIR);
1707#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
1708 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1709#endif
1710#endif
1711
1712 context = g_option_context_new ("ICONPATH");
1713 g_option_context_add_main_entries (context, args, GETTEXT_PACKAGE);
1714
1715 g_option_context_parse (context, &argc, &argv, NULL);
1716
1717 path = argv[1];
1718#ifdef G_OS_WIN32
1719 path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
1720#endif
1721
1722 if (validate)
1723 {
1724 gchar *file = g_build_filename (path, CACHE_NAME, NULL);
1725
1726 if (!g_file_test (file, G_FILE_TEST_IS_REGULAR))
1727 {
1728 if (!quiet)
1729 g_printerr (_("File not found: %s\n"), file);
1730 exit (1);
1731 }
1732 if (!validate_file (file))
1733 {
1734 if (!quiet)
1735 g_printerr (_("Not a valid icon cache: %s\n"), file);
1736 exit (1);
1737 }
1738 else
1739 {
1740 exit (0);
1741 }
1742 }
1743
1744 if (!ignore_theme_index && !has_theme_index (path))
1745 {
1746 if (path)
1747 {
1748 g_printerr (_("No theme index file.\n"));
1749 }
1750 else
1751 {
1752 g_printerr (_("No theme index file in '%s'.\n"
1753 "If you really want to create an icon cache here, use --ignore-theme-index.\n"), path);
1754 }
1755
1756 return 1;
1757 }
1758
1759 if (!force_update && is_cache_up_to_date (path))
1760 return 0;
1761
1762 replace_backslashes_with_slashes (path);
1763 build_cache (path);
1764
1765 if (strcmp (var_name, "-") != 0)
1766 write_csource (path);
1767
1768 return 0;
1769}
1770