Replace __gconv_alias_db, __gconv_modules_db, and __gconv_cache with
[kopensolaris-gnu/glibc.git] / iconv / gconv_trans.c
1 /* Transliteration using the locale's data.
2    Copyright (C) 2000 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 2000.
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, write to the Free
18    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 #include <assert.h>
22 #include <dlfcn.h>
23 #include <search.h>
24 #include <stdint.h>
25 #include <string.h>
26 #include <stdlib.h>
27
28 #include <bits/libc-lock.h>
29 #include "gconv_int.h"
30 #include "../locale/localeinfo.h"
31
32
33 int
34 __gconv_transliterate (struct __gconv_step *step,
35                        struct __gconv_step_data *step_data,
36                        void *trans_data __attribute__ ((unused)),
37                        const unsigned char *inbufstart,
38                        const unsigned char **inbufp,
39                        const unsigned char *inbufend,
40                        unsigned char **outbufstart, size_t *irreversible)
41 {
42   /* Find out about the locale's transliteration.  */
43   uint_fast32_t size;
44   const uint32_t *from_idx;
45   const uint32_t *from_tbl;
46   const uint32_t *to_idx;
47   const uint32_t *to_tbl;
48   const uint32_t *winbuf;
49   const uint32_t *winbufend;
50   uint_fast32_t low;
51   uint_fast32_t high;
52
53   /* The input buffer.  There are actually 4-byte values.  */
54   winbuf = (const uint32_t *) *inbufp;
55   winbufend = (const uint32_t *) inbufend;
56
57   /* If there is no transliteration information in the locale don't do
58      anything and return the error.  */
59   size = _NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_TRANSLIT_TAB_SIZE);
60   if (size == 0)
61     goto no_rules;
62
63   /* Get the rest of the values.  */
64   from_idx =
65     (const uint32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TRANSLIT_FROM_IDX);
66   from_tbl =
67     (const uint32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TRANSLIT_FROM_TBL);
68   to_idx =
69     (const uint32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TRANSLIT_TO_IDX);
70   to_tbl =
71     (const uint32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TRANSLIT_TO_TBL);
72
73   /* Test whether there is enough input.  */
74   if (winbuf + 1 > winbufend)
75     return (winbuf == winbufend
76             ? __GCONV_EMPTY_INPUT : __GCONV_INCOMPLETE_INPUT);
77
78   /* The array starting at FROM_IDX contains indeces to the string table
79      in FROM_TBL.  The indeces are sorted wrt to the strings.  I.e., we
80      are doing binary search.  */
81   low = 0;
82   high = size;
83   while (low < high)
84     {
85       uint_fast32_t med = (low + high) / 2;
86       uint32_t idx;
87       int cnt;
88
89       /* Compare the string at this index with the string at the current
90          position in the input buffer.  */
91       idx = from_idx[med];
92       cnt = 0;
93       do
94         {
95           if (from_tbl[idx + cnt] != winbuf[cnt])
96             /* Does not match.  */
97             break;
98           ++cnt;
99         }
100       while (from_tbl[idx + cnt] != L'\0' && winbuf + cnt < winbufend);
101
102       if (cnt > 0 && from_tbl[idx + cnt] == L'\0')
103         {
104           /* Found a matching input sequence.  Now try to convert the
105              possible replacements.  */
106           uint32_t idx2 = to_idx[med];
107
108           do
109             {
110               /* Determine length of replacement.  */
111               uint_fast32_t len = 0;
112               int res;
113               const unsigned char *toinptr;
114               unsigned char *outptr;
115
116               while (to_tbl[idx2 + len] != L'\0')
117                 ++len;
118
119               /* Try this input text.  */
120               toinptr = (const unsigned char *) &to_tbl[idx2];
121               outptr = *outbufstart;
122               res = DL_CALL_FCT (step->__fct,
123                                  (step, step_data, &toinptr,
124                                   (const unsigned char *) &to_tbl[idx2 + len],
125                                   &outptr, NULL, 0, 0));
126               if (res != __GCONV_ILLEGAL_INPUT)
127                 {
128                   /* If the conversion succeeds we have to increment the
129                      input buffer.  */
130                   if (res == __GCONV_EMPTY_INPUT)
131                     {
132                       *inbufp += cnt * sizeof (uint32_t);
133                       ++*irreversible;
134                       res = __GCONV_OK;
135                     }
136                   *outbufstart = outptr;
137
138                   return res;
139                 }
140
141               /* Next replacement.  */
142               idx2 += len + 1;
143             }
144           while (to_tbl[idx2] != L'\0');
145
146           /* Nothing found, continue searching.  */
147         }
148       else if (cnt > 0)
149         /* This means that the input buffer contents matches a prefix of
150            an entry.  Since we cannot match it unless we get more input,
151            we will tell the caller about it.  */
152         return __GCONV_INCOMPLETE_INPUT;
153
154       if (winbuf + cnt >= winbufend || from_tbl[idx + cnt] < winbuf[cnt])
155         low = med + 1;
156       else
157         high = med;
158     }
159
160  no_rules:
161   /* Maybe the character is supposed to be ignored.  */
162   if (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_TRANSLIT_IGNORE_LEN) != 0)
163     {
164       int n = _NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_TRANSLIT_IGNORE_LEN);
165       const uint32_t *ranges =
166         (const uint32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TRANSLIT_IGNORE);
167       const uint32_t wc = *(const uint32_t *) (*inbufp);
168       int i;
169
170       /* Test whether there is enough input.  */
171       if (winbuf + 1 > winbufend)
172         return (winbuf == winbufend
173                 ? __GCONV_EMPTY_INPUT : __GCONV_INCOMPLETE_INPUT);
174
175       for (i = 0; i < n; ranges += 3, ++i)
176         if (ranges[0] <= wc && wc <= ranges[1]
177             && (wc - ranges[0]) % ranges[2] == 0)
178           {
179             /* Matches the range.  Ignore it.  */
180             *inbufp += 4;
181             ++*irreversible;
182             return __GCONV_OK;
183           }
184         else if (wc < ranges[0])
185           /* There cannot be any other matching range since they are
186              sorted.  */
187           break;
188     }
189
190   /* One last chance: use the default replacement.  */
191   if (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_TRANSLIT_DEFAULT_MISSING_LEN) != 0)
192     {
193       const uint32_t *default_missing = (const uint32_t *)
194         _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TRANSLIT_DEFAULT_MISSING);
195       const unsigned char *toinptr = (const unsigned char *) default_missing;
196       uint32_t len = _NL_CURRENT_WORD (LC_CTYPE,
197                                        _NL_CTYPE_TRANSLIT_DEFAULT_MISSING_LEN);
198       unsigned char *outptr;
199       int res;
200
201       /* Test whether there is enough input.  */
202       if (winbuf + 1 > winbufend)
203         return (winbuf == winbufend
204                 ? __GCONV_EMPTY_INPUT : __GCONV_INCOMPLETE_INPUT);
205
206       outptr = *outbufstart;
207       res = DL_CALL_FCT (step->__fct,
208                          (step, step_data, &toinptr,
209                           (const unsigned char *) (default_missing + len),
210                           &outptr, NULL, 0, 0));
211
212       if (res != __GCONV_ILLEGAL_INPUT)
213         {
214           /* If the conversion succeeds we have to increment the
215              input buffer.  */
216           if (res == __GCONV_EMPTY_INPUT)
217             {
218               /* This worked but is not reversible.  */
219               ++*irreversible;
220               *inbufp += 4;
221               res = __GCONV_OK;
222             }
223           *outbufstart = outptr;
224
225           return res;
226         }
227     }
228
229   /* Haven't found a match.  */
230   return __GCONV_ILLEGAL_INPUT;
231 }
232
233
234 /* Structure to represent results of found (or not) transliteration
235    modules.  */
236 struct known_trans
237 {
238   /* This structure must remain the first member.  */
239   struct trans_struct info;
240
241   char *fname;
242   void *handle;
243   int open_count;
244 };
245
246
247 /* Tree with results of previous calls to __gconv_translit_find.  */
248 static void *search_tree;
249
250 /* We modify global data.   */
251 __libc_lock_define_initialized (static, lock);
252
253
254 /* Compare two transliteration entries.  */
255 static int
256 trans_compare (const void *p1, const void *p2)
257 {
258   const struct known_trans *s1 = (const struct known_trans *) p1;
259   const struct known_trans *s2 = (const struct known_trans *) p2;
260
261   return strcmp (s1->info.name, s2->info.name);
262 }
263
264
265 /* Open (maybe reopen) the module named in the struct.  Get the function
266    and data structure pointers we need.  */
267 static int
268 open_translit (struct known_trans *trans)
269 {
270   __gconv_trans_query_fct queryfct;
271
272   trans->handle = __libc_dlopen (trans->fname);
273   if (trans->handle == NULL)
274     /* Not available.  */
275     return 1;
276
277   /* Find the required symbol.  */
278   queryfct = __libc_dlsym (trans->handle, "gconv_trans_context");
279   if (queryfct == NULL)
280     {
281       /* We cannot live with that.  */
282     close_and_out:
283       __libc_dlclose (trans->handle);
284       trans->handle = NULL;
285       return 1;
286     }
287
288   /* Get the context.  */
289   if (queryfct (trans->info.name, &trans->info.csnames, &trans->info.ncsnames)
290       != 0)
291     goto close_and_out;
292
293   /* Of course we also have to have the actual function.  */
294   trans->info.trans_fct = __libc_dlsym (trans->handle, "gconv_trans");
295   if (trans->info.trans_fct == NULL)
296     goto close_and_out;
297
298   /* Now the optional functions.  */
299   trans->info.trans_init_fct =
300     __libc_dlsym (trans->handle, "gconv_trans_init");
301   trans->info.trans_context_fct =
302     __libc_dlsym (trans->handle, "gconv_trans_context");
303   trans->info.trans_end_fct =
304     __libc_dlsym (trans->handle, "gconv_trans_end");
305
306   trans->open_count = 1;
307
308   return 0;
309 }
310
311
312 int
313 internal_function
314 __gconv_translit_find (struct trans_struct *trans)
315 {
316   struct known_trans **found;
317   const struct path_elem *runp;
318   int res = 1;
319
320   /* We have to have a name.  */
321   assert (trans->name != NULL);
322
323   /* Acquire the lock.  */
324   __libc_lock_lock (lock);
325
326   /* See whether we know this module already.  */
327   found = __tfind (trans, &search_tree, trans_compare);
328   if (found != NULL)
329     {
330       /* Is this module available?  */
331       if ((*found)->handle != NULL)
332         {
333           /* Maybe we have to reopen the file.  */
334           if ((*found)->handle != (void *) -1)
335             /* The object is not unloaded.  */
336             res = 0;
337           else if (open_translit (*found) == 0)
338             {
339               /* Copy the data.  */
340               *trans = (*found)->info;
341               (*found)->open_count++;
342               res = 0;
343             }
344         }
345     }
346   else
347     {
348       size_t name_len = strlen (trans->name) + 1;
349       int need_so = 0;
350       struct known_trans *newp;
351
352       /* We have to continue looking for the module.  */
353       if (__gconv_path_elem == NULL)
354         __gconv_get_path ();
355
356       /* See whether we have to append .so.  */
357       if (name_len <= 4 || memcmp (&trans->name[name_len - 4], ".so", 3) != 0)
358         need_so = 1;
359
360       /* Create a new entry.  */
361       newp = (struct known_trans *) malloc (sizeof (struct known_trans)
362                                             + (__gconv_max_path_elem_len
363                                                + name_len + 3)
364                                             + name_len);
365       if (newp != NULL)
366         {
367           char *cp;
368
369           /* Clear the struct.  */
370           memset (newp, '\0', sizeof (struct known_trans));
371
372           /* Store a copy of the module name.  */
373           newp->info.name = cp = (char *) (newp + 1);
374           cp = __mempcpy (cp, trans->name, name_len);
375
376           newp->fname = cp;
377
378           /* Search in all the directories.  */
379           for (runp = __gconv_path_elem; runp->name != NULL; ++runp)
380             {
381               cp = __mempcpy (__stpcpy ((char *) newp->fname, runp->name),
382                               trans->name, name_len);
383               if (need_so)
384                 memcpy (cp, ".so", sizeof (".so"));
385
386               if (open_translit (newp) == 0)
387                 {
388                   /* We found a module.  */
389                   res = 0;
390                   break;
391                 }
392             }
393
394           if (res)
395             newp->fname = NULL;
396
397           /* In any case we'll add the entry to our search tree.  */
398           if (__tsearch (newp, &search_tree, trans_compare) == NULL)
399             {
400               /* Yickes, this should not happen.  Unload the object.  */
401               res = 1;
402               /* XXX unload here.  */
403             }
404         }
405     }
406
407   __libc_lock_unlock (lock);
408
409   return res;
410 }