xref: /illumos-gate/usr/src/cmd/gencat/gencat.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1990, 1991, 1994, Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <nl_types.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <limits.h>
34 #include <memory.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <locale.h>
40 #include <libintl.h>
41 
42 #ifndef NL_MSGMAX
43 #define	NL_MSGMAX 32767
44 #endif
45 
46 #ifndef NL_SETMAX
47 #define	NL_SETMAX 255
48 #endif
49 
50 #ifndef NL_TEXTMAX
51 #define	NL_TEXTMAX 2048
52 #endif
53 
54 #define	BS		'\b'
55 #define	CR		'\r'
56 #define	DOLLAR	'$'
57 #define	FF		'\f'
58 #define	NEWLINE	'\n'
59 #define	NUL		'\000'
60 #define	REVERSE_SOLIDUS '\\'
61 #define	SPACE	' '
62 #define	TAB		'\t'
63 #define	VTAB	'\v'
64 
65 #define	FPRINTF			(void) fprintf
66 #define	FREE(x)			free((char *)(x))
67 #define	MALLOC(n)		malloc((unsigned)(n))
68 #define	MEMCPY(dst, src, n) \
69 		(void) memcpy((char *)(dst), (char *)(src), (int)(n))
70 #define	MEMSET(s, c, n)	(void) memset((char *)(s), (int)(c), (int)(n));
71 #define	MSG(n)			gettext(MSG ## n)
72 #define	READ(fd, p, n)	read((int)(fd), (char *)(p), (unsigned)(n))
73 #define	REALLOC(x, n)	realloc((char *)(x), (unsigned)(n))
74 
75 /* double linked list */
76 struct cat_set {
77 	struct cat_set	*prev;
78 	struct cat_set	*next;
79 	int				set_no;
80 	struct cat_msg	*first_msg;
81 };
82 
83 /* double linked list */
84 struct cat_msg {
85 	struct cat_msg	*prev;
86 	struct cat_msg	*next;
87 	int				msg_no;
88 	int				msg_len;
89 	char			s[1];
90 };
91 
92 int		catfd;		/* File descriptor of catalog file */
93 char	*catfname;	/* Catalog file name */
94 char	*msgfname;	/* message source file name */
95 int		ateof;		/* boolean indicating END-OF-FILE */
96 int		lineno;		/* the line number of message source file */
97 int		quoting;	/* boolean indicating quotes is used */
98 int		quote;		/* the current quote */
99 int		text_len;	/* message text length */
100 int		text_size;	/* the size of allocated text memory */
101 char	*text;		/* messsge text */
102 
103 struct _cat_hdr	hdr;
104 int				current_set_no;	/* the current set number */
105 struct cat_set	*first_set;	/* the pointer to the first set */
106 struct cat_set	*current_set;	/* the pointer to the current set */
107 struct cat_msg	*current_msg;	/* the pointer to the first message */
108 
109 
110 /* Error message */
111 /* 0 */
112 #define	MSG0	""
113 /* 1 */
114 #define	MSG1	"usage: gencat catfile msgfile ...\n"
115 /* 2 */
116 #define	MSG2	"gencat: cannot open \"%s\"\n"
117 /* 3 */
118 #define	MSG3	"gencat: read error on \"%s\"\n"
119 /* 4 */
120 #define	MSG4	"gencat: bad magic number (%#lx)\n"
121 /* 5 */
122 #define	MSG5	"gencat: corrupt catalogue file \"%s\"\n"
123 /* 6 */
124 #define	MSG6	"gencat: memory limit exceeded\n"
125 /* 7 */
126 #define	MSG7	"gencat: seek error on \"%s\"\n"
127 /* 8 */
128 #define	MSG8	"gencat: write error on \"%s\"\n"
129 /* 9 */
130 #define	MSG9	"gencat: \"%s\", line %d: number too large (%s)\n"
131 /* 10 */
132 #define	MSG10	"gencat: \"%s\", line %d: 0 is not a permissible " \
133 				"message number\n"
134 /* 11 */
135 #define	MSG11	"gencat: \"%s\", line %d: warning, message number %d " \
136 				"exceeds limit (%d)\n"
137 /* 12 */
138 #define	MSG12	"gencat: \"%s\", line %d: missing quote (%wc)\n"
139 /* 13 */
140 #define	MSG13	"gencat: \"%s\", line %d: character value too large ('\\%o')\n"
141 /* 14 */
142 #define	MSG14	"gencat: \"%s\", line %d: extra characters following " \
143 				"message text\n"
144 /* 15 */
145 #define	MSG15	"gencat: \"%s\", line %d: extra characters following " \
146 				"$quote directive\n"
147 /* 16 */
148 #define	MSG16	"gencat: \"%s\", line %d: no set number specified in " \
149 				"$set directive\n"
150 /* 17 */
151 #define	MSG17	"getcat: \"%s\", line %d: 0 is not a permissible set number\n"
152 /* 18 */
153 #define	MSG18	"gencat: \"%s\", line %d: warning, set number %d " \
154 				"exceeds limit (%d)\n"
155 /* 19 */
156 #define	MSG19	"gencat: \"%s\", line %d: unknown directive %s\n"
157 /* 20 */
158 #define	MSG20	"gencat: \"%s\", line %d: no set number specified in " \
159 				"$delset directive\n"
160 /* 21 */
161 #define	MSG21	"stdin"
162 /* 22 */
163 #define	MSG22	"gencat: \"%s\", line %d: number or $ expected\n"
164 
165 struct cat_set *
166 new_set(n)
167 	int		n;
168 {
169 	struct cat_set *p;
170 
171 	p = (struct cat_set *) MALLOC(sizeof (struct cat_set));
172 	if (p == NULL) {
173 		FPRINTF(stderr, MSG(6));
174 		exit(1);
175 	}
176 	p->next = NULL;
177 	p->prev = NULL;
178 	p->set_no = n;
179 	p->first_msg = NULL;
180 	return (p);
181 }
182 
183 void
184 find_set(no)
185 	int		no;
186 {
187 	struct cat_set	*prev, *next;
188 
189 	if (current_set && current_set->set_no == no) {
190 		return;
191 	}
192 
193 	current_set_no = no;
194 	current_msg = NULL;
195 	/* if no set exists, create a new set */
196 	if (current_set == NULL) {
197 		if (first_set == NULL) {
198 			current_set = first_set = new_set(no);
199 			return;
200 		}
201 		current_set = first_set;
202 		if (current_set->set_no == no)
203 			return;
204 	}
205 
206 	if (current_set->set_no > no) {
207 		if (first_set->set_no > no) {
208 			/* prepend a new set */
209 			current_set = new_set(no);
210 			current_set->next = first_set;
211 			first_set->prev = current_set;
212 			first_set = current_set;
213 			return;
214 		}
215 		current_set = first_set;
216 		if (current_set->set_no == no)
217 			return;
218 	}
219 
220 	/* search for the set number 'no' */
221 	while (current_set->next && current_set->next->set_no < no)
222 		current_set = current_set->next;
223 
224 	if (current_set->next && current_set->next->set_no == no) {
225 		/* set number 'no' found */
226 		current_set = current_set->next;
227 		return;
228 	}
229 
230 	/* If set number is not found, insert a new set in the middle */
231 	prev = current_set;
232 	next = current_set->next;
233 	current_set = new_set(no);
234 	current_set->prev = prev;
235 	current_set->next = next;
236 	if (prev)
237 		prev->next = current_set;
238 	else
239 		first_set = current_set;
240 	if (next)
241 		next->prev = current_set;
242 }
243 
244 void
245 delete_set(no)
246 	int		no;
247 {
248 	struct cat_set	*prev, *next, *setp;
249 	struct cat_msg	*p, *q;
250 
251 	for (setp = first_set; setp && setp->set_no < no; setp = setp->next)
252 		continue;
253 
254 	if (setp == NULL || setp->set_no != no)	/* set not found */
255 		return;
256 
257 	if (setp == current_set) {
258 		current_set = NULL;
259 		current_msg = NULL;
260 	}
261 
262 	/* free all messages in the set */
263 	for (p = setp->first_msg; p; p) {
264 		q = p->next;
265 		FREE(p);
266 		p = q;
267 	}
268 
269 	/* do the link operation to delete the set */
270 	prev = setp->prev;
271 	next = setp->next;
272 	FREE(setp);
273 	if (prev)
274 		prev->next = next;
275 	else
276 		first_set = next;
277 	if (next)
278 		next->prev = prev;
279 }
280 
281 struct cat_msg *
282 new_msg(no, len, text)
283 	int		no;
284 	int		len;
285 	char	*text;
286 {
287 	struct cat_msg	*p;
288 
289 	p = (struct cat_msg *) MALLOC(sizeof (struct cat_msg) + len);
290 	if (p == NULL) {
291 		FPRINTF(stderr, MSG(6));
292 		exit(1);
293 	}
294 	p->next = NULL;
295 	p->prev = NULL;
296 	p->msg_no = no;
297 	p->msg_len = len;
298 	MEMCPY(p->s, text, len);
299 	return (p);
300 }
301 
302 
303 void
304 insert_msg(no, len, text)
305 	int		no;
306 	int		len;
307 	char	*text;
308 {
309 	struct cat_msg	*prev, *next;
310 
311 	if (current_msg == NULL) {
312 		if (current_set == NULL)
313 			find_set(current_set_no);
314 		current_msg = current_set->first_msg;
315 		if (current_msg == NULL) {
316 			current_msg = new_msg(no, len, text);
317 			current_set->first_msg = current_msg;
318 			return;
319 		}
320 	}
321 	if (current_msg->msg_no >= no) {
322 		current_msg = current_set->first_msg;
323 		if (current_msg->msg_no > no) {
324 			current_msg = new_msg(no, len, text);
325 			current_msg->next = current_set->first_msg;
326 			current_set->first_msg->prev = current_msg;
327 			current_set->first_msg = current_msg;
328 			return;
329 		}
330 		if (current_msg->msg_no == no) {
331 			current_msg = new_msg(no, len, text);
332 			current_msg->next = current_set->first_msg->next;
333 			if (current_set->first_msg->next)
334 				current_set->first_msg->next->prev =
335 					current_msg;
336 			FREE(current_set->first_msg);
337 			current_set->first_msg = current_msg;
338 			return;
339 		}
340 	}
341 	while (current_msg->next && current_msg->next->msg_no < no)
342 		current_msg = current_msg->next;
343 
344 	/*
345 	 * if the same msg number is found, then delte the message and
346 	 * insert the new message. This is same as replacing message.
347 	 */
348 	if (current_msg->next && current_msg->next->msg_no == no) {
349 		current_msg = current_msg->next;
350 		prev = current_msg->prev;
351 		next = current_msg->next;
352 		FREE(current_msg);
353 	} else {
354 		prev = current_msg;
355 		next = current_msg->next;
356 	}
357 
358 	current_msg = new_msg(no, len, text);
359 	current_msg->prev = prev;
360 	current_msg->next = next;
361 	if (prev)
362 		prev->next = current_msg;
363 	else
364 		current_set->first_msg = current_msg;
365 	if (next)
366 		next->prev = current_msg;
367 }
368 
369 void
370 delete_msg(no)
371 	int		no;
372 {
373 	struct cat_set	*p = current_set;
374 	struct cat_msg	*prev, *next;
375 
376 	if (current_msg == NULL) {
377 		if (current_set == NULL)
378 			for (p = first_set; p && p->set_no < current_set_no;
379 							p = p->next)
380 				continue;
381 		if (p == NULL || p->set_no != current_set_no)
382 			return;
383 		current_set = p;
384 		current_msg = current_set->first_msg;
385 		if (current_msg == NULL)
386 			return;
387 	}
388 	if (current_msg->msg_no > no)
389 		current_msg = current_set->first_msg;
390 
391 	while (current_msg && current_msg->msg_no != no)
392 		current_msg = current_msg->next;
393 
394 	if (current_msg && current_msg->msg_no == no) {
395 		prev = current_msg->prev;
396 		next = current_msg->next;
397 		FREE(current_msg);
398 		if (prev) {
399 			current_msg = prev;
400 			prev->next = next;
401 		} else {
402 			current_set->first_msg = next;
403 			current_msg = next;
404 		}
405 		if (next)
406 			next->prev = prev;
407 	}
408 }
409 
410 int
411 read_block(fd, p, n, pathname)
412 	int		fd;
413 	char	*p;
414 	int		n;
415 	char	*pathname;
416 {
417 	int		nbytes, bytes_read;
418 
419 	if (n == 0)
420 		return (0);
421 
422 	nbytes = 0;
423 	while (nbytes < n) {
424 		bytes_read = READ(fd, p + nbytes, n - nbytes);
425 		if (bytes_read < 0) {
426 			if (errno != EINTR) {
427 				FPRINTF(stderr, MSG(3), pathname);
428 				perror("");
429 				exit(1);
430 			}
431 		} else if (bytes_read == 0)
432 			break;
433 		else
434 			nbytes += bytes_read;
435 	}
436 
437 	return (nbytes);
438 }
439 
440 /*
441  * Check if catalog file read is valid
442  *
443  */
444 int
445 cat_ok(cat)
446 	char	*cat;
447 {
448 	int		i, j;
449 	int		nmsgs;
450 	int		msg_no;
451 	struct	_cat_msg_hdr	*msg;
452 	int		set_no;
453 	int		first_msg_hdr;
454 	struct	_cat_set_hdr	*set;
455 
456 	set = (struct _cat_set_hdr *) cat;
457 	set_no = 0;
458 	for (i = 0; i < hdr.__nsets; ++set, ++i) {
459 		if (set->__set_no < set_no)
460 			return (0);
461 		set_no = set->__set_no;
462 		nmsgs = set->__nmsgs;
463 		if (nmsgs < 0)
464 			return (0);
465 		if (nmsgs == 0)
466 			continue;
467 		first_msg_hdr = set->__first_msg_hdr;
468 		if (first_msg_hdr < 0)
469 			return (0);
470 		if (hdr.__msg_hdr_offset + (first_msg_hdr + nmsgs) *
471 					_CAT_MSG_HDR_SIZE > hdr.__mem)
472 			return (0);
473 
474 		msg = (struct _cat_msg_hdr *) (cat + hdr.__msg_hdr_offset) +
475 						first_msg_hdr;
476 		msg_no = 0;
477 		for (j = 0; j < nmsgs; ++msg, ++j) {
478 			if (msg->__msg_no < msg_no)
479 				return (0);
480 			msg_no = msg->__msg_no;
481 			if (msg->__msg_offset < 0)
482 				return (0);
483 			if (hdr.__msg_text_offset + msg->__msg_offset +
484 						msg->__msg_len > hdr.__mem)
485 				return (0);
486 		}
487 	}
488 
489 	return (1);
490 }
491 
492 /*
493  * convert a chunk of catalog file into double linked list format
494  */
495 void
496 initcat(cat)
497 	char	*cat;
498 {
499 	int		i, j;
500 	int		nmsgs;
501 	struct	_cat_set_hdr	*set;
502 	struct	_cat_msg_hdr	*msg;
503 
504 	set = (struct _cat_set_hdr *) cat;
505 	for (i = 0; i < hdr.__nsets; ++set, ++i) {
506 		nmsgs = set->__nmsgs;
507 		if (nmsgs == 0)
508 			continue;
509 		find_set(set->__set_no);
510 		msg = (struct _cat_msg_hdr *) (cat + hdr.__msg_hdr_offset)
511 			+ set->__first_msg_hdr;
512 		current_msg = current_set->first_msg;
513 		for (j = 0; j < nmsgs; ++msg, ++j) {
514 			insert_msg(msg->__msg_no, msg->__msg_len,
515 			    cat + hdr.__msg_text_offset + msg->__msg_offset);
516 		}
517 	}
518 }
519 
520 /*
521  * read a catalog file in a chunk and convert it to double linked list.
522  */
523 void
524 readcat(fd, pathname)
525 	int		fd;
526 	char	*pathname;
527 {
528 	int		i;
529 	char	*cat;
530 
531 	i = read_block(fd, (char *) &hdr, _CAT_HDR_SIZE, pathname);
532 	if (i == 0)
533 		return;
534 
535 	if (i >= 4 && hdr.__hdr_magic != _CAT_MAGIC) {
536 		FPRINTF(stderr, MSG(4), hdr.__hdr_magic);
537 		exit(1);
538 	}
539 	if (i < _CAT_HDR_SIZE || hdr.__nsets < 0) {
540 		FPRINTF(stderr, MSG(5), pathname);
541 		exit(1);
542 	}
543 	if (hdr.__nsets == 0)
544 		return;
545 
546 	if (hdr.__mem < 0 ||
547 	    hdr.__msg_hdr_offset < 0 ||
548 	    hdr.__msg_text_offset < 0 ||
549 	    hdr.__mem < hdr.__nsets * _CAT_SET_HDR_SIZE ||
550 	    hdr.__mem < hdr.__msg_hdr_offset ||
551 	    hdr.__mem < hdr.__msg_text_offset) {
552 		FPRINTF(stderr, MSG(5), pathname);
553 		exit(1);
554 	}
555 	cat = MALLOC(hdr.__mem);
556 	if (cat == NULL) {
557 		FPRINTF(stderr, MSG(6));
558 		exit(1);
559 	}
560 	i = read_block(fd, cat, hdr.__mem, pathname);
561 	if (i < hdr.__mem || !cat_ok(cat)) {
562 		FPRINTF(stderr, MSG(5), pathname);
563 		exit(1);
564 	}
565 	initcat(cat);
566 
567 	FREE(cat);
568 }
569 
570 /*
571  * Extend the memory in 1000 byte chunks whenever runs out of text space.
572  */
573 void
574 extend_text()
575 {
576 	text_size += 1000;
577 	if (text)
578 		text = REALLOC(text, text_size);
579 	else
580 		text = MALLOC(text_size);
581 	if (text == NULL) {
582 		FPRINTF(stderr, MSG(6));
583 		exit(1);
584 	}
585 }
586 
587 int
588 get_number(fp, c)
589 	FILE	*fp;
590 	int		c;
591 {
592 	int		i, n;
593 	char	*s, *t;
594 
595 	i = 0;
596 	do {
597 		while (i >= text_size)
598 			extend_text();
599 		text[i] = c;
600 		++i;
601 		c = getc(fp);
602 	}
603 	while (isdigit(c));
604 	(void) ungetc(c, fp);
605 
606 	while (i >= text_size)
607 		extend_text();
608 	text[i] = NUL;
609 
610 	for (s = text; *s == '0'; ++s)
611 		continue;
612 
613 	n = 0;
614 	for (t = s; isdigit(*t); ++t) {
615 		if (n > INT_MAX / 10 ||
616 			(n == INT_MAX / 10 && *t > '0' + INT_MAX % 10)) {
617 			FPRINTF(stderr, MSG(9), msgfname, lineno, s);
618 			exit(1);
619 		}
620 		n = 10 * n + (*t - '0');
621 	}
622 
623 	return (n);
624 }
625 
626 void
627 get_text(fp)
628 	FILE	*fp;
629 {
630 	int		c;
631 	int		n;
632 
633 	text_len = 0;
634 	c = fgetwc(fp);
635 	if (quoting && c == quote) {	/* quote is used */
636 		c = fgetwc(fp);
637 		while (c != quote) {
638 			if (c == NEWLINE || c == EOF) {
639 				FPRINTF(stderr, MSG(12), msgfname, lineno,
640 								quote);
641 				exit(1);
642 			}
643 			if (c == REVERSE_SOLIDUS) {
644 				c = fgetwc(fp);
645 				switch (c) {
646 				case EOF:
647 					FPRINTF(stderr, MSG(12), msgfname,
648 						lineno, quote);
649 					exit(1);
650 					break;
651 				case NEWLINE:
652 					++lineno;
653 					c = fgetwc(fp);
654 					continue;
655 					/* NOTREACHED */
656 					break;
657 				case '0':
658 				case '1':
659 				case '2':
660 				case '3':
661 				case '4':
662 				case '5':
663 				case '6':
664 				case '7':
665 					n = (c - '0');
666 					c = fgetwc(fp);
667 					if (c >= '0' && c <= '7') {
668 						n = 8 * n + (c - '0');
669 						c = fgetwc(fp);
670 						if (c >= '0' && c <= '7')
671 							n = 8 * n + (c - '0');
672 						else
673 							(void) ungetwc(c, fp);
674 					} else
675 						(void) ungetwc(c, fp);
676 					if (n > UCHAR_MAX) {
677 						FPRINTF(stderr, MSG(13),
678 							msgfname, lineno, n);
679 						exit(1);
680 					}
681 					c = n;
682 					break;
683 
684 				case 'n':
685 					c = NEWLINE;
686 					break;
687 
688 				case 't':
689 					c = TAB;
690 					break;
691 
692 				case 'v':
693 					c = VTAB;
694 					break;
695 
696 				case 'b':
697 					c = BS;
698 					break;
699 
700 				case 'r':
701 					c = CR;
702 					break;
703 
704 				case 'f':
705 					c = FF;
706 					break;
707 				}
708 			}
709 			while ((text_len + (int)MB_CUR_MAX + 1) >= text_size)
710 				extend_text();
711 			if ((n = wctomb(&text[text_len], c)) > 0)
712 				text_len += n;
713 			c = fgetwc(fp);
714 		}
715 
716 		while ((text_len + 1) >= text_size)
717 			extend_text();
718 		text[text_len] = '\0';
719 		++text_len;
720 
721 		do {
722 			c = getc(fp);
723 		} while (c == SPACE || c == TAB);
724 		if (c == NEWLINE) {
725 			++lineno;
726 			return;
727 		}
728 		if (c == EOF) {
729 			ateof = 1;
730 			return;
731 		}
732 		FPRINTF(stderr, MSG(14), msgfname, lineno);
733 		exit(1);
734 	}
735 
736 	while (c != NEWLINE && c != EOF) {	/* quote is not used */
737 		if (c == REVERSE_SOLIDUS) {
738 			c = fgetwc(fp);
739 			switch (c) {
740 			case EOF:
741 				return;
742 
743 			case NEWLINE:
744 				++lineno;
745 				c = fgetwc(fp);
746 				continue;
747 
748 			case '0':
749 			case '1':
750 			case '2':
751 			case '3':
752 			case '4':
753 			case '5':
754 			case '6':
755 			case '7':
756 				n = (c - '0');
757 				c = fgetwc(fp);
758 				if (c >= '0' && c <= '7') {
759 					n = 8 * n + (c - '0');
760 					c = fgetwc(fp);
761 					if (c >= '0' && c <= '7')
762 						n = 8 * n + (c - '0');
763 					else
764 						(void) ungetwc(c, fp);
765 				} else
766 					(void) ungetwc(c, fp);
767 				if (n > UCHAR_MAX) {
768 					FPRINTF(stderr, MSG(13), msgfname,
769 							lineno, n);
770 					exit(1);
771 				}
772 				c = n;
773 				break;
774 
775 			case 'n':
776 				c = NEWLINE;
777 				break;
778 
779 			case 't':
780 				c = TAB;
781 				break;
782 
783 			case 'v':
784 				c = VTAB;
785 				break;
786 
787 			case 'b':
788 				c = BS;
789 				break;
790 
791 			case 'r':
792 				c = CR;
793 				break;
794 
795 			case 'f':
796 				c = FF;
797 				break;
798 			}
799 		}
800 		while ((text_len + (int)MB_CUR_MAX + 1) >= text_size)
801 			extend_text();
802 		if ((n = wctomb(&text[text_len], c)) > 0)
803 			text_len += n;
804 		c = fgetwc(fp);
805 	}
806 
807 	while ((text_len + 1) >= text_size)
808 		extend_text();
809 	text[text_len] = '\0';
810 	++text_len;
811 
812 	if (c == NEWLINE)
813 		++lineno;
814 	else
815 		ateof = 1;
816 }
817 
818 /*
819  * This routine handles $ <comment>, $set, $delset, $quote
820  */
821 void
822 directive(fp)
823 	FILE	*fp;
824 {
825 	int		c;
826 	int		n;
827 
828 	c = fgetwc(fp);
829 	if (c == SPACE || c == TAB) {	/* $ <comment */
830 		do {
831 			c = fgetwc(fp);
832 		} while (c != NEWLINE && c != EOF);
833 	}
834 	if (c == NEWLINE) {
835 		++lineno;
836 		return;
837 	}
838 	if (c == EOF) {
839 		ateof = 1;
840 		return;
841 	}
842 	text_len = 1;
843 	while (text_len >= text_size)
844 		extend_text();
845 	text[0] = DOLLAR;
846 	while (isascii(c) && isalpha(c)) {
847 		while ((text_len + 1) >= text_size)
848 			extend_text();
849 		text[text_len] = c;
850 		++text_len;
851 		c = fgetwc(fp);
852 	}
853 
854 	while ((text_len + 1) >= text_size)
855 		extend_text();
856 	text[text_len] = NUL;
857 
858 	if (strcmp(text, "$set") == 0) {
859 		while (c == SPACE || c == TAB)
860 			c = fgetwc(fp);
861 		if (!isascii(c) || !isdigit(c)) {
862 			FPRINTF(stderr, MSG(16), msgfname, lineno);
863 			exit(1);
864 		}
865 		n = get_number(fp, c);
866 		if (n == 0) {
867 			FPRINTF(stderr, MSG(17), msgfname, lineno);
868 			exit(1);
869 		}
870 		if (n > NL_SETMAX) {
871 			FPRINTF(stderr, MSG(18), msgfname, lineno,
872 						n, NL_SETMAX);
873 		}
874 		find_set(n);
875 		do {	/* skip comment */
876 			c = getc(fp);
877 		} while (c != NEWLINE && c != EOF);
878 		if (c == NEWLINE)
879 			++lineno;
880 		else
881 			ateof = 1;
882 		return;
883 	} else if (strcmp(text, "$delset") == 0) {
884 		while (c == SPACE || c == TAB)
885 			c = fgetwc(fp);
886 		if (!isascii(c) || !isdigit(c)) {
887 			FPRINTF(stderr, MSG(20), msgfname, lineno);
888 			exit(1);
889 		}
890 		n = get_number(fp, c);
891 		if (n == 0) {
892 			FPRINTF(stderr, MSG(17), msgfname, lineno);
893 			exit(1);
894 		}
895 		if (n > NL_SETMAX) {
896 			FPRINTF(stderr, MSG(18), msgfname, lineno,
897 						n, NL_SETMAX);
898 		}
899 		delete_set(n);
900 		do {	/* skip comment */
901 			c = getc(fp);
902 		} while (c != NEWLINE && c != EOF);
903 		if (c == NEWLINE)
904 			++lineno;
905 		else
906 			ateof = 1;
907 		return;
908 	} else if (strcmp(text, "$quote") == 0) {
909 		if (c == NEWLINE) {
910 			quoting = 0;
911 			++lineno;
912 			return;
913 		}
914 		if (c == EOF) {
915 			quoting = 0;
916 			ateof = 1;
917 			return;
918 		}
919 		if (c == SPACE || c == TAB)
920 			c = fgetwc(fp);
921 		if (c == NEWLINE) {
922 			quoting = 0;
923 			++lineno;
924 			return;
925 		}
926 		if (c == EOF) {
927 			quoting = 0;
928 			ateof = 1;
929 			return;
930 		}
931 		quoting = 1;
932 		quote = c;
933 		do {	/* skip comment */
934 			c = getc(fp);
935 		} while (c == SPACE || c == TAB);
936 		if (c == NEWLINE) {
937 			++lineno;
938 			return;
939 		}
940 		if (c == EOF) {
941 			ateof = 1;
942 			return;
943 		}
944 		FPRINTF(stderr, MSG(15), msgfname, lineno);
945 		exit(1);
946 	} else {
947 		FPRINTF(stderr, MSG(19), msgfname, lineno, text);
948 		exit(1);
949 	}
950 }
951 
952 /*
953  * Read message source file and update double linked list message catalog.
954  */
955 void
956 read_msgfile(fp, pathname)
957 	FILE	*fp;
958 	char	*pathname;
959 {
960 	int		c;
961 	int		no;
962 
963 	ateof = 0;
964 	msgfname = pathname;
965 	lineno = 1;
966 	quoting = 0;
967 	current_set_no = NL_SETD;
968 	current_set = NULL;
969 	current_msg = NULL;
970 
971 	for (;;) {
972 		if (ateof)
973 			return;
974 		do {
975 			c = fgetwc(fp);
976 		} while (c == SPACE || c == TAB);
977 		if (c == DOLLAR) {
978 			directive(fp);
979 			continue;
980 		}
981 
982 		if (isascii(c) && isdigit(c)) {
983 			no = get_number(fp, c);
984 			if (no == 0) {
985 				FPRINTF(stderr, MSG(10), msgfname, lineno);
986 				exit(1);
987 			}
988 			if (no > NL_MSGMAX) {
989 				FPRINTF(stderr, MSG(11), msgfname,
990 					lineno, no, NL_MSGMAX);
991 			}
992 			c = fgetwc(fp);
993 			if (c == NEWLINE || c == EOF) {
994 				delete_msg(no);
995 				if (c == NEWLINE)
996 					++lineno;
997 				else
998 					return;
999 				continue;
1000 			} else {
1001 				if (c != SPACE && c != TAB)
1002 					(void) ungetwc(c, fp);
1003 				get_text(fp);
1004 				insert_msg(no, text_len, text);
1005 				continue;
1006 			}
1007 		}
1008 
1009 		if (c == NEWLINE) {
1010 			++lineno;
1011 			continue;
1012 		}
1013 		if (c == EOF)
1014 			return;
1015 
1016 		FPRINTF(stderr, MSG(22), msgfname, lineno);
1017 		exit(1);
1018 	}
1019 }
1020 
1021 /*
1022  * Write double linked list to the file.
1023  * It first converts a linked list to one chunk of memory and
1024  * write it to file.
1025  */
1026 void
1027 writecat(fd, pathname)
1028 	int		fd;
1029 	char	*pathname;
1030 {
1031 	int		i, n;
1032 	int		nsets;
1033 	int		mem;
1034 	int		nmsgs;
1035 	int		text_size;
1036 	int		first_msg_hdr;
1037 	int		msg_offset;
1038 	unsigned	nbytes;
1039 	char	*cat;
1040 	struct	_cat_hdr	*hdrp;
1041 	struct	cat_set		*setp;
1042 	struct	cat_msg		*msgp;
1043 	struct	_cat_set_hdr	*set;
1044 	struct	_cat_msg_hdr	*msg;
1045 	char	*text;
1046 
1047 	/* compute number of sets, number of messages, the total text size */
1048 	nsets = 0;
1049 	nmsgs = 0;
1050 	text_size = 0;
1051 	for (setp = first_set; setp; setp = setp->next) {
1052 		++nsets;
1053 		for (msgp = setp->first_msg; msgp; msgp = msgp->next) {
1054 			++nmsgs;
1055 			text_size += msgp->msg_len;
1056 		}
1057 	}
1058 
1059 	mem = nsets * _CAT_SET_HDR_SIZE + nmsgs * _CAT_MSG_HDR_SIZE + text_size;
1060 	n = _CAT_HDR_SIZE + mem;
1061 	cat = MALLOC(n);
1062 	if (cat == 0) {
1063 		FPRINTF(stderr, MSG(6));
1064 		exit(1);
1065 	}
1066 	MEMSET(cat, 0, n);
1067 
1068 	hdrp = (struct _cat_hdr *) cat;
1069 	hdrp->__hdr_magic = _CAT_MAGIC;
1070 	hdrp->__nsets = nsets;
1071 	hdrp->__mem = mem;
1072 	hdrp->__msg_hdr_offset = nsets * _CAT_SET_HDR_SIZE;
1073 	hdrp->__msg_text_offset = nsets * _CAT_SET_HDR_SIZE +
1074 				nmsgs * _CAT_MSG_HDR_SIZE;
1075 
1076 	set = (struct _cat_set_hdr *) (cat + _CAT_HDR_SIZE);
1077 	msg = (struct _cat_msg_hdr *) (set + nsets);
1078 	text = (char *) (msg + nmsgs);
1079 
1080 	/* convert linked list to one chunk of memory */
1081 	first_msg_hdr = 0;
1082 	msg_offset = 0;
1083 	for (setp = first_set; setp; ++set, setp = setp->next) {
1084 		set->__set_no = setp->set_no;
1085 		set->__first_msg_hdr = first_msg_hdr;
1086 		nmsgs = 0;
1087 		for (msgp = setp->first_msg; msgp; ++msg, msgp = msgp->next) {
1088 			++nmsgs;
1089 			msg->__msg_no = msgp->msg_no;
1090 			msg->__msg_len = msgp->msg_len;
1091 			msg->__msg_offset = msg_offset;
1092 			if (msgp->msg_len > 0) {
1093 				MEMCPY(text, msgp->s, msgp->msg_len);
1094 				text += msgp->msg_len;
1095 				msg_offset += msgp->msg_len;
1096 			}
1097 		}
1098 		set->__nmsgs = nmsgs;
1099 		first_msg_hdr += nmsgs;
1100 	}
1101 
1102 	/* write one chunk of memory to file */
1103 	nbytes = 0;
1104 	while (nbytes < n) {
1105 		i = write(fd, cat + nbytes, n - nbytes);
1106 		if (i < 0) {
1107 			if (errno != EINTR) {
1108 				FPRINTF(stderr, MSG(8), pathname);
1109 				perror("");
1110 				exit(1);
1111 			}
1112 		} else {
1113 			nbytes += n;
1114 		}
1115 	}
1116 
1117 	free(cat);
1118 }
1119 
1120 int
1121 main(argc, argv)
1122 	int		argc;
1123 	char	*argv[];
1124 {
1125 	int		i;
1126 	int		cat_exists;
1127 
1128 	(void) setlocale(LC_ALL, "");
1129 #if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
1130 #define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it weren't */
1131 #endif
1132 	(void) textdomain(TEXT_DOMAIN);
1133 
1134 	if (argc < 3) {
1135 		FPRINTF(stderr, MSG(1));
1136 		exit(1);
1137 	}
1138 	catfname = argv[1];
1139 	cat_exists = 0;
1140 	if ((*catfname == '-') && (*(catfname + 1) == '\0')) {
1141 		catfd = 1;				/* Use stdout */
1142 	} else {
1143 		catfd = open(catfname, O_WRONLY | O_CREAT | O_EXCL, 0666);
1144 		if (catfd < 0) {	/* file exists */
1145 			if (errno != EEXIST ||
1146 			    (catfd = open(catfname, O_RDWR)) < 0) {
1147 				/* cannot open file */
1148 				FPRINTF(stderr, MSG(2), catfname);
1149 				perror("");
1150 				exit(1);
1151 			}
1152 			cat_exists = 1;
1153 			/* read catalog file into memory */
1154 			readcat(catfd, catfname);
1155 			if (lseek(catfd, 0L, 0) < 0) {
1156 				FPRINTF(stderr, MSG(7), catfname);
1157 				perror("");
1158 				exit(1);
1159 			}
1160 		}
1161 	}
1162 
1163 	/* process all message source files */
1164 	if ((**(argv + 2) == '-') && (*(*(argv + 2) + 1) == '\0')) {
1165 		if (argc != 3) {
1166 			FPRINTF(stderr, MSG(1));
1167 			exit(1);
1168 		} else {
1169 			read_msgfile(stdin, MSG(21));
1170 		}
1171 	} else {
1172 		for (i = 2; i < argc; ++i) {
1173 			FILE	*fp;
1174 			fp = fopen(*(argv + i), "r");
1175 			if (fp == NULL) {
1176 				FPRINTF(stderr, MSG(2), *(argv + i));
1177 				perror("");
1178 				exit(1);
1179 			}
1180 			read_msgfile(fp, *(argv + i));
1181 			(void) fclose(fp);
1182 		}
1183 	}
1184 
1185 	if (cat_exists)
1186 		(void) ftruncate(catfd, 0L);
1187 
1188 	/* write catalog to file */
1189 	writecat(catfd, catfname);
1190 	return (0);
1191 }
1192