xref: /illumos-gate/usr/src/tools/sgs/sgsmsg/sgsmsg.c (revision b69c34dad3717624ff6b4f32b71014ee05b6a678)
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 (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
23  *
24  * sgsmsg generates several message files from an input template file.  Messages
25  * are constructed for use with gettext(3i) - the default - or catgets(3c).  The
26  * files generate are:
27  *
28  * msg.h	a header file containing definitions for each message.  The -h
29  *		option triggers the creation of these definitions and specifies
30  *		the name to use.
31  *
32  * msg.c	a data array of message strings.  The msg.h definitions are
33  *		offsets into this array.  The -d option triggers the creation of
34  *		these definitions and specifies the name to use.
35  *
36  * messages	a message file suitable for catgets(3c) or gettext(3i) use.  The
37  *		-m option triggers this output and specifies the filename to be
38  *		used.
39  *
40  * The template file is processed based on the first character of each line:
41  *
42  * # or $	entries are copied (as is) to the message file (messages).
43  *
44  * @ token(s)	entries are translated.  Two translations are possible dependent
45  *		on whether one or more tokens are supplied:
46  *
47  *		A single token is interpreted as one of two reserved message
48  *		output indicators, or a message identifier.  The reserved output
49  *		indicator _START_ enables output to the message file - Note that
50  *		the occurance of any other @ token will also enable message
51  *		output.  The reserved output indicator _END_ disables output to
52  *		the message file.  The use of these two indicators provides for
53  *		only those message strings that require translation to be output
54  *		to the message file.
55  *
56  *		Besides the reserved output indicators, a single token is taken
57  *		to be a message identifier which will be subsituted for a
58  *		`setid' for catgets(3c) output, or a `domain' name for
59  *		gettext(3i) output.  This value is determine by substituting the
60  *		token for the associated definition found in the message
61  *		identifier file (specified with the -i option).
62  *
63  *		Multiple tokens are taken to be a message definition followed by
64  *		the associated message string.  The message string is copied to
65  *		the data array being built in msg.c.  The index into this array
66  *		becomes the `message' identifier created in the msg.h file.
67  */
68 
69 #include	<fcntl.h>
70 #include	<stdlib.h>
71 #include	<stdio.h>
72 #include	<unistd.h>
73 #include	<limits.h>
74 #include	<string.h>
75 #include	<ctype.h>
76 #include	<errno.h>
77 #include	<sys/param.h>
78 
79 #include	<sgs.h>
80 #include	<_string_table.h>
81 
82 /*
83  * Define any error message strings.
84  */
85 static const char
86 	* Errmsg_malt =	"sgsmsg: file %s: line %d: malformed input "
87 			"at line\n",
88 	* Errmsg_nmem =	"sgsmsg: memory allocation failed: %s\n",
89 	* Errmsg_opne =	"sgsmsg: file %s: open failed: %s\n",
90 	* Errmsg_wrte =	"sgsmsg: file %s: write failed: %s\n",
91 	* Errmsg_read =	"sgsmsg: file %s: read failed %s\n",
92 	* Errmsg_stnw =	"sgsmsg: st_new(): failed: %s\n",
93 	* Errmsg_stin =	"sgsmsg: Str_tbl insert failed: %s\n",
94 	* Errmsg_mnfn =	"sgsmsg: message not found in Str_tbl: %s\n",
95 	* Errmsg_use  =	"usage: sgsmsg [-clv] [-d mesgdata] [-h mesgdefs] "
96 			"[-m messages] [-n name] [-i mesgident] file ...\n";
97 
98 /*
99  * Define all output filenames and associated descriptors.
100  */
101 static FILE	*fddefs, *fddata, *fdmsgs, *fdmids, *fddesc;
102 static char	*fldefs, *fldata, *flmsgs, *flmids, *fldesc;
103 static FILE	*fdlint;
104 static char	fllint[MAXPATHLEN];
105 
106 static uint_t		vflag;	/* verbose flag */
107 static Str_tbl		*stp;	/* string table */
108 
109 /*
110  * Define any default strings.
111  */
112 static const char
113 	*nmlint =	"/tmp/sgsmsg.lint",
114 	*interface =	"sgs_msg",
115 	*start =	"_START_",
116 	*end =		"_END_";
117 
118 /*
119  * Define any default flags and data items.
120  */
121 static int	cflag = 0, lflag = 0, prtmsgs = 0, line, ptr = 1, msgid = 0;
122 static char	*mesgid = 0, *setid = 0, *domain = 0;
123 
124 typedef struct msg_string {
125 	char			*ms_defn;
126 	char			*ms_message;
127 	struct msg_string	*ms_next;
128 } msg_string;
129 
130 static msg_string	*msg_head;
131 static msg_string	*msg_tail;
132 
133 /*
134  * message_append() is responsible for both inserting strings into
135  * the master Str_tbl as well as maintaining a list of the
136  * DEFINITIONS associated with each string.
137  *
138  * The list of strings is traversed at the end once the full
139  * Str_tbl has been constructed - and string offsets can be
140  * assigned.
141  */
142 static void
143 message_append(const char *defn, const char *message)
144 {
145 	msg_string	*msg;
146 	if ((msg = calloc(sizeof (msg_string), 1)) == 0) {
147 		(void) fprintf(stderr, Errmsg_nmem, strerror(errno));
148 		exit(1);
149 	}
150 
151 	/*
152 	 * Initialize the string table.
153 	 */
154 	if ((stp == 0) && ((stp = st_new(FLG_STNEW_COMPRESS)) == NULL)) {
155 		(void) fprintf(stderr, Errmsg_stnw, strerror(errno));
156 		exit(1);
157 	}
158 
159 
160 	if ((msg->ms_defn = strdup(defn)) == 0) {
161 		(void) fprintf(stderr, Errmsg_nmem, strerror(errno));
162 		exit(1);
163 	}
164 	if ((msg->ms_message = strdup(message)) == 0) {
165 		(void) fprintf(stderr, Errmsg_nmem, strerror(errno));
166 		exit(1);
167 	}
168 
169 	if (st_insert(stp, msg->ms_message) == -1) {
170 		(void) fprintf(stderr, Errmsg_stin,
171 		    message);
172 		exit(1);
173 	}
174 
175 	if (msg_head == 0) {
176 		msg_head = msg_tail = msg;
177 		return;
178 	}
179 	msg_tail->ms_next = msg;
180 	msg_tail = msg;
181 }
182 
183 /*
184  * Initialize a setid value.  Given a setid definition determine its numeric
185  * value from the specified message identifier file (specified with the -i
186  * option).  Return a pointer to the numeric string.
187  */
188 static int
189 getmesgid(char *id)
190 {
191 	char	*buffer, *token, *_mesgid = 0, *_setid = 0, *_domain = 0;
192 
193 	/*
194 	 * If we're being asked to interpret a message id but the user didn't
195 	 * provide the required message identifier file (-i option) we're in
196 	 * trouble.
197 	 */
198 	if (flmids == 0) {
199 		(void) fprintf(stderr, "sgsmsg: file %s: line %d: mesgid %s: "
200 		    "unable to process mesgid\n\t"
201 		    "no message identifier file specified "
202 		    "(see -i option)\n", fldesc, line, id);
203 		return (1);
204 	}
205 
206 	if ((buffer = malloc(LINE_MAX)) == 0) {
207 		(void) fprintf(stderr, Errmsg_nmem, strerror(errno));
208 		return (1);
209 	}
210 
211 	/*
212 	 * Read the message identifier file and locate the required mesgid.
213 	 */
214 	rewind(fdmids);
215 	while (fgets(buffer, LINE_MAX, fdmids) != NULL) {
216 		if ((token = strstr(buffer, id)) == NULL)
217 			continue;
218 
219 		/*
220 		 * Establish individual strings for the mesgid, setid and domain
221 		 * values.
222 		 */
223 		_mesgid = token;
224 		while (!(isspace(*token)))
225 			token++;
226 		*token++ = 0;
227 
228 		while (isspace(*token))
229 			token++;
230 		_setid = token;
231 		while (!(isspace(*token)))
232 			token++;
233 		*token++ = 0;
234 
235 		while (isspace(*token))
236 			token++;
237 		_domain = token;
238 		while (!(isspace(*token)))
239 			token++;
240 		*token = 0;
241 		break;
242 	}
243 
244 	/*
245 	 * Did we find a match?
246 	 */
247 	if ((_mesgid == 0) || (_setid == 0) || (_domain == 0)) {
248 		(void) fprintf(stderr, "sgsmsg: file %s: line %d: mesgid %s: "
249 		    "unable to process mesgid\n\t"
250 		    "identifier does not exist in file %s\n",
251 		    fldesc, line, id, flmids);
252 		return (1);
253 	}
254 
255 	/*
256 	 * Have we been here before?
257 	 */
258 	if (mesgid) {
259 		if (cflag == 1) {
260 			/*
261 			 * If we're being asked to process more than one mesgid
262 			 * warn the user that only one mesgid can be used for
263 			 * the catgets(3c) call.
264 			 */
265 			(void) fprintf(stderr, "sgsmsg: file %s: line %d: "
266 			    "setid %s: warning: multiple mesgids "
267 			    "encountered\n\t"
268 			    "last setting used in messaging code\n",
269 			    fldesc, line, id);
270 		}
271 	}
272 
273 	mesgid = _mesgid;
274 	setid = _setid;
275 	domain = _domain;
276 
277 	/*
278 	 * Generate the message file output (insure output flag is enabled).
279 	 */
280 	if (prtmsgs != -1)
281 		prtmsgs = 1;
282 	if (fdmsgs && (prtmsgs == 1)) {
283 		if (cflag == 1) {
284 			if (fprintf(fdmsgs, "$quote \"\n$set %s\n",
285 			    setid) < 0) {
286 				(void) fprintf(stderr, Errmsg_wrte, flmsgs,
287 				    strerror(errno));
288 				return (1);
289 			}
290 		} else {
291 			if (fprintf(fdmsgs, "domain\t\"%s\"\n", domain) < 0) {
292 				(void) fprintf(stderr, Errmsg_wrte, flmsgs,
293 				    strerror(errno));
294 				return (1);
295 			}
296 		}
297 	}
298 
299 	/*
300 	 * For catgets(3c) output generate a setid definition in the message
301 	 * definition file.
302 	 */
303 	if (fddefs && (cflag == 1) &&
304 	    (fprintf(fddefs, "#define\t%s\t%s\n\n", mesgid, setid) < 0)) {
305 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
306 		return (1);
307 	}
308 
309 	return (0);
310 }
311 
312 /*
313  * Dump contents of String Table to standard out
314  */
315 static void
316 dump_stringtab(Str_tbl *stp)
317 {
318 	uint_t	cnt;
319 
320 	if ((stp->st_flags & FLG_STTAB_COMPRESS) == 0) {
321 		(void) printf("string table full size: %ld: uncompressed\n",
322 		    stp->st_fullstrsize);
323 		return;
324 	}
325 
326 	(void) printf("string table full size: %ld compressed down to: %ld\n\n",
327 	    stp->st_fullstrsize, stp->st_strsize);
328 	(void) printf("string table compression information [%d buckets]:\n",
329 	    stp->st_hbckcnt);
330 
331 	for (cnt = 0; cnt < stp->st_hbckcnt; cnt++) {
332 		Str_hash	*sthash = stp->st_hashbcks[cnt];
333 
334 		if (sthash == 0)
335 			continue;
336 
337 		(void) printf(" bucket: [%d]\n", cnt);
338 
339 		while (sthash) {
340 			size_t	stroff = sthash->hi_mstr->sm_strlen -
341 			    sthash->hi_strlen;
342 
343 			if (stroff == 0) {
344 				(void) printf("  [%ld]: '%s'  <master>\n",
345 				    sthash->hi_refcnt, sthash->hi_mstr->sm_str);
346 			} else {
347 				(void) printf("  [%ld]: '%s'  <suffix of: "
348 				    "'%s'>\n", sthash->hi_refcnt,
349 				    &sthash->hi_mstr->sm_str[stroff],
350 				    sthash->hi_mstr->sm_str);
351 			}
352 			sthash = sthash->hi_next;
353 		}
354 	}
355 }
356 
357 /*
358  * Initialize the message definition header file stream.
359  */
360 static int
361 init_defs(void)
362 {
363 	static char	guard[FILENAME_MAX + 6];
364 	char		*optr;
365 	const char	*iptr, *_ptr;
366 
367 	/*
368 	 * Establish a header guard name using the files basename.
369 	 */
370 	for (iptr = 0, _ptr = fldefs; _ptr && (*_ptr != '\0'); _ptr++) {
371 		if (*_ptr == '/')
372 			iptr = _ptr + 1;
373 	}
374 	if (iptr == 0)
375 		iptr = fldefs;
376 
377 	optr = guard;
378 	for (*optr++ = '_'; iptr && (*iptr != '\0'); iptr++, optr++) {
379 		if (*iptr == '.') {
380 			*optr++ = '_';
381 			*optr++ = 'D';
382 			*optr++ = 'O';
383 			*optr++ = 'T';
384 			*optr = '_';
385 		} else
386 			*optr = toupper(*iptr);
387 	}
388 
389 	if (fprintf(fddefs, "#ifndef\t%s\n#define\t%s\n\n", guard, guard) < 0) {
390 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
391 		return (1);
392 	}
393 
394 	if (fprintf(fddefs, "#include <sgsmsg.h>\t/* Msg typedef */\n\n") < 0) {
395 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
396 		return (1);
397 	}
398 
399 	if (fprintf(fddefs, "#ifndef\t__lint\n\n") < 0) {
400 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
401 		return (1);
402 	}
403 
404 	/*
405 	 * The MSG_SGS_ARRAY_NAME macro supplies a generic way to
406 	 * reference the string table regardless of its name.
407 	 */
408 	if (fprintf(fddefs, "#define\tMSG_SGS_LOCAL_ARRAY\t__%s\n\n",
409 	    interface) < 0) {
410 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
411 		return (1);
412 	}
413 
414 	/*
415 	 * If the associated data array is global define a prototype.
416 	 * Define a macro to access the array elements.
417 	 */
418 	if (lflag == 0) {
419 		if (fprintf(fddefs, "extern\tconst char\t__%s[];\n\n",
420 		    interface) < 0) {
421 			(void) fprintf(stderr, Errmsg_wrte, fldefs,
422 			    strerror(errno));
423 			return (1);
424 		}
425 	}
426 	if (fprintf(fddefs,
427 	    "#define\tMSG_ORIG_STRTAB(_x, _s)\t&_s[_x]\n\n") < 0) {
428 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
429 		return (1);
430 	}
431 	if (fprintf(fddefs,
432 	    "#define\tMSG_ORIG(x)\tMSG_ORIG_STRTAB(x, __%s)\n\n",
433 	    interface) < 0) {
434 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
435 		return (1);
436 	}
437 
438 	/*
439 	 * Generate a prototype to access the associated data array.
440 	 */
441 	if (fprintf(fddefs, "extern\tconst char *\t_%s(Msg);\n\n",
442 	    interface) < 0) {
443 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
444 		return (1);
445 	}
446 	if (fprintf(fddefs, "#define\tMSG_INTL(x)\t_%s(x)\n\n",
447 	    interface) < 0) {
448 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
449 		return (1);
450 	}
451 
452 	return (0);
453 }
454 
455 
456 /*
457  * Finish the message definition header file.
458  */
459 static int
460 fini_defs(void)
461 {
462 	if (fprintf(fddefs, "\n#else\t/* __lint */\n\n") < 0) {
463 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
464 		return (1);
465 	}
466 
467 	if (fprintf(fddefs, "extern\tconst char *\t_%s(Msg);\n\n",
468 	    interface) < 0) {
469 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
470 		return (1);
471 	}
472 
473 	if (fprintf(fddefs, "#ifndef MSG_SGS_LOCAL_ARRAY\n"
474 	    "#define\tMSG_SGS_LOCAL_ARRAY\t\"\"\n#endif\n\n") < 0) {
475 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
476 		return (1);
477 	}
478 
479 	if (lflag == 0) {
480 		if (fprintf(fddefs, "extern\tconst char\t__%s[];\n\n",
481 		    interface) < 0) {
482 			(void) fprintf(stderr, Errmsg_wrte, fldefs,
483 			    strerror(errno));
484 			return (1);
485 		}
486 	}
487 
488 	if (fprintf(fddefs,
489 	    "#define MSG_ORIG_STRTAB(_x, _s)\t_x\n"
490 	    "#define MSG_ORIG(x)\tx\n#define MSG_INTL(x)\tx\n") < 0) {
491 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
492 		return (1);
493 	}
494 
495 	/*
496 	 * Provide a way to get the array and function declarations above
497 	 * without also getting the actual messages. This is useful in
498 	 * our lintsup.c files that include more than one message header.
499 	 * lintsup doesn't need the actual messages, and this prevents
500 	 * macro name collisions.
501 	 */
502 	if (fprintf(fddefs, "\n#ifndef LINTSUP_SUPPRESS_STRINGS\n") < 0) {
503 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
504 		return (1);
505 	}
506 
507 	/*
508 	 * Copy the temporary lint defs file into the new header.
509 	 */
510 	if (fdlint) {
511 		long	size;
512 		char	*buf;
513 
514 		size = ftell(fdlint);
515 		(void) rewind(fdlint);
516 
517 		if ((buf = malloc(size)) == 0) {
518 			(void) fprintf(stderr, Errmsg_nmem, strerror(errno));
519 			return (1);
520 		}
521 		if (fread(buf, size, 1, fdlint) == 0) {
522 			(void) fprintf(stderr, Errmsg_read, fllint,
523 			    strerror(errno));
524 			return (1);
525 		}
526 		if (fwrite(buf, size, 1, fddefs) == 0) {
527 			(void) fprintf(stderr, Errmsg_wrte, fldefs,
528 			    strerror(errno));
529 			return (1);
530 		}
531 		(void) free(buf);
532 	}
533 
534 	if (fprintf(fddefs, "\n#endif\t/* LINTSUP_SUPPRESS_STRINGS */\n") < 0) {
535 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
536 		return (1);
537 	}
538 
539 	if (fprintf(fddefs, "\n#endif\t/* __lint */\n") < 0) {
540 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
541 		return (1);
542 	}
543 
544 	if (fprintf(fddefs, "\n#endif\n") < 0) {
545 		(void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
546 		return (1);
547 	}
548 
549 	return (0);
550 }
551 
552 /*
553  * The entire messaging file has been scanned - and all strings have been
554  * inserted into the string_table.  We can now walk the message queue
555  * and create the '#define <DEFN>' for each string - with the strings
556  * assigned offset into the string_table.
557  */
558 static int
559 output_defs(void)
560 {
561 	msg_string	*msg;
562 	size_t		stbufsize;
563 	char		*stbuf;
564 
565 	stbufsize = st_getstrtab_sz(stp);
566 	if ((stbuf = malloc(stbufsize)) == 0) {
567 		(void) fprintf(stderr, Errmsg_nmem, strerror(errno));
568 		exit(1);
569 	}
570 	(void) st_setstrbuf(stp, stbuf, stbufsize);
571 	for (msg = msg_head; msg; msg = msg->ms_next) {
572 		size_t	stoff;
573 		if ((st_setstring(stp, msg->ms_message, &stoff)) == -1) {
574 			(void) fprintf(stderr, Errmsg_mnfn, msg->ms_message);
575 			return (1);
576 		}
577 		if (fprintf(fddefs, "\n#define\t%s\t%ld\n",
578 		    msg->ms_defn, stoff) < 0) {
579 			(void) fprintf(stderr, Errmsg_wrte,
580 			    fldefs, strerror(errno));
581 			return (1);
582 		}
583 		if (fddefs && fprintf(fddefs, "#define\t%s_SIZE\t%d\n",
584 		    msg->ms_defn, strlen(msg->ms_message)) < 0) {
585 			(void) fprintf(stderr, Errmsg_wrte,
586 			    fldefs, strerror(errno));
587 			return (1);
588 		}
589 	}
590 	return (0);
591 }
592 
593 
594 /*
595  * Finish off the data structure definition.
596  */
597 static int
598 output_data(void)
599 {
600 	size_t		stbufsize;
601 	size_t		ndx;
602 	size_t		column = 1;
603 	const char	*stbuf;
604 	const char	*fmtstr;
605 
606 	stbufsize = st_getstrtab_sz(stp);
607 	stbuf = st_getstrbuf(stp);
608 
609 	assert(stbuf);
610 
611 	/*
612 	 * Determine from the local flag whether the data declaration should
613 	 * be static.
614 	 */
615 	if (lflag)
616 		fmtstr = (const char *)"static const";
617 	else
618 		fmtstr = (const char *)"const";
619 
620 	if (fprintf(fddata, "\n%s char __%s[%ld] __attribute__((unused)) = { ",
621 	    fmtstr, interface, stbufsize) < 0) {
622 		(void) fprintf(stderr, Errmsg_wrte, fldata, strerror(errno));
623 		return (1);
624 	}
625 
626 	for (ndx = 0; ndx < (stbufsize - 1); ndx++) {
627 		if (column == 1) {
628 			if (fddata && fprintf(fddata,
629 			    "\n/* %4ld */ 0x%.2x,", ndx,
630 			    (unsigned char)stbuf[ndx]) < 0) {
631 				(void) fprintf(stderr, Errmsg_wrte,
632 				    fldata, strerror(errno));
633 				return (1);
634 			}
635 		} else {
636 			if (fddata && fprintf(fddata, "  0x%.2x,",
637 			    (unsigned char)stbuf[ndx]) < 0) {
638 				(void) fprintf(stderr, Errmsg_wrte,
639 				    fldata, strerror(errno));
640 				return (1);
641 			}
642 		}
643 
644 		if (column++ == 10)
645 			column = 1;
646 	}
647 
648 	if (column == 1)
649 		fmtstr = "\n\t0x%.2x };\n";
650 	else
651 		fmtstr = "  0x%.2x };\n";
652 
653 	if (fprintf(fddata, fmtstr, (unsigned char)stbuf[stbufsize - 1]) < 0) {
654 		(void) fprintf(stderr, Errmsg_wrte, fldata, strerror(errno));
655 		return (1);
656 	}
657 
658 	return (0);
659 }
660 
661 static int
662 file()
663 {
664 	char	buffer[LINE_MAX], * token;
665 	uint_t	bufsize;
666 	char	*token_buffer;
667 	int	escape = 0;
668 	int	len = 0;
669 
670 	if ((token_buffer = malloc(LINE_MAX)) == 0) {
671 		(void) fprintf(stderr, Errmsg_nmem, strerror(errno));
672 		return (1);
673 	}
674 	bufsize = LINE_MAX;
675 
676 	line = 1;
677 
678 	while ((token = fgets(buffer, LINE_MAX, fddesc)) != NULL) {
679 		char	defn[PATH_MAX], * _defn, * str;
680 
681 		switch (*token) {
682 		case '#':
683 		case '$':
684 			if (escape) {
685 				(void) fprintf(stderr, Errmsg_malt, fldesc,
686 				    line);
687 				return (1);
688 			}
689 
690 			/*
691 			 * If a msgid has been output a msgstr must follow
692 			 * before we digest the new token.  A msgid is only set
693 			 * if fdmsgs is in use.
694 			 */
695 			if (msgid) {
696 				msgid = 0;
697 				if (fprintf(fdmsgs, "msgstr\t\"\"\n") < 0) {
698 					(void) fprintf(stderr, Errmsg_wrte,
699 					    flmsgs, strerror(errno));
700 					return (1);
701 				}
702 			}
703 
704 			/*
705 			 * Pass lines directly through to the output message
706 			 * file.
707 			 */
708 			if (fdmsgs && (prtmsgs == 1)) {
709 				char	comment;
710 
711 				if (cflag == 0)
712 					comment = '#';
713 				else
714 					comment = '$';
715 
716 				if (fprintf(fdmsgs, "%c%s", comment,
717 				    ++token) < 0) {
718 					(void) fprintf(stderr, Errmsg_wrte,
719 					    flmsgs, strerror(errno));
720 					return (1);
721 				}
722 			}
723 			break;
724 
725 		case '@':
726 			if (escape) {
727 				(void) fprintf(stderr, Errmsg_malt, fldesc,
728 				    line);
729 				return (1);
730 			}
731 
732 			/*
733 			 * If a msgid has been output a msgstr must follow
734 			 * before we digest the new token.
735 			 */
736 			if (msgid) {
737 				msgid = 0;
738 				if (fprintf(fdmsgs, "msgstr\t\"\"\n") < 0) {
739 					(void) fprintf(stderr, Errmsg_wrte,
740 					    flmsgs, strerror(errno));
741 					return (1);
742 				}
743 			}
744 
745 			/*
746 			 * Determine whether we have one or more tokens.
747 			 */
748 			token++;
749 			while (isspace(*token))		/* rid any whitespace */
750 				token++;
751 			_defn = token;			/* definition start */
752 			while (!(isspace(*token)))
753 				token++;
754 			*token++ = 0;
755 
756 			while (isspace(*token))		/* rid any whitespace */
757 				token++;
758 
759 			/*
760 			 * Determine whether the single token is one of the
761 			 * reserved message output delimiters otherwise
762 			 * translate it as a message identifier.
763 			 */
764 			if (*token == 0) {
765 				if (strcmp(_defn, start) == 0)
766 					prtmsgs = 1;
767 				else if (strcmp(_defn, end) == 0)
768 					prtmsgs = -1;
769 				else if (getmesgid(_defn) == 1)
770 					return (1);
771 				break;
772 			}
773 
774 			/*
775 			 * Multiple tokens are translated by taking the first
776 			 * token as the message definition, and the rest of the
777 			 * line as the message itself.  A message line ending
778 			 * with an escape ('\') is expected to be continued on
779 			 * the next line.
780 			 */
781 			if (prtmsgs != -1)
782 				prtmsgs = 1;
783 			if (fdmsgs && (prtmsgs == 1)) {
784 				/*
785 				 * For catgets(3c) make sure a message
786 				 * identifier has been established (this is
787 				 * normally a domain for gettext(3i), but for
788 				 * sgsmsg use this could be argued as being
789 				 * redundent).  Also make sure that the message
790 				 * definitions haven't exceeeded the maximum
791 				 * value allowed by gencat(1) before generating
792 				 * any message file entries.
793 				 */
794 				if (cflag == 1) {
795 					if (setid == 0) {
796 						(void) fprintf(stderr, "file "
797 						    "%s: no message identifier "
798 						    "has been established\n",
799 						    fldesc);
800 						return (1);
801 					}
802 					if (ptr	> NL_MSGMAX) {
803 						(void) fprintf(stderr, "file "
804 						    "%s: message definition "
805 						    "(%d) exceeds allowable "
806 						    "limit (NL_MSGMAX)\n",
807 						    fldesc, ptr);
808 						return (1);
809 					}
810 				}
811 
812 				/*
813 				 * For catgets(3c) write the definition and the
814 				 * message string to the message file.  For
815 				 * gettext(3i) write the message string as a
816 				 * mesgid - indicate a mesgid has been output
817 				 * so that a msgstr can follow.
818 				 */
819 				if (cflag == 1) {
820 					if (fprintf(fdmsgs, "%d\t%s", ptr,
821 					    token) < 0) {
822 						(void) fprintf(stderr,
823 						    Errmsg_wrte, flmsgs,
824 						    strerror(errno));
825 						return (1);
826 					}
827 				} else {
828 					if (fprintf(fdmsgs, "msgid\t\"") < 0) {
829 						(void) fprintf(stderr,
830 						    Errmsg_wrte, flmsgs,
831 						    strerror(errno));
832 						return (1);
833 					}
834 					msgid = 1;
835 				}
836 			}
837 
838 			/*
839 			 * The message itself is a quoted string as this makes
840 			 * embedding spaces at the start (or the end) of the
841 			 * string very easy.
842 			 */
843 			if (*token != '"') {
844 				(void) fprintf(stderr, Errmsg_malt, fldesc,
845 				    line);
846 				return (1);
847 			}
848 
849 			(void) strcpy(defn, _defn);
850 
851 			/*
852 			 * Write the tag to the lint definitions.
853 			 */
854 			if (fdlint) {
855 				if (fprintf(fdlint, "\n#define\t%s\t",
856 				    _defn) < 0) {
857 					(void) fprintf(stderr, Errmsg_wrte,
858 					    fllint, strerror(errno));
859 					return (1);
860 				}
861 			}
862 
863 			len = 0;
864 
865 			/*
866 			 * Write each character of the message string to the
867 			 * data array.  Translate any escaped characters - use
868 			 * the same specially recognized characters as defined
869 			 * by gencat(1).
870 			 */
871 message:
872 			if (*token == '"') {
873 				if (fdlint &&
874 				    (fprintf(fdlint, "%c", *token) < 0)) {
875 					(void) fprintf(stderr, Errmsg_wrte,
876 					    fllint, strerror(errno));
877 					return (1);
878 				}
879 				token++;
880 			}
881 			while (*token) {
882 				char	_token;
883 
884 				if ((*token == '\\') && (escape == 0)) {
885 					escape = 1;
886 					if (fdlint && (*(token + 1) != '\n') &&
887 					    fprintf(fdlint, "%c", *token) < 0) {
888 						(void) fprintf(stderr,
889 						    Errmsg_wrte, fllint,
890 						    strerror(errno));
891 						return (1);
892 					}
893 					token++;
894 					continue;
895 				}
896 				if (escape) {
897 					if (*token == 'n')
898 						_token = '\n';
899 					else if (*token == 't')
900 						_token = '\t';
901 					else if (*token == 'v')
902 						_token = '\v';
903 					else if (*token == 'b')
904 						_token = '\b';
905 					else if (*token == 'f')
906 						_token = '\f';
907 					else if (*token == '\\')
908 						_token = '\\';
909 					else if (*token == '"')
910 						_token = '"';
911 					else if (*token == '\n')
912 						break;
913 					else
914 						_token = *token;
915 
916 					if (fdmsgs && (prtmsgs == 1) &&
917 					    (fprintf(fdmsgs, "\\") < 0)) {
918 						(void) fprintf(stderr,
919 						    Errmsg_wrte, flmsgs,
920 						    strerror(errno));
921 						return (1);
922 					}
923 				} else {
924 					/*
925 					 * If this is the trailing quote then
926 					 * thats the last of the message string.
927 					 * Eat up any remaining white space and
928 					 * unless an escape character is found
929 					 * terminate the data string with a 0.
930 					 */
931 					/* BEGIN CSTYLED */
932 					if (*token == '"') {
933 					    if (fdlint && (fprintf(fdlint,
934 						"%c", *token) < 0)) {
935 						(void) fprintf(stderr,
936 						    Errmsg_wrte, fllint,
937 						    strerror(errno));
938 						return (1);
939 					    }
940 
941 					    if (fdmsgs && (prtmsgs == 1) &&
942 						(fprintf(fdmsgs, "%c",
943 						*token) < 0)) {
944 						(void) fprintf(stderr,
945 						    Errmsg_wrte, flmsgs,
946 						    strerror(errno));
947 						return (1);
948 					    }
949 
950 					    while (*++token) {
951 						if (*token == '\n')
952 							break;
953 					    }
954 					    _token = '\0';
955 					} else
956 					    _token = *token;
957 					/* END CSTYLED */
958 				}
959 
960 				if (fdmsgs && (prtmsgs == 1) &&
961 				    (fprintf(fdmsgs, "%c", *token) < 0)) {
962 					(void) fprintf(stderr, Errmsg_wrte,
963 					    flmsgs, strerror(errno));
964 					return (1);
965 				}
966 
967 				if (fdlint && fprintf(fdlint,
968 				    "%c", *token) < 0) {
969 					(void) fprintf(stderr, Errmsg_wrte,
970 					    fllint, strerror(errno));
971 					return (1);
972 				}
973 
974 				if (len >= bufsize) {
975 					bufsize += LINE_MAX;
976 					if ((token_buffer = realloc(
977 					    token_buffer, bufsize)) == 0) {
978 						(void) fprintf(stderr,
979 						    Errmsg_nmem,
980 						    strerror(errno));
981 						return (1);
982 					}
983 				}
984 				token_buffer[len] = _token;
985 				ptr++, token++, len++;
986 				escape = 0;
987 
988 				if (_token == '\0')
989 					break;
990 			}
991 
992 			/*
993 			 * After the complete message string has been processed
994 			 * (including its continuation beyond one line), create
995 			 * a string size definition.
996 			 */
997 			if (escape == 0) {
998 				const char *form = "#define\t%s_SIZE\t%d\n";
999 
1000 				token_buffer[len] = '\0';
1001 
1002 				message_append(defn, token_buffer);
1003 
1004 				if (fdlint && fprintf(fdlint, form, defn,
1005 				    (len - 1)) < 0) {
1006 					(void) fprintf(stderr, Errmsg_wrte,
1007 					    fllint, strerror(errno));
1008 					return (1);
1009 				}
1010 			}
1011 			break;
1012 
1013 		default:
1014 			/*
1015 			 * Empty lines are passed through to the message file.
1016 			 */
1017 			while (isspace(*token))
1018 				token++;
1019 
1020 			if (*token == 0) {
1021 				if (msgid || (fdmsgs && (prtmsgs == 1))) {
1022 					/*
1023 					 * If a msgid has been output a msgstr
1024 					 * must follow before we digest the new
1025 					 * token.
1026 					 */
1027 					if (msgid) {
1028 						msgid = 0;
1029 						str = "msgstr\t\"\"\n\n";
1030 					} else
1031 						str = "\n";
1032 
1033 					if (fprintf(fdmsgs, str) < 0) {
1034 						(void) fprintf(stderr,
1035 						    Errmsg_wrte, flmsgs,
1036 						    strerror(errno));
1037 						return (1);
1038 					}
1039 				}
1040 				break;
1041 			}
1042 
1043 			/*
1044 			 * If an escape is in effect then any tokens are taken
1045 			 * to be message continuations.
1046 			 */
1047 			if (escape) {
1048 				escape = 0;
1049 				goto message;
1050 			}
1051 
1052 			(void) fprintf(stderr, "file %s: line %d: invalid "
1053 			    "input does not start with #, $ or @\n", fldesc,
1054 			    line);
1055 			return (1);
1056 		}
1057 		line++;
1058 	}
1059 
1060 	free(token_buffer);
1061 
1062 	return (0);
1063 }
1064 
1065 int
1066 main(int argc, char ** argv)
1067 {
1068 	opterr = 0;
1069 	while ((line = getopt(argc, argv, "cd:h:lm:n:i:v")) != EOF) {
1070 		switch (line) {
1071 		case 'c':			/* catgets instead of gettext */
1072 			cflag = 1;
1073 			break;
1074 		case 'd':			/* new message data filename */
1075 			fldata = optarg;	/*	(msg.c is default) */
1076 			break;
1077 		case 'h':			/* new message defs filename */
1078 			fldefs = optarg;	/*	(msg.h is default) */
1079 			break;
1080 		case 'i':			/* input message ids from */
1081 			flmids = optarg;	/*	from this file */
1082 			break;
1083 		case 'l':			/* define message data arrays */
1084 			lflag = 1;		/*	to be local (static) */
1085 			break;
1086 		case 'm':			/* generate message database */
1087 			flmsgs = optarg;	/*	to this file */
1088 			break;
1089 		case 'n':			/* new data array and func */
1090 			interface = optarg;	/*	name (msg is default) */
1091 			break;
1092 		case 'v':
1093 			vflag = 1;		/* set verbose flag */
1094 			break;
1095 		case '?':
1096 			(void) fprintf(stderr, Errmsg_use, argv[0]);
1097 			exit(1);
1098 		default:
1099 			break;
1100 		}
1101 	}
1102 
1103 	/*
1104 	 * Validate the we have been given at least one input file.
1105 	 */
1106 	if ((argc - optind) < 1) {
1107 		(void) fprintf(stderr, Errmsg_use);
1108 		exit(1);
1109 	}
1110 
1111 	/*
1112 	 * Open all the required output files.
1113 	 */
1114 	if (fldefs) {
1115 		if ((fddefs = fopen(fldefs, "w+")) == NULL) {
1116 			(void) fprintf(stderr, Errmsg_opne, fldefs,
1117 			    strerror(errno));
1118 			return (1);
1119 		}
1120 	}
1121 	if (fldata) {
1122 		if (fldefs && (strcmp(fldefs, fldata) == 0))
1123 			fddata = fddefs;
1124 		else if ((fddata = fopen(fldata, "w+")) == NULL) {
1125 			(void) fprintf(stderr, Errmsg_opne, fldata,
1126 			    strerror(errno));
1127 			return (1);
1128 		}
1129 	}
1130 	if (fddefs && fddata) {
1131 		(void) sprintf(fllint, "%s.%d.XXXXXX", nmlint, (int)getpid());
1132 		if ((mkstemp(fllint) == -1) ||
1133 		    ((fdlint = fopen(fllint, "w+")) == NULL)) {
1134 			(void) fprintf(stderr, Errmsg_opne, fllint,
1135 			    strerror(errno));
1136 			return (1);
1137 		}
1138 	}
1139 	if (flmsgs) {
1140 		if ((fdmsgs = fopen(flmsgs, "w+")) == NULL) {
1141 			(void) fprintf(stderr, Errmsg_opne, flmsgs,
1142 			    strerror(errno));
1143 			return (1);
1144 		}
1145 	}
1146 	if (flmids) {
1147 		if ((fdmids = fopen(flmids, "r")) == NULL) {
1148 			(void) fprintf(stderr, Errmsg_opne, flmids,
1149 			    strerror(errno));
1150 			return (1);
1151 		}
1152 	}
1153 
1154 
1155 	/*
1156 	 * Initialize the message definition and message data streams.
1157 	 */
1158 	if (fddefs) {
1159 		if (init_defs())
1160 			return (1);
1161 	}
1162 
1163 	/*
1164 	 * Read the input message file, and for each line process accordingly.
1165 	 */
1166 	for (; optind < argc; optind++) {
1167 		int	err;
1168 
1169 		fldesc = argv[optind];
1170 
1171 		if ((fddesc = fopen(fldesc, "r")) == NULL) {
1172 			(void) fprintf(stderr, Errmsg_opne, fldesc,
1173 			    strerror(errno));
1174 			return (1);
1175 		}
1176 		err = file();
1177 		(void) fclose(fddesc);
1178 
1179 		if (err != 0)
1180 			return (1);
1181 	}
1182 
1183 	/*
1184 	 * If a msgid has been output a msgstr must follow before we end the
1185 	 * file.
1186 	 */
1187 	if (msgid) {
1188 		msgid = 0;
1189 		if (fprintf(fdmsgs, "msgstr\t\"\"\n") < 0) {
1190 			(void) fprintf(stderr, Errmsg_wrte, flmsgs,
1191 			    strerror(errno));
1192 			return (1);
1193 		}
1194 	}
1195 
1196 	if (fdmids)
1197 		(void) fclose(fdmids);
1198 	if (fdmsgs)
1199 		(void) fclose(fdmsgs);
1200 
1201 	if (fddefs) {
1202 		if (output_defs())
1203 			return (1);
1204 	}
1205 
1206 	/*
1207 	 * Finish off any generated data and header file.
1208 	 */
1209 	if (fldata) {
1210 		if (output_data())
1211 			return (1);
1212 	}
1213 	if (fddefs) {
1214 		if (fini_defs())
1215 			return (1);
1216 	}
1217 
1218 	if (vflag)
1219 		dump_stringtab(stp);
1220 
1221 	/*
1222 	 * Close up everything and go home.
1223 	 */
1224 	if (fddata)
1225 		(void) fclose(fddata);
1226 	if (fddefs && (fddefs != fddata))
1227 		(void) fclose(fddefs);
1228 	if (fddefs && fddata) {
1229 		(void) fclose(fdlint);
1230 		(void) unlink(fllint);
1231 	}
1232 
1233 	if (stp)
1234 		st_destroy(stp);
1235 
1236 	return (0);
1237 }
1238