1 /* $NetBSD: gettext.c,v 1.29 2015/05/29 12:26:28 christos Exp $ */
4 * Copyright (c) 2000, 2001 Citrus Project,
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * $Citrus: xpg4dl/FreeBSD/lib/libintl/gettext.c,v 1.31 2001/09/27 15:18:45 yamt Exp $
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: gettext.c,v 1.29 2015/05/29 12:26:28 christos Exp $");
34 #include <sys/param.h>
50 #include "libintl_local.h"
51 #include "plural_parser.h"
52 #include "pathnames.h"
54 /* GNU gettext added a hack to add some context to messages. If a message is
55 * used in multiple locations, it needs some amount of context to make the
56 * translation clear to translators. GNU gettext, rather than modifying the
57 * message format, concatenates the context, \004 and the message id.
59 #define MSGCTXT_ID_SEPARATOR '\004'
61 static const char *pgettext_impl(const char *, const char *, const char *,
62 const char *, unsigned long int, int);
63 static char *concatenate_ctxt_id(const char *, const char *);
64 static const char *lookup_category(int);
65 static const char *split_locale(const char *);
66 static const char *lookup_mofile(char *, size_t, const char *, const char *,
67 const char *, const char *,
68 struct domainbinding
*);
69 static uint32_t flip(uint32_t, uint32_t);
70 static int validate(void *, struct mohandle
*);
71 static int mapit(const char *, struct domainbinding
*);
72 static int unmapit(struct domainbinding
*);
73 static const char *lookup_hash(const char *, struct domainbinding
*, size_t *);
74 static const char *lookup_bsearch(const char *, struct domainbinding
*,
76 static const char *lookup(const char *, struct domainbinding
*, size_t *);
77 static const char *get_lang_env(const char *);
80 * shortcut functions. the main implementation resides in dcngettext().
83 gettext(const char *msgid
)
86 return dcngettext(NULL
, msgid
, NULL
, 1UL, LC_MESSAGES
);
90 dgettext(const char *domainname
, const char *msgid
)
93 return dcngettext(domainname
, msgid
, NULL
, 1UL, LC_MESSAGES
);
97 dcgettext(const char *domainname
, const char *msgid
, int category
)
100 return dcngettext(domainname
, msgid
, NULL
, 1UL, category
);
104 ngettext(const char *msgid1
, const char *msgid2
, unsigned long int n
)
107 return dcngettext(NULL
, msgid1
, msgid2
, n
, LC_MESSAGES
);
111 dngettext(const char *domainname
, const char *msgid1
, const char *msgid2
,
115 return dcngettext(domainname
, msgid1
, msgid2
, n
, LC_MESSAGES
);
119 pgettext(const char *msgctxt
, const char *msgid
)
122 return pgettext_impl(NULL
, msgctxt
, msgid
, NULL
, 1UL, LC_MESSAGES
);
126 dpgettext(const char *domainname
, const char *msgctxt
, const char *msgid
)
129 return pgettext_impl(domainname
, msgctxt
, msgid
, NULL
, 1UL, LC_MESSAGES
);
133 dcpgettext(const char *domainname
, const char *msgctxt
, const char *msgid
,
137 return pgettext_impl(domainname
, msgctxt
, msgid
, NULL
, 1UL, category
);
141 npgettext(const char *msgctxt
, const char *msgid1
, const char *msgid2
,
145 return pgettext_impl(NULL
, msgctxt
, msgid1
, msgid2
, n
, LC_MESSAGES
);
149 dnpgettext(const char *domainname
, const char *msgctxt
, const char *msgid1
,
150 const char *msgid2
, unsigned long int n
)
153 return pgettext_impl(domainname
, msgctxt
, msgid1
, msgid2
, n
, LC_MESSAGES
);
157 dcnpgettext(const char *domainname
, const char *msgctxt
, const char *msgid1
,
158 const char *msgid2
, unsigned long int n
, int category
)
161 return pgettext_impl(domainname
, msgctxt
, msgid1
, msgid2
, n
, category
);
165 pgettext_impl(const char *domainname
, const char *msgctxt
, const char *msgid1
,
166 const char *msgid2
, unsigned long int n
, int category
)
172 if ((msgctxt_id
= concatenate_ctxt_id(msgctxt
, msgid1
)) == NULL
)
175 translation
= dcngettext(domainname
, msgctxt_id
,
176 msgid2
, n
, category
);
179 p
= strchr(translation
, '\004');
187 * lookup internationalized message on database locale/category/domainname
188 * (like ja_JP.eucJP/LC_MESSAGES/domainname).
189 * if n equals to 1, internationalized message will be looked up for msgid1.
190 * otherwise, message will be looked up for msgid2.
191 * if the lookup fails, the function will return msgid1 or msgid2 as is.
193 * Even though the return type is "char *", caller should not rewrite the
194 * region pointed to by the return value (should be "const char *", but can't
195 * change it for compatibility with other implementations).
197 * by default (if domainname == NULL), domainname is taken from the value set
198 * by textdomain(). usually name of the application (like "ls") is used as
199 * domainname. category is usually LC_MESSAGES.
201 * the code reads in *.mo files generated by GNU gettext. *.mo is a host-
202 * endian encoded file. both endians are supported here, as the files are in
203 * /usr/share/locale! (or we should move those files into /usr/libdata)
207 concatenate_ctxt_id(const char *msgctxt
, const char *msgid
)
211 if (asprintf(&ret
, "%s%c%s", msgctxt
, MSGCTXT_ID_SEPARATOR
, msgid
) == -1)
218 lookup_category(int category
)
222 case LC_COLLATE
: return "LC_COLLATE";
223 case LC_CTYPE
: return "LC_CTYPE";
224 case LC_MONETARY
: return "LC_MONETARY";
225 case LC_NUMERIC
: return "LC_NUMERIC";
226 case LC_TIME
: return "LC_TIME";
227 case LC_MESSAGES
: return "LC_MESSAGES";
233 * XPG syntax: language[_territory[.codeset]][@modifier]
234 * XXX boundary check on "result" is lacking
237 split_locale(const char *lname
)
239 char buf
[BUFSIZ
], tmp
[BUFSIZ
];
241 static char result
[BUFSIZ
];
243 memset(result
, 0, sizeof(result
));
245 if (strlen(lname
) + 1 > sizeof(buf
)) {
250 strlcpy(buf
, lname
, sizeof(buf
));
251 m
= strrchr(buf
, '@');
254 c
= strrchr(buf
, '.');
257 t
= strrchr(buf
, '_');
269 snprintf(tmp
, sizeof(tmp
), "%s_%s.%s@%s",
271 strlcat(result
, tmp
, sizeof(result
));
272 strlcat(result
, ":", sizeof(result
));
274 snprintf(tmp
, sizeof(tmp
), "%s_%s@%s", l
, t
, m
);
275 strlcat(result
, tmp
, sizeof(result
));
276 strlcat(result
, ":", sizeof(result
));
278 snprintf(tmp
, sizeof(tmp
), "%s@%s", l
, m
);
279 strlcat(result
, tmp
, sizeof(result
));
280 strlcat(result
, ":", sizeof(result
));
284 snprintf(tmp
, sizeof(tmp
), "%s_%s.%s", l
, t
, c
);
285 strlcat(result
, tmp
, sizeof(result
));
286 strlcat(result
, ":", sizeof(result
));
288 snprintf(tmp
, sizeof(tmp
), "%s_%s", l
, t
);
289 strlcat(result
, tmp
, sizeof(result
));
290 strlcat(result
, ":", sizeof(result
));
292 strlcat(result
, l
, sizeof(result
));
298 lookup_mofile(char *buf
, size_t len
, const char *dir
, const char *lpath
,
299 const char *category
, const char *domainname
,
300 struct domainbinding
*db
)
304 char lpath_tmp
[BUFSIZ
];
307 * LANGUAGE is a colon separated list of locale names.
310 strlcpy(lpath_tmp
, lpath
, sizeof(lpath_tmp
));
320 /* don't mess with default locales */
321 if (strcmp(p
, "C") == 0 || strcmp(p
, "POSIX") == 0)
324 /* validate pathname */
325 if (strchr(p
, '/') || strchr(category
, '/'))
328 if (strchr(domainname
, '/'))
332 snprintf(buf
, len
, "%s/%s/%s/%s.mo", dir
, p
,
333 category
, domainname
);
334 if (stat(buf
, &st
) < 0)
336 if ((st
.st_mode
& S_IFMT
) != S_IFREG
)
339 if (mapit(buf
, db
) == 0)
347 flip(uint32_t v
, uint32_t magic
)
350 if (magic
== MO_MAGIC
)
352 else if (magic
== MO_MAGIC_SWAPPED
) {
353 v
= ((v
>> 24) & 0xff) | ((v
>> 8) & 0xff00) |
354 ((v
<< 8) & 0xff0000) | ((v
<< 24) & 0xff000000);
363 validate(void *arg
, struct mohandle
*mohandle
)
368 if (p
< (char *)mohandle
->addr
||
369 p
> (char *)mohandle
->addr
+ mohandle
->len
)
376 * calculate the step value if the hash value is conflicted.
378 static __inline
uint32_t
379 calc_collision_step(uint32_t hashval
, uint32_t hashsize
)
381 _DIAGASSERT(hashsize
>2);
382 return (hashval
% (hashsize
- 2)) + 1;
386 * calculate the next index while conflicting.
388 static __inline
uint32_t
389 calc_next_index(uint32_t curidx
, uint32_t hashsize
, uint32_t step
)
391 return curidx
+step
- (curidx
>= hashsize
-step
? hashsize
: 0);
395 get_sysdep_string_table(struct mosysdepstr_h
**table_h
, uint32_t *ofstable
,
396 uint32_t nstrings
, uint32_t magic
, char *base
)
401 struct mosysdepstr
*table
;
403 for (i
=0; i
<nstrings
; i
++) {
404 /* get mosysdepstr record */
405 /* LINTED: ignore the alignment problem. */
406 table
= (struct mosysdepstr
*)(base
+ flip(ofstable
[i
], magic
));
407 /* count number of segments */
409 while (flip(table
->segs
[count
++].ref
, magic
) != MO_LASTSEG
)
412 l
= sizeof(struct mosysdepstr_h
) +
413 sizeof(struct mosysdepsegentry_h
) * (count
-1);
414 table_h
[i
] = (struct mosysdepstr_h
*)malloc(l
);
417 memset(table_h
[i
], 0, l
);
418 table_h
[i
]->off
= (const char *)(base
+ flip(table
->off
, magic
));
419 for (j
=0; j
<count
; j
++) {
420 table_h
[i
]->segs
[j
].len
=
421 flip(table
->segs
[j
].len
, magic
);
422 table_h
[i
]->segs
[j
].ref
=
423 flip(table
->segs
[j
].ref
, magic
);
425 /* LINTED: ignore the alignment problem. */
426 table
= (struct mosysdepstr
*)&table
->segs
[count
];
432 expand_sysdep(struct mohandle
*mohandle
, struct mosysdepstr_h
*str
)
438 /* check whether already expanded */
442 /* calc total length */
443 str
->expanded_len
= 1;
444 for (i
=0; /*CONSTCOND*/1; i
++) {
445 str
->expanded_len
+= str
->segs
[i
].len
;
446 if (str
->segs
[i
].ref
== MO_LASTSEG
)
449 mohandle
->mo
.mo_sysdep_segs
[str
->segs
[i
].ref
].len
;
452 str
->expanded
= malloc(str
->expanded_len
);
457 for (i
=0; /*CONSTCOND*/1; i
++) {
458 memcpy(dst
, src
, str
->segs
[i
].len
);
459 src
+= str
->segs
[i
].len
;
460 dst
+= str
->segs
[i
].len
;
461 if (str
->segs
[i
].ref
== MO_LASTSEG
)
463 memcpy(dst
, mohandle
->mo
.mo_sysdep_segs
[str
->segs
[i
].ref
].str
,
464 mohandle
->mo
.mo_sysdep_segs
[str
->segs
[i
].ref
].len
);
465 dst
+= mohandle
->mo
.mo_sysdep_segs
[str
->segs
[i
].ref
].len
;
473 insert_to_hash(uint32_t *htable
, uint32_t hsize
, const char *str
, uint32_t ref
)
475 uint32_t hashval
, idx
, step
;
477 hashval
= __intl_string_hash(str
);
478 step
= calc_collision_step(hashval
, hsize
);
479 idx
= hashval
% hsize
;
482 idx
= calc_next_index(idx
, hsize
, step
);
488 setup_sysdep_stuffs(struct mo
*mo
, struct mohandle
*mohandle
, char *base
)
491 struct moentry
*stable
;
497 magic
= mo
->mo_magic
;
499 mohandle
->mo
.mo_sysdep_nsegs
= flip(mo
->mo_sysdep_nsegs
, magic
);
500 mohandle
->mo
.mo_sysdep_nstring
= flip(mo
->mo_sysdep_nstring
, magic
);
502 if (mohandle
->mo
.mo_sysdep_nstring
== 0)
505 /* check hash size */
506 if (mohandle
->mo
.mo_hsize
<= 2 ||
507 mohandle
->mo
.mo_hsize
<
508 (mohandle
->mo
.mo_nstring
+ mohandle
->mo
.mo_sysdep_nstring
))
511 /* get sysdep segments */
512 l
= sizeof(struct mosysdepsegs_h
) * mohandle
->mo
.mo_sysdep_nsegs
;
513 mohandle
->mo
.mo_sysdep_segs
= (struct mosysdepsegs_h
*)malloc(l
);
514 if (!mohandle
->mo
.mo_sysdep_segs
)
516 /* LINTED: ignore the alignment problem. */
517 stable
= (struct moentry
*)(base
+ flip(mo
->mo_sysdep_segoff
, magic
));
518 for (i
=0; i
<mohandle
->mo
.mo_sysdep_nsegs
; i
++) {
519 v
= base
+ flip(stable
[i
].off
, magic
);
520 mohandle
->mo
.mo_sysdep_segs
[i
].str
=
521 __intl_sysdep_get_string_by_tag(
523 &mohandle
->mo
.mo_sysdep_segs
[i
].len
);
526 /* get sysdep string table */
527 mohandle
->mo
.mo_sysdep_otable
=
528 (struct mosysdepstr_h
**)calloc(mohandle
->mo
.mo_sysdep_nstring
,
529 sizeof(struct mosysdepstr_h
*));
530 if (!mohandle
->mo
.mo_sysdep_otable
)
532 /* LINTED: ignore the alignment problem. */
533 ofstable
= (uint32_t *)(base
+ flip(mo
->mo_sysdep_otable
, magic
));
534 if (get_sysdep_string_table(mohandle
->mo
.mo_sysdep_otable
, ofstable
,
535 mohandle
->mo
.mo_sysdep_nstring
, magic
,
538 mohandle
->mo
.mo_sysdep_ttable
=
539 (struct mosysdepstr_h
**)calloc(mohandle
->mo
.mo_sysdep_nstring
,
540 sizeof(struct mosysdepstr_h
*));
541 if (!mohandle
->mo
.mo_sysdep_ttable
)
543 /* LINTED: ignore the alignment problem. */
544 ofstable
= (uint32_t *)(base
+ flip(mo
->mo_sysdep_ttable
, magic
));
545 if (get_sysdep_string_table(mohandle
->mo
.mo_sysdep_ttable
, ofstable
,
546 mohandle
->mo
.mo_sysdep_nstring
, magic
,
551 for (i
=0; i
<mohandle
->mo
.mo_sysdep_nstring
; i
++) {
552 if (expand_sysdep(mohandle
, mohandle
->mo
.mo_sysdep_otable
[i
]))
554 insert_to_hash(mohandle
->mo
.mo_htable
,
555 mohandle
->mo
.mo_hsize
,
556 mohandle
->mo
.mo_sysdep_otable
[i
]->expanded
,
557 (i
+1) | MO_HASH_SYSDEP_MASK
);
564 mapit(const char *path
, struct domainbinding
*db
)
569 uint32_t magic
, revision
, flags
= 0;
570 struct moentry
*otable
, *ttable
;
571 const uint32_t *htable
;
577 struct mohandle
*mohandle
= &db
->mohandle
;
579 if (mohandle
->addr
&& mohandle
->addr
!= MAP_FAILED
&&
580 mohandle
->mo
.mo_magic
)
581 return 0; /*already opened*/
586 if (secure_path(path
) != 0)
589 if (stat(path
, &st
) < 0)
591 if ((st
.st_mode
& S_IFMT
) != S_IFREG
|| st
.st_size
> GETTEXT_MMAP_MAX
)
593 fd
= open(path
, O_RDONLY
);
596 if (read(fd
, &magic
, sizeof(magic
)) != sizeof(magic
) ||
597 (magic
!= MO_MAGIC
&& magic
!= MO_MAGIC_SWAPPED
)) {
601 if (read(fd
, &revision
, sizeof(revision
)) != sizeof(revision
)) {
605 switch (flip(revision
, magic
)) {
606 case MO_MAKE_REV(0, 0):
608 case MO_MAKE_REV(0, 1):
609 case MO_MAKE_REV(1, 1):
610 flags
|= MO_F_SYSDEP
;
616 mohandle
->addr
= mmap(NULL
, (size_t)st
.st_size
, PROT_READ
,
617 MAP_FILE
| MAP_SHARED
, fd
, (off_t
)0);
618 if (!mohandle
->addr
|| mohandle
->addr
== MAP_FAILED
) {
623 mohandle
->len
= (size_t)st
.st_size
;
625 base
= mohandle
->addr
;
626 mo
= (struct mo
*)mohandle
->addr
;
628 /* flip endian. do not flip magic number! */
629 mohandle
->mo
.mo_magic
= mo
->mo_magic
;
630 mohandle
->mo
.mo_revision
= flip(mo
->mo_revision
, magic
);
631 mohandle
->mo
.mo_nstring
= flip(mo
->mo_nstring
, magic
);
632 mohandle
->mo
.mo_hsize
= flip(mo
->mo_hsize
, magic
);
633 mohandle
->mo
.mo_flags
= flags
;
635 /* validate otable/ttable */
636 /* LINTED: ignore the alignment problem. */
637 otable
= (struct moentry
*)(base
+ flip(mo
->mo_otable
, magic
));
638 /* LINTED: ignore the alignment problem. */
639 ttable
= (struct moentry
*)(base
+ flip(mo
->mo_ttable
, magic
));
640 if (!validate(otable
, mohandle
) ||
641 !validate(&otable
[mohandle
->mo
.mo_nstring
], mohandle
)) {
645 if (!validate(ttable
, mohandle
) ||
646 !validate(&ttable
[mohandle
->mo
.mo_nstring
], mohandle
)) {
651 /* allocate [ot]table, and convert to normal pointer representation. */
652 l
= sizeof(struct moentry_h
) * mohandle
->mo
.mo_nstring
;
653 mohandle
->mo
.mo_otable
= (struct moentry_h
*)malloc(l
);
654 if (!mohandle
->mo
.mo_otable
) {
658 mohandle
->mo
.mo_ttable
= (struct moentry_h
*)malloc(l
);
659 if (!mohandle
->mo
.mo_ttable
) {
663 p
= mohandle
->mo
.mo_otable
;
664 for (i
= 0; i
< mohandle
->mo
.mo_nstring
; i
++) {
665 p
[i
].len
= flip(otable
[i
].len
, magic
);
666 p
[i
].off
= base
+ flip(otable
[i
].off
, magic
);
668 if (!validate(p
[i
].off
, mohandle
) ||
669 !validate(p
[i
].off
+ p
[i
].len
+ 1, mohandle
)) {
674 p
= mohandle
->mo
.mo_ttable
;
675 for (i
= 0; i
< mohandle
->mo
.mo_nstring
; i
++) {
676 p
[i
].len
= flip(ttable
[i
].len
, magic
);
677 p
[i
].off
= base
+ flip(ttable
[i
].off
, magic
);
679 if (!validate(p
[i
].off
, mohandle
) ||
680 !validate(p
[i
].off
+ p
[i
].len
+ 1, mohandle
)) {
685 /* allocate htable, and convert it to the host order. */
686 if (mohandle
->mo
.mo_hsize
> 2) {
687 l
= sizeof(uint32_t) * mohandle
->mo
.mo_hsize
;
688 mohandle
->mo
.mo_htable
= (uint32_t *)malloc(l
);
689 if (!mohandle
->mo
.mo_htable
) {
693 /* LINTED: ignore the alignment problem. */
694 htable
= (const uint32_t *)(base
+flip(mo
->mo_hoffset
, magic
));
695 for (i
=0; i
< mohandle
->mo
.mo_hsize
; i
++) {
696 mohandle
->mo
.mo_htable
[i
] = flip(htable
[i
], magic
);
697 if (mohandle
->mo
.mo_htable
[i
] >=
698 mohandle
->mo
.mo_nstring
+1) {
699 /* illegal string number. */
705 /* grab MIME-header and charset field */
706 mohandle
->mo
.mo_header
= lookup("", db
, &headerlen
);
707 if (mohandle
->mo
.mo_header
)
708 v
= strstr(mohandle
->mo
.mo_header
, "charset=");
712 mohandle
->mo
.mo_charset
= strdup(v
+ 8);
713 if (!mohandle
->mo
.mo_charset
)
715 v
= strchr(mohandle
->mo
.mo_charset
, '\n');
719 if (!mohandle
->mo
.mo_header
||
720 _gettext_parse_plural(&mohandle
->mo
.mo_plural
,
721 &mohandle
->mo
.mo_nplurals
,
722 mohandle
->mo
.mo_header
, headerlen
))
723 mohandle
->mo
.mo_plural
= NULL
;
726 * XXX check charset, reject it if we are unable to support the charset
727 * with the current locale.
728 * for example, if we are using euc-jp locale and we are looking at
729 * *.mo file encoded by euc-kr (charset=euc-kr), we should reject
730 * the *.mo file as we cannot support it.
733 /* system dependent string support */
734 if ((mohandle
->mo
.mo_flags
& MO_F_SYSDEP
) != 0) {
735 if (setup_sysdep_stuffs(mo
, mohandle
, base
)) {
748 free_sysdep_table(struct mosysdepstr_h
**table
, uint32_t nstring
)
754 for (uint32_t i
= 0; i
< nstring
; i
++) {
756 free(table
[i
]->expanded
);
764 unmapit(struct domainbinding
*db
)
766 struct mohandle
*mohandle
= &db
->mohandle
;
768 /* unmap if there's already mapped region */
769 if (mohandle
->addr
&& mohandle
->addr
!= MAP_FAILED
)
770 munmap(mohandle
->addr
, mohandle
->len
);
771 mohandle
->addr
= NULL
;
772 free(mohandle
->mo
.mo_otable
);
773 free(mohandle
->mo
.mo_ttable
);
774 free(mohandle
->mo
.mo_charset
);
775 free(mohandle
->mo
.mo_htable
);
776 free(mohandle
->mo
.mo_sysdep_segs
);
777 free_sysdep_table(mohandle
->mo
.mo_sysdep_otable
,
778 mohandle
->mo
.mo_sysdep_nstring
);
779 free_sysdep_table(mohandle
->mo
.mo_sysdep_ttable
,
780 mohandle
->mo
.mo_sysdep_nstring
);
781 _gettext_free_plural(mohandle
->mo
.mo_plural
);
782 memset(&mohandle
->mo
, 0, sizeof(mohandle
->mo
));
788 lookup_hash(const char *msgid
, struct domainbinding
*db
, size_t *rlen
)
790 struct mohandle
*mohandle
= &db
->mohandle
;
791 uint32_t idx
, hashval
, step
, strno
;
793 struct mosysdepstr_h
*sysdep_otable
, *sysdep_ttable
;
795 if (mohandle
->mo
.mo_hsize
<= 2 || mohandle
->mo
.mo_htable
== NULL
)
798 hashval
= __intl_string_hash(msgid
);
799 step
= calc_collision_step(hashval
, mohandle
->mo
.mo_hsize
);
800 idx
= hashval
% mohandle
->mo
.mo_hsize
;
802 while (/*CONSTCOND*/1) {
803 strno
= mohandle
->mo
.mo_htable
[idx
];
805 /* unexpected miss */
809 if ((strno
& MO_HASH_SYSDEP_MASK
) == 0) {
810 /* system independent strings */
811 if (len
<= mohandle
->mo
.mo_otable
[strno
].len
&&
812 !strcmp(msgid
, mohandle
->mo
.mo_otable
[strno
].off
)) {
816 mohandle
->mo
.mo_ttable
[strno
].len
;
817 return mohandle
->mo
.mo_ttable
[strno
].off
;
820 /* system dependent strings */
821 strno
&= ~MO_HASH_SYSDEP_MASK
;
822 sysdep_otable
= mohandle
->mo
.mo_sysdep_otable
[strno
];
823 sysdep_ttable
= mohandle
->mo
.mo_sysdep_ttable
[strno
];
824 if (len
<= sysdep_otable
->expanded_len
&&
825 !strcmp(msgid
, sysdep_otable
->expanded
)) {
827 if (expand_sysdep(mohandle
, sysdep_ttable
))
828 /* memory exhausted */
831 *rlen
= sysdep_ttable
->expanded_len
;
832 return sysdep_ttable
->expanded
;
835 idx
= calc_next_index(idx
, mohandle
->mo
.mo_hsize
, step
);
841 lookup_bsearch(const char *msgid
, struct domainbinding
*db
, size_t *rlen
)
843 int top
, bottom
, middle
, omiddle
;
845 struct mohandle
*mohandle
= &db
->mohandle
;
848 bottom
= mohandle
->mo
.mo_nstring
;
854 middle
= (top
+ bottom
) / 2;
855 /* avoid possible infinite loop, when the data is not sorted */
856 if (omiddle
== middle
)
858 if ((size_t)middle
>= mohandle
->mo
.mo_nstring
)
861 n
= strcmp(msgid
, mohandle
->mo
.mo_otable
[middle
].off
);
864 *rlen
= mohandle
->mo
.mo_ttable
[middle
].len
;
865 return (const char *)mohandle
->mo
.mo_ttable
[middle
].off
;
878 lookup(const char *msgid
, struct domainbinding
*db
, size_t *rlen
)
882 v
= lookup_hash(msgid
, db
, rlen
);
886 return lookup_bsearch(msgid
, db
, rlen
);
890 get_lang_env(const char *category_name
)
895 * 1. see LANGUAGE variable first.
897 * LANGUAGE is a GNU extension.
898 * It's a colon separated list of locale names.
900 lang
= getenv("LANGUAGE");
905 * 2. if LANGUAGE isn't set, see LC_ALL, LC_xxx, LANG.
907 * It's essentially setlocale(LC_xxx, NULL).
909 lang
= getenv("LC_ALL");
911 lang
= getenv(category_name
);
913 lang
= getenv("LANG");
916 return 0; /* error */
918 return split_locale(lang
);
922 get_indexed_string(const char *str
, size_t len
, unsigned long idx
)
937 #define _NGETTEXT_DEFAULT(msgid1, msgid2, n) \
938 ((char *)__UNCONST((n) == 1 ? (msgid1) : (msgid2)))
941 dcngettext(const char *domainname
, const char *msgid1
, const char *msgid2
,
942 unsigned long int n
, int category
)
947 static char olpath
[PATH_MAX
];
948 const char *cname
= NULL
;
950 static char *ocname
= NULL
;
951 static char *odomainname
= NULL
;
952 struct domainbinding
*db
;
953 unsigned long plural_index
= 0;
957 domainname
= __current_domainname
;
958 cname
= lookup_category(category
);
959 if (!domainname
|| !cname
)
962 lpath
= get_lang_env(cname
);
966 for (db
= __bindings
; db
; db
= db
->next
)
967 if (strcmp(db
->domainname
, domainname
) == 0)
970 if (!bindtextdomain(domainname
, _PATH_TEXTDOMAIN
))
975 /* resolve relative path */
976 /* XXX not necessary? */
977 if (db
->path
[0] != '/') {
980 if (getcwd(buf
, sizeof(buf
)) == 0)
982 if (strlcat(buf
, "/", sizeof(buf
)) >= sizeof(buf
))
984 if (strlcat(buf
, db
->path
, sizeof(buf
)) >= sizeof(buf
))
986 strlcpy(db
->path
, buf
, sizeof(db
->path
));
989 /* don't bother looking it up if the values are the same */
990 if (odomainname
&& strcmp(domainname
, odomainname
) == 0 &&
991 ocname
&& strcmp(cname
, ocname
) == 0 && strcmp(lpath
, olpath
) == 0 &&
992 db
->mohandle
.mo
.mo_magic
)
995 /* try to find appropriate file, from $LANGUAGE */
996 if (lookup_mofile(path
, sizeof(path
), db
->path
, lpath
, cname
,
997 domainname
, db
) == NULL
)
1003 odomainname
= strdup(domainname
);
1004 ocname
= strdup(cname
);
1005 if (!odomainname
|| !ocname
) {
1009 odomainname
= ocname
= NULL
;
1012 strlcpy(olpath
, lpath
, sizeof(olpath
));
1015 if (db
->mohandle
.mo
.mo_plural
) {
1017 _gettext_calculate_plural(db
->mohandle
.mo
.mo_plural
, n
);
1018 if (plural_index
>= db
->mohandle
.mo
.mo_nplurals
)
1022 msgid
= _NGETTEXT_DEFAULT(msgid1
, msgid2
, n
);
1027 v
= lookup(msgid
, db
, &len
);
1029 if (db
->mohandle
.mo
.mo_plural
)
1030 v
= get_indexed_string(v
, len
, plural_index
);
1032 * convert the translated message's encoding.
1035 * a result of gettext("") shouldn't need any conversion.
1038 v
= __gettext_iconv(v
, db
);
1041 * Given the amount of printf-format security issues, it may
1042 * be a good idea to validate if the original msgid and the
1043 * translated message format string carry the same printf-like
1044 * format identifiers.
1050 return (char *)__UNCONST(msgid
);
1053 return _NGETTEXT_DEFAULT(msgid1
, msgid2
, n
);