1 /* Provide access to the collection of available transformation modules.
2 Copyright (C) 1997 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
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.
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.
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. */
25 #include <bits/libc-lock.h>
28 /* Simple data structure for alias mapping. We have two names, `from'
30 void *__gconv_alias_db
;
32 /* Array with available modules. */
33 size_t __gconv_nmodules
;
34 struct gconv_module
**__gconv_modules_db
;
37 /* Function for searching alias. */
39 __gconv_alias_compare (const void *p1
, const void *p2
)
41 struct gconv_alias
*s1
= (struct gconv_alias
*) p1
;
42 struct gconv_alias
*s2
= (struct gconv_alias
*) p2
;
43 return __strcasecmp (s1
->fromname
, s2
->fromname
);
47 /* To search for a derivation we create a list of intermediate steps.
48 Each element contains a pointer to the element which precedes it
49 in the derivation order. */
50 struct derivation_step
52 const char *result_set
;
53 struct gconv_module
*code
;
54 struct derivation_step
*last
;
55 struct derivation_step
*next
;
58 #define NEW_STEP(result, module, last_mod) \
59 ({ struct derivation_step *newp = alloca (sizeof (struct derivation_step)); \
60 newp->result_set = result; \
61 newp->code = module; \
62 newp->last = last_mod; \
67 /* If a specific transformation is used more than once we should not need
68 to start looking for it again. Instead cache each successful result. */
69 struct known_derivation
73 struct gconv_step
*steps
;
77 /* Compare function for database of found derivations. */
79 derivation_compare (const void *p1
, const void *p2
)
81 struct known_derivation
*s1
= (struct known_derivation
*) p1
;
82 struct known_derivation
*s2
= (struct known_derivation
*) p2
;
85 result
= strcmp (s1
->from
, s2
->from
);
87 result
= strcmp (s1
->to
, s2
->to
);
91 /* The search tree for known derivations. */
92 static void *known_derivations
;
94 /* Look up whether given transformation was already requested before. */
96 derivation_lookup (const char *fromset
, const char *toset
,
97 struct gconv_step
**handle
, size_t *nsteps
)
99 struct known_derivation key
= { fromset
, toset
, NULL
, 0 };
100 struct known_derivation
*result
;
102 result
= __tfind (&key
, &known_derivations
, derivation_compare
);
107 *handle
= result
->steps
;
108 *nsteps
= result
->nsteps
;
110 /* Please note that we return GCONV_OK even if the last search for
111 this transformation was unsuccessful. */
115 /* Add new derivation to list of known ones. */
117 add_derivation (const char *fromset
, const char *toset
,
118 struct gconv_step
*handle
, size_t nsteps
)
120 struct known_derivation
*new_deriv
;
121 size_t fromset_len
= strlen (fromset
) + 1;
122 size_t toset_len
= strlen (toset
) + 1;
124 new_deriv
= (struct known_derivation
*)
125 malloc (sizeof (struct known_derivation
) + fromset_len
+ toset_len
);
126 if (new_deriv
!= NULL
)
128 new_deriv
->from
= memcpy ((char *) new_deriv
129 + sizeof (struct known_derivation
),
130 fromset
, fromset_len
);
131 new_deriv
->to
= memcpy ((char *) new_deriv
->from
+ fromset_len
,
134 new_deriv
->steps
= handle
;
135 new_deriv
->nsteps
= nsteps
;
137 __tsearch (new_deriv
, &known_derivations
, derivation_compare
);
139 /* Please note that we don't complain if the allocation failed. This
140 is not tragically but in case we use the memory debugging facilities
141 not all memory will be freed. */
145 free_derivation (void *p
)
147 struct known_derivation
*deriv
= (struct known_derivation
*) p
;
149 free ((struct gconv_step
*) deriv
->steps
);
156 gen_steps (struct derivation_step
*best
, const char *toset
,
157 const char *fromset
, struct gconv_step
**handle
, size_t *nsteps
)
160 struct gconv_step
*result
;
161 struct derivation_step
*current
;
162 int status
= GCONV_NOMEM
;
164 /* First determine number of steps. */
165 for (current
= best
; current
->last
!= NULL
; current
= current
->last
)
168 result
= (struct gconv_step
*) malloc (sizeof (struct gconv_step
)
176 while (step_cnt
-- > 0)
178 result
[step_cnt
].from_name
= (step_cnt
== 0
180 : current
->last
->result_set
);
181 result
[step_cnt
].to_name
= (step_cnt
+ 1 == *nsteps
182 ? __strdup (current
->result_set
)
183 : result
[step_cnt
+ 1].from_name
);
185 if (current
->code
->module_name
[0] == '/')
187 /* Load the module, return handle for it. */
189 __gconv_find_shlib (current
->code
->module_name
);
191 if (shlib_handle
== NULL
)
197 result
[step_cnt
].shlib_handle
= shlib_handle
;
199 result
[step_cnt
].fct
= __gconv_find_func (shlib_handle
, "gconv");
200 if (result
[step_cnt
].fct
== NULL
)
202 /* Argh, no conversion function. There is something
204 __gconv_release_shlib (result
[step_cnt
].shlib_handle
);
209 result
[step_cnt
].init_fct
= __gconv_find_func (shlib_handle
,
211 result
[step_cnt
].end_fct
= __gconv_find_func (shlib_handle
,
215 /* It's a builtin transformation. */
216 __gconv_get_builtin_trans (current
->code
->module_name
,
219 current
= current
->last
;
224 /* Something went wrong while initializing the modules. */
225 while (++step_cnt
< *nsteps
)
226 __gconv_release_shlib (result
[step_cnt
].shlib_handle
);
229 status
= GCONV_NOCONV
;
242 /* The main function: find a possible derivation from the `fromset' (either
243 the given name or the alias) to the `toset' (again with alias). */
246 find_derivation (const char *toset
, const char *toset_expand
,
247 const char *fromset
, const char *fromset_expand
,
248 struct gconv_step
**handle
, size_t *nsteps
)
250 __libc_lock_define_initialized (static, lock
)
251 struct derivation_step
*first
, *current
, **lastp
, *best
= NULL
;
255 result
= derivation_lookup (fromset_expand
?: fromset
, toset_expand
?: toset
,
257 if (result
== GCONV_OK
)
260 __libc_lock_lock (lock
);
262 /* There is a small chance that this derivation is meanwhile found. This
263 can happen if in `find_derivation' we look for this derivation, didn't
264 find it but at the same time another thread looked for this derivation. */
265 result
= derivation_lookup (fromset_expand
?: fromset
, toset_expand
?: toset
,
267 if (result
== GCONV_OK
)
271 For now we use a simple algorithm with quadratic runtime behaviour.
272 The task is to match the `toset' with any of the available. */
273 if (fromset_expand
!= NULL
)
275 first
= NEW_STEP (fromset_expand
, NULL
, NULL
);
276 first
->next
= NEW_STEP (fromset
, NULL
, NULL
);
277 lastp
= &first
->next
->next
;
281 first
= NEW_STEP (fromset
, NULL
, NULL
);
282 lastp
= &first
->next
;
286 while (current
!= NULL
)
288 /* Now match all the available module specifications against the
289 current charset name. If any of them matches check whether
290 we already have a derivation for this charset. If yes, use the
291 one with the lower costs. Otherwise add the new charset at the
295 for (cnt
= 0; cnt
< __gconv_nmodules
; ++cnt
)
297 const char *result_set
= NULL
;
299 if (__gconv_modules_db
[cnt
]->from_pattern
== NULL
)
301 if (__strcasecmp (current
->result_set
,
302 __gconv_modules_db
[cnt
]->from_constpfx
) == 0)
304 if (strcmp (__gconv_modules_db
[cnt
]->to_string
, "-") == 0)
305 result_set
= toset_expand
?: toset
;
307 result_set
= __gconv_modules_db
[cnt
]->to_string
;
311 /* We have a regular expression. First see if the prefix
313 if (__strncasecmp (current
->result_set
,
314 __gconv_modules_db
[cnt
]->from_constpfx
,
315 __gconv_modules_db
[cnt
]->from_constpfx_len
)
318 /* First compile the regex if not already done. */
319 if (__gconv_modules_db
[cnt
]->from_regex
== NULL
)
321 regex_t
*newp
= (regex_t
*) malloc (sizeof (regex_t
));
323 if (regcomp (newp
, __gconv_modules_db
[cnt
]->from_pattern
,
324 REG_EXTENDED
| REG_ICASE
) != 0)
326 /* Something is wrong. Remember this. */
328 __gconv_modules_db
[cnt
]->from_regex
= (regex_t
*) -1L;
331 __gconv_modules_db
[cnt
]->from_regex
= newp
;
334 if (__gconv_modules_db
[cnt
]->from_regex
!= (regex_t
*) -1L)
336 /* Try to match the from name. */
339 if (regexec (__gconv_modules_db
[cnt
]->from_regex
,
340 current
->result_set
, 4, match
, 0) == 0
341 && match
[0].rm_so
== 0
342 && current
->result_set
[match
[0].rm_eo
] == '\0')
344 /* At least the whole <from> string is matched.
345 We must now match sed-like possible
346 subexpressions from the match to the
348 #define ENSURE_LEN(LEN) \
349 if (wp + (LEN) >= constr + len - 1) \
351 char *newp = alloca (len += 128); \
352 memcpy (newp, constr, wp - constr); \
353 wp = newp + (wp - constr); \
357 char *constr
= alloca (len
);
359 const char *cp
= __gconv_modules_db
[cnt
]->to_string
;
368 else if (cp
[1] == '\0')
369 /* Backslash at end of string. */
379 else if (*cp
< '1' || *cp
> '3')
384 if (match
[idx
].rm_so
== -1)
388 ENSURE_LEN (match
[idx
].rm_eo
391 ¤t
->result_set
[match
[idx
].rm_so
],
398 if (*cp
== '\0' && wp
!= constr
)
400 /* Terminate the constructed string. */
408 if (result_set
!= NULL
)
410 /* We managed to find a derivation. First see whether
411 this is what we are looking for. */
412 if (__strcasecmp (result_set
, toset
) == 0
413 || (toset_expand
!= NULL
414 && __strcasecmp (result_set
, toset_expand
) == 0))
416 /* Determine the costs. If they are lower than the
417 previous solution (or this is the first solution)
418 remember this solution. */
419 int cost
= __gconv_modules_db
[cnt
]->cost
;
420 struct derivation_step
*runp
= current
;
421 while (runp
->code
!= NULL
)
423 cost
+= runp
->code
->cost
;
426 if (best
== NULL
|| cost
< best_cost
)
428 best
= NEW_STEP (result_set
, __gconv_modules_db
[cnt
],
435 /* Append at the end if there is no entry with this name. */
436 struct derivation_step
*runp
= first
;
440 if (__strcasecmp (result_set
, runp
->result_set
) == 0)
447 *lastp
= NEW_STEP (result_set
, __gconv_modules_db
[cnt
],
449 lastp
= &(*lastp
)->next
;
455 /* Go on with the next entry. */
456 current
= current
->next
;
460 /* We really found a way to do the transformation. Now build a data
461 structure describing the transformation steps.*/
462 result
= gen_steps (best
, toset_expand
?: toset
, fromset_expand
?: fromset
,
466 /* We haven't found a transformation. Clear the result values. */
471 /* Add result in any case to list of known derivations. */
472 add_derivation (fromset_expand
?: fromset
, toset_expand
?: toset
,
475 __libc_lock_unlock (lock
);
482 __gconv_find_transform (const char *toset
, const char *fromset
,
483 struct gconv_step
**handle
, size_t *nsteps
)
485 __libc_once_define (static, once
);
486 const char *fromset_expand
= NULL
;
487 const char *toset_expand
= NULL
;
490 /* Ensure that the configuration data is read. */
491 __libc_once (once
, __gconv_read_conf
);
493 /* If we don't have a module database return with an error. */
494 if (__gconv_modules_db
== NULL
)
497 /* See whether the names are aliases. */
498 if (__gconv_alias_db
!= NULL
)
500 struct gconv_alias key
;
501 struct gconv_alias
**found
;
503 key
.fromname
= fromset
;
504 found
= __tfind (&key
, &__gconv_alias_db
, __gconv_alias_compare
);
505 fromset_expand
= found
!= NULL
? (*found
)->toname
: NULL
;
507 key
.fromname
= toset
;
508 found
= __tfind (&key
, &__gconv_alias_db
, __gconv_alias_compare
);
509 toset_expand
= found
!= NULL
? (*found
)->toname
: NULL
;
512 result
= find_derivation (toset
, toset_expand
, fromset
, fromset_expand
,
515 /* The following code is necessary since `find_derivation' will return
516 GCONV_OK even when no derivation was found but the same request
517 was processed before. I.e., negative results will also be cached. */
518 return (result
== GCONV_OK
519 ? (*handle
== NULL
? GCONV_NOCONV
: GCONV_OK
)
524 /* Release the entries of the modules list. */
526 __gconv_close_transform (struct gconv_step
*steps
, size_t nsteps
)
528 int result
= GCONV_OK
;
531 if (steps
[nsteps
].shlib_handle
!= NULL
)
533 result
= __gconv_release_shlib (steps
[nsteps
].shlib_handle
);
534 if (result
!= GCONV_OK
)
542 /* Free all resources if necessary. */
543 static void __attribute__ ((unused
))
548 if (__gconv_alias_db
!= NULL
)
549 __tdestroy (__gconv_alias_db
, free
);
551 for (cnt
= 0; cnt
< __gconv_nmodules
; ++cnt
)
553 if (__gconv_modules_db
[cnt
]->from_regex
!= NULL
)
554 regfree ((regex_t
*) __gconv_modules_db
[cnt
]->from_regex
);
556 /* Modules which names do not start with a slash are builtin
557 transformations and the memory is not allocated dynamically. */
558 if (__gconv_modules_db
[cnt
]->module_name
[0] == '/')
559 free (__gconv_modules_db
[cnt
]);
562 if (known_derivations
!= NULL
)
563 __tdestroy (known_derivations
, free_derivation
);
566 text_set_element (__libc_subfreeres
, free_mem
);