upload tizen1.0 source
[external/shared-mime-info.git] / update-mime-database.c
1 #include <config.h>
2
3 #define N_(x) x
4 #define _(x) (x)
5
6 #include <string.h>
7 #include <ctype.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <glib.h>
12 #include <glib/gprintf.h>
13 #include <errno.h>
14 #include <dirent.h>
15 #include <libxml/parser.h>
16 #include <libxml/tree.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19
20 #define XML_NS XML_XML_NAMESPACE
21 #define XMLNS_NS "http://www.w3.org/2000/xmlns/"
22 #define FREE_NS (xmlChar *)"http://www.freedesktop.org/standards/shared-mime-info"
23
24 #define COPYING                                                         \
25              N_("Copyright (C) 2003 Thomas Leonard.\n"                  \
26                 "update-mime-database comes with ABSOLUTELY NO WARRANTY,\n" \
27                 "to the extent permitted by law.\n"                     \
28                 "You may redistribute copies of update-mime-database\n" \
29                 "under the terms of the GNU General Public License.\n"  \
30                 "For more information about these matters, "            \
31                 "see the file named COPYING.\n")
32
33 #define MIME_ERROR g_quark_from_static_string("mime-error-quark")
34
35 #ifndef PATH_SEPARATOR
36 # ifdef _WIN32
37 #  define PATH_SEPARATOR ";"
38 # else
39 #  define PATH_SEPARATOR ":"
40 # endif
41 #endif
42
43 /* This is the list of directories to scan when finding old type files to
44  * delete. It is also used to warn about invalid MIME types.
45  */
46 const char *media_types[] = {
47         "text",
48         "application",
49         "image",
50         "audio",
51         "inode",
52         "video",
53         "message",
54         "model",
55         "multipart",
56         "x-content",
57         "x-epoc",
58 };
59
60 /* Represents a MIME type */
61 typedef struct _Type Type;
62
63 /* A parsed <magic> element */
64 typedef struct _Magic Magic;
65
66 /* A parsed <match> element */
67 typedef struct _Match Match;
68
69 /* A parsed <treemagic> element */
70 typedef struct _TreeMagic TreeMagic;
71
72 /* A parsed <treematch> element */
73 typedef struct _TreeMatch TreeMatch;
74
75 /* A parsed <glob> element */
76 typedef struct _Glob Glob;
77
78 struct _Type {
79         char *media;
80         char *subtype;
81
82         /* Contains xmlNodes for elements that are being copied to the output.
83          * That is, <comment>, <sub-class-of> and <alias> nodes, and anything
84          * with an unknown namespace.
85          */
86         xmlDoc  *output;
87 };
88
89 struct _Glob {
90         int weight;
91         char *pattern;
92         Type *type;
93 };
94
95 struct _Magic {
96         int priority;
97         Type *type;
98         GList *matches;
99 };
100
101 struct _Match {
102         long range_start;
103         int range_length;
104         char word_size;
105         int data_length;
106         char *data;
107         char *mask;
108         GList *matches;
109 };
110
111 struct _TreeMagic {
112         int priority;
113         Type *type;
114         GList *matches;
115 };
116
117 struct _TreeMatch {
118         char *path;
119         gboolean match_case;
120         gboolean executable;
121         gboolean non_empty;
122         gint type;
123         char *mimetype;
124
125         GList *matches;
126 };
127
128 /* Maps MIME type names to Types */
129 static GHashTable *types = NULL;
130
131 /* Maps "namespaceURI localName" strings to Types */
132 static GHashTable *namespace_hash = NULL;
133
134 /* Maps glob patterns to Types */
135 static GHashTable *globs_hash = NULL;
136
137 /* 'magic' nodes */
138 static GPtrArray *magic_array = NULL;
139
140 /* 'treemagic' nodes */
141 static GPtrArray *tree_magic_array = NULL;
142
143 /* Maps MIME type names to superclass names */
144 static GHashTable *subclass_hash = NULL;
145
146 /* Maps aliases to Types */
147 static GHashTable *alias_hash = NULL;
148
149 /* Maps MIME type names to icon names */
150 static GHashTable *icon_hash = NULL;
151
152 /* Maps MIME type names to icon names */
153 static GHashTable *generic_icon_hash = NULL;
154
155 /* Lists enabled log levels */
156 static GLogLevelFlags enabled_log_levels = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING;
157
158 /* Static prototypes */
159 static Magic *magic_new(xmlNode *node, Type *type, GError **error);
160
161 static TreeMagic *tree_magic_new(xmlNode *node, Type *type, GError **error);
162
163 static void g_log_handler (const gchar   *log_domain,
164                            GLogLevelFlags log_level,
165                            const gchar   *message,
166                            gpointer       unused_data)
167 {
168     if (log_level & enabled_log_levels) {
169         g_printf("%s\n", message);
170     }
171 }
172
173
174 static void usage(const char *name)
175 {
176         g_fprintf(stderr, _("Usage: %s [-hvV] MIME-DIR\n"), name);
177 }
178
179 static void free_type(gpointer data)
180 {
181         Type *type = (Type *) data;
182
183         g_free(type->media);
184         g_free(type->subtype);
185
186         xmlFreeDoc(type->output);
187
188         g_free(type);
189 }
190
191 /* Ugly workaround to shut up gcc4 warnings about signedness issues
192  * (xmlChar is typedef'ed to unsigned char)
193  */
194 static char *my_xmlGetNsProp (xmlNodePtr node, 
195                               const char *name,
196                               const xmlChar *namespace)
197 {
198         return (char *)xmlGetNsProp (node, (xmlChar *)name, namespace);
199 }
200
201 /* If we've seen this type before, return the existing object.
202  * Otherwise, create a new one. Checks that the name looks sensible;
203  * if not, sets error and returns NULL.
204  * Also warns about unknown media types, but does not set error.
205  */
206 static Type *get_type(const char *name, GError **error)
207 {
208         xmlNode *root;
209         xmlNs *ns;
210         const char *slash;
211         Type *type;
212         int i;
213
214         slash = strchr(name, '/');
215         if (!slash || strchr(slash + 1, '/'))
216         {
217                 g_set_error(error, MIME_ERROR, 0,
218                                 _("Invalid MIME-type '%s'"), name);
219                 return NULL;
220         }
221
222         type = g_hash_table_lookup(types, name);
223         if (type)
224                 return type;
225
226         type = g_new(Type, 1);
227         type->media = g_strndup(name, slash - name);
228         type->subtype = g_strdup(slash + 1);
229         g_hash_table_insert(types, g_strdup(name), type);
230
231         type->output = xmlNewDoc((xmlChar *)"1.0");
232         root = xmlNewDocNode(type->output, NULL, (xmlChar *)"mime-type", NULL);
233         ns = xmlNewNs(root, FREE_NS, NULL);
234         xmlSetNs(root, ns);
235         xmlDocSetRootElement(type->output, root);
236         xmlSetNsProp(root, NULL, (xmlChar *)"type", (xmlChar *)name);
237         xmlAddChild(root, xmlNewDocComment(type->output,
238                 (xmlChar *)"Created automatically by update-mime-database. DO NOT EDIT!"));
239
240         for (i = 0; i < G_N_ELEMENTS(media_types); i++)
241         {
242                 if (strcmp(media_types[i], type->media) == 0)
243                         return type;
244         }
245
246         g_warning("Unknown media type in type '%s'\n", name);
247
248         return type;
249 }
250
251 /* Test that this node has the expected name and namespace */
252 static gboolean match_node(xmlNode *node,
253                            const char *namespaceURI,
254                            const char *localName)
255 {
256         if (namespaceURI)
257                 return node->ns &&
258                         strcmp((char *)node->ns->href, namespaceURI) == 0 &&
259                         strcmp((char *)node->name, localName) == 0;
260         else
261                 return strcmp((char *)node->name, localName) == 0 && !node->ns;
262 }
263
264 static int get_int_attribute(xmlNode *node, const char *name)
265 {
266         char *prio_string;
267         int p;
268
269         prio_string = my_xmlGetNsProp(node, name, NULL);
270         if (prio_string)
271         {
272                 char *end;
273
274                 p = strtol(prio_string, &end, 10);
275                 if (*prio_string == '\0' || *end != '\0')
276                         p = -1;
277                 xmlFree(prio_string);
278                 if (p < 0 || p > 100)
279                         return -1;
280                 return p;
281         }
282         else
283                 return 50;
284 }
285
286 /* Return the priority of a <magic> node.
287  * Returns 50 if no priority is given, or -1 if a priority is given but
288  * is invalid.
289  */
290 static int get_priority(xmlNode *node)
291 {
292        return get_int_attribute (node, "priority");
293 }
294
295 /* Return the weight a <glob> node.
296  * Returns 50 if no weight is given, or -1 if a weight is given but
297  * is invalid.
298  */
299 static int get_weight(xmlNode *node)
300 {
301        return get_int_attribute (node, "weight");
302 }
303
304 /* Process a <root-XML> element by adding a rule to namespace_hash */
305 static void add_namespace(Type *type, const char *namespaceURI,
306                           const char *localName, GError **error)
307 {
308         g_return_if_fail(type != NULL);
309
310         if (!namespaceURI)
311         {
312                 g_set_error(error, MIME_ERROR, 0,
313                         _("Missing 'namespaceURI' attribute'"));
314                 return;
315         }
316
317         if (!localName)
318         {
319                 g_set_error(error, MIME_ERROR, 0,
320                         _("Missing 'localName' attribute'"));
321                 return;
322         }
323
324         if (!*namespaceURI && !*localName)
325         {
326                 g_set_error(error, MIME_ERROR, 0,
327                         _("namespaceURI and localName attributes can't "
328                           "both be empty"));
329                 return;
330         }
331
332         if (strpbrk(namespaceURI, " \n") || strpbrk(localName, " \n"))
333         {
334                 g_set_error(error, MIME_ERROR, 0,
335                         _("namespaceURI and localName cannot contain "
336                           "spaces or newlines"));
337                 return;
338         }
339
340         g_hash_table_insert(namespace_hash,
341                         g_strconcat(namespaceURI, " ", localName, NULL),
342                         type);
343 }
344
345 /* 'field' was found in the definition of 'type' and has the freedesktop.org
346  * namespace. If it's a known field, process it and return TRUE, else
347  * return FALSE to add it to the output XML document.
348  * On error, returns FALSE and sets 'error'.
349  */
350 static gboolean process_freedesktop_node(Type *type, xmlNode *field,
351                                          GError **error)
352 {
353         if (strcmp((char *)field->name, "glob") == 0)
354         {
355                 gchar *pattern; 
356                 gint weight;
357
358                 weight = get_weight(field);
359
360                 if (weight == -1)
361                 {
362                         g_set_error(error, MIME_ERROR, 0,
363                                     _("Bad weight (%d) in <glob> element"), weight);
364                 }
365                 pattern = my_xmlGetNsProp(field, "pattern", NULL);
366
367                 if (pattern && *pattern)
368                 {
369                         Glob *glob;
370                         GList *list = g_hash_table_lookup (globs_hash, pattern);
371                         
372                         glob = g_new0 (Glob, 1);
373                         glob->pattern = g_strdup (pattern);
374                         glob->type = type;
375                         glob->weight = weight;
376                         list = g_list_append (list, glob);
377                         g_hash_table_insert(globs_hash, g_strdup (glob->pattern), list);
378                         xmlFree(pattern);
379                 }
380                 else
381                 {
382                         if (pattern)
383                                 xmlFree(pattern);
384                         g_set_error(error, MIME_ERROR, 0,
385                                 _("Missing 'pattern' attribute in <glob> "
386                                   "element"));
387                 }
388         }
389         else if (strcmp((char *)field->name, "magic") == 0)
390         {
391                 Magic *magic;
392
393                 magic = magic_new(field, type, error);
394
395                 if (!*error)
396                 {
397                         g_return_val_if_fail(magic != NULL, FALSE);
398                         g_ptr_array_add(magic_array, magic);
399                 }
400                 else
401                         g_return_val_if_fail(magic == NULL, FALSE);
402         }
403         else if (strcmp((char *)field->name, "treemagic") == 0)
404         {
405                 TreeMagic *magic;
406
407                 magic = tree_magic_new(field, type, error);
408
409                 if (!*error)
410                 {
411                         g_return_val_if_fail(magic != NULL, FALSE);
412                         g_ptr_array_add(tree_magic_array, magic);
413                 }
414                 else
415                         g_return_val_if_fail(magic == NULL, FALSE);
416         }
417         else if (strcmp((char *)field->name, "comment") == 0 ||
418                  strcmp((char *)field->name, "acronym") == 0 ||
419                  strcmp((char *)field->name, "expanded-acronym") == 0)
420                 return FALSE;
421         else if (strcmp((char *)field->name, "alias") == 0 ||
422                  strcmp((char *)field->name, "sub-class-of") == 0)
423         {
424                 char *other_type;
425                 gboolean valid;
426                 GSList *list, *nlist;
427
428                 other_type = my_xmlGetNsProp(field, "type", NULL);
429                 valid = other_type && strchr(other_type, '/');
430                 if (valid)
431                 {
432                         char *typename;
433
434                         typename = g_strdup_printf("%s/%s", 
435                                                    type->media,
436                                                    type->subtype);
437                         
438                         if (strcmp((char *)field->name, "alias") == 0)
439                                 g_hash_table_insert(alias_hash,
440                                                     g_strdup(other_type), type);
441                                 
442                         else
443                         {
444                                 list = g_hash_table_lookup(subclass_hash, typename);
445                                 nlist = g_slist_append (list, g_strdup(other_type));
446                                 if (list == NULL)
447                                         g_hash_table_insert(subclass_hash, 
448                                                             g_strdup(typename), nlist);    
449                         }
450                         g_free(typename);
451                         xmlFree(other_type);
452                         
453                         return FALSE;   /* Copy through */
454                 }
455                 
456                 xmlFree(other_type);
457                 g_set_error(error, MIME_ERROR, 0,
458                         _("Incorrect or missing 'type' attribute "
459                           "in <%s>"), field->name);
460         }
461         else if (strcmp((char *)field->name, "root-XML") == 0)
462         {
463                 char *namespaceURI, *localName;
464
465                 namespaceURI = my_xmlGetNsProp(field, "namespaceURI", NULL);
466                 localName = my_xmlGetNsProp(field, "localName", NULL);
467
468                 add_namespace(type, namespaceURI, localName, error);
469
470                 if (namespaceURI)
471                         xmlFree(namespaceURI);
472                 if (localName)
473                         xmlFree(localName);
474         }
475         else if (strcmp((char *)field->name, "generic-icon") == 0 ||
476                  strcmp((char *)field->name, "icon") == 0) 
477         {
478                 char *icon;
479                 char *typename;
480
481                 icon = my_xmlGetNsProp(field, "name", NULL);
482
483                 if (icon) 
484                 {
485                         typename = g_strdup_printf("%s/%s",
486                                                    type->media,
487                                                    type->subtype);
488
489                         if (strcmp((char *)field->name, "icon") == 0)
490                                 g_hash_table_insert(icon_hash,
491                                                     typename, g_strdup (icon));
492                         else
493                                 g_hash_table_insert(generic_icon_hash,
494                                                     typename, g_strdup (icon));
495
496                         xmlFree (icon);
497
498                         return FALSE;   /* Copy through */
499                 }
500         }
501
502         return !*error;
503 }
504
505 /* Checks to see if 'node' has the given value for xml:lang.
506  * If 'lang' is NULL, checks that 'node' doesn't have an xml:lang.
507  */
508 static gboolean has_lang(xmlNode *node, const char *lang)
509 {
510         char *lang2;
511
512         lang2 = my_xmlGetNsProp(node, "lang", XML_NS);
513         if (!lang2)
514                 return !lang;
515
516         if (lang && strcmp(lang, lang2) == 0)
517         {
518                 xmlFree(lang2);
519                 return TRUE;
520         }
521         xmlFree(lang2);
522         return FALSE;
523 }
524
525 /* We're about to add 'new' to the list of fields to be output for the
526  * type. Remove any existing nodes which it replaces.
527  */
528 static void remove_old(Type *type, xmlNode *new)
529 {
530         xmlNode *field, *fields;
531         char *lang;
532
533         if (new->ns == NULL || xmlStrcmp(new->ns->href, FREE_NS) != 0)
534                 return; /* No idea what we're doing -- leave it in! */
535
536         if (strcmp((char *)new->name, "comment") != 0)
537                 return;
538
539         lang = my_xmlGetNsProp(new, "lang", XML_NS);
540
541         fields = xmlDocGetRootElement(type->output);
542         for (field = fields->xmlChildrenNode; field; field = field->next)
543         {
544                 if (match_node(field, (char *)FREE_NS, "comment") &&
545                     has_lang(field, lang))
546                 {
547                         xmlUnlinkNode(field);
548                         xmlFreeNode(field);
549                         break;
550                 }
551         }
552
553         xmlFree(lang);
554 }
555
556 /* 'node' is a <mime-type> node from a source file, whose type is 'type'.
557  * Process all the child elements, setting 'error' if anything goes wrong.
558  */
559 static void load_type(Type *type, xmlNode *node, GError **error)
560 {
561         xmlNode *field;
562
563         g_return_if_fail(type != NULL);
564         g_return_if_fail(node != NULL);
565         g_return_if_fail(error != NULL);
566
567         for (field = node->xmlChildrenNode; field; field = field->next)
568         {
569                 xmlNode *copy;
570
571                 if (field->type != XML_ELEMENT_NODE)
572                         continue;
573
574                 if (field->ns && xmlStrcmp(field->ns->href, FREE_NS) == 0)
575                 {
576                         if (process_freedesktop_node(type, field, error))
577                         {
578                                 g_return_if_fail(*error == NULL);
579                                 continue;
580                         }
581                 }
582
583                 if (*error)
584                         return;
585
586                 copy = xmlDocCopyNode(field, type->output, 1);
587                 
588                 /* Ugly hack to stop the xmlns= attributes appearing on
589                  * every node...
590                  */
591                 if (copy->ns && copy->ns->prefix == NULL &&
592                         xmlStrcmp(copy->ns->href, FREE_NS) == 0)
593                 {
594                         if (copy->nsDef)
595                         {
596                                 /* Still used somewhere... */
597                                 /* xmlFreeNsList(copy->nsDef); */
598                                 /* (this leaks) */
599                                 copy->nsDef = NULL;
600                         }
601                 }
602
603                 remove_old(type, field);
604
605                 xmlAddChild(xmlDocGetRootElement(type->output), copy);
606         }
607 }
608
609 /* Parse 'filename' as an XML file and add all the information to the
610  * database. If called more than once, information read in later calls
611  * overrides information read previously.
612  */
613 static void load_source_file(const char *filename)
614 {
615         xmlDoc *doc;
616         xmlNode *root, *node;
617
618         doc = xmlParseFile(filename);
619         if (!doc)
620         {
621                 g_warning(_("Failed to parse '%s'\n"), filename);
622                 return;
623         }
624
625         root = xmlDocGetRootElement(doc);
626
627         if (root->ns == NULL || xmlStrcmp(root->ns->href, FREE_NS) != 0)
628         {
629                 g_warning("* Wrong namespace on document element\n"
630                           "*   in '%s'\n"
631                           "*   (should be %s)\n", filename, FREE_NS);
632                 goto out;
633         }
634         
635         if (strcmp((char *)root->name, "mime-info") != 0)
636         {
637                 g_warning("* Root element <%s> is not <mime-info>\n"
638                           "*   (in '%s')\n", root->name, filename);
639                 goto out;
640         }
641
642         for (node = root->xmlChildrenNode; node; node = node->next)
643         {
644                 Type *type = NULL;
645                 char *type_name = NULL;
646                 GError *error = NULL;
647
648                 if (node->type != XML_ELEMENT_NODE)
649                         continue;
650
651                 if (!match_node(node, (char *)FREE_NS, "mime-type"))
652                         g_set_error(&error, MIME_ERROR, 0,
653                                 _("Excepted <mime-type>, but got wrong name "
654                                   "or namespace"));
655
656                 if (!error)
657                 {
658                         type_name = my_xmlGetNsProp(node, "type", NULL);
659
660                         if (!type_name)
661                                 g_set_error(&error, MIME_ERROR, 0,
662                                         _("<mime-type> element has no 'type' "
663                                           "attribute"));
664                 }
665
666                 if (type_name)
667                 {
668                         type = get_type(type_name, &error);
669                         xmlFree(type_name);
670                 }
671
672                 if (!error)
673                 {
674                         g_return_if_fail(type != NULL);
675                         load_type(type, node, &error);
676                 }
677                 else
678                         g_return_if_fail(type == NULL);
679
680                 if (error)
681                 {
682                         g_warning("* Error in type '%s/%s'\n"
683                                   "*   (in %s):\n"
684                                   "*   %s.\n",
685                                   type ? type->media : _("unknown"),
686                                   type ? type->subtype : _("unknown"),
687                                   filename, error->message);
688                         g_error_free(error);
689                 }
690         }
691 out:
692         xmlFreeDoc(doc);
693 }
694
695 /* Used as the sort function for sorting GPtrArrays */
696 static gint strcmp2(gconstpointer a, gconstpointer b)
697 {
698         const char *aa = *(char **) a;
699         const char *bb = *(char **) b;
700
701         return strcmp(aa, bb);
702 }
703
704 /* 'path' should be a 'packages' directory. Loads the information from
705  * every file in the directory.
706  */
707 static void scan_source_dir(const char *path)
708 {
709         DIR *dir;
710         struct dirent *ent;
711         char *filename;
712         GPtrArray *files;
713         int i;
714         gboolean have_override = FALSE;
715
716         dir = opendir(path);
717         if (!dir)
718         {
719                 perror("scan_source_dir");
720                 exit(EXIT_FAILURE);
721         }
722
723         files = g_ptr_array_new();
724         while ((ent = readdir(dir)))
725         {
726                 int l;
727                 l = strlen(ent->d_name);
728                 if (l < 4 || strcmp(ent->d_name + l - 4, ".xml") != 0)
729                         continue;
730                 if (strcmp(ent->d_name, "Override.xml") == 0)
731                 {
732                         have_override = TRUE;
733                         continue;
734                 }
735                 g_ptr_array_add(files, g_strdup(ent->d_name));
736         }
737         closedir(dir);
738
739         g_ptr_array_sort(files, strcmp2);
740
741         if (have_override)
742                 g_ptr_array_add(files, g_strdup("Override.xml"));
743
744         for (i = 0; i < files->len; i++)
745         {
746                 gchar *leaf = (gchar *) files->pdata[i];
747
748                 filename = g_strconcat(path, "/", leaf, NULL);
749                 load_source_file(filename);
750                 g_free(filename);
751         }
752
753         for (i = 0; i < files->len; i++)
754                 g_free(files->pdata[i]);
755         g_ptr_array_free(files, TRUE);
756 }
757
758 /* Save doc as XML as filename, 0 on success or -1 on failure */
759 static int save_xml_file(xmlDocPtr doc, const gchar *filename)
760 {
761 #if LIBXML_VERSION > 20400
762         if (xmlSaveFormatFileEnc(filename, doc, "utf-8", 1) < 0)
763                 return 1;
764 #else
765         FILE *out;
766         
767         out = fopen(filename, "w");
768         if (!out)
769                 return 1;
770
771         xmlDocDump(out, doc);  /* Some versions return void */
772
773         if (fclose(out))
774                 return 1;
775 #endif
776
777         return 0;
778 }
779
780 /* Write out globs for one pattern to the 'globs' file */
781 static void write_out_glob(GList *globs, FILE *stream)
782 {
783         GList *list;
784         Glob *glob;
785         Type *type;
786
787         for (list = globs; list; list = list->next) {
788                 glob = (Glob *)list->data;
789                 type = glob->type;
790                 if (strchr(glob->pattern, '\n'))
791                         g_warning("* Glob patterns can't contain literal newlines "
792                                   "(%s in type %s/%s)\n", glob->pattern,
793                                   type->media, type->subtype);
794                 else
795                         g_fprintf(stream, "%s/%s:%s\n",
796                                 type->media, type->subtype, glob->pattern);
797         }
798 }
799
800 /* Write out globs and weights for one pattern to the 'globs2' file */
801 static void write_out_glob2(GList *globs, FILE *stream)
802 {
803         GList *list;
804         Glob *glob;
805         Type *type;
806
807         for (list = globs ; list; list = list->next) {
808                 glob = (Glob *)list->data;
809                 type = glob->type;
810                 if (strchr(glob->pattern, '\n'))
811                         g_warning("* Glob patterns can't contain literal newlines "
812                                   "(%s in type %s/%s)\n", glob->pattern,
813                                   type->media, type->subtype);
814                 else
815                         g_fprintf(stream, "%d:%s/%s:%s\n",
816                                   glob->weight, type->media, type->subtype, glob->pattern);
817         }
818 }
819
820 static void collect_glob2(gpointer key, gpointer value, gpointer data)
821 {
822         GList **listp = data;
823
824         *listp = g_list_concat (*listp, g_list_copy ((GList *)value));
825 }
826
827 static int compare_by_weight (gpointer a, gpointer b)
828 {
829         Glob *ag = (Glob *)a;
830         Glob *bg = (Glob *)b;
831
832         return bg->weight - ag->weight;
833 }
834
835 /* Renames pathname by removing the .new extension */
836 static void atomic_update(const gchar *pathname)
837 {
838         gchar *new_name;
839         int len;
840
841         len = strlen(pathname);
842
843         g_return_if_fail(strcmp(pathname + len - 4, ".new") == 0);
844
845         new_name = g_strndup(pathname, len - 4);
846
847 #ifdef _WIN32
848         /* we need to remove the old file first! */
849         remove(new_name);
850 #endif
851         if (rename(pathname, new_name))
852                 g_warning("Failed to rename %s as %s , errno: %d\n", pathname, new_name, errno);
853
854         g_free(new_name);
855 }
856
857 /* Write out an XML file for one type */
858 static void write_out_type(gpointer key, gpointer value, gpointer data)
859 {
860         Type *type = (Type *) value;
861         const char *mime_dir = (char *) data;
862         char *media, *filename;
863
864         media = g_strconcat(mime_dir, "/", type->media, NULL);
865 #ifdef _WIN32
866         mkdir(media);
867 #else
868         mkdir(media, 0755);
869 #endif
870
871         filename = g_strconcat(media, "/", type->subtype, ".xml.new", NULL);
872         g_free(media);
873         media = NULL;
874         
875         if (save_xml_file(type->output, filename) != 0)
876                 g_warning("Failed to write out '%s'\n", filename);
877
878         atomic_update(filename);
879
880         g_free(filename);
881 }
882
883 /* Comparison function to get the magic rules in priority order */
884 static gint cmp_magic(gconstpointer a, gconstpointer b)
885 {
886         Magic *aa = *(Magic **) a;
887         Magic *bb = *(Magic **) b;
888         int retval;
889
890         if (aa->priority > bb->priority)
891                 return -1;
892         else if (aa->priority < bb->priority)
893                 return 1;
894
895         retval = strcmp(aa->type->media, bb->type->media);
896         if (!retval)
897                 retval = strcmp(aa->type->subtype, bb->type->subtype);
898
899         return retval;
900 }
901
902 /* Comparison function to get the tree magic rules in priority order */
903 static gint cmp_tree_magic(gconstpointer a, gconstpointer b)
904 {
905         TreeMagic *aa = *(TreeMagic **) a;
906         TreeMagic *bb = *(TreeMagic **) b;
907         int retval;
908
909         if (aa->priority > bb->priority)
910                 return -1;
911         else if (aa->priority < bb->priority)
912                 return 1;
913
914         retval = strcmp(aa->type->media, bb->type->media);
915         if (!retval)
916                 retval = strcmp(aa->type->subtype, bb->type->subtype);
917
918         return retval;
919 }
920
921 /* Write out 'n' as a two-byte big-endian number to 'stream' */
922 static void write16(FILE *stream, guint32 n)
923 {
924         guint16 big = GUINT16_TO_BE(n);
925
926         g_return_if_fail(n <= 0xffff);
927
928         fwrite(&big, sizeof(big), 1, stream);
929 }
930
931 /* Single hex char to int; -1 if not a hex char.
932  * From file(1).
933  */
934 static int hextoint(int c)
935 {
936         if (!isascii((unsigned char) c))
937                 return -1;
938         if (isdigit((unsigned char) c))
939                 return c - '0';
940         if ((c >= 'a')&&(c <= 'f'))
941                 return c + 10 - 'a';
942         if (( c>= 'A')&&(c <= 'F'))
943                 return c + 10 - 'A';
944         return -1;
945 }
946
947 /*
948  * Convert a string containing C character escapes.  Stop at an unescaped
949  * space or tab.
950  * Copy the converted version to "p", returning its length in *slen.
951  * Return updated scan pointer as function result.
952  * Stolen from file(1) and heavily modified.
953  */
954 static void getstr(const char *s, GString *out)
955 {
956         int     c;
957         int     val;
958
959         while ((c = *s++) != '\0') {
960                 if(c == '\\') {
961                         switch(c = *s++) {
962
963                         case '\0':
964                                 return;
965
966                         default:
967                                 g_string_append_c(out, (char) c);
968                                 break;
969
970                         case 'n':
971                                 g_string_append_c(out, '\n');
972                                 break;
973
974                         case 'r':
975                                 g_string_append_c(out, '\r');
976                                 break;
977
978                         case 'b':
979                                 g_string_append_c(out, '\b');
980                                 break;
981
982                         case 't':
983                                 g_string_append_c(out, '\t');
984                                 break;
985
986                         case 'f':
987                                 g_string_append_c(out, '\f');
988                                 break;
989
990                         case 'v':
991                                 g_string_append_c(out, '\v');
992                                 break;
993
994                         /* \ and up to 3 octal digits */
995                         case '0':
996                         case '1':
997                         case '2':
998                         case '3':
999                         case '4':
1000                         case '5':
1001                         case '6':
1002                         case '7':
1003                                 val = c - '0';
1004                                 c = *s++;  /* try for 2 */
1005                                 if(c >= '0' && c <= '7') {
1006                                         val = (val<<3) | (c - '0');
1007                                         c = *s++;  /* try for 3 */
1008                                         if(c >= '0' && c <= '7')
1009                                                 val = (val<<3) | (c-'0');
1010                                         else
1011                                                 --s;
1012                                 }
1013                                 else
1014                                         --s;
1015                                 g_string_append_c(out, (char)val);
1016                                 break;
1017
1018                         /* \x and up to 2 hex digits */
1019                         case 'x':
1020                                 val = 'x';      /* Default if no digits */
1021                                 c = hextoint(*s++);     /* Get next char */
1022                                 if (c >= 0) {
1023                                         val = c;
1024                                         c = hextoint(*s++);
1025                                         if (c >= 0)
1026                                                 val = (val << 4) + c;
1027                                         else
1028                                                 --s;
1029                                 } else
1030                                         --s;
1031                                 g_string_append_c(out, (char)val);
1032                                 break;
1033                         }
1034                 } else
1035                         g_string_append_c(out, (char)c);
1036         }
1037 }
1038
1039 /* Parse the value and mask attributes of a <match> element with a
1040  * numerical type (anything except "string").
1041  */
1042 static void parse_int_value(int bytes, const char *in, const char *in_mask,
1043                             GString *parsed_value, char **parsed_mask,
1044                             gboolean big_endian, GError **error)
1045 {
1046         char *end;
1047         char *out_mask = NULL;
1048         unsigned long value;
1049         int b;
1050
1051         value = strtoul(in, &end, 0);
1052         if (errno == ERANGE) {
1053                 g_set_error(error, MIME_ERROR, 0,
1054                             "Number out-of-range (%s should fit in %d bytes)",
1055                             in, bytes);
1056                 return;
1057         }
1058
1059         if (*end != '\0')
1060         {
1061                 g_set_error(error, MIME_ERROR, 0, "Value is not a number");
1062                 return;
1063         }
1064
1065         for (b = 0; b < bytes; b++)
1066         {
1067                 int shift = (big_endian ? (bytes - b - 1) : b) * 8;
1068                 g_string_append_c(parsed_value, (value >> shift) & 0xff);
1069         }
1070
1071         if ((bytes == 1 && (value & ~0xff)) ||
1072             (bytes == 2 && (value & ~0xffff)))
1073         {
1074                 g_set_error(error, MIME_ERROR, 0,
1075                             "Number out-of-range (%lx should fit in %d bytes)",
1076                             value, bytes);
1077                 return;
1078         }
1079
1080         if (in_mask)
1081         {
1082                 int b;
1083                 unsigned long mask;
1084                 
1085                 mask = strtoul(in_mask, &end, 0);
1086                 if (errno == ERANGE) {
1087                         g_set_error(error, MIME_ERROR, 0,
1088                                     "Mask out-of-range (%s should fit in %d bytes)",
1089                                     in_mask, bytes);
1090                         return;
1091                 }
1092
1093
1094                 if (*end != '\0')
1095                 {
1096                         g_set_error(error, MIME_ERROR, 0,
1097                                     "Mask is not a number");
1098                         return;
1099                 }
1100
1101                 out_mask = g_new(char, bytes);
1102                 for (b = 0; b < bytes; b++)
1103                 {
1104                         int shift = (big_endian ? (bytes - b - 1) : b) * 8;
1105                         out_mask[b] = (mask >> shift) & 0xff;
1106                 }
1107         }
1108
1109         *parsed_mask = out_mask;
1110 }
1111
1112 /* 'len' is the length of the value. The mask created will be the same
1113  * length.
1114  */
1115 static char *parse_string_mask(const char *mask, int len, GError **error)
1116 {
1117         int i;
1118         char *parsed_mask = NULL;
1119
1120         g_return_val_if_fail(mask != NULL, NULL);
1121         g_return_val_if_fail(len > 0, NULL);
1122
1123         if (mask[0] != '0' || mask[1] != 'x')
1124         {
1125                 g_set_error(error, MIME_ERROR, 0,
1126                         "String masks must be in base 16 (starting with 0x)");
1127                 goto err;
1128         }
1129         mask += 2;
1130
1131         parsed_mask = g_new0(char, len);
1132
1133         i = 0; /* Nybble to write to next */
1134         while (mask[i])
1135         {
1136                 int c;
1137
1138                 c = hextoint(mask[i]);
1139                 if (c == -1)
1140                 {
1141                         g_set_error(error, MIME_ERROR, 0,
1142                                 "'%c' is not a valid hex digit", mask[i]);
1143                         goto err;
1144                 }
1145
1146                 if (i >= len * 2)
1147                 {
1148                         g_set_error(error, MIME_ERROR, 0,
1149                                 "Mask is longer than value");
1150                         goto err;
1151                 }
1152                 
1153                 if (i & 1)
1154                         parsed_mask[i >> 1] |= c;
1155                 else
1156                         parsed_mask[i >> 1] |= c << 4;
1157
1158                 i++;
1159         }
1160
1161         return parsed_mask;
1162 err:
1163         g_return_val_if_fail(error == NULL || *error != NULL, NULL);
1164         g_free(parsed_mask);
1165         return NULL;
1166 }
1167
1168 /* Parse the value and mask attributes for a <match> element */
1169 static void parse_value(const char *type, const char *in, const char *in_mask,
1170                         GString *parsed_value, char **parsed_mask,
1171                         GError **error)
1172 {
1173         *parsed_mask = NULL;
1174
1175         if (in == NULL || !in[0])
1176         {
1177                 g_set_error(error, MIME_ERROR, 0, "No value specified");
1178                 return;
1179         }
1180
1181         if (strstr(type, "16"))
1182                 parse_int_value(2, in, in_mask, parsed_value, parsed_mask,
1183                                 type[0] != 'l', error);
1184         else if (strstr(type, "32"))
1185                 parse_int_value(4, in, in_mask, parsed_value, parsed_mask,
1186                                 type[0] != 'l', error);
1187         else if (strcmp(type, "byte") == 0)
1188                 parse_int_value(1, in, in_mask, parsed_value, parsed_mask,
1189                                 FALSE, error);
1190         else if (strcmp(type, "string") == 0)
1191         {
1192                 getstr(in, parsed_value);
1193                 if (in_mask)
1194                         *parsed_mask = parse_string_mask(in_mask,
1195                                                 parsed_value->len, error);
1196         }
1197         else
1198                 g_assert_not_reached();
1199 }
1200
1201 static Match *match_new(void)
1202 {
1203         Match *match;
1204
1205         match = g_new(Match, 1);
1206         match->range_start = 0;
1207         match->range_length = 1;
1208         match->word_size = 1;
1209         match->data_length = 0;
1210         match->data = NULL;
1211         match->mask = NULL;
1212         match->matches = NULL;
1213
1214         return match;
1215 }
1216
1217 static void match_free(Match *match)
1218 {
1219         GList *next;
1220
1221         g_return_if_fail(match != NULL);
1222
1223         for (next = match->matches; next; next = next->next)
1224                 match_free((Match *) next->data);
1225
1226         g_list_free(match->matches);
1227
1228         g_free(match->data);
1229         g_free(match->mask);
1230
1231         g_free(match);
1232 }
1233
1234 /* Sets match->range_start and match->range_length */
1235 static void match_offset(Match *match, xmlNode *node, GError **error)
1236 {
1237         char *offset = NULL;
1238         char *end;
1239
1240         offset = my_xmlGetNsProp(node, "offset", NULL);
1241         if (offset == NULL || !*offset)
1242         {
1243                 g_set_error(error, MIME_ERROR, 0, "Missing 'offset' attribute");
1244                 goto err;
1245         }
1246
1247         match->range_start = strtol(offset, &end, 10);
1248         if (errno == ERANGE) {
1249                 char *number;
1250                 number = g_strndup(offset, end-offset);
1251                 g_set_error(error, MIME_ERROR, 0,
1252                             "Number out-of-range (%s should fit in 4 bytes)",
1253                             number);
1254                 g_free(number);
1255                 return;
1256         }
1257
1258         if (*end == ':' && end[1] && match->range_start >= 0)
1259         {
1260                 int last;
1261                 char *begin;
1262
1263                 begin = end + 1;
1264                 last = strtol(begin, &end, 10);
1265                 if (errno == ERANGE) {
1266                         char *number;
1267                         number = g_strndup(begin, end-begin);
1268                         g_set_error(error, MIME_ERROR, 0,
1269                                     "Number out-of-range (%s should fit in 8 bytes)",
1270                                     number);
1271                         g_free(number);
1272                         return;
1273                 }
1274
1275                 if (*end == '\0' && last >= match->range_start)
1276                         match->range_length = last - match->range_start + 1;
1277                 else
1278                         g_set_error(error, MIME_ERROR, 0, "Invalid offset");
1279         }
1280         else if (*end != '\0')
1281                 g_set_error(error, MIME_ERROR, 0, "Invalid offset");
1282 err:
1283         xmlFree(offset);
1284 }
1285
1286 /* Sets match->data, match->data_length and match->mask */
1287 static void match_value_and_mask(Match *match, xmlNode *node, GError **error)
1288 {
1289         char *mask = NULL;
1290         char *value = NULL;
1291         char *type = NULL;
1292         char *parsed_mask = NULL;
1293         GString *parsed_value;
1294
1295         type = my_xmlGetNsProp(node, "type", NULL);
1296         g_return_if_fail(type != NULL);
1297
1298         mask = my_xmlGetNsProp(node, "mask", NULL);
1299         value = my_xmlGetNsProp(node, "value", NULL);
1300
1301         parsed_value = g_string_new(NULL);
1302
1303         parse_value(type, value, mask, parsed_value,
1304                         &parsed_mask, error);
1305
1306         if (*error)
1307         {
1308                 g_string_free(parsed_value, TRUE);
1309                 g_return_if_fail(parsed_mask == NULL);
1310         }
1311         else
1312         {
1313                 match->data = parsed_value->str;
1314                 match->data_length = parsed_value->len;
1315                 match->mask = parsed_mask;
1316
1317                 g_string_free(parsed_value, FALSE);
1318         }
1319
1320         if (mask)
1321                 xmlFree(mask);
1322         if (value)
1323                 xmlFree(value);
1324         xmlFree(type);
1325 }
1326
1327 /* Sets match->word_size */
1328 static void match_word_size(Match *match, xmlNode *node, GError **error)
1329 {
1330         char *type;
1331
1332         type = my_xmlGetNsProp(node, "type", NULL);
1333
1334         if (!type)
1335         {
1336                 g_set_error(error, MIME_ERROR, 0,
1337                         _("Missing 'type' attribute in <match>"));
1338                 return;
1339         }
1340
1341         if (strcmp(type, "host16") == 0)
1342                 match->word_size = 2;
1343         else if (strcmp(type, "host32") == 0)
1344                 match->word_size = 4;
1345         else if (!*error && strcmp(type, "big16") &&
1346                         strcmp(type, "big32") &&
1347                         strcmp(type, "little16") && strcmp(type, "little32") &&
1348                         strcmp(type, "string") && strcmp(type, "byte"))
1349         {
1350                 g_set_error(error, MIME_ERROR, 0,
1351                                 "Unknown magic type '%s'", type);
1352         }
1353
1354         xmlFree(type);
1355 }
1356
1357 /* Turn the list of child nodes of 'parent' into a list of Matches */
1358 static GList *build_matches(xmlNode *parent, GError **error)
1359 {
1360         xmlNode *node;
1361         GList *out = NULL;
1362
1363         g_return_val_if_fail(error != NULL, NULL);
1364
1365         for (node = parent->xmlChildrenNode; node; node = node->next)
1366         {
1367                 Match *match;
1368
1369                 if (node->type != XML_ELEMENT_NODE)
1370                         continue;
1371
1372                 if (node->ns == NULL || xmlStrcmp(node->ns->href, FREE_NS) != 0)
1373                 {
1374                         g_set_error(error, MIME_ERROR, 0,
1375                                 _("Element found with non-freedesktop.org "
1376                                   "namespace"));
1377                         break;
1378                 }
1379
1380                 if (strcmp((char *)node->name, "match") != 0)
1381                 {
1382                         g_set_error(error, MIME_ERROR, 0,
1383                                 _("Expected <match> element, but found "
1384                                   "<%s> instead"), node->name);
1385                         break;
1386                 }
1387
1388                 match = match_new();
1389                 match_offset(match, node, error);
1390                 if (!*error)
1391                         match_word_size(match, node, error);
1392                 if (!*error)
1393                         match_value_and_mask(match, node, error);
1394
1395                 if (*error)
1396                 {
1397                         match_free(match);
1398                         break;
1399                 }
1400
1401                 out = g_list_append(out, match);
1402
1403                 match->matches = build_matches(node, error);
1404                 if (*error)
1405                         break;
1406         }
1407
1408         return out;
1409 }
1410
1411 static void magic_free(Magic *magic)
1412 {
1413         GList *next;
1414
1415         g_return_if_fail(magic != NULL);
1416
1417         for (next = magic->matches; next; next = next->next)
1418                 match_free((Match *) next->data);
1419         g_list_free(magic->matches);
1420
1421         g_free(magic);
1422 }
1423
1424 /* Create a new Magic object by parsing 'node' (a <magic> element) */
1425 static Magic *magic_new(xmlNode *node, Type *type, GError **error)
1426 {
1427         Magic *magic = NULL;
1428         int prio;
1429
1430         g_return_val_if_fail(node != NULL, NULL);
1431         g_return_val_if_fail(type != NULL, NULL);
1432         g_return_val_if_fail(error != NULL, NULL);
1433
1434         prio = get_priority(node);
1435
1436         if (prio == -1)
1437         {
1438                 g_set_error(error, MIME_ERROR, 0,
1439                         _("Bad priority (%d) in <magic> element"), prio);
1440         }
1441         else
1442         {
1443                 magic = g_new(Magic, 1);
1444                 magic->priority = prio;
1445                 magic->type = type;
1446                 magic->matches = build_matches(node, error);
1447
1448                 if (*error)
1449                 {
1450                         gchar *old = (*error)->message;
1451                         magic_free(magic);
1452                         magic = NULL;
1453                         (*error)->message = g_strconcat(
1454                                 _("Error in <match> element: "), old, NULL);
1455                         g_free(old);
1456                 }
1457         }
1458
1459         return magic;
1460 }
1461
1462 static TreeMatch *tree_match_new(void)
1463 {
1464         TreeMatch *match;
1465
1466         match = g_new(TreeMatch, 1);
1467         match->path = NULL;
1468         match->match_case = 0;
1469         match->executable = 0;
1470         match->non_empty = 0;
1471         match->type = 0;
1472         match->mimetype = NULL;
1473         match->matches = NULL;
1474
1475         return match;
1476 }
1477
1478 static void tree_match_free(TreeMatch *match)
1479 {
1480         GList *next;
1481
1482         g_return_if_fail(match != NULL);
1483
1484         for (next = match->matches; next; next = next->next)
1485                 tree_match_free((TreeMatch *) next->data);
1486
1487         g_list_free(match->matches);
1488
1489         g_free(match->path);
1490         g_free(match->mimetype);
1491
1492         g_free(match);
1493 }
1494
1495 /* Turn the list of child nodes of 'parent' into a list of TreeMatches */
1496 static GList *build_tree_matches(xmlNode *parent, GError **error)
1497 {
1498         xmlNode *node;
1499         GList *out = NULL;
1500         char *attr;
1501
1502         g_return_val_if_fail(error != NULL, NULL);
1503
1504         for (node = parent->xmlChildrenNode; node; node = node->next)
1505         {
1506                 TreeMatch *match;
1507
1508                 if (node->type != XML_ELEMENT_NODE)
1509                         continue;
1510
1511                 if (node->ns == NULL || xmlStrcmp(node->ns->href, FREE_NS) != 0)
1512                 {
1513                         g_set_error(error, MIME_ERROR, 0,
1514                                 _("Element found with non-freedesktop.org "
1515                                   "namespace"));
1516                         break;
1517                 }
1518
1519                 if (strcmp((char *)node->name, "treematch") != 0)
1520                 {
1521                         g_set_error(error, MIME_ERROR, 0,
1522                                 _("Expected <treematch> element, but found "
1523                                   "<%s> instead"), node->name);
1524                         break;
1525                 }
1526
1527                 match = tree_match_new();
1528
1529                 attr = my_xmlGetNsProp(node, "path", NULL);
1530                 if (attr)
1531                 {
1532                         match->path = g_strdup (attr);
1533                         xmlFree (attr);
1534                 }
1535                 else 
1536                 {
1537                         g_set_error(error, MIME_ERROR, 0,
1538                                 _("Missing 'path' attribute in <treematch>"));
1539                 }
1540                 if (!*error) 
1541                 {
1542                         attr = my_xmlGetNsProp(node, "type", NULL);
1543                         if (attr) 
1544                         {
1545                                 if (strcmp (attr, "file") == 0) 
1546                                 {
1547                                         match->type = 1;
1548                                 }
1549                                 else if (strcmp (attr, "directory") == 0)
1550                                 {
1551                                         match->type = 2;
1552                                 }
1553                                 else if (strcmp (attr, "link") == 0)
1554                                 {
1555                                         match->type = 3;
1556                                 }
1557                                 else
1558                                 {
1559                                         g_set_error(error, MIME_ERROR, 0,
1560                                                 _("Invalid 'type' attribute in <treematch>"));
1561                                 }
1562                                 xmlFree(attr);
1563                         }
1564                 }
1565                 if (!*error)
1566                 {
1567                         attr = my_xmlGetNsProp(node, "executable", NULL);
1568                         if (attr)
1569                         {
1570                                 if (strcmp (attr, "true") == 0) 
1571                                 {
1572                                         match->executable = 1;
1573                                 }
1574                                 xmlFree(attr);
1575                         }
1576                 }
1577                 if (!*error)
1578                 {
1579                         attr = my_xmlGetNsProp(node, "match-case", NULL);
1580                         if (attr)
1581                         {
1582                                 if (strcmp (attr, "true") == 0) 
1583                                 {
1584                                         match->match_case = 1;
1585                                 }
1586                                 xmlFree(attr);
1587                         }
1588                 }
1589                 if (!*error)
1590                 {
1591                         attr = my_xmlGetNsProp(node, "non-empty", NULL);
1592                         if (attr)
1593                         {
1594                                 if (strcmp (attr, "true") == 0) 
1595                                 {
1596                                         match->non_empty = 1;
1597                                 }
1598                                 xmlFree(attr);
1599                         }
1600                 }
1601                 if (!*error)
1602                 {
1603                         attr = my_xmlGetNsProp(node, "mimetype", NULL);
1604                         if (attr)
1605                         {
1606                                 match->mimetype = g_strdup (attr);
1607                                 xmlFree(attr);
1608                         }
1609                 }
1610
1611                 if (*error)
1612                 {
1613                         tree_match_free(match);
1614                         break;
1615                 }
1616
1617                 out = g_list_append(out, match);
1618
1619                 match->matches = build_tree_matches(node, error);
1620                 if (*error)
1621                         break;
1622         }
1623
1624         return out;
1625 }
1626
1627 static void tree_magic_free(TreeMagic *magic)
1628 {
1629         GList *next;
1630
1631         g_return_if_fail(magic != NULL);
1632
1633         for (next = magic->matches; next; next = next->next)
1634                 tree_match_free((TreeMatch *) next->data);
1635         g_list_free(magic->matches);
1636
1637         g_free(magic);
1638 }
1639
1640 /* Create a new TreeMagic object by parsing 'node' (a <treemagic> element) */
1641 static TreeMagic *tree_magic_new(xmlNode *node, Type *type, GError **error)
1642 {
1643         TreeMagic *magic = NULL;
1644         int prio;
1645
1646         g_return_val_if_fail(node != NULL, NULL);
1647         g_return_val_if_fail(type != NULL, NULL);
1648         g_return_val_if_fail(error != NULL, NULL);
1649
1650         prio = get_priority(node);
1651
1652         if (prio == -1)
1653         {
1654                 g_set_error(error, MIME_ERROR, 0,
1655                         _("Bad priority (%d) in <treemagic> element"), prio);
1656         }
1657         else
1658         {
1659                 magic = g_new(TreeMagic, 1);
1660                 magic->priority = prio;
1661                 magic->type = type;
1662                 magic->matches = build_tree_matches(node, error);
1663
1664                 if (*error)
1665                 {
1666                         gchar *old = (*error)->message;
1667                         tree_magic_free(magic);
1668                         magic = NULL;
1669                         (*error)->message = g_strconcat(
1670                                 _("Error in <treematch> element: "), old, NULL);
1671                         g_free(old);
1672                 }
1673         }
1674
1675         return magic;
1676 }
1677
1678 /* Write a list of Match elements (and their children) to the 'magic' file */
1679 static void write_magic_children(FILE *stream, GList *matches, int indent)
1680 {
1681         GList *next;
1682
1683         for (next = matches; next; next = next->next)
1684         {
1685                 Match *match = (Match *) next->data;
1686
1687                 if (indent)
1688                         g_fprintf(stream,
1689                                   "%d>%ld=",
1690                                   indent,
1691                                   match->range_start);
1692                 else
1693                         g_fprintf(stream, ">%ld=", match->range_start);
1694
1695                 write16(stream, match->data_length);
1696                 fwrite(match->data, match->data_length, 1, stream);
1697                 if (match->mask)
1698                 {
1699                         fputc('&', stream);
1700                         fwrite(match->mask, match->data_length, 1, stream);
1701                 }
1702                 if (match->word_size != 1)
1703                         g_fprintf(stream, "~%d", match->word_size);
1704                 if (match->range_length != 1)
1705                         g_fprintf(stream, "+%d", match->range_length);
1706
1707                 fputc('\n', stream);
1708
1709                 write_magic_children(stream, match->matches, indent + 1);
1710         }
1711 }
1712
1713 /* Write a whole Magic element to the 'magic' file */
1714 static void write_magic(FILE *stream, Magic *magic)
1715 {
1716         g_fprintf(stream, "[%d:%s/%s]\n", magic->priority,
1717                 magic->type->media, magic->type->subtype);
1718
1719         write_magic_children(stream, magic->matches, 0);
1720 }
1721
1722 /* Write a list of TreeMatch elements (and their children) to the 'treemagic' file */
1723 static void write_tree_magic_children(FILE *stream, GList *matches, int indent)
1724 {
1725         GList *next;
1726
1727         for (next = matches; next; next = next->next)
1728         {
1729                 TreeMatch *match = (TreeMatch *) next->data;
1730
1731                 if (indent)
1732                         g_fprintf(stream,
1733                                   "%d>\"%s\"=",
1734                                   indent,
1735                                   match->path);
1736                 else
1737                         g_fprintf(stream, ">\"%s\"=", match->path);
1738
1739                 switch (match->type)
1740                 {
1741                 default:
1742                 case 0: 
1743                         fputs("any", stream);
1744                         break;
1745                 case 1: 
1746                         fputs("file", stream);
1747                         break;
1748                 case 2: 
1749                         fputs("directory", stream);
1750                         break;
1751                 case 3: 
1752                         fputs("link", stream);
1753                         break;
1754                 }
1755                 if (match->match_case)
1756                         fputs (",match-case", stream);
1757                 if (match->executable)
1758                         fputs (",executable", stream);
1759                 if (match->non_empty)
1760                         fputs (",non-empty", stream);
1761                 if (match->mimetype)
1762                         g_fprintf (stream, ",%s", match->mimetype);
1763
1764                 fputc('\n', stream);
1765
1766                 write_tree_magic_children(stream, match->matches, indent + 1);
1767         }
1768 }
1769 /* Write a whole TreeMagic element to the 'treemagic' file */
1770 static void write_tree_magic(FILE *stream, TreeMagic *magic)
1771 {
1772         g_fprintf(stream, "[%d:%s/%s]\n", magic->priority,
1773                 magic->type->media, magic->type->subtype);
1774
1775         write_tree_magic_children(stream, magic->matches, 0);
1776 }
1777
1778 /* Check each of the directories with generated XML files, looking for types
1779  * which we didn't get on this scan, and delete them.
1780  */
1781 static void delete_old_types(const gchar *mime_dir)
1782 {
1783         int i;
1784
1785         for (i = 0; i < G_N_ELEMENTS(media_types); i++)
1786         {
1787                 gchar *media_dir;
1788                 DIR   *dir;
1789                 struct dirent *ent;
1790                 
1791                 media_dir = g_strconcat(mime_dir, "/", media_types[i], NULL);
1792                 dir = opendir(media_dir);
1793                 g_free(media_dir);
1794                 if (!dir)
1795                         continue;
1796
1797                 while ((ent = readdir(dir)))
1798                 {
1799                         char *type_name;
1800                         int l;
1801                         l = strlen(ent->d_name);
1802                         if (l < 4 || strcmp(ent->d_name + l - 4, ".xml") != 0)
1803                                 continue;
1804
1805                         type_name = g_strconcat(media_types[i], "/",
1806                                                 ent->d_name, NULL);
1807                         type_name[strlen(type_name) - 4] = '\0';
1808                         if (!g_hash_table_lookup(types, type_name))
1809                         {
1810                                 char *path;
1811                                 path = g_strconcat(mime_dir, "/",
1812                                                 type_name, ".xml", NULL);
1813 #if 0
1814                                 g_warning("* Removing old info for type %s\n",
1815                                                 path);
1816 #endif
1817                                 unlink(path);
1818                                 g_free(path);
1819                         }
1820                         g_free(type_name);
1821                 }
1822                 
1823                 closedir(dir);
1824         }
1825 }
1826
1827 /* Extract one entry from namespace_hash and put it in the GPtrArray so
1828  * we can sort it.
1829  */
1830 static void add_ns(gpointer key, gpointer value, gpointer data)
1831 {
1832         GPtrArray *lines = (GPtrArray *) data;
1833         const gchar *ns = (gchar *) key;
1834         Type *type = (Type *) value;
1835
1836         g_ptr_array_add(lines, g_strconcat(ns, " ", type->media,
1837                                            "/", type->subtype, "\n", NULL));
1838 }
1839
1840 /* Write all the collected namespace rules to 'XMLnamespaces' */
1841 static void write_namespaces(FILE *stream)
1842 {
1843         GPtrArray *lines;
1844         int i;
1845         
1846         lines = g_ptr_array_new();
1847
1848         g_hash_table_foreach(namespace_hash, add_ns, lines);
1849
1850         g_ptr_array_sort(lines, strcmp2);
1851
1852         for (i = 0; i < lines->len; i++)
1853         {
1854                 char *line = (char *) lines->pdata[i];
1855
1856                 fwrite(line, 1, strlen(line), stream);
1857
1858                 g_free(line);
1859         }
1860
1861         g_ptr_array_free(lines, TRUE);
1862 }
1863
1864 static void write_subclass(gpointer key, gpointer value, gpointer data)
1865 {
1866         GSList *list = value;
1867         FILE *stream = data;
1868         GSList *l;
1869         char *line;
1870
1871         for (l = list; l; l = l->next)
1872         {
1873                 line = g_strconcat (key, " ", l->data, "\n", NULL);
1874                 fwrite(line, 1, strlen(line), stream);
1875                 g_free (line);
1876         }
1877 }
1878
1879 /* Write all the collected subclass information to 'subclasses' */
1880 static void write_subclasses(FILE *stream)
1881 {
1882         g_hash_table_foreach(subclass_hash, write_subclass, stream);
1883 }
1884
1885 /* Extract one entry from alias_hash and put it in the GPtrArray so
1886  * we can sort it.
1887  */
1888 static void add_alias(gpointer key, gpointer value, gpointer data)
1889 {
1890         GPtrArray *lines = (GPtrArray *) data;
1891         const gchar *alias = (gchar *) key;
1892         Type *type = (Type *) value;
1893         
1894         g_ptr_array_add(lines, g_strconcat(alias, " ", type->media,
1895                                            "/", type->subtype, "\n", 
1896                                            NULL));
1897 }
1898
1899 /* Write all the collected aliases */
1900 static void write_aliases(FILE *stream)
1901 {
1902         GPtrArray *lines;
1903         int i;
1904         
1905         lines = g_ptr_array_new();
1906
1907         g_hash_table_foreach(alias_hash, add_alias, lines);
1908
1909         g_ptr_array_sort(lines, strcmp2);
1910
1911         for (i = 0; i < lines->len; i++)
1912         {
1913                 char *line = (char *) lines->pdata[i];
1914
1915                 fwrite(line, 1, strlen(line), stream);
1916
1917                 g_free(line);
1918         }
1919
1920         g_ptr_array_free(lines, TRUE);
1921 }
1922
1923 static void add_type(gpointer key, gpointer value, gpointer data)
1924 {
1925         GPtrArray *lines = (GPtrArray *) data;
1926         
1927         g_ptr_array_add(lines, g_strconcat((char *)key, "\n", NULL));
1928 }
1929
1930 /* Write all the collected types */
1931 static void write_types(FILE *stream)
1932 {
1933         GPtrArray *lines;
1934         int i;
1935         
1936         lines = g_ptr_array_new();
1937
1938         g_hash_table_foreach(types, add_type, lines);
1939
1940         g_ptr_array_sort(lines, strcmp2);
1941
1942         for (i = 0; i < lines->len; i++)
1943         {
1944                 char *line = (char *) lines->pdata[i];
1945
1946                 fwrite(line, 1, strlen(line), stream);
1947
1948                 g_free(line);
1949         }
1950
1951         g_ptr_array_free(lines, TRUE);
1952 }
1953
1954
1955 static void write_one_icon(gpointer key, gpointer value, gpointer data)
1956 {
1957         char *mimetype = (char *)key;
1958         char *iconname = (char *)value;
1959         FILE *stream = (FILE *)data;
1960         char *line;
1961
1962         line = g_strconcat (mimetype, ":", iconname, "\n", NULL);
1963         fwrite(line, 1, strlen(line), stream);
1964         g_free (line);
1965 }
1966
1967 static void write_icons(GHashTable *icons, FILE *stream)
1968 {
1969         g_hash_table_foreach(icons, write_one_icon, stream);
1970 }
1971
1972 /* Issue a warning if 'path' won't be found by applications */
1973 static void check_in_path_xdg_data(const char *mime_path)
1974 {
1975         struct stat path_info, dir_info;
1976         const char *env;
1977         char **dirs;
1978         char *path;
1979         int i, n;
1980
1981         path = g_path_get_dirname(mime_path);
1982
1983         if (stat(path, &path_info))
1984         {
1985                 g_warning("Can't stat '%s' directory: %s",
1986                                 path, g_strerror(errno));
1987                 goto out;
1988         }
1989
1990         env = getenv("XDG_DATA_DIRS");
1991         if (!env)
1992                 env = "/usr/local/share/"PATH_SEPARATOR"/usr/share/";
1993         dirs = g_strsplit(env, PATH_SEPARATOR, 0);
1994         g_return_if_fail(dirs != NULL);
1995         for (n = 0; dirs[n]; n++)
1996                 ;
1997         env = getenv("XDG_DATA_HOME");
1998         if (env)
1999                 dirs[n] = g_strdup(env);
2000         else
2001                 dirs[n] = g_build_filename(g_get_home_dir(), ".local",
2002                                                 "share", NULL);
2003         n++;
2004         
2005         for (i = 0; i < n; i++)
2006         {
2007                 if (stat(dirs[i], &dir_info) == 0 &&
2008                     dir_info.st_ino == path_info.st_ino &&
2009                     dir_info.st_dev == path_info.st_dev)
2010                         break;
2011         }
2012
2013         if (i == n)
2014         {
2015                 g_warning(_("\nNote that '%s' is not in the search path\n"
2016                         "set by the XDG_DATA_HOME and XDG_DATA_DIRS\n"
2017                         "environment variables, so applications may not\n"
2018                         "be able to find it until you set them. The\n"
2019                         "directories currently searched are:\n\n"), path);
2020                 g_printerr("- %s\n", dirs[n - 1]);
2021                 for (i = 0; i < n - 1; i++)
2022                         g_printerr("- %s\n", dirs[i]);
2023                 g_printerr("\n");
2024         }
2025
2026         for (i = 0; i < n; i++)
2027                 g_free(dirs[i]);
2028         g_free(dirs);
2029 out:
2030         g_free(path);
2031 }
2032
2033 static void free_string_list(gpointer data)
2034 {
2035   GSList *list = data;
2036
2037   g_slist_foreach(list, (GFunc)g_free, NULL);
2038   g_slist_free(list);
2039 }
2040
2041 #define ALIGN_VALUE(this, boundary) \
2042   (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
2043
2044
2045 static gint
2046 write_data (FILE *cache, const gchar *n, gint len)
2047 {
2048   gchar *s;
2049   int i, l;
2050   
2051   l = ALIGN_VALUE (len, 4);
2052   
2053   s = g_malloc0 (l);
2054   memcpy (s, n, len);
2055
2056   i = fwrite (s, l, 1, cache);
2057
2058   return i == 1;
2059   
2060 }
2061
2062 static gint
2063 write_string (FILE *cache, const gchar *n)
2064 {
2065   return write_data (cache, n, strlen (n) + 1);
2066 }
2067
2068 static gboolean
2069 write_card16 (FILE *cache, guint16 n)
2070 {
2071   int i;
2072
2073   n = GUINT16_TO_BE (n);
2074   
2075   i = fwrite ((char *)&n, 2, 1, cache);
2076
2077   return i == 1;
2078 }
2079
2080 static gboolean
2081 write_card32 (FILE *cache, guint32 n)
2082 {
2083   int i;
2084
2085   n = GUINT32_TO_BE (n);
2086   
2087   i = fwrite ((char *)&n, 4, 1, cache);
2088
2089   return i == 1;
2090 }
2091
2092 #define MAJOR_VERSION 1
2093 #define MINOR_VERSION 1
2094
2095 static gboolean
2096 write_header (FILE *cache,   
2097               gint  alias_offset,
2098               gint  parent_offset,
2099               gint  literal_offset,
2100               gint  suffix_offset,
2101               gint  glob_offset,
2102               gint  magic_offset,
2103               gint  namespace_offset,
2104               gint  icons_list_offset,
2105               gint  generic_icons_list_offset,
2106               gint  type_offset,
2107               guint *offset)
2108 {
2109   *offset = 44;
2110
2111   return (write_card16 (cache, MAJOR_VERSION) &&
2112           write_card16 (cache, MINOR_VERSION) &&
2113           write_card32 (cache, alias_offset) &&
2114           write_card32 (cache, parent_offset) &&
2115           write_card32 (cache, literal_offset) &&
2116           write_card32 (cache, suffix_offset) &&
2117           write_card32 (cache, glob_offset) &&
2118           write_card32 (cache, magic_offset) &&
2119           write_card32 (cache, namespace_offset) &&
2120           write_card32 (cache, icons_list_offset) &&
2121           write_card32 (cache, generic_icons_list_offset) &&
2122           write_card32 (cache, type_offset));
2123 }
2124
2125
2126 typedef gboolean (FilterFunc) (gpointer key);
2127 typedef gchar ** (GetValueFunc) (gpointer data, gchar *key);
2128
2129 typedef struct
2130 {
2131   FILE         *cache;
2132   GHashTable   *pool;
2133   guint         offset;
2134   GetValueFunc *get_value;
2135   gpointer      data;
2136   gboolean      weighted;
2137   gboolean      error;
2138 } MapData;
2139
2140 static void
2141 write_map_entry (gpointer key,
2142                  gpointer data)
2143 {
2144   MapData *map_data = (MapData *)data;
2145   gchar **values;
2146   guint offset, i;
2147   guint weight;
2148
2149   values = (* map_data->get_value) (map_data->data, key);
2150   for (i = 0; values[i]; i++)
2151     {
2152       if (map_data->weighted && (i % 3 == 2)) 
2153         {
2154           weight = atoi (values[i]);
2155
2156           if (!write_card32 (map_data->cache, weight))
2157             map_data->error = TRUE;
2158
2159           map_data->offset += 4;
2160         }
2161       else 
2162         {
2163           offset = GPOINTER_TO_UINT (g_hash_table_lookup (map_data->pool, values[i]));
2164           if (offset == 0)
2165             {
2166               g_warning ("Missing string: '%s'\n", values[i]);
2167               map_data->error = TRUE;  
2168             }
2169           if (!write_card32 (map_data->cache, offset))
2170           map_data->error = TRUE;
2171           map_data->offset += 4;
2172         }
2173     }
2174
2175   g_strfreev (values);
2176 }
2177
2178 typedef struct 
2179 {
2180   FilterFunc *filter;
2181   GPtrArray  *keys;
2182 } FilterData;
2183
2184 static void 
2185 add_key (gpointer key, 
2186          gpointer value, 
2187          gpointer data)
2188 {
2189   FilterData *filter_data = (FilterData *)data;
2190
2191   if (!filter_data->filter || (* filter_data->filter) (key))
2192     g_ptr_array_add (filter_data->keys, key);
2193 }
2194
2195 static gboolean
2196 write_map (FILE         *cache,
2197            GHashTable   *strings,
2198            GHashTable   *map,
2199            FilterFunc   *filter,
2200            GetValueFunc *get_value,
2201            gboolean      weighted,
2202            guint        *offset)
2203 {
2204   GPtrArray *keys;
2205   MapData map_data;
2206   FilterData filter_data;
2207
2208   keys = g_ptr_array_new ();
2209   
2210   filter_data.keys = keys;
2211   filter_data.filter = filter;
2212   g_hash_table_foreach (map, add_key, &filter_data);
2213
2214   g_ptr_array_sort (keys, strcmp2);
2215
2216   if (!write_card32 (cache, keys->len))
2217     return FALSE;
2218
2219   map_data.cache = cache;
2220   map_data.pool = strings;
2221   map_data.get_value = get_value;
2222   map_data.data = map;
2223   map_data.weighted = weighted;
2224   map_data.offset = *offset + 4;
2225   map_data.error = FALSE;
2226
2227   g_ptr_array_foreach (keys, write_map_entry, &map_data);
2228
2229   *offset = map_data.offset;
2230
2231   return !map_data.error;
2232 }
2233
2234 static gchar **
2235 get_type_value (gpointer  data, 
2236                 gchar    *key)
2237 {
2238   Type *type;
2239   gchar **result;
2240
2241   type = (Type *)g_hash_table_lookup ((GHashTable *)data, key);
2242   
2243   result = g_new0 (gchar *, 3);
2244   result[0] = g_strdup (key);
2245   result[1] = g_strdup_printf ("%s/%s", type->media, type->subtype);
2246
2247   return result;
2248 }
2249
2250 static gchar **
2251 get_glob_list_value (gpointer  data, 
2252                      gchar    *key)
2253 {
2254   GList *list;
2255   Glob *glob;
2256   Type *type;
2257   gchar **result;
2258   gint i;
2259
2260   list = (GList *)g_hash_table_lookup ((GHashTable *)data, key);
2261   
2262   result = g_new0 (gchar *, 1 + 3 * g_list_length (list));
2263   
2264   i = 0;
2265   for (; list; list = list->next)
2266     {
2267       glob = (Glob *)list->data;
2268       type = glob->type;
2269
2270       result[i++] = g_strdup (glob->pattern);
2271       result[i++] = g_strdup_printf ("%s/%s", type->media, type->subtype);
2272       result[i++] = g_strdup_printf ("%d", glob->weight);
2273     }
2274   return result;
2275 }
2276
2277 static gboolean
2278 write_alias_cache (FILE       *cache, 
2279                    GHashTable *strings,
2280                    guint      *offset)
2281 {
2282   return write_map (cache, strings, alias_hash, NULL, get_type_value, FALSE, offset);
2283 }
2284                    
2285 static void
2286 write_parent_entry (gpointer key,
2287                     gpointer data)
2288 {
2289   gchar *mimetype = (gchar *)key;
2290   MapData *map_data = (MapData *)data;
2291   guint parents_offset, offset;
2292   GList *parents;
2293
2294   parents = (GList *)g_hash_table_lookup (subclass_hash, mimetype);
2295   offset = GPOINTER_TO_UINT (g_hash_table_lookup (map_data->pool, mimetype));
2296   if (offset == 0)
2297     {
2298       g_warning ("Missing string: '%s'\n", (gchar *)key);
2299       map_data->error = TRUE;  
2300     }
2301
2302   parents_offset = map_data->offset;
2303   map_data->offset += 4 + 4 * g_list_length (parents);
2304
2305   if (!write_card32 (map_data->cache, offset) ||
2306       !write_card32 (map_data->cache, parents_offset))
2307     map_data->error = TRUE;
2308 }
2309
2310 static void
2311 write_parent_list (gpointer key,
2312                    gpointer data)
2313 {
2314   gchar *mimetype = (gchar *)key;
2315   MapData *map_data = (MapData *)data;
2316   guint offset;
2317   GList *parents, *p;
2318
2319   parents = (GList *)g_hash_table_lookup (subclass_hash, mimetype);
2320
2321   if (!write_card32 (map_data->cache, g_list_length (parents)))
2322     map_data->error = TRUE;
2323
2324   for (p = parents; p; p = p->next)
2325     {
2326       gchar *parent = (gchar *)p->data;
2327       
2328       offset = GPOINTER_TO_UINT (g_hash_table_lookup (map_data->pool, parent));
2329       if (offset == 0)
2330         {
2331           g_warning ("Missing string: '%s'\n", parent);
2332           map_data->error = TRUE;  
2333         }
2334       
2335       if (!write_card32 (map_data->cache, offset))
2336         map_data->error = TRUE;
2337     }
2338
2339   map_data->offset += 4 + 4 * g_list_length (parents);
2340 }
2341
2342 static gboolean
2343 write_parent_cache (FILE       *cache,
2344                     GHashTable *strings,
2345                     guint      *offset)
2346 {
2347   GPtrArray *keys;
2348   MapData map_data;
2349   FilterData filter_data;
2350
2351   keys = g_ptr_array_new ();
2352
2353   filter_data.keys = keys;
2354   filter_data.filter = NULL;
2355   g_hash_table_foreach (subclass_hash, add_key, &filter_data);
2356
2357   g_ptr_array_sort (keys, strcmp2);
2358
2359   if (!write_card32 (cache, keys->len))
2360     return FALSE;
2361
2362   map_data.cache = cache;
2363   map_data.pool = strings;
2364   map_data.offset = *offset + 4 + keys->len * 8;
2365   map_data.error = FALSE;
2366
2367   g_ptr_array_foreach (keys, write_parent_entry, &map_data);
2368
2369   map_data.offset = *offset + 4 + keys->len * 8;
2370   g_ptr_array_foreach (keys, write_parent_list, &map_data);
2371
2372   *offset = map_data.offset;
2373
2374   return !map_data.error;
2375 }
2376
2377 typedef enum 
2378 {
2379   GLOB_LITERAL,
2380   GLOB_SIMPLE,
2381   GLOB_FULL
2382 } GlobType;
2383
2384 static GlobType
2385 glob_type (gchar *glob)
2386 {
2387   gchar *ptr;
2388   gboolean maybe_in_simple_glob = FALSE;
2389   gboolean first_char = TRUE;
2390
2391   ptr = glob;
2392
2393   while (*ptr != '\0')
2394     {
2395       if (*ptr == '*' && first_char)
2396         maybe_in_simple_glob = TRUE;
2397       else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*')
2398         return GLOB_FULL;
2399       
2400       first_char = FALSE;
2401       ptr = g_utf8_next_char (ptr);
2402     }
2403
2404   if (maybe_in_simple_glob)
2405     return GLOB_SIMPLE;
2406
2407   return GLOB_LITERAL;
2408 }
2409
2410 static gboolean
2411 is_literal_glob (gpointer key)
2412 {
2413   return glob_type ((gchar *)key) == GLOB_LITERAL;
2414 }
2415
2416 static gboolean
2417 is_simple_glob (gpointer key)
2418 {
2419   return glob_type ((gchar *)key) == GLOB_SIMPLE;
2420 }
2421
2422 static gboolean
2423 is_full_glob (gpointer key)
2424 {
2425   return glob_type ((gchar *)key) == GLOB_FULL;
2426 }
2427
2428 static gboolean
2429 write_literal_cache (FILE       *cache,
2430                      GHashTable *strings,
2431                      guint      *offset)
2432 {
2433   return write_map (cache, strings, globs_hash, is_literal_glob, 
2434                     get_glob_list_value, TRUE, offset); 
2435 }
2436
2437 static gboolean
2438 write_glob_cache (FILE       *cache,
2439                   GHashTable *strings,
2440                   guint      *offset)
2441 {
2442   return write_map (cache, strings, globs_hash, is_full_glob, 
2443                     get_glob_list_value, TRUE, offset); 
2444 }
2445
2446 typedef struct _SuffixEntry SuffixEntry;
2447
2448 struct _SuffixEntry
2449 {
2450   gunichar character;
2451   gchar *mimetype;
2452   gint weight;
2453   GList *children;
2454   guint size;
2455   guint depth;
2456 };
2457
2458 static GList *
2459 insert_suffix (gunichar *suffix, 
2460                gchar    *mimetype,
2461                gint      weight, 
2462                GList    *suffixes)
2463 {
2464   GList *l;
2465   SuffixEntry *s = NULL;
2466
2467   for (l = suffixes; l; l = l->next)
2468     {
2469       s = (SuffixEntry *)l->data;
2470
2471       if (s->character > suffix[0])
2472         {
2473           s = g_new0 (SuffixEntry, 1);
2474           s->character = suffix[0];
2475           s->mimetype = NULL;
2476           s->children = NULL;
2477
2478           suffixes = g_list_insert_before (suffixes, l, s);
2479         }
2480
2481       if (s->character == suffix[0])
2482         break;
2483     }
2484
2485   if (!s || s->character != suffix[0])
2486     {
2487       s = g_new0 (SuffixEntry, 1);
2488       s->character = suffix[0];
2489       s->mimetype = NULL;
2490       s->children = NULL;
2491
2492       suffixes = g_list_append (suffixes, s);
2493     }
2494
2495   if (suffix[1] == 0)
2496     {
2497       GList *l2;
2498       SuffixEntry *s2;
2499       gboolean found = FALSE;
2500
2501       for (l2 = s->children; l2; l2 = l2->next)
2502         {
2503           s2 = (SuffixEntry *)l2->data;
2504           if (s2->character != 0)
2505             break;
2506           if (strcmp (s2->mimetype, mimetype) == 0)
2507             {
2508               if (s2->weight < weight)
2509                 s2->weight = weight;
2510               found = TRUE;
2511               break;
2512             }
2513         }
2514       if (!found)
2515         {
2516           s2 = g_new0 (SuffixEntry, 1);
2517           s2->character = 0;
2518           s2->mimetype = mimetype;
2519           s2->weight = weight;
2520           s2->children = NULL;
2521           s->children = g_list_insert_before (s->children, l2, s2);
2522         }
2523     } 
2524   else
2525     s->children = insert_suffix (suffix + 1, mimetype, weight, s->children);
2526
2527   return suffixes;
2528 }
2529
2530 static void
2531 ucs4_reverse (gunichar *in, glong len)
2532 {
2533   int i;
2534   gunichar c;
2535
2536   for (i = 0; i < len - i - 1; i++)
2537     {
2538       c = in[i];
2539       in[i] = in[len - i - 1];
2540       in[len - i - 1] = c;
2541     }
2542 }
2543
2544 static void
2545 build_suffixes (gpointer key,
2546                 gpointer value,
2547                 gpointer data)
2548 {
2549   gchar *pattern = (gchar *)key;
2550   GList *list = (GList *)value;
2551   GList **suffixes = (GList **)data;
2552   gunichar *suffix;
2553   gchar *mimetype;
2554   Glob *glob;
2555   Type *type;
2556   glong len;
2557   
2558   if (is_simple_glob (pattern))
2559     {
2560       suffix = g_utf8_to_ucs4 (pattern + 1, -1, NULL, &len, NULL);
2561       
2562       if (suffix == NULL)
2563         {
2564           g_warning ("Glob '%s' is not valid UTF-8\n", pattern);
2565           return;
2566         }
2567
2568       ucs4_reverse (suffix, len);
2569       for ( ; list; list = list->next)
2570         {
2571           glob = (Glob *)list->data;
2572           type = glob->type;
2573           mimetype = g_strdup_printf ("%s/%s", type->media, type->subtype);
2574
2575           *suffixes = insert_suffix (suffix, mimetype, glob->weight, *suffixes);
2576         }
2577
2578       g_free (suffix);
2579     }
2580 }
2581
2582 static void
2583 calculate_size (SuffixEntry *entry)
2584 {
2585   GList *s;
2586
2587   entry->size = 0;
2588   entry->depth = 0;
2589   for (s = entry->children; s; s= s->next)
2590     {
2591       SuffixEntry *child = (SuffixEntry *)s->data;
2592
2593       calculate_size (child);
2594       entry->size += 1 + child->size;
2595       entry->depth = MAX (entry->depth, child->depth + 1);
2596     }
2597 }
2598
2599 static gboolean 
2600 write_suffix_entries (FILE        *cache, 
2601                       guint        depth,
2602                       SuffixEntry *entry,
2603                       GHashTable *strings, 
2604                       guint      *child_offset)
2605 {
2606   GList *c;
2607   guint offset;
2608
2609   if (depth > 0)
2610     {
2611       gboolean error = FALSE;
2612
2613       for (c = entry->children; c; c = c->next)
2614         {
2615           SuffixEntry *child = (SuffixEntry *)c->data;
2616           if (!write_suffix_entries (cache, depth - 1, child, strings, child_offset))
2617             error = TRUE;
2618         }
2619
2620       return !error;
2621     }
2622     
2623   if (entry->mimetype)
2624     {
2625       offset = GPOINTER_TO_UINT(g_hash_table_lookup (strings, entry->mimetype));
2626       if (offset == 0)
2627         {
2628           g_warning ("Missing string: '%s'\n", entry->mimetype);
2629           return FALSE;
2630         }
2631     }
2632   else
2633     offset = 0;
2634
2635   if (entry->character == 0)
2636     {
2637       if (!write_card32 (cache, entry->character))
2638         return FALSE;
2639
2640       if (!write_card32 (cache, offset))
2641         return FALSE;
2642
2643       if (!write_card32 (cache, entry->weight))
2644         return FALSE;
2645     }
2646   else
2647     {
2648       if (!write_card32 (cache, entry->character))
2649         return FALSE;
2650
2651       if (!write_card32 (cache, g_list_length (entry->children)))
2652         return FALSE;
2653   
2654       if (!write_card32 (cache, *child_offset))
2655         return FALSE;
2656     }
2657
2658   *child_offset += 12 * g_list_length (entry->children);
2659
2660   return TRUE;
2661 }
2662
2663 static gboolean
2664 write_suffix_cache (FILE        *cache, 
2665                     GHashTable *strings, 
2666                     guint      *offset)
2667 {
2668   GList *suffixes, *s;
2669   guint n_entries;
2670   guint child_offset;
2671   guint depth, d;
2672
2673   suffixes = NULL;
2674
2675   g_hash_table_foreach (globs_hash, build_suffixes, &suffixes);
2676
2677   n_entries = g_list_length (suffixes);
2678
2679   *offset += 8;
2680   child_offset = *offset + 12 * n_entries;
2681   depth = 0;
2682   for (s = suffixes; s; s= s->next)
2683     {
2684       SuffixEntry *entry = (SuffixEntry *)s->data;
2685       calculate_size (entry);
2686       depth = MAX (depth, entry->depth + 1);
2687     }
2688
2689   if (!write_card32 (cache, n_entries) || !write_card32 (cache, *offset))
2690     return FALSE;
2691
2692   for (d = 0; d < depth; d++)
2693     {
2694       for (s = suffixes; s; s = s->next)
2695         {
2696           SuffixEntry *entry = (SuffixEntry *)s->data;
2697           
2698           if (!write_suffix_entries (cache,  d, entry, strings, &child_offset))
2699             return FALSE;
2700         }
2701     }
2702
2703   *offset = child_offset;
2704
2705   return TRUE;
2706 }
2707
2708 typedef struct {
2709   FILE       *cache;
2710   GHashTable *strings;
2711   GList      *matches;
2712   guint       offset;
2713   gboolean    error;
2714 } WriteMatchData;
2715
2716
2717 static void
2718 write_match (gpointer key,
2719              gpointer data)
2720 {
2721   Magic *magic = (Magic *)key;
2722   WriteMatchData *mdata = (WriteMatchData *)data;
2723   gchar *mimetype;
2724   guint offset;
2725
2726   if (!write_card32 (mdata->cache, magic->priority))
2727     {
2728       mdata->error = TRUE;
2729       return;
2730     }
2731
2732   mimetype = g_strdup_printf ("%s/%s", magic->type->media, magic->type->subtype);
2733   offset = GPOINTER_TO_UINT (g_hash_table_lookup (mdata->strings, mimetype));
2734   if (offset == 0)
2735     {
2736       g_warning ("Missing string: '%s'\n", mimetype);
2737       g_free (mimetype);
2738       mdata->error = TRUE;
2739       return;
2740     }
2741   g_free (mimetype);
2742   
2743   if (!write_card32 (mdata->cache, offset))
2744     {
2745       mdata->error = TRUE;
2746       return;
2747     }
2748
2749   if (!write_card32 (mdata->cache, g_list_length (magic->matches)))
2750     {
2751       mdata->error = TRUE;
2752       return;
2753     }
2754
2755     offset = mdata->offset + 32 * g_list_index (mdata->matches, magic->matches->data);
2756
2757   if (!write_card32 (mdata->cache, offset))
2758     {
2759       mdata->error = TRUE;
2760       return;
2761     }
2762 }
2763
2764 static gboolean
2765 write_matchlet (FILE           *cache,
2766                 Match          *match,
2767                 GList          *matches,
2768                 gint            offset,
2769                 gint           *offset2)
2770 {
2771   if (!write_card32 (cache, match->range_start) ||
2772       !write_card32 (cache, match->range_length) ||
2773       !write_card32 (cache, match->word_size) ||
2774       !write_card32 (cache, match->data_length) ||
2775       !write_card32 (cache, *offset2))
2776     return FALSE;
2777   
2778   *offset2 = ALIGN_VALUE (*offset2 + match->data_length, 4);
2779       
2780   if (match->mask)
2781     {
2782       if (!write_card32 (cache, *offset2))
2783         return FALSE;
2784       
2785       *offset2 = ALIGN_VALUE (*offset2 + match->data_length, 4);
2786     }
2787   else
2788     {
2789       if (!write_card32 (cache, 0))
2790         return FALSE;
2791     }
2792
2793   if (match->matches)
2794     {
2795       if (!write_card32 (cache, g_list_length (match->matches)) ||
2796           !write_card32 (cache, offset + 32 * g_list_index (matches, match->matches->data)))
2797         return FALSE;
2798     }
2799   else
2800     {
2801       if (!write_card32 (cache, 0) ||
2802           !write_card32 (cache, 0))
2803         return FALSE;
2804     }
2805
2806   return TRUE;
2807 }  
2808
2809 static gboolean
2810 write_matchlet_data (FILE           *cache,
2811                      Match          *match,
2812                      gint           *offset2)
2813 {
2814   if (!write_data (cache, match->data, match->data_length))
2815     return FALSE;
2816   
2817   *offset2 = ALIGN_VALUE (*offset2 + match->data_length, 4);
2818
2819   if (match->mask)
2820     {
2821       if (!write_data (cache, match->mask, match->data_length))
2822         return FALSE;
2823
2824       *offset2 = ALIGN_VALUE (*offset2 + match->data_length, 4);
2825     }
2826
2827   return TRUE;
2828 }
2829
2830 static void
2831 collect_matches_list (GList *list, GList **matches)
2832 {
2833   GList *l;
2834
2835   for (l = list; l; l = l->next)
2836     *matches = g_list_prepend (*matches, l->data);
2837
2838   for (l = list; l; l = l->next)
2839     {  
2840       Match *match = (Match *)l->data;
2841       collect_matches_list (match->matches, matches);
2842     }
2843 }
2844
2845 static void
2846 collect_matches (gpointer key, gpointer data)
2847 {
2848   Magic *magic = (Magic *)key;
2849   GList **matches = (GList **)data;
2850
2851   collect_matches_list (magic->matches, matches);
2852 }
2853
2854 static gboolean
2855 write_magic_cache (FILE        *cache, 
2856                    GHashTable *strings, 
2857                    guint      *offset)
2858 {
2859   guint n_entries, max_extent;
2860   gint offset2;
2861   GList *m;
2862   WriteMatchData data;
2863   
2864   data.matches = NULL;
2865   g_ptr_array_foreach (magic_array, collect_matches, &data.matches);
2866   data.matches = g_list_reverse (data.matches);
2867
2868   max_extent = 0;
2869   for (m = data.matches; m; m = m->next)
2870     {
2871       Match *match = (Match *)m->data;
2872       max_extent = MAX (max_extent, match->data_length + match->range_start + match->range_length);
2873     }
2874
2875   n_entries = magic_array->len;
2876
2877   *offset += 12;
2878   
2879   if (!write_card32 (cache, n_entries) ||
2880       !write_card32 (cache, max_extent) ||
2881       !write_card32 (cache, *offset))
2882     return FALSE;
2883
2884   *offset += 16 * n_entries;
2885
2886   data.cache = cache;
2887   data.strings = strings;
2888   data.offset = *offset;
2889   data.error = FALSE;
2890
2891   offset2 = *offset + 32 * g_list_length (data.matches);
2892
2893   g_ptr_array_foreach (magic_array, write_match, &data);
2894   for (m = data.matches; m; m = m->next)
2895     {
2896       Match *match = (Match *)m->data;
2897       write_matchlet (cache, match, data.matches, *offset, &offset2);
2898     }
2899
2900   offset2 = *offset + 32 * g_list_length (data.matches);
2901
2902   for (m = data.matches; m; m = m->next)
2903     {
2904       Match *match = (Match *)m->data;
2905       write_matchlet_data (cache, match, &offset2);
2906     }
2907
2908   *offset = offset2;
2909
2910   g_list_free (data.matches);
2911
2912   return !data.error;
2913 }
2914
2915 static gchar **
2916 get_namespace_value (gpointer  data, 
2917                      gchar    *key)
2918 {
2919   Type *type;
2920   gchar **result;
2921   gchar *space;
2922
2923   type = (Type *)g_hash_table_lookup ((GHashTable *)data, key);
2924   
2925   result = g_new0 (gchar *, 4);
2926   space = strchr (key, ' ');
2927   if (*space)
2928     {
2929       *space = '\0';
2930       result[0] = g_strdup (key);
2931       result[1] = g_strdup (space + 1);
2932       *space = ' ';
2933     }
2934   else 
2935     result[0] = g_strdup (key);
2936
2937   result[2] = g_strdup_printf ("%s/%s", type->media, type->subtype);
2938
2939   return result;
2940 }
2941
2942 static gboolean
2943 write_namespace_cache (FILE       *cache,
2944                        GHashTable *strings,
2945                        guint      *offset)
2946 {
2947   return write_map (cache, strings, namespace_hash, NULL, 
2948                     get_namespace_value, FALSE, offset); 
2949 }
2950
2951 static gchar **
2952 get_icon_value (gpointer  data, 
2953                 gchar    *key)
2954 {
2955   gchar *iconname;
2956   gchar **result;
2957
2958   iconname = (gchar *)g_hash_table_lookup ((GHashTable *)data, key);
2959   
2960   result = g_new0 (gchar *, 3);
2961   result[0] = g_strdup (key);
2962   result[1] = g_strdup (iconname);
2963   result[2] = NULL;
2964
2965   return result;
2966 }
2967
2968 static gboolean
2969 write_icons_cache (FILE       *cache,
2970                    GHashTable *strings,
2971                    GHashTable *icon_hash,
2972                    guint      *offset)
2973 {
2974   return write_map (cache, strings, icon_hash, NULL, 
2975                     get_icon_value, FALSE, offset); 
2976 }
2977
2978 /* Write all the collected types */
2979 static gboolean
2980 write_types_cache (FILE       *cache,
2981                    GHashTable *strings,
2982                    GHashTable *types,
2983                    guint      *offset)
2984 {
2985         GPtrArray *lines;
2986         int i;
2987         char *mimetype;
2988         guint mime_offset;
2989         
2990         lines = g_ptr_array_new();
2991
2992         g_hash_table_foreach(types, add_type, lines);
2993
2994         g_ptr_array_sort(lines, strcmp2);
2995
2996         if (!write_card32 (cache, lines->len))
2997                 return FALSE;
2998
2999         for (i = 0; i < lines->len; i++)
3000         {
3001                 mimetype = (char *) lines->pdata[i];
3002                 mime_offset = GPOINTER_TO_UINT (g_hash_table_lookup (strings, mimetype));
3003                 if (!write_card32 (cache, mime_offset))
3004                         return FALSE;
3005
3006                 g_free(mimetype);
3007         }
3008
3009         *offset += 4 + 4 * lines->len;
3010
3011         g_ptr_array_free(lines, TRUE);
3012
3013         return TRUE;
3014 }
3015
3016 static void
3017 collect_alias (gpointer key,
3018                gpointer value,
3019                gpointer data)
3020 {
3021   GHashTable *strings = (GHashTable *)data;
3022   Type *type = (Type *)value;
3023   gchar *mimetype;
3024   
3025   mimetype = g_strdup_printf ("%s/%s", type->media, type->subtype);
3026   g_hash_table_insert (strings, key, NULL);
3027   g_hash_table_insert (strings, mimetype, NULL);
3028 }
3029
3030
3031 static void
3032 collect_parents (gpointer key,
3033                  gpointer value,
3034                  gpointer data)
3035 {
3036   GList *parents = (GList *)value;
3037   GHashTable *strings = (GHashTable *)data;
3038   GList *p;
3039   
3040   g_hash_table_insert (strings, key, NULL);
3041   for (p = parents; p; p = p->next)
3042     g_hash_table_insert (strings, p->data, NULL);
3043 }
3044
3045 static void
3046 collect_glob (gpointer key,
3047               gpointer value,
3048               gpointer data)
3049 {
3050   GList *list = (GList *)value;
3051   GHashTable *strings = (GHashTable *)data;
3052   gchar *mimetype;
3053   Glob *glob;
3054   Type *type;
3055
3056   switch (glob_type ((char *)key))
3057     {
3058       case GLOB_LITERAL:
3059       case GLOB_FULL:
3060         g_hash_table_insert (strings, key, NULL);
3061         break;
3062      default:
3063         break;
3064    }
3065
3066   for (; list; list = list->next)
3067     {
3068       glob = (Glob *)list->data;
3069       type = glob->type;
3070       mimetype = g_strdup_printf ("%s/%s", type->media, type->subtype);
3071
3072      g_hash_table_insert (strings, mimetype, NULL);
3073     }
3074 }
3075
3076 static void
3077 collect_magic (gpointer key,
3078                gpointer data)
3079 {
3080   Magic *magic = (Magic *)key;
3081   GHashTable *strings = (GHashTable *)data;
3082   gchar *mimetype;
3083   
3084   mimetype = g_strdup_printf ("%s/%s", magic->type->media, magic->type->subtype);
3085   g_hash_table_insert (strings, mimetype, NULL);
3086 }
3087
3088 static void
3089 collect_namespace (gpointer key,
3090                    gpointer value,
3091                    gpointer data)
3092 {
3093   gchar *ns = (gchar *)key;
3094   Type *type = (Type *)value;
3095   GHashTable *strings = (GHashTable *)data;
3096   gchar *mimetype;
3097   gchar *space;
3098
3099   mimetype = g_strdup_printf ("%s/%s", type->media, type->subtype);
3100   g_hash_table_insert (strings, mimetype, NULL);
3101   
3102   space = strchr (ns, ' ');
3103
3104   if (space)
3105     {
3106       *space = '\0';
3107       g_hash_table_insert (strings, g_strdup (ns), NULL);
3108       g_hash_table_insert (strings, space + 1, NULL);
3109       *space = ' ';
3110     }
3111   else     
3112     g_hash_table_insert (strings, ns, NULL);
3113 }
3114
3115 static void 
3116 collect_icons(gpointer key, 
3117               gpointer value, 
3118               gpointer data)
3119 {
3120   gchar *mimetype = (gchar *)key;
3121   gchar *iconname = (gchar *)value;
3122   GHashTable *strings = (GHashTable *)data;
3123
3124   g_hash_table_insert (strings, mimetype, NULL);
3125   g_hash_table_insert (strings, iconname, NULL);
3126 }
3127
3128
3129 static void
3130 collect_strings (GHashTable *strings)
3131 {
3132   g_hash_table_foreach (alias_hash, collect_alias, strings); 
3133   g_hash_table_foreach (subclass_hash, collect_parents, strings); 
3134   g_hash_table_foreach (globs_hash, collect_glob, strings); 
3135   g_ptr_array_foreach (magic_array, collect_magic, strings); 
3136   g_hash_table_foreach (namespace_hash, collect_namespace, strings); 
3137   g_hash_table_foreach (generic_icon_hash, collect_icons, strings); 
3138   g_hash_table_foreach (icon_hash, collect_icons, strings); 
3139 }
3140
3141 typedef struct 
3142 {
3143   FILE       *cache;
3144   GHashTable *strings;
3145   guint       offset;
3146   gboolean    error;
3147 } StringData;
3148
3149 static void
3150 write_one_string (gpointer key,
3151                   gpointer value,
3152                   gpointer data)
3153 {
3154   gchar *str = (gchar *)key;
3155   StringData *sdata = (StringData *)data;
3156
3157   if (!write_string (sdata->cache, str))
3158     sdata->error = TRUE;
3159
3160   g_hash_table_insert (sdata->strings, str, GUINT_TO_POINTER (sdata->offset));
3161   
3162   sdata->offset = ALIGN_VALUE (sdata->offset + strlen (str) + 1, 4);
3163 }
3164
3165 static gboolean
3166 write_strings (FILE       *cache, 
3167                GHashTable *strings,       
3168                guint      *offset)
3169 {
3170   StringData data;
3171
3172   data.cache = cache;
3173   data.strings = strings;
3174   data.offset = *offset;
3175   data.error = FALSE;
3176
3177   g_hash_table_foreach (strings, write_one_string, &data);
3178
3179   *offset = data.offset;
3180
3181   return !data.error;
3182 }
3183
3184 static gboolean 
3185 write_cache (FILE *cache)
3186 {
3187   guint strings_offset;
3188   guint alias_offset;
3189   guint parent_offset;
3190   guint literal_offset;
3191   guint suffix_offset;
3192   guint glob_offset;
3193   guint magic_offset;
3194   guint namespace_offset;
3195   guint icons_list_offset;
3196   guint generic_icons_list_offset;
3197   guint type_offset;
3198   guint offset;
3199   GHashTable *strings;
3200
3201   offset = 0;
3202   if (!write_header (cache, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &offset))
3203     {
3204       g_warning ("Failed to write header\n");
3205       return FALSE;
3206     }
3207
3208   strings = g_hash_table_new (g_str_hash, g_str_equal);
3209   collect_strings (strings);
3210   strings_offset = offset;
3211
3212   if (!write_strings (cache, strings, &offset))
3213     {
3214       g_warning ("Failed to write strings\n");
3215       return FALSE;
3216     }
3217   g_message ("Wrote %d strings at %x - %x\n", 
3218            g_hash_table_size (strings), strings_offset, offset);
3219
3220   alias_offset = offset;
3221   if (!write_alias_cache (cache, strings, &offset))
3222     {
3223       g_warning ("Failed to write alias list\n");
3224       return FALSE;
3225     }
3226   g_message ("Wrote aliases at %x - %x\n", alias_offset, offset);
3227
3228   parent_offset = offset;
3229   if (!write_parent_cache (cache, strings, &offset))
3230     {
3231       g_warning ("Failed to write parent list\n");
3232       return FALSE;
3233     }
3234   g_message ("Wrote parents at %x - %x\n", parent_offset, offset);
3235
3236   literal_offset = offset;
3237   if (!write_literal_cache (cache, strings, &offset))
3238     {
3239       g_warning ("Failed to write literal list\n");
3240       return FALSE;
3241     }
3242   g_message ("Wrote literal globs at %x - %x\n", literal_offset, offset);
3243
3244   suffix_offset = offset;
3245   if (!write_suffix_cache (cache, strings, &offset))
3246     {
3247       g_warning ("Failed to write suffix list\n");
3248       return FALSE;
3249     }
3250   g_message ("Wrote suffix globs at %x - %x\n", suffix_offset, offset);
3251
3252   glob_offset = offset;
3253   if (!write_glob_cache (cache, strings, &offset))
3254     {
3255       g_warning ("Failed to write glob list\n");
3256       return FALSE;
3257     }
3258   g_message ("Wrote full globs at %x - %x\n", glob_offset, offset);
3259
3260   magic_offset = offset;
3261   if (!write_magic_cache (cache, strings, &offset))
3262     {
3263       g_warning ("Failed to write magic list\n");
3264       return FALSE;
3265     }
3266   g_message ("Wrote magic at %x - %x\n", magic_offset, offset);
3267
3268   namespace_offset = offset;
3269   if (!write_namespace_cache (cache, strings, &offset))
3270     {
3271       g_warning ("Failed to write namespace list\n");
3272       return FALSE;
3273     }
3274   g_message ("Wrote namespace list at %x - %x\n", namespace_offset, offset);
3275
3276   icons_list_offset = offset;
3277   if (!write_icons_cache (cache, strings, icon_hash, &offset))
3278     {
3279       g_warning ("Failed to write icons list\n");
3280       return FALSE;
3281     }
3282   g_message ("Wrote icons list at %x - %x\n", icons_list_offset, offset);
3283
3284   generic_icons_list_offset = offset;
3285   if (!write_icons_cache (cache, strings, generic_icon_hash, &offset))
3286     {
3287       g_warning ("Failed to write generic icons list\n");
3288       return FALSE;
3289     }
3290   g_message ("Wrote generic icons list at %x - %x\n", generic_icons_list_offset, offset);
3291
3292   type_offset = offset;
3293   if (!write_types_cache (cache, strings, types, &offset))
3294     {
3295       g_warning ("Failed to write types list\n");
3296       return FALSE;
3297     }
3298   g_message ("Wrote types list at %x - %x\n", type_offset, offset);
3299
3300   rewind (cache);
3301   offset = 0; 
3302
3303   if (!write_header (cache, 
3304                      alias_offset, parent_offset, literal_offset,
3305                      suffix_offset, glob_offset, magic_offset, 
3306                      namespace_offset, icons_list_offset,
3307                      generic_icons_list_offset, type_offset, 
3308                      &offset))
3309     {
3310       g_warning ("Failed to rewrite header\n");
3311       return FALSE;
3312     }
3313
3314   g_hash_table_destroy (strings);
3315
3316   return TRUE;
3317 }
3318
3319
3320 static FILE *
3321 open_or_die(const char *filename)
3322 {
3323         FILE *stream = fopen(filename, "wb");
3324
3325         if (!stream)
3326         {
3327                 g_printerr("Failed to open '%s' for writing\n", filename);
3328                 exit(EXIT_FAILURE);
3329         }
3330
3331         return stream;
3332 }
3333
3334 int main(int argc, char **argv)
3335 {
3336         char *mime_dir = NULL;
3337         char *package_dir = NULL;
3338         int opt;
3339
3340         /* Install the filtering log handler */
3341         g_log_set_default_handler(g_log_handler, NULL);
3342
3343         while ((opt = getopt(argc, argv, "hvV")) != -1)
3344         {
3345                 switch (opt)
3346                 {
3347                         case '?':
3348                                 usage(argv[0]);
3349                                 return EXIT_FAILURE;
3350                         case 'h':
3351                                 usage(argv[0]);
3352                                 return EXIT_SUCCESS;
3353                         case 'v':
3354                                 g_fprintf(stderr,
3355                                           "update-mime-database (" PACKAGE ") "
3356                                           VERSION "\n" COPYING);
3357                                 return EXIT_SUCCESS;
3358                         case 'V':
3359                                 enabled_log_levels |= G_LOG_LEVEL_MESSAGE
3360                                                       | G_LOG_LEVEL_INFO;
3361                                 break;
3362                         default:
3363                                 return EXIT_FAILURE;
3364                 }
3365         }
3366
3367         if (optind != argc - 1)
3368         {
3369                 usage(argv[0]);
3370                 return EXIT_FAILURE;
3371         }
3372
3373         LIBXML_TEST_VERSION;
3374
3375         mime_dir = argv[optind];
3376
3377         /* Strip trailing / characters */
3378         {
3379                 int l = strlen(mime_dir);
3380                 while (l > 1 && mime_dir[l - 1] == '/')
3381                 {
3382                         l--;
3383                         mime_dir[l] = '\0';
3384                 }
3385         }
3386
3387         package_dir = g_strconcat(mime_dir, "/packages", NULL);
3388
3389         if (access(mime_dir, W_OK))
3390         {
3391                 g_warning(_("%s: I don't have write permission on %s.\n"
3392                              "Try rerunning me as root.\n"), argv[0], mime_dir);
3393                 return EXIT_FAILURE;
3394         }
3395
3396         g_message("Updating MIME database in %s...\n", mime_dir);
3397
3398         if (access(package_dir, F_OK))
3399         {
3400                 g_fprintf(stderr,
3401                         _("Directory '%s' does not exist!\n"), package_dir);
3402                 return EXIT_FAILURE;
3403         }
3404
3405         types = g_hash_table_new_full(g_str_hash, g_str_equal,
3406                                         g_free, free_type);
3407         globs_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
3408                                         g_free, NULL);
3409         namespace_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
3410                                         g_free, NULL);
3411         magic_array = g_ptr_array_new();
3412         tree_magic_array = g_ptr_array_new();
3413         subclass_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
3414                                               g_free, free_string_list);
3415         alias_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
3416                                            g_free, NULL);
3417         icon_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
3418                                           g_free, NULL);
3419         generic_icon_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
3420                                                   g_free, NULL);
3421
3422         scan_source_dir(package_dir);
3423         g_free(package_dir);
3424
3425         delete_old_types(mime_dir);
3426
3427         g_hash_table_foreach(types, write_out_type, (gpointer) mime_dir);
3428
3429         {
3430                 FILE *globs;
3431                 char *globs_path;
3432                 GList *glob_list = NULL;
3433
3434                 g_hash_table_foreach(globs_hash, collect_glob2, &glob_list);
3435                 glob_list = g_list_sort(glob_list, (GCompareFunc)compare_by_weight);
3436                 globs_path = g_strconcat(mime_dir, "/globs.new", NULL);
3437                 globs = open_or_die(globs_path);
3438                 g_fprintf(globs,
3439                           "# This file was automatically generated by the\n"
3440                           "# update-mime-database command. DO NOT EDIT!\n");
3441                 write_out_glob(glob_list, globs);
3442                 fclose(globs);
3443                 atomic_update(globs_path);
3444                 g_free(globs_path);
3445
3446                 globs_path = g_strconcat(mime_dir, "/globs2.new", NULL);
3447                 globs = open_or_die(globs_path);
3448                 g_fprintf(globs,
3449                           "# This file was automatically generated by the\n"
3450                           "# update-mime-database command. DO NOT EDIT!\n");
3451                 write_out_glob2 (glob_list, globs);
3452                 fclose(globs);
3453                 atomic_update(globs_path);
3454                 g_free(globs_path);
3455
3456                 g_list_free (glob_list);
3457         }
3458
3459         {
3460                 FILE *stream;
3461                 char *magic_path;
3462                 int i;
3463                 magic_path = g_strconcat(mime_dir, "/magic.new", NULL);
3464                 stream = open_or_die(magic_path);
3465                 fwrite("MIME-Magic\0\n", 1, 12, stream);
3466
3467                 if (magic_array->len)
3468                         g_ptr_array_sort(magic_array, cmp_magic);
3469                 for (i = 0; i < magic_array->len; i++)
3470                 {
3471                         Magic *magic = (Magic *) magic_array->pdata[i];
3472
3473                         write_magic(stream, magic);
3474                 }
3475                 fclose(stream);
3476
3477                 atomic_update(magic_path);
3478                 g_free(magic_path);
3479         }
3480
3481         {
3482                 FILE *stream;
3483                 char *ns_path;
3484
3485                 ns_path = g_strconcat(mime_dir, "/XMLnamespaces.new", NULL);
3486                 stream = open_or_die(ns_path);
3487                 write_namespaces(stream);
3488                 fclose(stream);
3489
3490                 atomic_update(ns_path);
3491                 g_free(ns_path);
3492         }
3493         
3494         {
3495                 FILE *stream;
3496                 char *path;
3497                 
3498                 path = g_strconcat(mime_dir, "/subclasses.new", NULL);
3499                 stream = open_or_die(path);
3500                 write_subclasses(stream);
3501                 fclose(stream);
3502
3503                 atomic_update(path);
3504                 g_free(path);
3505         }
3506
3507         {
3508                 FILE *stream;
3509                 char *path;
3510                 
3511                 path = g_strconcat(mime_dir, "/aliases.new", NULL);
3512                 stream = open_or_die(path);
3513                 write_aliases(stream);
3514                 fclose(stream);
3515
3516                 atomic_update(path);
3517                 g_free(path);
3518         }
3519
3520         {
3521                 FILE *stream;
3522                 char *path;
3523                 
3524                 path = g_strconcat(mime_dir, "/types.new", NULL);
3525                 stream = open_or_die(path);
3526                 write_types(stream);
3527                 fclose(stream);
3528
3529                 atomic_update(path);
3530                 g_free(path);
3531         }
3532
3533         {
3534                 FILE *stream;
3535                 char *icon_path;
3536
3537                 icon_path = g_strconcat(mime_dir, "/generic-icons.new", NULL);
3538                 stream = open_or_die(icon_path);
3539                 write_icons(generic_icon_hash, stream);
3540                 fclose(stream);
3541
3542                 atomic_update(icon_path);
3543                 g_free(icon_path);
3544         }
3545
3546         {
3547                 FILE *stream;
3548                 char *icon_path;
3549
3550                 icon_path = g_strconcat(mime_dir, "/icons.new", NULL);
3551                 stream = open_or_die(icon_path);
3552                 write_icons(icon_hash, stream);
3553                 fclose(stream);
3554
3555                 atomic_update(icon_path);
3556                 g_free(icon_path);
3557         }
3558
3559         {
3560                 FILE *stream;
3561                 char *path;
3562                 int i;
3563                 path = g_strconcat(mime_dir, "/treemagic.new", NULL);
3564                 stream = open_or_die(path);
3565                 fwrite("MIME-TreeMagic\0\n", 1, 16, stream);
3566
3567                 if (tree_magic_array->len)
3568                         g_ptr_array_sort(tree_magic_array, cmp_tree_magic);
3569                 for (i = 0; i < tree_magic_array->len; i++)
3570                 {
3571                         TreeMagic *magic = (TreeMagic *) tree_magic_array->pdata[i];
3572
3573                         write_tree_magic(stream, magic);
3574                 }
3575                 fclose(stream);
3576
3577                 atomic_update(path);
3578                 g_free(path);
3579         }
3580
3581         {
3582                 FILE *stream;
3583                 char *path;
3584                 
3585                 path = g_strconcat(mime_dir, "/mime.cache.new", NULL);
3586                 stream = open_or_die(path);
3587                 write_cache(stream);
3588                 fclose(stream);
3589
3590                 atomic_update(path);
3591                 g_free(path);
3592         }
3593
3594         g_ptr_array_foreach(magic_array, (GFunc)magic_free, NULL);
3595         g_ptr_array_free(magic_array, TRUE);
3596         g_ptr_array_foreach(tree_magic_array, (GFunc)tree_magic_free, NULL);
3597         g_ptr_array_free(tree_magic_array, TRUE);
3598
3599         g_hash_table_destroy(types);
3600         g_hash_table_destroy(globs_hash);
3601         g_hash_table_destroy(namespace_hash);
3602         g_hash_table_destroy(subclass_hash);
3603         g_hash_table_destroy(alias_hash);
3604         g_hash_table_destroy(icon_hash);
3605         g_hash_table_destroy(generic_icon_hash);
3606
3607         check_in_path_xdg_data(mime_dir);
3608
3609         return EXIT_SUCCESS;
3610 }