xref: /illumos-gate/usr/src/lib/libc/port/i18n/gettext_gnu.c (revision e7cbe64f7a72dae5cb44f100db60ca88f3313c65)
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 <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <sys/mman.h>
37 #include <sys/param.h>
38 #include <sys/stat.h>
39 #include <thread.h>
40 #include <synch.h>
41 #include <unistd.h>
42 #include <limits.h>
43 #include <errno.h>
44 #include "libc.h"
45 #include "msgfmt.h"
46 #include "nlspath_checks.h"
47 #include "gettext.h"
48 
49 #ifdef DEBUG
50 #include <assert.h>
51 #endif
52 
53 static const char	*nullstr = "";
54 
55 #define	CHARSET_MOD	"charset="
56 #define	CHARSET_LEN	(sizeof (CHARSET_MOD) - 1)
57 #define	NPLURALS_MOD	"nplurals="
58 #define	NPLURALS_LEN	(sizeof (NPLURALS_MOD) - 1)
59 #define	PLURAL_MOD	"plural="
60 #define	PLURAL_LEN	(sizeof (PLURAL_MOD) - 1)
61 
62 /*
63  * free_conv_msgstr
64  *
65  * release the memory allocated for storing code-converted messages
66  */
67 static void
68 free_conv_msgstr(Msg_g_node *gmnp)
69 {
70 	int	i;
71 	unsigned int	num_of_str;
72 
73 #ifdef GETTEXT_DEBUG
74 	(void) printf("*************** free_conv_msgstr(0x%p)\n",
75 		(void *)gmnp);
76 	printgnumsg(gmnp, 0);
77 #endif
78 
79 	num_of_str = SWAP(gmnp, gmnp->msg_file_info->num_of_str);
80 	for (i = 0; i < num_of_str; i++) {
81 		if (gmnp->conv_msgstr[i]) {
82 			free(gmnp->conv_msgstr[i]);
83 		}
84 	}
85 	free(gmnp->conv_msgstr);
86 	gmnp->conv_msgstr = NULL;
87 
88 }
89 
90 /*
91  * dfltmsgstr
92  *
93  * choose an appropriate message by evaluating the plural expression,
94  * and return it.
95  */
96 static char *
97 dfltmsgstr(Msg_g_node *gmnp, const char *msgstr, size_t msgstr_len,
98 	struct msg_pack *mp)
99 {
100 	unsigned int	pindex;
101 	size_t	len;
102 	const char	*p;
103 
104 #ifdef GETTEXT_DEBUG
105 	(void) printf("*************** dfltmsgstr(0x%p, \"%s\", %d, 0x%p)\n",
106 		(void *)gmnp,
107 		msgstr ? msgstr : "(null)", msgstr_len, (void *)mp);
108 	printgnumsg(gmnp, 0);
109 	printmp(mp, 0);
110 #endif
111 
112 	if (mp->plural) {
113 		if (gmnp->plural) {
114 			pindex = plural_eval(gmnp->plural, mp->n);
115 		} else {
116 			/*
117 			 * This mo does not have plural information.
118 			 * Using the English form.
119 			 */
120 			if (mp->n == 1)
121 				pindex = 0;
122 			else
123 				pindex = 1;
124 		}
125 #ifdef GETTEXT_DEBUG
126 		(void) printf("plural_eval returned: %d\n", pindex);
127 #endif
128 		if (pindex >= gmnp->nplurals) {
129 			/* should never happen */
130 			pindex = 0;
131 		}
132 		p = msgstr;
133 		for (; pindex != 0; pindex--) {
134 			len = msgstr_len - (p - msgstr);
135 			p = memchr(p, '\0', len);
136 			if (!p) {
137 				/*
138 				 * null byte not found
139 				 * this should never happen
140 				 */
141 				char	*result;
142 				DFLTMSG(result, mp->msgid1, mp->msgid2,
143 					mp->n, mp->plural);
144 				return (result);
145 			}
146 			p++;		/* skip */
147 		}
148 		return ((char *)p);
149 	}
150 
151 	return ((char *)msgstr);
152 }
153 
154 /*
155  * parse_header
156  *
157  * parse the header entry of the GNU MO file and
158  * extract the src encoding and the plural information of the MO file
159  */
160 static int
161 parse_header(const char *header, Msg_g_node *gmnp)
162 {
163 	char	*charset = NULL;
164 	char	*charset_str;
165 	size_t	len;
166 	char	*nplurals_str, *plural_str;
167 	plural_expr_t	plural;
168 	char	*p, *q;
169 	unsigned int	nplurals;
170 	int	ret;
171 
172 #ifdef GETTEXT_DEBUG
173 	(void) printf("*************** parse_header(\"%s\", 0x%p)\n",
174 		header ? header : "(null)", (void *)gmnp);
175 	printgnumsg(gmnp, 0);
176 #endif
177 
178 	if (!header) {
179 		gmnp->src_encoding = (char *)nullstr;
180 		gmnp->nplurals = 2;
181 		gmnp->plural = NULL;
182 #ifdef GETTEXT_DEBUG
183 		(void) printf("*************** exiting parse_header\n");
184 		(void) printf("no header\n");
185 #endif
186 
187 		return (0);
188 	}
189 
190 	charset_str = strstr(header, CHARSET_MOD);
191 	if (!charset_str) {
192 		gmnp->src_encoding = (char *)nullstr;
193 	} else {
194 		p = charset_str + CHARSET_LEN;
195 		q = p;
196 		while ((*q != ' ') && (*q != '\t') &&
197 			(*q != '\n')) {
198 			q++;
199 		}
200 		len = q - p;
201 		if (len > 0) {
202 			charset = (char *)malloc(len + 1);
203 			if (!charset) {
204 				gmnp->src_encoding = (char *)nullstr;
205 				gmnp->nplurals = 2;
206 				gmnp->plural = NULL;
207 				return (-1);
208 			}
209 			(void) memcpy(charset, p, len);
210 			charset[len] = '\0';
211 			gmnp->src_encoding = charset;
212 		} else {
213 			gmnp->src_encoding = (char *)nullstr;
214 		}
215 	}
216 
217 	nplurals_str = strstr(header, NPLURALS_MOD);
218 	plural_str = strstr(header, PLURAL_MOD);
219 	if (!nplurals_str || !plural_str) {
220 		/* no valid plural specification */
221 		gmnp->nplurals = 2;
222 		gmnp->plural = NULL;
223 #ifdef GETTEXT_DEBUG
224 		(void) printf("*************** exiting parse_header\n");
225 		(void) printf("no plural entry\n");
226 #endif
227 		return (0);
228 	} else {
229 		p = nplurals_str + NPLURALS_LEN;
230 		while (*p && isspace((unsigned char)*p)) {
231 			p++;
232 		}
233 		nplurals = (unsigned int)strtol(p, &q, 10);
234 		if (p != q) {
235 			gmnp->nplurals = nplurals;
236 		} else {
237 			gmnp->nplurals = 2;
238 		}
239 
240 		p = plural_str + PLURAL_LEN;
241 #ifdef GETTEXT_DEBUG
242 		(void) printf("plural_str: \"%s\"\n", p);
243 #endif
244 
245 		ret = plural_expr(&plural, (const char *)p);
246 		if (ret == 0) {
247 			/* parse succeeded */
248 			gmnp->plural = plural;
249 #ifdef GETTEXT_DEBUG
250 		(void) printf("*************** exiting parse_header\n");
251 		(void) printf("charset: \"%s\"\n",
252 			charset ? charset : "(null)");
253 		printexpr(plural, 0);
254 #endif
255 			return (0);
256 		} else if (ret == 1) {
257 			/* parse error */
258 			gmnp->nplurals = 2;
259 			gmnp->plural = NULL;
260 			return (0);
261 		} else {
262 			/* fatal error */
263 			if (charset)
264 				free(charset);
265 			gmnp->src_encoding = (char *)nullstr;
266 			gmnp->nplurals = 2;
267 			gmnp->plural = NULL;
268 			return (-1);
269 		}
270 	}
271 	/* NOTREACHED */
272 }
273 
274 static char *
275 handle_gnu_mo(struct cache_pack *cp, struct msg_pack *mp,
276 	Gettext_t *gt)
277 {
278 	char	*result;
279 	char	*codeset = get_codeset(mp->domain);
280 
281 	result = gnu_key_2_text(cp->mnp->msg.gnumsg, codeset, mp);
282 	if (mp->plural) {
283 		if (((result == mp->msgid1) && (mp->n == 1)) ||
284 			((result == mp->msgid2) && (mp->n != 1))) {
285 			return (NULL);
286 		}
287 	} else {
288 		if (result == mp->msgid1) {
289 			return (NULL);
290 		}
291 	}
292 	gt->c_m_node = cp->mnp;
293 	if (!cp->mnp->trusted) {
294 		result = check_format(mp->msgid1, result, 0);
295 		if (result == mp->msgid1) {
296 			DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n,
297 				mp->plural);
298 		}
299 	}
300 	return (result);
301 }
302 
303 /*
304  * handle_lang
305  *
306  * take care of the LANGUAGE specification
307  */
308 char *
309 handle_lang(struct cache_pack *cp, struct msg_pack *mp)
310 {
311 	Gettext_t *gt = global_gt;
312 	struct stat64	statbuf;
313 	const char	*p, *op, *q;
314 	char	*locale = NULL, *olocale, *result;
315 	unsigned int	hash_locale;
316 	size_t	locale_len, olocale_len = 0;
317 	int	gnu_mo_found = 0;
318 	int	fd;
319 	int	ret;
320 
321 #ifdef GETTEXT_DEBUG
322 	(void) printf("*************** handle_lang(0x%p, 0x%p)\n",
323 		(void *)cp, (void *)mp);
324 	printcp(cp, 0);
325 	printmp(mp, 0);
326 #endif
327 
328 	p = mp->language;
329 
330 	while (*p) {
331 		op = p;
332 		q = strchr(p, ':');
333 		if (!q) {
334 			locale_len = strlen(p);
335 			p += locale_len;
336 		} else {
337 			locale_len = q - p;
338 			p += locale_len + 1;
339 		}
340 		if ((locale_len >= MAXPATHLEN) ||
341 			(locale_len == 0)) {
342 			/* illegal locale name */
343 			continue;
344 		}
345 		if (olocale_len < locale_len) {
346 			olocale = locale;
347 			locale = (char *)realloc(locale, locale_len + 1);
348 			if (!locale) {
349 				if (olocale)
350 					free(olocale);
351 				DFLTMSG(result, mp->msgid1, mp->msgid2,
352 					mp->n, mp->plural);
353 				return (result);
354 			}
355 			olocale_len = locale_len;
356 		}
357 		(void) memcpy(locale, op, locale_len);
358 		locale[locale_len] = '\0';
359 		hash_locale = get_hashid(locale, NULL);
360 		mp->locale = locale;
361 		mp->hash_locale = hash_locale;
362 		mp->locale_len = locale_len;
363 #ifdef GETTEXT_DEBUG
364 		*mp->msgfile = '\0';
365 #endif
366 		if (mk_msgfile(mp) == NULL) {
367 			/* illegal locale name */
368 			continue;
369 		}
370 
371 		cp->node_hash = NULL;
372 
373 		ret = check_cache(cp, mp);
374 		if (ret) {
375 			/*
376 			 * found in cache
377 			 */
378 			switch (cp->mnp->type) {
379 			case T_ILL_MO:
380 				/* invalid MO */
381 				continue;
382 			case T_SUN_MO:
383 				/* Solaris MO */
384 				goto out_loop;
385 			case T_GNU_MO:
386 				/* GNU MO */
387 				gnu_mo_found = 1;
388 				result = handle_gnu_mo(cp, mp, gt);
389 				if (result) {
390 					free(locale);
391 					return (result);
392 				}
393 				continue;
394 			}
395 			/* NOTREACHED */
396 		}
397 		/*
398 		 * not found in cache
399 		 */
400 		fd = nls_safe_open(mp->msgfile, &statbuf, &mp->trusted, 1);
401 		if ((fd == -1) || (statbuf.st_size > LONG_MAX)) {
402 			if (connect_invalid_entry(cp, mp) == -1) {
403 				DFLTMSG(result, mp->msgid1, mp->msgid2,
404 					mp->n, mp->plural);
405 				free(locale);
406 				return (result);
407 			}
408 			continue;
409 		}
410 		mp->fsz = (size_t)statbuf.st_size;
411 		mp->addr = mmap(0, mp->fsz, PROT_READ, MAP_SHARED, fd, 0);
412 		(void) close(fd);
413 
414 		if (mp->addr == (caddr_t)-1) {
415 			if (connect_invalid_entry(cp, mp) == -1) {
416 				DFLTMSG(result, mp->msgid1, mp->msgid2,
417 					mp->n, mp->plural);
418 				free(locale);
419 				return (result);
420 			}
421 			continue;
422 		}
423 
424 		cp->mnp = create_mnp(mp);
425 		if (!cp->mnp) {
426 			free(locale);
427 			free_mnp_mp(cp->mnp, mp);
428 			DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n,
429 				mp->plural);
430 			return (result);
431 		}
432 
433 		if (setmsg(cp->mnp, (char *)mp->addr, mp->fsz) == -1) {
434 			free(locale);
435 			free_mnp_mp(cp->mnp, mp);
436 			DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n,
437 				mp->plural);
438 			return (result);
439 		}
440 		if (!cp->cacheline) {
441 			cp->cnp = create_cnp(cp->mnp, mp);
442 			if (!cp->cnp) {
443 				free(locale);
444 				free_mnp_mp(cp->mnp, mp);
445 				DFLTMSG(result, mp->msgid1, mp->msgid2,
446 					mp->n, mp->plural);
447 				return (result);
448 			}
449 		}
450 		cp->mnp->trusted = mp->trusted;
451 		connect_entry(cp);
452 
453 		switch (cp->mnp->type) {
454 		case T_ILL_MO:
455 			/* invalid MO */
456 			continue;
457 		case T_SUN_MO:
458 			/* Solaris MO */
459 			goto out_loop;
460 		case T_GNU_MO:
461 			/* GNU MO */
462 			gnu_mo_found = 1;
463 
464 			result = handle_gnu_mo(cp, mp, gt);
465 			if (result) {
466 				free(locale);
467 				return (result);
468 			}
469 			continue;
470 		}
471 		/* NOTREACHED */
472 	}
473 
474 out_loop:
475 	if (gnu_mo_found) {
476 		DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
477 		free(locale);
478 		return (result);
479 	}
480 	if (locale)
481 		free(locale);
482 	return (NULL);
483 }
484 
485 
486 /*
487  * gnu_msgsearch
488  *
489  * Searchs the translation message for the specified msgid1.
490  * Hash algorithm used in this function is Open Addressing
491  * with Double Hashing:
492  * H(k, i) = (H1(k) + i * H2(k)) mod M
493  * H1(k) = hashvalue % M
494  * H2(k) = 1 + (hashvalue % (M - 2))
495  *
496  * Ref: The Art of Computer Programming Volume 3
497  * Sorting and Searching, second edition
498  * Donald E Knuth
499  */
500 static char *
501 gnu_msgsearch(Msg_g_node *gmnp, const char *msgid1,
502 	size_t *msgstrlen, unsigned int *midx)
503 {
504 	unsigned int	*hash_table;
505 	struct gnu_msg_ent	*msgid_tbl, *msgstr_tbl;
506 	char	*base;
507 	struct gnu_msg_info	*header = gmnp->msg_file_info;
508 	unsigned int	hash_size, hash_val, hash_inc, hash_idx;
509 	unsigned int	offset, msglen, idx;
510 	unsigned int	num_of_str;
511 	unsigned int	off_msgid_tbl, off_msgstr_tbl;
512 	size_t	msgid1_len;
513 
514 	base = (char *)header;
515 	off_msgid_tbl = SWAP(gmnp, header->off_msgid_tbl);
516 	off_msgstr_tbl = SWAP(gmnp, header->off_msgstr_tbl);
517 
518 	/* LINTED */
519 	msgid_tbl = (struct gnu_msg_ent *)(base + off_msgid_tbl);
520 	/* LINTED */
521 	msgstr_tbl = (struct gnu_msg_ent *)(base + off_msgstr_tbl);
522 	hash_table = gmnp->hash_table;
523 	hash_size = SWAP(gmnp, header->sz_hashtbl);
524 	num_of_str = SWAP(gmnp, header->num_of_str);
525 
526 #ifdef GETTEXT_DEBUG
527 	(void) printf("*************** gnu_msgsearch("
528 		"0x%p, \"%s\", 0x%p, 0x%p)\n",
529 		(void *)gmnp,
530 		msgid1 ? msgid1 : "(null)",
531 		(void *)msgstrlen, (void *)midx);
532 	printgnumsg(gmnp, 0);
533 #endif
534 
535 	if (!hash_table || (hash_size <= 2)) {
536 		/*
537 		 * No hash table exists or
538 		 * hash size is enough small
539 		 */
540 		unsigned int	top, bottom;
541 		char	*msg_id_str;
542 		int	val;
543 
544 		top = 0;
545 		bottom = num_of_str;
546 		while (top < bottom) {
547 			idx = (top + bottom) / 2;
548 			msg_id_str = base +
549 				SWAP(gmnp, msgid_tbl[idx].offset);
550 
551 			val = strcmp(msg_id_str, msgid1);
552 			if (val < 0) {
553 				top = idx + 1;
554 			} else if (val > 0) {
555 				bottom = idx;
556 			} else {
557 				goto found;
558 			}
559 		}
560 		/* not found */
561 		return ((char *)msgid1);
562 	}
563 
564 	/* use hash table */
565 	hash_val = get_hashid(msgid1, &msgid1_len);
566 	msglen = (unsigned int)msgid1_len;
567 	hash_idx = hash_val % hash_size;
568 	hash_inc = 1 + (hash_val % (hash_size - 2));
569 
570 	for (;;) {
571 		offset = SWAP(gmnp, hash_table[hash_idx]);
572 
573 		if (offset == 0) {
574 			return ((char *)msgid1);
575 		}
576 
577 		idx = offset - 1;
578 		if ((msglen <= SWAP(gmnp, msgid_tbl[idx].len)) &&
579 			strcmp(msgid1, base +
580 			SWAP(gmnp, msgid_tbl[idx].offset)) == 0) {
581 			/* found */
582 			goto found;
583 		}
584 
585 		hash_idx = (hash_idx + hash_inc) % hash_size;
586 	}
587 	/* NOTREACHED */
588 
589 found:
590 	if (msgstrlen)
591 		*msgstrlen = SWAP(gmnp, msgstr_tbl[idx].len) + 1;
592 	if (midx)
593 		*midx = idx;
594 	return (base + SWAP(gmnp, msgstr_tbl[idx].offset));
595 }
596 
597 /*
598  * do_conv
599  *
600  * Converts the specified string from the src encoding
601  * to the dst encoding by calling iconv()
602  */
603 static size_t
604 do_conv(iconv_t fd, char **dst, const char *src, size_t srclen)
605 {
606 	size_t	oleft, ileft, bufsize, tolen;
607 	char	*to, *tptr;
608 
609 #ifdef GETTEXT_DEBUG
610 	(void) printf("*************** do_conv("
611 		"0x%p, 0x%p, \"%s\", %d)\n",
612 		(void *)fd, (void *)dst, src ? src : "(null)", srclen);
613 #endif
614 
615 	bufsize = srclen * 2;
616 	ileft = srclen;
617 	oleft = bufsize;
618 	to = (char *)malloc(bufsize);
619 	if (!to) {
620 		return ((size_t)-1);
621 	}
622 
623 	for (; ; ) {
624 		tptr = to;
625 		errno = 0;
626 #ifdef GETTEXT_DEBUG
627 		(void) printf("******* calling iconv()\n");
628 #endif
629 		if (iconv(fd, &src, &ileft, &tptr, &oleft) ==
630 			(size_t)-1) {
631 			if (errno == E2BIG) {
632 				char	*oto;
633 				oleft += bufsize;
634 				bufsize *= 2;
635 				oto = to;
636 				to = (char *)realloc(oto, bufsize);
637 				if (!to) {
638 					free(oto);
639 					return ((size_t)-1);
640 				}
641 				continue;
642 			} else {
643 				tolen = bufsize - oleft;
644 				break;
645 			}
646 		}
647 		tolen = bufsize - oleft;
648 		break;
649 	}
650 	*dst = to;
651 	return (tolen);
652 }
653 
654 /*
655  * gnu_key_2_text
656  *
657  * Extracts msgstr from the GNU MO file
658  */
659 char *
660 gnu_key_2_text(Msg_g_node *gmnp, const char *codeset,
661 	struct msg_pack *mp)
662 {
663 	char	*result, *msgstr;
664 	size_t	msgstr_len;
665 	unsigned int	midx;
666 	int	ret;
667 	char	*conv_msgstr, *conv_dst;
668 	size_t	*p;
669 	size_t	conv_msgstr_len, buflen;
670 	iconv_t	fd;
671 	int	conversion, new_encoding;
672 	unsigned int	num_of_str;
673 
674 #ifdef GETTEXT_DEBUG
675 	(void) printf("*************** gnu_key_2_text("
676 		"0x%p, \"%s\", 0x%p)\n",
677 		(void *)gmnp, codeset ? codeset : "(null)", (void *)mp);
678 	printgnumsg(gmnp, 0);
679 	printmp(mp, 0);
680 #endif
681 
682 	/* first checks if header entry has been processed */
683 	if (!(gmnp->flag & ST_CHK)) {
684 		char	*msg_header;
685 
686 		msg_header = gnu_msgsearch(gmnp, "", NULL, NULL);
687 		ret = parse_header((const char *)msg_header, gmnp);
688 		if (ret == -1) {
689 			/* fatal error */
690 			DFLTMSG(result, mp->msgid1, mp->msgid2,
691 				mp->n, mp->plural);
692 			return (result);
693 		}
694 		gmnp->flag |= ST_CHK;
695 	}
696 	msgstr = gnu_msgsearch(gmnp, mp->msgid1, &msgstr_len, &midx);
697 	if (msgstr == mp->msgid1) {
698 		/* not found */
699 		DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
700 		return (result);
701 	}
702 
703 #ifdef GETTEXT_DEBUG
704 	printgnumsg(gmnp, 0);
705 #endif
706 	if (!gmnp->dst_encoding) {
707 		/*
708 		 * destination encoding has not been set.
709 		 */
710 		char	*dupcodeset = strdup(codeset);
711 		if (!dupcodeset) {
712 			/* strdup failed */
713 			result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp);
714 			return (result);
715 		}
716 		gmnp->dst_encoding = dupcodeset;
717 
718 		if (strcmp(gmnp->dst_encoding, gmnp->src_encoding) == 0) {
719 			/*
720 			 * target encoding and src encoding
721 			 * are the same.
722 			 * No conversion required.
723 			 */
724 			conversion = 0;
725 		} else {
726 			/*
727 			 * target encoding is different from
728 			 * src encoding.
729 			 * New conversion required.
730 			 */
731 			/* sanity check */
732 			if (gmnp->fd && (gmnp->fd != (iconv_t)-1)) {
733 				(void) iconv_close(gmnp->fd);
734 				gmnp->fd = (iconv_t)-1;
735 			}
736 			if (gmnp->conv_msgstr)
737 				free_conv_msgstr(gmnp);
738 			conversion = 1;
739 			new_encoding = 1;
740 		}
741 	} else {
742 		/*
743 		 * dst encoding has been already set.
744 		 */
745 		if (strcmp(gmnp->dst_encoding, codeset) == 0) {
746 			/*
747 			 * dst encoding and target encoding are the same.
748 			 */
749 			if (strcmp(gmnp->dst_encoding, gmnp->src_encoding)
750 				== 0) {
751 				/*
752 				 * dst encoding and src encoding are the same.
753 				 * No conversion required.
754 				 */
755 				conversion = 0;
756 			} else {
757 				/*
758 				 * dst encoding is different from src encoding.
759 				 * current conversion is valid.
760 				 */
761 				conversion = 1;
762 				new_encoding = 0;
763 				/* checks if iconv_open has succeeded before */
764 				if (gmnp->fd == (iconv_t)-1) {
765 					/*
766 					 * iconv_open should have failed before
767 					 * Assume this conversion is invalid
768 					 */
769 					conversion = 0;
770 				} else {
771 					if (!gmnp->conv_msgstr) {
772 						/*
773 						 * memory allocation for
774 						 * conv_msgstr should
775 						 * have failed before.
776 						 */
777 						new_encoding = 1;
778 						if (gmnp->fd)
779 							(void) iconv_close(
780 								gmnp->fd);
781 						gmnp->fd = (iconv_t)-1;
782 					}
783 				}
784 			}
785 		} else {
786 			/*
787 			 * dst encoding is different from target encoding.
788 			 * It has changed since before.
789 			 */
790 			char	*dupcodeset = strdup(codeset);
791 			if (!dupcodeset) {
792 				result = dfltmsgstr(gmnp, msgstr,
793 					msgstr_len, mp);
794 				return (result);
795 			}
796 			free(gmnp->dst_encoding);
797 			gmnp->dst_encoding = dupcodeset;
798 			if (strcmp(gmnp->dst_encoding, gmnp->src_encoding)
799 				== 0) {
800 				/*
801 				 * dst encoding and src encoding are the same.
802 				 * now, no conversion required.
803 				 */
804 				conversion = 0;
805 			} else {
806 				/*
807 				 * dst encoding is different from src encoding.
808 				 * new conversion required.
809 				 */
810 				conversion = 1;
811 				new_encoding = 1;
812 			}
813 
814 			if (gmnp->fd && (gmnp->fd != (iconv_t)-1)) {
815 				(void) iconv_close(gmnp->fd);
816 			}
817 			if (gmnp->fd != (iconv_t)-1) {
818 				gmnp->fd = (iconv_t)-1;
819 			}
820 			if (gmnp->conv_msgstr)
821 				free_conv_msgstr(gmnp);
822 		}
823 	}
824 
825 	if (conversion == 0) {
826 		/* no conversion */
827 		result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp);
828 		return (result);
829 	}
830 	/* conversion required */
831 
832 	if (new_encoding == 0) {
833 		/* dst codeset hasn't been changed since before */
834 		if (!gmnp->conv_msgstr[midx]) {
835 			/* this msgstr hasn't been converted yet */
836 			conv_msgstr_len = do_conv(gmnp->fd,
837 				&conv_dst, (const char *)msgstr, msgstr_len);
838 			if (conv_msgstr_len == (size_t)-1) {
839 				result = dfltmsgstr(gmnp, msgstr,
840 					msgstr_len, mp);
841 				return (result);
842 			}
843 			buflen = (conv_msgstr_len + sizeof (size_t));
844 			/* allign to sizeof (size_t) */
845 			if (buflen % sizeof (size_t))
846 				buflen += (sizeof (size_t) -
847 					(buflen % sizeof (size_t)));
848 			p = (size_t *)malloc(buflen);
849 			if (!p) {
850 				free(conv_dst);
851 				result = dfltmsgstr(gmnp, msgstr,
852 					msgstr_len, mp);
853 				return (result);
854 			}
855 			*p = conv_msgstr_len;
856 			(void) memcpy(p + 1, conv_dst, conv_msgstr_len);
857 			free(conv_dst);
858 			gmnp->conv_msgstr[midx] = (char *)p;
859 			conv_msgstr = (char *)(p + 1);
860 		} else {
861 			/* this msgstr is in the conversion cache */
862 			/* LINTED */
863 			size_t	*cmsg = (size_t *)gmnp->conv_msgstr[midx];
864 			conv_msgstr_len = *cmsg;
865 			conv_msgstr = (char *)(cmsg + 1);
866 		}
867 		result = dfltmsgstr(gmnp, conv_msgstr, conv_msgstr_len, mp);
868 		return (result);
869 	}
870 	/* new conversion */
871 #ifdef GETTEXT_DEBUG
872 	(void) printf("******* calling iconv_open()\n");
873 	(void) printf("      dst: \"%s\", src: \"%s\"\n",
874 		gmnp->dst_encoding, gmnp->src_encoding);
875 #endif
876 	fd = iconv_open(gmnp->dst_encoding, gmnp->src_encoding);
877 	gmnp->fd = fd;
878 	if (fd == (iconv_t)-1) {
879 		/*
880 		 * iconv_open() failed.
881 		 * no conversion
882 		 */
883 		result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp);
884 		return (result);
885 	}
886 	num_of_str = SWAP(gmnp, gmnp->msg_file_info->num_of_str);
887 	gmnp->conv_msgstr = (char **)calloc((size_t)num_of_str,
888 		sizeof (char *));
889 	if (!gmnp->conv_msgstr) {
890 		/* malloc failed */
891 		result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp);
892 		return (result);
893 	}
894 	conv_msgstr_len = do_conv(gmnp->fd, &conv_dst,
895 		(const char *)msgstr, msgstr_len);
896 	if (conv_msgstr_len == (size_t)-1) {
897 		free_conv_msgstr(gmnp);
898 		result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp);
899 		return (result);
900 	}
901 	buflen = (conv_msgstr_len + sizeof (size_t));
902 	/* allign to sizeof (size_t) */
903 	if (buflen % sizeof (size_t))
904 		buflen += (sizeof (size_t) - (buflen % sizeof (size_t)));
905 	p = (size_t *)malloc(buflen);
906 	if (!p) {
907 		free(conv_dst);
908 		free_conv_msgstr(gmnp);
909 		result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp);
910 		return (result);
911 	}
912 	*p = conv_msgstr_len;
913 	(void) memcpy(p + 1, conv_dst, conv_msgstr_len);
914 	free(conv_dst);
915 	gmnp->conv_msgstr[midx] = (char *)p;
916 	conv_msgstr = (char *)(p + 1);
917 	result = dfltmsgstr(gmnp, conv_msgstr, conv_msgstr_len, mp);
918 	return (result);
919 }
920