Add debugging functions.
[kopensolaris-gnu/glibc.git] / iconv / gconv_dl.c
1 /* Handle loading/unloading of shared object for transformation.
2    Copyright (C) 1997, 1998, 1999 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
5
6    The GNU C Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    License, or (at your option) any later version.
10
11    The GNU C Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the GNU C Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.  */
20
21 #include <dlfcn.h>
22 #include <inttypes.h>
23 #include <search.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <bits/libc-lock.h>
27 #include <sys/param.h>
28
29 #include <gconv_int.h>
30
31
32 #ifdef DEBUG
33 /* For debugging purposes.  */
34 static void print_all (void);
35 #endif
36
37
38 /* This is a tuning parameter.  If a transformation module is not used
39    anymore it gets not immediately unloaded.  Instead we wait a certain
40    number of load attempts for further modules.  If none of the
41    subsequent load attempts name the same object it finally gets unloaded.
42    Otherwise it is still available which hopefully is the frequent case.
43    The following number is the number of unloading attempts we wait
44    before unloading.  */
45 #define TRIES_BEFORE_UNLOAD     2
46
47 /* Array of loaded objects.  This is shared by all threads so we have
48    to use semaphores to access it.  */
49 static void *loaded;
50
51 /* Comparison function for searching `loaded_object' tree.  */
52 static int
53 known_compare (const void *p1, const void *p2)
54 {
55   const struct __gconv_loaded_object *s1 =
56     (const struct __gconv_loaded_object *) p1;
57   const struct __gconv_loaded_object *s2 =
58     (const struct __gconv_loaded_object *) p2;
59
60   return strcmp (s1->name, s2->name);
61 }
62
63 /* Open the gconv database if necessary.  A non-negative return value
64    means success.  */
65 struct __gconv_loaded_object *
66 internal_function
67 __gconv_find_shlib (const char *name)
68 {
69   struct __gconv_loaded_object *found;
70   void *keyp;
71
72   /* Search the tree of shared objects previously requested.  Data in
73      the tree are `loaded_object' structures, whose first member is a
74      `const char *', the lookup key.  The search returns a pointer to
75      the tree node structure; the first member of the is a pointer to
76      our structure (i.e. what will be a `loaded_object'); since the
77      first member of that is the lookup key string, &FCT_NAME is close
78      enough to a pointer to our structure to use as a lookup key that
79      will be passed to `known_compare' (above).  */
80
81   keyp = __tfind (&name, &loaded, known_compare);
82   if (keyp == NULL)
83     {
84       /* This name was not known before.  */
85       found = malloc (sizeof (struct __gconv_loaded_object));
86       if (found != NULL)
87         {
88           /* Point the tree node at this new structure.  */
89           found->name = name;
90           found->counter = -TRIES_BEFORE_UNLOAD - 1;
91           found->handle = NULL;
92
93           if (__tsearch (found, &loaded, known_compare) == NULL)
94             {
95               /* Something went wrong while inserting the entry.  */
96               free (found);
97               found = NULL;
98             }
99         }
100     }
101   else
102     found = *(struct __gconv_loaded_object **) keyp;
103
104   /* Try to load the shared object if the usage count is 0.  This
105      implies that if the shared object is not loadable, the handle is
106      NULL and the usage count > 0.  */
107   if (found != NULL)
108     {
109       if (found->counter < -TRIES_BEFORE_UNLOAD)
110         {
111           found->handle = __libc_dlopen (found->name);
112           if (found->handle != NULL)
113             {
114               found->fct = __libc_dlsym (found->handle, "gconv");
115               if (found->fct == NULL)
116                 {
117                   /* Argh, no conversion function.  There is something
118                      wrong here.  */
119                   __gconv_release_shlib (found);
120                   found = NULL;
121                 }
122               else
123                 {
124                   found->init_fct = __libc_dlsym (found->handle, "gconv_init");
125                   found->end_fct = __libc_dlsym (found->handle, "gconv_end");
126
127                   /* We have succeeded in loading the shared object.  */
128                   found->counter = 1;
129                 }
130             }
131           else
132             /* Error while loading the shared object.  */
133             found = NULL;
134         }
135       else if (found->handle != NULL)
136         found->counter = MAX (found->counter + 1, 1);
137     }
138
139   return found;
140 }
141
142
143 /* This is very ugly but the tsearch functions provide no way to pass
144    information to the walker function.  So we use a global variable.
145    It is MT safe since we use a lock.  */
146 static struct __gconv_loaded_object *release_handle;
147
148 static void
149 do_release_shlib (const void *nodep, VISIT value, int level)
150 {
151   struct __gconv_loaded_object *obj = *(struct __gconv_loaded_object **) nodep;
152
153   if (value != preorder && value != leaf)
154     return;
155
156   if (obj == release_handle)
157     /* This is the object we want to unload.  Now set the release
158        counter to zero.  */
159     obj->counter = 0;
160   else if (obj->counter <= 0)
161     {
162       if (--obj->counter < -TRIES_BEFORE_UNLOAD && obj->handle != NULL)
163         {
164           /* Unload the shared object.  */
165           __libc_dlclose (obj->handle);
166           obj->handle = NULL;
167         }
168     }
169 }
170
171
172 /* Notify system that a shared object is not longer needed.  */
173 int
174 internal_function
175 __gconv_release_shlib (struct __gconv_loaded_object *handle)
176 {
177   /* Urgh, this is ugly but we have no other possibility.  */
178   release_handle = handle;
179
180   /* Process all entries.  Please note that we also visit entries
181      with release counts <= 0.  This way we can finally unload them
182      if necessary.  */
183   __twalk (loaded, do_release_shlib);
184
185   return __GCONV_OK;
186 }
187
188
189 /* We run this if we debug the memory allocation.  */
190 static void
191 do_release_all (void *nodep)
192 {
193   struct __gconv_loaded_object *obj = (struct __gconv_loaded_object *) nodep;
194
195   /* Unload the shared object.  */
196   if (obj->handle != NULL)
197     __libc_dlclose (obj->handle);
198
199   free (obj);
200 }
201
202 static void __attribute__ ((unused))
203 free_mem (void)
204 {
205   __tdestroy (loaded, do_release_all);
206 }
207 text_set_element (__libc_subfreeres, free_mem);
208
209
210 #ifdef DEBUG
211 static void
212 do_print (const void *nodep, VISIT value, int level)
213 {
214   struct __gconv_loaded_object *obj = *(struct __gconv_loaded_object **) nodep;
215
216   printf ("%10s: \"%s\", %d\n",
217           value == leaf ? "leaf" :
218           value == preorder ? "preorder" :
219           value == postorder ? "postorder" : "endorder",
220           obj->name, obj->counter);
221 }
222
223 static void
224 print_all (void)
225 {
226   __twalk (loaded, do_print);
227 }
228 #endif