| 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 |  | 
|---|