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