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

source code of gtk/tools/updateiconcache.c