1 /* Implementation of the internal dcigettext function.
2 Copyright (C) 1995-1999, 2000, 2001 Free Software Foundation, Inc.
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)
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.
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. */
18 /* Tell glibc's <string.h> to provide a prototype for mempcpy().
19 This must come before <config.h> because <config.h> may include
20 <features.h>, and once <features.h> has been included, it's too late. */
29 #include <sys/types.h>
31 #if defined HAVE_ALLOCA_H
38 void *alloca(size_t size
);
53 #if defined HAVE_UNISTD_H
57 #if defined HAVE_SYS_PARAM_H
58 #include <sys/param.h>
62 #include <malloc.h> /* Needed for alloca() on MingW/Win32 */
67 #include "intl/gettext/gettextP.h"
68 #include "intl/gettext/libintl.h"
69 #include "intl/gettext/hash-string.h"
70 #include "util/string.h"
72 /* Amount to increase buffer size by in each try. */
75 /* The following is from pathmax.h. */
76 /* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define
77 PATH_MAX but might cause redefinition warnings when sys/param.h is
78 later included (as on MORE/BSD 4.3). */
79 #if defined _POSIX_VERSION || defined HAVE_LIMITS_H
83 #ifndef _POSIX_PATH_MAX
84 #define _POSIX_PATH_MAX 255
87 #if !defined PATH_MAX && defined _PC_PATH_MAX
88 #define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf ("/", _PC_PATH_MAX))
91 /* Don't include sys/param.h if it already has been. */
92 #if defined HAVE_SYS_PARAM_H && !defined PATH_MAX && !defined MAXPATHLEN
93 #include <sys/param.h>
96 #if !defined PATH_MAX && defined MAXPATHLEN
97 #define PATH_MAX MAXPATHLEN
101 #define PATH_MAX _POSIX_PATH_MAX
105 IS_ABSOLUTE_PATH(P) tests whether P is an absolute path. If it is not,
106 it may be concatenated to a directory pathname.
107 IS_PATH_WITH_DIR(P) tests whether P contains a directory specification.
109 #if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
110 /* Win32, OS/2, DOS */
111 #define HAS_DEVICE(P) (isasciialpha((P)[0]) && (P)[1] == ':')
112 #define IS_ABSOLUTE_PATH(P) (dir_sep((P)[0]) || HAS_DEVICE (P))
113 #define IS_PATH_WITH_DIR(P) (strchr ((const char *)(P), '/') || strchr ((const char *)(P), '\\') || HAS_DEVICE (P))
116 #define IS_ABSOLUTE_PATH(P) dir_sep((P)[0])
117 #define IS_PATH_WITH_DIR(P) strchr ((const char *)(P), '/')
120 /* XPG3 defines the result of `setlocale (category, NULL)' as:
121 ``Directs `setlocale()' to query `category' and return the current
122 setting of `local'.''
123 However it does not specify the exact format. Neither do SUSV2 and
124 ISO C 99. So we can use this feature only on selected systems (e.g.
125 those using GNU C Library). */
126 #if (defined __GNU_LIBRARY__ && __GNU_LIBRARY__ >= 2)
127 #define HAVE_LOCALE_NULL
130 /* This is the type used for the search tree where known translations
132 struct known_translation_t
{
133 /* Domain in which to search. */
134 unsigned char *domainname
;
139 /* State of the catalog counter at the point the string was found. */
142 /* Catalog where the string was found. */
143 struct loaded_l10nfile
*domain
;
145 /* And finally the translation. */
146 const unsigned char *translation
;
147 size_t translation_length
;
149 /* Pointer to the string in question. */
150 unsigned char msgid
[1];
153 /* Root of the search tree with known translations. We can use this
154 only if the system provides the `tsearch' function family. */
155 #if defined HAVE_TSEARCH
160 /* Function to compare two entries in the table of known translations. */
161 static int transcmp(const void *p1
, const void *p2
);
165 transcmp(const void *p1
, const void *p2
)
167 const struct known_translation_t
*s1
;
168 const struct known_translation_t
*s2
;
171 s1
= (const struct known_translation_t
*) p1
;
172 s2
= (const struct known_translation_t
*) p2
;
174 result
= strcmp(s1
->msgid
, s2
->msgid
);
176 result
= strcmp(s1
->domainname
, s2
->domainname
);
178 /* We compare the category last (though this is the cheapest
179 operation) since it is hopefully always the same (namely
181 result
= s1
->category
- s2
->category
;
188 /* Name of the default domain used for gettext(3) prior any call to
189 textdomain(3). The default value for this is "messages". */
190 const char _AAA_nl_default_default_domain__
[] = "messages";
192 const unsigned char *_nl_default_default_domain__
= (const unsigned char *)_AAA_nl_default_default_domain__
;
194 /* Value used as the default domain for gettext(3). */
195 const unsigned char *_nl_current_default_domain__
= (const unsigned char *)_AAA_nl_default_default_domain__
;
197 /* Contains the default location of the message catalogs. */
198 const char _AAA_nl_default_dirname__
[] = LOCALEDIR
;
200 const unsigned char *_nl_default_dirname__
= (const unsigned char *)_AAA_nl_default_dirname__
;
202 /* Contains application-specific LANGUAGE variation, taking precedence to the
203 * $LANGUAGE environment variable. */
204 unsigned char *LANGUAGE
= NULL
;
206 /* List with bindings of specific domains created by bindtextdomain()
208 struct binding
*_nl_domain_bindings__
;
210 /* Prototypes for local functions. */
211 static unsigned char *plural_lookup(struct loaded_l10nfile
* domain
,
213 const unsigned char *translation
,
214 size_t translation_len
);
215 static unsigned long int plural_eval(struct expression
* pexp
,
216 unsigned long int n
);
217 static const unsigned char *category_to_name(int category
);
218 static const unsigned char *guess_category_value(int category
,
219 const unsigned char *categoryname
);
221 /* For those loosing systems which don't have `alloca' we have to add
222 some additional code emulating it. */
224 /* Nothing has to be done. */
225 #define ADD_BLOCK(list, address) /* nothing */
226 #define FREE_BLOCKS(list) /* nothing */
230 struct block_list
*next
;
232 #define ADD_BLOCK(list, addr) \
234 struct block_list *newp = (struct block_list *) malloc(sizeof(*newp)); \
235 /* If we cannot get a free block we cannot add the new element to \
237 if (newp != NULL) { \
238 newp->address = (addr); \
239 newp->next = (list); \
243 #define FREE_BLOCKS(list) \
245 while (list != NULL) { \
246 struct block_list *old = list; \
248 free (old->address); \
253 #define alloca(size) (malloc (size))
254 #endif /* have alloca */
256 typedef unsigned char transmem_block_t
;
258 /* Checking whether the binaries runs SUID must be done and glibc provides
259 easier methods therefore we make a difference here. */
269 #define geteuid() getuid()
273 #define getegid() getgid()
276 static int enable_secure
;
278 #define ENABLE_SECURE (enable_secure == 1)
279 #define DETERMINE_SECURE \
280 if (enable_secure == 0) \
282 if (getuid () != geteuid () || getgid () != getegid ()) \
285 enable_secure = -1; \
288 /* Look up MSGID in the DOMAINNAME message catalog for the current
289 CATEGORY locale and, if PLURAL is nonzero, search over string
290 depending on the plural form determined by N. */
292 dcigettext__(const unsigned char *domainname
, const unsigned char *msgid1
, const unsigned char *msgid2
,
293 int plural
, unsigned long int n
, int category
)
296 struct block_list
*block_list
= NULL
;
298 struct loaded_l10nfile
*domain
;
299 struct binding
*binding
;
300 const unsigned char *categoryname
;
301 const unsigned char *categoryvalue
;
302 unsigned char *dirname
, *xdomainname
;
303 unsigned char *single_locale
;
304 unsigned char *retval
;
308 #if defined HAVE_TSEARCH
309 struct known_translation_t
*search
;
310 struct known_translation_t
**foundp
= NULL
;
313 size_t domainname_len
;
315 /* If no real MSGID is given return NULL. */
319 /* If DOMAINNAME is NULL, we are interested in the default domain. If
320 CATEGORY is not LC_MESSAGES this might not make much sense but the
321 definition left this undefined. */
322 if (domainname
== NULL
)
323 domainname
= _nl_current_default_domain__
;
325 #if defined HAVE_TSEARCH
326 msgid_len
= strlen(msgid1
) + 1;
328 /* Try to find the translation among those which we found at
330 search
= (struct known_translation_t
*)
331 alloca(offsetof(struct known_translation_t
, msgid
) + msgid_len
);
333 memcpy(search
->msgid
, msgid1
, msgid_len
);
334 search
->domainname
= (unsigned char *) domainname
;
335 search
->category
= category
;
337 foundp
= (struct known_translation_t
**) tfind(search
, &root
, transcmp
);
338 if (foundp
!= NULL
&& (*foundp
)->counter
== _nl_msg_cat_cntr
) {
339 /* Now deal with plural. */
341 retval
= plural_lookup((*foundp
)->domain
, n
,
342 (*foundp
)->translation
,
343 (*foundp
)->translation_length
);
345 retval
= (unsigned char *) (*foundp
)->translation
;
351 /* Preserve the `errno' value. */
354 /* See whether this is a SUID binary or not. */
357 /* First find matching binding. */
358 for (binding
= _nl_domain_bindings__
; binding
!= NULL
;
359 binding
= binding
->next
) {
360 int compare
= strcmp(domainname
, binding
->domainname
);
366 /* It is not in the list. */
373 dirname
= (unsigned char *) _nl_default_dirname__
;
374 else if (IS_ABSOLUTE_PATH(binding
->dirname
))
375 dirname
= binding
->dirname
;
377 /* We have a relative path. Make it absolute now. */
378 size_t dirname_len
= strlen(binding
->dirname
) + 1;
382 path_max
= (unsigned int) PATH_MAX
;
383 path_max
+= 2; /* The getcwd docs say to do this. */
386 dirname
= (unsigned char *) alloca(path_max
+ dirname_len
);
387 ADD_BLOCK(block_list
, dirname
);
390 ret
= getcwd(dirname
, path_max
);
391 if (ret
!= NULL
|| errno
!= ERANGE
)
394 path_max
+= path_max
/ 2;
395 path_max
+= PATH_INCR
;
399 /* We cannot get the current working directory. Don't signal an
400 error but simply return the default string. */
401 FREE_BLOCKS(block_list
);
403 return (plural
== 0 ? (unsigned char *) msgid1
404 /* Use the Germanic plural rule. */
405 : n
== 1 ? (unsigned char *) msgid1
: (unsigned char *) msgid2
);
408 stpcpy(stpcpy(strchr((const char *)dirname
, '\0'), "/"), binding
->dirname
);
411 /* Now determine the symbolic name of CATEGORY and its value. */
412 categoryname
= category_to_name(category
);
413 categoryvalue
= guess_category_value(category
, categoryname
);
415 domainname_len
= strlen(domainname
);
416 xdomainname
= (unsigned char *) alloca(strlen(categoryname
)
417 + domainname_len
+ 5);
418 ADD_BLOCK(block_list
, xdomainname
);
420 stpcpy(mempcpy(stpcpy(stpcpy(xdomainname
, categoryname
), "/"),
421 domainname
, domainname_len
), ".mo");
423 /* Creating working area. */
424 single_locale
= (unsigned char *) alloca(strlen(categoryvalue
) + 1);
425 ADD_BLOCK(block_list
, single_locale
);
427 /* Search for the given string. This is a loop because we perhaps
428 got an ordered list of languages to consider for the translation. */
430 /* Make CATEGORYVALUE point to the next element of the list. */
431 while (categoryvalue
[0] != '\0' && categoryvalue
[0] == ':')
433 if (categoryvalue
[0] == '\0') {
434 /* The whole contents of CATEGORYVALUE has been searched but
435 no valid entry has been found. We solve this situation
436 by implicitly appending a "C" entry, i.e. no translation
438 single_locale
[0] = 'C';
439 single_locale
[1] = '\0';
441 unsigned char *cp
= single_locale
;
443 while (categoryvalue
[0] != '\0'
444 && categoryvalue
[0] != ':')
445 *cp
++ = *categoryvalue
++;
448 /* When this is a SUID binary we must not allow accessing files
449 outside the dedicated directories. */
450 if (ENABLE_SECURE
&& IS_PATH_WITH_DIR(single_locale
))
451 /* Ingore this entry. */
455 /* If the current locale value is C (or POSIX) we don't load a
456 domain. Return the MSGID. */
457 if (strcmp(single_locale
, "C") == 0
458 || strcmp(single_locale
, "POSIX") == 0) {
459 FREE_BLOCKS(block_list
);
461 return (plural
== 0 ? (unsigned char *) msgid1
462 /* Use the Germanic plural rule. */
463 : n
== 1 ? (unsigned char *) msgid1
: (unsigned char *) msgid2
);
466 /* Find structure describing the message catalog matching the
467 DOMAINNAME and CATEGORY. */
468 domain
= _nl_find_domain(dirname
, single_locale
, xdomainname
,
471 if (domain
!= NULL
) {
472 retval
= _nl_find_msg(domain
, binding
, msgid1
, &retlen
);
474 if (retval
== NULL
) {
477 for (cnt
= 0; domain
->successor
[cnt
] != NULL
;
479 retval
= _nl_find_msg(domain
->
484 if (retval
!= NULL
) {
485 domain
= domain
->successor
[cnt
];
491 if (retval
!= NULL
) {
492 /* Found the translation of MSGID1 in domain DOMAIN:
493 starting at RETVAL, RETLEN bytes. */
494 FREE_BLOCKS(block_list
);
496 #if defined HAVE_TSEARCH
497 if (foundp
== NULL
) {
498 /* Create a new entry and add it to the search tree. */
499 struct known_translation_t
*newp
;
501 newp
= (struct known_translation_t
*)
513 memcpy(newp
->domainname
,
516 newp
->category
= category
;
519 newp
->domain
= domain
;
520 newp
->translation
= retval
;
521 newp
->translation_length
=
524 /* Insert the entry in the search tree. */
532 /* The insert failed. */
536 /* We can update the existing entry. */
537 (*foundp
)->counter
= _nl_msg_cat_cntr
;
538 (*foundp
)->domain
= domain
;
539 (*foundp
)->translation
= retval
;
540 (*foundp
)->translation_length
= retlen
;
543 /* Now deal with plural. */
545 retval
= plural_lookup(domain
, n
,
556 _nl_find_msg(struct loaded_l10nfile
*domain_file
,
557 struct binding
*domainbinding
,
558 const unsigned char *msgid
, size_t *lengthp
)
560 struct loaded_domain
*domain
;
562 unsigned char *result
;
565 if (domain_file
->decided
== 0)
566 _nl_load_domain(domain_file
, domainbinding
);
568 if (domain_file
->data
== NULL
)
571 domain
= (struct loaded_domain
*) domain_file
->data
;
573 /* Locate the MSGID and its translation. */
574 if (domain
->hash_size
> 2 && domain
->hash_tab
!= NULL
) {
575 /* Use the hashing table. */
576 nls_uint32 len
= strlen(msgid
);
577 nls_uint32 hash_val
= hash_string(msgid
);
578 nls_uint32 idx
= hash_val
% domain
->hash_size
;
579 nls_uint32 incr
= 1 + (hash_val
% (domain
->hash_size
- 2));
583 W(domain
->must_swap
, domain
->hash_tab
[idx
]);
586 /* Hash table entry is empty. */
589 /* Compare msgid with the original string at index nstr-1.
590 We compare the lengths with >=, not ==, because plural entries
591 are represented by strings with an embedded NUL. */
594 domain
->orig_tab
[nstr
- 1].length
) >= len
598 domain
->data
+ W(domain
->must_swap
,
599 domain
->orig_tab
[nstr
-
606 if (idx
>= domain
->hash_size
- incr
)
607 idx
-= domain
->hash_size
- incr
;
613 /* Try the default method: binary search in the sorted array of
618 top
= domain
->nstrings
;
619 while (bottom
< top
) {
622 act
= (bottom
+ top
) / 2;
623 cmp_val
= strcmp(msgid
, (domain
->data
624 + W(domain
->must_swap
,
625 domain
->orig_tab
[act
].
629 else if (cmp_val
> 0)
634 /* No translation was found. */
639 /* The translation was found at index ACT. If we have to convert the
640 string to use a different character set, this is the time. */
641 result
= ((unsigned char *) domain
->data
642 + W(domain
->must_swap
, domain
->trans_tab
[act
].offset
));
643 resultlen
= W(domain
->must_swap
, domain
->trans_tab
[act
].length
) + 1;
646 if (domain
->codeset_cntr
647 != (domainbinding
!= NULL
? domainbinding
->codeset_cntr
: 0)) {
648 /* The domain's codeset has changed through bind_textdomain_codeset()
649 since the message catalog was initialized or last accessed. We
650 have to reinitialize the converter. */
651 _nl_free_domain_conv(domain
);
652 _nl_init_domain_conv(domain_file
, domain
, domainbinding
);
655 if (domain
->conv
!= (iconv_t
) - 1) {
656 /* We are supposed to do a conversion. First allocate an
657 appropriate table with the same structure as the table
658 of translations in the file, where we can put the pointers
659 to the converted strings in.
660 There is a slight complication with plural entries. They
661 are represented by consecutive NUL terminated strings. We
662 handle this case by converting RESULTLEN bytes, including
665 if (domain
->conv_tab
== NULL
666 && ((domain
->conv_tab
= (unsigned char **) calloc(domain
->nstrings
,
667 sizeof(unsigned char *)))
669 /* Mark that we didn't succeed allocating a table. */
670 domain
->conv_tab
= (unsigned char **) -1;
672 if (domain
->conv_tab
== (unsigned char **) -1)
673 /* Nothing we can do, no more memory. */
676 if (domain
->conv_tab
[act
] == NULL
) {
677 /* We haven't used this string so far, so it is not
678 translated yet. Do this now. */
679 /* We use a bit more efficient memory handling.
680 We allocate always larger blocks which get used over
681 time. This is faster than many small allocations. */
682 #define INITIAL_BLOCK_SIZE 4080
683 static unsigned char *freemem
;
684 static size_t freemem_size
;
686 const unsigned char *inbuf
;
687 unsigned char *outbuf
;
689 transmem_block_t
*transmem_list
= NULL
;
691 inbuf
= (const unsigned char *) result
;
692 outbuf
= freemem
+ sizeof(size_t);
696 transmem_block_t
*newmem
;
697 ICONV_CONST
char *inptr
= (ICONV_CONST
char *) inbuf
;
698 size_t inleft
= resultlen
;
699 char *outptr
= (unsigned char *) outbuf
;
702 if (freemem_size
< sizeof(size_t))
705 outleft
= freemem_size
- sizeof(size_t);
706 if (iconv(domain
->conv
, &inptr
, &inleft
,
709 outbuf
= (unsigned char *) outptr
;
712 if (errno
!= E2BIG
) {
717 /* We must allocate a new buffer or resize the old one. */
718 if (malloc_count
> 0) {
723 newmem
= (transmem_block_t
*)
724 realloc(transmem_list
,
728 freemem_size
= INITIAL_BLOCK_SIZE
;
729 newmem
= (transmem_block_t
*)
730 malloc(freemem_size
);
732 if (newmem
== NULL
) {
737 transmem_list
= newmem
;
739 outbuf
= freemem
+ sizeof(size_t);
742 /* We have now in our buffer a converted string. Put this
743 into the table of conversions. */
744 *(size_t *) freemem
= outbuf
- freemem
- sizeof(size_t);
745 domain
->conv_tab
[act
] = (unsigned char *) freemem
;
746 /* Shrink freemem, but keep it aligned. */
747 freemem_size
-= outbuf
- freemem
;
749 freemem
+= freemem_size
& (alignof(size_t) - 1);
750 freemem_size
= freemem_size
& ~(alignof(size_t) - 1);
754 /* Now domain->conv_tab[act] contains the translation of all
755 the plural variants. */
756 result
= domain
->conv_tab
[act
] + sizeof(size_t);
757 resultlen
= *(size_t *) domain
->conv_tab
[act
];
761 /* The result string is converted. */
763 #endif /* HAVE_ICONV */
765 *lengthp
= resultlen
;
769 /* Look up a plural variant. */
770 static unsigned char *
771 plural_lookup(struct loaded_l10nfile
*domain
, unsigned long int n
,
772 const unsigned char *translation
, size_t translation_len
)
774 struct loaded_domain
*domaindata
=
775 (struct loaded_domain
*) domain
->data
;
776 unsigned long int indexx
;
777 const unsigned char *p
;
779 indexx
= plural_eval(domaindata
->plural
, n
);
780 if (indexx
>= domaindata
->nplurals
)
781 /* This should never happen. It means the plural expression and the
782 given maximum value do not match. */
785 /* Skip INDEX strings at TRANSLATION. */
787 while (indexx
-- > 0) {
788 p
= strchr((const char *)p
, '\0');
790 /* And skip over the NUL byte. */
793 if (p
>= translation
+ translation_len
)
794 /* This should never happen. It means the plural expression
795 evaluated to a value larger than the number of variants
796 available for MSGID1. */
797 return (unsigned char *) translation
;
799 return (unsigned char *) p
;
802 /* Function to evaluate the plural expression and return an index value. */
803 static unsigned long int
804 plural_eval(struct expression
*pexp
, unsigned long int n
)
806 switch (pexp
->nargs
) {
808 switch (pexp
->operation
) {
812 return pexp
->val
.num
;
820 /* pexp->operation must be lnot. */
821 unsigned long int arg
=
822 plural_eval(pexp
->val
.args
[0], n
);
827 unsigned long int leftarg
=
828 plural_eval(pexp
->val
.args
[0], n
);
829 if (pexp
->operation
== lor
)
831 || plural_eval(pexp
->val
.
833 else if (pexp
->operation
== land
)
835 && plural_eval(pexp
->val
.
838 unsigned long int rightarg
=
839 plural_eval(pexp
->val
.args
[1],
842 switch (pexp
->operation
) {
867 case greater_or_equal
:
885 /* pexp->operation must be qmop. */
886 unsigned long int boolarg
=
887 plural_eval(pexp
->val
.args
[0], n
);
888 return plural_eval(pexp
->val
.
889 args
[boolarg
? 1 : 2], n
);
896 /* Return string representation of locale CATEGORY. */
897 static const unsigned char *
898 category_to_name(int category
)
900 const unsigned char *retval
;
905 retval
= "LC_COLLATE";
915 retval
= "LC_MONETARY";
920 retval
= "LC_NUMERIC";
930 retval
= "LC_MESSAGES";
935 retval
= "LC_RESPONSE";
940 /* This might not make sense but is perhaps better than any other
946 /* If you have a better idea for a default value let me know. */
953 /* Guess value of current locale from value of the environment variables. */
954 static const unsigned char *
955 guess_category_value(int category
, const unsigned char *categoryname
)
957 const unsigned char *language
;
958 const unsigned char *retval
;
960 /* Takes precedence to anything else, damn it's what the application wants!
962 if (LANGUAGE
&& *LANGUAGE
)
965 /* The highest priority value is the `LANGUAGE' environment
966 variable. But we don't use the value if the currently selected
967 locale is the C locale. This is a GNU extension. */
968 /* XXX: This GNU extension breaks things for me and I can't see what is it
969 * good for - I think it only makes things more difficult and arcane, since
970 * it requires you to set up more variables than LANGUAGE, it's poorly
971 * documented and so on. If this breaks anything, let me know at
972 * pasky@ucw.cz, I'm really curious. If LANGUAGE exists, we just use it and
973 * do no more tests. This is an ELinks extension. --pasky */
974 language
= getenv("LANGUAGE");
975 if (language
&& language
[0])
978 /* We have to proceed with the POSIX methods of looking to `LC_ALL',
979 `LC_xxx', and `LANG'. On some systems this can be done by the
980 `setlocale' function itself. */
981 #if (defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES && defined HAVE_LOCALE_NULL)
982 retval
= setlocale(category
, NULL
);
984 /* Setting of LC_ALL overwrites all other. */
985 retval
= getenv("LC_ALL");
986 if (retval
== NULL
|| retval
[0] == '\0') {
987 /* Next comes the name of the desired category. */
988 retval
= getenv(categoryname
);
989 if (retval
== NULL
|| retval
[0] == '\0') {
990 /* Last possibility is the LANG environment variable. */
991 retval
= getenv("LANG");
992 if (retval
== NULL
|| retval
[0] == '\0')
993 /* We use C as the default domain. POSIX says this is
994 implementation defined. */