xref: /illumos-gate/usr/src/lib/libc/port/i18n/gettext_real.c (revision a07094369b21309434206d9b3601d162693466fc)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
29 
30 #include "synonyms.h"
31 #include "mtlib.h"
32 #include <ctype.h>
33 #include <locale.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/types.h>
38 #include <sys/mman.h>
39 #include <sys/param.h>
40 #include <sys/stat.h>
41 #include <libintl.h>
42 #include <thread.h>
43 #include <synch.h>
44 #include <limits.h>
45 #include <unistd.h>
46 #include "libc.h"
47 #include "_loc_path.h"
48 #include "msgfmt.h"
49 #include "gettext.h"
50 #include "nlspath_checks.h"
51 
52 static int	process_nlspath(const char *, const char *,
53 	const char *, char **);
54 static char *replace_nls_option(char *, const char *, char *,
55 	char *, char *, char *, char *);
56 static char *key_2_text(Msg_s_node *, const char *);
57 static char *handle_mo(struct cache_pack *, struct msg_pack *);
58 static void	mini_strcpy(char *, const char *);
59 static size_t	mini_strlen(const char *);
60 
61 char *
62 _real_gettext_u(const char *domain,
63 	const char *msgid1, const char *msgid2,
64 	unsigned long int ln, int category,
65 	int plural)
66 {
67 	char	msgfile[MAXPATHLEN]; 	/* 1024 */
68 	char	binding[MAXPATHLEN]; 	/* 1024 */
69 	char	mydomain[TEXTDOMAINMAX + 1]; /* 256 + 1 */
70 	char	*cur_binding;	/* points to current binding in list */
71 	char	*bptr, *cur_locale, *cur_domain, *result, *nlspath;
72 	char	*locale, *msgloc, *cb, *cur_domain_binding;
73 	char	*language;
74 	int	n = (unsigned int)ln;	/* we don't need long for n */
75 	size_t	cblen, cur_locale_len, cur_domain_len;
76 	unsigned int	hash_locale;
77 
78 	struct msg_pack	*mp, omp;
79 	struct cache_pack	*cp, ocp;
80 
81 #ifdef GETTEXT_DEBUG
82 	(void) printf("*************** _real_gettext_u(%s, %s, "
83 		"%s, %d, %d, %d)\n",
84 	    domain ? domain : "NULL", msgid1 ? msgid1 : "NULL",
85 		msgid2 ? msgid2 : "NULL", n, category, plural);
86 #endif
87 
88 	if (msgid1 == NULL)
89 		return (NULL);
90 
91 	cp = memset(&ocp, 0, sizeof (ocp));	/* cache pack */
92 	mp = memset(&omp, 0, sizeof (omp));	/* msg pack */
93 
94 	/*
95 	 * category may be LC_MESSAGES or LC_TIME
96 	 * locale contains the value of 'category'
97 	 * hash_locale contains the hash value of locale
98 	 * msgloc contains the value of LC_MESSAGES
99 	 * hash_msgloc contains the hash value of msgloc
100 	 */
101 	locale = setlocale(category, NULL);
102 	hash_locale = get_hashid(locale, &cur_locale_len);
103 
104 	/*
105 	 * content of locale will be overridden by
106 	 * succeeding setlocale invocation.
107 	 * So, duplicate it
108 	 */
109 	cur_locale = (char *)malloc(cur_locale_len + 1);
110 	if (!cur_locale) {
111 		DFLTMSG(result, msgid1, msgid2, n, plural);
112 		return (result);
113 	}
114 	mini_strcpy(cur_locale, locale);
115 
116 	language = getenv("LANGUAGE"); /* for GNU */
117 	if (language) {
118 		if (!*language || strchr(language, '/') != NULL) {
119 			/*
120 			 * LANGUAGE is an empty string or
121 			 * LANGUAGE contains '/'.
122 			 * Ignore it.
123 			 */
124 			language = NULL;
125 		}
126 	}
127 
128 	/*
129 	 * Query the current domain if domain argument is NULL pointer
130 	 */
131 	mydomain[0] = '\0';
132 	if (!domain) {
133 		/*
134 		 * if NULL is specified for domainname,
135 		 * use the currently bound domain.
136 		 */
137 		cur_domain = _textdomain_u(NULL, mydomain);
138 		cur_domain_len = mini_strlen(cur_domain);
139 	} else if (!*domain) {
140 		/*
141 		 * if an empty string is specified
142 		 */
143 		cur_domain = DEFAULT_DOMAIN;
144 		cur_domain_len = DEFAULT_DOMAIN_LEN;
145 	} else {
146 		cur_domain_len = mini_strlen(domain);
147 		if (cur_domain_len > TEXTDOMAINMAX) {
148 			/* domain is invalid, return msg_id */
149 			free(cur_locale);
150 			DFLTMSG(result, msgid1, msgid2, n, plural);
151 			return (result);
152 		}
153 		cur_domain = (char *)domain;
154 	}
155 
156 	nlspath = getenv("NLSPATH"); /* get the content of NLSPATH */
157 	if (!nlspath || !*nlspath) {
158 		/* no NLSPATH is defined in the environ */
159 		if ((*cur_locale == 'C') && (*(cur_locale + 1) == '\0')) {
160 			/*
161 			 * If C locale,
162 			 * return the original msgid immediately.
163 			 */
164 			free(cur_locale);
165 			DFLTMSG(result, msgid1, msgid2, n, plural);
166 			return (result);
167 		}
168 		nlspath = NULL;
169 	} else {
170 		/* NLSPATH is set */
171 		int	ret;
172 
173 		msgloc = setlocale(LC_MESSAGES, NULL);
174 
175 		ret = process_nlspath(cur_domain, msgloc,
176 			(const char *)nlspath, &cur_binding);
177 		if (ret == -1) {
178 			/* error occurred */
179 			free(cur_locale);
180 			DFLTMSG(result, msgid1, msgid2, n, plural);
181 			return (result);
182 		} else if (ret == 0) {
183 			nlspath = NULL;
184 		}
185 	}
186 
187 	cur_domain_binding = _real_bindtextdomain_u(cur_domain,
188 		NULL, TP_BINDING);
189 	if (!cur_domain_binding) {
190 		free(cur_locale);
191 		DFLTMSG(result, msgid1, msgid2, n, plural);
192 		return (result);
193 	}
194 
195 	mp->msgid1 = msgid1;
196 	mp->msgid2 = msgid2;
197 	mp->msgfile = msgfile;
198 	mp->domain = cur_domain;
199 	mp->binding = cur_domain_binding;
200 	mp->locale = cur_locale;
201 	mp->language = language;
202 	mp->locale_len = cur_locale_len;
203 	mp->domain_len = cur_domain_len;
204 	mp->n = n;
205 	mp->category = category;
206 	mp->plural = plural;
207 	mp->hash_locale = hash_locale;
208 
209 	/*
210 	 * Spec1170 requires that we use NLSPATH if it's defined, to
211 	 * override any system default variables.  If NLSPATH is not
212 	 * defined or if a message catalog is not found in any of the
213 	 * components (bindings) specified by NLSPATH, dcgettext_u() will
214 	 * search for the message catalog in either a) the binding path set
215 	 * by any previous application calls to bindtextdomain() or
216 	 * b) the default binding path (/usr/lib/locale).  Save the original
217 	 * binding path so that we can search it if the message catalog
218 	 * is not found via NLSPATH.  The original binding is restored before
219 	 * returning from this routine because the gettext routines should
220 	 * not change the binding set by the application.  This allows
221 	 * bindtextdomain() to be called once for all gettext() calls in the
222 	 * application.
223 	 */
224 
225 	/*
226 	 * First, examine NLSPATH
227 	 */
228 	bptr = binding;
229 	if (nlspath) {
230 		/*
231 		 * NLSPATH binding has been successfully built
232 		 */
233 #ifdef GETTEXT_DEBUG
234 		(void) printf("************************** examining NLSPATH\n");
235 		(void) printf("       cur_binding: \"%s\"\n",
236 			cur_binding ? cur_binding : "(null)");
237 #endif
238 
239 		mp->nlsp = 1;
240 		/*
241 		 * cur_binding always ends with ':' before a null
242 		 * termination.
243 		 */
244 		while (*cur_binding) {
245 			cb = cur_binding;
246 			while (*cur_binding != ':')
247 				cur_binding++;
248 			cblen = cur_binding - cb;
249 			cur_binding++;
250 			if (cblen >= MAXPATHLEN) {
251 				/* cur_binding too long */
252 				free(cur_locale);
253 				DFLTMSG(result, msgid1, msgid2, n, plural);
254 				return (result);
255 			}
256 			(void) memcpy(bptr, cb, cblen);
257 			*(bptr + cblen) = '\0';
258 
259 			(void) memcpy(mp->msgfile, bptr, cblen + 1);
260 			mp->msgfile_len = cblen;
261 #ifdef GETTEXT_DEBUG
262 			(void) printf("*******************"
263 				"********************* \n");
264 			(void) printf("       msgfile: \"%s\"\n",
265 				msgfile ? msgfile : "(null)");
266 			(void) printf("*******************"
267 				"********************* \n");
268 #endif
269 			result = handle_mo(cp, mp);
270 			if (result) {
271 				free(cur_locale);
272 				return (result);
273 			}
274 		}
275 	}
276 
277 	mp->nlsp = 0;
278 	mp->binding = cur_domain_binding;
279 	/*
280 	 * Next, examine LANGUAGE
281 	 */
282 	if (language) {
283 		char	*ret_msg;
284 		ret_msg = handle_lang(cp, mp);
285 		if (ret_msg != NULL) {
286 			/*
287 			 * GNU MO found
288 			 */
289 			free(cur_locale);
290 			return (ret_msg);
291 		}
292 		/*
293 		 * handle_lang() may have overridden
294 		 * locale and hash_locale
295 		 */
296 		mp->locale = cur_locale;
297 		mp->locale_len = cur_locale_len;
298 		mp->hash_locale = hash_locale;
299 	}
300 
301 	/*
302 	 * Finally, handle a single binding
303 	 */
304 #ifdef GETTEXT_DEBUG
305 	*mp->msgfile = '\0';
306 #endif
307 	if (mk_msgfile(mp) == NULL) {
308 		free(cur_locale);
309 		DFLTMSG(result, msgid1, msgid2, n, plural);
310 		return (result);
311 	}
312 
313 	result = handle_mo(cp, mp);
314 	free(cur_locale);
315 	if (result) {
316 		return (result);
317 	}
318 	DFLTMSG(result, msgid1, msgid2, n, plural);
319 	return (result);
320 } /* _real_gettext_u */
321 
322 #define	ALLFREE	\
323 	free_all(nlstmp, nnp, pathname, ppaths, lang, cacheline, cnp)
324 
325 static void
326 free_all(Nlstmp *nlstmp, Nls_node *nnp, char *pathname,
327 	char *ppaths, char *lang, int cacheline, Cache_node *cnp)
328 {
329 	Nlstmp	*tp, *tq;
330 
331 	tp = nlstmp;
332 	while (tp) {
333 		tq = tp->next;
334 		free(tp);
335 		tp = tq;
336 	}
337 	if (nnp->locale)
338 		free(nnp->locale);
339 	if (nnp->domain)
340 		free(nnp->domain);
341 	if (pathname)
342 		free(pathname);
343 	if (ppaths)
344 		free(ppaths);
345 	if (lang)
346 		free(lang);
347 	if (!cacheline)
348 		free(cnp);
349 	free(nnp);
350 }
351 
352 /*
353  * process_nlspath(): process the NLSPATH environment variable.
354  *
355  *		this routine looks at NLSPATH in the environment,
356  *		and will try to build up the binding list based
357  *		on the settings of NLSPATH.
358  *
359  * RETURN:
360  * -1:  Error occurred
361  *  0:  No error, but no binding list has been built
362  *  1:  No error, and a binding list has been built
363  *
364  */
365 static int
366 process_nlspath(const char *cur_domain, const char *cur_msgloc,
367 	const char *nlspath, char **binding)
368 {
369 	char 	*s;				/* generic string ptr */
370 	char	*territory;		/* our current territory element */
371 	char	*codeset;		/* our current codeset element */
372 	char	*s1;			/* for handling territory */
373 	char	*s2;			/* for handling codeset */
374 	char	*lang = NULL;	/* our current language element */
375 	char	*ppaths = NULL;	/* ptr to all of the templates */
376 	char	*pathname = NULL;	/* the full pathname to the file */
377 	unsigned int	hashid;
378 	size_t	nlspath_len, domain_len, locale_len, path_len;
379 	size_t	ppaths_len = 0;
380 	int	cacheline = 0;
381 	Nlstmp	*nlstmp = NULL;
382 	Nlstmp	*pnlstmp, *qnlstmp;
383 	Cache_node	*cnp;
384 	Nls_node	*cur_nls, *nnp = NULL;
385 	Gettext_t	*gt = global_gt;
386 
387 #ifdef GETTEXT_DEBUG
388 	(void) printf("*************** process_nlspath(%s, %s, "
389 		"%s, 0x%p)\n", cur_domain,
390 	    cur_msgloc, nlspath, (void *)binding);
391 #endif
392 
393 	cur_nls = gt->c_n_node;
394 	if (cur_nls &&
395 		(strcmp(cur_nls->domain, cur_domain) == 0 &&
396 		strcmp(cur_nls->locale, cur_msgloc) == 0 &&
397 		strcmp(cur_nls->nlspath, nlspath) == 0)) {
398 		*binding = cur_nls->ppaths;
399 		return (1);
400 	}
401 
402 	hashid = get_hashid(cur_msgloc, NULL);
403 
404 	cnp = gt->c_node;
405 	while (cnp) {
406 		if (cnp->hashid == hashid) {
407 			nnp = cnp->n_node;
408 			cacheline = 1;
409 			while (nnp) {
410 				if (strcmp(nnp->locale, cur_msgloc) == 0 &&
411 					strcmp(nnp->domain, cur_domain) == 0 &&
412 					strcmp(nnp->nlspath, nlspath) == 0) {
413 					gt->c_n_node = nnp;
414 					*binding = nnp->ppaths;
415 					return (1);
416 				}
417 				nnp = nnp->next;
418 			}
419 			break;
420 		} else {
421 			cnp = cnp->next;
422 		}
423 	}
424 
425 	if (cacheline) {
426 		nnp = (Nls_node *)calloc(1, sizeof (Nls_node));
427 		if (!nnp) {
428 			ALLFREE;
429 			return (-1);
430 		}
431 	} else {
432 		cnp = (Cache_node *)calloc(1, sizeof (Cache_node));
433 		if (!cnp) {
434 			ALLFREE;
435 			return (-1);
436 		}
437 		cnp->hashid = hashid;
438 		nnp = (Nls_node *)calloc(1, sizeof (Nls_node));
439 		if (!nnp) {
440 			ALLFREE;
441 			return (-1);
442 		}
443 		cnp->n_node = nnp;
444 		cnp->n_last = nnp;
445 	}
446 
447 	nlspath_len = strlen(nlspath);
448 	locale_len = strlen(cur_msgloc);
449 	domain_len = strlen(cur_domain);
450 
451 	/*
452 	 * nlspath_len, locale_len, and domain_len
453 	 * are including a null termination.
454 	 */
455 	nlspath_len++;
456 	locale_len++;
457 	domain_len++;
458 
459 	lang = NULL;
460 	territory = NULL;
461 	codeset = NULL;
462 
463 	if (cur_msgloc) {
464 		lang = s = strdup(cur_msgloc);
465 		if (lang == NULL) {
466 			ALLFREE;
467 			return (-1);
468 		}
469 		s1 = s2 = NULL;
470 		while (s && *s) {
471 			if (*s == '_') {
472 				s1 = s;
473 				*s1++ = '\0';
474 			} else if (*s == '.') {
475 				s2 = s;
476 				*s2++ = '\0';
477 			}
478 			s++;
479 		}
480 		territory = s1;
481 		codeset = s2;
482 	}
483 
484 	/*
485 	 * now that we have the name (domain), we first look through NLSPATH,
486 	 * in an attempt to get the locale. A locale may be completely
487 	 * specified as "language_territory.codeset". NLSPATH consists
488 	 * of templates separated by ":" characters. The following are
489 	 * the substitution values within NLSPATH:
490 	 *	%N = DEFAULT_DOMAIN
491 	 *	%L = The value of the LC_MESSAGES category.
492 	 *	%I = The language element from the LC_MESSAGES category.
493 	 *	%t = The territory element from the LC_MESSAGES category.
494 	 *	%c = The codeset element from the LC_MESSAGES category.
495 	 *	%% = A single character.
496 	 * if we find one of these characters, we will carry out the
497 	 * appropriate substitution.
498 	 */
499 	pathname = (char *)malloc(MAXPATHLEN);
500 	if (pathname == NULL) {
501 		ALLFREE;
502 		return (-1);
503 	}
504 	s = (char *)nlspath;		/* s has a content of NLSPATH */
505 	while (*s) {				/* march through NLSPATH */
506 		(void) memset(pathname, 0, MAXPATHLEN);
507 		if (*s == ':') {
508 			/*
509 			 * this loop only occurs if we have to replace
510 			 * ":" by "name". replace_nls_option() below
511 			 * will handle the subsequent ":"'s.
512 			 */
513 			pnlstmp = (Nlstmp *)malloc(sizeof (Nlstmp));
514 			if (!pnlstmp) {
515 				ALLFREE;
516 				return (-1);
517 			}
518 
519 			(void) memcpy(pnlstmp->pathname, cur_domain,
520 				domain_len);
521 			ppaths_len += domain_len;
522 
523 			pnlstmp->next = NULL;
524 
525 			if (!nlstmp) {
526 				nlstmp = pnlstmp;
527 				qnlstmp = pnlstmp;
528 			} else {
529 				qnlstmp->next = pnlstmp;
530 				qnlstmp = pnlstmp;
531 			}
532 
533 			++s;
534 			continue;
535 		}
536 		/* replace Substitution field */
537 		s = replace_nls_option(s, cur_domain, pathname,
538 			(char *)cur_msgloc, lang, territory, codeset);
539 
540 		if (s == NULL) {
541 			ALLFREE;
542 			return (-1);
543 		}
544 
545 		/* if we've found a valid file: */
546 		if (*pathname) {
547 			/* add template to end of chain of pathnames: */
548 			pnlstmp = (Nlstmp *)malloc(sizeof (Nlstmp));
549 			if (!pnlstmp) {
550 				ALLFREE;
551 				return (-1);
552 			}
553 
554 			path_len = strlen(pathname) + 1;
555 			(void) memcpy(pnlstmp->pathname, pathname,
556 				path_len);
557 			ppaths_len += path_len;
558 
559 			pnlstmp->next = NULL;
560 
561 			if (!nlstmp) {
562 				nlstmp = pnlstmp;
563 				qnlstmp = pnlstmp;
564 			} else {
565 				qnlstmp->next = pnlstmp;
566 				qnlstmp = pnlstmp;
567 			}
568 		}
569 		if (*s) {
570 			++s;
571 		}
572 	}
573 	/*
574 	 * now that we've handled the pathname templates, concatenate them
575 	 * all into the form "template1:template2:..." for _bindtextdomain_u()
576 	 */
577 
578 	if (ppaths_len != 0) {
579 		ppaths = (char *)malloc(ppaths_len + 1);
580 		if (!ppaths) {
581 			ALLFREE;
582 			return (-1);
583 		}
584 		*ppaths = '\0';
585 	} else {
586 		ALLFREE;
587 		return (0);
588 	}
589 
590 	/*
591 	 * extract the path templates (fifo), and concatenate them
592 	 * all into a ":" separated string for _bindtextdomain_u()
593 	 */
594 	pnlstmp = nlstmp;
595 	while (pnlstmp) {
596 		(void) strcat(ppaths, pnlstmp->pathname);
597 		(void) strcat(ppaths, ":");
598 		qnlstmp = pnlstmp->next;
599 		free(pnlstmp);
600 		pnlstmp = qnlstmp;
601 	}
602 	nlstmp = NULL;
603 
604 	nnp->domain = (char *)malloc(domain_len);
605 	if (!nnp->domain) {
606 		ALLFREE;
607 		return (-1);
608 	} else {
609 		(void) memcpy(nnp->domain, cur_domain, domain_len);
610 	}
611 	nnp->locale = (char *)malloc(locale_len);
612 	if (!nnp->locale) {
613 		ALLFREE;
614 		return (-1);
615 	} else {
616 		(void) memcpy(nnp->locale, cur_msgloc, locale_len);
617 	}
618 	nnp->nlspath = (char *)malloc(nlspath_len);
619 	if (!nnp->nlspath) {
620 		ALLFREE;
621 		return (-1);
622 	} else {
623 		(void) memcpy(nnp->nlspath, nlspath, nlspath_len);
624 	}
625 	nnp->ppaths = ppaths;
626 	nnp->next = NULL;
627 
628 	if (cacheline) {
629 		if (cnp->n_last)
630 			cnp->n_last->next = nnp;
631 		else
632 			cnp->n_node = nnp;
633 		cnp->n_last = nnp;
634 	} else {
635 		if (gt->c_last)
636 			gt->c_last->next = cnp;
637 		else
638 			gt->c_node = cnp;
639 		gt->c_last = cnp;
640 	}
641 	gt->c_n_node = nnp;
642 
643 	free(pathname);
644 	free(lang);
645 #ifdef GETTEXT_DEBUG
646 	(void) printf("*************** existing process_nlspath "
647 		"with success\n");
648 	(void) printf("       binding: \"%s\"\n", ppaths);
649 #endif
650 	*binding = ppaths;
651 	return (1);
652 }
653 
654 
655 /*
656  * This routine will replace substitution parameters in NLSPATH
657  * with appropiate values.
658  */
659 static char *
660 replace_nls_option(char *s, const char *name, char *pathname,
661 	char *locale, char *lang, char *territory, char *codeset)
662 {
663 	char	*t, *u;
664 	char	*limit;
665 
666 	t = pathname;
667 	limit = pathname + MAXPATHLEN - 1;
668 
669 	while (*s && *s != ':') {
670 		if (t < limit) {
671 			/*
672 			 * %% is considered a single % character (XPG).
673 			 * %L : LC_MESSAGES (XPG4) LANG(XPG3)
674 			 * %l : The language element from the current locale.
675 			 *	(XPG3, XPG4)
676 			 */
677 			if (*s != '%')
678 				*t++ = *s;
679 			else if (*++s == 'N') {
680 				if (name) {
681 					u = (char *)name;
682 					while (*u && (t < limit))
683 						*t++ = *u++;
684 				}
685 			} else if (*s == 'L') {
686 				if (locale) {
687 					u = locale;
688 					while (*u && (t < limit))
689 						*t++ = *u++;
690 				}
691 			} else if (*s == 'l') {
692 				if (lang) {
693 					u = lang;
694 					while (*u && (*u != '_') &&
695 						(t < limit))
696 						*t++ = *u++;
697 				}
698 			} else if (*s == 't') {
699 				if (territory) {
700 					u = territory;
701 					while (*u && (*u != '.') &&
702 						(t < limit))
703 						*t++ = *u++;
704 				}
705 			} else if (*s == 'c') {
706 				if (codeset) {
707 					u = codeset;
708 					while (*u && (t < limit))
709 						*t++ = *u++;
710 				}
711 			} else {
712 				if (t < limit)
713 					*t++ = *s;
714 			}
715 		} else {
716 			/* too long pathname */
717 			return (NULL);
718 		}
719 		++s;
720 	}
721 	*t = '\0';
722 	return (s);
723 }
724 
725 
726 char *
727 _real_bindtextdomain_u(const char *domain, const char *binding,
728 	int type)
729 {
730 	struct domain_binding	*bind, *prev;
731 	Gettext_t	*gt = global_gt;
732 	char	**binding_addr;
733 
734 #ifdef GETTEXT_DEBUG
735 	(void) printf("*************** _real_bindtextdomain_u(%s, %s, %s)\n",
736 		(domain ? domain : ""),
737 		(binding ? binding : ""),
738 		(type == TP_BINDING) ? "TP_BINDING" : "TP_CODESET");
739 #endif
740 
741 	/*
742 	 * If domain is a NULL pointer, no change will occur regardless
743 	 * of binding value. Just return NULL.
744 	 */
745 	if (!domain) {
746 		return (NULL);
747 	}
748 
749 	/*
750 	 * Global Binding is not supported any more.
751 	 * Just return NULL if domain is NULL string.
752 	 */
753 	if (*domain == '\0') {
754 		return (NULL);
755 	}
756 
757 	/* linear search for binding, rebind if found, add if not */
758 	bind = FIRSTBIND(gt);
759 	prev = NULL;	/* Two pointers needed for pointer operations */
760 
761 	while (bind) {
762 		if (strcmp(domain, bind->domain) == 0) {
763 			/*
764 			 * Domain found.
765 			 */
766 			binding_addr = (type == TP_BINDING) ? &(bind->binding) :
767 				&(bind->codeset);
768 			if (!binding) {
769 				/*
770 				 * if binding is null, then query
771 				 */
772 				return (*binding_addr);
773 			}
774 			/* replace existing binding with new binding */
775 			if (*binding_addr) {
776 				free(*binding_addr);
777 			}
778 			if ((*binding_addr = strdup(binding)) == NULL) {
779 				return (NULL);
780 			}
781 #ifdef GETTEXT_DEBUG
782 			printlist();
783 #endif
784 			return (*binding_addr);
785 		}
786 		prev = bind;
787 		bind = bind->next;
788 	} /* while (bind) */
789 
790 	/* domain has not been found in the list at this point */
791 	if (binding) {
792 		/*
793 		 * domain is not found, but binding is not NULL.
794 		 * Then add a new node to the end of linked list.
795 		 */
796 
797 		if ((bind = (Dbinding *)malloc(sizeof (Dbinding))) == NULL) {
798 			return (NULL);
799 		}
800 		if ((bind->domain = strdup(domain)) == NULL) {
801 			free(bind);
802 			return (NULL);
803 		}
804 		bind->binding = NULL;
805 		bind->codeset = NULL;
806 		binding_addr = (type == TP_BINDING) ? &(bind->binding) :
807 			&(bind->codeset);
808 		if ((*binding_addr = strdup(binding)) == NULL) {
809 			free(bind->domain);
810 			free(bind);
811 			return (NULL);
812 		}
813 		bind->next = NULL;
814 
815 		if (prev) {
816 			/* reached the end of list */
817 			prev->next = bind;
818 		} else {
819 			/* list was empty */
820 			FIRSTBIND(gt) = bind;
821 		}
822 
823 #ifdef GETTEXT_DEBUG
824 		printlist();
825 #endif
826 		return (*binding_addr);
827 	} else {
828 		/*
829 		 * Query of domain which is not found in the list
830 		 * for bindtextdomain, returns defaultbind
831 		 * for bind_textdomain_codeset, returns NULL
832 		 */
833 		if (type == TP_BINDING) {
834 			return ((char *)defaultbind);
835 		} else {
836 			return (NULL);
837 		}
838 	} /* if (binding) */
839 
840 	/* Must not reach here */
841 
842 } /* _real_bindtextdomain_u */
843 
844 
845 char *
846 _textdomain_u(const char *domain, char *result)
847 {
848 	char	*p;
849 	size_t	domain_len;
850 	Gettext_t	*gt = global_gt;
851 
852 #ifdef GETTEXT_DEBUG
853 	(void) printf("*************** _textdomain_u(\"%s\", 0x%p)\n",
854 		(domain ? domain : ""), (void *)result);
855 #endif
856 
857 	/* Query is performed for NULL domain pointer */
858 	if (domain == NULL) {
859 		mini_strcpy(result, CURRENT_DOMAIN(gt));
860 		return (result);
861 	}
862 
863 	/* check for error. */
864 	/*
865 	 * domain is limited to TEXTDOMAINMAX bytes
866 	 * excluding a null termination.
867 	 */
868 	domain_len = mini_strlen(domain);
869 	if (domain_len > TEXTDOMAINMAX) {
870 		/* too long */
871 		return (NULL);
872 	}
873 
874 	/*
875 	 * Calling textdomain() with a null domain string sets
876 	 * the domain to the default domain.
877 	 * If non-null string is passwd, current domain is changed
878 	 * to the new domain.
879 	 */
880 
881 	/* actually this if clause should be protected from signals */
882 	if (*domain == '\0') {
883 		if (CURRENT_DOMAIN(gt) != default_domain) {
884 			free(CURRENT_DOMAIN(gt));
885 			CURRENT_DOMAIN(gt) = (char *)default_domain;
886 		}
887 	} else {
888 		p = (char *)malloc(domain_len + 1);
889 		if (!p)
890 			return (NULL);
891 		mini_strcpy(p, domain);
892 		if (CURRENT_DOMAIN(gt) != default_domain)
893 			free(CURRENT_DOMAIN(gt));
894 		CURRENT_DOMAIN(gt) = p;
895 	}
896 
897 	mini_strcpy(result, CURRENT_DOMAIN(gt));
898 	return (result);
899 } /* _textdomain_u */
900 
901 /*
902  * key_2_text() translates msd_id into target string.
903  */
904 static char *
905 key_2_text(Msg_s_node *messages, const char *key_string)
906 {
907 	int	val;
908 	char	*msg_id_str;
909 	unsigned char	kc = *(unsigned char *)key_string;
910 	struct msg_struct	*check_msg_list;
911 
912 #ifdef GETTEXT_DEBUG
913 	(void) printf("*************** key_2_text(0x%p, \"%s\")\n",
914 		(void *)messages, key_string ? key_string : "(null)");
915 	printsunmsg(messages, 0);
916 #endif
917 
918 	check_msg_list = messages->msg_list +
919 		messages->msg_file_info->msg_mid;
920 	for (;;) {
921 		msg_id_str = messages->msg_ids +
922 			check_msg_list->msgid_offset;
923 		/*
924 		 * To maintain the compatibility with Zeus mo file,
925 		 * msg_id's are stored in descending order.
926 		 * If the ascending order is desired, change "msgfmt.c"
927 		 * and switch msg_id_str and key_string in the following
928 		 * strcmp() statement.
929 		 */
930 		val = *(unsigned char *)msg_id_str - kc;
931 		if ((val == 0) &&
932 			(val = strcmp(msg_id_str, key_string)) == 0) {
933 			return (messages->msg_strs
934 				+ check_msg_list->msgstr_offset);
935 		} else if (val < 0) {
936 			if (check_msg_list->less != LEAFINDICATOR) {
937 				check_msg_list = messages->msg_list +
938 					check_msg_list->less;
939 				continue;
940 			}
941 			return ((char *)key_string);
942 		} else {
943 			/* val > 0 */
944 			if (check_msg_list->more != LEAFINDICATOR) {
945 				check_msg_list = messages->msg_list +
946 					check_msg_list->more;
947 				continue;
948 			}
949 			return ((char *)key_string);
950 		}
951 	}
952 }
953 
954 static char *
955 handle_type_mo(struct cache_pack *cp, struct msg_pack *mp)
956 {
957 	char	*result;
958 
959 	switch (cp->mnp->type) {
960 	case T_ILL_MO:
961 		return (NULL);
962 	case T_SUN_MO:
963 		if (mp->plural) {
964 			/*
965 			 * *ngettext is called against
966 			 * Sun MO file
967 			 */
968 			int	exp = (mp->n == 1);
969 			result = (char *)mp->msgid1;
970 			if (!exp)
971 				result = (char *)mp->msgid2;
972 			return (result);
973 		}
974 		result = key_2_text(cp->mnp->msg.sunmsg, mp->msgid1);
975 		if (!cp->mnp->trusted) {
976 			result = check_format(mp->msgid1, result, 0);
977 		}
978 		return (result);
979 	case T_GNU_MO:
980 		if (mp->language) {
981 			/*
982 			 * LANGUAGE has been set.
983 			 * Failed to find out a valid GNU MO in
984 			 * handle_lang() using LANGUAGE.
985 			 * Now found a valid GNU MO. But, gettext()
986 			 * needs to default-return.
987 			 */
988 			DFLTMSG(result, mp->msgid1, mp->msgid2,
989 				mp->n, mp->plural);
990 			return (result);
991 		}
992 		result = gnu_key_2_text(cp->mnp->msg.gnumsg,
993 			get_codeset(mp->domain), mp);
994 		if (!cp->mnp->trusted) {
995 			result = check_format(mp->msgid1, result, 0);
996 			if (result == mp->msgid1) {
997 				DFLTMSG(result, mp->msgid1, mp->msgid2,
998 					mp->n, mp->plural);
999 			}
1000 		}
1001 		return (result);
1002 	default:
1003 		/* this should never happen */
1004 		return (NULL);
1005 	}
1006 	/* NOTREACHED */
1007 }
1008 
1009 static char *
1010 handle_mo(struct cache_pack *cp, struct msg_pack *mp)
1011 {
1012 	int	fd, ret;
1013 	char	*result;
1014 	struct stat64	statbuf;
1015 	Gettext_t	*gt = global_gt;
1016 
1017 #ifdef GETTEXT_DEBUG
1018 	(void) printf("*************** handle_mo(0x%p, 0x%p)\n",
1019 		(void *)cp, (void *)mp);
1020 	printcp(cp, 0);
1021 	printmp(mp, 0);
1022 #endif
1023 
1024 	/*
1025 	 * At this point, msgfile contains full path for
1026 	 * domain.
1027 	 * Look up cache entry first. If cache misses,
1028 	 * then search domain look-up table.
1029 	 */
1030 
1031 	ret = check_cache(cp, mp);
1032 
1033 	if (ret) {
1034 		/* cache found */
1035 		gt->c_m_node = cp->mnp;
1036 		return (handle_type_mo(cp, mp));
1037 	}
1038 	/*
1039 	 * Valid entry not found in the cache
1040 	 */
1041 	fd = nls_safe_open(mp->msgfile, &statbuf, &mp->trusted,
1042 			!mp->nlsp);
1043 	if ((fd == -1) || (statbuf.st_size > LONG_MAX)) {
1044 		if (connect_invalid_entry(cp, mp) == -1) {
1045 			DFLTMSG(result, mp->msgid1, mp->msgid2,
1046 				mp->n, mp->plural);
1047 			return (result);
1048 		}
1049 		return (NULL);
1050 	}
1051 	mp->fsz = (size_t)statbuf.st_size;
1052 	mp->addr = mmap(0, mp->fsz, PROT_READ, MAP_SHARED, fd, 0);
1053 	(void) close(fd);
1054 
1055 	if (mp->addr == (caddr_t)-1) {
1056 		if (connect_invalid_entry(cp, mp) == -1) {
1057 			DFLTMSG(result, mp->msgid1, mp->msgid2,
1058 				mp->n, mp->plural);
1059 			return (result);
1060 		}
1061 		return (NULL);
1062 	}
1063 
1064 	cp->mnp = create_mnp(mp);
1065 	if (!cp->mnp) {
1066 		free_mnp_mp(cp->mnp, mp);
1067 		DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
1068 		return (result);
1069 	}
1070 
1071 	if (setmsg(cp->mnp, (char *)mp->addr, mp->fsz) == -1) {
1072 		free_mnp_mp(cp->mnp, mp);
1073 		(void) munmap(mp->addr, mp->fsz);
1074 		DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
1075 		return (result);
1076 	}
1077 	if (!cp->cacheline) {
1078 		cp->cnp = create_cnp(cp->mnp, mp);
1079 		if (!cp->cnp) {
1080 			free_mnp_mp(cp->mnp, mp);
1081 			(void) munmap(mp->addr, mp->fsz);
1082 			DFLTMSG(result, mp->msgid1, mp->msgid2,
1083 				mp->n, mp->plural);
1084 			return (result);
1085 		}
1086 	}
1087 	cp->mnp->trusted = mp->trusted;
1088 	connect_entry(cp);
1089 
1090 	return (handle_type_mo(cp, mp));
1091 	/* NOTREACHED */
1092 }
1093 
1094 static void
1095 mini_strcpy(char *dst, const char *src)
1096 {
1097 	const char	*p = (const char *)src;
1098 	char	*q = dst;
1099 	while (*q++ = *p++)
1100 		;
1101 }
1102 
1103 static size_t
1104 mini_strlen(const char *str)
1105 {
1106 	const char	*p = (const char *)str;
1107 	size_t	len;
1108 
1109 	while (*p)
1110 		p++;
1111 	len = (size_t)(p - str);
1112 	return (len);
1113 }
1114