xf.li | bdd93d5 | 2023-05-12 07:10:14 -0700 | [diff] [blame] | 1 | /* Handle loading/unloading of shared object for transformation. |
| 2 | Copyright (C) 1997-2016 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 | <http://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 | internal_function |
| 68 | __gconv_find_shlib (const char *name) |
| 69 | { |
| 70 | struct __gconv_loaded_object *found; |
| 71 | void *keyp; |
| 72 | |
| 73 | /* Search the tree of shared objects previously requested. Data in |
| 74 | the tree are `loaded_object' structures, whose first member is a |
| 75 | `const char *', the lookup key. The search returns a pointer to |
| 76 | the tree node structure; the first member of the is a pointer to |
| 77 | our structure (i.e. what will be a `loaded_object'); since the |
| 78 | first member of that is the lookup key string, &FCT_NAME is close |
| 79 | enough to a pointer to our structure to use as a lookup key that |
| 80 | will be passed to `known_compare' (above). */ |
| 81 | |
| 82 | keyp = __tfind (&name, &loaded, known_compare); |
| 83 | if (keyp == NULL) |
| 84 | { |
| 85 | /* This name was not known before. */ |
| 86 | size_t namelen = strlen (name) + 1; |
| 87 | |
| 88 | found = malloc (sizeof (struct __gconv_loaded_object) + namelen); |
| 89 | if (found != NULL) |
| 90 | { |
| 91 | /* Point the tree node at this new structure. */ |
| 92 | found->name = (char *) memcpy (found + 1, name, namelen); |
| 93 | found->counter = -TRIES_BEFORE_UNLOAD - 1; |
| 94 | found->handle = NULL; |
| 95 | |
| 96 | if (__builtin_expect (__tsearch (found, &loaded, known_compare) |
| 97 | == NULL, 0)) |
| 98 | { |
| 99 | /* Something went wrong while inserting the entry. */ |
| 100 | free (found); |
| 101 | found = NULL; |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | else |
| 106 | found = *(struct __gconv_loaded_object **) keyp; |
| 107 | |
| 108 | /* Try to load the shared object if the usage count is 0. This |
| 109 | implies that if the shared object is not loadable, the handle is |
| 110 | NULL and the usage count > 0. */ |
| 111 | if (found != NULL) |
| 112 | { |
| 113 | if (found->counter < -TRIES_BEFORE_UNLOAD) |
| 114 | { |
| 115 | assert (found->handle == NULL); |
| 116 | found->handle = __libc_dlopen (found->name); |
| 117 | if (found->handle != NULL) |
| 118 | { |
| 119 | found->fct = __libc_dlsym (found->handle, "gconv"); |
| 120 | if (found->fct == NULL) |
| 121 | { |
| 122 | /* Argh, no conversion function. There is something |
| 123 | wrong here. */ |
| 124 | __gconv_release_shlib (found); |
| 125 | found = NULL; |
| 126 | } |
| 127 | else |
| 128 | { |
| 129 | found->init_fct = __libc_dlsym (found->handle, "gconv_init"); |
| 130 | found->end_fct = __libc_dlsym (found->handle, "gconv_end"); |
| 131 | |
| 132 | #ifdef PTR_MANGLE |
| 133 | PTR_MANGLE (found->fct); |
| 134 | if (found->init_fct != NULL) |
| 135 | PTR_MANGLE (found->init_fct); |
| 136 | if (found->end_fct != NULL) |
| 137 | PTR_MANGLE (found->end_fct); |
| 138 | #endif |
| 139 | |
| 140 | /* We have succeeded in loading the shared object. */ |
| 141 | found->counter = 1; |
| 142 | } |
| 143 | } |
| 144 | else |
| 145 | /* Error while loading the shared object. */ |
| 146 | found = NULL; |
| 147 | } |
| 148 | else if (found->handle != NULL) |
| 149 | found->counter = MAX (found->counter + 1, 1); |
| 150 | } |
| 151 | |
| 152 | return found; |
| 153 | } |
| 154 | |
| 155 | |
| 156 | /* This is very ugly but the tsearch functions provide no way to pass |
| 157 | information to the walker function. So we use a global variable. |
| 158 | It is MT safe since we use a lock. */ |
| 159 | static struct __gconv_loaded_object *release_handle; |
| 160 | |
| 161 | static void |
| 162 | do_release_shlib (void *nodep, VISIT value, int level) |
| 163 | { |
| 164 | struct __gconv_loaded_object *obj = *(struct __gconv_loaded_object **) nodep; |
| 165 | |
| 166 | if (value != preorder && value != leaf) |
| 167 | return; |
| 168 | |
| 169 | if (obj == release_handle) |
| 170 | { |
| 171 | /* This is the object we want to unload. Now decrement the |
| 172 | reference counter. */ |
| 173 | assert (obj->counter > 0); |
| 174 | --obj->counter; |
| 175 | } |
| 176 | else if (obj->counter <= 0 && obj->counter >= -TRIES_BEFORE_UNLOAD |
| 177 | && --obj->counter < -TRIES_BEFORE_UNLOAD && obj->handle != NULL) |
| 178 | { |
| 179 | /* Unload the shared object. */ |
| 180 | __libc_dlclose (obj->handle); |
| 181 | obj->handle = NULL; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | |
| 186 | /* Notify system that a shared object is not longer needed. */ |
| 187 | void |
| 188 | internal_function |
| 189 | __gconv_release_shlib (struct __gconv_loaded_object *handle) |
| 190 | { |
| 191 | /* Urgh, this is ugly but we have no other possibility. */ |
| 192 | release_handle = handle; |
| 193 | |
| 194 | /* Process all entries. Please note that we also visit entries |
| 195 | with release counts <= 0. This way we can finally unload them |
| 196 | if necessary. */ |
| 197 | __twalk (loaded, (__action_fn_t) do_release_shlib); |
| 198 | } |
| 199 | |
| 200 | |
| 201 | /* We run this if we debug the memory allocation. */ |
| 202 | static void __libc_freeres_fn_section |
| 203 | do_release_all (void *nodep) |
| 204 | { |
| 205 | struct __gconv_loaded_object *obj = (struct __gconv_loaded_object *) nodep; |
| 206 | |
| 207 | /* Unload the shared object. */ |
| 208 | if (obj->handle != NULL) |
| 209 | __libc_dlclose (obj->handle); |
| 210 | |
| 211 | free (obj); |
| 212 | } |
| 213 | |
| 214 | libc_freeres_fn (free_mem) |
| 215 | { |
| 216 | __tdestroy (loaded, do_release_all); |
| 217 | loaded = NULL; |
| 218 | } |
| 219 | |
| 220 | |
| 221 | #ifdef DEBUG |
| 222 | |
| 223 | #include <stdio.h> |
| 224 | |
| 225 | static void |
| 226 | do_print (const void *nodep, VISIT value, int level) |
| 227 | { |
| 228 | struct __gconv_loaded_object *obj = *(struct __gconv_loaded_object **) nodep; |
| 229 | |
| 230 | printf ("%10s: \"%s\", %d\n", |
| 231 | value == leaf ? "leaf" : |
| 232 | value == preorder ? "preorder" : |
| 233 | value == postorder ? "postorder" : "endorder", |
| 234 | obj->name, obj->counter); |
| 235 | } |
| 236 | |
| 237 | static void __attribute__ ((used)) |
| 238 | print_all (void) |
| 239 | { |
| 240 | __twalk (loaded, do_print); |
| 241 | } |
| 242 | #endif |