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