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