1 | /* Handle loading/unloading of shared object for transformation. |
2 | Copyright (C) 1997-2020 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 Lesser General Public |
8 | License as published by the Free Software Foundation; either |
9 | version 2.1 of the 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 | Lesser General Public License for more details. |
15 | |
16 | You should have received a copy of the GNU Lesser General Public |
17 | License along with the GNU C Library; if not, see |
18 | <https://www.gnu.org/licenses/>. */ |
19 | |
20 | #include <assert.h> |
21 | #include <dlfcn.h> |
22 | #include <inttypes.h> |
23 | #include <search.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include <libc-lock.h> |
27 | #include <sys/param.h> |
28 | |
29 | #include <gconv_int.h> |
30 | #include <sysdep.h> |
31 | |
32 | |
33 | #ifdef DEBUG |
34 | /* For debugging purposes. */ |
35 | static void print_all (void); |
36 | #endif |
37 | |
38 | |
39 | /* This is a tuning parameter. If a transformation module is not used |
40 | anymore it gets not immediately unloaded. Instead we wait a certain |
41 | number of load attempts for further modules. If none of the |
42 | subsequent load attempts name the same object it finally gets unloaded. |
43 | Otherwise it is still available which hopefully is the frequent case. |
44 | The following number is the number of unloading attempts we wait |
45 | before unloading. */ |
46 | #define TRIES_BEFORE_UNLOAD 2 |
47 | |
48 | /* Array of loaded objects. This is shared by all threads so we have |
49 | to use semaphores to access it. */ |
50 | static void *loaded; |
51 | |
52 | /* Comparison function for searching `loaded_object' tree. */ |
53 | static int |
54 | known_compare (const void *p1, const void *p2) |
55 | { |
56 | const struct __gconv_loaded_object *s1 = |
57 | (const struct __gconv_loaded_object *) p1; |
58 | const struct __gconv_loaded_object *s2 = |
59 | (const struct __gconv_loaded_object *) p2; |
60 | |
61 | return strcmp (s1->name, s2->name); |
62 | } |
63 | |
64 | /* Open the gconv database if necessary. A non-negative return value |
65 | means success. */ |
66 | struct __gconv_loaded_object * |
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 | size_t namelen = strlen (name) + 1; |
86 | |
87 | found = malloc (sizeof (struct __gconv_loaded_object) + namelen); |
88 | if (found != NULL) |
89 | { |
90 | /* Point the tree node at this new structure. */ |
91 | found->name = (char *) memcpy (found + 1, name, namelen); |
92 | found->counter = -TRIES_BEFORE_UNLOAD - 1; |
93 | found->handle = NULL; |
94 | |
95 | if (__builtin_expect (__tsearch (found, &loaded, known_compare) |
96 | == NULL, 0)) |
97 | { |
98 | /* Something went wrong while inserting the entry. */ |
99 | free (found); |
100 | found = NULL; |
101 | } |
102 | } |
103 | } |
104 | else |
105 | found = *(struct __gconv_loaded_object **) keyp; |
106 | |
107 | /* Try to load the shared object if the usage count is 0. This |
108 | implies that if the shared object is not loadable, the handle is |
109 | NULL and the usage count > 0. */ |
110 | if (found != NULL) |
111 | { |
112 | if (found->counter < -TRIES_BEFORE_UNLOAD) |
113 | { |
114 | assert (found->handle == NULL); |
115 | found->handle = __libc_dlopen (found->name); |
116 | if (found->handle != NULL) |
117 | { |
118 | found->fct = __libc_dlsym (found->handle, "gconv" ); |
119 | if (found->fct == NULL) |
120 | { |
121 | /* Argh, no conversion function. There is something |
122 | wrong here. */ |
123 | __gconv_release_shlib (found); |
124 | found = NULL; |
125 | } |
126 | else |
127 | { |
128 | found->init_fct = __libc_dlsym (found->handle, "gconv_init" ); |
129 | found->end_fct = __libc_dlsym (found->handle, "gconv_end" ); |
130 | |
131 | #ifdef PTR_MANGLE |
132 | PTR_MANGLE (found->fct); |
133 | PTR_MANGLE (found->init_fct); |
134 | PTR_MANGLE (found->end_fct); |
135 | #endif |
136 | |
137 | /* We have succeeded in loading the shared object. */ |
138 | found->counter = 1; |
139 | } |
140 | } |
141 | else |
142 | /* Error while loading the shared object. */ |
143 | found = NULL; |
144 | } |
145 | else if (found->handle != NULL) |
146 | found->counter = MAX (found->counter + 1, 1); |
147 | } |
148 | |
149 | return found; |
150 | } |
151 | |
152 | static void |
153 | do_release_shlib (const void *nodep, VISIT value, void *closure) |
154 | { |
155 | struct __gconv_loaded_object *release_handle = closure; |
156 | struct __gconv_loaded_object *obj = *(struct __gconv_loaded_object **) nodep; |
157 | |
158 | if (value != preorder && value != leaf) |
159 | return; |
160 | |
161 | if (obj == release_handle) |
162 | { |
163 | /* This is the object we want to unload. Now decrement the |
164 | reference counter. */ |
165 | assert (obj->counter > 0); |
166 | --obj->counter; |
167 | } |
168 | else if (obj->counter <= 0 && obj->counter >= -TRIES_BEFORE_UNLOAD |
169 | && --obj->counter < -TRIES_BEFORE_UNLOAD && obj->handle != NULL) |
170 | { |
171 | /* Unload the shared object. */ |
172 | __libc_dlclose (obj->handle); |
173 | obj->handle = NULL; |
174 | } |
175 | } |
176 | |
177 | |
178 | /* Notify system that a shared object is not longer needed. */ |
179 | void |
180 | __gconv_release_shlib (struct __gconv_loaded_object *handle) |
181 | { |
182 | /* Process all entries. Please note that we also visit entries |
183 | with release counts <= 0. This way we can finally unload them |
184 | if necessary. */ |
185 | __twalk_r (loaded, do_release_shlib, handle); |
186 | } |
187 | |
188 | |
189 | /* We run this if we debug the memory allocation. */ |
190 | static void __libc_freeres_fn_section |
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 | libc_freeres_fn (free_mem) |
203 | { |
204 | __tdestroy (loaded, do_release_all); |
205 | loaded = NULL; |
206 | } |
207 | |
208 | |
209 | #ifdef DEBUG |
210 | |
211 | #include <stdio.h> |
212 | |
213 | static void |
214 | do_print (const void *nodep, VISIT value, int level) |
215 | { |
216 | struct __gconv_loaded_object *obj = *(struct __gconv_loaded_object **) nodep; |
217 | |
218 | printf ("%10s: \"%s\", %d\n" , |
219 | value == leaf ? "leaf" |
220 | : value == preorder ? "preorder" |
221 | : value == postorder ? "postorder" : "endorder" , |
222 | obj->name, obj->counter); |
223 | } |
224 | |
225 | static void __attribute__ ((used)) |
226 | print_all (void) |
227 | { |
228 | __twalk (loaded, do_print); |
229 | } |
230 | #endif |
231 | |