/* updateiconcache.c * Copyright (C) 2004 Anders Carlsson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #ifdef _MSC_VER #include #include #else #include #endif #include #include #include #include #include "gtkiconcachevalidatorprivate.h" static gboolean force_update = FALSE; static gboolean ignore_theme_index = FALSE; static gboolean quiet = FALSE; static gboolean index_only = TRUE; static gboolean validate = FALSE; static char *var_name = (char *) "-"; #define CACHE_NAME "icon-theme.cache" #define HAS_SUFFIX_XPM (1 << 0) #define HAS_SUFFIX_SVG (1 << 1) #define HAS_SUFFIX_PNG (1 << 2) #define HAS_ICON_FILE (1 << 3) #define MAJOR_VERSION 1 #define MINOR_VERSION 0 #define HASH_OFFSET 12 #define ALIGN_VALUE(this, boundary) \ (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1))) #ifdef HAVE_FTW_H #include static GStatBuf cache_dir_stat; static gboolean cache_up_to_date; static int check_dir_mtime (const char *dir, const struct stat *sb, int tf) { if (tf != FTW_NS && sb->st_mtime > cache_dir_stat.st_mtime) { cache_up_to_date = FALSE; /* stop tree walk */ return 1; } return 0; } static gboolean is_cache_up_to_date (const char *path) { char *cache_path; int retval; cache_path = g_build_filename (path, CACHE_NAME, NULL); retval = g_stat (cache_path, &cache_dir_stat); g_free (cache_path); if (retval < 0) { /* Cache file not found */ return FALSE; } cache_up_to_date = TRUE; ftw (path, check_dir_mtime, 20); return cache_up_to_date; } #else /* !HAVE_FTW_H */ gboolean is_cache_up_to_date (const char *path) { GStatBuf path_stat, cache_stat; char *cache_path; int retval; retval = g_stat (path, &path_stat); if (retval < 0) { /* We can't stat the path, * assume we have a updated cache */ return TRUE; } cache_path = g_build_filename (path, CACHE_NAME, NULL); retval = g_stat (cache_path, &cache_stat); g_free (cache_path); if (retval < 0) { /* Cache file not found */ return FALSE; } /* Check mtime */ return cache_stat.st_mtime >= path_stat.st_mtime; } #endif /* !HAVE_FTW_H */ static gboolean has_theme_index (const char *path) { gboolean result; char *index_path; index_path = g_build_filename (path, "index.theme", NULL); result = g_file_test (index_path, G_FILE_TEST_IS_REGULAR); g_free (index_path); return result; } typedef struct { GdkPixdata pixdata; gboolean has_pixdata; guint32 offset; guint size; } ImageData; typedef struct { int has_embedded_rect; int x0, y0, x1, y1; int n_attach_points; int *attach_points; int n_display_names; char **display_names; guint32 offset; int size; } IconData; static GHashTable *image_data_hash = NULL; static GHashTable *icon_data_hash = NULL; typedef struct { int flags; int dir_index; ImageData *image_data; guint pixel_data_size; IconData *icon_data; guint icon_data_size; } Image; static gboolean foreach_remove_func (gpointer key, gpointer value, gpointer user_data) { Image *image = (Image *)value; GHashTable *files = user_data; GList *list; gboolean free_key = FALSE; if (image->flags == HAS_ICON_FILE) { /* just a .icon file, throw away */ g_free (key); g_free (image); return TRUE; } list = g_hash_table_lookup (files, key); if (list) free_key = TRUE; list = g_list_prepend (list, value); g_hash_table_insert (files, key, list); if (free_key) g_free (key); return TRUE; } static IconData * load_icon_data (const char *path) { GKeyFile *icon_file; char **split; gsize length; char *str; char *split_point; int i; int *ivalues; GError *error = NULL; char **keys; gsize n_keys; IconData *data; icon_file = g_key_file_new (); g_key_file_set_list_separator (icon_file, ','); g_key_file_load_from_file (icon_file, path, G_KEY_FILE_KEEP_TRANSLATIONS, &error); if (error) { g_error_free (error); g_key_file_free (icon_file); return NULL; } data = g_new0 (IconData, 1); ivalues = g_key_file_get_integer_list (icon_file, "Icon Data", "EmbeddedTextRectangle", &length, NULL); if (ivalues) { if (length == 4) { data->has_embedded_rect = TRUE; data->x0 = ivalues[0]; data->y0 = ivalues[1]; data->x1 = ivalues[2]; data->y1 = ivalues[3]; } g_free (ivalues); } str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL); if (str) { split = g_strsplit (str, "|", -1); data->n_attach_points = g_strv_length (split); data->attach_points = g_new (int, 2 * data->n_attach_points); for (i = 0; i < data->n_attach_points; ++i) { split_point = strchr (split[i], ','); if (split_point) { *split_point = 0; split_point++; data->attach_points[2 * i] = atoi (split[i]); data->attach_points[2 * i + 1] = atoi (split_point); } } g_strfreev (split); g_free (str); } keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error); data->display_names = g_new0 (char *, 2 * n_keys + 1); data->n_display_names = 0; for (i = 0; i < n_keys; i++) { char *lang, *name; if (g_str_has_prefix (keys[i], "DisplayName")) { char *open, *close = NULL; open = strchr (keys[i], '['); if (open) close = strchr (open, ']'); if (open && close) { lang = g_strndup (open + 1, close - open - 1); name = g_key_file_get_locale_string (icon_file, "Icon Data", "DisplayName", lang, NULL); } else { lang = g_strdup ("C"); name = g_key_file_get_string (icon_file, "Icon Data", "DisplayName", NULL); } data->display_names[2 * data->n_display_names] = lang; data->display_names[2 * data->n_display_names + 1] = name; data->n_display_names++; } } g_strfreev (keys); g_key_file_free (icon_file); /* -1 means not computed yet, the real value depends * on string pool state, and will be computed * later */ data->size = -1; return data; } /* * This function was copied from gtkfilesystemunix.c, it should * probably go to GLib */ static void canonicalize_filename (char *filename) { char *p, *q; gboolean last_was_slash = FALSE; p = filename; q = filename; while (*p) { if (*p == G_DIR_SEPARATOR) { if (!last_was_slash) *q++ = G_DIR_SEPARATOR; last_was_slash = TRUE; } else { if (last_was_slash && *p == '.') { if (*(p + 1) == G_DIR_SEPARATOR || *(p + 1) == '\0') { if (*(p + 1) == '\0') break; p += 1; } else if (*(p + 1) == '.' && (*(p + 2) == G_DIR_SEPARATOR || *(p + 2) == '\0')) { if (q > filename + 1) { q--; while (q > filename + 1 && *(q - 1) != G_DIR_SEPARATOR) q--; } if (*(p + 2) == '\0') break; p += 2; } else { *q++ = *p; last_was_slash = FALSE; } } else { *q++ = *p; last_was_slash = FALSE; } } p++; } if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR) q--; *q = '\0'; } static char * follow_links (const char *path) { char *target; char *d, *s; char *path2 = NULL; path2 = g_strdup (path); while (g_file_test (path2, G_FILE_TEST_IS_SYMLINK)) { target = g_file_read_link (path2, NULL); if (target) { if (g_path_is_absolute (target)) path2 = target; else { d = g_path_get_dirname (path2); s = g_build_filename (d, target, NULL); g_free (d); g_free (target); g_free (path2); path2 = s; } } else break; } if (strcmp (path, path2) == 0) { g_free (path2); path2 = NULL; } return path2; } static void maybe_cache_image_data (Image *image, const char *path) { if (!index_only && !image->image_data && (g_str_has_suffix (path, ".png") || g_str_has_suffix (path, ".xpm"))) { GdkPixbuf *pixbuf; ImageData *idata; char *path2; idata = g_hash_table_lookup (image_data_hash, path); path2 = follow_links (path); if (path2) { ImageData *idata2; canonicalize_filename (path2); idata2 = g_hash_table_lookup (image_data_hash, path2); if (idata && idata2 && idata != idata2) g_error ("different idatas found for symlinked '%s' and '%s'\n", path, path2); if (idata && !idata2) g_hash_table_insert (image_data_hash, g_strdup (path2), idata); if (!idata && idata2) { g_hash_table_insert (image_data_hash, g_strdup (path), idata2); idata = idata2; } } if (!idata) { idata = g_new0 (ImageData, 1); g_hash_table_insert (image_data_hash, g_strdup (path), idata); if (path2) g_hash_table_insert (image_data_hash, g_strdup (path2), idata); } if (!idata->has_pixdata) { pixbuf = gdk_pixbuf_new_from_file (path, NULL); if (pixbuf) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS; gdk_pixdata_from_pixbuf (&idata->pixdata, pixbuf, FALSE); G_GNUC_END_IGNORE_DEPRECATIONS; idata->size = idata->pixdata.length + 8; idata->has_pixdata = TRUE; } } image->image_data = idata; g_free (path2); } } static void maybe_cache_icon_data (Image *image, const char *path) { if (g_str_has_suffix (path, ".icon")) { IconData *idata = NULL; char *path2 = NULL; idata = g_hash_table_lookup (icon_data_hash, path); path2 = follow_links (path); if (path2) { IconData *idata2; canonicalize_filename (path2); idata2 = g_hash_table_lookup (icon_data_hash, path2); if (idata && idata2 && idata != idata2) g_error ("different idatas found for symlinked '%s' and '%s'\n", path, path2); if (idata && !idata2) g_hash_table_insert (icon_data_hash, g_strdup (path2), idata); if (!idata && idata2) { g_hash_table_insert (icon_data_hash, g_strdup (path), idata2); idata = idata2; } } if (!idata) { idata = load_icon_data (path); g_hash_table_insert (icon_data_hash, g_strdup (path), idata); if (path2) g_hash_table_insert (icon_data_hash, g_strdup (path2), idata); } image->icon_data = idata; g_free (path2); } } /* * Finds all dir separators and replaces them with “/”. * This makes sure that only /-separated paths are written in cache files, * maintaining compatibility with theme index files that use slashes as * directory separators on all platforms. */ static void replace_backslashes_with_slashes (char *path) { size_t i; if (path == NULL) return; for (i = 0; path[i]; i++) if (G_IS_DIR_SEPARATOR (path[i])) path[i] = '/'; } static GList * scan_directory (const char *base_path, const char *subdir, GHashTable *files, GList *directories, int depth) { GHashTable *dir_hash; GDir *dir; GList *list = NULL, *iterator = NULL; const char *name; char *dir_path; gboolean dir_added = FALSE; guint dir_index = 0xffff; dir_path = g_build_path ("/", base_path, subdir, NULL); /* FIXME: Use the gerror */ dir = g_dir_open (dir_path, 0, NULL); if (!dir) return directories; dir_hash = g_hash_table_new (g_str_hash, g_str_equal); while ((name = g_dir_read_name (dir))) { list = g_list_prepend (list, g_strdup (name)); } list = g_list_sort (list, (GCompareFunc) strcmp); for (iterator = list; iterator; iterator = iterator->next) { name = iterator->data; char *path; gboolean retval; int flags = 0; Image *image; char *basename, *dot; path = g_build_filename (dir_path, name, NULL); retval = g_file_test (path, G_FILE_TEST_IS_DIR); if (retval) { char *subsubdir; if (subdir) subsubdir = g_build_path ("/", subdir, name, NULL); else subsubdir = g_strdup (name); directories = scan_directory (base_path, subsubdir, files, directories, depth + 1); g_free (subsubdir); continue; } /* ignore images in the toplevel directory */ if (subdir == NULL) continue; retval = g_file_test (path, G_FILE_TEST_IS_REGULAR); if (retval) { if (g_str_has_suffix (name, ".png")) flags |= HAS_SUFFIX_PNG; else if (g_str_has_suffix (name, ".svg")) flags |= HAS_SUFFIX_SVG; else if (g_str_has_suffix (name, ".xpm")) flags |= HAS_SUFFIX_XPM; else if (g_str_has_suffix (name, ".icon")) flags |= HAS_ICON_FILE; if (flags == 0) continue; basename = g_strdup (name); dot = strrchr (basename, '.'); *dot = '\0'; image = g_hash_table_lookup (dir_hash, basename); if (!image) { if (!dir_added) { dir_added = TRUE; if (subdir) { dir_index = g_list_length (directories); directories = g_list_append (directories, g_strdup (subdir)); } else dir_index = 0xffff; } image = g_new0 (Image, 1); image->dir_index = dir_index; g_hash_table_insert (dir_hash, g_strdup (basename), image); } image->flags |= flags; maybe_cache_image_data (image, path); maybe_cache_icon_data (image, path); g_free (basename); } g_free (path); } g_list_free_full (list, g_free); g_dir_close (dir); /* Move dir into the big file hash */ g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files); g_hash_table_destroy (dir_hash); return directories; } typedef struct _HashNode HashNode; struct _HashNode { HashNode *next; char *name; GList *image_list; int offset; }; static guint icon_name_hash (gconstpointer key) { const signed char *p = key; guint32 h = *p; if (h) for (p += 1; *p != '\0'; p++) h = (h << 5) - h + *p; return h; } typedef struct { int size; HashNode **nodes; } HashContext; static gboolean convert_to_hash (gpointer key, gpointer value, gpointer user_data) { HashContext *context = user_data; guint hash; HashNode *node; hash = icon_name_hash (key) % context->size; node = g_new0 (HashNode, 1); node->next = NULL; node->name = key; node->image_list = value; if (context->nodes[hash] != NULL) node->next = context->nodes[hash]; context->nodes[hash] = node; return TRUE; } static GHashTable *string_pool = NULL; static int find_string (const char *n) { return GPOINTER_TO_INT (g_hash_table_lookup (string_pool, n)); } static void add_string (const char *n, int offset) { g_hash_table_insert (string_pool, (gpointer) n, GINT_TO_POINTER (offset)); } static gboolean write_string (FILE *cache, const char *n) { char *s; int i, l; l = ALIGN_VALUE (strlen (n) + 1, 4); s = g_malloc0 (l); strcpy (s, n); i = fwrite (s, l, 1, cache); g_free (s); return i == 1; } static gboolean write_card16 (FILE *cache, guint16 n) { int i; n = GUINT16_TO_BE (n); i = fwrite ((char *)&n, 2, 1, cache); return i == 1; } static gboolean write_card32 (FILE *cache, guint32 n) { int i; n = GUINT32_TO_BE (n); i = fwrite ((char *)&n, 4, 1, cache); return i == 1; } static gboolean write_image_data (FILE *cache, ImageData *image_data, int offset) { guint8 *s; guint len; int i; GdkPixdata *pixdata = &image_data->pixdata; /* Type 0 is GdkPixdata */ if (!write_card32 (cache, 0)) return FALSE; G_GNUC_BEGIN_IGNORE_DEPRECATIONS; s = gdk_pixdata_serialize (pixdata, &len); G_GNUC_END_IGNORE_DEPRECATIONS; if (!write_card32 (cache, len)) { g_free (s); return FALSE; } i = fwrite (s, len, 1, cache); g_free (s); return i == 1; } static gboolean write_icon_data (FILE *cache, IconData *icon_data, int offset) { int ofs = offset + 12; int j; int tmp, tmp2; if (icon_data->has_embedded_rect) { if (!write_card32 (cache, ofs)) return FALSE; ofs += 8; } else { if (!write_card32 (cache, 0)) return FALSE; } if (icon_data->n_attach_points > 0) { if (!write_card32 (cache, ofs)) return FALSE; ofs += 4 + 4 * icon_data->n_attach_points; } else { if (!write_card32 (cache, 0)) return FALSE; } if (icon_data->n_display_names > 0) { if (!write_card32 (cache, ofs)) return FALSE; } else { if (!write_card32 (cache, 0)) return FALSE; } if (icon_data->has_embedded_rect) { if (!write_card16 (cache, icon_data->x0) || !write_card16 (cache, icon_data->y0) || !write_card16 (cache, icon_data->x1) || !write_card16 (cache, icon_data->y1)) return FALSE; } if (icon_data->n_attach_points > 0) { if (!write_card32 (cache, icon_data->n_attach_points)) return FALSE; for (j = 0; j < 2 * icon_data->n_attach_points; j++) { if (!write_card16 (cache, icon_data->attach_points[j])) return FALSE; } } if (icon_data->n_display_names > 0) { if (!write_card32 (cache, icon_data->n_display_names)) return FALSE; ofs += 4 + 8 * icon_data->n_display_names; tmp = ofs; for (j = 0; j < 2 * icon_data->n_display_names; j++) { tmp2 = find_string (icon_data->display_names[j]); if (tmp2 == 0 || tmp2 == -1) { tmp2 = tmp; tmp += ALIGN_VALUE (strlen (icon_data->display_names[j]) + 1, 4); /* We're playing a little game with negative * offsets here to handle duplicate strings in * the array. */ add_string (icon_data->display_names[j], -tmp2); } else if (tmp2 < 0) { tmp2 = -tmp2; } if (!write_card32 (cache, tmp2)) return FALSE; } g_assert (ofs == ftell (cache)); for (j = 0; j < 2 * icon_data->n_display_names; j++) { tmp2 = find_string (icon_data->display_names[j]); g_assert (tmp2 != 0 && tmp2 != -1); if (tmp2 < 0) { tmp2 = -tmp2; g_assert (tmp2 == ftell (cache)); add_string (icon_data->display_names[j], tmp2); if (!write_string (cache, icon_data->display_names[j])) return FALSE; } } } return TRUE; } static gboolean write_header (FILE *cache, guint32 dir_list_offset) { return (write_card16 (cache, MAJOR_VERSION) && write_card16 (cache, MINOR_VERSION) && write_card32 (cache, HASH_OFFSET) && write_card32 (cache, dir_list_offset)); } static int get_image_meta_data_size (Image *image) { int i; /* The complication with storing the size in both * IconData and Image is necessary since we attribute * the size of the IconData only to the first Image * using it (at which time it is written out in the * cache). Later Images just refer to the written out * IconData via the offset. */ if (image->icon_data_size == 0) { if (image->icon_data && image->icon_data->size < 0) { IconData *data = image->icon_data; data->size = 0; if (data->has_embedded_rect || data->n_attach_points > 0 || data->n_display_names > 0) data->size += 12; if (data->has_embedded_rect) data->size += 8; if (data->n_attach_points > 0) data->size += 4 + data->n_attach_points * 4; if (data->n_display_names > 0) { data->size += 4 + 8 * data->n_display_names; for (i = 0; data->display_names[i]; i++) { int poolv; if ((poolv = find_string (data->display_names[i])) == 0) { data->size += ALIGN_VALUE (strlen (data->display_names[i]) + 1, 4); /* Adding the string to the pool with -1 * to indicate that it hasn't been written out * to the cache yet. We still need it in the * pool in case the same string occurs twice * during a get_single_node_size() calculation. */ add_string (data->display_names[i], -1); } } } image->icon_data_size = data->size; data->size = 0; } } g_assert (image->icon_data_size % 4 == 0); return image->icon_data_size; } static int get_image_pixel_data_size (Image *image) { /* The complication with storing the size in both * ImageData and Image is necessary since we attribute * the size of the ImageData only to the first Image * using it (at which time it is written out in the * cache). Later Images just refer to the written out * ImageData via the offset. */ if (image->pixel_data_size == 0) { if (image->image_data && image->image_data->has_pixdata) { image->pixel_data_size = image->image_data->size; image->image_data->size = 0; } } g_assert (image->pixel_data_size % 4 == 0); return image->pixel_data_size; } static int get_image_data_size (Image *image) { int len; len = 0; len += get_image_pixel_data_size (image); len += get_image_meta_data_size (image); /* Even if len is zero, we need to reserve space to * write the ImageData, unless this is an .svg without * .icon, in which case both image_data and icon_data * are NULL. */ if (len > 0 || image->image_data || image->icon_data) len += 8; return len; } static void get_single_node_size (HashNode *node, int *node_size, int *image_data_size) { GList *list; /* Node pointers */ *node_size = 12; /* Name */ if (find_string (node->name) == 0) { *node_size += ALIGN_VALUE (strlen (node->name) + 1, 4); add_string (node->name, -1); } /* Image list */ *node_size += 4 + g_list_length (node->image_list) * 8; /* Image data */ *image_data_size = 0; for (list = node->image_list; list; list = list->next) { Image *image = list->data; *image_data_size += get_image_data_size (image); } } static gboolean write_bucket (FILE *cache, HashNode *node, int *offset) { while (node != NULL) { int node_size, image_data_size; int next_offset, image_data_offset; int data_offset; int name_offset; int name_size; int image_list_offset; int i, len; GList *list; g_assert (*offset == ftell (cache)); node->offset = *offset; get_single_node_size (node, &node_size, &image_data_size); g_assert (node_size % 4 == 0); g_assert (image_data_size % 4 == 0); image_data_offset = *offset + node_size; next_offset = *offset + node_size + image_data_size; /* Chain offset */ if (node->next != NULL) { if (!write_card32 (cache, next_offset)) return FALSE; } else { if (!write_card32 (cache, 0xffffffff)) return FALSE; } name_size = 0; name_offset = find_string (node->name); if (name_offset <= 0) { name_offset = *offset + 12; name_size = ALIGN_VALUE (strlen (node->name) + 1, 4); add_string (node->name, name_offset); } if (!write_card32 (cache, name_offset)) return FALSE; image_list_offset = *offset + 12 + name_size; if (!write_card32 (cache, image_list_offset)) return FALSE; /* Icon name */ if (name_size > 0) { if (!write_string (cache, node->name)) return FALSE; } /* Image list */ len = g_list_length (node->image_list); if (!write_card32 (cache, len)) return FALSE; list = node->image_list; data_offset = image_data_offset; for (i = 0; i < len; i++) { Image *image = list->data; int image_size = get_image_data_size (image); /* Directory index */ if (!write_card16 (cache, image->dir_index)) return FALSE; /* Flags */ if (!write_card16 (cache, image->flags)) return FALSE; /* Image data offset */ if (image_size > 0) { if (!write_card32 (cache, data_offset)) return FALSE; data_offset += image_size; } else { if (!write_card32 (cache, 0)) return FALSE; } list = list->next; } /* Now write the image data */ list = node->image_list; for (i = 0; i < len; i++, list = list->next) { Image *image = list->data; int pixel_data_size = get_image_pixel_data_size (image); int meta_data_size = get_image_meta_data_size (image); if (get_image_data_size (image) == 0) continue; /* Pixel data */ if (pixel_data_size > 0) { image->image_data->offset = image_data_offset + 8; if (!write_card32 (cache, image->image_data->offset)) return FALSE; } else { if (!write_card32 (cache, (guint32) (image->image_data ? image->image_data->offset : 0))) return FALSE; } if (meta_data_size > 0) { image->icon_data->offset = image_data_offset + pixel_data_size + 8; if (!write_card32 (cache, image->icon_data->offset)) return FALSE; } else { if (!write_card32 (cache, image->icon_data ? image->icon_data->offset : 0)) return FALSE; } if (pixel_data_size > 0) { if (!write_image_data (cache, image->image_data, image->image_data->offset)) return FALSE; } if (meta_data_size > 0) { if (!write_icon_data (cache, image->icon_data, image->icon_data->offset)) return FALSE; } image_data_offset += pixel_data_size + meta_data_size + 8; } *offset = next_offset; node = node->next; } return TRUE; } static gboolean write_hash_table (FILE *cache, HashContext *context, int *new_offset) { int offset = HASH_OFFSET; int node_offset; int i; if (!(write_card32 (cache, context->size))) return FALSE; offset += 4; node_offset = offset + context->size * 4; /* Just write zeros here, we will rewrite this later */ for (i = 0; i < context->size; i++) { if (!write_card32 (cache, 0)) return FALSE; } /* Now write the buckets */ for (i = 0; i < context->size; i++) { if (!context->nodes[i]) continue; g_assert (node_offset % 4 == 0); if (!write_bucket (cache, context->nodes[i], &node_offset)) return FALSE; } *new_offset = node_offset; /* Now write out the bucket offsets */ fseek (cache, offset, SEEK_SET); for (i = 0; i < context->size; i++) { if (context->nodes[i] != NULL) node_offset = context->nodes[i]->offset; else node_offset = 0xffffffff; if (!write_card32 (cache, node_offset)) return FALSE; } fseek (cache, 0, SEEK_END); return TRUE; } static gboolean write_dir_index (FILE *cache, int offset, GList *directories) { int n_dirs; GList *d; char *dir; int tmp, tmp2; n_dirs = g_list_length (directories); if (!write_card32 (cache, n_dirs)) return FALSE; offset += 4 + n_dirs * 4; tmp = offset; for (d = directories; d; d = d->next) { dir = d->data; tmp2 = find_string (dir); if (tmp2 == 0 || tmp2 == -1) { tmp2 = tmp; tmp += ALIGN_VALUE (strlen (dir) + 1, 4); /* We're playing a little game with negative * offsets here to handle duplicate strings in * the array, even though that should not * really happen for the directory index. */ add_string (dir, -tmp2); } else if (tmp2 < 0) { tmp2 = -tmp2; } if (!write_card32 (cache, tmp2)) return FALSE; } g_assert (offset == ftell (cache)); for (d = directories; d; d = d->next) { dir = d->data; tmp2 = find_string (dir); g_assert (tmp2 != 0 && tmp2 != -1); if (tmp2 < 0) { tmp2 = -tmp2; g_assert (tmp2 == ftell (cache)); add_string (dir, tmp2); if (!write_string (cache, dir)) return FALSE; } } return TRUE; } static gboolean write_file (FILE *cache, GHashTable *files, GList *directories) { HashContext context; int new_offset; /* Convert the hash table into something looking a bit more * like what we want to write to disk. */ context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3); context.nodes = g_new0 (HashNode *, context.size); g_hash_table_foreach_remove (files, convert_to_hash, &context); /* Now write the file */ /* We write 0 as the directory list offset and go * back and change it later */ if (!write_header (cache, 0)) { g_printerr (_("Failed to write header\n")); return FALSE; } if (!write_hash_table (cache, &context, &new_offset)) { g_printerr (_("Failed to write hash table\n")); return FALSE; } if (!write_dir_index (cache, new_offset, directories)) { g_printerr (_("Failed to write folder index\n")); return FALSE; } rewind (cache); if (!write_header (cache, new_offset)) { g_printerr (_("Failed to rewrite header\n")); return FALSE; } return TRUE; } static gboolean validate_file (const char *file) { GMappedFile *map; CacheInfo info; map = g_mapped_file_new (file, FALSE, NULL); if (!map) return FALSE; info.cache = g_mapped_file_get_contents (map); info.cache_size = g_mapped_file_get_length (map); info.n_directories = 0; info.flags = CHECK_OFFSETS|CHECK_STRINGS|CHECK_PIXBUFS; if (!gtk_icon_cache_validate (&info)) { g_mapped_file_unref (map); return FALSE; } g_mapped_file_unref (map); return TRUE; } /** * safe_fclose: * @f: A FILE* stream, must have underlying fd * * Unix defaults for data preservation after system crash * are unspecified, and many systems will eat your data * in this situation unless you explicitly fsync(). * * Returns: %TRUE on success, %FALSE on failure, and will set errno() */ static gboolean safe_fclose (FILE *f) { int fd = fileno (f); g_assert (fd >= 0); if (fflush (f) == EOF) return FALSE; #ifndef G_OS_WIN32 if (fsync (fd) < 0) return FALSE; #endif if (fclose (f) == EOF) return FALSE; return TRUE; } static void build_cache (const char *path) { char *cache_path, *tmp_cache_path; #ifdef G_OS_WIN32 char *bak_cache_path = NULL; #endif GHashTable *files; FILE *cache; GStatBuf path_stat, cache_stat; struct utimbuf utime_buf; GList *directories = NULL; int fd; int retry_count = 0; #ifndef G_OS_WIN32 mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; #else int mode = _S_IWRITE | _S_IREAD; #endif #ifndef _O_BINARY #define _O_BINARY 0 #endif tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL); cache_path = g_build_filename (path, CACHE_NAME, NULL); opentmp: if ((fd = g_open (tmp_cache_path, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | _O_BINARY, mode)) == -1) { if (retry_count == 0) { retry_count++; g_remove (tmp_cache_path); goto opentmp; } g_printerr (_("Failed to open file %s : %s\n"), tmp_cache_path, g_strerror (errno)); exit (1); } cache = fdopen (fd, "wb"); if (!cache) { g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno)); exit (1); } files = g_hash_table_new (g_str_hash, g_str_equal); image_data_hash = g_hash_table_new (g_str_hash, g_str_equal); icon_data_hash = g_hash_table_new (g_str_hash, g_str_equal); string_pool = g_hash_table_new (g_str_hash, g_str_equal); directories = scan_directory (path, NULL, files, NULL, 0); if (g_hash_table_size (files) == 0) { /* Empty table, just close and remove the file */ fclose (cache); g_unlink (tmp_cache_path); g_unlink (cache_path); exit (0); } /* FIXME: Handle failure */ if (!write_file (cache, files, directories)) { g_unlink (tmp_cache_path); exit (1); } if (!safe_fclose (cache)) { g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno)); g_unlink (tmp_cache_path); exit (1); } cache = NULL; g_list_free_full (directories, g_free); if (!validate_file (tmp_cache_path)) { g_printerr (_("The generated cache was invalid.\n")); /*g_unlink (tmp_cache_path);*/ exit (1); } #ifdef G_OS_WIN32 if (g_file_test (cache_path, G_FILE_TEST_EXISTS)) { bak_cache_path = g_strconcat (cache_path, ".bak", NULL); g_unlink (bak_cache_path); if (g_rename (cache_path, bak_cache_path) == -1) { int errsv = errno; g_printerr (_("Could not rename %s to %s: %s, removing %s then.\n"), cache_path, bak_cache_path, g_strerror (errsv), cache_path); g_unlink (cache_path); bak_cache_path = NULL; } } #endif if (g_rename (tmp_cache_path, cache_path) == -1) { int errsv = errno; g_printerr (_("Could not rename %s to %s: %s\n"), tmp_cache_path, cache_path, g_strerror (errsv)); g_unlink (tmp_cache_path); #ifdef G_OS_WIN32 if (bak_cache_path != NULL) if (g_rename (bak_cache_path, cache_path) == -1) { errsv = errno; g_printerr (_("Could not rename %s back to %s: %s.\n"), bak_cache_path, cache_path, g_strerror (errsv)); } #endif exit (1); } #ifdef G_OS_WIN32 if (bak_cache_path != NULL) g_unlink (bak_cache_path); #endif /* Update time */ /* FIXME: What do do if an error occurs here? */ if (g_stat (path, &path_stat) < 0 || g_stat (cache_path, &cache_stat)) exit (1); utime_buf.actime = path_stat.st_atime; utime_buf.modtime = cache_stat.st_mtime; #if GLIB_CHECK_VERSION (2, 17, 1) g_utime (path, &utime_buf); #else utime (path, &utime_buf); #endif if (!quiet) g_printerr (_("Cache file created successfully.\n")); } static void write_csource (const char *path) { char *cache_path; char *data; gsize len; int i; cache_path = g_build_filename (path, CACHE_NAME, NULL); if (!g_file_get_contents (cache_path, &data, &len, NULL)) exit (1); g_printf ("#ifdef __SUNPRO_C\n"); g_printf ("#pragma align 4 (%s)\n", var_name); g_printf ("#endif\n"); g_printf ("#ifdef __GNUC__\n"); g_printf ("static const guint8 %s[] __attribute__ ((__aligned__ (4))) = \n", var_name); g_printf ("#else\n"); g_printf ("static const guint8 %s[] = \n", var_name); g_printf ("#endif\n"); g_printf ("{\n"); for (i = 0; i < len - 1; i++) { if (i %12 == 0) g_printf (" "); g_printf ("0x%02x, ", (guint8)data[i]); if (i % 12 == 11) g_printf ("\n"); } g_printf ("0x%02x\n};\n", (guint8)data[i]); } static GOptionEntry args[] = { { "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, N_("Overwrite an existing cache, even if up to date"), NULL }, { "ignore-theme-index", 't', 0, G_OPTION_ARG_NONE, &ignore_theme_index, N_("Don’t check for the existence of index.theme"), NULL }, { "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Don’t include image data in the cache"), NULL }, { "include-image-data", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &index_only, N_("Include image data in the cache"), NULL }, { "source", 'c', 0, G_OPTION_ARG_STRING, &var_name, N_("Output a C header file"), "NAME" }, { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, N_("Turn off verbose output"), NULL }, { "validate", 'v', 0, G_OPTION_ARG_NONE, &validate, N_("Validate existing icon cache"), NULL }, { NULL } }; static void printerr_handler (const char *string) { const char *charset; fputs (g_get_prgname (), stderr); fputs (": ", stderr); if (g_get_charset (&charset)) fputs (string, stderr); /* charset is UTF-8 already */ else { char *result; result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, NULL); if (result) { fputs (result, stderr); g_free (result); } fflush (stderr); } } int main (int argc, char **argv) { char *path; GOptionContext *context; if (argc < 2) return 0; g_set_printerr_handler (printerr_handler); setlocale (LC_ALL, ""); #ifdef ENABLE_NLS bindtextdomain (GETTEXT_PACKAGE, GTK_LOCALEDIR); #ifdef HAVE_BIND_TEXTDOMAIN_CODESET bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); #endif #endif context = g_option_context_new ("ICONPATH"); g_option_context_add_main_entries (context, args, GETTEXT_PACKAGE); g_option_context_parse (context, &argc, &argv, NULL); path = argv[1]; #ifdef G_OS_WIN32 path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL); #endif if (validate) { char *file = g_build_filename (path, CACHE_NAME, NULL); if (!g_file_test (file, G_FILE_TEST_IS_REGULAR)) { if (!quiet) g_printerr (_("File not found: %s\n"), file); exit (1); } if (!validate_file (file)) { if (!quiet) g_printerr (_("Not a valid icon cache: %s\n"), file); exit (1); } else { exit (0); } } if (!ignore_theme_index && !has_theme_index (path)) { if (path) { g_printerr (_("No theme index file.\n")); } else { g_printerr (_("No theme index file in “%s”.\n" "If you really want to create an icon cache here, use --ignore-theme-index.\n"), path); } return 1; } if (!force_update && is_cache_up_to_date (path)) return 0; replace_backslashes_with_slashes (path); build_cache (path); if (strcmp (var_name, "-") != 0) write_csource (path); return 0; }