]> git.ipfire.org Git - thirdparty/glibc.git/blob - iconv/gconv_db.c
Update.
[thirdparty/glibc.git] / iconv / gconv_db.c
1 /* Provide access to the collection of available transformation modules.
2 Copyright (C) 1997, 1998, 1999, 2000 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 <limits.h>
22 #include <search.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/param.h>
26 #include <bits/libc-lock.h>
27
28 #include <dlfcn.h>
29 #include <gconv_int.h>
30
31
32 /* Simple data structure for alias mapping. We have two names, `from'
33 and `to'. */
34 void *__gconv_alias_db;
35
36 /* Array with available modules. */
37 struct gconv_module *__gconv_modules_db;
38
39 /* We modify global data. */
40 __libc_lock_define_initialized (static, lock)
41
42
43 /* Function for searching alias. */
44 int
45 __gconv_alias_compare (const void *p1, const void *p2)
46 {
47 struct gconv_alias *s1 = (struct gconv_alias *) p1;
48 struct gconv_alias *s2 = (struct gconv_alias *) p2;
49 return strcmp (s1->fromname, s2->fromname);
50 }
51
52
53 /* To search for a derivation we create a list of intermediate steps.
54 Each element contains a pointer to the element which precedes it
55 in the derivation order. */
56 struct derivation_step
57 {
58 const char *result_set;
59 size_t result_set_len;
60 int cost_lo;
61 int cost_hi;
62 struct gconv_module *code;
63 struct derivation_step *last;
64 struct derivation_step *next;
65 };
66
67 #define NEW_STEP(result, hi, lo, module, last_mod) \
68 ({ struct derivation_step *newp = alloca (sizeof (struct derivation_step)); \
69 newp->result_set = result; \
70 newp->result_set_len = strlen (result); \
71 newp->cost_hi = hi; \
72 newp->cost_lo = lo; \
73 newp->code = module; \
74 newp->last = last_mod; \
75 newp->next = NULL; \
76 newp; })
77
78
79 /* If a specific transformation is used more than once we should not need
80 to start looking for it again. Instead cache each successful result. */
81 struct known_derivation
82 {
83 const char *from;
84 const char *to;
85 struct __gconv_step *steps;
86 size_t nsteps;
87 };
88
89 /* Compare function for database of found derivations. */
90 static int
91 derivation_compare (const void *p1, const void *p2)
92 {
93 struct known_derivation *s1 = (struct known_derivation *) p1;
94 struct known_derivation *s2 = (struct known_derivation *) p2;
95 int result;
96
97 result = strcmp (s1->from, s2->from);
98 if (result == 0)
99 result = strcmp (s1->to, s2->to);
100 return result;
101 }
102
103 /* The search tree for known derivations. */
104 static void *known_derivations;
105
106 /* Look up whether given transformation was already requested before. */
107 static int
108 internal_function
109 derivation_lookup (const char *fromset, const char *toset,
110 struct __gconv_step **handle, size_t *nsteps)
111 {
112 struct known_derivation key = { fromset, toset, NULL, 0 };
113 struct known_derivation **result;
114
115 result = __tfind (&key, &known_derivations, derivation_compare);
116
117 if (result == NULL)
118 return __GCONV_NOCONV;
119
120 *handle = (*result)->steps;
121 *nsteps = (*result)->nsteps;
122
123 /* Please note that we return GCONV_OK even if the last search for
124 this transformation was unsuccessful. */
125 return __GCONV_OK;
126 }
127
128 /* Add new derivation to list of known ones. */
129 static void
130 internal_function
131 add_derivation (const char *fromset, const char *toset,
132 struct __gconv_step *handle, size_t nsteps)
133 {
134 struct known_derivation *new_deriv;
135 size_t fromset_len = strlen (fromset) + 1;
136 size_t toset_len = strlen (toset) + 1;
137
138 new_deriv = (struct known_derivation *)
139 malloc (sizeof (struct known_derivation) + fromset_len + toset_len);
140 if (new_deriv != NULL)
141 {
142 new_deriv->from = (char *) (new_deriv + 1);
143 new_deriv->to = memcpy (__mempcpy (new_deriv + 1, fromset, fromset_len),
144 toset, toset_len);
145
146 new_deriv->steps = handle;
147 new_deriv->nsteps = nsteps;
148
149 if (__tsearch (new_deriv, &known_derivations, derivation_compare)
150 == NULL)
151 /* There is some kind of memory allocation problem. */
152 free (new_deriv);
153 }
154 /* Please note that we don't complain if the allocation failed. This
155 is not tragically but in case we use the memory debugging facilities
156 not all memory will be freed. */
157 }
158
159 static void
160 free_derivation (void *p)
161 {
162 struct known_derivation *deriv = (struct known_derivation *) p;
163 size_t cnt;
164
165 for (cnt = 0; cnt < deriv->nsteps; ++cnt)
166 if (deriv->steps[cnt].__end_fct)
167 DL_CALL_FCT (deriv->steps[cnt].__end_fct, (&deriv->steps[cnt]));
168
169 /* Free the name strings. */
170 free ((char *) deriv->steps[0].__from_name);
171 free ((char *) deriv->steps[deriv->nsteps - 1].__to_name);
172
173 free ((struct __gconv_step *) deriv->steps);
174 free (deriv);
175 }
176
177
178 static int
179 internal_function
180 gen_steps (struct derivation_step *best, const char *toset,
181 const char *fromset, struct __gconv_step **handle, size_t *nsteps)
182 {
183 size_t step_cnt = 0;
184 struct __gconv_step *result;
185 struct derivation_step *current;
186 int status = __GCONV_NOMEM;
187
188 /* First determine number of steps. */
189 for (current = best; current->last != NULL; current = current->last)
190 ++step_cnt;
191
192 result = (struct __gconv_step *) malloc (sizeof (struct __gconv_step)
193 * step_cnt);
194 if (result != NULL)
195 {
196 int failed = 0;
197
198 status = __GCONV_OK;
199 *nsteps = step_cnt;
200 current = best;
201 while (step_cnt-- > 0)
202 {
203 result[step_cnt].__from_name = (step_cnt == 0
204 ? __strdup (fromset)
205 : current->last->result_set);
206 result[step_cnt].__to_name = (step_cnt + 1 == *nsteps
207 ? __strdup (current->result_set)
208 : result[step_cnt + 1].__from_name);
209
210 #ifndef STATIC_GCONV
211 if (current->code->module_name[0] == '/')
212 {
213 /* Load the module, return handle for it. */
214 struct __gconv_loaded_object *shlib_handle =
215 __gconv_find_shlib (current->code->module_name);
216
217 if (shlib_handle == NULL)
218 {
219 failed = 1;
220 break;
221 }
222
223 result[step_cnt].__shlib_handle = shlib_handle;
224 result[step_cnt].__modname = shlib_handle->name;
225 result[step_cnt].__counter = 1;
226 result[step_cnt].__fct = shlib_handle->fct;
227 result[step_cnt].__init_fct = shlib_handle->init_fct;
228 result[step_cnt].__end_fct = shlib_handle->end_fct;
229 }
230 else
231 #endif
232 /* It's a builtin transformation. */
233 __gconv_get_builtin_trans (current->code->module_name,
234 &result[step_cnt]);
235
236 /* Call the init function. */
237 if (result[step_cnt].__init_fct != NULL)
238 {
239 status = DL_CALL_FCT (result[step_cnt].__init_fct,
240 (&result[step_cnt]));
241
242 if (__builtin_expect (status, __GCONV_OK) != __GCONV_OK)
243 {
244 failed = 1;
245 /* Make sure we unload this modules. */
246 --step_cnt;
247 break;
248 }
249 }
250
251 current = current->last;
252 }
253
254 if (__builtin_expect (failed, 0) != 0)
255 {
256 /* Something went wrong while initializing the modules. */
257 while (++step_cnt < *nsteps)
258 {
259 if (result[step_cnt].__end_fct != NULL)
260 DL_CALL_FCT (result[step_cnt].__end_fct, (&result[step_cnt]));
261 #ifndef STATIC_GCONV
262 __gconv_release_shlib (result[step_cnt].__shlib_handle);
263 #endif
264 }
265 free (result);
266 *nsteps = 0;
267 *handle = NULL;
268 if (status == __GCONV_OK)
269 status = __GCONV_NOCONV;
270 }
271 else
272 *handle = result;
273 }
274 else
275 {
276 *nsteps = 0;
277 *handle = NULL;
278 }
279
280 return status;
281 }
282
283
284 #ifndef STATIC_GCONV
285 static int
286 internal_function
287 increment_counter (struct __gconv_step *steps, size_t nsteps)
288 {
289 /* Increment the user counter. */
290 size_t cnt = nsteps;
291 int result = __GCONV_OK;
292
293 while (cnt-- > 0)
294 if (steps[cnt].__counter++ == 0)
295 {
296 steps[cnt].__shlib_handle =
297 __gconv_find_shlib (steps[cnt].__modname);
298 if (steps[cnt].__shlib_handle == NULL)
299 {
300 /* Oops, this is the second time we use this module (after
301 unloading) and this time loading failed!? */
302 while (++cnt < nsteps)
303 __gconv_release_shlib (steps[cnt].__shlib_handle);
304 result = __GCONV_NOCONV;
305 break;
306 }
307 }
308 return result;
309 }
310 #endif
311
312
313 /* The main function: find a possible derivation from the `fromset' (either
314 the given name or the alias) to the `toset' (again with alias). */
315 static int
316 internal_function
317 find_derivation (const char *toset, const char *toset_expand,
318 const char *fromset, const char *fromset_expand,
319 struct __gconv_step **handle, size_t *nsteps)
320 {
321 struct derivation_step *first, *current, **lastp, *solution = NULL;
322 int best_cost_hi = INT_MAX;
323 int best_cost_lo = INT_MAX;
324 int result;
325
326 /* There is a small chance that this derivation is meanwhile found. This
327 can happen if in `find_derivation' we look for this derivation, didn't
328 find it but at the same time another thread looked for this derivation. */
329 result = derivation_lookup (fromset_expand ?: fromset, toset_expand ?: toset,
330 handle, nsteps);
331 if (result == __GCONV_OK)
332 {
333 #ifndef STATIC_GCONV
334 result = increment_counter (*handle, *nsteps);
335 #endif
336 return result;
337 }
338
339 /* For now we use a simple algorithm with quadratic runtime behaviour.
340 The task is to match the `toset' with any of the available rules,
341 starting from FROMSET. */
342 if (fromset_expand != NULL)
343 {
344 first = NEW_STEP (fromset_expand, 0, 0, NULL, NULL);
345 first->next = NEW_STEP (fromset, 0, 0, NULL, NULL);
346 lastp = &first->next->next;
347 }
348 else
349 {
350 first = NEW_STEP (fromset, 0, 0, NULL, NULL);
351 lastp = &first->next;
352 }
353
354 for (current = first; current != NULL; current = current->next)
355 {
356 /* Now match all the available module specifications against the
357 current charset name. If any of them matches check whether
358 we already have a derivation for this charset. If yes, use the
359 one with the lower costs. Otherwise add the new charset at the
360 end.
361
362 The module database is organized in a tree form which allows
363 searching for prefixes. So we search for the first entry with a
364 matching prefix and any other matching entry can be found from
365 this place. */
366 struct gconv_module *node = __gconv_modules_db;
367
368 /* Maybe it is not necessary anymore to look for a solution for
369 this entry since the cost is already as high (or heigher) as
370 the cost for the best solution so far. */
371 if (current->cost_hi > best_cost_hi
372 || (current->cost_hi == best_cost_hi
373 && current->cost_lo >= best_cost_lo))
374 continue;
375
376 while (node != NULL)
377 {
378 int cmpres = strcmp (current->result_set, node->from_string);
379 if (cmpres == 0)
380 {
381 /* Walk through the list of modules with this prefix and
382 try to match the name. */
383 struct gconv_module *runp;
384
385 /* Check all the modules with this prefix. */
386 runp = node;
387 do
388 {
389 const char *result_set = (strcmp (runp->to_string, "-") == 0
390 ? (toset_expand ?: toset)
391 : runp->to_string);
392 int cost_hi = runp->cost_hi + current->cost_hi;
393 int cost_lo = runp->cost_lo + current->cost_lo;
394 struct derivation_step *step;
395
396 /* We managed to find a derivation. First see whether
397 this is what we are looking for. */
398 if (strcmp (result_set, toset) == 0
399 || (toset_expand != NULL
400 && strcmp (result_set, toset_expand) == 0))
401 {
402 if (solution == NULL || cost_hi < best_cost_hi
403 || (cost_hi == best_cost_hi
404 && cost_lo < best_cost_lo))
405 {
406 best_cost_hi = cost_hi;
407 best_cost_lo = cost_lo;
408 }
409
410 /* Append this solution to list. */
411 if (solution == NULL)
412 solution = NEW_STEP (result_set, 0, 0, runp, current);
413 else
414 {
415 while (solution->next != NULL)
416 solution = solution->next;
417
418 solution->next = NEW_STEP (result_set, 0, 0,
419 runp, current);
420 }
421 }
422 else if (cost_hi < best_cost_hi
423 || (cost_hi == best_cost_hi
424 && cost_lo < best_cost_lo))
425 {
426 /* Append at the end if there is no entry with
427 this name. */
428 for (step = first; step != NULL; step = step->next)
429 if (strcmp (result_set, step->result_set) == 0)
430 break;
431
432 if (step == NULL)
433 {
434 *lastp = NEW_STEP (result_set,
435 cost_hi, cost_lo,
436 runp, current);
437 lastp = &(*lastp)->next;
438 }
439 else if (step->cost_hi > cost_hi
440 || (step->cost_hi == cost_hi
441 && step->cost_lo > cost_lo))
442 {
443 step->code = runp;
444 step->last = current;
445
446 /* Update the cost for all steps. */
447 for (step = first; step != NULL;
448 step = step->next)
449 {
450 struct derivation_step *back;
451
452 if (step->code == NULL)
453 /* This is one of the entries we started
454 from. */
455 continue;
456
457 step->cost_hi = step->code->cost_hi;
458 step->cost_lo = step->code->cost_lo;
459
460 for (back = step->last; back->code != NULL;
461 back = back->last)
462 {
463 step->cost_hi += back->code->cost_hi;
464 step->cost_lo += back->code->cost_lo;
465 }
466 }
467
468 for (step = solution; step != NULL;
469 step = step->next)
470 {
471 step->cost_hi = (step->code->cost_hi
472 + step->last->cost_hi);
473 step->cost_lo = (step->code->cost_lo
474 + step->last->cost_lo);
475
476 if (step->cost_hi < best_cost_hi
477 || (step->cost_hi == best_cost_hi
478 && step->cost_lo < best_cost_lo))
479 {
480 solution = step;
481 best_cost_hi = step->cost_hi;
482 best_cost_lo = step->cost_lo;
483 }
484 }
485 }
486 }
487
488 runp = runp->same;
489 }
490 while (runp != NULL);
491
492 break;
493 }
494 else if (cmpres < 0)
495 node = node->left;
496 else
497 node = node->right;
498 }
499 }
500
501 if (solution != NULL)
502 /* We really found a way to do the transformation. Now build a data
503 structure describing the transformation steps.*/
504 result = gen_steps (solution, toset_expand ?: toset,
505 fromset_expand ?: fromset, handle, nsteps);
506 else
507 {
508 /* We haven't found a transformation. Clear the result values. */
509 *handle = NULL;
510 *nsteps = 0;
511 }
512
513 /* Add result in any case to list of known derivations. */
514 add_derivation (fromset_expand ?: fromset, toset_expand ?: toset,
515 *handle, *nsteps);
516
517 return result;
518 }
519
520
521 int
522 internal_function
523 __gconv_find_transform (const char *toset, const char *fromset,
524 struct __gconv_step **handle, size_t *nsteps,
525 int flags)
526 {
527 __libc_once_define (static, once);
528 const char *fromset_expand = NULL;
529 const char *toset_expand = NULL;
530 int result;
531
532 /* Ensure that the configuration data is read. */
533 __libc_once (once, __gconv_read_conf);
534
535 /* Acquire the lock. */
536 __libc_lock_lock (lock);
537
538 /* If we don't have a module database return with an error. */
539 if (__gconv_modules_db == NULL)
540 {
541 __libc_lock_unlock (lock);
542 return __GCONV_NOCONV;
543 }
544
545 /* See whether the names are aliases. */
546 if (__gconv_alias_db != NULL)
547 {
548 struct gconv_alias key;
549 struct gconv_alias **found;
550
551 key.fromname = fromset;
552 found = __tfind (&key, &__gconv_alias_db, __gconv_alias_compare);
553 fromset_expand = found != NULL ? (*found)->toname : NULL;
554
555 key.fromname = toset;
556 found = __tfind (&key, &__gconv_alias_db, __gconv_alias_compare);
557 toset_expand = found != NULL ? (*found)->toname : NULL;
558 }
559
560 if (__builtin_expect (flags & GCONV_AVOID_NOCONV, 0)
561 /* We are not supposed to create a pseudo transformation (means
562 copying) when the input and output character set are the same. */
563 && (strcmp (toset, fromset) == 0
564 || (toset_expand != NULL && strcmp (toset_expand, fromset) == 0)
565 || (fromset_expand != NULL
566 && (strcmp (toset, fromset_expand) == 0
567 || (toset_expand != NULL
568 && strcmp (toset_expand, fromset_expand) == 0)))))
569 {
570 /* Both character sets are the same. */
571 __libc_lock_unlock (lock);
572 return __GCONV_NOCONV;
573 }
574
575 result = find_derivation (toset, toset_expand, fromset, fromset_expand,
576 handle, nsteps);
577
578 /* Release the lock. */
579 __libc_lock_unlock (lock);
580
581 /* The following code is necessary since `find_derivation' will return
582 GCONV_OK even when no derivation was found but the same request
583 was processed before. I.e., negative results will also be cached. */
584 return (result == __GCONV_OK
585 ? (*handle == NULL ? __GCONV_NOCONV : __GCONV_OK)
586 : result);
587 }
588
589
590 /* Release the entries of the modules list. */
591 int
592 internal_function
593 __gconv_close_transform (struct __gconv_step *steps, size_t nsteps)
594 {
595 int result = __GCONV_OK;
596
597 #ifndef STATIC_GCONV
598 /* Acquire the lock. */
599 __libc_lock_lock (lock);
600
601 while (nsteps-- > 0)
602 if (steps[nsteps].__shlib_handle != NULL
603 && --steps[nsteps].__counter == 0)
604 {
605 result = __gconv_release_shlib (steps[nsteps].__shlib_handle);
606 if (result != __GCONV_OK)
607 break;
608 steps[nsteps].__shlib_handle = NULL;
609 }
610
611 /* Release the lock. */
612 __libc_lock_unlock (lock);
613 #endif
614
615 return result;
616 }
617
618
619 /* Free the modules mentioned. */
620 static void
621 internal_function
622 free_modules_db (struct gconv_module *node)
623 {
624 if (node->left != NULL)
625 free_modules_db (node->left);
626 if (node->right != NULL)
627 free_modules_db (node->right);
628 do
629 {
630 struct gconv_module *act = node;
631 node = node->same;
632 if (act->module_name[0] == '/')
633 free (act);
634 }
635 while (node != NULL);
636 }
637
638
639 /* Free all resources if necessary. */
640 static void __attribute__ ((unused))
641 free_mem (void)
642 {
643 if (__gconv_alias_db != NULL)
644 __tdestroy (__gconv_alias_db, free);
645
646 if (__gconv_modules_db != NULL)
647 free_modules_db (__gconv_modules_db);
648
649 if (known_derivations != NULL)
650 __tdestroy (known_derivations, free_derivation);
651 }
652
653 text_set_element (__libc_subfreeres, free_mem);