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