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