xref: /netbsd-src/lib/libintl/gettext.c (revision 9b2f4799723719bf87ce8aa35a73fb5ab025996d)
1 /*	$NetBSD: gettext.c,v 1.33 2024/08/18 17:46:24 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 2000, 2001 Citrus Project,
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
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.
15  *
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
26  * SUCH DAMAGE.
27  *
28  * $Citrus: xpg4dl/FreeBSD/lib/libintl/gettext.c,v 1.31 2001/09/27 15:18:45 yamt Exp $
29  */
30 
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: gettext.c,v 1.33 2024/08/18 17:46:24 christos Exp $");
33 
34 #include <sys/param.h>
35 #include <sys/stat.h>
36 #include <sys/mman.h>
37 #include <sys/uio.h>
38 
39 #include <assert.h>
40 #include <fcntl.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <string.h>
45 #if 0
46 #include <util.h>
47 #endif
48 #include <libintl.h>
49 #include <locale.h>
50 #include "libintl_local.h"
51 #include "plural_parser.h"
52 #include "pathnames.h"
53 
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.
58  */
59 #define	MSGCTXT_ID_SEPARATOR	'\004'
60 
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 *,
75 				  size_t *);
76 static const char *lookup(const char *, struct domainbinding *, size_t *);
77 static const char *get_lang_env(const char *);
78 
79 /*
80  * shortcut functions.  the main implementation resides in dcngettext().
81  */
82 char *
83 gettext(const char *msgid)
84 {
85 
86 	return dcngettext(NULL, msgid, NULL, 1UL, LC_MESSAGES);
87 }
88 
89 char *
90 dgettext(const char *domainname, const char *msgid)
91 {
92 
93 	return dcngettext(domainname, msgid, NULL, 1UL, LC_MESSAGES);
94 }
95 
96 char *
97 dcgettext(const char *domainname, const char *msgid, int category)
98 {
99 
100 	return dcngettext(domainname, msgid, NULL, 1UL, category);
101 }
102 
103 char *
104 ngettext(const char *msgid1, const char *msgid2, unsigned long int n)
105 {
106 
107 	return dcngettext(NULL, msgid1, msgid2, n, LC_MESSAGES);
108 }
109 
110 char *
111 dngettext(const char *domainname, const char *msgid1, const char *msgid2,
112 	  unsigned long int n)
113 {
114 
115 	return dcngettext(domainname, msgid1, msgid2, n, LC_MESSAGES);
116 }
117 
118 const char *
119 pgettext(const char *msgctxt, const char *msgid)
120 {
121 
122 	return pgettext_impl(NULL, msgctxt, msgid, NULL, 1UL, LC_MESSAGES);
123 }
124 
125 const char *
126 dpgettext(const char *domainname, const char *msgctxt, const char *msgid)
127 {
128 
129 	return pgettext_impl(domainname, msgctxt, msgid, NULL, 1UL, LC_MESSAGES);
130 }
131 
132 const char *
133 dcpgettext(const char *domainname, const char *msgctxt, const char *msgid,
134 	int category)
135 {
136 
137 	return pgettext_impl(domainname, msgctxt, msgid, NULL, 1UL, category);
138 }
139 
140 const char *
141 npgettext(const char *msgctxt, const char *msgid1, const char *msgid2,
142 	unsigned long int n)
143 {
144 
145 	return pgettext_impl(NULL, msgctxt, msgid1, msgid2, n, LC_MESSAGES);
146 }
147 
148 const char *
149 dnpgettext(const char *domainname, const char *msgctxt, const char *msgid1,
150 	const char *msgid2, unsigned long int n)
151 {
152 
153 	return pgettext_impl(domainname, msgctxt, msgid1, msgid2, n, LC_MESSAGES);
154 }
155 
156 const char *
157 dcnpgettext(const char *domainname, const char *msgctxt, const char *msgid1,
158 	const char *msgid2, unsigned long int n, int category)
159 {
160 
161 	return pgettext_impl(domainname, msgctxt, msgid1, msgid2, n, category);
162 }
163 
164 static const char *
165 pgettext_impl(const char *domainname, const char *msgctxt, const char *msgid1,
166 	const char *msgid2, unsigned long int n, int category)
167 {
168 	char *msgctxt_id;
169 	char *translation;
170 	char *p;
171 
172 	if ((msgctxt_id = concatenate_ctxt_id(msgctxt, msgid1)) == NULL)
173 		return msgid1;
174 
175 	translation = dcngettext(domainname, msgctxt_id,
176 		msgid2, n, category);
177 
178 	if (translation == msgctxt_id) {
179 		free(msgctxt_id);
180 		return msgid1;
181 	}
182 
183 	free(msgctxt_id);
184 	p = strchr(translation, '\004');
185 	if (p)
186 		return p + 1;
187 	return translation;
188 }
189 
190 /*
191  * dcngettext() -
192  * lookup internationalized message on database locale/category/domainname
193  * (like ja_JP.eucJP/LC_MESSAGES/domainname).
194  * if n equals to 1, internationalized message will be looked up for msgid1.
195  * otherwise, message will be looked up for msgid2.
196  * if the lookup fails, the function will return msgid1 or msgid2 as is.
197  *
198  * Even though the return type is "char *", caller should not rewrite the
199  * region pointed to by the return value (should be "const char *", but can't
200  * change it for compatibility with other implementations).
201  *
202  * by default (if domainname == NULL), domainname is taken from the value set
203  * by textdomain().  usually name of the application (like "ls") is used as
204  * domainname.  category is usually LC_MESSAGES.
205  *
206  * the code reads in *.mo files generated by GNU gettext.  *.mo is a host-
207  * endian encoded file.  both endians are supported here, as the files are in
208  * /usr/share/locale! (or we should move those files into /usr/libdata)
209  */
210 
211 static char *
212 concatenate_ctxt_id(const char *msgctxt, const char *msgid)
213 {
214 	char *ret;
215 
216 	if (asprintf(&ret, "%s%c%s", msgctxt, MSGCTXT_ID_SEPARATOR, msgid) == -1)
217 		return NULL;
218 
219 	return ret;
220 }
221 
222 static const char *
223 lookup_category(int category)
224 {
225 
226 	switch (category) {
227 	case LC_COLLATE:	return "LC_COLLATE";
228 	case LC_CTYPE:		return "LC_CTYPE";
229 	case LC_MONETARY:	return "LC_MONETARY";
230 	case LC_NUMERIC:	return "LC_NUMERIC";
231 	case LC_TIME:		return "LC_TIME";
232 	case LC_MESSAGES:	return "LC_MESSAGES";
233 	}
234 	return NULL;
235 }
236 
237 #define MAXBUFLEN	1024
238 /*
239  * XPG syntax: language[_territory[.codeset]][@modifier]
240  * XXX boundary check on "result" is lacking
241  */
242 static const char *
243 split_locale(const char *lname)
244 {
245 	char buf[MAXBUFLEN], tmp[2 * MAXBUFLEN];
246 	char *l, *t, *c, *m;
247 	static char result[4 * MAXBUFLEN];
248 
249 	memset(result, 0, sizeof(result));
250 
251 	if (strlen(lname) + 1 > sizeof(buf)) {
252 fail:
253 		return lname;
254 	}
255 
256 	strlcpy(buf, lname, sizeof(buf));
257 	m = strrchr(buf, '@');
258 	if (m)
259 		*m++ = '\0';
260 	c = strrchr(buf, '.');
261 	if (c)
262 		*c++ = '\0';
263 	t = strrchr(buf, '_');
264 	if (t)
265 		*t++ = '\0';
266 	l = buf;
267 	if (strlen(l) == 0)
268 		goto fail;
269 	if (c && !t)
270 		goto fail;
271 
272 	if (m) {
273 		if (t) {
274 			if (c) {
275 				snprintf(tmp, sizeof(tmp), "%s_%s.%s@%s",
276 				    l, t, c, m);
277 				strlcat(result, tmp, sizeof(result));
278 				strlcat(result, ":", sizeof(result));
279 			}
280 			snprintf(tmp, sizeof(tmp), "%s_%s@%s", l, t, m);
281 			strlcat(result, tmp, sizeof(result));
282 			strlcat(result, ":", sizeof(result));
283 		}
284 		snprintf(tmp, sizeof(tmp), "%s@%s", l, m);
285 		strlcat(result, tmp, sizeof(result));
286 		strlcat(result, ":", sizeof(result));
287 	}
288 	if (t) {
289 		if (c) {
290 			snprintf(tmp, sizeof(tmp), "%s_%s.%s", l, t, c);
291 			strlcat(result, tmp, sizeof(result));
292 			strlcat(result, ":", sizeof(result));
293 		}
294 		snprintf(tmp, sizeof(tmp), "%s_%s", l, t);
295 		strlcat(result, tmp, sizeof(result));
296 		strlcat(result, ":", sizeof(result));
297 	}
298 	strlcat(result, l, sizeof(result));
299 
300 	return result;
301 }
302 
303 static const char *
304 lookup_mofile(char *buf, size_t len, const char *dir, const char *lpath,
305 	      const char *category, const char *domainname,
306 	      struct domainbinding *db)
307 {
308 	struct stat st;
309 	char *p, *q;
310 	char lpath_tmp[BUFSIZ];
311 
312 	/*
313 	 * LANGUAGE is a colon separated list of locale names.
314 	 */
315 
316 	strlcpy(lpath_tmp, lpath, sizeof(lpath_tmp));
317 	q = lpath_tmp;
318 	/* CONSTCOND */
319 	while (1) {
320 		p = strsep(&q, ":");
321 		if (!p)
322 			break;
323 		if (!*p)
324 			continue;
325 
326 		/* don't mess with default locales */
327 		if (strcmp(p, "C") == 0 || strcmp(p, "POSIX") == 0)
328 			return NULL;
329 
330 		/* validate pathname */
331 		if (strchr(p, '/') || strchr(category, '/'))
332 			continue;
333 #if 1	/*?*/
334 		if (strchr(domainname, '/'))
335 			continue;
336 #endif
337 
338 		int rv = snprintf(buf, len, "%s/%s/%s/%s.mo", dir, p,
339 		    category, domainname);
340 		if (rv > (int)len)
341 			return NULL;
342 		if (stat(buf, &st) < 0)
343 			continue;
344 		if ((st.st_mode & S_IFMT) != S_IFREG)
345 			continue;
346 
347 		if (mapit(buf, db) == 0)
348 			return buf;
349 	}
350 
351 	return NULL;
352 }
353 
354 static uint32_t
355 flip(uint32_t v, uint32_t magic)
356 {
357 
358 	if (magic == MO_MAGIC)
359 		return v;
360 	else if (magic == MO_MAGIC_SWAPPED) {
361 		v = ((v >> 24) & 0xff) | ((v >> 8) & 0xff00) |
362 		    ((v << 8) & 0xff0000) | ((v << 24) & 0xff000000);
363 		return v;
364 	} else {
365 		abort();
366 		/*NOTREACHED*/
367 	}
368 }
369 
370 static int
371 validate(void *arg, struct mohandle *mohandle)
372 {
373 	char *p;
374 
375 	p = (char *)arg;
376 	if (p < (char *)mohandle->addr ||
377 	    p > (char *)mohandle->addr + mohandle->len)
378 		return 0;
379 	else
380 		return 1;
381 }
382 
383 /*
384  * calculate the step value if the hash value is conflicted.
385  */
386 static __inline uint32_t
387 calc_collision_step(uint32_t hashval, uint32_t hashsize)
388 {
389 	_DIAGASSERT(hashsize>2);
390 	return (hashval % (hashsize - 2)) + 1;
391 }
392 
393 /*
394  * calculate the next index while conflicting.
395  */
396 static __inline uint32_t
397 calc_next_index(uint32_t curidx, uint32_t hashsize, uint32_t step)
398 {
399 	return curidx+step - (curidx >= hashsize-step ? hashsize : 0);
400 }
401 
402 static int
403 get_sysdep_string_table(struct mosysdepstr_h **table_h, uint32_t *ofstable,
404 			uint32_t nstrings, uint32_t magic, char *base)
405 {
406 	unsigned int i;
407 	int j, count;
408 	size_t l;
409 	struct mosysdepstr *table;
410 
411 	for (i=0; i<nstrings; i++) {
412 		/* get mosysdepstr record */
413 		/* LINTED: ignore the alignment problem. */
414 		table = (struct mosysdepstr *)(base + flip(ofstable[i], magic));
415 		/* count number of segments */
416 		count = 0;
417 		while (flip(table->segs[count++].ref, magic) != MO_LASTSEG)
418 			;
419 		/* get table */
420 		l = sizeof(struct mosysdepstr_h) +
421 		    sizeof(struct mosysdepsegentry_h) * (count-1);
422 		table_h[i] = (struct mosysdepstr_h *)malloc(l);
423 		if (!table_h[i])
424 			return -1;
425 		memset(table_h[i], 0, l);
426 		table_h[i]->off = (const char *)(base + flip(table->off, magic));
427 		for (j=0; j<count; j++) {
428 			table_h[i]->segs[j].len =
429 			    flip(table->segs[j].len, magic);
430 			table_h[i]->segs[j].ref =
431 			    flip(table->segs[j].ref, magic);
432 		}
433 		/* LINTED: ignore the alignment problem. */
434 		table = (struct mosysdepstr *)&table->segs[count];
435 	}
436 	return 0;
437 }
438 
439 static int
440 expand_sysdep(struct mohandle *mohandle, struct mosysdepstr_h *str)
441 {
442 	int i;
443 	const char *src;
444 	char *dst;
445 
446 	/* check whether already expanded */
447 	if (str->expanded)
448 		return 0;
449 
450 	/* calc total length */
451 	str->expanded_len = 1;
452 	for (i=0; /*CONSTCOND*/1; i++) {
453 		str->expanded_len += str->segs[i].len;
454 		if (str->segs[i].ref == MO_LASTSEG)
455 			break;
456 		str->expanded_len +=
457 		    mohandle->mo.mo_sysdep_segs[str->segs[i].ref].len;
458 	}
459 	/* expand */
460 	str->expanded = malloc(str->expanded_len);
461 	if (!str->expanded)
462 		return -1;
463 	src = str->off;
464 	dst = str->expanded;
465 	for (i=0; /*CONSTCOND*/1; i++) {
466 		memcpy(dst, src, str->segs[i].len);
467 		src += str->segs[i].len;
468 		dst += str->segs[i].len;
469 		if (str->segs[i].ref == MO_LASTSEG)
470 			break;
471 		memcpy(dst, mohandle->mo.mo_sysdep_segs[str->segs[i].ref].str,
472 		       mohandle->mo.mo_sysdep_segs[str->segs[i].ref].len);
473 		dst += mohandle->mo.mo_sysdep_segs[str->segs[i].ref].len;
474 	}
475 	*dst = '\0';
476 
477 	return 0;
478 }
479 
480 static void
481 insert_to_hash(uint32_t *htable, uint32_t hsize, const char *str, uint32_t ref)
482 {
483 	uint32_t hashval, idx, step;
484 
485 	hashval = __intl_string_hash(str);
486 	step = calc_collision_step(hashval, hsize);
487 	idx = hashval % hsize;
488 
489 	while (htable[idx])
490 		idx = calc_next_index(idx, hsize, step);
491 
492 	htable[idx] = ref;
493 }
494 
495 static int
496 setup_sysdep_stuffs(struct mo *mo, struct mohandle *mohandle, char *base)
497 {
498 	uint32_t magic;
499 	struct moentry *stable;
500 	size_t l;
501 	unsigned int i;
502 	char *v;
503 	uint32_t *ofstable;
504 
505 	magic = mo->mo_magic;
506 
507 	mohandle->mo.mo_sysdep_nsegs = flip(mo->mo_sysdep_nsegs, magic);
508 	mohandle->mo.mo_sysdep_nstring = flip(mo->mo_sysdep_nstring, magic);
509 
510 	if (mohandle->mo.mo_sysdep_nstring == 0)
511 		return 0;
512 
513 	/* check hash size */
514 	if (mohandle->mo.mo_hsize <= 2 ||
515 	    mohandle->mo.mo_hsize <
516 	    (mohandle->mo.mo_nstring + mohandle->mo.mo_sysdep_nstring))
517 		return -1;
518 
519 	/* get sysdep segments */
520 	l = sizeof(struct mosysdepsegs_h) * mohandle->mo.mo_sysdep_nsegs;
521 	mohandle->mo.mo_sysdep_segs = (struct mosysdepsegs_h *)malloc(l);
522 	if (!mohandle->mo.mo_sysdep_segs)
523 		return -1;
524 	/* LINTED: ignore the alignment problem. */
525 	stable = (struct moentry *)(base + flip(mo->mo_sysdep_segoff, magic));
526 	for (i=0; i<mohandle->mo.mo_sysdep_nsegs; i++) {
527 		v = base + flip(stable[i].off, magic);
528 		mohandle->mo.mo_sysdep_segs[i].str =
529 		    __intl_sysdep_get_string_by_tag(
530 			    v,
531 			    &mohandle->mo.mo_sysdep_segs[i].len);
532 	}
533 
534 	/* get sysdep string table */
535 	mohandle->mo.mo_sysdep_otable =
536 	    (struct mosysdepstr_h **)calloc(mohandle->mo.mo_sysdep_nstring,
537 					    sizeof(struct mosysdepstr_h *));
538 	if (!mohandle->mo.mo_sysdep_otable)
539 		return -1;
540 	/* LINTED: ignore the alignment problem. */
541 	ofstable = (uint32_t *)(base + flip(mo->mo_sysdep_otable, magic));
542 	if (get_sysdep_string_table(mohandle->mo.mo_sysdep_otable, ofstable,
543 				    mohandle->mo.mo_sysdep_nstring, magic,
544 				    base))
545 		return -1;
546 	mohandle->mo.mo_sysdep_ttable =
547 	    (struct mosysdepstr_h **)calloc(mohandle->mo.mo_sysdep_nstring,
548 					    sizeof(struct mosysdepstr_h *));
549 	if (!mohandle->mo.mo_sysdep_ttable)
550 		return -1;
551 	/* LINTED: ignore the alignment problem. */
552 	ofstable = (uint32_t *)(base + flip(mo->mo_sysdep_ttable, magic));
553 	if (get_sysdep_string_table(mohandle->mo.mo_sysdep_ttable, ofstable,
554 				    mohandle->mo.mo_sysdep_nstring, magic,
555 				    base))
556 		return -1;
557 
558 	/* update hash */
559 	for (i=0; i<mohandle->mo.mo_sysdep_nstring; i++) {
560 		if (expand_sysdep(mohandle, mohandle->mo.mo_sysdep_otable[i]))
561 			return -1;
562 		insert_to_hash(mohandle->mo.mo_htable,
563 			       mohandle->mo.mo_hsize,
564 			       mohandle->mo.mo_sysdep_otable[i]->expanded,
565 			       (i+1) | MO_HASH_SYSDEP_MASK);
566 	}
567 
568 	return 0;
569 }
570 
571 int
572 mapit(const char *path, struct domainbinding *db)
573 {
574 	int fd;
575 	struct stat st;
576 	char *base;
577 	uint32_t magic, revision, flags = 0;
578 	struct moentry *otable, *ttable;
579 	const uint32_t *htable;
580 	struct moentry_h *p;
581 	struct mo *mo;
582 	size_t l, headerlen;
583 	unsigned int i;
584 	char *v;
585 	struct mohandle *mohandle = &db->mohandle;
586 
587 	if (mohandle->addr && mohandle->addr != MAP_FAILED &&
588 	    mohandle->mo.mo_magic)
589 		return 0;	/*already opened*/
590 
591 	unmapit(db);
592 
593 #if 0
594 	if (secure_path(path) != 0)
595 		goto fail;
596 #endif
597 	if (stat(path, &st) < 0)
598 		goto fail;
599 	if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size > GETTEXT_MMAP_MAX)
600 		goto fail;
601 	fd = open(path, O_RDONLY);
602 	if (fd < 0)
603 		goto fail;
604 	if (read(fd, &magic, sizeof(magic)) != sizeof(magic) ||
605 	    (magic != MO_MAGIC && magic != MO_MAGIC_SWAPPED)) {
606 		close(fd);
607 		goto fail;
608 	}
609 	if (read(fd, &revision, sizeof(revision)) != sizeof(revision)) {
610 		close(fd);
611 		goto fail;
612 	}
613 	switch (flip(revision, magic)) {
614 	case MO_MAKE_REV(0, 0):
615 		break;
616 	case MO_MAKE_REV(0, 1):
617 	case MO_MAKE_REV(1, 1):
618 		flags |= MO_F_SYSDEP;
619 		break;
620 	default:
621 		close(fd);
622 		goto fail;
623 	}
624 	mohandle->addr = mmap(NULL, (size_t)st.st_size, PROT_READ,
625 	    MAP_FILE | MAP_SHARED, fd, (off_t)0);
626 	if (!mohandle->addr || mohandle->addr == MAP_FAILED) {
627 		close(fd);
628 		goto fail;
629 	}
630 	close(fd);
631 	mohandle->len = (size_t)st.st_size;
632 
633 	base = mohandle->addr;
634 	mo = (struct mo *)mohandle->addr;
635 
636 	/* flip endian.  do not flip magic number! */
637 	mohandle->mo.mo_magic = mo->mo_magic;
638 	mohandle->mo.mo_revision = flip(mo->mo_revision, magic);
639 	mohandle->mo.mo_nstring = flip(mo->mo_nstring, magic);
640 	mohandle->mo.mo_hsize = flip(mo->mo_hsize, magic);
641 	mohandle->mo.mo_flags = flags;
642 
643 	/* validate otable/ttable */
644 	/* LINTED: ignore the alignment problem. */
645 	otable = (struct moentry *)(base + flip(mo->mo_otable, magic));
646 	/* LINTED: ignore the alignment problem. */
647 	ttable = (struct moentry *)(base + flip(mo->mo_ttable, magic));
648 	if (!validate(otable, mohandle) ||
649 	    !validate(&otable[mohandle->mo.mo_nstring], mohandle)) {
650 		unmapit(db);
651 		goto fail;
652 	}
653 	if (!validate(ttable, mohandle) ||
654 	    !validate(&ttable[mohandle->mo.mo_nstring], mohandle)) {
655 		unmapit(db);
656 		goto fail;
657 	}
658 
659 	/* allocate [ot]table, and convert to normal pointer representation. */
660 	l = sizeof(struct moentry_h) * mohandle->mo.mo_nstring;
661 	mohandle->mo.mo_otable = (struct moentry_h *)malloc(l);
662 	if (!mohandle->mo.mo_otable) {
663 		unmapit(db);
664 		goto fail;
665 	}
666 	mohandle->mo.mo_ttable = (struct moentry_h *)malloc(l);
667 	if (!mohandle->mo.mo_ttable) {
668 		unmapit(db);
669 		goto fail;
670 	}
671 	p = mohandle->mo.mo_otable;
672 	for (i = 0; i < mohandle->mo.mo_nstring; i++) {
673 		p[i].len = flip(otable[i].len, magic);
674 		p[i].off = base + flip(otable[i].off, magic);
675 
676 		if (!validate(p[i].off, mohandle) ||
677 		    !validate(p[i].off + p[i].len + 1, mohandle)) {
678 			unmapit(db);
679 			goto fail;
680 		}
681 	}
682 	p = mohandle->mo.mo_ttable;
683 	for (i = 0; i < mohandle->mo.mo_nstring; i++) {
684 		p[i].len = flip(ttable[i].len, magic);
685 		p[i].off = base + flip(ttable[i].off, magic);
686 
687 		if (!validate(p[i].off, mohandle) ||
688 		    !validate(p[i].off + p[i].len + 1, mohandle)) {
689 			unmapit(db);
690 			goto fail;
691 		}
692 	}
693 	/* allocate htable, and convert it to the host order. */
694 	if (mohandle->mo.mo_hsize > 2) {
695 		l = sizeof(uint32_t) * mohandle->mo.mo_hsize;
696 		mohandle->mo.mo_htable = (uint32_t *)malloc(l);
697 		if (!mohandle->mo.mo_htable) {
698 			unmapit(db);
699 			goto fail;
700 		}
701 		/* LINTED: ignore the alignment problem. */
702 		htable = (const uint32_t *)(base+flip(mo->mo_hoffset, magic));
703 		for (i=0; i < mohandle->mo.mo_hsize; i++) {
704 			mohandle->mo.mo_htable[i] = flip(htable[i], magic);
705 			if (mohandle->mo.mo_htable[i] >=
706 			    mohandle->mo.mo_nstring+1) {
707 				/* illegal string number. */
708 				unmapit(db);
709 				goto fail;
710 			}
711 		}
712 	}
713 	/* grab MIME-header and charset field */
714 	mohandle->mo.mo_header = lookup("", db, &headerlen);
715 	if (mohandle->mo.mo_header)
716 		v = strstr(mohandle->mo.mo_header, "charset=");
717 	else
718 		v = NULL;
719 	if (v) {
720 		mohandle->mo.mo_charset = strdup(v + 8);
721 		if (!mohandle->mo.mo_charset)
722 			goto fail;
723 		v = strchr(mohandle->mo.mo_charset, '\n');
724 		if (v)
725 			*v = '\0';
726 	}
727 	if (!mohandle->mo.mo_header ||
728 	    _gettext_parse_plural(&mohandle->mo.mo_plural,
729 				  &mohandle->mo.mo_nplurals,
730 				  mohandle->mo.mo_header, headerlen))
731 		mohandle->mo.mo_plural = NULL;
732 
733 	/*
734 	 * XXX check charset, reject it if we are unable to support the charset
735 	 * with the current locale.
736 	 * for example, if we are using euc-jp locale and we are looking at
737 	 * *.mo file encoded by euc-kr (charset=euc-kr), we should reject
738 	 * the *.mo file as we cannot support it.
739 	 */
740 
741 	/* system dependent string support */
742 	if ((mohandle->mo.mo_flags & MO_F_SYSDEP) != 0) {
743 		if (setup_sysdep_stuffs(mo, mohandle, base)) {
744 			unmapit(db);
745 			goto fail;
746 		}
747 	}
748 
749 	return 0;
750 
751 fail:
752 	return -1;
753 }
754 
755 static void
756 free_sysdep_table(struct mosysdepstr_h **table, uint32_t nstring)
757 {
758 
759 	if (! table)
760 		return;
761 
762 	for (uint32_t i = 0; i < nstring; i++) {
763 		if (table[i]) {
764 			free(table[i]->expanded);
765 			free(table[i]);
766 		}
767 	}
768 	free(table);
769 }
770 
771 static int
772 unmapit(struct domainbinding *db)
773 {
774 	struct mohandle *mohandle = &db->mohandle;
775 
776 	/* unmap if there's already mapped region */
777 	if (mohandle->addr && mohandle->addr != MAP_FAILED)
778 		munmap(mohandle->addr, mohandle->len);
779 	mohandle->addr = NULL;
780 	free(mohandle->mo.mo_otable);
781 	free(mohandle->mo.mo_ttable);
782 	free(mohandle->mo.mo_charset);
783 	free(mohandle->mo.mo_htable);
784 	free(mohandle->mo.mo_sysdep_segs);
785 	free_sysdep_table(mohandle->mo.mo_sysdep_otable,
786 	    mohandle->mo.mo_sysdep_nstring);
787 	free_sysdep_table(mohandle->mo.mo_sysdep_ttable,
788 	    mohandle->mo.mo_sysdep_nstring);
789 	_gettext_free_plural(mohandle->mo.mo_plural);
790 	memset(&mohandle->mo, 0, sizeof(mohandle->mo));
791 	return 0;
792 }
793 
794 /* ARGSUSED */
795 static const char *
796 lookup_hash(const char *msgid, struct domainbinding *db, size_t *rlen)
797 {
798 	struct mohandle *mohandle = &db->mohandle;
799 	uint32_t idx, hashval, step, strno;
800 	size_t len;
801 	struct mosysdepstr_h *sysdep_otable, *sysdep_ttable;
802 
803 	if (mohandle->mo.mo_hsize <= 2 || mohandle->mo.mo_htable == NULL)
804 		return NULL;
805 
806 	hashval = __intl_string_hash(msgid);
807 	step = calc_collision_step(hashval, mohandle->mo.mo_hsize);
808 	idx = hashval % mohandle->mo.mo_hsize;
809 	len = strlen(msgid);
810 	while (/*CONSTCOND*/1) {
811 		strno = mohandle->mo.mo_htable[idx];
812 		if (strno == 0) {
813 			/* unexpected miss */
814 			return NULL;
815 		}
816 		strno--;
817 		if ((strno & MO_HASH_SYSDEP_MASK) == 0) {
818 			/* system independent strings */
819 			if (len <= mohandle->mo.mo_otable[strno].len &&
820 			    !strcmp(msgid, mohandle->mo.mo_otable[strno].off)) {
821 				/* hit */
822 				if (rlen)
823 					*rlen =
824 					    mohandle->mo.mo_ttable[strno].len;
825 				return mohandle->mo.mo_ttable[strno].off;
826 			}
827 		} else {
828 			/* system dependent strings */
829 			strno &= ~MO_HASH_SYSDEP_MASK;
830 			sysdep_otable = mohandle->mo.mo_sysdep_otable[strno];
831 			sysdep_ttable = mohandle->mo.mo_sysdep_ttable[strno];
832 			if (len <= sysdep_otable->expanded_len &&
833 			    !strcmp(msgid, sysdep_otable->expanded)) {
834 				/* hit */
835 				if (expand_sysdep(mohandle, sysdep_ttable))
836 					/* memory exhausted */
837 					return NULL;
838 				if (rlen)
839 					*rlen = sysdep_ttable->expanded_len;
840 				return sysdep_ttable->expanded;
841 			}
842 		}
843 		idx = calc_next_index(idx, mohandle->mo.mo_hsize, step);
844 	}
845 	/*NOTREACHED*/
846 }
847 
848 static const char *
849 lookup_bsearch(const char *msgid, struct domainbinding *db, size_t *rlen)
850 {
851 	int top, bottom, middle, omiddle;
852 	int n;
853 	struct mohandle *mohandle = &db->mohandle;
854 
855 	top = 0;
856 	bottom = mohandle->mo.mo_nstring;
857 	omiddle = -1;
858 	/* CONSTCOND */
859 	while (1) {
860 		if (top > bottom)
861 			break;
862 		middle = (top + bottom) / 2;
863 		/* avoid possible infinite loop, when the data is not sorted */
864 		if (omiddle == middle)
865 			break;
866 		if ((size_t)middle >= mohandle->mo.mo_nstring)
867 			break;
868 
869 		n = strcmp(msgid, mohandle->mo.mo_otable[middle].off);
870 		if (n == 0) {
871 			if (rlen)
872 				*rlen = mohandle->mo.mo_ttable[middle].len;
873 			return (const char *)mohandle->mo.mo_ttable[middle].off;
874 		}
875 		else if (n < 0)
876 			bottom = middle;
877 		else
878 			top = middle;
879 		omiddle = middle;
880 	}
881 
882 	return NULL;
883 }
884 
885 static const char *
886 lookup(const char *msgid, struct domainbinding *db, size_t *rlen)
887 {
888 	const char *v;
889 
890 	v = lookup_hash(msgid, db, rlen);
891 	if (v)
892 		return v;
893 
894 	return lookup_bsearch(msgid, db, rlen);
895 }
896 
897 static const char *
898 get_lang_env(const char *category_name)
899 {
900 	const char *lang;
901 
902 	/*
903 	 * 1. see LANGUAGE variable first.
904 	 *
905 	 * LANGUAGE is a GNU extension.
906 	 * It's a colon separated list of locale names.
907 	 */
908 	lang = getenv("LANGUAGE");
909 	if (lang)
910 		return lang;
911 
912 	/*
913 	 * 2. if LANGUAGE isn't set, see LC_ALL, LC_xxx, LANG.
914 	 *
915 	 * It's essentially setlocale(LC_xxx, NULL).
916 	 */
917 	lang = getenv("LC_ALL");
918 	if (!lang)
919 		lang = getenv(category_name);
920 	if (!lang)
921 		lang = getenv("LANG");
922 
923 	if (!lang)
924 		return 0; /* error */
925 
926 	return split_locale(lang);
927 }
928 
929 static const char *
930 get_indexed_string(const char *str, size_t len, unsigned long idx)
931 {
932 	while (idx > 0) {
933 		if (len <= 1)
934 			return str;
935 		if (*str == '\0')
936 			idx--;
937 		if (len > 0) {
938 			str++;
939 			len--;
940 		}
941 	}
942 	return str;
943 }
944 
945 #define	_NGETTEXT_DEFAULT(msgid1, msgid2, n)	\
946 	((char *)__UNCONST((n) == 1 ? (msgid1) : (msgid2)))
947 
948 char *
949 dcngettext(const char *domainname, const char *msgid1, const char *msgid2,
950 	   unsigned long int n, int category)
951 {
952 	const char *msgid;
953 	char path[PATH_MAX+1];
954 	const char *lpath;
955 	static char olpath[PATH_MAX];
956 	const char *cname = NULL;
957 	const char *v;
958 	static char *ocname = NULL;
959 	static char *odomainname = NULL;
960 	struct domainbinding *db;
961 	unsigned long plural_index = 0;
962 	size_t len;
963 
964 	if (!domainname)
965 		domainname = __current_domainname;
966 	cname = lookup_category(category);
967 	if (!domainname || !cname)
968 		goto fail;
969 
970 	lpath = get_lang_env(cname);
971 	if (!lpath)
972 		goto fail;
973 
974 	for (db = __bindings; db; db = db->next)
975 		if (strcmp(db->domainname, domainname) == 0)
976 			break;
977 	if (!db) {
978 		if (!bindtextdomain(domainname, _PATH_TEXTDOMAIN))
979 			goto fail;
980 		db = __bindings;
981 	}
982 
983 	/* resolve relative path */
984 	/* XXX not necessary? */
985 	if (db->path[0] != '/') {
986 		char buf[PATH_MAX];
987 
988 		if (getcwd(buf, sizeof(buf)) == 0)
989 			goto fail;
990 		if (strlcat(buf, "/", sizeof(buf)) >= sizeof(buf))
991 			goto fail;
992 		if (strlcat(buf, db->path, sizeof(buf)) >= sizeof(buf))
993 			goto fail;
994 		strlcpy(db->path, buf, sizeof(db->path));
995 	}
996 
997 	/* don't bother looking it up if the values are the same */
998 	if (odomainname && strcmp(domainname, odomainname) == 0 &&
999 	    ocname && strcmp(cname, ocname) == 0 && strcmp(lpath, olpath) == 0 &&
1000 	    db->mohandle.mo.mo_magic)
1001 		goto found;
1002 
1003 	/* try to find appropriate file, from $LANGUAGE */
1004 	if (lookup_mofile(path, sizeof(path), db->path, lpath, cname,
1005 	    domainname, db) == NULL)
1006 		goto fail;
1007 
1008 	free(odomainname);
1009 	free(ocname);
1010 
1011 	odomainname = strdup(domainname);
1012 	ocname = strdup(cname);
1013 	if (!odomainname || !ocname) {
1014 		free(odomainname);
1015 		free(ocname);
1016 
1017 		odomainname = ocname = NULL;
1018 	}
1019 	else
1020 		strlcpy(olpath, lpath, sizeof(olpath));
1021 
1022 found:
1023 	if (db->mohandle.mo.mo_plural) {
1024 		plural_index =
1025 		    _gettext_calculate_plural(db->mohandle.mo.mo_plural, n);
1026 		if (plural_index >= db->mohandle.mo.mo_nplurals)
1027 			plural_index = 0;
1028 		msgid = msgid1;
1029 	} else
1030 		msgid = _NGETTEXT_DEFAULT(msgid1, msgid2, n);
1031 
1032 	if (msgid == NULL)
1033 		return NULL;
1034 
1035 	v = lookup(msgid, db, &len);
1036 	if (v) {
1037 		if (db->mohandle.mo.mo_plural)
1038 			v = get_indexed_string(v, len, plural_index);
1039 		/*
1040 		 * convert the translated message's encoding.
1041 		 *
1042 		 * special case:
1043 		 *	a result of gettext("") shouldn't need any conversion.
1044 		 */
1045 		if (msgid[0])
1046 			v = __gettext_iconv(v, db);
1047 
1048 		/*
1049 		 * Given the amount of printf-format security issues, it may
1050 		 * be a good idea to validate if the original msgid and the
1051 		 * translated message format string carry the same printf-like
1052 		 * format identifiers.
1053 		 */
1054 
1055 		msgid = v;
1056 	}
1057 
1058 	return (char *)__UNCONST(msgid);
1059 
1060 fail:
1061 	return _NGETTEXT_DEFAULT(msgid1, msgid2, n);
1062 }
1063