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 2007 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 <stdio.h>
29#include <limits.h>
30#include <stdlib.h>
31#include <string.h>
32#include <stdarg.h>
33#include <libintl.h>
34#include <locale.h>
35#include <libgen.h>
36#include <ctype.h>
37#include <unistd.h>
38#include <signal.h>
39#include <sys/types.h>
40#include <sys/stat.h>
41
42#include "genmsg.h"
43
44#define	SET_TOKEN	"$set"
45#define	DELSET_TOKEN	"$delset"
46#define	QUOTE_TOKEN	"$quote"
47
48#define	SkipSpace(s)	while (*(s) == ' ' || *(s) == '\t') s++
49
50extern char *program;		/* from main.c */
51extern char *mctag;		/* from main.c */
52extern char *sctag;		/* from main.c */
53extern char *premsg;		/* from main.c */
54extern char *sufmsg;		/* from main.c */
55extern int suppress_error;	/* from main.c */
56extern void warning(char *);	/* from genmsg.l */
57
58typedef struct _SetID *SetID;
59typedef struct _MsgID *MsgID;
60
61typedef struct _SetID SetIDRec;
62struct _SetID {
63	int id;
64	char *comment;
65	MsgID top;
66	SetID next;
67};
68
69typedef struct _MsgID MsgIDRec;
70struct _MsgID {
71	int no_write;
72	int id;
73	char *msg;
74	int line;
75	char *file;
76	char *comment;
77	MsgID next;
78};
79
80
81/* Top pointer of the setid list. */
82static SetID setid_top;
83
84/* comment for messages. */
85static char *msg_comment;
86
87/* comment for set numbers. */
88static char *set_comment;
89
90/* List of set number's maximum message numbers. */
91static int msgid_table[NL_SETMAX+1];
92
93/* Quote character to surround messages. */
94static char quote = QUOTE;
95
96/* Internal functions. */
97static void add_msgid(SetID, int, char *, char *, int, int);
98static void add_setid(int, int, char *, char *, int, int);
99static SetID lookup_setid(int);
100static MsgID lookup_msgid(SetID, int, char *, char *, int);
101static void print_prefix(FILE *, char *, int, char *);
102static int is_bs_terminated(char *);
103static char *ustrdup(char *);
104static void makeup_msg(char **);
105
106void
107add_msg(int setid, int msgid, char *msg, char *file, int line, int no_write)
108{
109	SetID si;
110
111	if (si = lookup_setid(setid)) {
112		if (lookup_msgid(si, msgid, msg, file, line)) {
113			return; /* we already have the one. */
114		} else {
115			add_msgid(si, msgid, msg, file, line, no_write);
116		}
117	} else {
118		add_setid(setid, msgid, msg, file, line, no_write);
119	}
120}
121
122int
123is_writable(char *file)
124{
125	struct stat buf;
126
127	if (stat(file, &buf) == -1)
128		return (TRUE);
129
130	if (access(file, W_OK) == 0)
131		return (TRUE);
132
133	return (FALSE);
134}
135
136void
137write_msgfile(char *file)
138{
139	FILE *fp;
140	SetID si = setid_top;
141	char *mode = "w";
142	char pquote[2];
143
144	if (is_writable(file) == FALSE) {
145		prg_err(gettext("cannot create \"%s\": permission denied"),
146		    file);
147		return;
148	}
149
150	if (IsActiveMode(AppendMode)) {
151		mode = "a";
152	}
153
154	if ((fp = fopen(file, mode)) == NULL) {
155		prg_err(gettext("cannot create \"%s\""), file);
156		return;
157	}
158
159	if (quote) {
160		pquote[0] = quote;
161	} else {
162		pquote[0] = '\0';
163	}
164	pquote[1] = '\0';
165
166	/* AppendMode is already turned off if the file doesn't exist. */
167	if (!IsActiveMode(AppendMode)) {
168		(void) fprintf(fp, "\n$quote %s\n\n", pquote);
169	}
170
171	while (si) {
172		int is_set = FALSE;
173		MsgID mi = si->top;
174		while (mi) {
175			char msg[NL_TEXTMAX+32]; /* 32 is some other stuff. */
176
177			if (mi->no_write) {
178				mi = mi->next;
179				continue;
180			}
181			if (is_set == FALSE) {
182				if (si->comment &&
183				    !IsActiveMode(BackCommentMode)) {
184					(void) fprintf(fp, "\n");
185					print_prefix(fp, "$ ", TRUE,
186					    si->comment);
187					(void) fprintf(fp, "$set\t%d\n",
188					    si->id);
189				} else {
190					(void) fprintf(fp, "\n$set\t%d\n",
191					    si->id);
192				}
193				if (si->comment &&
194				    IsActiveMode(BackCommentMode)) {
195					print_prefix(fp, "$ ", TRUE,
196					    si->comment);
197				}
198				(void) fprintf(fp, "\n");
199				is_set = TRUE;
200			}
201
202			makeup_msg(&(mi->msg));
203
204			(void) snprintf(msg, sizeof (msg), "%d\t%s%s%s\n",
205			    mi->id, pquote, mi->msg, pquote);
206
207			if (!IsActiveMode(BackCommentMode)) {
208				if (mi->line && mi->file &&
209				    IsActiveMode(LineInfoMode)) {
210					(void) fprintf(fp,
211					    "$ File:%s, line:%d\n",
212					    basename(mi->file), mi->line);
213				}
214
215				if (mi->comment) {
216					print_prefix(fp, "$ ", TRUE,
217					    mi->comment);
218				}
219
220				if (IsActiveMode(DoubleLineMode)) {
221					print_prefix(fp, "$ ", FALSE, msg);
222				}
223			}
224
225			(void) fprintf(fp, "%s", msg);
226
227			if (IsActiveMode(BackCommentMode)) {
228				if (mi->line && mi->file &&
229				    IsActiveMode(LineInfoMode)) {
230					(void) fprintf(fp,
231					    "$ File:%s, line:%d\n",
232					    basename(mi->file), mi->line);
233				}
234
235				if (mi->comment) {
236					print_prefix(fp, "$ ", TRUE,
237					    mi->comment);
238				}
239
240				if (IsActiveMode(DoubleLineMode)) {
241					print_prefix(fp, "$ ", FALSE, msg);
242				}
243			}
244
245			(void) fprintf(fp, "\n");
246
247			mi = mi->next;
248		}
249		si = si->next;
250	}
251
252	(void) fclose(fp);
253}
254
255static SetID
256lookup_setid(int id)
257{
258	SetID si = setid_top;
259	while (si) {
260		if (si->id == id) {
261			return (si);
262		}
263		si = si->next;
264	}
265	return (NULL);
266}
267
268static MsgID
269lookup_msgid(SetID si, int msgid, char *msg, char *file, int line)
270{
271	MsgID mi = si->top;
272	while (mi) {
273		if (mi->id == msgid) {
274			/* same setid & msgid, but different msg. */
275			if (strcmp(mi->msg, msg)) {
276				src_err(file, line, gettext(
277			"multiple messages: set number %d, message number %d\n"
278			"	current : \"%s\"\n"
279			"	previous: \"%s\" : \"%s\", line %d"),
280				    si->id, mi->id,
281				    msg,
282				    mi->msg, mi->file, mi->line);
283			}
284			return (mi);
285		}
286		mi = mi->next;
287	}
288	return (NULL);
289}
290
291static void
292add_msgid(SetID si, int msgid, char *msg, char *file, int line, int no_write)
293{
294	MsgID mi = si->top, newmi, prev = NULL;
295	int len = strlen(msg);
296
297	if (msgid == 0) {
298		src_err(file, line, gettext("improper message number: %d"),
299		    msgid);
300		return;
301	}
302
303	if (msgid > NL_MSGMAX) {
304		src_err(file, line, gettext("too large message number: %d"),
305		    msgid);
306		return;
307	}
308
309	if (len > NL_TEXTMAX) {
310		src_err(file, line, gettext("too long message text"));
311		return;
312	}
313
314	while (mi) {
315		if (mi->id > msgid) {
316			break;
317		}
318		prev = mi;
319		mi = mi->next;
320	}
321
322	if ((newmi = malloc(sizeof (MsgIDRec))) == NULL) {
323		prg_err(gettext("fatal: out of memory"));
324		exit(EXIT_FAILURE);
325	}
326
327	newmi->no_write = no_write;
328	newmi->id = msgid;
329	newmi->msg = ustrdup(msg);
330	newmi->file = ustrdup(file);
331	newmi->line = line;
332	newmi->next = mi;
333
334	if (msg_comment) {
335		newmi->comment = ustrdup(msg_comment);
336		free(msg_comment);
337		msg_comment = NULL;
338	} else {
339		newmi->comment = NULL;
340	}
341
342	if (prev == NULL) {
343		si->top = newmi;
344	} else {
345		prev->next = newmi;
346	}
347}
348
349static void
350add_setid(int setid, int msgid, char *msg, char *file, int line, int no_write)
351{
352	SetID si = setid_top, newsi, prev = NULL;
353
354	while (si) {
355		if (si->id > setid) {
356			break;
357		}
358		prev = si;
359		si = si->next;
360	}
361
362	if ((newsi = malloc(sizeof (SetIDRec))) == NULL) {
363		prg_err(gettext("fatal: out of memory"));
364		exit(EXIT_FAILURE);
365	}
366
367	newsi->id = setid;
368	newsi->top = NULL;
369	newsi->next = si;
370
371	if (set_comment) {
372		newsi->comment = ustrdup(set_comment);
373		free(set_comment);
374		set_comment = NULL;
375	} else {
376		newsi->comment = NULL;
377	}
378
379	if (prev == NULL) {
380		setid_top = newsi;
381	} else {
382		prev->next = newsi;
383	}
384
385	add_msgid(newsi, msgid, msg, file, line, no_write);
386}
387
388static void
389print_prefix(FILE *fp, char *prefix, int rm_blank, char *str)
390{
391	(void) fprintf(fp, "%s", prefix);
392	while (*str) {
393		(void) fputc(*str, fp);
394		if (*str == '\n' && *(str+1) != '\0') {
395			(void) fprintf(fp, "%s", prefix);
396			if (rm_blank == TRUE) {
397				str++;
398				SkipSpace(str);
399				continue;
400			}
401		}
402		str++;
403	}
404	if (*(str-1) != '\n') {
405		(void) fputc('\n', fp);
406	}
407}
408
409int
410read_projfile(char *file)
411{
412	FILE *fp;
413	char line[LINE_MAX];
414
415	if (file == NULL) {
416		return (0);
417	}
418
419	if ((fp = fopen(file, "r")) == NULL) {
420		return (0);
421	}
422
423	while (fgets(line, sizeof (line), fp) != NULL) {
424		char *p = line;
425		int n, setid, msgid;
426
427		SkipSpace(p);
428
429		if (*p == '#' || *p == '\n') {
430			continue;
431		}
432
433		n = sscanf(p, "%d %d", &setid, &msgid);
434
435		if (n == 2) {
436			if (setid > NL_SETMAX) {
437				prg_err(gettext("%s: too large set number: %d"),
438				    file, setid);
439				continue;
440			}
441			msgid_table[setid] = msgid;
442		} else {
443			prg_err(gettext(
444			    "warning: %s: missing or invalid entry"), file);
445		}
446	}
447
448	(void) fclose(fp);
449
450	return (1);
451}
452
453void
454write_projfile(char *file)
455{
456	FILE *fp;
457	register int i;
458
459	if (is_writable(file) == FALSE) {
460		prg_err(gettext("cannot create \"%s\": permission denied"),
461		    file);
462		return;
463	}
464
465	if ((fp = fopen(file, "w")) == NULL) {
466		prg_err(gettext("cannot create \"%s\""), file);
467		return;
468	}
469
470	for (i = 1; i <= NL_SETMAX; i++) {
471		if (msgid_table[i] > 0) {
472			SetID si;
473			char *com = NULL;
474
475			if (IsActiveMode(SetCommentMode) &&
476			    (si = lookup_setid(i)) && si->comment) {
477				com = si->comment;
478			}
479
480			if (com && !IsActiveMode(BackCommentMode)) {
481				print_prefix(fp, "# ", TRUE, com);
482			}
483
484			(void) fprintf(fp, "%d\t%d\n", i, msgid_table[i]);
485
486			if (com && IsActiveMode(BackCommentMode)) {
487				print_prefix(fp, "# ", TRUE, com);
488			}
489		}
490	}
491
492	(void) fclose(fp);
493}
494
495int
496get_msgid(char *file, int line, int setid, char *str)
497{
498	SetID si = setid_top;
499	int id = msgid_table[setid];
500
501	while (si) {
502		if (si->id == setid) {
503			MsgID mi = si->top;
504			while (mi) {
505				if (strcmp(mi->msg, str) == 0) {
506					return (mi->id);
507				}
508				mi = mi->next;
509			}
510		}
511		si = si->next;
512	}
513
514	id++;
515
516	if (id > NL_MSGMAX) {
517		src_err(file, line,
518		    gettext("run out of message number in set number: %d"),
519		    setid);
520		return (NOMSGID);
521	}
522
523	return (msgid_table[setid] = id);
524}
525
526void
527set_msgid(int setid, int msgid)
528{
529	if (msgid_table[setid] < msgid) {
530		msgid_table[setid] = msgid;
531	}
532}
533
534void
535add_comment(Mode mode, char *str)
536{
537	char *tag = (mode == MsgCommentMode) ? mctag : sctag;
538	char **comment = (mode == MsgCommentMode)
539	    ? &msg_comment : &set_comment;
540
541	if (strstr(str, tag) == NULL) {
542		return;
543	}
544
545	if (*comment) {
546		free(*comment);
547	}
548
549	*comment = ustrdup(str);
550}
551
552void
553read_msgfile(char *file)
554{
555	FILE *fp;
556	char c = 0;
557	int line = 0;
558	int inmsg = FALSE;
559	int setid = 0, unsetid = -1, msgid = 0;
560	struct stat buf;
561
562	if ((fp = fopen(file, "r")) == NULL) {
563		prg_err(gettext("cannot open \"%s\""), file);
564		ResetActiveMode(AppendMode);
565		return;
566	}
567
568	if (stat(file, &buf) == -1 && buf.st_size == 0) {
569		ResetActiveMode(AppendMode);
570		return;
571	}
572
573	quote = c;
574
575	/*CONSTCOND*/
576	while (1) {
577		char buf[LINE_MAX];
578		char *ptr;
579		char msg[NL_TEXTMAX];
580
581		if (fgets(buf, sizeof (buf), fp) == NULL) {
582			break;
583		}
584
585		line++;
586
587		ptr = &buf[0];
588
589		SkipSpace(ptr);
590
591		if ((*ptr == '$' && (*(ptr+1) == ' ' || *(ptr+1) == '\t')) ||
592		    ((*ptr == '\n') && inmsg == FALSE)) {
593			inmsg = FALSE;
594			continue;
595		}
596
597		if (strncmp(ptr, SET_TOKEN, sizeof (SET_TOKEN) - 1) == 0) {
598			if (sscanf(ptr, "%*s %d", &setid) != 1) {
599				setid = 0;
600			}
601			inmsg = FALSE;
602			continue;
603		} else if (strncmp(ptr, DELSET_TOKEN,
604		    sizeof (DELSET_TOKEN) - 1) == 0) {
605			if (sscanf(ptr, "%*s %d", &unsetid) != 1) {
606				unsetid = -1;
607			}
608			inmsg = FALSE;
609			continue;
610		} else if (strncmp(ptr, QUOTE_TOKEN,
611		    sizeof (QUOTE_TOKEN) - 1) == 0) {
612			if (sscanf(ptr, "%*s %c", &c) != 1) {
613				c = 0;
614			}
615			quote = c;
616			inmsg = FALSE;
617			continue;
618		}
619
620		if (setid == unsetid) {
621			continue;
622		}
623
624		if (inmsg) {
625			if (is_bs_terminated(ptr)) {
626				(void) strlcat(msg, ptr, sizeof (msg));
627				inmsg = TRUE;
628			} else {
629				int len = strlen(ptr);
630				*(ptr + len - 1) = '\0';
631				if (c && (*(ptr + len - 2) == c)) {
632					*(ptr + len - 2) = '\0';
633				}
634				(void) strlcat(msg, ptr, sizeof (msg));
635				add_msg(setid, msgid, msg, file, line, TRUE);
636				inmsg = FALSE;
637			}
638			continue;
639		}
640
641		if (isdigit((unsigned char)*ptr)) {
642			char	*pptr;
643
644			SkipSpace(ptr);
645
646			msgid = (int)strtol(ptr, &pptr, 10);
647			ptr = pptr;
648
649			SkipSpace(ptr);
650
651			if (is_bs_terminated(ptr)) {
652				(void) memset(msg, 0, sizeof (msg));
653				if (c && (*ptr == c)) {
654					ptr++;
655				}
656				(void) strlcpy(msg, ptr, sizeof (msg));
657				inmsg = TRUE;
658			} else {
659				int len = strlen(ptr);
660				*(ptr + len - 1) = '\0';
661				if (c && ((*ptr == c) &&
662				    (*(ptr + len - 2) == c))) {
663					*(ptr + len - 2) = '\0';
664					ptr++;
665				}
666				add_msg(setid, msgid, ptr, file, line, TRUE);
667				inmsg = FALSE;
668			}
669		}
670	}
671
672	(void) fclose(fp);
673}
674
675static int
676is_bs_terminated(char *msg)
677{
678	int len = strlen(msg);
679
680	while (--len >= 0) {
681		if (msg[len] == ' ' || msg[len] == '\t' || msg[len] == '\n') {
682			continue;
683		} else if (msg[len] == '\\') {
684			len--;
685			if (len >= 0 && msg[len] == '\\')
686				return (0);
687			return (1);
688		} else {
689			return (0);
690		}
691	}
692	return (0);
693}
694
695static char *
696ustrdup(char *str)
697{
698	char *tmp = strdup(str);
699	if (tmp == NULL) {
700		prg_err(gettext("fatal: out of memory"));
701		exit(EXIT_FAILURE);
702	}
703	return (tmp);
704}
705
706int
707file_copy(char *in, char *out)
708{
709	int ret = TRUE;
710	FILE *fin, *fout;
711	int c;
712	sigset_t newmask, oldmask;
713
714	(void) sigemptyset(&newmask);
715	(void) sigaddset(&newmask, SIGQUIT);
716	(void) sigaddset(&newmask, SIGINT);
717	(void) sigaddset(&newmask, SIGHUP);
718	(void) sigaddset(&newmask, SIGTERM);
719	(void) sigprocmask(SIG_BLOCK, &newmask, &oldmask);
720
721	if ((fin = fopen(in, "r")) == NULL) {
722		prg_err(gettext("cannot open \"%s\""), in);
723		ret = FALSE;
724		goto done;
725	}
726
727	if ((fout = fopen(out, "w")) == NULL) {
728		prg_err(gettext("cannot create \"%s\""), out);
729		ret = FALSE;
730		goto done;
731	}
732
733	while ((c = getc(fin)) != EOF)
734		(void) putc(c, fout);
735
736	(void) fclose(fin);
737	(void) fclose(fout);
738
739done:
740	(void) sigprocmask(SIG_SETMASK, &oldmask, NULL);
741	return (ret);
742}
743
744static void
745makeup_msg(char **pmsg)
746{
747	char buf[NL_TEXTMAX];
748	char *msg;
749
750	msg = *pmsg;
751	buf[0] = '\0';
752
753	if (IsActiveMode(TripleMode) &&	strchr(msg, '%') == NULL) {
754		/* there is no '%' in message. */
755		int len = strlen(msg);
756
757		if (msg[len-2] == '\\' && msg[len-1] == 'n') {
758			msg[len-2] = '\0';
759			(void) strlcat(buf, msg, sizeof (buf));
760			(void) strlcat(buf, msg, sizeof (buf));
761			(void) strlcat(buf, msg, sizeof (buf));
762			(void) strlcat(buf, "\\n", sizeof (buf));
763		} else {
764			(void) strlcat(buf, msg, sizeof (buf));
765			(void) strlcat(buf, msg, sizeof (buf));
766			(void) strlcat(buf, msg, sizeof (buf));
767		}
768		free(msg);
769		*pmsg = ustrdup(buf);
770	}
771
772	msg = *pmsg;
773	buf[0] = '\0';
774
775	if (IsActiveMode(PrefixMode)) {
776		(void) strlcat(buf, premsg, sizeof (buf));
777		(void) strlcat(buf, msg, sizeof (buf));
778		free(msg);
779		*pmsg = ustrdup(buf);
780	}
781
782	msg = *pmsg;
783	buf[0] = '\0';
784
785	if (IsActiveMode(SuffixMode)) {
786		int len = strlen(msg);
787
788		if (msg[len-2] == '\\' && msg[len-1] == 'n') {
789			msg[len-2] = '\0';
790			(void) strlcat(buf, msg, sizeof (buf));
791			(void) strlcat(buf, sufmsg, sizeof (buf));
792			(void) strlcat(buf, "\\n", sizeof (buf));
793		} else {
794			(void) strlcat(buf, msg, sizeof (buf));
795			(void) strlcat(buf, sufmsg, sizeof (buf));
796		}
797		free(msg);
798		*pmsg = ustrdup(buf);
799	}
800}
801
802void
803prg_err(char *fmt, ...)
804{
805	va_list ap;
806
807	va_start(ap, fmt);
808
809	(void) fprintf(stderr, "%s: ", program);
810	/* LINTED: E_SEC_PRINTF_VAR_FMT */
811	(void) vfprintf(stderr, fmt, ap);
812	(void) fprintf(stderr, "\n");
813
814	va_end(ap);
815}
816
817void
818src_err(char *file, int line, char *fmt, ...)
819{
820	va_list ap;
821
822	if (suppress_error == TRUE) {
823		return;
824	}
825
826	va_start(ap, fmt);
827
828	(void) fprintf(stderr, gettext("\"%s\", line %d: "), file, line);
829	/* LINTED: E_SEC_PRINTF_VAR_FMT */
830	(void) vfprintf(stderr, fmt, ap);
831	(void) fprintf(stderr, "\n");
832
833	va_end(ap);
834}
835