* add xdgmime from freedesktop.org
[citadel.git] / libcitadel / lib / xdgmime / 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 "xdgmimeparent.h"
38 #include "xdgmimecache.h"
39 #include <stdio.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <sys/time.h>
44 #include <unistd.h>
45 #include <assert.h>
46
47 typedef struct XdgDirTimeList XdgDirTimeList;
48 typedef struct XdgCallbackList XdgCallbackList;
49
50 static int need_reread = TRUE;
51 static time_t last_stat_time = 0;
52
53 static XdgGlobHash *global_hash = NULL;
54 static XdgMimeMagic *global_magic = NULL;
55 static XdgAliasList *alias_list = NULL;
56 static XdgParentList *parent_list = NULL;
57 static XdgDirTimeList *dir_time_list = NULL;
58 static XdgCallbackList *callback_list = NULL;
59
60 XdgMimeCache **_xdg_mime_caches = NULL;
61 static int n_caches = 0;
62
63 const char xdg_mime_type_unknown[] = "application/octet-stream";
64
65
66 enum
67 {
68   XDG_CHECKED_UNCHECKED,
69   XDG_CHECKED_VALID,
70   XDG_CHECKED_INVALID
71 };
72
73 struct XdgDirTimeList
74 {
75   time_t mtime;
76   char *directory_name;
77   int checked;
78   XdgDirTimeList *next;
79   XdgMimeCache *cache;
80 };
81
82 struct XdgCallbackList
83 {
84   XdgCallbackList *next;
85   XdgCallbackList *prev;
86   int              callback_id;
87   XdgMimeCallback  callback;
88   void            *data;
89   XdgMimeDestroy   destroy;
90 };
91
92 /* Function called by xdg_run_command_on_dirs.  If it returns TRUE, further
93  * directories aren't looked at */
94 typedef int (*XdgDirectoryFunc) (const char *directory,
95                                  void       *user_data);
96
97 static XdgDirTimeList *
98 xdg_dir_time_list_new (void)
99 {
100   XdgDirTimeList *retval;
101
102   retval = calloc (1, sizeof (XdgDirTimeList));
103   retval->checked = XDG_CHECKED_UNCHECKED;
104
105   return retval;
106 }
107
108 static void
109 xdg_dir_time_list_free (XdgDirTimeList *list)
110 {
111   XdgDirTimeList *next;
112
113   while (list)
114     {
115       next = list->next;
116       free (list->directory_name);
117       free (list);
118       list = next;
119     }
120 }
121
122 static int
123 xdg_mime_init_from_directory (const char *directory)
124 {
125   char *file_name;
126   struct stat st;
127   XdgDirTimeList *list;
128
129   assert (directory != NULL);
130
131   file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1);
132   strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache");
133   if (stat (file_name, &st) == 0)
134     {
135       XdgMimeCache *cache = _xdg_mime_cache_new_from_file (file_name);
136
137       if (cache != NULL)
138         {
139           list = xdg_dir_time_list_new ();
140           list->directory_name = file_name;
141           list->mtime = st.st_mtime;
142           list->next = dir_time_list;
143           list->cache = cache;
144           dir_time_list = list;
145
146           _xdg_mime_caches = realloc (_xdg_mime_caches, sizeof (XdgMimeCache *) * (n_caches + 2));
147           _xdg_mime_caches[n_caches] = cache;
148           _xdg_mime_caches[n_caches + 1] = NULL;
149           n_caches++;
150
151           return FALSE;
152         }
153     }
154   free (file_name);
155
156   file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
157   strcpy (file_name, directory); strcat (file_name, "/mime/globs");
158   if (stat (file_name, &st) == 0)
159     {
160       _xdg_mime_glob_read_from_file (global_hash, file_name);
161
162       list = xdg_dir_time_list_new ();
163       list->directory_name = file_name;
164       list->mtime = st.st_mtime;
165       list->next = dir_time_list;
166       dir_time_list = list;
167     }
168   else
169     {
170       free (file_name);
171     }
172
173   file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
174   strcpy (file_name, directory); strcat (file_name, "/mime/magic");
175   if (stat (file_name, &st) == 0)
176     {
177       _xdg_mime_magic_read_from_file (global_magic, file_name);
178
179       list = xdg_dir_time_list_new ();
180       list->directory_name = file_name;
181       list->mtime = st.st_mtime;
182       list->next = dir_time_list;
183       dir_time_list = list;
184     }
185   else
186     {
187       free (file_name);
188     }
189
190   file_name = malloc (strlen (directory) + strlen ("/mime/aliases") + 1);
191   strcpy (file_name, directory); strcat (file_name, "/mime/aliases");
192   _xdg_mime_alias_read_from_file (alias_list, file_name);
193   free (file_name);
194
195   file_name = malloc (strlen (directory) + strlen ("/mime/subclasses") + 1);
196   strcpy (file_name, directory); strcat (file_name, "/mime/subclasses");
197   _xdg_mime_parent_read_from_file (parent_list, file_name);
198   free (file_name);
199
200   return FALSE; /* Keep processing */
201 }
202
203 /* Runs a command on all the directories in the search path */
204 static void
205 xdg_run_command_on_dirs (XdgDirectoryFunc  func,
206                          void             *user_data)
207 {
208   const char *xdg_data_home;
209   const char *xdg_data_dirs;
210   const char *ptr;
211
212   xdg_data_home = getenv ("XDG_DATA_HOME");
213   if (xdg_data_home)
214     {
215       if ((func) (xdg_data_home, user_data))
216         return;
217     }
218   else
219     {
220       const char *home;
221
222       home = getenv ("HOME");
223       if (home != NULL)
224         {
225           char *guessed_xdg_home;
226           int stop_processing;
227
228           guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1);
229           strcpy (guessed_xdg_home, home);
230           strcat (guessed_xdg_home, "/.local/share/");
231           stop_processing = (func) (guessed_xdg_home, user_data);
232           free (guessed_xdg_home);
233
234           if (stop_processing)
235             return;
236         }
237     }
238
239   xdg_data_dirs = getenv ("XDG_DATA_DIRS");
240   if (xdg_data_dirs == NULL)
241     xdg_data_dirs = "/usr/local/share/:/usr/share/";
242
243   ptr = xdg_data_dirs;
244
245   while (*ptr != '\000')
246     {
247       const char *end_ptr;
248       char *dir;
249       int len;
250       int stop_processing;
251
252       end_ptr = ptr;
253       while (*end_ptr != ':' && *end_ptr != '\000')
254         end_ptr ++;
255
256       if (end_ptr == ptr)
257         {
258           ptr++;
259           continue;
260         }
261
262       if (*end_ptr == ':')
263         len = end_ptr - ptr;
264       else
265         len = end_ptr - ptr + 1;
266       dir = malloc (len + 1);
267       strncpy (dir, ptr, len);
268       dir[len] = '\0';
269       stop_processing = (func) (dir, user_data);
270       free (dir);
271
272       if (stop_processing)
273         return;
274
275       ptr = end_ptr;
276     }
277 }
278
279 static XdgMimeCache *
280 xdg_lookup_cache_for_file (const char *file_path)
281 {
282   XdgDirTimeList *list;
283
284   for (list = dir_time_list; list; list = list->next)
285       if (! strcmp (list->directory_name, file_path))
286          return list->cache;
287
288   return NULL;
289 }
290
291 /* Checks file_path to make sure it has the same mtime as last time it was
292  * checked.  If it has a different mtime, or if the file doesn't exist, it
293  * returns FALSE.
294  *
295  * FIXME: This doesn't protect against permission changes.
296  */
297 static int
298 xdg_check_file (const char *file_path)
299 {
300   struct stat st;
301
302   /* If the file exists */
303   if (stat (file_path, &st) == 0)
304     {
305       XdgDirTimeList *list;
306
307       for (list = dir_time_list; list; list = list->next)
308         {
309           if (! strcmp (list->directory_name, file_path) &&
310               st.st_mtime == list->mtime)
311             {
312               if (list->checked == XDG_CHECKED_UNCHECKED)
313                 list->checked = XDG_CHECKED_VALID;
314               else if (list->checked == XDG_CHECKED_VALID)
315                 list->checked = XDG_CHECKED_INVALID;
316
317               return (list->checked != XDG_CHECKED_VALID);
318             }
319         }
320       return TRUE;
321     }
322
323   return FALSE;
324 }
325
326 static int
327 xdg_check_dir (const char *directory,
328                int        *invalid_dir_list)
329 {
330   int invalid, has_cache;
331   char *file_name;
332
333   assert (directory != NULL);
334
335   /* Check the mime.cache file */
336   file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1);
337   strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache");
338   invalid = xdg_check_file (file_name);
339   has_cache = xdg_lookup_cache_for_file (file_name) != NULL;
340   free (file_name);
341
342   if (has_cache)
343     {
344       if (invalid)
345         {
346           *invalid_dir_list = TRUE;
347           return TRUE;
348         }
349
350       return FALSE;
351     }
352
353   /* Check the globs file */
354   file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
355   strcpy (file_name, directory); strcat (file_name, "/mime/globs");
356   invalid = xdg_check_file (file_name);
357   free (file_name);
358   if (invalid)
359     {
360       *invalid_dir_list = TRUE;
361       return TRUE;
362     }
363
364   /* Check the magic file */
365   file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
366   strcpy (file_name, directory); strcat (file_name, "/mime/magic");
367   invalid = xdg_check_file (file_name);
368   free (file_name);
369   if (invalid)
370     {
371       *invalid_dir_list = TRUE;
372       return TRUE;
373     }
374
375   return FALSE; /* Keep processing */
376 }
377
378 /* Walks through all the mime files stat()ing them to see if they've changed.
379  * Returns TRUE if they have. */
380 static int
381 xdg_check_dirs (void)
382 {
383   XdgDirTimeList *list;
384   int invalid_dir_list = FALSE;
385
386   for (list = dir_time_list; list; list = list->next)
387     list->checked = XDG_CHECKED_UNCHECKED;
388
389   xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir,
390                            &invalid_dir_list);
391
392   if (invalid_dir_list)
393     return TRUE;
394
395   for (list = dir_time_list; list; list = list->next)
396     {
397       if (list->checked != XDG_CHECKED_VALID)
398         return TRUE;
399     }
400
401   return FALSE;
402 }
403
404 /* We want to avoid stat()ing on every single mime call, so we only look for
405  * newer files every 5 seconds.  This will return TRUE if we need to reread the
406  * mime data from disk.
407  */
408 static int
409 xdg_check_time_and_dirs (void)
410 {
411   struct timeval tv;
412   time_t current_time;
413   int retval = FALSE;
414
415   gettimeofday (&tv, NULL);
416   current_time = tv.tv_sec;
417
418   if (current_time >= last_stat_time + 5)
419     {
420       retval = xdg_check_dirs ();
421       last_stat_time = current_time;
422     }
423
424   return retval;
425 }
426
427 /* Called in every public function.  It reloads the hash function if need be.
428  */
429 static void
430 xdg_mime_init (void)
431 {
432   if (xdg_check_time_and_dirs ())
433     {
434       xdg_mime_shutdown ();
435     }
436
437   if (need_reread)
438     {
439       global_hash = _xdg_glob_hash_new ();
440       global_magic = _xdg_mime_magic_new ();
441       alias_list = _xdg_mime_alias_list_new ();
442       parent_list = _xdg_mime_parent_list_new ();
443
444       xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory,
445                                NULL);
446
447       need_reread = FALSE;
448     }
449 }
450
451 const char *
452 xdg_mime_get_mime_type_for_data (const void *data,
453                                  size_t      len)
454 {
455   const char *mime_type;
456
457   xdg_mime_init ();
458
459   if (_xdg_mime_caches)
460     return _xdg_mime_cache_get_mime_type_for_data (data, len);
461
462   mime_type = _xdg_mime_magic_lookup_data (global_magic, data, len, NULL, 0);
463
464   if (mime_type)
465     return mime_type;
466
467   return XDG_MIME_TYPE_UNKNOWN;
468 }
469
470 const char *
471 xdg_mime_get_mime_type_for_file (const char  *file_name,
472                                  struct stat *statbuf)
473 {
474   const char *mime_type;
475   /* Used to detect whether multiple MIME types match file_name */
476   const char *mime_types[2];
477   FILE *file;
478   unsigned char *data;
479   int max_extent;
480   int bytes_read;
481   struct stat buf;
482   const char *base_name;
483   int n;
484
485   if (file_name == NULL)
486     return NULL;
487   if (! _xdg_utf8_validate (file_name))
488     return NULL;
489
490   xdg_mime_init ();
491
492   if (_xdg_mime_caches)
493     return _xdg_mime_cache_get_mime_type_for_file (file_name, statbuf);
494
495   base_name = _xdg_get_base_name (file_name);
496   n = _xdg_glob_hash_lookup_file_name (global_hash, base_name, mime_types, 2);
497
498   if (n == 1)
499     return mime_types[0];
500
501   if (!statbuf)
502     {
503       if (stat (file_name, &buf) != 0)
504         return XDG_MIME_TYPE_UNKNOWN;
505
506       statbuf = &buf;
507     }
508
509   if (!S_ISREG (statbuf->st_mode))
510     return XDG_MIME_TYPE_UNKNOWN;
511
512   /* FIXME: Need to make sure that max_extent isn't totally broken.  This could
513    * be large and need getting from a stream instead of just reading it all
514    * in. */
515   max_extent = _xdg_mime_magic_get_buffer_extents (global_magic);
516   data = malloc (max_extent);
517   if (data == NULL)
518     return XDG_MIME_TYPE_UNKNOWN;
519         
520   file = fopen (file_name, "r");
521   if (file == NULL)
522     {
523       free (data);
524       return XDG_MIME_TYPE_UNKNOWN;
525     }
526
527   bytes_read = fread (data, 1, max_extent, file);
528   if (ferror (file))
529     {
530       free (data);
531       fclose (file);
532       return XDG_MIME_TYPE_UNKNOWN;
533     }
534
535   mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read,
536                                            mime_types, n);
537
538   free (data);
539   fclose (file);
540
541   if (mime_type)
542     return mime_type;
543
544   return XDG_MIME_TYPE_UNKNOWN;
545 }
546
547 const char *
548 xdg_mime_get_mime_type_from_file_name (const char *file_name)
549 {
550   const char *mime_types[2];
551
552   xdg_mime_init ();
553
554   if (_xdg_mime_caches)
555     return _xdg_mime_cache_get_mime_type_from_file_name (file_name);
556
557   if (_xdg_glob_hash_lookup_file_name (global_hash, file_name, mime_types, 2) == 1)
558     return mime_types[0];
559   else
560     return XDG_MIME_TYPE_UNKNOWN;
561 }
562
563 int
564 xdg_mime_is_valid_mime_type (const char *mime_type)
565 {
566   /* FIXME: We should make this a better test
567    */
568   return _xdg_utf8_validate (mime_type);
569 }
570
571 void
572 xdg_mime_shutdown (void)
573 {
574   XdgCallbackList *list;
575
576   /* FIXME: Need to make this (and the whole library) thread safe */
577   if (dir_time_list)
578     {
579       xdg_dir_time_list_free (dir_time_list);
580       dir_time_list = NULL;
581     }
582         
583   if (global_hash)
584     {
585       _xdg_glob_hash_free (global_hash);
586       global_hash = NULL;
587     }
588   if (global_magic)
589     {
590       _xdg_mime_magic_free (global_magic);
591       global_magic = NULL;
592     }
593
594   if (alias_list)
595     {
596       _xdg_mime_alias_list_free (alias_list);
597       alias_list = NULL;
598     }
599
600   if (parent_list)
601     {
602       _xdg_mime_parent_list_free (parent_list);
603       parent_list = NULL;
604     }
605
606   if (_xdg_mime_caches)
607     {
608       int i;
609       for (i = 0; i < n_caches; i++)
610         _xdg_mime_cache_unref (_xdg_mime_caches[i]);
611       free (_xdg_mime_caches);
612       _xdg_mime_caches = NULL;
613       n_caches = 0;
614     }
615
616   for (list = callback_list; list; list = list->next)
617     (list->callback) (list->data);
618
619   need_reread = TRUE;
620 }
621
622 int
623 xdg_mime_get_max_buffer_extents (void)
624 {
625   xdg_mime_init ();
626   
627   if (_xdg_mime_caches)
628     return _xdg_mime_cache_get_max_buffer_extents ();
629
630   return _xdg_mime_magic_get_buffer_extents (global_magic);
631 }
632
633 static const char *
634 _xdg_mime_unalias_mime_type (const char *mime_type)
635 {
636   const char *lookup;
637
638   if (_xdg_mime_caches)
639     return _xdg_mime_cache_unalias_mime_type (mime_type);
640
641   if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL)
642     return lookup;
643
644   return mime_type;
645 }
646
647 const char *
648 xdg_mime_unalias_mime_type (const char *mime_type)
649 {
650   xdg_mime_init ();
651
652   return _xdg_mime_unalias_mime_type (mime_type);
653 }
654
655 int
656 _xdg_mime_mime_type_equal (const char *mime_a,
657                            const char *mime_b)
658 {
659   const char *unalias_a, *unalias_b;
660
661   unalias_a = _xdg_mime_unalias_mime_type (mime_a);
662   unalias_b = _xdg_mime_unalias_mime_type (mime_b);
663
664   if (strcmp (unalias_a, unalias_b) == 0)
665     return 1;
666
667   return 0;
668 }
669
670 int
671 xdg_mime_mime_type_equal (const char *mime_a,
672                           const char *mime_b)
673 {
674   xdg_mime_init ();
675
676   return _xdg_mime_mime_type_equal (mime_a, mime_b);
677 }
678
679 int
680 _xdg_mime_media_type_equal (const char *mime_a,
681                             const char *mime_b)
682 {
683   char *sep;
684
685   xdg_mime_init ();
686
687   sep = strchr (mime_a, '/');
688   
689   if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0)
690     return 1;
691
692   return 0;
693 }
694
695 int
696 xdg_mime_media_type_equal (const char *mime_a,
697                            const char *mime_b)
698 {
699   xdg_mime_init ();
700
701   return _xdg_mime_media_type_equal (mime_a, mime_b);
702 }
703
704 #if 0
705 static int
706 xdg_mime_is_super_type (const char *mime)
707 {
708   int length;
709   const char *type;
710
711   length = strlen (mime);
712   type = &(mime[length - 2]);
713
714   if (strcmp (type, "/*") == 0)
715     return 1;
716
717   return 0;
718 }
719 #endif
720
721 int
722 _xdg_mime_mime_type_subclass (const char *mime,
723                               const char *base)
724 {
725   const char *umime, *ubase;
726   const char **parents;
727
728   if (_xdg_mime_caches)
729     return _xdg_mime_cache_mime_type_subclass (mime, base);
730
731   umime = _xdg_mime_unalias_mime_type (mime);
732   ubase = _xdg_mime_unalias_mime_type (base);
733
734   if (strcmp (umime, ubase) == 0)
735     return 1;
736
737 #if 0  
738   /* Handle supertypes */
739   if (xdg_mime_is_super_type (ubase) &&
740       _xdg_mime_media_type_equal (umime, ubase))
741     return 1;
742 #endif
743
744   /*  Handle special cases text/plain and application/octet-stream */
745   if (strcmp (ubase, "text/plain") == 0 && 
746       strncmp (umime, "text/", 5) == 0)
747     return 1;
748
749   if (strcmp (ubase, "application/octet-stream") == 0)
750     return 1;
751   
752   parents = _xdg_mime_parent_list_lookup (parent_list, umime);
753   for (; parents && *parents; parents++)
754     {
755       if (_xdg_mime_mime_type_subclass (*parents, ubase))
756         return 1;
757     }
758
759   return 0;
760 }
761
762 int
763 xdg_mime_mime_type_subclass (const char *mime,
764                              const char *base)
765 {
766   xdg_mime_init ();
767
768   return _xdg_mime_mime_type_subclass (mime, base);
769 }
770
771 char **
772 xdg_mime_list_mime_parents (const char *mime)
773 {
774   const char **parents;
775   char **result;
776   int i, n;
777
778   if (_xdg_mime_caches)
779     return _xdg_mime_cache_list_mime_parents (mime);
780
781   parents = xdg_mime_get_mime_parents (mime);
782
783   if (!parents)
784     return NULL;
785
786   for (i = 0; parents[i]; i++) ;
787   
788   n = (i + 1) * sizeof (char *);
789   result = (char **) malloc (n);
790   memcpy (result, parents, n);
791
792   return result;
793 }
794
795 const char **
796 xdg_mime_get_mime_parents (const char *mime)
797 {
798   const char *umime;
799
800   xdg_mime_init ();
801
802   umime = _xdg_mime_unalias_mime_type (mime);
803
804   return _xdg_mime_parent_list_lookup (parent_list, umime);
805 }
806
807 void 
808 xdg_mime_dump (void)
809 {
810   printf ("*** ALIASES ***\n\n");
811   _xdg_mime_alias_list_dump (alias_list);
812   printf ("\n*** PARENTS ***\n\n");
813   _xdg_mime_parent_list_dump (parent_list);
814   printf ("\n*** CACHE ***\n\n");
815   _xdg_glob_hash_dump (global_hash);
816 }
817
818
819 /* Registers a function to be called every time the mime database reloads its files
820  */
821 int
822 xdg_mime_register_reload_callback (XdgMimeCallback  callback,
823                                    void            *data,
824                                    XdgMimeDestroy   destroy)
825 {
826   XdgCallbackList *list_el;
827   static int callback_id = 1;
828
829   /* Make a new list element */
830   list_el = calloc (1, sizeof (XdgCallbackList));
831   list_el->callback_id = callback_id;
832   list_el->callback = callback;
833   list_el->data = data;
834   list_el->destroy = destroy;
835   list_el->next = callback_list;
836   if (list_el->next)
837     list_el->next->prev = list_el;
838
839   callback_list = list_el;
840   callback_id ++;
841
842   return callback_id - 1;
843 }
844
845 void
846 xdg_mime_remove_callback (int callback_id)
847 {
848   XdgCallbackList *list;
849
850   for (list = callback_list; list; list = list->next)
851     {
852       if (list->callback_id == callback_id)
853         {
854           if (list->next)
855             list->next = list->prev;
856
857           if (list->prev)
858             list->prev->next = list->next;
859           else
860             callback_list = list->next;
861
862           /* invoke the destroy handler */
863           (list->destroy) (list->data);
864           free (list);
865           return;
866         }
867     }
868 }