Fix static analysis issues
[platform/core/appfw/xdgmime.git] / xdgmime / src / xdgmime.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* xdgmime.c: XDG Mime Spec mime resolver.  Based on version 0.11 of the spec.
3  *
4  * More info can be found at http://www.freedesktop.org/standards/
5  * 
6  * Copyright (C) 2003,2004  Red Hat, Inc.
7  * Copyright (C) 2003,2004  Jonathan Blandford <jrb@alum.mit.edu>
8  *
9  * Licensed under the Academic Free License version 2.0
10  * Or under the following terms:
11  * 
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31
32 #include "xdgmime.h"
33 #include "xdgmimeint.h"
34 #include "xdgmimeglob.h"
35 #include "xdgmimemagic.h"
36 #include "xdgmimealias.h"
37 #include "xdgmimeicon.h"
38 #include "xdgmimeparent.h"
39 #include "xdgmimecache.h"
40 #include <stdio.h>
41 #include <string.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #include <sys/time.h>
45 #include <unistd.h>
46 #include <assert.h>
47
48 #ifndef API
49 #define API __attribute__ ((visibility("default")))
50 #endif
51
52 typedef struct XdgDirTimeList XdgDirTimeList;
53 typedef struct XdgCallbackList XdgCallbackList;
54
55 static int need_reread = TRUE;
56 static time_t last_stat_time = 0;
57
58 static XdgGlobHash *global_hash = NULL;
59 static XdgMimeMagic *global_magic = NULL;
60 static XdgAliasList *alias_list = NULL;
61 static XdgParentList *parent_list = NULL;
62 static XdgDirTimeList *dir_time_list = NULL;
63 static XdgCallbackList *callback_list = NULL;
64 static XdgIconList *icon_list = NULL;
65 static XdgIconList *generic_icon_list = NULL;
66
67 XdgMimeCache **_caches = NULL;
68 static int n_caches = 0;
69
70 const char xdg_mime_type_unknown[] = "application/octet-stream";
71
72
73 enum
74 {
75   XDG_CHECKED_UNCHECKED,
76   XDG_CHECKED_VALID,
77   XDG_CHECKED_INVALID
78 };
79
80 struct XdgDirTimeList
81 {
82   time_t mtime;
83   char *directory_name;
84   int checked;
85   XdgDirTimeList *next;
86 };
87
88 struct XdgCallbackList
89 {
90   XdgCallbackList *next;
91   XdgCallbackList *prev;
92   int              callback_id;
93   XdgMimeCallback  callback;
94   void            *data;
95   XdgMimeDestroy   destroy;
96 };
97
98 /* Function called by xdg_run_command_on_dirs.  If it returns TRUE, further
99  * directories aren't looked at */
100 typedef int (*XdgDirectoryFunc) (const char *directory,
101                                  void       *user_data);
102
103 static void
104 xdg_dir_time_list_add (char   *file_name, 
105                        time_t  mtime)
106 {
107   XdgDirTimeList *list;
108
109   for (list = dir_time_list; list; list = list->next) 
110     {
111       if (strcmp (list->directory_name, file_name) == 0)
112         {
113           free (file_name);
114           return;
115         }
116     }
117   
118   list = calloc (1, sizeof (XdgDirTimeList));
119   list->checked = XDG_CHECKED_UNCHECKED;
120   list->directory_name = file_name;
121   list->mtime = mtime;
122   list->next = dir_time_list;
123   dir_time_list = list;
124 }
125  
126 static void
127 xdg_dir_time_list_free (XdgDirTimeList *list)
128 {
129   XdgDirTimeList *next;
130
131   while (list)
132     {
133       next = list->next;
134       free (list->directory_name);
135       free (list);
136       list = next;
137     }
138 }
139
140 static int
141 xdg_mime_init_from_directory (const char *directory)
142 {
143   char *file_name;
144   struct stat st;
145
146   assert (directory != NULL);
147
148   file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1);
149   if (file_name == NULL)
150       return TRUE;
151
152   strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache");
153   if (stat (file_name, &st) == 0)
154     {
155       XdgMimeCache *cache = _xdg_mime_cache_new_from_file (file_name);
156
157       if (cache != NULL)
158         {
159           xdg_dir_time_list_add (file_name, st.st_mtime);
160
161           XdgMimeCache **tmp_caches = realloc (_caches, sizeof (XdgMimeCache *) * (n_caches + 2));
162           if (tmp_caches != NULL)
163             {
164               _caches = tmp_caches;
165               _caches[n_caches] = cache;
166               _caches[n_caches + 1] = NULL;
167               n_caches++;
168
169               return FALSE;
170             }
171           else
172             {
173               free(cache);
174               return TRUE;
175             }
176         }
177     }
178   free (file_name);
179
180   file_name = malloc (strlen (directory) + strlen ("/mime/globs2") + 1);
181   if (file_name == NULL)
182     return TRUE;
183
184   strcpy (file_name, directory); strcat (file_name, "/mime/globs2");
185   if (stat (file_name, &st) == 0)
186     {
187       _xdg_mime_glob_read_from_file (global_hash, file_name, TRUE);
188       xdg_dir_time_list_add (file_name, st.st_mtime);
189     }
190   else
191     {
192       free (file_name);
193       file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
194       if (file_name == NULL)
195         return TRUE;
196
197       strcpy (file_name, directory); strcat (file_name, "/mime/globs");
198       if (stat (file_name, &st) == 0)
199         {
200           _xdg_mime_glob_read_from_file (global_hash, file_name, FALSE);
201           xdg_dir_time_list_add (file_name, st.st_mtime);
202         }
203       else
204         {
205           free (file_name);
206         }
207     }
208
209   file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
210   if (file_name == NULL)
211     return TRUE;
212
213   strcpy (file_name, directory); strcat (file_name, "/mime/magic");
214   if (stat (file_name, &st) == 0)
215     {
216       _xdg_mime_magic_read_from_file (global_magic, file_name);
217       xdg_dir_time_list_add (file_name, st.st_mtime);
218     }
219   else
220     {
221       free (file_name);
222     }
223
224   file_name = malloc (strlen (directory) + strlen ("/mime/aliases") + 1);
225   if (file_name == NULL)
226     return TRUE;
227
228   strcpy (file_name, directory); strcat (file_name, "/mime/aliases");
229   _xdg_mime_alias_read_from_file (alias_list, file_name);
230   free (file_name);
231
232   file_name = malloc (strlen (directory) + strlen ("/mime/subclasses") + 1);
233   if (file_name == NULL)
234     return TRUE;
235
236   strcpy (file_name, directory); strcat (file_name, "/mime/subclasses");
237   _xdg_mime_parent_read_from_file (parent_list, file_name);
238   free (file_name);
239
240   file_name = malloc (strlen (directory) + strlen ("/mime/icons") + 1);
241   if (file_name == NULL)
242     return TRUE;
243
244   strcpy (file_name, directory); strcat (file_name, "/mime/icons");
245   _xdg_mime_icon_read_from_file (icon_list, file_name);
246   free (file_name);
247
248   file_name = malloc (strlen (directory) + strlen ("/mime/generic-icons") + 1);
249   if (file_name == NULL)
250     return TRUE;
251
252   strcpy (file_name, directory); strcat (file_name, "/mime/generic-icons");
253   _xdg_mime_icon_read_from_file (generic_icon_list, file_name);
254   free (file_name);
255
256   return FALSE; /* Keep processing */
257 }
258
259 /* Runs a command on all the directories in the search path */
260 static void
261 xdg_run_command_on_dirs (XdgDirectoryFunc  func,
262                          void             *user_data)
263 {
264   const char *xdg_data_home;
265   const char *xdg_data_dirs;
266   const char *ptr;
267
268   xdg_data_home = getenv ("XDG_DATA_HOME");
269   if (xdg_data_home)
270     {
271       if ((func) (xdg_data_home, user_data))
272         return;
273     }
274   else
275     {
276       const char *home;
277
278       home = getenv ("HOME");
279       if (home != NULL)
280         {
281           char *guessed_xdg_home;
282           int stop_processing;
283
284           guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1);
285           strcpy (guessed_xdg_home, home);
286           strcat (guessed_xdg_home, "/.local/share/");
287           stop_processing = (func) (guessed_xdg_home, user_data);
288           free (guessed_xdg_home);
289
290           if (stop_processing)
291             return;
292         }
293     }
294
295   xdg_data_dirs = getenv ("XDG_DATA_DIRS");
296   if (xdg_data_dirs == NULL)
297     xdg_data_dirs = "/usr/local/share/:/usr/share/";
298
299   ptr = xdg_data_dirs;
300
301   while (*ptr != '\000')
302     {
303       const char *end_ptr;
304       char *dir;
305       int len;
306       int stop_processing;
307
308       end_ptr = ptr;
309       while (*end_ptr != ':' && *end_ptr != '\000')
310         end_ptr ++;
311
312       if (end_ptr == ptr)
313         {
314           ptr++;
315           continue;
316         }
317
318       if (*end_ptr == ':')
319         len = end_ptr - ptr;
320       else
321         len = end_ptr - ptr + 1;
322       dir = malloc (len + 1);
323       strncpy (dir, ptr, len);
324       dir[len] = '\0';
325       stop_processing = (func) (dir, user_data);
326       free (dir);
327
328       if (stop_processing)
329         return;
330
331       ptr = end_ptr;
332     }
333 }
334
335 /* Checks file_path to make sure it has the same mtime as last time it was
336  * checked.  If it has a different mtime, or if the file doesn't exist, it
337  * returns FALSE.
338  *
339  * FIXME: This doesn't protect against permission changes.
340  */
341 static int
342 xdg_check_file (const char *file_path,
343                 int        *exists)
344 {
345   struct stat st;
346
347   /* If the file exists */
348   if (stat (file_path, &st) == 0)
349     {
350       XdgDirTimeList *list;
351
352       if (exists)
353         *exists = TRUE;
354
355       for (list = dir_time_list; list; list = list->next)
356         {
357           if (! strcmp (list->directory_name, file_path))
358             {
359               if (st.st_mtime == list->mtime)
360                 list->checked = XDG_CHECKED_VALID;
361               else 
362                 list->checked = XDG_CHECKED_INVALID;
363
364               return (list->checked != XDG_CHECKED_VALID);
365             }
366         }
367       return TRUE;
368     }
369
370   if (exists)
371     *exists = FALSE;
372
373   return FALSE;
374 }
375
376 static int
377 xdg_check_dir (const char *directory,
378                int        *invalid_dir_list)
379 {
380   int invalid, exists;
381   char *file_name;
382
383   assert (directory != NULL);
384
385   /* Check the mime.cache file */
386   file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1);
387   strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache");
388   invalid = xdg_check_file (file_name, &exists);
389   free (file_name);
390   if (invalid)
391     {
392       *invalid_dir_list = TRUE;
393       return TRUE;
394     }
395   else if (exists)
396     {
397       return FALSE;
398     }
399
400   /* Check the globs file */
401   file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
402   strcpy (file_name, directory); strcat (file_name, "/mime/globs");
403   invalid = xdg_check_file (file_name, NULL);
404   free (file_name);
405   if (invalid)
406     {
407       *invalid_dir_list = TRUE;
408       return TRUE;
409     }
410
411   /* Check the magic file */
412   file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
413   strcpy (file_name, directory); strcat (file_name, "/mime/magic");
414   invalid = xdg_check_file (file_name, NULL);
415   free (file_name);
416   if (invalid)
417     {
418       *invalid_dir_list = TRUE;
419       return TRUE;
420     }
421
422   return FALSE; /* Keep processing */
423 }
424
425 /* Walks through all the mime files stat()ing them to see if they've changed.
426  * Returns TRUE if they have. */
427 static int
428 xdg_check_dirs (void)
429 {
430   XdgDirTimeList *list;
431   int invalid_dir_list = FALSE;
432
433   for (list = dir_time_list; list; list = list->next)
434     list->checked = XDG_CHECKED_UNCHECKED;
435
436   xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir,
437                            &invalid_dir_list);
438
439   if (invalid_dir_list)
440     return TRUE;
441
442   for (list = dir_time_list; list; list = list->next)
443     {
444       if (list->checked != XDG_CHECKED_VALID)
445         return TRUE;
446     }
447
448   return FALSE;
449 }
450
451 /* We want to avoid stat()ing on every single mime call, so we only look for
452  * newer files every 5 seconds.  This will return TRUE if we need to reread the
453  * mime data from disk.
454  */
455 static int
456 xdg_check_time_and_dirs (void)
457 {
458   struct timeval tv;
459   time_t current_time;
460   int retval = FALSE;
461
462   gettimeofday (&tv, NULL);
463   current_time = tv.tv_sec;
464
465   if (current_time >= last_stat_time + 5)
466     {
467       retval = xdg_check_dirs ();
468       last_stat_time = current_time;
469     }
470
471   return retval;
472 }
473
474 /* Called in every public function.  It reloads the hash function if need be.
475  */
476 static void
477 xdg_mime_init (void)
478 {
479   if (xdg_check_time_and_dirs ())
480     {
481       xdg_mime_shutdown ();
482     }
483
484   if (need_reread)
485     {
486       global_hash = _xdg_glob_hash_new ();
487       global_magic = _xdg_mime_magic_new ();
488       alias_list = _xdg_mime_alias_list_new ();
489       parent_list = _xdg_mime_parent_list_new ();
490       icon_list = _xdg_mime_icon_list_new ();
491       generic_icon_list = _xdg_mime_icon_list_new ();
492
493       xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory,
494                                NULL);
495
496       need_reread = FALSE;
497     }
498 }
499
500 API const char *
501 xdg_mime_get_mime_type_for_data (const void *data,
502                                  size_t      len,
503                                  int        *result_prio)
504 {
505   const char *mime_type;
506
507   xdg_mime_init ();
508
509   if (_caches)
510     return _xdg_mime_cache_get_mime_type_for_data (data, len, result_prio);
511
512   mime_type = _xdg_mime_magic_lookup_data (global_magic, data, len, result_prio, NULL, 0);
513
514   if (mime_type)
515     return mime_type;
516
517   return XDG_MIME_TYPE_UNKNOWN;
518 }
519
520 API const char *
521 xdg_mime_get_mime_type_for_file (const char  *file_name,
522                                  struct stat *statbuf)
523 {
524   const char *mime_type;
525   /* currently, only a few globs occur twice, and none
526    * more often, so 5 seems plenty.
527    */
528   const char *mime_types[5];
529   FILE *file;
530   unsigned char *data;
531   int max_extent;
532   int bytes_read;
533   struct stat buf;
534   const char *base_name;
535   int n;
536   int fd;
537
538   if (file_name == NULL)
539     return NULL;
540   if (! _xdg_utf8_validate (file_name))
541     return NULL;
542
543   xdg_mime_init ();
544
545   if (_caches)
546     return _xdg_mime_cache_get_mime_type_for_file (file_name, statbuf);
547
548   base_name = _xdg_get_base_name (file_name);
549   n = _xdg_glob_hash_lookup_file_name (global_hash, base_name, mime_types, 5);
550
551   if (n == 1)
552     return mime_types[0];
553
554   file = fopen (file_name, "r");
555   if (file == NULL)
556     return XDG_MIME_TYPE_UNKNOWN;
557
558   if (!statbuf)
559     {
560       fd = fileno(file);
561       if (fstat (fd, &buf) != 0)
562         {
563           fclose(file);
564           return XDG_MIME_TYPE_UNKNOWN;
565         }
566
567       statbuf = &buf;
568     }
569
570   if (!S_ISREG (statbuf->st_mode))
571     {
572       fclose(file);
573       return XDG_MIME_TYPE_UNKNOWN;
574     }
575
576   /* FIXME: Need to make sure that max_extent isn't totally broken.  This could
577    * be large and need getting from a stream instead of just reading it all
578    * in. */
579   max_extent = _xdg_mime_magic_get_buffer_extents (global_magic);
580   data = malloc (max_extent);
581   if (data == NULL)
582     {
583       fclose(file);
584       return XDG_MIME_TYPE_UNKNOWN;
585     }
586
587   bytes_read = fread (data, 1, max_extent, file);
588   if (ferror (file))
589     {
590       free (data);
591       fclose (file);
592       return XDG_MIME_TYPE_UNKNOWN;
593     }
594
595   mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read, NULL,
596                                            mime_types, n);
597
598   free (data);
599   fclose (file);
600
601   if (mime_type)
602     return mime_type;
603
604   return XDG_MIME_TYPE_UNKNOWN;
605 }
606
607 API const char *
608 xdg_mime_get_mime_type_from_file_name (const char *file_name)
609 {
610   const char *mime_type;
611
612   xdg_mime_init ();
613
614   if (_caches)
615     return _xdg_mime_cache_get_mime_type_from_file_name (file_name);
616
617   if (_xdg_glob_hash_lookup_file_name (global_hash, file_name, &mime_type, 1))
618     return mime_type;
619   else
620     return XDG_MIME_TYPE_UNKNOWN;
621 }
622
623 int
624 xdg_mime_get_mime_types_from_file_name (const char *file_name,
625                                         const char  *mime_types[],
626                                         int          n_mime_types)
627 {
628   xdg_mime_init ();
629   
630   if (_caches)
631     return _xdg_mime_cache_get_mime_types_from_file_name (file_name, mime_types, n_mime_types);
632   
633   return _xdg_glob_hash_lookup_file_name (global_hash, file_name, mime_types, n_mime_types);
634 }
635
636 int
637 xdg_mime_is_valid_mime_type (const char *mime_type)
638 {
639   /* FIXME: We should make this a better test
640    */
641   return _xdg_utf8_validate (mime_type);
642 }
643
644 void
645 xdg_mime_shutdown (void)
646 {
647   XdgCallbackList *list;
648
649   /* FIXME: Need to make this (and the whole library) thread safe */
650   if (dir_time_list)
651     {
652       xdg_dir_time_list_free (dir_time_list);
653       dir_time_list = NULL;
654     }
655         
656   if (global_hash)
657     {
658       _xdg_glob_hash_free (global_hash);
659       global_hash = NULL;
660     }
661   if (global_magic)
662     {
663       _xdg_mime_magic_free (global_magic);
664       global_magic = NULL;
665     }
666
667   if (alias_list)
668     {
669       _xdg_mime_alias_list_free (alias_list);
670       alias_list = NULL;
671     }
672
673   if (parent_list)
674     {
675       _xdg_mime_parent_list_free (parent_list);
676       parent_list = NULL;
677     }
678
679   if (icon_list)
680     {
681       _xdg_mime_icon_list_free (icon_list);
682       icon_list = NULL;
683     }
684
685   if (generic_icon_list)
686     {
687       _xdg_mime_icon_list_free (generic_icon_list);
688       generic_icon_list = NULL;
689     }
690   
691   if (_caches)
692     {
693       int i;
694
695       for (i = 0; i < n_caches; i++)
696         _xdg_mime_cache_unref (_caches[i]);
697       free (_caches);
698       _caches = NULL;
699       n_caches = 0;
700     }
701
702   for (list = callback_list; list; list = list->next)
703     (list->callback) (list->data);
704
705   need_reread = TRUE;
706 }
707
708 int
709 xdg_mime_get_max_buffer_extents (void)
710 {
711   xdg_mime_init ();
712   
713   if (_caches)
714     return _xdg_mime_cache_get_max_buffer_extents ();
715
716   return _xdg_mime_magic_get_buffer_extents (global_magic);
717 }
718
719 const char *
720 _xdg_mime_unalias_mime_type (const char *mime_type)
721 {
722   const char *lookup;
723
724   if (_caches)
725     return _xdg_mime_cache_unalias_mime_type (mime_type);
726
727   if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL)
728     return lookup;
729
730   return mime_type;
731 }
732
733 API const char *
734 xdg_mime_unalias_mime_type (const char *mime_type)
735 {
736   xdg_mime_init ();
737
738   return _xdg_mime_unalias_mime_type (mime_type);
739 }
740
741 int
742 _xdg_mime_mime_type_equal (const char *mime_a,
743                            const char *mime_b)
744 {
745   const char *unalias_a, *unalias_b;
746
747   unalias_a = _xdg_mime_unalias_mime_type (mime_a);
748   unalias_b = _xdg_mime_unalias_mime_type (mime_b);
749
750   if (strcmp (unalias_a, unalias_b) == 0)
751     return 1;
752
753   return 0;
754 }
755
756 int
757 xdg_mime_mime_type_equal (const char *mime_a,
758                           const char *mime_b)
759 {
760   xdg_mime_init ();
761
762   return _xdg_mime_mime_type_equal (mime_a, mime_b);
763 }
764
765 int
766 xdg_mime_media_type_equal (const char *mime_a,
767                            const char *mime_b)
768 {
769   char *sep;
770
771   sep = strchr (mime_a, '/');
772   
773   if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0)
774     return 1;
775
776   return 0;
777 }
778
779 #if 1
780 static int
781 xdg_mime_is_super_type (const char *mime)
782 {
783   int length;
784   const char *type;
785
786   length = strlen (mime);
787   type = &(mime[length - 2]);
788
789   if (strcmp (type, "/*") == 0)
790     return 1;
791
792   return 0;
793 }
794 #endif
795
796 int
797 _xdg_mime_mime_type_subclass (const char *mime,
798                               const char *base)
799 {
800   const char *umime, *ubase;
801   const char **parents;
802
803   if (_caches)
804     return _xdg_mime_cache_mime_type_subclass (mime, base);
805
806   umime = _xdg_mime_unalias_mime_type (mime);
807   ubase = _xdg_mime_unalias_mime_type (base);
808
809   if (strcmp (umime, ubase) == 0)
810     return 1;
811
812 #if 1  
813   /* Handle supertypes */
814   if (xdg_mime_is_super_type (ubase) &&
815       xdg_mime_media_type_equal (umime, ubase))
816     return 1;
817 #endif
818
819   /*  Handle special cases text/plain and application/octet-stream */
820   if (strcmp (ubase, "text/plain") == 0 && 
821       strncmp (umime, "text/", 5) == 0)
822     return 1;
823
824   if (strcmp (ubase, "application/octet-stream") == 0)
825     return 1;
826   
827   parents = _xdg_mime_parent_list_lookup (parent_list, umime);
828   for (; parents && *parents; parents++)
829     {
830       if (_xdg_mime_mime_type_subclass (*parents, ubase))
831         return 1;
832     }
833
834   return 0;
835 }
836
837 int
838 xdg_mime_mime_type_subclass (const char *mime,
839                              const char *base)
840 {
841   xdg_mime_init ();
842
843   return _xdg_mime_mime_type_subclass (mime, base);
844 }
845
846 char **
847 xdg_mime_list_mime_parents (const char *mime)
848 {
849   const char **parents;
850   char **result;
851   int i, n;
852
853   if (_caches)
854     return _xdg_mime_cache_list_mime_parents (mime);
855
856   parents = xdg_mime_get_mime_parents (mime);
857
858   if (!parents)
859     return NULL;
860
861   for (i = 0; parents[i]; i++) ;
862   
863   n = (i + 1) * sizeof (char *);
864   result = (char **) malloc (n);
865   memcpy (result, parents, n);
866
867   return result;
868 }
869
870 const char **
871 xdg_mime_get_mime_parents (const char *mime)
872 {
873   const char *umime;
874
875   xdg_mime_init ();
876
877   umime = _xdg_mime_unalias_mime_type (mime);
878
879   return _xdg_mime_parent_list_lookup (parent_list, umime);
880 }
881
882 void 
883 xdg_mime_dump (void)
884 {
885   xdg_mime_init();
886
887   printf ("*** ALIASES ***\n\n");
888   _xdg_mime_alias_list_dump (alias_list);
889   printf ("\n*** PARENTS ***\n\n");
890   _xdg_mime_parent_list_dump (parent_list);
891   printf ("\n*** CACHE ***\n\n");
892   _xdg_glob_hash_dump (global_hash);
893   printf ("\n*** GLOBS ***\n\n");
894   _xdg_glob_hash_dump (global_hash);
895   printf ("\n*** GLOBS REVERSE TREE ***\n\n");
896   _xdg_mime_cache_glob_dump ();
897 }
898
899
900 /* Registers a function to be called every time the mime database reloads its files
901  */
902 int
903 xdg_mime_register_reload_callback (XdgMimeCallback  callback,
904                                    void            *data,
905                                    XdgMimeDestroy   destroy)
906 {
907   XdgCallbackList *list_el;
908   static int callback_id = 1;
909
910   /* Make a new list element */
911   list_el = calloc (1, sizeof (XdgCallbackList));
912   list_el->callback_id = callback_id;
913   list_el->callback = callback;
914   list_el->data = data;
915   list_el->destroy = destroy;
916   list_el->next = callback_list;
917   if (list_el->next)
918     list_el->next->prev = list_el;
919
920   callback_list = list_el;
921   callback_id ++;
922
923   return callback_id - 1;
924 }
925
926 void
927 xdg_mime_remove_callback (int callback_id)
928 {
929   XdgCallbackList *list;
930
931   for (list = callback_list; list; list = list->next)
932     {
933       if (list->callback_id == callback_id)
934         {
935           if (list->next)
936             list->next = list->prev;
937
938           if (list->prev)
939             list->prev->next = list->next;
940           else
941             callback_list = list->next;
942
943           /* invoke the destroy handler */
944           (list->destroy) (list->data);
945           free (list);
946           return;
947         }
948     }
949 }
950
951 API const char *
952 xdg_mime_get_icon (const char *mime)
953 {
954   xdg_mime_init ();
955   
956   if (_caches)
957     return _xdg_mime_cache_get_icon (mime);
958
959   return _xdg_mime_icon_list_lookup (icon_list, mime);
960 }
961
962 API const char *
963 xdg_mime_get_generic_icon (const char *mime)
964 {
965   xdg_mime_init ();
966   
967   if (_caches)
968     return _xdg_mime_cache_get_generic_icon (mime);
969
970   return _xdg_mime_icon_list_lookup (generic_icon_list, mime);
971 }