Mercurial > pidgin.yaz
comparison intl/dcgettext.c @ 347:d64101dc5c7e
[gaim-migrate @ 357]
I18n, here we come!
committer: Tailor Script <tailor@pidgin.im>
author | Eric Warmenhoven <eric@warmenhoven.org> |
---|---|
date | Wed, 07 Jun 2000 13:30:04 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
346:ec9fe9a58bd7 | 347:d64101dc5c7e |
---|---|
1 /* Implementation of the dcgettext(3) function. | |
2 Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc. | |
3 | |
4 This program is free software; you can redistribute it and/or modify | |
5 it under the terms of the GNU General Public License as published by | |
6 the Free Software Foundation; either version 2, or (at your option) | |
7 any later version. | |
8 | |
9 This program is distributed in the hope that it will be useful, | |
10 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 GNU General Public License for more details. | |
13 | |
14 You should have received a copy of the GNU General Public License | |
15 along with this program; if not, write to the Free Software Foundation, | |
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |
17 | |
18 #ifdef HAVE_CONFIG_H | |
19 # include <config.h> | |
20 #endif | |
21 | |
22 #include <sys/types.h> | |
23 | |
24 #if defined __GNUC__ && !defined C_ALLOCA | |
25 # define alloca __builtin_alloca | |
26 # define HAVE_ALLOCA 1 | |
27 #else | |
28 # if (defined HAVE_ALLOCA_H || defined _LIBC) && !defined C_ALLOCA | |
29 # include <alloca.h> | |
30 # else | |
31 # ifdef _AIX | |
32 #pragma alloca | |
33 # else | |
34 # ifndef alloca | |
35 char *alloca (); | |
36 # endif | |
37 # endif | |
38 # endif | |
39 #endif | |
40 | |
41 #include <errno.h> | |
42 #ifndef errno | |
43 extern int errno; | |
44 #endif | |
45 #ifndef __set_errno | |
46 # define __set_errno(val) errno = (val) | |
47 #endif | |
48 | |
49 #if defined STDC_HEADERS || defined _LIBC | |
50 # include <stdlib.h> | |
51 #else | |
52 char *getenv (); | |
53 # ifdef HAVE_MALLOC_H | |
54 # include <malloc.h> | |
55 # else | |
56 void free (); | |
57 # endif | |
58 #endif | |
59 | |
60 #if defined HAVE_STRING_H || defined _LIBC | |
61 # ifndef _GNU_SOURCE | |
62 # define _GNU_SOURCE 1 | |
63 # endif | |
64 # include <string.h> | |
65 #else | |
66 # include <strings.h> | |
67 #endif | |
68 #if !HAVE_STRCHR && !defined _LIBC | |
69 # ifndef strchr | |
70 # define strchr index | |
71 # endif | |
72 #endif | |
73 | |
74 #if defined HAVE_UNISTD_H || defined _LIBC | |
75 # include <unistd.h> | |
76 #endif | |
77 | |
78 #include "gettext.h" | |
79 #include "gettextP.h" | |
80 #ifdef _LIBC | |
81 # include <libintl.h> | |
82 #else | |
83 # include "libgettext.h" | |
84 #endif | |
85 #include "hash-string.h" | |
86 | |
87 /* @@ end of prolog @@ */ | |
88 | |
89 #ifdef _LIBC | |
90 /* Rename the non ANSI C functions. This is required by the standard | |
91 because some ANSI C functions will require linking with this object | |
92 file and the name space must not be polluted. */ | |
93 # define getcwd __getcwd | |
94 # ifndef stpcpy | |
95 # define stpcpy __stpcpy | |
96 # endif | |
97 #else | |
98 # if !defined HAVE_GETCWD | |
99 char *getwd (); | |
100 # define getcwd(buf, max) getwd (buf) | |
101 # else | |
102 char *getcwd (); | |
103 # endif | |
104 # ifndef HAVE_STPCPY | |
105 static char *stpcpy PARAMS ((char *dest, const char *src)); | |
106 # endif | |
107 #endif | |
108 | |
109 /* Amount to increase buffer size by in each try. */ | |
110 #define PATH_INCR 32 | |
111 | |
112 /* The following is from pathmax.h. */ | |
113 /* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define | |
114 PATH_MAX but might cause redefinition warnings when sys/param.h is | |
115 later included (as on MORE/BSD 4.3). */ | |
116 #if defined(_POSIX_VERSION) || (defined(HAVE_LIMITS_H) && !defined(__GNUC__)) | |
117 # include <limits.h> | |
118 #endif | |
119 | |
120 #ifndef _POSIX_PATH_MAX | |
121 # define _POSIX_PATH_MAX 255 | |
122 #endif | |
123 | |
124 #if !defined(PATH_MAX) && defined(_PC_PATH_MAX) | |
125 # define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf ("/", _PC_PATH_MAX)) | |
126 #endif | |
127 | |
128 /* Don't include sys/param.h if it already has been. */ | |
129 #if defined(HAVE_SYS_PARAM_H) && !defined(PATH_MAX) && !defined(MAXPATHLEN) | |
130 # include <sys/param.h> | |
131 #endif | |
132 | |
133 #if !defined(PATH_MAX) && defined(MAXPATHLEN) | |
134 # define PATH_MAX MAXPATHLEN | |
135 #endif | |
136 | |
137 #ifndef PATH_MAX | |
138 # define PATH_MAX _POSIX_PATH_MAX | |
139 #endif | |
140 | |
141 /* XPG3 defines the result of `setlocale (category, NULL)' as: | |
142 ``Directs `setlocale()' to query `category' and return the current | |
143 setting of `local'.'' | |
144 However it does not specify the exact format. And even worse: POSIX | |
145 defines this not at all. So we can use this feature only on selected | |
146 system (e.g. those using GNU C Library). */ | |
147 #ifdef _LIBC | |
148 # define HAVE_LOCALE_NULL | |
149 #endif | |
150 | |
151 /* Name of the default domain used for gettext(3) prior any call to | |
152 textdomain(3). The default value for this is "messages". */ | |
153 const char _nl_default_default_domain[] = "messages"; | |
154 | |
155 /* Value used as the default domain for gettext(3). */ | |
156 const char *_nl_current_default_domain = _nl_default_default_domain; | |
157 | |
158 /* Contains the default location of the message catalogs. */ | |
159 const char _nl_default_dirname[] = GNULOCALEDIR; | |
160 | |
161 /* List with bindings of specific domains created by bindtextdomain() | |
162 calls. */ | |
163 struct binding *_nl_domain_bindings; | |
164 | |
165 /* Prototypes for local functions. */ | |
166 static char *find_msg PARAMS ((struct loaded_l10nfile *domain_file, | |
167 const char *msgid)) internal_function; | |
168 static const char *category_to_name PARAMS ((int category)) internal_function; | |
169 static const char *guess_category_value PARAMS ((int category, | |
170 const char *categoryname)) | |
171 internal_function; | |
172 | |
173 | |
174 /* For those loosing systems which don't have `alloca' we have to add | |
175 some additional code emulating it. */ | |
176 #ifdef HAVE_ALLOCA | |
177 /* Nothing has to be done. */ | |
178 # define ADD_BLOCK(list, address) /* nothing */ | |
179 # define FREE_BLOCKS(list) /* nothing */ | |
180 #else | |
181 struct block_list | |
182 { | |
183 void *address; | |
184 struct block_list *next; | |
185 }; | |
186 # define ADD_BLOCK(list, addr) \ | |
187 do { \ | |
188 struct block_list *newp = (struct block_list *) malloc (sizeof (*newp)); \ | |
189 /* If we cannot get a free block we cannot add the new element to \ | |
190 the list. */ \ | |
191 if (newp != NULL) { \ | |
192 newp->address = (addr); \ | |
193 newp->next = (list); \ | |
194 (list) = newp; \ | |
195 } \ | |
196 } while (0) | |
197 # define FREE_BLOCKS(list) \ | |
198 do { \ | |
199 while (list != NULL) { \ | |
200 struct block_list *old = list; \ | |
201 list = list->next; \ | |
202 free (old); \ | |
203 } \ | |
204 } while (0) | |
205 # undef alloca | |
206 # define alloca(size) (malloc (size)) | |
207 #endif /* have alloca */ | |
208 | |
209 | |
210 /* Names for the libintl functions are a problem. They must not clash | |
211 with existing names and they should follow ANSI C. But this source | |
212 code is also used in GNU C Library where the names have a __ | |
213 prefix. So we have to make a difference here. */ | |
214 #ifdef _LIBC | |
215 # define DCGETTEXT __dcgettext | |
216 #else | |
217 # define DCGETTEXT dcgettext__ | |
218 #endif | |
219 | |
220 /* Checking whether the binaries runs SUID must be done and glibc provides | |
221 easier methods therefore we make a difference here. */ | |
222 #ifdef _LIBC | |
223 # define ENABLE_SECURE __libc_enable_secure | |
224 # define DETERMINE_SECURE | |
225 #else | |
226 static int enable_secure; | |
227 # define ENABLE_SECURE (enable_secure == 1) | |
228 # define DETERMINE_SECURE \ | |
229 if (enable_secure == 0) \ | |
230 { \ | |
231 if (getuid () != geteuid () || getgid () != getegid ()) \ | |
232 enable_secure = 1; \ | |
233 else \ | |
234 enable_secure = -1; \ | |
235 } | |
236 #endif | |
237 | |
238 /* Look up MSGID in the DOMAINNAME message catalog for the current CATEGORY | |
239 locale. */ | |
240 char * | |
241 DCGETTEXT (domainname, msgid, category) | |
242 const char *domainname; | |
243 const char *msgid; | |
244 int category; | |
245 { | |
246 #ifndef HAVE_ALLOCA | |
247 struct block_list *block_list = NULL; | |
248 #endif | |
249 struct loaded_l10nfile *domain; | |
250 struct binding *binding; | |
251 const char *categoryname; | |
252 const char *categoryvalue; | |
253 char *dirname, *xdomainname; | |
254 char *single_locale; | |
255 char *retval; | |
256 int saved_errno = errno; | |
257 | |
258 /* If no real MSGID is given return NULL. */ | |
259 if (msgid == NULL) | |
260 return NULL; | |
261 | |
262 /* See whether this is a SUID binary or not. */ | |
263 DETERMINE_SECURE; | |
264 | |
265 /* If DOMAINNAME is NULL, we are interested in the default domain. If | |
266 CATEGORY is not LC_MESSAGES this might not make much sense but the | |
267 definition left this undefined. */ | |
268 if (domainname == NULL) | |
269 domainname = _nl_current_default_domain; | |
270 | |
271 /* First find matching binding. */ | |
272 for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next) | |
273 { | |
274 int compare = strcmp (domainname, binding->domainname); | |
275 if (compare == 0) | |
276 /* We found it! */ | |
277 break; | |
278 if (compare < 0) | |
279 { | |
280 /* It is not in the list. */ | |
281 binding = NULL; | |
282 break; | |
283 } | |
284 } | |
285 | |
286 if (binding == NULL) | |
287 dirname = (char *) _nl_default_dirname; | |
288 else if (binding->dirname[0] == '/') | |
289 dirname = binding->dirname; | |
290 else | |
291 { | |
292 /* We have a relative path. Make it absolute now. */ | |
293 size_t dirname_len = strlen (binding->dirname) + 1; | |
294 size_t path_max; | |
295 char *ret; | |
296 | |
297 path_max = (unsigned int) PATH_MAX; | |
298 path_max += 2; /* The getcwd docs say to do this. */ | |
299 | |
300 dirname = (char *) alloca (path_max + dirname_len); | |
301 ADD_BLOCK (block_list, dirname); | |
302 | |
303 __set_errno (0); | |
304 while ((ret = getcwd (dirname, path_max)) == NULL && errno == ERANGE) | |
305 { | |
306 path_max += PATH_INCR; | |
307 dirname = (char *) alloca (path_max + dirname_len); | |
308 ADD_BLOCK (block_list, dirname); | |
309 __set_errno (0); | |
310 } | |
311 | |
312 if (ret == NULL) | |
313 { | |
314 /* We cannot get the current working directory. Don't signal an | |
315 error but simply return the default string. */ | |
316 FREE_BLOCKS (block_list); | |
317 __set_errno (saved_errno); | |
318 return (char *) msgid; | |
319 } | |
320 | |
321 stpcpy (stpcpy (strchr (dirname, '\0'), "/"), binding->dirname); | |
322 } | |
323 | |
324 /* Now determine the symbolic name of CATEGORY and its value. */ | |
325 categoryname = category_to_name (category); | |
326 categoryvalue = guess_category_value (category, categoryname); | |
327 | |
328 xdomainname = (char *) alloca (strlen (categoryname) | |
329 + strlen (domainname) + 5); | |
330 ADD_BLOCK (block_list, xdomainname); | |
331 | |
332 stpcpy (stpcpy (stpcpy (stpcpy (xdomainname, categoryname), "/"), | |
333 domainname), | |
334 ".mo"); | |
335 | |
336 /* Creating working area. */ | |
337 single_locale = (char *) alloca (strlen (categoryvalue) + 1); | |
338 ADD_BLOCK (block_list, single_locale); | |
339 | |
340 | |
341 /* Search for the given string. This is a loop because we perhaps | |
342 got an ordered list of languages to consider for the translation. */ | |
343 while (1) | |
344 { | |
345 /* Make CATEGORYVALUE point to the next element of the list. */ | |
346 while (categoryvalue[0] != '\0' && categoryvalue[0] == ':') | |
347 ++categoryvalue; | |
348 if (categoryvalue[0] == '\0') | |
349 { | |
350 /* The whole contents of CATEGORYVALUE has been searched but | |
351 no valid entry has been found. We solve this situation | |
352 by implicitly appending a "C" entry, i.e. no translation | |
353 will take place. */ | |
354 single_locale[0] = 'C'; | |
355 single_locale[1] = '\0'; | |
356 } | |
357 else | |
358 { | |
359 char *cp = single_locale; | |
360 while (categoryvalue[0] != '\0' && categoryvalue[0] != ':') | |
361 *cp++ = *categoryvalue++; | |
362 *cp = '\0'; | |
363 | |
364 /* When this is a SUID binary we must not allow accessing files | |
365 outside the dedicated directories. */ | |
366 if (ENABLE_SECURE | |
367 && (memchr (single_locale, '/', | |
368 _nl_find_language (single_locale) - single_locale) | |
369 != NULL)) | |
370 /* Ingore this entry. */ | |
371 continue; | |
372 } | |
373 | |
374 /* If the current locale value is C (or POSIX) we don't load a | |
375 domain. Return the MSGID. */ | |
376 if (strcmp (single_locale, "C") == 0 | |
377 || strcmp (single_locale, "POSIX") == 0) | |
378 { | |
379 FREE_BLOCKS (block_list); | |
380 __set_errno (saved_errno); | |
381 return (char *) msgid; | |
382 } | |
383 | |
384 | |
385 /* Find structure describing the message catalog matching the | |
386 DOMAINNAME and CATEGORY. */ | |
387 domain = _nl_find_domain (dirname, single_locale, xdomainname); | |
388 | |
389 if (domain != NULL) | |
390 { | |
391 retval = find_msg (domain, msgid); | |
392 | |
393 if (retval == NULL) | |
394 { | |
395 int cnt; | |
396 | |
397 for (cnt = 0; domain->successor[cnt] != NULL; ++cnt) | |
398 { | |
399 retval = find_msg (domain->successor[cnt], msgid); | |
400 | |
401 if (retval != NULL) | |
402 break; | |
403 } | |
404 } | |
405 | |
406 if (retval != NULL) | |
407 { | |
408 FREE_BLOCKS (block_list); | |
409 __set_errno (saved_errno); | |
410 return retval; | |
411 } | |
412 } | |
413 } | |
414 /* NOTREACHED */ | |
415 } | |
416 | |
417 #ifdef _LIBC | |
418 /* Alias for function name in GNU C Library. */ | |
419 weak_alias (__dcgettext, dcgettext); | |
420 #endif | |
421 | |
422 | |
423 static char * | |
424 internal_function | |
425 find_msg (domain_file, msgid) | |
426 struct loaded_l10nfile *domain_file; | |
427 const char *msgid; | |
428 { | |
429 size_t act = 0; | |
430 size_t top, bottom; | |
431 struct loaded_domain *domain; | |
432 | |
433 if (domain_file->decided == 0) | |
434 _nl_load_domain (domain_file); | |
435 | |
436 if (domain_file->data == NULL) | |
437 return NULL; | |
438 | |
439 domain = (struct loaded_domain *) domain_file->data; | |
440 | |
441 /* Locate the MSGID and its translation. */ | |
442 if (domain->hash_size > 2 && domain->hash_tab != NULL) | |
443 { | |
444 /* Use the hashing table. */ | |
445 nls_uint32 len = strlen (msgid); | |
446 nls_uint32 hash_val = hash_string (msgid); | |
447 nls_uint32 idx = hash_val % domain->hash_size; | |
448 nls_uint32 incr = 1 + (hash_val % (domain->hash_size - 2)); | |
449 nls_uint32 nstr = W (domain->must_swap, domain->hash_tab[idx]); | |
450 | |
451 if (nstr == 0) | |
452 /* Hash table entry is empty. */ | |
453 return NULL; | |
454 | |
455 if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len | |
456 && strcmp (msgid, | |
457 domain->data + W (domain->must_swap, | |
458 domain->orig_tab[nstr - 1].offset)) == 0) | |
459 return (char *) domain->data + W (domain->must_swap, | |
460 domain->trans_tab[nstr - 1].offset); | |
461 | |
462 while (1) | |
463 { | |
464 if (idx >= domain->hash_size - incr) | |
465 idx -= domain->hash_size - incr; | |
466 else | |
467 idx += incr; | |
468 | |
469 nstr = W (domain->must_swap, domain->hash_tab[idx]); | |
470 if (nstr == 0) | |
471 /* Hash table entry is empty. */ | |
472 return NULL; | |
473 | |
474 if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len | |
475 && strcmp (msgid, | |
476 domain->data + W (domain->must_swap, | |
477 domain->orig_tab[nstr - 1].offset)) | |
478 == 0) | |
479 return (char *) domain->data | |
480 + W (domain->must_swap, domain->trans_tab[nstr - 1].offset); | |
481 } | |
482 /* NOTREACHED */ | |
483 } | |
484 | |
485 /* Now we try the default method: binary search in the sorted | |
486 array of messages. */ | |
487 bottom = 0; | |
488 top = domain->nstrings; | |
489 while (bottom < top) | |
490 { | |
491 int cmp_val; | |
492 | |
493 act = (bottom + top) / 2; | |
494 cmp_val = strcmp (msgid, domain->data | |
495 + W (domain->must_swap, | |
496 domain->orig_tab[act].offset)); | |
497 if (cmp_val < 0) | |
498 top = act; | |
499 else if (cmp_val > 0) | |
500 bottom = act + 1; | |
501 else | |
502 break; | |
503 } | |
504 | |
505 /* If an translation is found return this. */ | |
506 return bottom >= top ? NULL : (char *) domain->data | |
507 + W (domain->must_swap, | |
508 domain->trans_tab[act].offset); | |
509 } | |
510 | |
511 | |
512 /* Return string representation of locale CATEGORY. */ | |
513 static const char * | |
514 internal_function | |
515 category_to_name (category) | |
516 int category; | |
517 { | |
518 const char *retval; | |
519 | |
520 switch (category) | |
521 { | |
522 #ifdef LC_COLLATE | |
523 case LC_COLLATE: | |
524 retval = "LC_COLLATE"; | |
525 break; | |
526 #endif | |
527 #ifdef LC_CTYPE | |
528 case LC_CTYPE: | |
529 retval = "LC_CTYPE"; | |
530 break; | |
531 #endif | |
532 #ifdef LC_MONETARY | |
533 case LC_MONETARY: | |
534 retval = "LC_MONETARY"; | |
535 break; | |
536 #endif | |
537 #ifdef LC_NUMERIC | |
538 case LC_NUMERIC: | |
539 retval = "LC_NUMERIC"; | |
540 break; | |
541 #endif | |
542 #ifdef LC_TIME | |
543 case LC_TIME: | |
544 retval = "LC_TIME"; | |
545 break; | |
546 #endif | |
547 #ifdef LC_MESSAGES | |
548 case LC_MESSAGES: | |
549 retval = "LC_MESSAGES"; | |
550 break; | |
551 #endif | |
552 #ifdef LC_RESPONSE | |
553 case LC_RESPONSE: | |
554 retval = "LC_RESPONSE"; | |
555 break; | |
556 #endif | |
557 #ifdef LC_ALL | |
558 case LC_ALL: | |
559 /* This might not make sense but is perhaps better than any other | |
560 value. */ | |
561 retval = "LC_ALL"; | |
562 break; | |
563 #endif | |
564 default: | |
565 /* If you have a better idea for a default value let me know. */ | |
566 retval = "LC_XXX"; | |
567 } | |
568 | |
569 return retval; | |
570 } | |
571 | |
572 /* Guess value of current locale from value of the environment variables. */ | |
573 static const char * | |
574 internal_function | |
575 guess_category_value (category, categoryname) | |
576 int category; | |
577 const char *categoryname; | |
578 { | |
579 const char *retval; | |
580 | |
581 /* The highest priority value is the `LANGUAGE' environment | |
582 variable. This is a GNU extension. */ | |
583 retval = getenv ("LANGUAGE"); | |
584 if (retval != NULL && retval[0] != '\0') | |
585 return retval; | |
586 | |
587 /* `LANGUAGE' is not set. So we have to proceed with the POSIX | |
588 methods of looking to `LC_ALL', `LC_xxx', and `LANG'. On some | |
589 systems this can be done by the `setlocale' function itself. */ | |
590 #if defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES && defined HAVE_LOCALE_NULL | |
591 return setlocale (category, NULL); | |
592 #else | |
593 /* Setting of LC_ALL overwrites all other. */ | |
594 retval = getenv ("LC_ALL"); | |
595 if (retval != NULL && retval[0] != '\0') | |
596 return retval; | |
597 | |
598 /* Next comes the name of the desired category. */ | |
599 retval = getenv (categoryname); | |
600 if (retval != NULL && retval[0] != '\0') | |
601 return retval; | |
602 | |
603 /* Last possibility is the LANG environment variable. */ | |
604 retval = getenv ("LANG"); | |
605 if (retval != NULL && retval[0] != '\0') | |
606 return retval; | |
607 | |
608 /* We use C as the default domain. POSIX says this is implementation | |
609 defined. */ | |
610 return "C"; | |
611 #endif | |
612 } | |
613 | |
614 /* @@ begin of epilog @@ */ | |
615 | |
616 /* We don't want libintl.a to depend on any other library. So we | |
617 avoid the non-standard function stpcpy. In GNU C Library this | |
618 function is available, though. Also allow the symbol HAVE_STPCPY | |
619 to be defined. */ | |
620 #if !_LIBC && !HAVE_STPCPY | |
621 static char * | |
622 stpcpy (dest, src) | |
623 char *dest; | |
624 const char *src; | |
625 { | |
626 while ((*dest++ = *src++) != '\0') | |
627 /* Do nothing. */ ; | |
628 return dest - 1; | |
629 } | |
630 #endif | |
631 | |
632 | |
633 #ifdef _LIBC | |
634 /* If we want to free all resources we have to do some work at | |
635 program's end. */ | |
636 static void __attribute__ ((unused)) | |
637 free_mem (void) | |
638 { | |
639 struct binding *runp; | |
640 | |
641 for (runp = _nl_domain_bindings; runp != NULL; runp = runp->next) | |
642 { | |
643 free (runp->domainname); | |
644 if (runp->dirname != _nl_default_dirname) | |
645 /* Yes, this is a pointer comparison. */ | |
646 free (runp->dirname); | |
647 } | |
648 | |
649 if (_nl_current_default_domain != _nl_default_default_domain) | |
650 /* Yes, again a pointer comparison. */ | |
651 free ((char *) _nl_current_default_domain); | |
652 } | |
653 | |
654 text_set_element (__libc_subfreeres, free_mem); | |
655 #endif |