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