summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBastien Nocera <hadess@hadess.net>2020-02-08 12:51:24 +0100
committerBastien Nocera <hadess@hadess.net>2020-02-08 13:47:31 +0100
commit5d56ce04729ed3a2d8794f1d46586423cde23fe2 (patch)
treecb5f49ade35001562eb2251395957af10095bf1b /src
parent071644f8db4850b93934d0939500fc2b4df9eeb1 (diff)
downloadshared-mime-info-5d56ce04729ed3a2d8794f1d46586423cde23fe2.tar.gz
build: Move source code to src/
Diffstat (limited to 'src')
-rw-r--r--src/test-subclassing.c47
-rw-r--r--src/test-tree-magic.c695
-rw-r--r--src/update-mime-database.c3981
3 files changed, 4723 insertions, 0 deletions
diff --git a/src/test-subclassing.c b/src/test-subclassing.c
new file mode 100644
index 00000000..dd099e44
--- /dev/null
+++ b/src/test-subclassing.c
@@ -0,0 +1,47 @@
+#include <libxml/tree.h>
+#include <stdio.h>
+#include <string.h>
+
+int
+main (int argc,
+ char **argv)
+{
+ xmlDocPtr doc;
+ xmlNodePtr node;
+
+ doc = xmlReadFile ("freedesktop.org.xml", NULL, 0);
+ if (doc == NULL) {
+ fprintf (stderr, "Reading \"freedesktop.org.xml\" failed. Aborting.\n");
+ return 1;
+ }
+
+ if ((node = xmlDocGetRootElement (doc)) == NULL) {
+ fprintf (stderr, "\"freedesktop.org.xml\" has no root node. Aborting.\n");
+ return 1;
+ }
+
+ for (node = node->children; node != NULL; node = node->next) {
+ if (!strcmp ((char *) node->name, "mime-type")) {
+ xmlNodePtr p;
+ xmlChar *prop;
+
+ for (p = node->children; p != NULL; p = p->next) {
+ if (!strcmp ((char *) p->name, "sub-class-of")) {
+ break;
+ }
+ }
+
+ if (p != NULL) {
+ /* got sub-class-of node */
+ continue;
+ }
+
+ prop = xmlGetProp (node, (xmlChar *) "type");
+ printf ("%s\n", prop);
+ xmlFree (prop);
+ }
+ }
+
+ xmlFreeDoc (doc);
+ return 0;
+}
diff --git a/src/test-tree-magic.c b/src/test-tree-magic.c
new file mode 100644
index 00000000..0ff949a1
--- /dev/null
+++ b/src/test-tree-magic.c
@@ -0,0 +1,695 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * Author: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+
+typedef struct
+{
+ gchar *path;
+ GFileType type;
+ guint match_case : 1;
+ guint executable : 1;
+ guint non_empty : 1;
+ guint on_disc : 1;
+ gchar *mimetype;
+ GList *matches;
+} TreeMatchlet;
+
+typedef struct
+{
+ gchar *contenttype;
+ gint priority;
+ GList *matches;
+} TreeMatch;
+
+static GList *tree_matches = NULL;
+
+static void
+tree_matchlet_free (TreeMatchlet *matchlet)
+{
+ g_list_foreach (matchlet->matches, (GFunc)tree_matchlet_free, NULL);
+ g_list_free (matchlet->matches);
+ g_free (matchlet->path);
+ g_free (matchlet->mimetype);
+ g_slice_free (TreeMatchlet, matchlet);
+}
+
+static void
+tree_match_free (TreeMatch *match)
+{
+ g_list_foreach (match->matches, (GFunc)tree_matchlet_free, NULL);
+ g_list_free (match->matches);
+ g_free (match->contenttype);
+ g_slice_free (TreeMatch, match);
+}
+
+static void
+tree_magic_shutdown (void)
+{
+ g_list_foreach (tree_matches, (GFunc)tree_match_free, NULL);
+ g_list_free (tree_matches);
+ tree_matches = NULL;
+}
+
+static TreeMatch *
+parse_header (gchar *line)
+{
+ gint len;
+ gchar *s;
+ TreeMatch *match;
+
+ len = strlen (line);
+
+ if (line[0] != '[' || line[len - 1] != ']')
+ return NULL;
+
+ line[len - 1] = 0;
+ s = strchr (line, ':');
+
+ match = g_slice_new0 (TreeMatch);
+ match->priority = atoi (line + 1);
+ match->contenttype = g_strdup (s + 1);
+
+ return match;
+}
+
+static TreeMatchlet *
+parse_match_line (gchar *line,
+ gint *depth)
+{
+ gchar *s, *p;
+ TreeMatchlet *matchlet;
+ gchar **parts;
+ gint i;
+
+ matchlet = g_slice_new0 (TreeMatchlet);
+
+ if (line[0] == '>') {
+ *depth = 0;
+ s = line;
+ }
+ else {
+ *depth = atoi (line);
+ s = strchr (line, '>');
+ }
+ s += 2;
+ p = strchr (s, '"');
+ *p = 0;
+
+ matchlet->path = g_strdup (s);
+ s = p + 1;
+ parts = g_strsplit (s, ",", 0);
+ if (strcmp (parts[0], "=file") == 0)
+ matchlet->type = G_FILE_TYPE_REGULAR;
+ else if (strcmp (parts[0], "=directory") == 0)
+ matchlet->type = G_FILE_TYPE_DIRECTORY;
+ else if (strcmp (parts[0], "=link") == 0)
+ matchlet->type = G_FILE_TYPE_SYMBOLIC_LINK;
+ else
+ matchlet->type = G_FILE_TYPE_UNKNOWN;
+ for (i = 1; parts[i]; i++) {
+ if (strcmp (parts[i], "executable") == 0)
+ matchlet->executable = 1;
+ else if (strcmp (parts[i], "match-case") == 0)
+ matchlet->match_case = 1;
+ else if (strcmp (parts[i], "non-empty") == 0)
+ matchlet->non_empty = 1;
+ else if (strcmp (parts[i], "on-disc") == 0)
+ matchlet->on_disc = 1;
+ else
+ matchlet->mimetype = g_strdup (parts[i]);
+ }
+
+ g_strfreev (parts);
+
+ return matchlet;
+}
+
+static gint
+cmp_match (gconstpointer a, gconstpointer b)
+{
+ const TreeMatch *aa = (const TreeMatch *)a;
+ const TreeMatch *bb = (const TreeMatch *)b;
+
+ return bb->priority - aa->priority;
+}
+
+static void
+insert_match (TreeMatch *match)
+{
+ tree_matches = g_list_insert_sorted (tree_matches, match, cmp_match);
+}
+
+static void
+insert_matchlet (TreeMatch *match,
+ TreeMatchlet *matchlet,
+ gint depth)
+{
+ g_return_if_fail (match != NULL);
+
+ if (depth == 0)
+ match->matches = g_list_append (match->matches, matchlet);
+ else {
+ GList *last;
+ TreeMatchlet *m;
+
+ last = g_list_last (match->matches);
+ if (!last) {
+ tree_matchlet_free (matchlet);
+ g_warning ("can't insert matchlet at depth %d", depth);
+ return;
+ }
+
+ m = (TreeMatchlet *) last->data;
+ depth--;
+ while (depth > 0) {
+ last = g_list_last (m->matches);
+ if (!last) {
+ tree_matchlet_free (matchlet);
+ g_warning ("can't insert matchlet at depth %d", depth);
+ return;
+ }
+
+ m = (TreeMatchlet *) last->data;
+ depth--;
+ }
+ m->matches = g_list_append (m->matches, matchlet);
+ }
+}
+
+static void
+read_tree_magic_from_directory (const gchar *prefix)
+{
+ gchar *filename;
+ gchar *text;
+ gsize len;
+ gint i;
+ TreeMatch *match;
+ TreeMatchlet *matchlet;
+ gint depth;
+
+ filename = g_build_filename (prefix, "mime", "treemagic", NULL);
+ match = NULL;
+
+ if (g_file_get_contents (filename, &text, &len, NULL)) {
+ if (strcmp (text, "MIME-TreeMagic") == 0) {
+ gchar **lines;
+
+ lines = g_strsplit (text + strlen ("MIME-TreeMagic") + 2, "\n", 0);
+ for (i = 0; lines[i] && lines[i][0]; i++) {
+ if (lines[i][0] == '[') {
+ match = parse_header (lines[i]);
+ insert_match (match);
+ }
+ else {
+ matchlet = parse_match_line (lines[i], &depth);
+ insert_matchlet (match, matchlet, depth);
+ }
+ }
+ g_strfreev (lines);
+ }
+ else
+ g_warning ("%s: header not found, skipping\n", filename);
+
+ g_free (text);
+ }
+
+ g_free (filename);
+}
+
+typedef struct
+{
+ gchar *path;
+ gint depth;
+ gboolean ignore_case;
+ gchar **components;
+ gchar **case_components;
+ GFileEnumerator **enumerators;
+ GFile **children;
+} Enumerator;
+
+static gboolean
+component_match (Enumerator *e,
+ gint depth,
+ const gchar *name)
+{
+ gchar *case_folded, *key;
+ gboolean found;
+
+ if (strcmp (name, e->components[depth]) == 0)
+ return TRUE;
+
+ if (!e->ignore_case)
+ return FALSE;
+
+ case_folded = g_utf8_casefold (name, -1);
+ key = g_utf8_collate_key (case_folded, -1);
+
+ found = strcmp (key, e->case_components[depth]) == 0;
+
+ g_free (case_folded);
+ g_free (key);
+
+ return found;
+}
+
+static GFile *
+next_match_recurse (Enumerator *e,
+ gint depth)
+{
+ GFile *file;
+ GFileInfo *info;
+ const gchar *name;
+
+ while (TRUE) {
+ if (e->enumerators[depth] == NULL) {
+ if (depth > 0) {
+ file = next_match_recurse (e, depth - 1);
+ if (file) {
+ e->children[depth] = file;
+ e->enumerators[depth] = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ }
+ }
+ if (e->enumerators[depth] == NULL)
+ return NULL;
+ }
+
+ while ((info = g_file_enumerator_next_file (e->enumerators[depth], NULL, NULL))) {
+ name = g_file_info_get_name (info);
+ if (component_match (e, depth, name)) {
+ file = g_file_get_child (e->children[depth], name);
+ g_object_unref (info);
+ return file;
+ }
+ g_object_unref (info);
+ }
+
+ g_object_unref (e->enumerators[depth]);
+ e->enumerators[depth] = NULL;
+ g_object_unref (e->children[depth]);
+ e->children[depth] = NULL;
+ }
+}
+
+static GFile *
+enumerator_next (Enumerator *e)
+{
+ return next_match_recurse (e, e->depth - 1);
+}
+
+static Enumerator *
+enumerator_new (GFile *root,
+ const char *path,
+ gboolean ignore_case)
+{
+ Enumerator *e;
+ gint i;
+ gchar *case_folded;
+
+ e = g_new0 (Enumerator, 1);
+ e->path = g_strdup (path);
+ e->ignore_case = ignore_case;
+
+ e->components = g_strsplit (e->path, G_DIR_SEPARATOR_S, -1);
+ e->depth = g_strv_length (e->components);
+ if (e->ignore_case) {
+ e->case_components = g_new0 (char *, e->depth + 1);
+ for (i = 0; e->components[i]; i++) {
+ case_folded = g_utf8_casefold (e->components[i], -1);
+ e->case_components[i] = g_utf8_collate_key (case_folded, -1);
+ g_free (case_folded);
+ }
+ }
+
+ e->children = g_new0 (GFile *, e->depth);
+ e->children[0] = g_object_ref (root);
+ e->enumerators = g_new0 (GFileEnumerator *, e->depth);
+ e->enumerators[0] = g_file_enumerate_children (root,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ return e;
+}
+
+static void
+enumerator_free (Enumerator *e)
+{
+ gint i;
+
+ for (i = 0; i < e->depth; i++) {
+ if (e->enumerators[i])
+ g_object_unref (e->enumerators[i]);
+ if (e->children[i])
+ g_object_unref (e->children[i]);
+ }
+
+ g_free (e->enumerators);
+ g_free (e->children);
+ g_strfreev (e->components);
+ if (e->case_components)
+ g_strfreev (e->case_components);
+ g_free (e->path);
+ g_free (e);
+}
+
+static gboolean
+matchlet_match (TreeMatchlet *matchlet,
+ GFile *root)
+{
+ GFile *file;
+ GFileInfo *info;
+ gboolean result;
+ const gchar *attrs;
+ Enumerator *e;
+ GList *l;
+
+ e = enumerator_new (root, matchlet->path, !matchlet->match_case);
+
+ do {
+ file = enumerator_next (e);
+ if (!file) {
+ enumerator_free (e);
+ return FALSE;
+ }
+
+ if (matchlet->mimetype)
+ attrs = G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
+ else
+ attrs = G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE;
+ info = g_file_query_info (file,
+ attrs,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (info) {
+ result = TRUE;
+
+ if (matchlet->type != G_FILE_TYPE_UNKNOWN &&
+ g_file_info_get_file_type (info) != matchlet->type)
+ result = FALSE;
+
+ if (matchlet->executable &&
+ !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+ result = FALSE;
+ }
+ else
+ result = FALSE;
+
+ if (result && matchlet->non_empty) {
+ GFileEnumerator *child_enum;
+ GFileInfo *child_info;
+
+ child_enum = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (child_enum) {
+ child_info = g_file_enumerator_next_file (child_enum, NULL, NULL);
+ if (child_info)
+ g_object_unref (child_info);
+ else
+ result = FALSE;
+ g_object_unref (child_enum);
+ }
+ else
+ result = FALSE;
+ }
+
+ if (result && matchlet->mimetype) {
+ if (strcmp (matchlet->mimetype, g_file_info_get_content_type (info)) != 0)
+ result = FALSE;
+ }
+
+ g_object_unref (info);
+ g_object_unref (file);
+
+ }
+ while (!result);
+
+ enumerator_free (e);
+
+ if (!matchlet->matches)
+ return TRUE;
+
+ for (l = matchlet->matches; l; l = l->next) {
+ TreeMatchlet *submatchlet;
+
+ submatchlet = l->data;
+ if (matchlet_match (submatchlet, root))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+match_match (TreeMatch *match,
+ GFile *root,
+ GPtrArray *types)
+{
+ GList *l;
+
+ for (l = match->matches; l; l = l->next) {
+ TreeMatchlet *matchlet = l->data;
+ if (matchlet_match (matchlet, root)) {
+ g_ptr_array_add (types, g_strdup (match->contenttype));
+ break;
+ }
+ }
+}
+
+static void
+tree_magic_init (void)
+{
+ static gboolean initialized = FALSE;
+ const gchar *dir;
+ const gchar * const * dirs;
+ int i;
+
+ if (!initialized) {
+ initialized = TRUE;
+
+ dir = g_get_user_data_dir ();
+
+ read_tree_magic_from_directory (dir);
+
+ dirs = g_get_system_data_dirs ();
+ for (i = 0; dirs[i]; i++)
+ read_tree_magic_from_directory (dirs[i]);
+ }
+}
+
+static GPtrArray *
+sniff_content_type (GFile *root)
+{
+ GPtrArray *types;
+ GList *l;
+
+ /* TODO: monitor and reload */
+ tree_magic_init ();
+
+ types = g_ptr_array_new ();
+
+ for (l = tree_matches; l; l = l->next) {
+ TreeMatch *match = l->data;
+ match_match (match, root, types);
+ }
+
+ return types;
+}
+
+static gboolean
+handle_one_line (const char *line,
+ const char *cwd)
+{
+ GFile *file;
+ GPtrArray *array;
+ gboolean retval = TRUE;
+ gboolean supposed_to_fail = FALSE;
+ char **items, **mimetypes;
+ char *filename;
+ guint i;
+
+ if (strncmp (line, "x ", 2) == 0) {
+ supposed_to_fail = TRUE;
+ items = g_strsplit (line + 2, " ", 2);
+ } else {
+ items = g_strsplit (line, " ", 2);
+ }
+
+ filename = items[0];
+ mimetypes = g_strsplit (items[1], " ", -1);
+
+ file = g_file_new_for_commandline_arg_and_cwd (filename, cwd);
+
+ array = sniff_content_type (file);
+ if (array->len == 0) {
+ if (supposed_to_fail == FALSE) {
+ g_warning ("Tree %s didn't match %s (found nothing)",
+ filename, items[1]);
+ retval = FALSE;
+ } else {
+ g_message ("Tree %s failed to match %s (expected)",
+ filename, items[1]);
+ retval = TRUE;
+ }
+ goto bail;
+ }
+
+ for (i = 0; i < array->len; i++) {
+ char *found;
+ gboolean matched;
+ guint j;
+
+ matched = FALSE;
+ found = (char *) array->pdata[i];
+ for (j = 0; mimetypes[j] != NULL; j++) {
+ if (strcmp (mimetypes[j], found) == 0) {
+ matched = TRUE;
+ break;
+ }
+ }
+ if (matched == FALSE && supposed_to_fail == FALSE) {
+ g_warning ("Tree %s didn't match %s (found %s)",
+ filename, items[1], found);
+ retval = FALSE;
+ goto bail;
+ } else if (matched == FALSE) {
+ g_message ("Tree %s failed to match %s (expected)",
+ filename, items[1]);
+ } else {
+ g_message ("Tree %s looks good (found %s)",
+ filename, found);
+ }
+ }
+
+bail:
+ g_strfreev (items);
+ g_ptr_array_free (array, TRUE);
+ g_strfreev (mimetypes);
+
+ return retval;
+}
+
+static const char *
+type_to_path (GFileType type)
+{
+ switch (type) {
+ case G_FILE_TYPE_REGULAR:
+ return "Regular";
+ case G_FILE_TYPE_DIRECTORY:
+ return "Directory";
+ case G_FILE_TYPE_SYMBOLIC_LINK:
+ return "Symbolic link";
+ default:
+ return "Unknown";
+ }
+}
+
+static void
+print_matchlet (TreeMatchlet *matchlet, guint depth)
+{
+ GList *l;
+ guint i;
+
+ for (i = depth + 1; i != 0; i--)
+ g_print ("\t");
+ g_print ("%s (type=%s)\n", matchlet->path, type_to_path (matchlet->type));
+ for (l = matchlet->matches ; l != NULL; l = l->next)
+ print_matchlet (l->data, depth + 1);
+}
+
+int main (int argc, char **argv)
+{
+ GError *error = NULL;
+ char *content, **lines, *cwd;
+ guint i;
+
+#if GLIB_CHECK_VERSION(2,36,0)
+#else
+ g_type_init ();
+#endif
+
+ if (argc != 2) {
+ g_print ("Usage: %s [file]\n", argv[0]);
+ g_print ("Where file contains, on each line, a directory path,\n"
+ "followed by one or more content types\n");
+ g_print ("Lines starting with '#' are ignored\n");
+ g_print ("Examples:\n");
+ g_print ("\t#Supposed to work\n");
+ g_print ("\ttests/image-dcf x-content/image-dcf\n");
+ g_print ("\t# Supposed to fail\n");
+ g_print ("\tx tests/ x-content/image-dcf\n");
+ return 1;
+ }
+
+ if (strcmp (argv[1], "-d") == 0) {
+ GList *l;
+
+ tree_magic_init ();
+ for (l = tree_matches ; l != NULL; l = l->next) {
+ GList *k;
+ TreeMatch *match = (TreeMatch *) l->data;
+ g_print ("Type: %s\n", match->contenttype);
+ for (k = match->matches ; k != NULL; k = k->next) {
+ TreeMatchlet *matchlet = (TreeMatchlet *) k->data;
+ print_matchlet (matchlet, 0);
+ }
+ }
+ return 0;
+ }
+
+ if (g_file_get_contents (argv[1], &content, NULL, &error) == FALSE) {
+ g_warning ("Failed to load %s: %s", argv[1], error->message);
+ return 1;
+ }
+ lines = g_strsplit (content, "\n", -1);
+ g_free (content);
+
+ cwd = g_path_get_dirname (argv[1]);
+ for (i = 0; lines[i] != NULL; i++) {
+ if (*lines[i] == '\0' || *lines[i] == '#')
+ continue;
+ if (handle_one_line (lines[i], cwd) == FALSE)
+ return 1;
+ }
+ g_free (cwd);
+
+ tree_magic_shutdown ();
+
+ return 0;
+}
+
diff --git a/src/update-mime-database.c b/src/update-mime-database.c
new file mode 100644
index 00000000..d48e69a9
--- /dev/null
+++ b/src/update-mime-database.c
@@ -0,0 +1,3981 @@
+#include <config.h>
+
+#define N_(x) x
+#define _(x) (x)
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+#include <errno.h>
+#include <dirent.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#define XML_NS XML_XML_NAMESPACE
+#define XMLNS_NS "http://www.w3.org/2000/xmlns/"
+#define FREE_NS (xmlChar *)"http://www.freedesktop.org/standards/shared-mime-info"
+
+#define COPYING \
+ N_("Copyright (C) 2003 Thomas Leonard.\n" \
+ "update-mime-database comes with ABSOLUTELY NO WARRANTY,\n" \
+ "to the extent permitted by law.\n" \
+ "You may redistribute copies of update-mime-database\n" \
+ "under the terms of the GNU General Public License.\n" \
+ "For more information about these matters, " \
+ "see the file named COPYING.\n")
+
+#define MIME_ERROR g_quark_from_static_string("mime-error-quark")
+
+#define NOGLOBS "__NOGLOBS__"
+#define NOMAGIC "__NOMAGIC__"
+
+#ifndef PATH_SEPARATOR
+# ifdef _WIN32
+# define PATH_SEPARATOR ";"
+# else
+# define PATH_SEPARATOR ":"
+# endif
+#endif
+
+/* This is the list of directories to scan when finding old type files to
+ * delete. It is also used to warn about invalid MIME types.
+ */
+const char *media_types[] = {
+ "all",
+ "uri",
+ "print",
+ "text",
+ "application",
+ "image",
+ "audio",
+ "inode",
+ "video",
+ "message",
+ "model",
+ "multipart",
+ "x-content",
+ "x-epoc",
+ "x-scheme-handler",
+ "font",
+};
+
+/* Represents a MIME type */
+typedef struct _Type Type;
+
+/* A parsed <magic> element */
+typedef struct _Magic Magic;
+
+/* A parsed <match> element */
+typedef struct _Match Match;
+
+/* A parsed <treemagic> element */
+typedef struct _TreeMagic TreeMagic;
+
+/* A parsed <treematch> element */
+typedef struct _TreeMatch TreeMatch;
+
+/* A parsed <glob> element */
+typedef struct _Glob Glob;
+
+struct _Type {
+ char *media;
+ char *subtype;
+
+ /* Contains xmlNodes for elements that are being copied to the output.
+ * That is, <comment>, <sub-class-of> and <alias> nodes, and anything
+ * with an unknown namespace.
+ */
+ xmlDoc *output;
+};
+
+struct _Glob {
+ int weight;
+ char *pattern;
+ Type *type;
+ gboolean noglob;
+ gboolean case_sensitive;
+};
+
+struct _Magic {
+ int priority;
+ Type *type;
+ GList *matches;
+ gboolean nomagic;
+};
+
+struct _Match {
+ long range_start;
+ int range_length;
+ char word_size;
+ int data_length;
+ char *data;
+ char *mask;
+ GList *matches;
+};
+
+struct _TreeMagic {
+ int priority;
+ Type *type;
+ GList *matches;
+};
+
+struct _TreeMatch {
+ char *path;
+ gboolean match_case;
+ gboolean executable;
+ gboolean non_empty;
+ gint type;
+ char *mimetype;
+
+ GList *matches;
+};
+
+/* Maps MIME type names to Types */
+static GHashTable *types = NULL;
+
+/* Maps "namespaceURI localName" strings to Types */
+static GHashTable *namespace_hash = NULL;
+
+/* Maps glob patterns to Types */
+static GHashTable *globs_hash = NULL;
+
+/* 'magic' nodes */
+static GPtrArray *magic_array = NULL;
+
+/* 'treemagic' nodes */
+static GPtrArray *tree_magic_array = NULL;
+
+/* Maps MIME type names to superclass names */
+static GHashTable *subclass_hash = NULL;
+
+/* Maps aliases to Types */
+static GHashTable *alias_hash = NULL;
+
+/* Maps MIME type names to icon names */
+static GHashTable *icon_hash = NULL;
+
+/* Maps MIME type names to icon names */
+static GHashTable *generic_icon_hash = NULL;
+
+/* Lists enabled log levels */
+static GLogLevelFlags enabled_log_levels = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING;
+
+/* Static prototypes */
+static Magic *magic_new(xmlNode *node, Type *type, GError **error);
+static Match *match_new(void);
+
+static TreeMagic *tree_magic_new(xmlNode *node, Type *type, GError **error);
+
+static void g_log_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer unused_data)
+{
+ if (log_level & enabled_log_levels) {
+ g_printf("%s\n", message);
+ }
+}
+
+static void
+fatal_gerror (GError *error) G_GNUC_NORETURN;
+
+static void
+fatal_gerror (GError *error)
+{
+ g_assert(error != NULL);
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ exit (EXIT_FAILURE);
+}
+
+static void usage(const char *name)
+{
+ g_fprintf(stderr, _("Usage: %s [-hvVn] MIME-DIR\n"), name);
+}
+
+static void free_type(gpointer data)
+{
+ Type *type = (Type *) data;
+
+ g_free(type->media);
+ g_free(type->subtype);
+
+ xmlFreeDoc(type->output);
+
+ g_free(type);
+}
+
+/* Ugly workaround to shut up gcc4 warnings about signedness issues
+ * (xmlChar is typedef'ed to unsigned char)
+ */
+static char *my_xmlGetNsProp (xmlNodePtr node,
+ const char *name,
+ const xmlChar *namespace)
+{
+ return (char *)xmlGetNsProp (node, (xmlChar *)name, namespace);
+}
+
+/* If we've seen this type before, return the existing object.
+ * Otherwise, create a new one. Checks that the name looks sensible;
+ * if not, sets error and returns NULL.
+ * Also warns about unknown media types, but does not set error.
+ */
+static Type *get_type(const char *name, GError **error)
+{
+ xmlNode *root;
+ xmlNs *ns;
+ const char *slash;
+ Type *type;
+ int i;
+
+ slash = strchr(name, '/');
+ if (!slash || strchr(slash + 1, '/'))
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Invalid MIME-type '%s'"), name);
+ return NULL;
+ }
+
+ type = g_hash_table_lookup(types, name);
+ if (type)
+ return type;
+
+ type = g_new(Type, 1);
+ type->media = g_strndup(name, slash - name);
+ type->subtype = g_strdup(slash + 1);
+ g_hash_table_insert(types, g_strdup(name), type);
+
+ type->output = xmlNewDoc((xmlChar *)"1.0");
+ root = xmlNewDocNode(type->output, NULL, (xmlChar *)"mime-type", NULL);
+ ns = xmlNewNs(root, FREE_NS, NULL);
+ xmlSetNs(root, ns);
+ xmlDocSetRootElement(type->output, root);
+ xmlSetNsProp(root, NULL, (xmlChar *)"type", (xmlChar *)name);
+ xmlAddChild(root, xmlNewDocComment(type->output,
+ (xmlChar *)"Created automatically by update-mime-database. DO NOT EDIT!"));
+
+ for (i = 0; i < G_N_ELEMENTS(media_types); i++)
+ {
+ if (strcmp(media_types[i], type->media) == 0)
+ return type;
+ }
+
+ g_message("Unknown media type in type '%s'", name);
+
+ return type;
+}
+
+/* Test that this node has the expected name and namespace */
+static gboolean match_node(xmlNode *node,
+ const char *namespaceURI,
+ const char *localName)
+{
+ if (namespaceURI)
+ return node->ns &&
+ strcmp((char *)node->ns->href, namespaceURI) == 0 &&
+ strcmp((char *)node->name, localName) == 0;
+ else
+ return strcmp((char *)node->name, localName) == 0 && !node->ns;
+}
+
+static int get_int_attribute(xmlNode *node, const char *name)
+{
+ char *prio_string;
+ int p;
+
+ prio_string = my_xmlGetNsProp(node, name, NULL);
+ if (prio_string)
+ {
+ char *end;
+
+ p = strtol(prio_string, &end, 10);
+ if (*prio_string == '\0' || *end != '\0')
+ p = -1;
+ xmlFree(prio_string);
+ if (p < 0 || p > 100)
+ return -1;
+ return p;
+ }
+ else
+ return 50;
+}
+
+/* Return the priority of a <magic> node.
+ * Returns 50 if no priority is given, or -1 if a priority is given but
+ * is invalid.
+ */
+static int get_priority(xmlNode *node)
+{
+ return get_int_attribute (node, "priority");
+}
+
+/* Return the weight a <glob> node.
+ * Returns 50 if no weight is given, or -1 if a weight is given but
+ * is invalid.
+ */
+static int get_weight(xmlNode *node)
+{
+ return get_int_attribute (node, "weight");
+}
+
+/* Return the value of a false/true attribute, which defaults to false.
+ * Returns 0 or 1.
+ */
+static gboolean get_boolean_attribute(xmlNode *node, const char* name)
+{
+ char *attr;
+ attr = my_xmlGetNsProp(node, name, NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "true") == 0)
+ {
+ return TRUE;
+ }
+ xmlFree(attr);
+ }
+ return FALSE;
+}
+
+/* Process a <root-XML> element by adding a rule to namespace_hash */
+static void add_namespace(Type *type, const char *namespaceURI,
+ const char *localName, GError **error)
+{
+ g_return_if_fail(type != NULL);
+
+ if (!namespaceURI)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Missing 'namespaceURI' attribute'"));
+ return;
+ }
+
+ if (!localName)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Missing 'localName' attribute'"));
+ return;
+ }
+
+ if (!*namespaceURI && !*localName)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("namespaceURI and localName attributes can't "
+ "both be empty"));
+ return;
+ }
+
+ if (strpbrk(namespaceURI, " \n") || strpbrk(localName, " \n"))
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("namespaceURI and localName cannot contain "
+ "spaces or newlines"));
+ return;
+ }
+
+ g_hash_table_insert(namespace_hash,
+ g_strconcat(namespaceURI, " ", localName, NULL),
+ type);
+}
+
+/* 'field' was found in the definition of 'type' and has the freedesktop.org
+ * namespace. If it's a known field, process it and return TRUE, else
+ * return FALSE to add it to the output XML document.
+ * On error, returns FALSE and sets 'error'.
+ */
+static gboolean process_freedesktop_node(Type *type, xmlNode *field,
+ GError **error)
+{
+ gboolean copy_to_xml;
+
+ copy_to_xml = FALSE;
+ if (strcmp((char *)field->name, "glob") == 0)
+ {
+ gchar *pattern;
+ gint weight;
+ gboolean case_sensitive;
+
+ weight = get_weight(field);
+ case_sensitive = get_boolean_attribute(field, "case-sensitive");
+
+ if (weight == -1)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Bad weight (%d) in <glob> element"), weight);
+ }
+ pattern = my_xmlGetNsProp(field, "pattern", NULL);
+
+ if (pattern && *pattern)
+ {
+ Glob *glob;
+ char *pat = case_sensitive ? g_strdup (pattern) : g_ascii_strdown (pattern, -1);
+ GList *list = g_hash_table_lookup (globs_hash, pat);
+
+ glob = g_new0 (Glob, 1);
+ glob->pattern = pat;
+ glob->type = type;
+ glob->weight = weight;
+ glob->case_sensitive = case_sensitive;
+ list = g_list_append (list, glob);
+ g_hash_table_insert(globs_hash, g_strdup (glob->pattern), list);
+ xmlFree(pattern);
+ copy_to_xml = TRUE;
+ }
+ else
+ {
+ if (pattern)
+ xmlFree(pattern);
+ g_set_error(error, MIME_ERROR, 0,
+ _("Missing 'pattern' attribute in <glob> "
+ "element"));
+ }
+ }
+ else if (strcmp((char *)field->name, "glob-deleteall") == 0)
+ {
+ Glob *glob;
+ GList *list = g_hash_table_lookup (globs_hash, NOGLOBS);
+
+ glob = g_new0 (Glob, 1);
+ glob->pattern = g_strdup (NOGLOBS);
+ glob->type = type;
+ glob->weight = 0;
+ glob->noglob = TRUE;
+ glob->case_sensitive = FALSE;
+ list = g_list_append (list, glob);
+ g_hash_table_insert(globs_hash, g_strdup (glob->pattern), list);
+ copy_to_xml = TRUE;
+ }
+ else if (strcmp((char *)field->name, "magic") == 0)
+ {
+ Magic *magic;
+
+ magic = magic_new(field, type, error);
+
+ if (!*error)
+ {
+ g_return_val_if_fail(magic != NULL, FALSE);
+ g_ptr_array_add(magic_array, magic);
+ }
+ else
+ g_return_val_if_fail(magic == NULL, FALSE);
+ }
+ else if (strcmp((char *)field->name, "magic-deleteall") == 0)
+ {
+ Magic *magic;
+ Match *match;
+
+ magic = g_new0(Magic, 1);
+ magic->priority = 0;
+ magic->type = type;
+ magic->nomagic = TRUE;
+ match = match_new ();
+ match->data = g_strdup (NOMAGIC);
+ match->data_length = strlen (NOMAGIC);
+ magic->matches = g_list_prepend (NULL, match);
+
+ g_ptr_array_add(magic_array, magic);
+ }
+ else if (strcmp((char *)field->name, "treemagic") == 0)
+ {
+ TreeMagic *magic;
+
+ magic = tree_magic_new(field, type, error);
+
+ if (!*error)
+ {
+ g_return_val_if_fail(magic != NULL, FALSE);
+ g_ptr_array_add(tree_magic_array, magic);
+ }
+ else
+ g_return_val_if_fail(magic == NULL, FALSE);
+ }
+ else if (strcmp((char *)field->name, "comment") == 0 ||
+ strcmp((char *)field->name, "acronym") == 0 ||
+ strcmp((char *)field->name, "expanded-acronym") == 0)
+ copy_to_xml = TRUE;
+ else if (strcmp((char *)field->name, "alias") == 0 ||
+ strcmp((char *)field->name, "sub-class-of") == 0)
+ {
+ char *other_type;
+ gboolean valid;
+ GSList *list, *nlist;
+
+ other_type = my_xmlGetNsProp(field, "type", NULL);
+ valid = other_type && strchr(other_type, '/');
+ if (valid)
+ {
+ char *typename;
+
+ typename = g_strdup_printf("%s/%s",
+ type->media,
+ type->subtype);
+
+ if (strcmp((char *)field->name, "alias") == 0)
+ g_hash_table_insert(alias_hash,
+ g_strdup(other_type), type);
+
+ else
+ {
+ list = g_hash_table_lookup(subclass_hash, typename);
+ nlist = g_slist_append (list, g_strdup(other_type));
+ if (list == NULL)
+ g_hash_table_insert(subclass_hash,
+ g_strdup(typename), nlist);
+ }
+ g_free(typename);
+ xmlFree(other_type);
+
+ copy_to_xml = TRUE; /* Copy through */
+ }
+ else
+ {
+ xmlFree(other_type);
+ g_set_error(error, MIME_ERROR, 0,
+ _("Incorrect or missing 'type' attribute "
+ "in <%s>"), field->name);
+ }
+ }
+ else if (strcmp((char *)field->name, "root-XML") == 0)
+ {
+ char *namespaceURI, *localName;
+
+ namespaceURI = my_xmlGetNsProp(field, "namespaceURI", NULL);
+ localName = my_xmlGetNsProp(field, "localName", NULL);
+
+ add_namespace(type, namespaceURI, localName, error);
+
+ if (namespaceURI)
+ xmlFree(namespaceURI);
+ if (localName)
+ xmlFree(localName);
+ }
+ else if (strcmp((char *)field->name, "generic-icon") == 0 ||
+ strcmp((char *)field->name, "icon") == 0)
+ {
+ char *icon;
+ char *typename;
+
+ icon = my_xmlGetNsProp(field, "name", NULL);
+
+ if (icon)
+ {
+ typename = g_strdup_printf("%s/%s",
+ type->media,
+ type->subtype);
+
+ if (strcmp((char *)field->name, "icon") == 0)
+ g_hash_table_insert(icon_hash,
+ typename, g_strdup (icon));
+ else
+ g_hash_table_insert(generic_icon_hash,
+ typename, g_strdup (icon));
+
+ xmlFree (icon);
+
+ copy_to_xml = TRUE; /* Copy through */
+ }
+ }
+
+ if (*error)
+ return FALSE;
+ return !copy_to_xml;
+}
+
+/* Checks to see if 'node' has the given value for xml:lang.
+ * If 'lang' is NULL, checks that 'node' doesn't have an xml:lang.
+ */
+static gboolean has_lang(xmlNode *node, const char *lang)
+{
+ char *lang2;
+
+ lang2 = my_xmlGetNsProp(node, "lang", XML_NS);
+ if (!lang2)
+ return !lang;
+
+ if (lang && strcmp(lang, lang2) == 0)
+ {
+ xmlFree(lang2);
+ return TRUE;
+ }
+ xmlFree(lang2);
+ return FALSE;
+}
+
+/* We're about to add 'new' to the list of fields to be output for the
+ * type. Remove any existing nodes which it replaces.
+ */
+static void remove_old(Type *type, xmlNode *new)
+{
+ xmlNode *field, *fields;
+ char *lang;
+
+ if (new->ns == NULL || xmlStrcmp(new->ns->href, FREE_NS) != 0)
+ return; /* No idea what we're doing -- leave it in! */
+
+ if (strcmp((char *)new->name, "comment") != 0)
+ return;
+
+ lang = my_xmlGetNsProp(new, "lang", XML_NS);
+
+ fields = xmlDocGetRootElement(type->output);
+ for (field = fields->xmlChildrenNode; field; field = field->next)
+ {
+ if (match_node(field, (char *)FREE_NS, "comment") &&
+ has_lang(field, lang))
+ {
+ xmlUnlinkNode(field);
+ xmlFreeNode(field);
+ break;
+ }
+ }
+
+ xmlFree(lang);
+}
+
+/* 'node' is a <mime-type> node from a source file, whose type is 'type'.
+ * Process all the child elements, setting 'error' if anything goes wrong.
+ */
+static void load_type(Type *type, xmlNode *node, GError **error)
+{
+ xmlNode *field;
+
+ g_return_if_fail(type != NULL);
+ g_return_if_fail(node != NULL);
+ g_return_if_fail(error != NULL);
+
+ for (field = node->xmlChildrenNode; field; field = field->next)
+ {
+ xmlNode *copy;
+
+ if (field->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (field->ns && xmlStrcmp(field->ns->href, FREE_NS) == 0)
+ {
+ if (process_freedesktop_node(type, field, error))
+ {
+ g_return_if_fail(*error == NULL);
+ continue;
+ }
+ }
+
+ if (*error)
+ return;
+
+ copy = xmlDocCopyNode(field, type->output, 1);
+
+ /* Ugly hack to stop the xmlns= attributes appearing on
+ * every node...
+ */
+ if (copy->ns && copy->ns->prefix == NULL &&
+ xmlStrcmp(copy->ns->href, FREE_NS) == 0)
+ {
+ if (copy->nsDef)
+ {
+ /* Still used somewhere... */
+ /* xmlFreeNsList(copy->nsDef); */
+ /* (this leaks) */
+ copy->nsDef = NULL;
+ }
+ }
+
+ remove_old(type, field);
+
+ xmlAddChild(xmlDocGetRootElement(type->output), copy);
+ }
+}
+
+/* Parse 'filename' as an XML file and add all the information to the
+ * database. If called more than once, information read in later calls
+ * overrides information read previously.
+ */
+static void load_source_file(const char *filename)
+{
+ xmlDoc *doc;
+ xmlNode *root, *node;
+
+ doc = xmlParseFile(filename);
+ if (!doc)
+ {
+ g_warning(_("Failed to parse '%s'"), filename);
+ return;
+ }
+
+ g_message("Parsing source file %s...", filename);
+
+ root = xmlDocGetRootElement(doc);
+
+ if (root->ns == NULL || xmlStrcmp(root->ns->href, FREE_NS) != 0)
+ {
+ g_warning("Wrong namespace on document element in '%s' (should be %s)", filename, FREE_NS);
+ goto out;
+ }
+
+ if (strcmp((char *)root->name, "mime-info") != 0)
+ {
+ g_warning("Root element <%s> is not <mime-info> (in '%s')", root->name, filename);
+ goto out;
+ }
+
+ for (node = root->xmlChildrenNode; node; node = node->next)
+ {
+ Type *type = NULL;
+ char *type_name = NULL;
+ GError *error = NULL;
+
+ if (node->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (!match_node(node, (char *)FREE_NS, "mime-type"))
+ g_set_error(&error, MIME_ERROR, 0,
+ _("Excepted <mime-type>, but got wrong name "
+ "or namespace"));
+
+ if (!error)
+ {
+ type_name = my_xmlGetNsProp(node, "type", NULL);
+
+ if (!type_name)
+ g_set_error(&error, MIME_ERROR, 0,
+ _("<mime-type> element has no 'type' "
+ "attribute"));
+ }
+
+ if (type_name)
+ {
+ type = get_type(type_name, &error);
+ xmlFree(type_name);
+ }
+
+ if (!error)
+ {
+ g_return_if_fail(type != NULL);
+ load_type(type, node, &error);
+ }
+ else
+ g_return_if_fail(type == NULL);
+
+ if (error)
+ {
+ g_warning("Error in type '%s/%s' (in %s): %s.",
+ type ? type->media : _("unknown"),
+ type ? type->subtype : _("unknown"),
+ filename, error->message);
+ g_error_free(error);
+ }
+ }
+out:
+ xmlFreeDoc(doc);
+}
+
+/* Used as the sort function for sorting GPtrArrays */
+static gint strcmp2(gconstpointer a, gconstpointer b)
+{
+ const char *aa = *(char **) a;
+ const char *bb = *(char **) b;
+
+ return strcmp(aa, bb);
+}
+
+/* 'path' should be a 'packages' directory. Loads the information from
+ * every file in the directory.
+ */
+static void scan_source_dir(const char *path)
+{
+ DIR *dir;
+ struct dirent *ent;
+ char *filename;
+ GPtrArray *files;
+ int i;
+ gboolean have_override = FALSE;
+
+ dir = opendir(path);
+ if (!dir)
+ {
+ perror("scan_source_dir");
+ exit(EXIT_FAILURE);
+ }
+
+ files = g_ptr_array_new();
+ while ((ent = readdir(dir)))
+ {
+ int l;
+ l = strlen(ent->d_name);
+ if (l < 4 || strcmp(ent->d_name + l - 4, ".xml") != 0)
+ continue;
+ if (strcmp(ent->d_name, "Override.xml") == 0)
+ {
+ have_override = TRUE;
+ continue;
+ }
+ g_ptr_array_add(files, g_strdup(ent->d_name));
+ }
+ closedir(dir);
+
+ g_ptr_array_sort(files, strcmp2);
+
+ if (have_override)
+ g_ptr_array_add(files, g_strdup("Override.xml"));
+
+ for (i = 0; i < files->len; i++)
+ {
+ gchar *leaf = (gchar *) files->pdata[i];
+
+ filename = g_strconcat(path, "/", leaf, NULL);
+ load_source_file(filename);
+ g_free(filename);
+ }
+
+ for (i = 0; i < files->len; i++)
+ g_free(files->pdata[i]);
+ g_ptr_array_free(files, TRUE);
+}
+
+static gboolean save_xml_file(xmlDocPtr doc, const gchar *filename, GError **error)
+{
+#if LIBXML_VERSION > 20400
+ if (xmlSaveFormatFileEnc(filename, doc, "utf-8", 1) < 0)
+ {
+ g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ "Failed to write XML file; For permission problems, try rerunning as root");
+ return FALSE;
+ }
+#else
+ FILE *out;
+
+ out = fopen_gerror(filename, error);
+ if (!out)
+ return FALSE;
+
+ xmlDocDump(out, doc); /* Some versions return void */
+
+ if (!fclose_gerror(out, error))
+ return FALSE;
+#endif
+
+ return TRUE;
+}
+
+/* Write out globs for one pattern to the 'globs' file */
+static void write_out_glob(GList *globs, FILE *stream)
+{
+ GList *list;
+ Glob *glob;
+ Type *type;
+
+ for (list = globs; list; list = list->next) {
+ glob = (Glob *)list->data;
+ type = glob->type;
+ if (strchr(glob->pattern, '\n'))
+ g_warning("Glob patterns can't contain literal newlines "
+ "(%s in type %s/%s)", glob->pattern,
+ type->media, type->subtype);
+ else
+ g_fprintf(stream, "%s/%s:%s\n",
+ type->media, type->subtype, glob->pattern);
+ }
+}
+
+/* Write out globs and weights for one pattern to the 'globs2' file */
+static void write_out_glob2(GList *globs, FILE *stream)
+{
+ GList *list;
+ Glob *glob;
+ Type *type;
+ gboolean need_flags;
+
+ for (list = globs ; list; list = list->next) {
+ glob = (Glob *)list->data;
+ type = glob->type;
+ if (strchr(glob->pattern, '\n'))
+ g_warning("Glob patterns can't contain literal newlines "
+ "(%s in type %s/%s)", glob->pattern,
+ type->media, type->subtype);
+ else
+ {
+ need_flags = FALSE;
+ if (glob->case_sensitive)
+ need_flags = TRUE;
+
+ if (need_flags) {
+ g_fprintf(stream, "%d:%s/%s:%s%s\n",
+ glob->weight, type->media, type->subtype, glob->pattern,
+ glob->case_sensitive ? ":cs" : "");
+ }
+
+ /* Always write the line without the flags, for older parsers */
+ g_fprintf(stream, "%d:%s/%s:%s\n",
+ glob->weight, type->media, type->subtype, glob->pattern);
+ }
+ }
+}
+
+static void collect_glob2(gpointer key, gpointer value, gpointer data)
+{
+ GList **listp = data;
+
+ *listp = g_list_concat (*listp, g_list_copy ((GList *)value));
+}
+
+static int compare_glob_by_weight (gpointer a, gpointer b)
+{
+ Glob *ag = (Glob *)a;
+ Glob *bg = (Glob *)b;
+
+ if (ag->noglob || bg->noglob)
+ return bg->noglob - ag->noglob;
+
+ return bg->weight - ag->weight;
+}
+
+static void
+set_error_from_errno (GError **error)
+{
+ int errsv = errno;
+ g_set_error_literal(error, G_FILE_ERROR, g_file_error_from_errno(errsv),
+ g_strerror(errsv));
+}
+
+#ifdef HAVE_FDATASYNC
+static gboolean
+sync_enabled(void)
+{
+ const char *env;
+
+ env = g_getenv("PKGSYSTEM_ENABLE_FSYNC");
+ if (!env)
+ return TRUE;
+ return atoi(env);
+}
+#endif
+
+static int
+sync_file(const gchar *pathname, GError **error)
+{
+#ifdef HAVE_FDATASYNC
+ int fd;
+
+ if (!sync_enabled())
+ return 0;
+
+ fd = open(pathname, O_RDWR);
+ if (fd == -1)
+ {
+ set_error_from_errno(error);
+ return -1;
+ }
+ if (fdatasync(fd) == -1)
+ {
+ set_error_from_errno(error);
+ close(fd);
+ return -1;
+ }
+ if (close(fd) == -1)
+ {
+ set_error_from_errno(error);
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+/* Renames pathname by removing the .new extension */
+static gboolean atomic_update(const gchar *pathname, GError **error)
+{
+ gboolean ret = FALSE;
+ gchar *new_name = NULL;
+ int len;
+
+ len = strlen(pathname);
+
+ g_return_val_if_fail(strcmp(pathname + len - 4, ".new") == 0, FALSE);
+
+ new_name = g_strndup(pathname, len - 4);
+
+ if (sync_file(pathname, error) == -1)
+ goto out;
+
+#ifdef _WIN32
+ /* we need to remove the old file first! */
+ remove(new_name);
+#endif
+ if (rename(pathname, new_name) == -1)
+ {
+ int errsv = errno;
+ g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errsv),
+ "Failed to rename %s as %s: %s", pathname, new_name,
+ g_strerror(errsv));
+ goto out;
+ }
+
+ ret = TRUE;
+out:
+ g_free(new_name);
+ return ret;
+}
+
+/* Write out an XML file for one type */
+static void write_out_type(gpointer key, gpointer value, gpointer data)
+{
+ Type *type = (Type *) value;
+ const char *mime_dir = (char *) data;
+ char *media, *filename;
+ GError *local_error = NULL;
+ char *lower;
+
+ lower = g_ascii_strdown(type->media, -1);
+ media = g_strconcat(mime_dir, "/", lower, NULL);
+ g_free(lower);
+#ifdef _WIN32
+ mkdir(media);
+#else
+ mkdir(media, 0755);
+#endif
+
+ lower = g_ascii_strdown(type->subtype, -1);
+ filename = g_strconcat(media, "/", lower, ".xml.new", NULL);
+ g_free(lower);
+ g_free(media);
+ media = NULL;
+
+ if (!save_xml_file(type->output, filename, &local_error))
+ fatal_gerror(local_error);
+
+ if (!atomic_update(filename, &local_error))
+ fatal_gerror(local_error);
+
+ g_free(filename);
+}
+
+/* Comparison function to get the magic rules in priority order */
+static gint cmp_magic(gconstpointer a, gconstpointer b)
+{
+ Magic *aa = *(Magic **) a;
+ Magic *bb = *(Magic **) b;
+ int retval;
+
+ /* Sort nomagic items at start */
+ if (aa->nomagic || bb->nomagic)
+ return bb->nomagic - aa->nomagic;
+
+ if (aa->priority > bb->priority)
+ return -1;
+ else if (aa->priority < bb->priority)
+ return 1;
+
+ retval = strcmp(aa->type->media, bb->type->media);
+ if (!retval)
+ retval = strcmp(aa->type->subtype, bb->type->subtype);
+
+ return retval;
+}
+
+/* Comparison function to get the tree magic rules in priority order */
+static gint cmp_tree_magic(gconstpointer a, gconstpointer b)
+{
+ TreeMagic *aa = *(TreeMagic **) a;
+ TreeMagic *bb = *(TreeMagic **) b;
+ int retval;
+
+ if (aa->priority > bb->priority)
+ return -1;
+ else if (aa->priority < bb->priority)
+ return 1;
+
+ retval = strcmp(aa->type->media, bb->type->media);
+ if (!retval)
+ retval = strcmp(aa->type->subtype, bb->type->subtype);
+
+ return retval;
+}
+
+/* Write out 'n' as a two-byte big-endian number to 'stream' */
+static void write16(FILE *stream, guint32 n)
+{
+ guint16 big = GUINT16_TO_BE(n);
+
+ g_return_if_fail(n <= 0xffff);
+
+ fwrite(&big, sizeof(big), 1, stream);
+}
+
+/* Single hex char to int; -1 if not a hex char.
+ * From file(1).
+ */
+static int hextoint(int c)
+{
+ if (!isascii((unsigned char) c))
+ return -1;
+ if (isdigit((unsigned char) c))
+ return c - '0';
+ if ((c >= 'a')&&(c <= 'f'))
+ return c + 10 - 'a';
+ if (( c>= 'A')&&(c <= 'F'))
+ return c + 10 - 'A';
+ return -1;
+}
+
+/*
+ * Convert a string containing C character escapes. Stop at an unescaped
+ * space or tab.
+ * Copy the converted version to "p", returning its length in *slen.
+ * Return updated scan pointer as function result.
+ * Stolen from file(1) and heavily modified.
+ */
+static void getstr(const char *s, GString *out)
+{
+ int c;
+ int val;
+
+ while ((c = *s++) != '\0') {
+ if(c == '\\') {
+ switch(c = *s++) {
+
+ case '\0':
+ return;
+
+ default:
+ g_string_append_c(out, (char) c);
+ break;
+
+ case 'n':
+ g_string_append_c(out, '\n');
+ break;
+
+ case 'r':
+ g_string_append_c(out, '\r');
+ break;
+
+ case 'b':
+ g_string_append_c(out, '\b');
+ break;
+
+ case 't':
+ g_string_append_c(out, '\t');
+ break;
+
+ case 'f':
+ g_string_append_c(out, '\f');
+ break;
+
+ case 'v':
+ g_string_append_c(out, '\v');
+ break;
+
+ /* \ and up to 3 octal digits */
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ val = c - '0';
+ c = *s++; /* try for 2 */
+ if(c >= '0' && c <= '7') {
+ val = (val<<3) | (c - '0');
+ c = *s++; /* try for 3 */
+ if(c >= '0' && c <= '7')
+ val = (val<<3) | (c-'0');
+ else
+ --s;
+ }
+ else
+ --s;
+ g_string_append_c(out, (char)val);
+ break;
+
+ /* \x and up to 2 hex digits */
+ case 'x':
+ val = 'x'; /* Default if no digits */
+ c = hextoint(*s++); /* Get next char */
+ if (c >= 0) {
+ val = c;
+ c = hextoint(*s++);
+ if (c >= 0)
+ val = (val << 4) + c;
+ else
+ --s;
+ } else
+ --s;
+ g_string_append_c(out, (char)val);
+ break;
+ }
+ } else
+ g_string_append_c(out, (char)c);
+ }
+}
+
+/* Parse the value and mask attributes of a <match> element with a
+ * numerical type (anything except "string").
+ */
+static void parse_int_value(int bytes, const char *in, const char *in_mask,
+ GString *parsed_value, char **parsed_mask,
+ gboolean big_endian, GError **error)
+{
+ char *end;
+ char *out_mask = NULL;
+ unsigned long value;
+ int b;
+
+ value = strtoul(in, &end, 0);
+ if (errno == ERANGE) {
+ g_set_error(error, MIME_ERROR, 0,
+ "Number out-of-range (%s should fit in %d bytes)",
+ in, bytes);
+ return;
+ }
+
+ if (*end != '\0')
+ {
+ g_set_error(error, MIME_ERROR, 0, "Value is not a number");
+ return;
+ }
+
+ for (b = 0; b < bytes; b++)
+ {
+ int shift = (big_endian ? (bytes - b - 1) : b) * 8;
+ g_string_append_c(parsed_value, (value >> shift) & 0xff);
+ }
+
+ if ((bytes == 1 && (value & ~0xff)) ||
+ (bytes == 2 && (value & ~0xffff)))
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ "Number out-of-range (%lx should fit in %d bytes)",
+ value, bytes);
+ return;
+ }
+
+ if (in_mask)
+ {
+ int b;
+ unsigned long mask;
+
+ mask = strtoul(in_mask, &end, 0);
+ if (errno == ERANGE) {
+ g_set_error(error, MIME_ERROR, 0,
+ "Mask out-of-range (%s should fit in %d bytes)",
+ in_mask, bytes);
+ return;
+ }
+
+
+ if (*end != '\0')
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ "Mask is not a number");
+ return;
+ }
+
+ out_mask = g_new(char, bytes);
+ for (b = 0; b < bytes; b++)
+ {
+ int shift = (big_endian ? (bytes - b - 1) : b) * 8;
+ out_mask[b] = (mask >> shift) & 0xff;
+ }
+ }
+
+ *parsed_mask = out_mask;
+}
+
+/* 'len' is the length of the value. The mask created will be the same
+ * length.
+ */
+static char *parse_string_mask(const char *mask, int len, GError **error)
+{
+ int i;
+ char *parsed_mask = NULL;
+
+ g_return_val_if_fail(mask != NULL, NULL);
+ g_return_val_if_fail(len > 0, NULL);
+
+ if (mask[0] != '0' || mask[1] != 'x')
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ "String masks must be in base 16 (starting with 0x)");
+ goto err;
+ }
+ mask += 2;
+
+ parsed_mask = g_new0(char, len);
+
+ i = 0; /* Nybble to write to next */
+ while (mask[i])
+ {
+ int c;
+
+ c = hextoint(mask[i]);
+ if (c == -1)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ "'%c' is not a valid hex digit", mask[i]);
+ goto err;
+ }
+
+ if (i >= len * 2)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ "Mask is longer than value");
+ goto err;
+ }
+
+ if (i & 1)
+ parsed_mask[i >> 1] |= c;
+ else
+ parsed_mask[i >> 1] |= c << 4;
+
+ i++;
+ }
+
+ return parsed_mask;
+err:
+ g_free(parsed_mask);
+ g_return_val_if_fail(error == NULL || *error != NULL, NULL);
+ return NULL;
+}
+
+/* Parse the value and mask attributes for a <match> element */
+static void parse_value(const char *type, const char *in, const char *in_mask,
+ GString *parsed_value, char **parsed_mask,
+ GError **error)
+{
+ *parsed_mask = NULL;
+
+ if (in == NULL || !in[0])
+ {
+ g_set_error(error, MIME_ERROR, 0, "No value specified");
+ return;
+ }
+
+ if (strstr(type, "16"))
+ parse_int_value(2, in, in_mask, parsed_value, parsed_mask,
+ type[0] != 'l', error);
+ else if (strstr(type, "32"))
+ parse_int_value(4, in, in_mask, parsed_value, parsed_mask,
+ type[0] != 'l', error);
+ else if (strcmp(type, "byte") == 0)
+ parse_int_value(1, in, in_mask, parsed_value, parsed_mask,
+ FALSE, error);
+ else if (strcmp(type, "string") == 0)
+ {
+ getstr(in, parsed_value);
+ if (in_mask)
+ *parsed_mask = parse_string_mask(in_mask,
+ parsed_value->len, error);
+ }
+ else
+ g_assert_not_reached();
+}
+
+static Match *match_new(void)
+{
+ Match *match;
+
+ match = g_new(Match, 1);
+ match->range_start = 0;
+ match->range_length = 1;
+ match->word_size = 1;
+ match->data_length = 0;
+ match->data = NULL;
+ match->mask = NULL;
+ match->matches = NULL;
+
+ return match;
+}
+
+static void match_free(Match *match)
+{
+ GList *next;
+
+ g_return_if_fail(match != NULL);
+
+ for (next = match->matches; next; next = next->next)
+ match_free((Match *) next->data);
+
+ g_list_free(match->matches);
+
+ g_free(match->data);
+ g_free(match->mask);
+
+ g_free(match);
+}
+
+/* Sets match->range_start and match->range_length */
+static void match_offset(Match *match, xmlNode *node, GError **error)
+{
+ char *offset = NULL;
+ char *end;
+
+ offset = my_xmlGetNsProp(node, "offset", NULL);
+ if (offset == NULL || !*offset)
+ {
+ g_set_error(error, MIME_ERROR, 0, "Missing 'offset' attribute");
+ goto err;
+ }
+
+ match->range_start = strtol(offset, &end, 10);
+ if (errno == ERANGE) {
+ char *number;
+ number = g_strndup(offset, end-offset);
+ g_set_error(error, MIME_ERROR, 0,
+ "Number out-of-range (%s should fit in 4 bytes)",
+ number);
+ g_free(number);
+ return;
+ }
+
+ if (*end == ':' && end[1] && match->range_start >= 0)
+ {
+ int last;
+ char *begin;
+
+ begin = end + 1;
+ last = strtol(begin, &end, 10);
+ if (errno == ERANGE) {
+ char *number;
+ number = g_strndup(begin, end-begin);
+ g_set_error(error, MIME_ERROR, 0,
+ "Number out-of-range (%s should fit in 8 bytes)",
+ number);
+ g_free(number);
+ return;
+ }
+
+ if (*end == '\0' && last >= match->range_start)
+ match->range_length = last - match->range_start + 1;
+ else
+ g_set_error(error, MIME_ERROR, 0, "Invalid offset");
+ }
+ else if (*end != '\0')
+ g_set_error(error, MIME_ERROR, 0, "Invalid offset");
+err:
+ xmlFree(offset);
+}
+
+/* Sets match->data, match->data_length and match->mask */
+static void match_value_and_mask(Match *match, xmlNode *node, GError **error)
+{
+ char *mask = NULL;
+ char *value = NULL;
+ char *type = NULL;
+ char *parsed_mask = NULL;
+ GString *parsed_value;
+
+ type = my_xmlGetNsProp(node, "type", NULL);
+ g_return_if_fail(type != NULL);
+
+ mask = my_xmlGetNsProp(node, "mask", NULL);
+ value = my_xmlGetNsProp(node, "value", NULL);
+
+ parsed_value = g_string_new(NULL);
+
+ parse_value(type, value, mask, parsed_value,
+ &parsed_mask, error);
+
+ if (*error)
+ {
+ g_string_free(parsed_value, TRUE);
+ g_return_if_fail(parsed_mask == NULL);
+ }
+ else
+ {
+ match->data = parsed_value->str;
+ match->data_length = parsed_value->len;
+ match->mask = parsed_mask;
+
+ g_string_free(parsed_value, FALSE);
+ }
+
+ if (mask)
+ xmlFree(mask);
+ if (value)
+ xmlFree(value);
+ xmlFree(type);
+}
+
+/* Sets match->word_size */
+static void match_word_size(Match *match, xmlNode *node, GError **error)
+{
+ char *type;
+
+ type = my_xmlGetNsProp(node, "type", NULL);
+
+ if (!type)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Missing 'type' attribute in <match>"));
+ return;
+ }
+
+ if (strcmp(type, "host16") == 0)
+ match->word_size = 2;
+ else if (strcmp(type, "host32") == 0)
+ match->word_size = 4;
+ else if (!*error && strcmp(type, "big16") &&
+ strcmp(type, "big32") &&
+ strcmp(type, "little16") && strcmp(type, "little32") &&
+ strcmp(type, "string") && strcmp(type, "byte"))
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ "Unknown magic type '%s'", type);
+ }
+
+ xmlFree(type);
+}
+
+/* Turn the list of child nodes of 'parent' into a list of Matches */
+static GList *build_matches(xmlNode *parent, GError **error)
+{
+ xmlNode *node;
+ GList *out = NULL;
+
+ g_return_val_if_fail(error != NULL, NULL);
+
+ for (node = parent->xmlChildrenNode; node; node = node->next)
+ {
+ Match *match;
+
+ if (node->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (node->ns == NULL || xmlStrcmp(node->ns->href, FREE_NS) != 0)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Element found with non-freedesktop.org "
+ "namespace"));
+ break;
+ }
+
+ if (strcmp((char *)node->name, "match") != 0)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Expected <match> element, but found "
+ "<%s> instead"), node->name);
+ break;
+ }
+
+ match = match_new();
+ match_offset(match, node, error);
+ if (!*error)
+ match_word_size(match, node, error);
+ if (!*error)
+ match_value_and_mask(match, node, error);
+
+ if (*error)
+ {
+ match_free(match);
+ break;
+ }
+
+ out = g_list_append(out, match);
+
+ match->matches = build_matches(node, error);
+ if (*error)
+ break;
+ }
+
+ return out;
+}
+
+static void magic_free(Magic *magic)
+{
+ GList *next;
+
+ g_return_if_fail(magic != NULL);
+
+ for (next = magic->matches; next; next = next->next)
+ match_free((Match *) next->data);
+ g_list_free(magic->matches);
+
+ g_free(magic);
+}
+
+/* Create a new Magic object by parsing 'node' (a <magic> element) */
+static Magic *magic_new(xmlNode *node, Type *type, GError **error)
+{
+ Magic *magic = NULL;
+ int prio;
+
+ g_return_val_if_fail(node != NULL, NULL);
+ g_return_val_if_fail(type != NULL, NULL);
+ g_return_val_if_fail(error != NULL, NULL);
+
+ prio = get_priority(node);
+
+ if (prio == -1)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Bad priority (%d) in <magic> element"), prio);
+ }
+ else
+ {
+ magic = g_new0(Magic, 1);
+ magic->priority = prio;
+ magic->type = type;
+ magic->matches = build_matches(node, error);
+
+
+ if (*error)
+ {
+ gchar *old = (*error)->message;
+ magic_free(magic);
+ magic = NULL;
+ (*error)->message = g_strconcat(
+ _("Error in <match> element: "), old, NULL);
+ g_free(old);
+ } else if (magic->matches == NULL) {
+ magic_free(magic);
+ magic = NULL;
+ g_set_error(error, MIME_ERROR, 0,
+ _("Incomplete <magic> element"));
+ }
+ }
+
+ return magic;
+}
+
+static TreeMatch *tree_match_new(void)
+{
+ TreeMatch *match;
+
+ match = g_new(TreeMatch, 1);
+ match->path = NULL;
+ match->match_case = 0;
+ match->executable = 0;
+ match->non_empty = 0;
+ match->type = 0;
+ match->mimetype = NULL;
+ match->matches = NULL;
+
+ return match;
+}
+
+static void tree_match_free(TreeMatch *match)
+{
+ GList *next;
+
+ g_return_if_fail(match != NULL);
+
+ for (next = match->matches; next; next = next->next)
+ tree_match_free((TreeMatch *) next->data);
+
+ g_list_free(match->matches);
+
+ g_free(match->path);
+ g_free(match->mimetype);
+
+ g_free(match);
+}
+
+/* Turn the list of child nodes of 'parent' into a list of TreeMatches */
+static GList *build_tree_matches(xmlNode *parent, GError **error)
+{
+ xmlNode *node;
+ GList *out = NULL;
+ char *attr;
+
+ g_return_val_if_fail(error != NULL, NULL);
+
+ for (node = parent->xmlChildrenNode; node; node = node->next)
+ {
+ TreeMatch *match;
+
+ if (node->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (node->ns == NULL || xmlStrcmp(node->ns->href, FREE_NS) != 0)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Element found with non-freedesktop.org "
+ "namespace"));
+ break;
+ }
+
+ if (strcmp((char *)node->name, "treematch") != 0)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Expected <treematch> element, but found "
+ "<%s> instead"), node->name);
+ break;
+ }
+
+ match = tree_match_new();
+
+ attr = my_xmlGetNsProp(node, "path", NULL);
+ if (attr)
+ {
+ match->path = g_strdup (attr);
+ xmlFree (attr);
+ }
+ else
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Missing 'path' attribute in <treematch>"));
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "type", NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "file") == 0)
+ {
+ match->type = 1;
+ }
+ else if (strcmp (attr, "directory") == 0)
+ {
+ match->type = 2;
+ }
+ else if (strcmp (attr, "link") == 0)
+ {
+ match->type = 3;
+ }
+ else
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Invalid 'type' attribute in <treematch>"));
+ }
+ xmlFree(attr);
+ }
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "executable", NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "true") == 0)
+ {
+ match->executable = 1;
+ }
+ xmlFree(attr);
+ }
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "match-case", NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "true") == 0)
+ {
+ match->match_case = 1;
+ }
+ xmlFree(attr);
+ }
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "non-empty", NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "true") == 0)
+ {
+ match->non_empty = 1;
+ }
+ xmlFree(attr);
+ }
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "mimetype", NULL);
+ if (attr)
+ {
+ match->mimetype = g_strdup (attr);
+ xmlFree(attr);
+ }
+ }
+
+ if (*error)
+ {
+ tree_match_free(match);
+ break;
+ }
+
+ out = g_list_append(out, match);
+
+ match->matches = build_tree_matches(node, error);
+ if (*error)
+ break;
+ }
+
+ return out;
+}
+
+static void tree_magic_free(TreeMagic *magic)
+{
+ GList *next;
+
+ g_return_if_fail(magic != NULL);
+
+ for (next = magic->matches; next; next = next->next)
+ tree_match_free((TreeMatch *) next->data);
+ g_list_free(magic->matches);
+
+ g_free(magic);
+}
+
+/* Create a new TreeMagic object by parsing 'node' (a <treemagic> element) */
+static TreeMagic *tree_magic_new(xmlNode *node, Type *type, GError **error)
+{
+ TreeMagic *magic = NULL;
+ int prio;
+
+ g_return_val_if_fail(node != NULL, NULL);
+ g_return_val_if_fail(type != NULL, NULL);
+ g_return_val_if_fail(error != NULL, NULL);
+
+ prio = get_priority(node);
+
+ if (prio == -1)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Bad priority (%d) in <treemagic> element"), prio);
+ }
+ else
+ {
+ magic = g_new(TreeMagic, 1);
+ magic->priority = prio;
+ magic->type = type;
+ magic->matches = build_tree_matches(node, error);
+
+ if (*error)
+ {
+ gchar *old = (*error)->message;
+ tree_magic_free(magic);
+ magic = NULL;
+ (*error)->message = g_strconcat(
+ _("Error in <treematch> element: "), old, NULL);
+ g_free(old);
+ }
+ }
+
+ return magic;
+}
+
+/* Write a list of Match elements (and their children) to the 'magic' file */
+static void write_magic_children(FILE *stream, GList *matches, int indent)
+{
+ GList *next;
+
+ for (next = matches; next; next = next->next)
+ {
+ Match *match = (Match *) next->data;
+
+ if (indent)
+ g_fprintf(stream,
+ "%d>%ld=",
+ indent,
+ match->range_start);
+ else
+ g_fprintf(stream, ">%ld=", match->range_start);
+
+ write16(stream, match->data_length);
+ fwrite(match->data, match->data_length, 1, stream);
+ if (match->mask)
+ {
+ fputc('&', stream);
+ fwrite(match->mask, match->data_length, 1, stream);
+ }
+ if (match->word_size != 1)
+ g_fprintf(stream, "~%d", match->word_size);
+ if (match->range_length != 1)
+ g_fprintf(stream, "+%d", match->range_length);
+
+ fputc('\n', stream);
+
+ write_magic_children(stream, match->matches, indent + 1);
+ }
+}
+
+/* Write a whole Magic element to the 'magic' file */
+static void write_magic(FILE *stream, Magic *magic)
+{
+ g_fprintf(stream, "[%d:%s/%s]\n", magic->priority,
+ magic->type->media, magic->type->subtype);
+
+ write_magic_children(stream, magic->matches, 0);
+}
+
+/* Write a list of TreeMatch elements (and their children) to the 'treemagic' file */
+static void write_tree_magic_children(FILE *stream, GList *matches, int indent)
+{
+ GList *next;
+
+ for (next = matches; next; next = next->next)
+ {
+ TreeMatch *match = (TreeMatch *) next->data;
+
+ if (indent)
+ g_fprintf(stream,
+ "%d>\"%s\"=",
+ indent,
+ match->path);
+ else
+ g_fprintf(stream, ">\"%s\"=", match->path);
+
+ switch (match->type)
+ {
+ default:
+ case 0:
+ fputs("any", stream);
+ break;
+ case 1:
+ fputs("file", stream);
+ break;
+ case 2:
+ fputs("directory", stream);
+ break;
+ case 3:
+ fputs("link", stream);
+ break;
+ }
+ if (match->match_case)
+ fputs (",match-case", stream);
+ if (match->executable)
+ fputs (",executable", stream);
+ if (match->non_empty)
+ fputs (",non-empty", stream);
+ if (match->mimetype)
+ g_fprintf (stream, ",%s", match->mimetype);
+
+ fputc('\n', stream);
+
+ write_tree_magic_children(stream, match->matches, indent + 1);
+ }
+}
+/* Write a whole TreeMagic element to the 'treemagic' file */
+static void write_tree_magic(FILE *stream, TreeMagic *magic)
+{
+ g_fprintf(stream, "[%d:%s/%s]\n", magic->priority,
+ magic->type->media, magic->type->subtype);
+
+ write_tree_magic_children(stream, magic->matches, 0);
+}
+
+/* Check each of the directories with generated XML files, looking for types
+ * which we didn't get on this scan, and delete them.
+ */
+static void delete_old_types(const gchar *mime_dir)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS(media_types); i++)
+ {
+ gchar *media_dir;
+ DIR *dir;
+ struct dirent *ent;
+
+ media_dir = g_strconcat(mime_dir, "/", media_types[i], NULL);
+ dir = opendir(media_dir);
+ g_free(media_dir);
+ if (!dir)
+ continue;
+
+ while ((ent = readdir(dir)))
+ {
+ char *type_name;
+ int l;
+ l = strlen(ent->d_name);
+ if (l < 4 || strcmp(ent->d_name + l - 4, ".xml") != 0)
+ continue;
+
+ type_name = g_strconcat(media_types[i], "/",
+ ent->d_name, NULL);
+ type_name[strlen(type_name) - 4] = '\0';
+ if (!g_hash_table_lookup(types, type_name))
+ {
+ char *path;
+ path = g_strconcat(mime_dir, "/",
+ type_name, ".xml", NULL);
+#if 0
+ g_warning("Removing old info for type %s",
+ path);
+#endif
+ unlink(path);
+ g_free(path);
+ }
+ g_free(type_name);
+ }
+
+ closedir(dir);
+ }
+}
+
+/* Extract one entry from namespace_hash and put it in the GPtrArray so
+ * we can sort it.
+ */
+static void add_ns(gpointer key, gpointer value, gpointer data)
+{
+ GPtrArray *lines = (GPtrArray *) data;
+ const gchar *ns = (gchar *) key;
+ Type *type = (Type *) value;
+
+ g_ptr_array_add(lines, g_strconcat(ns, " ", type->media,
+ "/", type->subtype, "\n", NULL));
+}
+
+/* Write all the collected namespace rules to 'XMLnamespaces' */
+static void write_namespaces(FILE *stream)
+{
+ GPtrArray *lines;
+ int i;
+
+ lines = g_ptr_array_new();
+
+ g_hash_table_foreach(namespace_hash, add_ns, lines);
+
+ g_ptr_array_sort(lines, strcmp2);
+
+ for (i = 0; i < lines->len; i++)
+ {
+ char *line = (char *) lines->pdata[i];
+
+ fwrite(line, 1, strlen(line), stream);
+
+ g_free(line);
+ }
+
+ g_ptr_array_free(lines, TRUE);
+}
+
+static void write_subclass(gpointer key, gpointer value, gpointer data)
+{
+ GSList *list = value;
+ FILE *stream = data;
+ GSList *l;
+ char *line;
+
+ for (l = list; l; l = l->next)
+ {
+ line = g_strconcat (key, " ", l->data, "\n", NULL);
+ fwrite(line, 1, strlen(line), stream);
+ g_free (line);
+ }
+}
+
+/* Write all the collected subclass information to 'subclasses' */
+static void write_subclasses(FILE *stream)
+{
+ g_hash_table_foreach(subclass_hash, write_subclass, stream);
+}
+
+/* Extract one entry from alias_hash and put it in the GPtrArray so
+ * we can sort it.
+ */
+static void add_alias(gpointer key, gpointer value, gpointer data)
+{
+ GPtrArray *lines = (GPtrArray *) data;
+ const gchar *alias = (gchar *) key;
+ Type *type = (Type *) value;
+
+ g_ptr_array_add(lines, g_strconcat(alias, " ", type->media,
+ "/", type->subtype, "\n",
+ NULL));
+}
+
+/* Write all the collected aliases */
+static void write_aliases(FILE *stream)
+{
+ GPtrArray *lines;
+ int i;
+
+ lines = g_ptr_array_new();
+
+ g_hash_table_foreach(alias_hash, add_alias, lines);
+
+ g_ptr_array_sort(lines, strcmp2);
+
+ for (i = 0; i < lines->len; i++)
+ {
+ char *line = (char *) lines->pdata[i];
+
+ fwrite(line, 1, strlen(line), stream);
+
+ g_free(line);
+ }
+
+ g_ptr_array_free(lines, TRUE);
+}
+
+static void add_type(gpointer key, gpointer value, gpointer data)
+{
+ GPtrArray *lines = (GPtrArray *) data;
+
+ g_ptr_array_add(lines, g_strconcat((char *)key, "\n", NULL));
+}
+
+/* Write all the collected types */
+static void write_types(FILE *stream)
+{
+ GPtrArray *lines;
+ int i;
+
+ lines = g_ptr_array_new();
+
+ g_hash_table_foreach(types, add_type, lines);
+
+ g_ptr_array_sort(lines, strcmp2);
+
+ for (i = 0; i < lines->len; i++)
+ {
+ char *line = (char *) lines->pdata[i];
+
+ fwrite(line, 1, strlen(line), stream);
+
+ g_free(line);
+ }
+
+ g_ptr_array_free(lines, TRUE);
+}
+
+
+static void write_one_icon(gpointer key, gpointer value, gpointer data)
+{
+ char *mimetype = (char *)key;
+ char *iconname = (char *)value;
+ FILE *stream = (FILE *)data;
+ char *line;
+
+ line = g_strconcat (mimetype, ":", iconname, "\n", NULL);
+ fwrite(line, 1, strlen(line), stream);
+ g_free (line);
+}
+
+static void write_icons(GHashTable *icons, FILE *stream)
+{
+ g_hash_table_foreach(icons, write_one_icon, stream);
+}
+
+/* Issue a warning if 'path' won't be found by applications */
+static void check_in_path_xdg_data(const char *mime_path)
+{
+ struct stat path_info, dir_info;
+ const char *env;
+ char **dirs;
+ char *path;
+ int i, n;
+
+ path = g_path_get_dirname(mime_path);
+
+ if (stat(path, &path_info))
+ {
+ g_warning("Can't stat '%s' directory: %s",
+ path, g_strerror(errno));
+ goto out;
+ }
+
+ env = getenv("XDG_DATA_DIRS");
+ if (!env)
+ env = "/usr/local/share/"PATH_SEPARATOR"/usr/share/";
+ dirs = g_strsplit(env, PATH_SEPARATOR, 0);
+ g_return_if_fail(dirs != NULL);
+ for (n = 0; dirs[n]; n++)
+ ;
+ env = getenv("XDG_DATA_HOME");
+ if (env)
+ dirs[n] = g_strdup(env);
+ else
+ dirs[n] = g_build_filename(g_get_home_dir(), ".local",
+ "share", NULL);
+ n++;
+
+ for (i = 0; i < n; i++)
+ {
+ if (stat(dirs[i], &dir_info) == 0 &&
+ dir_info.st_ino == path_info.st_ino &&
+ dir_info.st_dev == path_info.st_dev)
+ break;
+ }
+
+ if (i == n)
+ {
+ g_printerr(_("\nNote that '%s' is not in the search path\n"
+ "set by the XDG_DATA_HOME and XDG_DATA_DIRS\n"
+ "environment variables, so applications may not\n"
+ "be able to find it until you set them. The\n"
+ "directories currently searched are:\n\n"), path);
+ g_printerr("- %s\n", dirs[n - 1]);
+ for (i = 0; i < n - 1; i++)
+ g_printerr("- %s\n", dirs[i]);
+ g_printerr("\n");
+ }
+
+ for (i = 0; i < n; i++)
+ g_free(dirs[i]);
+ g_free(dirs);
+out:
+ g_free(path);
+}
+
+static void free_string_list(gpointer data)
+{
+ GSList *list = data;
+
+ g_slist_foreach(list, (GFunc)g_free, NULL);
+ g_slist_free(list);
+}
+
+#define ALIGN_VALUE(this, boundary) \
+ (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
+
+
+static gint
+write_data (FILE *cache, const gchar *n, gint len)
+{
+ gchar *s;
+ int i, l;
+
+ l = ALIGN_VALUE (len, 4);
+
+ s = g_malloc0 (l);
+ memcpy (s, n, len);
+
+ i = fwrite (s, l, 1, cache);
+
+ g_free(s);
+
+ return i == 1;
+
+}
+
+static gint
+write_string (FILE *cache, const gchar *n)
+{
+ return write_data (cache, n, strlen (n) + 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;
+}
+
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 2
+
+static gboolean
+write_header (FILE *cache,
+ gint alias_offset,
+ gint parent_offset,
+ gint literal_offset,
+ gint suffix_offset,
+ gint glob_offset,
+ gint magic_offset,
+ gint namespace_offset,
+ gint icons_list_offset,
+ gint generic_icons_list_offset,
+ gint type_offset,
+ guint *offset)
+{
+ *offset = 44;
+
+ return (write_card16 (cache, MAJOR_VERSION) &&
+ write_card16 (cache, MINOR_VERSION) &&
+ write_card32 (cache, alias_offset) &&
+ write_card32 (cache, parent_offset) &&
+ write_card32 (cache, literal_offset) &&
+ write_card32 (cache, suffix_offset) &&
+ write_card32 (cache, glob_offset) &&
+ write_card32 (cache, magic_offset) &&
+ write_card32 (cache, namespace_offset) &&
+ write_card32 (cache, icons_list_offset) &&
+ write_card32 (cache, generic_icons_list_offset) &&
+ write_card32 (cache, type_offset));
+}
+
+
+typedef gboolean (FilterFunc) (gpointer key);
+typedef gchar ** (GetValueFunc) (gpointer data, gchar *key);
+
+typedef struct
+{
+ FILE *cache;
+ GHashTable *pool;
+ guint offset;
+ GetValueFunc *get_value;
+ gpointer data;
+ gboolean weighted;
+ gboolean error;
+} MapData;
+
+static void
+write_map_entry (gpointer key,
+ gpointer data)
+{
+ MapData *map_data = (MapData *)data;
+ gchar **values;
+ guint offset, i;
+ guint weight;
+
+ values = (* map_data->get_value) (map_data->data, key);
+ for (i = 0; values[i]; i++)
+ {
+ if (map_data->weighted && (i % 3 == 2))
+ {
+ weight = atoi (values[i]);
+
+ if (!write_card32 (map_data->cache, weight))
+ map_data->error = TRUE;
+
+ map_data->offset += 4;
+ }
+ else
+ {
+ offset = GPOINTER_TO_UINT (g_hash_table_lookup (map_data->pool, values[i]));
+ if (offset == 0)
+ {
+ g_warning ("Missing string: '%s'", values[i]);
+ map_data->error = TRUE;
+ }
+ if (!write_card32 (map_data->cache, offset))
+ map_data->error = TRUE;
+ map_data->offset += 4;
+ }
+ }
+
+ g_strfreev (values);
+}
+
+typedef struct
+{
+ FilterFunc *filter;
+ GPtrArray *keys;
+} FilterData;
+
+static void
+add_key (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ FilterData *filter_data = (FilterData *)data;
+
+ if (!filter_data->filter || (* filter_data->filter) (key))
+ g_ptr_array_add (filter_data->keys, key);
+}
+
+typedef struct
+{
+ GetValueFunc *get_value;
+ gpointer data;
+ guint count;
+ gboolean weighted;
+} CountData;
+
+static void
+count_map_entry (gpointer key,
+ gpointer data)
+{
+ CountData *count_data = (CountData *)data;
+ gchar **values;
+
+ values = (* count_data->get_value) (count_data->data, key);
+ count_data->count += g_strv_length (values) / (count_data->weighted ? 3 : 2);
+ g_strfreev (values);
+}
+
+static gboolean
+write_map (FILE *cache,
+ GHashTable *strings,
+ GHashTable *map,
+ FilterFunc *filter,
+ GetValueFunc *get_value,
+ gboolean weighted,
+ guint *offset)
+{
+ GPtrArray *keys;
+ MapData map_data;
+ FilterData filter_data;
+ CountData count_data;
+
+ keys = g_ptr_array_new ();
+
+ filter_data.keys = keys;
+ filter_data.filter = filter;
+ g_hash_table_foreach (map, add_key, &filter_data);
+
+ g_ptr_array_sort (keys, strcmp2);
+
+ count_data.data = map;
+ count_data.count = 0;
+ count_data.get_value = get_value;
+ count_data.weighted = weighted;
+
+ g_ptr_array_foreach (keys, count_map_entry, &count_data);
+
+ if (!write_card32 (cache, count_data.count))
+ return FALSE;
+
+ map_data.cache = cache;
+ map_data.pool = strings;
+ map_data.get_value = get_value;
+ map_data.data = map;
+ map_data.weighted = weighted;
+ map_data.offset = *offset + 4;
+ map_data.error = FALSE;
+
+ g_ptr_array_foreach (keys, write_map_entry, &map_data);
+
+ *offset = map_data.offset;
+
+ return !map_data.error;
+}
+
+static gchar **
+get_type_value (gpointer data,
+ gchar *key)
+{
+ Type *type;
+ gchar **result;
+
+ type = (Type *)g_hash_table_lookup ((GHashTable *)data, key);
+
+ result = g_new0 (gchar *, 3);
+ result[0] = g_strdup (key);
+ result[1] = g_strdup_printf ("%s/%s", type->media, type->subtype);
+
+ return result;
+}
+
+static guint32
+get_glob_weight_and_flags (Glob *glob)
+{
+ guint32 res;
+
+ res = glob->weight & 0xff;
+ if (glob->case_sensitive)
+ res |= 0x100;
+ return res;
+}
+
+static gchar **
+get_glob_list_value (gpointer data,
+ gchar *key)
+{
+ GList *list;
+ Glob *glob;
+ Type *type;
+ gchar **result;
+ gint i;
+
+ list = (GList *)g_hash_table_lookup ((GHashTable *)data, key);
+
+ result = g_new0 (gchar *, 1 + 3 * g_list_length (list));
+
+ i = 0;
+ for (; list; list = list->next)
+ {
+ glob = (Glob *)list->data;
+ type = glob->type;
+
+ result[i++] = g_strdup (glob->pattern);
+ result[i++] = g_strdup_printf ("%s/%s", type->media, type->subtype);
+ result[i++] = g_strdup_printf ("%ud", get_glob_weight_and_flags (glob));
+ }
+ return result;
+}
+
+static gboolean
+write_alias_cache (FILE *cache,
+ GHashTable *strings,
+ guint *offset)
+{
+ return write_map (cache, strings, alias_hash, NULL, get_type_value, FALSE, offset);
+}
+
+static void
+write_parent_entry (gpointer key,
+ gpointer data)
+{
+ gchar *mimetype = (gchar *)key;
+ MapData *map_data = (MapData *)data;
+ guint parents_offset, offset;
+ GList *parents;
+
+ parents = (GList *)g_hash_table_lookup (subclass_hash, mimetype);
+ offset = GPOINTER_TO_UINT (g_hash_table_lookup (map_data->pool, mimetype));
+ if (offset == 0)
+ {
+ g_warning ("Missing string: '%s'", (gchar *)key);
+ map_data->error = TRUE;
+ }
+
+ parents_offset = map_data->offset;
+ map_data->offset += 4 + 4 * g_list_length (parents);
+
+ if (!write_card32 (map_data->cache, offset) ||
+ !write_card32 (map_data->cache, parents_offset))
+ map_data->error = TRUE;
+}
+
+static void
+write_parent_list (gpointer key,
+ gpointer data)
+{
+ gchar *mimetype = (gchar *)key;
+ MapData *map_data = (MapData *)data;
+ guint offset;
+ GList *parents, *p;
+
+ parents = (GList *)g_hash_table_lookup (subclass_hash, mimetype);
+
+ if (!write_card32 (map_data->cache, g_list_length (parents)))
+ map_data->error = TRUE;
+
+ for (p = parents; p; p = p->next)
+ {
+ gchar *parent = (gchar *)p->data;
+
+ offset = GPOINTER_TO_UINT (g_hash_table_lookup (map_data->pool, parent));
+ if (offset == 0)
+ {
+ g_warning ("Missing string: '%s'", parent);
+ map_data->error = TRUE;
+ }
+
+ if (!write_card32 (map_data->cache, offset))
+ map_data->error = TRUE;
+ }
+
+ map_data->offset += 4 + 4 * g_list_length (parents);
+}
+
+static gboolean
+write_parent_cache (FILE *cache,
+ GHashTable *strings,
+ guint *offset)
+{
+ GPtrArray *keys;
+ MapData map_data;
+ FilterData filter_data;
+
+ keys = g_ptr_array_new ();
+
+ filter_data.keys = keys;
+ filter_data.filter = NULL;
+ g_hash_table_foreach (subclass_hash, add_key, &filter_data);
+
+ g_ptr_array_sort (keys, strcmp2);
+
+ if (!write_card32 (cache, keys->len))
+ return FALSE;
+
+ map_data.cache = cache;
+ map_data.pool = strings;
+ map_data.offset = *offset + 4 + keys->len * 8;
+ map_data.error = FALSE;
+
+ g_ptr_array_foreach (keys, write_parent_entry, &map_data);
+
+ map_data.offset = *offset + 4 + keys->len * 8;
+ g_ptr_array_foreach (keys, write_parent_list, &map_data);
+
+ *offset = map_data.offset;
+
+ return !map_data.error;
+}
+
+typedef enum
+{
+ GLOB_LITERAL,
+ GLOB_SIMPLE,
+ GLOB_FULL
+} GlobType;
+
+static GlobType
+glob_type (gchar *glob)
+{
+ gchar *ptr;
+ gboolean maybe_in_simple_glob = FALSE;
+ gboolean first_char = TRUE;
+
+ ptr = glob;
+
+ while (*ptr != '\0')
+ {
+ if (*ptr == '*' && first_char)
+ maybe_in_simple_glob = TRUE;
+ else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*')
+ return GLOB_FULL;
+
+ first_char = FALSE;
+ ptr = g_utf8_next_char (ptr);
+ }
+
+ if (maybe_in_simple_glob)
+ return GLOB_SIMPLE;
+
+ return GLOB_LITERAL;
+}
+
+static gboolean
+is_literal_glob (gpointer key)
+{
+ return glob_type ((gchar *)key) == GLOB_LITERAL;
+}
+
+static gboolean
+is_simple_glob (gpointer key)
+{
+ return glob_type ((gchar *)key) == GLOB_SIMPLE;
+}
+
+static gboolean
+is_full_glob (gpointer key)
+{
+ return glob_type ((gchar *)key) == GLOB_FULL;
+}
+
+static gboolean
+write_literal_cache (FILE *cache,
+ GHashTable *strings,
+ guint *offset)
+{
+ return write_map (cache, strings, globs_hash, is_literal_glob,
+ get_glob_list_value, TRUE, offset);
+}
+
+static gboolean
+write_glob_cache (FILE *cache,
+ GHashTable *strings,
+ guint *offset)
+{
+ return write_map (cache, strings, globs_hash, is_full_glob,
+ get_glob_list_value, TRUE, offset);
+}
+
+typedef struct _SuffixEntry SuffixEntry;
+
+struct _SuffixEntry
+{
+ gunichar character;
+ gchar *mimetype;
+ gint weight;
+ guint32 flags;
+ GList *children;
+ guint size;
+ guint depth;
+};
+
+static GList *
+insert_suffix (gunichar *suffix,
+ gchar *mimetype,
+ gint weight,
+ guint32 flags,
+ GList *suffixes)
+{
+ GList *l;
+ SuffixEntry *s = NULL;
+
+ for (l = suffixes; l; l = l->next)
+ {
+ s = (SuffixEntry *)l->data;
+
+ if (s->character > suffix[0])
+ {
+ s = g_new0 (SuffixEntry, 1);
+ s->character = suffix[0];
+ s->mimetype = NULL;
+ s->children = NULL;
+
+ suffixes = g_list_insert_before (suffixes, l, s);
+ }
+
+ if (s->character == suffix[0])
+ break;
+ }
+
+ if (!s || s->character != suffix[0])
+ {
+ s = g_new0 (SuffixEntry, 1);
+ s->character = suffix[0];
+ s->mimetype = NULL;
+ s->children = NULL;
+
+ suffixes = g_list_append (suffixes, s);
+ }
+
+ if (suffix[1] == 0)
+ {
+ GList *l2;
+ SuffixEntry *s2;
+ gboolean found = FALSE;
+
+ for (l2 = s->children; l2; l2 = l2->next)
+ {
+ s2 = (SuffixEntry *)l2->data;
+ if (s2->character != 0)
+ break;
+ if (strcmp (s2->mimetype, mimetype) == 0)
+ {
+ if (s2->weight < weight)
+ s2->weight = weight;
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found)
+ {
+ s2 = g_new0 (SuffixEntry, 1);
+ s2->character = 0;
+ s2->mimetype = mimetype;
+ s2->weight = weight;
+ s2->flags = flags;
+ s2->children = NULL;
+ s->children = g_list_insert_before (s->children, l2, s2);
+ }
+ }
+ else
+ s->children = insert_suffix (suffix + 1, mimetype, weight, flags, s->children);
+
+ return suffixes;
+}
+
+static void
+ucs4_reverse (gunichar *in, glong len)
+{
+ int i;
+ gunichar c;
+
+ for (i = 0; i < len - i - 1; i++)
+ {
+ c = in[i];
+ in[i] = in[len - i - 1];
+ in[len - i - 1] = c;
+ }
+}
+
+static void
+build_suffixes (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ gchar *pattern = (gchar *)key;
+ GList *list = (GList *)value;
+ GList **suffixes = (GList **)data;
+ gunichar *suffix;
+ gchar *mimetype;
+ Glob *glob;
+ Type *type;
+ glong len;
+ guint32 flags;
+
+ if (is_simple_glob (pattern))
+ {
+ suffix = g_utf8_to_ucs4 (pattern + 1, -1, NULL, &len, NULL);
+
+ if (suffix == NULL)
+ {
+ g_warning ("Glob '%s' is not valid UTF-8", pattern);
+ return;
+ }
+
+ ucs4_reverse (suffix, len);
+ for ( ; list; list = list->next)
+ {
+ glob = (Glob *)list->data;
+ type = glob->type;
+ mimetype = g_strdup_printf ("%s/%s", type->media, type->subtype);
+
+ flags = 0;
+ if (glob->case_sensitive)
+ flags |= 0x100;
+ *suffixes = insert_suffix (suffix, mimetype, glob->weight, flags, *suffixes);
+ }
+
+ g_free (suffix);
+ }
+}
+
+static void
+calculate_size (SuffixEntry *entry)
+{
+ GList *s;
+
+ entry->size = 0;
+ entry->depth = 0;
+ for (s = entry->children; s; s= s->next)
+ {
+ SuffixEntry *child = (SuffixEntry *)s->data;
+
+ calculate_size (child);
+ entry->size += 1 + child->size;
+ entry->depth = MAX (entry->depth, child->depth + 1);
+ }
+}
+
+static gboolean
+write_suffix_entries (FILE *cache,
+ guint depth,
+ SuffixEntry *entry,
+ GHashTable *strings,
+ guint *child_offset)
+{
+ GList *c;
+ guint offset;
+
+ if (depth > 0)
+ {
+ gboolean error = FALSE;
+
+ for (c = entry->children; c; c = c->next)
+ {
+ SuffixEntry *child = (SuffixEntry *)c->data;
+ if (!write_suffix_entries (cache, depth - 1, child, strings, child_offset))
+ error = TRUE;
+ }
+
+ return !error;
+ }
+
+ if (entry->mimetype)
+ {
+ offset = GPOINTER_TO_UINT(g_hash_table_lookup (strings, entry->mimetype));
+ if (offset == 0)
+ {
+ g_warning ("Missing string: '%s'", entry->mimetype);
+ return FALSE;
+ }
+ }
+ else
+ offset = 0;
+
+ if (entry->character == 0)
+ {
+ if (!write_card32 (cache, entry->character))
+ return FALSE;
+
+ if (!write_card32 (cache, offset))
+ return FALSE;
+
+ if (!write_card32 (cache, (entry->weight & 0xff) | entry->flags))
+ return FALSE;
+ }
+ else
+ {
+ if (!write_card32 (cache, entry->character))
+ return FALSE;
+
+ if (!write_card32 (cache, g_list_length (entry->children)))
+ return FALSE;
+
+ if (!write_card32 (cache, *child_offset))
+ return FALSE;
+ }
+
+ *child_offset += 12 * g_list_length (entry->children);
+
+ return TRUE;
+}
+
+static gboolean
+write_suffix_cache (FILE *cache,
+ GHashTable *strings,
+ guint *offset)
+{
+ GList *suffixes, *s;
+ guint n_entries;
+ guint child_offset;
+ guint depth, d;
+
+ suffixes = NULL;
+
+ g_hash_table_foreach (globs_hash, build_suffixes, &suffixes);
+
+ n_entries = g_list_length (suffixes);
+
+ *offset += 8;
+ child_offset = *offset + 12 * n_entries;
+ depth = 0;
+ for (s = suffixes; s; s= s->next)
+ {
+ SuffixEntry *entry = (SuffixEntry *)s->data;
+ calculate_size (entry);
+ depth = MAX (depth, entry->depth + 1);
+ }
+
+ if (!write_card32 (cache, n_entries) || !write_card32 (cache, *offset))
+ return FALSE;
+
+ for (d = 0; d < depth; d++)
+ {
+ for (s = suffixes; s; s = s->next)
+ {
+ SuffixEntry *entry = (SuffixEntry *)s->data;
+
+ if (!write_suffix_entries (cache, d, entry, strings, &child_offset))
+ return FALSE;
+ }
+ }
+
+ *offset = child_offset;
+
+ return TRUE;
+}
+
+typedef struct {
+ FILE *cache;
+ GHashTable *strings;
+ GList *matches;
+ guint offset;
+ gboolean error;
+} WriteMatchData;
+
+
+static void
+write_match (gpointer key,
+ gpointer data)
+{
+ Magic *magic = (Magic *)key;
+ WriteMatchData *mdata = (WriteMatchData *)data;
+ gchar *mimetype;
+ guint offset;
+
+ if (!write_card32 (mdata->cache, magic->priority))
+ {
+ mdata->error = TRUE;
+ return;
+ }
+
+ mimetype = g_strdup_printf ("%s/%s", magic->type->media, magic->type->subtype);
+ offset = GPOINTER_TO_UINT (g_hash_table_lookup (mdata->strings, mimetype));
+ if (offset == 0)
+ {
+ g_warning ("Missing string: '%s'", mimetype);
+ g_free (mimetype);
+ mdata->error = TRUE;
+ return;
+ }
+ g_free (mimetype);
+
+ if (!write_card32 (mdata->cache, offset))
+ {
+ mdata->error = TRUE;
+ return;
+ }
+
+ if (!write_card32 (mdata->cache, g_list_length (magic->matches)))
+ {
+ mdata->error = TRUE;
+ return;
+ }
+
+ offset = mdata->offset + 32 * g_list_index (mdata->matches, magic->matches->data);
+
+ if (!write_card32 (mdata->cache, offset))
+ {
+ mdata->error = TRUE;
+ return;
+ }
+}
+
+static gboolean
+write_matchlet (FILE *cache,
+ Match *match,
+ GList *matches,
+ gint offset,
+ gint *offset2)
+{
+ if (!write_card32 (cache, match->range_start) ||
+ !write_card32 (cache, match->range_length) ||
+ !write_card32 (cache, match->word_size) ||
+ !write_card32 (cache, match->data_length) ||
+ !write_card32 (cache, *offset2))
+ return FALSE;
+
+ *offset2 = ALIGN_VALUE (*offset2 + match->data_length, 4);
+
+ if (match->mask)
+ {
+ if (!write_card32 (cache, *offset2))
+ return FALSE;
+
+ *offset2 = ALIGN_VALUE (*offset2 + match->data_length, 4);
+ }
+ else
+ {
+ if (!write_card32 (cache, 0))
+ return FALSE;
+ }
+
+ if (match->matches)
+ {
+ if (!write_card32 (cache, g_list_length (match->matches)) ||
+ !write_card32 (cache, offset + 32 * g_list_index (matches, match->matches->data)))
+ return FALSE;
+ }
+ else
+ {
+ if (!write_card32 (cache, 0) ||
+ !write_card32 (cache, 0))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+write_matchlet_data (FILE *cache,
+ Match *match,
+ gint *offset2)
+{
+ if (!write_data (cache, match->data, match->data_length))
+ return FALSE;
+
+ *offset2 = ALIGN_VALUE (*offset2 + match->data_length, 4);
+
+ if (match->mask)
+ {
+ if (!write_data (cache, match->mask, match->data_length))
+ return FALSE;
+
+ *offset2 = ALIGN_VALUE (*offset2 + match->data_length, 4);
+ }
+
+ return TRUE;
+}
+
+static void
+collect_matches_list (GList *list, GList **matches)
+{
+ GList *l;
+
+ for (l = list; l; l = l->next)
+ *matches = g_list_prepend (*matches, l->data);
+
+ for (l = list; l; l = l->next)
+ {
+ Match *match = (Match *)l->data;
+ collect_matches_list (match->matches, matches);
+ }
+}
+
+static void
+collect_matches (gpointer key, gpointer data)
+{
+ Magic *magic = (Magic *)key;
+ GList **matches = (GList **)data;
+
+ collect_matches_list (magic->matches, matches);
+}
+
+static gboolean
+write_magic_cache (FILE *cache,
+ GHashTable *strings,
+ guint *offset)
+{
+ guint n_entries, max_extent;
+ gint offset2;
+ GList *m;
+ WriteMatchData data;
+
+ data.matches = NULL;
+ g_ptr_array_foreach (magic_array, collect_matches, &data.matches);
+ data.matches = g_list_reverse (data.matches);
+
+ max_extent = 0;
+ for (m = data.matches; m; m = m->next)
+ {
+ Match *match = (Match *)m->data;
+ max_extent = MAX (max_extent, match->data_length + match->range_start + match->range_length);
+ }
+
+ n_entries = magic_array->len;
+
+ *offset += 12;
+
+ if (!write_card32 (cache, n_entries) ||
+ !write_card32 (cache, max_extent) ||
+ !write_card32 (cache, *offset))
+ return FALSE;
+
+ *offset += 16 * n_entries;
+
+ data.cache = cache;
+ data.strings = strings;
+ data.offset = *offset;
+ data.error = FALSE;
+
+ offset2 = *offset + 32 * g_list_length (data.matches);
+
+ g_ptr_array_foreach (magic_array, write_match, &data);
+ for (m = data.matches; m; m = m->next)
+ {
+ Match *match = (Match *)m->data;
+ write_matchlet (cache, match, data.matches, *offset, &offset2);
+ }
+
+ offset2 = *offset + 32 * g_list_length (data.matches);
+
+ for (m = data.matches; m; m = m->next)
+ {
+ Match *match = (Match *)m->data;
+ write_matchlet_data (cache, match, &offset2);
+ }
+
+ *offset = offset2;
+
+ g_list_free (data.matches);
+
+ return !data.error;
+}
+
+static gchar **
+get_namespace_value (gpointer data,
+ gchar *key)
+{
+ Type *type;
+ gchar **result;
+ gchar *space;
+
+ type = (Type *)g_hash_table_lookup ((GHashTable *)data, key);
+
+ result = g_new0 (gchar *, 4);
+ space = strchr (key, ' ');
+ if (*space)
+ {
+ *space = '\0';
+ result[0] = g_strdup (key);
+ result[1] = g_strdup (space + 1);
+ *space = ' ';
+ }
+ else
+ result[0] = g_strdup (key);
+
+ result[2] = g_strdup_printf ("%s/%s", type->media, type->subtype);
+
+ return result;
+}
+
+static gboolean
+write_namespace_cache (FILE *cache,
+ GHashTable *strings,
+ guint *offset)
+{
+ return write_map (cache, strings, namespace_hash, NULL,
+ get_namespace_value, FALSE, offset);
+}
+
+static gchar **
+get_icon_value (gpointer data,
+ gchar *key)
+{
+ gchar *iconname;
+ gchar **result;
+
+ iconname = (gchar *)g_hash_table_lookup ((GHashTable *)data, key);
+
+ result = g_new0 (gchar *, 3);
+ result[0] = g_strdup (key);
+ result[1] = g_strdup (iconname);
+ result[2] = NULL;
+
+ return result;
+}
+
+static gboolean
+write_icons_cache (FILE *cache,
+ GHashTable *strings,
+ GHashTable *icon_hash,
+ guint *offset)
+{
+ return write_map (cache, strings, icon_hash, NULL,
+ get_icon_value, FALSE, offset);
+}
+
+/* Write all the collected types */
+static gboolean
+write_types_cache (FILE *cache,
+ GHashTable *strings,
+ GHashTable *types,
+ guint *offset)
+{
+ GPtrArray *lines;
+ int i;
+ char *mimetype;
+ guint mime_offset;
+
+ lines = g_ptr_array_new();
+
+ g_hash_table_foreach(types, add_type, lines);
+
+ g_ptr_array_sort(lines, strcmp2);
+
+ if (!write_card32 (cache, lines->len))
+ return FALSE;
+
+ for (i = 0; i < lines->len; i++)
+ {
+ mimetype = (char *) lines->pdata[i];
+ mime_offset = GPOINTER_TO_UINT (g_hash_table_lookup (strings, mimetype));
+ if (!write_card32 (cache, mime_offset))
+ return FALSE;
+
+ g_free(mimetype);
+ }
+
+ *offset += 4 + 4 * lines->len;
+
+ g_ptr_array_free(lines, TRUE);
+
+ return TRUE;
+}
+
+static void
+collect_alias (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GHashTable *strings = (GHashTable *)data;
+ Type *type = (Type *)value;
+ gchar *mimetype;
+
+ mimetype = g_strdup_printf ("%s/%s", type->media, type->subtype);
+ g_hash_table_insert (strings, key, NULL);
+ g_hash_table_insert (strings, mimetype, NULL);
+}
+
+
+static void
+collect_parents (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GList *parents = (GList *)value;
+ GHashTable *strings = (GHashTable *)data;
+ GList *p;
+
+ g_hash_table_insert (strings, key, NULL);
+ for (p = parents; p; p = p->next)
+ g_hash_table_insert (strings, p->data, NULL);
+}
+
+static void
+collect_glob (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GList *list = (GList *)value;
+ GHashTable *strings = (GHashTable *)data;
+ gchar *mimetype;
+ Glob *glob;
+ Type *type;
+
+ switch (glob_type ((char *)key))
+ {
+ case GLOB_LITERAL:
+ case GLOB_FULL:
+ g_hash_table_insert (strings, key, NULL);
+ break;
+ default:
+ break;
+ }
+
+ for (; list; list = list->next)
+ {
+ glob = (Glob *)list->data;
+ type = glob->type;
+ mimetype = g_strdup_printf ("%s/%s", type->media, type->subtype);
+
+ g_hash_table_insert (strings, mimetype, NULL);
+ }
+}
+
+static void
+collect_magic (gpointer key,
+ gpointer data)
+{
+ Magic *magic = (Magic *)key;
+ GHashTable *strings = (GHashTable *)data;
+ gchar *mimetype;
+
+ mimetype = g_strdup_printf ("%s/%s", magic->type->media, magic->type->subtype);
+ g_hash_table_insert (strings, mimetype, NULL);
+}
+
+static void
+collect_namespace (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ gchar *ns = (gchar *)key;
+ Type *type = (Type *)value;
+ GHashTable *strings = (GHashTable *)data;
+ gchar *mimetype;
+ gchar *space;
+
+ mimetype = g_strdup_printf ("%s/%s", type->media, type->subtype);
+ g_hash_table_insert (strings, mimetype, NULL);
+
+ space = strchr (ns, ' ');
+
+ if (space)
+ {
+ *space = '\0';
+ g_hash_table_insert (strings, g_strdup (ns), NULL);
+ g_hash_table_insert (strings, space + 1, NULL);
+ *space = ' ';
+ }
+ else
+ g_hash_table_insert (strings, ns, NULL);
+}
+
+static void
+collect_icons(gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ gchar *mimetype = (gchar *)key;
+ gchar *iconname = (gchar *)value;
+ GHashTable *strings = (GHashTable *)data;
+
+ g_hash_table_insert (strings, mimetype, NULL);
+ g_hash_table_insert (strings, iconname, NULL);
+}
+
+
+static void
+collect_strings (GHashTable *strings)
+{
+ g_hash_table_foreach (alias_hash, collect_alias, strings);
+ g_hash_table_foreach (subclass_hash, collect_parents, strings);
+ g_hash_table_foreach (globs_hash, collect_glob, strings);
+ g_ptr_array_foreach (magic_array, collect_magic, strings);
+ g_hash_table_foreach (namespace_hash, collect_namespace, strings);
+ g_hash_table_foreach (generic_icon_hash, collect_icons, strings);
+ g_hash_table_foreach (icon_hash, collect_icons, strings);
+}
+
+typedef struct
+{
+ FILE *cache;
+ GHashTable *strings;
+ guint offset;
+ gboolean error;
+} StringData;
+
+static void
+write_one_string (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ gchar *str = (gchar *)key;
+ StringData *sdata = (StringData *)data;
+
+ if (!write_string (sdata->cache, str))
+ sdata->error = TRUE;
+
+ g_hash_table_insert (sdata->strings, str, GUINT_TO_POINTER (sdata->offset));
+
+ sdata->offset = ALIGN_VALUE (sdata->offset + strlen (str) + 1, 4);
+}
+
+static gboolean
+write_strings (FILE *cache,
+ GHashTable *strings,
+ guint *offset)
+{
+ StringData data;
+
+ data.cache = cache;
+ data.strings = strings;
+ data.offset = *offset;
+ data.error = FALSE;
+
+ g_hash_table_foreach (strings, write_one_string, &data);
+
+ *offset = data.offset;
+
+ return !data.error;
+}
+
+static gboolean
+write_cache (FILE *cache)
+{
+ guint strings_offset;
+ guint alias_offset;
+ guint parent_offset;
+ guint literal_offset;
+ guint suffix_offset;
+ guint glob_offset;
+ guint magic_offset;
+ guint namespace_offset;
+ guint icons_list_offset;
+ guint generic_icons_list_offset;
+ guint type_offset;
+ guint offset;
+ GHashTable *strings;
+
+ offset = 0;
+ if (!write_header (cache, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &offset))
+ {
+ g_warning ("Failed to write header");
+ return FALSE;
+ }
+
+ strings = g_hash_table_new (g_str_hash, g_str_equal);
+ collect_strings (strings);
+ strings_offset = offset;
+
+ if (!write_strings (cache, strings, &offset))
+ {
+ g_warning ("Failed to write strings");
+ return FALSE;
+ }
+ g_message ("Wrote %d strings at %x - %x",
+ g_hash_table_size (strings), strings_offset, offset);
+
+ alias_offset = offset;
+ if (!write_alias_cache (cache, strings, &offset))
+ {
+ g_warning ("Failed to write alias list");
+ return FALSE;
+ }
+ g_message ("Wrote aliases at %x - %x", alias_offset, offset);
+
+ parent_offset = offset;
+ if (!write_parent_cache (cache, strings, &offset))
+ {
+ g_warning ("Failed to write parent list");
+ return FALSE;
+ }
+ g_message ("Wrote parents at %x - %x", parent_offset, offset);
+
+ literal_offset = offset;
+ if (!write_literal_cache (cache, strings, &offset))
+ {
+ g_warning ("Failed to write literal list");
+ return FALSE;
+ }
+ g_message ("Wrote literal globs at %x - %x", literal_offset, offset);
+
+ suffix_offset = offset;
+ if (!write_suffix_cache (cache, strings, &offset))
+ {
+ g_warning ("Failed to write suffix list");
+ return FALSE;
+ }
+ g_message ("Wrote suffix globs at %x - %x", suffix_offset, offset);
+
+ glob_offset = offset;
+ if (!write_glob_cache (cache, strings, &offset))
+ {
+ g_warning ("Failed to write glob list");
+ return FALSE;
+ }
+ g_message ("Wrote full globs at %x - %x", glob_offset, offset);
+
+ magic_offset = offset;
+ if (!write_magic_cache (cache, strings, &offset))
+ {
+ g_warning ("Failed to write magic list");
+ return FALSE;
+ }
+ g_message ("Wrote magic at %x - %x", magic_offset, offset);
+
+ namespace_offset = offset;
+ if (!write_namespace_cache (cache, strings, &offset))
+ {
+ g_warning ("Failed to write namespace list");
+ return FALSE;
+ }
+ g_message ("Wrote namespace list at %x - %x", namespace_offset, offset);
+
+ icons_list_offset = offset;
+ if (!write_icons_cache (cache, strings, icon_hash, &offset))
+ {
+ g_warning ("Failed to write icons list");
+ return FALSE;
+ }
+ g_message ("Wrote icons list at %x - %x", icons_list_offset, offset);
+
+ generic_icons_list_offset = offset;
+ if (!write_icons_cache (cache, strings, generic_icon_hash, &offset))
+ {
+ g_warning ("Failed to write generic icons list");
+ return FALSE;
+ }
+ g_message ("Wrote generic icons list at %x - %x", generic_icons_list_offset, offset);
+
+ type_offset = offset;
+ if (!write_types_cache (cache, strings, types, &offset))
+ {
+ g_warning ("Failed to write types list");
+ return FALSE;
+ }
+ g_message ("Wrote types list at %x - %x", type_offset, offset);
+
+ rewind (cache);
+ offset = 0;
+
+ if (!write_header (cache,
+ alias_offset, parent_offset, literal_offset,
+ suffix_offset, glob_offset, magic_offset,
+ namespace_offset, icons_list_offset,
+ generic_icons_list_offset, type_offset,
+ &offset))
+ {
+ g_warning ("Failed to rewrite header");
+ return FALSE;
+ }
+
+ g_hash_table_destroy (strings);
+
+ return TRUE;
+}
+
+
+static FILE *
+fopen_gerror(const char *filename, GError **error)
+{
+ FILE *stream = fopen(filename, "wb");
+
+ if (!stream)
+ set_error_from_errno(error);
+
+ return stream;
+}
+
+static gboolean
+fclose_gerror(FILE *f, GError **error)
+{
+ int err = ferror(f);
+ if (err != 0)
+ {
+ set_error_from_errno(error);
+ return FALSE;
+ }
+ if (fclose(f) != 0)
+ {
+ set_error_from_errno(error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gint64
+newest_mtime(const char *packagedir)
+{
+ GDir *dir;
+#if !GLIB_CHECK_VERSION(2,26,0)
+ struct stat GStatBuf;
+#else
+ GStatBuf statbuf;
+#endif
+ gint64 mtime = G_MININT64;
+ const char *name;
+ int retval;
+
+ retval = g_stat(packagedir, &statbuf);
+ if (retval < 0)
+ return mtime;
+ mtime = statbuf.st_mtime;
+
+ dir = g_dir_open(packagedir, 0, NULL);
+ if (!dir)
+ return mtime;
+
+ while ((name = g_dir_read_name(dir))) {
+ char *path;
+
+ path = g_build_filename(packagedir, name, NULL);
+ retval = g_stat(path, &statbuf);
+ g_free(path);
+ if (retval < 0)
+ continue;
+ if (statbuf.st_mtime > mtime)
+ mtime = statbuf.st_mtime;
+ }
+
+ g_dir_close(dir);
+ return mtime;
+}
+
+static gboolean
+is_cache_up_to_date (const char *mimedir, const char *packagedir)
+{
+ GStatBuf version_stat;
+ gint64 package_mtime;
+ char *mimeversion;
+ int retval;
+
+ mimeversion = g_build_filename(mimedir, "/version", NULL);
+ retval = g_stat(mimeversion, &version_stat);
+ g_free(mimeversion);
+ if (retval < 0)
+ return FALSE;
+
+ package_mtime = newest_mtime(packagedir);
+ if (package_mtime < 0)
+ return FALSE;
+
+ return version_stat.st_mtime >= package_mtime;
+}
+
+int main(int argc, char **argv)
+{
+ char *mime_dir = NULL;
+ char *package_dir = NULL;
+ int opt;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+ gboolean if_newer = FALSE;
+
+ /* Install the filtering log handler */
+ g_log_set_default_handler(g_log_handler, NULL);
+
+ while ((opt = getopt(argc, argv, "hvVn")) != -1)
+ {
+ switch (opt)
+ {
+ case '?':
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ case 'h':
+ usage(argv[0]);
+ return EXIT_SUCCESS;
+ case 'v':
+ g_fprintf(stderr,
+ "update-mime-database (" PACKAGE ") "
+ VERSION "\n" COPYING);
+ return EXIT_SUCCESS;
+ case 'V':
+ enabled_log_levels |= G_LOG_LEVEL_MESSAGE
+ | G_LOG_LEVEL_INFO;
+ break;
+ case 'n':
+ if_newer = TRUE;
+ break;
+ default:
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (optind != argc - 1)
+ {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ LIBXML_TEST_VERSION;
+
+ mime_dir = argv[optind];
+
+ /* Strip trailing / characters */
+ {
+ int l = strlen(mime_dir);
+ while (l > 1 && mime_dir[l - 1] == '/')
+ {
+ l--;
+ mime_dir[l] = '\0';
+ }
+ }
+
+ package_dir = g_strconcat(mime_dir, "/packages", NULL);
+
+ if (access(mime_dir, F_OK))
+ {
+ g_warning(_("Directory '%s' does not exist!"), package_dir);
+ return EXIT_FAILURE;
+ }
+
+ g_message("Updating MIME database in %s...\n", mime_dir);
+
+ if (access(package_dir, F_OK))
+ {
+ g_fprintf(stderr,
+ _("Directory '%s' does not exist!\n"), package_dir);
+ return EXIT_FAILURE;
+ }
+
+ if (if_newer && is_cache_up_to_date(mime_dir, package_dir)) {
+ g_message ("Skipping mime update as the cache is up-to-date");
+ return EXIT_SUCCESS;
+ }
+
+ types = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, free_type);
+ globs_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, NULL);
+ namespace_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, NULL);
+ magic_array = g_ptr_array_new();
+ tree_magic_array = g_ptr_array_new();
+ subclass_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, free_string_list);
+ alias_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, NULL);
+ icon_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, NULL);
+ generic_icon_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ scan_source_dir(package_dir);
+ g_free(package_dir);
+
+ delete_old_types(mime_dir);
+
+ g_hash_table_foreach(types, write_out_type, (gpointer) mime_dir);
+
+ {
+ FILE *globs;
+ char *globs_path;
+ GList *glob_list = NULL;
+
+ g_hash_table_foreach(globs_hash, collect_glob2, &glob_list);
+ glob_list = g_list_sort(glob_list, (GCompareFunc)compare_glob_by_weight);
+ globs_path = g_strconcat(mime_dir, "/globs.new", NULL);
+ globs = fopen_gerror(globs_path, error);
+ if (!globs)
+ goto out;
+ g_fprintf(globs,
+ "# This file was automatically generated by the\n"
+ "# update-mime-database command. DO NOT EDIT!\n");
+ write_out_glob(glob_list, globs);
+ if (!fclose_gerror(globs, error))
+ goto out;
+ if (!atomic_update(globs_path, error))
+ goto out;
+ g_free(globs_path);
+
+ globs_path = g_strconcat(mime_dir, "/globs2.new", NULL);
+ globs = fopen_gerror(globs_path, error);
+ if (!globs)
+ goto out;
+ g_fprintf(globs,
+ "# This file was automatically generated by the\n"
+ "# update-mime-database command. DO NOT EDIT!\n");
+ write_out_glob2 (glob_list, globs);
+ if (!fclose_gerror(globs, error))
+ goto out;
+ if (!atomic_update(globs_path, error))
+ goto out;
+ g_free(globs_path);
+
+ g_list_free (glob_list);
+ }
+
+ {
+ FILE *stream;
+ char *magic_path;
+ int i;
+ magic_path = g_strconcat(mime_dir, "/magic.new", NULL);
+ stream = fopen_gerror(magic_path, error);
+ if (!stream)
+ goto out;
+ fwrite("MIME-Magic\0\n", 1, 12, stream);
+
+ if (magic_array->len)
+ g_ptr_array_sort(magic_array, cmp_magic);
+ for (i = 0; i < magic_array->len; i++)
+ {
+ Magic *magic = (Magic *) magic_array->pdata[i];
+
+ write_magic(stream, magic);
+ }
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(magic_path, error))
+ goto out;
+ g_free(magic_path);
+ }
+
+ {
+ FILE *stream;
+ char *ns_path;
+
+ ns_path = g_strconcat(mime_dir, "/XMLnamespaces.new", NULL);
+ stream = fopen_gerror(ns_path, error);
+ if (!stream)
+ goto out;
+ write_namespaces(stream);
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(ns_path, error))
+ goto out;
+ g_free(ns_path);
+ }
+
+ {
+ FILE *stream;
+ char *path;
+
+ path = g_strconcat(mime_dir, "/subclasses.new", NULL);
+ stream = fopen_gerror(path, error);
+ if (!stream)
+ goto out;
+ write_subclasses(stream);
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(path, error))
+ goto out;
+ g_free(path);
+ }
+
+ {
+ FILE *stream;
+ char *path;
+
+ path = g_strconcat(mime_dir, "/aliases.new", NULL);
+ stream = fopen_gerror(path, error);
+ if (!stream)
+ goto out;
+ write_aliases(stream);
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(path, error))
+ goto out;
+ g_free(path);
+ }
+
+ {
+ FILE *stream;
+ char *path;
+
+ path = g_strconcat(mime_dir, "/types.new", NULL);
+ stream = fopen_gerror(path, error);
+ if (!stream)
+ goto out;
+ write_types(stream);
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(path, error))
+ goto out;
+ g_free(path);
+ }
+
+ {
+ FILE *stream;
+ char *icon_path;
+
+ icon_path = g_strconcat(mime_dir, "/generic-icons.new", NULL);
+ stream = fopen_gerror(icon_path, error);
+ if (!stream)
+ goto out;
+ write_icons(generic_icon_hash, stream);
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(icon_path, error))
+ goto out;
+ g_free(icon_path);
+ }
+
+ {
+ FILE *stream;
+ char *icon_path;
+
+ icon_path = g_strconcat(mime_dir, "/icons.new", NULL);
+ stream = fopen_gerror(icon_path, error);
+ if (!stream)
+ goto out;
+ write_icons(icon_hash, stream);
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(icon_path, error))
+ goto out;
+ g_free(icon_path);
+ }
+
+ {
+ FILE *stream;
+ char *path;
+ int i;
+ path = g_strconcat(mime_dir, "/treemagic.new", NULL);
+ stream = fopen_gerror(path, error);
+ if (!stream)
+ goto out;
+ fwrite("MIME-TreeMagic\0\n", 1, 16, stream);
+
+ if (tree_magic_array->len)
+ g_ptr_array_sort(tree_magic_array, cmp_tree_magic);
+ for (i = 0; i < tree_magic_array->len; i++)
+ {
+ TreeMagic *magic = (TreeMagic *) tree_magic_array->pdata[i];
+
+ write_tree_magic(stream, magic);
+ }
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(path, error))
+ goto out;
+ g_free(path);
+ }
+
+ {
+ FILE *stream;
+ char *path;
+
+ path = g_strconcat(mime_dir, "/mime.cache.new", NULL);
+ stream = fopen_gerror(path, error);
+ if (!stream)
+ goto out;
+ write_cache(stream);
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(path, error))
+ goto out;
+ g_free(path);
+ }
+
+ {
+ FILE *stream;
+ char *path;
+
+ path = g_strconcat(mime_dir, "/version.new", NULL);
+ stream = fopen_gerror(path, error);
+ if (!stream)
+ goto out;
+ g_fprintf(stream,
+ VERSION "\n");
+ if (!fclose_gerror(stream, error))
+ goto out;
+ if (!atomic_update(path, error))
+ goto out;
+ g_free(path);
+ }
+
+ g_ptr_array_foreach(magic_array, (GFunc)magic_free, NULL);
+ g_ptr_array_free(magic_array, TRUE);
+ g_ptr_array_foreach(tree_magic_array, (GFunc)tree_magic_free, NULL);
+ g_ptr_array_free(tree_magic_array, TRUE);
+
+ g_hash_table_destroy(types);
+ g_hash_table_destroy(globs_hash);
+ g_hash_table_destroy(namespace_hash);
+ g_hash_table_destroy(subclass_hash);
+ g_hash_table_destroy(alias_hash);
+ g_hash_table_destroy(icon_hash);
+ g_hash_table_destroy(generic_icon_hash);
+
+ check_in_path_xdg_data(mime_dir);
+
+out:
+ if (local_error != NULL)
+ fatal_gerror(local_error);
+ return EXIT_SUCCESS;
+}