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 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#include "gnu_msgfmt.h"
29
30static int	next_entry_is_fuzzy = 0;
31static int	next_entry_is_c_format = 0;
32static struct catalog	*cur_catalog = NULL;
33static char	*cur_mo = NULL;
34
35FILE	*fp;
36iconv_t	cd = (iconv_t)-1;
37struct catalog	*catalog_head = NULL;
38int	cur_po_index = 0;
39
40static size_t
41search_alias(char **paddr, size_t size, const char *variant)
42{
43	char	*addr = *paddr;
44	char 	*p, *sp, *q;
45	size_t	var_len, can_len;
46
47	var_len = strlen(variant);
48	p = addr;
49	q = addr + size;
50	while (q > p) {
51		if (*p == '#') {
52			/*
53			 * Line beginning with '#' is a comment
54			 */
55			p++;
56			while ((q > p) && (*p++ != '\n'))
57				;
58			continue;
59		}
60		/* skip leading spaces */
61		while ((q > p) &&
62		    ((*p == ' ') || (*p == '\t')))
63			p++;
64		if (q <= p)
65			break;
66		sp = p;
67		while ((q > p) && (*p != ' ') &&
68		    (*p != '\t') && (*p != '\n'))
69			p++;
70		if (q <= p) {
71			/* invalid entry */
72			break;
73		}
74		if (*p == '\n') {
75			/* invalid entry */
76			p++;
77			continue;
78		}
79
80		if (((p - sp) != var_len) ||
81		    ((strncmp(sp, variant, var_len) != 0) &&
82		    (strncasecmp(sp, variant, var_len) != 0))) {
83			/*
84			 * didn't match
85			 */
86
87			/* skip remaining chars in this line */
88			p++;
89			while ((q > p) && (*p++ != '\n'))
90				;
91			continue;
92		}
93
94		/* matching entry found */
95
96		/* skip spaces */
97		while ((q > p) &&
98		    ((*p == ' ') || (*p == '\t')))
99			p++;
100		if (q <= p)
101			break;
102		sp = p;
103		while ((q > p) && (*p != ' ') &&
104		    (*p != '\t') && (*p != '\n'))
105			p++;
106		can_len = p - sp;
107		if (can_len == 0) {
108			while ((q > p) && (*p++ != '\n'))
109				;
110			continue;
111		}
112		*paddr = sp;
113		return (can_len);
114	}
115	return (0);
116}
117
118/*
119 * Checks if the specified charset is equivalent to UTF-8.
120 * If it's equivalent to UTF-8, returns 1; Otherwise, returns 0.
121 */
122static int
123check_utf8(const char *charset)
124{
125	int	fd;
126	struct stat64	statbuf;
127	caddr_t	addr;
128	size_t	buflen, charset_len, utf8_len;
129	char	*c_charset, *c_utf8, *p;
130
131	if (strcmp(charset, DEST_CHARSET) == 0)
132		return (1);
133
134	fd = open(_ENCODING_ALIAS_PATH, O_RDONLY);
135	if (fd == -1) {
136		/* no alias file found */
137		return (0);
138	}
139	if (fstat64(fd, &statbuf) == -1) {
140		(void) close(fd);
141		return (0);
142	}
143	buflen = (size_t)statbuf.st_size;
144	addr = mmap(NULL, buflen, PROT_READ, MAP_SHARED, fd, 0);
145	(void) close(fd);
146	if (addr == MAP_FAILED) {
147		warning("mmap() for %s failed.", _ENCODING_ALIAS_PATH);
148		return (0);
149	}
150	p = (char *)addr;
151	charset_len = search_alias(&p, buflen, charset);
152	if (charset_len) {
153		c_charset = alloca(charset_len + 1);
154		(void) memcpy(c_charset, p, charset_len);
155		c_charset[charset_len] = '\0';
156	} else {
157		c_charset = (char *)charset;
158	}
159	p = (char *)addr;
160	utf8_len = search_alias(&p, buflen, DEST_CHARSET);
161	if (utf8_len) {
162		c_utf8 = alloca(utf8_len + 1);
163		(void) memcpy(c_utf8, p, utf8_len);
164		c_utf8[utf8_len] = '\0';
165	} else {
166		c_utf8 = DEST_CHARSET;
167	}
168	(void) munmap(addr, buflen);
169	if (charset_len == 0 && utf8_len == 0) {
170		/*
171		 * Entry for neither charset nor utf8 found
172		 */
173		return (0);
174	}
175
176	if (strcmp(c_charset, c_utf8) == 0)
177		return (1);
178	else
179		return (0);
180}
181
182static void
183conv_init(const char *charset)
184{
185	if (charset == NULL) {
186		/*
187		 * No conversion
188		 */
189		cd = (iconv_t)-1;
190		return;
191	}
192	if (check_utf8(charset)) {
193		/*
194		 * Charset is UTF-8.
195		 * No conversion is required.
196		 */
197		cd = (iconv_t)-1;
198		return;
199	}
200	cd = iconv_open(DEST_CHARSET, charset);
201	if (cd == (iconv_t)-1) {
202		/*
203		 * No such a conversion
204		 */
205		warning(gettext(WARN_NOCONV),
206			cur_line, cur_po, charset, DEST_CHARSET);
207		return;
208	}
209}
210
211void
212clear_state(void)
213{
214	next_entry_is_fuzzy = 0;
215	next_entry_is_c_format = 0;
216}
217
218void
219handle_domain(char *domainname)
220{
221	if (outfile) {
222		/*
223		 * outfile has been specified by -o option
224		 * ignore all domain directives
225		 */
226		if (verbose_flag) {
227			diag(gettext(DIAG_IGNORE_DOMAIN),
228				cur_line, cur_po, domainname);
229		}
230		free(domainname);
231		return;
232	}
233
234	if (strict_flag) {
235		/*
236		 * add ".mo" to the domain
237		 */
238		char	*tmp;
239		tmp = Xrealloc(domainname, strlen(domainname) + 3 + 1);
240		(void) strcat(tmp, ".mo");
241		domainname = tmp;
242	}
243	catalog_init(domainname);
244	free(domainname);
245}
246
247void
248catalog_init(const char *filename)
249{
250	struct catalog	*p;
251
252	if (!catalog_head) {
253		p = Xcalloc(1, sizeof (struct catalog));
254		p->fname = Xstrdup(filename);
255		p->msg_size = DEF_MSG_NUM;
256		p->nmsg = 0;
257		p->msg = Xcalloc(p->msg_size, sizeof (struct messages));
258		p->thash_size = find_prime(DEF_MSG_NUM);
259		p->thash = Xcalloc(p->thash_size, sizeof (unsigned int));
260		catalog_head = p;
261	} else {
262		p = catalog_head;
263		for (; ; ) {
264			struct catalog	*tmp;
265			if (strcmp(p->fname, filename) == 0) {
266				/* already registered */
267				break;
268			}
269			if (p->next) {
270				p = p->next;
271				continue;
272			}
273			/*
274			 * this domain hasn't been registered
275			 */
276			tmp = Xcalloc(1, sizeof (struct catalog));
277			tmp->fname = Xstrdup(filename);
278			tmp->msg_size = DEF_MSG_NUM;
279			tmp->nmsg = 0;
280			tmp->msg = Xcalloc(tmp->msg_size,
281			    sizeof (struct messages));
282			tmp->thash_size = find_prime(DEF_MSG_NUM);
283			tmp->thash = Xcalloc(tmp->thash_size,
284			    sizeof (unsigned int));
285			p->next = tmp;
286			p = tmp;
287			break;
288		}
289	}
290	cur_catalog = p;
291	cur_mo = p->fname;
292}
293
294
295void
296handle_comment(char *comment)
297{
298	char	*p;
299
300	p = comment;
301
302	if (*p != ',') {
303		/*
304		 * This comment is just informative only.
305		 */
306		free(comment);
307		return;
308	}
309	/*
310	 * Checks "fuzzy", "c-format", and "no-c-format"
311	 */
312	p++;
313	if (strstr(p, "fuzzy") != NULL) {
314		next_entry_is_fuzzy = 1;
315	}
316	if (strstr(p, "no-c-format") != NULL) {
317		next_entry_is_c_format = 0;
318	} else if (strstr(p, "c-format") != NULL) {
319		next_entry_is_c_format = 1;
320	}
321
322	free(comment);
323}
324
325void
326handle_message(struct entry *id, struct entry *str)
327{
328	char	*charset, *nplurals, *tmp, *p;
329	struct messages	*msg, *dupmsg;
330	size_t	len;
331	unsigned int	hash_val;
332	unsigned int	nmsg, n, thash_idx;
333
334	if (cur_mo == NULL) {
335		/*
336		 * output file hasn't been specified, nor
337		 * no domain directive found
338		 */
339		char	*default_domain;
340
341		default_domain = strict_flag ? DEFAULT_DOMAIN_MO :
342		    DEFAULT_DOMAIN;
343		catalog_init(default_domain);
344	}
345
346	/*
347	 * cur_catalog should be valid, at this point
348	 */
349
350	hash_val = hashpjw(id->str);
351	dupmsg = search_msg(cur_catalog, id->str, hash_val);
352
353	if (dupmsg) {
354		if ((dupmsg->str_len == str->len) &&
355		    (memcmp(dupmsg->str, str->str, str->len) == 0)) {
356			/* totally same entry */
357			if (verbose_flag) {
358				warning(gettext(WARN_DUP_ENTRIES),
359				    dupmsg->num, po_names[dupmsg->po],
360				    id->num, cur_po);
361			}
362			free(id->str);
363			if (id->pos)
364				free(id->pos);
365			free(str->str);
366			if (str->pos)
367				free(str->pos);
368			return;
369		}
370		/* duplicate msgid */
371		if (verbose_flag) {
372			diag(gettext(ERR_DUP_ENTRIES),
373			    dupmsg->num, po_names[dupmsg->po],
374			    id->num, cur_po);
375			po_error++;
376		}
377		/* ignore this etnry */
378		free(id->str);
379		if (id->pos)
380			free(id->pos);
381		free(str->str);
382		if (str->pos)
383			free(str->pos);
384		return;
385	}
386
387	if (next_entry_is_fuzzy) {
388		/* fuzzy entry */
389		cur_catalog->fnum++;
390		if (!fuzzy_flag) {
391			/* ignore this entry */
392			free(id->str);
393			if (id->pos)
394				free(id->pos);
395			free(str->str);
396			if (str->pos)
397				free(str->pos);
398			return;
399		}
400	}
401
402	if (str->len == str->no) {
403		/* this entry is not translated */
404		cur_catalog->unum++;
405		free(id->str);
406		if (id->pos)
407			free(id->pos);
408		free(str->str);
409		if (str->pos)
410			free(str->pos);
411		return;
412	}
413
414	/* Checks if this is the header entry */
415	if ((id->no == 1) && (id->len == 1)) {
416		/*
417		 * Header entry
418		 */
419		cur_catalog->header++;
420
421		/*
422		 * Need to extract the charset information
423		 */
424		charset = strstr(str->str, CHARSET_STR);
425		if (charset == NULL) {
426			/* no charset information */
427			warning(gettext(WARN_NOCHARSET),
428			    id->num, cur_po, str->num);
429			conv_init(NULL);
430		} else {
431			charset += CHARSET_LEN;
432			p = strpbrk(charset, " \t\n");
433			if (p != NULL) {
434				/* p points to a space, tab or new line char */
435				len = p - charset;
436			} else {
437				/* not found */
438				len = strlen(charset);
439			}
440			tmp = Xmalloc(len + 1);
441			(void) memcpy(tmp, charset, len);
442			*(tmp + len) = '\0';
443			charset = tmp;
444			conv_init(charset);
445			free(charset);
446		}
447		nplurals = strstr(str->str, NPLURALS_STR);
448		if (nplurals == NULL) {
449			cur_catalog->nplurals = 0;
450		} else {
451			unsigned int	num;
452			nplurals += NPLURALS_LEN;
453			p = nplurals;
454			num = 0;
455			while (isdigit((unsigned char)*p)) {
456				num = num * 10 + *p++ - '0';
457			}
458			cur_catalog->nplurals = num;
459		}
460	}
461
462	if (verbose_flag)
463		check_format(id, str, next_entry_is_c_format);
464
465	if (id->pos)
466		free(id->pos);
467	if (str->pos)
468		free(str->pos);
469
470	msg = cur_catalog->msg;
471	nmsg = cur_catalog->nmsg;
472
473	msg[nmsg].po = cur_po_index;
474	msg[nmsg].num = id->num;
475	msg[nmsg].id = id->str;
476	msg[nmsg].id_len = id->len;
477	msg[nmsg].str = str->str;
478	msg[nmsg].str_len = str->len;
479	msg[nmsg].hash = hash_val;
480
481	thash_idx = get_hash_index(cur_catalog->thash,
482	    hash_val, cur_catalog->thash_size);
483	cur_catalog->thash[thash_idx] = nmsg + 1;
484	cur_catalog->nmsg++;
485
486	if (cur_catalog->nmsg >= cur_catalog->msg_size) {
487		/* no vacancy in message array */
488		cur_catalog->msg_size += DEF_MSG_NUM;
489		cur_catalog->msg = Xrealloc(cur_catalog->msg,
490		    cur_catalog->msg_size * sizeof (struct messages));
491
492		cur_catalog->thash_size =
493			find_prime(cur_catalog->msg_size);
494		free(cur_catalog->thash);
495		cur_catalog->thash = Xcalloc(cur_catalog->thash_size,
496		    sizeof (unsigned int));
497
498		for (n = 0; n < cur_catalog->nmsg; n++) {
499			thash_idx = get_hash_index(cur_catalog->thash,
500			    cur_catalog->msg[n].hash,
501			    cur_catalog->thash_size);
502			cur_catalog->thash[thash_idx] = n + 1;
503		}
504	}
505}
506
507void
508po_init(const char *file)
509{
510	char	*filename;
511
512	if (!inputdir) {
513		filename = Xstrdup(file);
514	} else {
515		size_t	dirlen, filelen, len;
516
517		dirlen = strlen(inputdir);
518		filelen = strlen(file);
519		len = dirlen + 1 + filelen + 1;
520		filename = Xmalloc(len);
521		(void) memcpy(filename, inputdir, dirlen);
522		*(filename + dirlen) = '/';
523		(void) memcpy(filename + dirlen + 1, file, filelen);
524		*(filename + dirlen + 1 + filelen) = '\0';
525	}
526
527	fp = fopen(filename, "r");
528	if (fp == NULL) {
529		error(gettext(ERR_OPEN_FAILED), filename);
530		/* NOTREACHED */
531	}
532
533	po_names[cur_po_index] = filename;
534	cur_line = 1;
535	cd = (iconv_t)-1;
536	if (!outfile)
537		cur_mo = NULL;
538}
539
540void
541po_fini(void)
542{
543	cur_po_index++;
544	(void) fclose(fp);
545	if (cd != (iconv_t)-1)
546		(void) iconv_close(cd);
547}
548