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 */
76struct 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 */
84struct 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
92int		catfd;		/* File descriptor of catalog file */
93char	*catfname;	/* Catalog file name */
94char	*msgfname;	/* message source file name */
95int		ateof;		/* boolean indicating END-OF-FILE */
96int		lineno;		/* the line number of message source file */
97int		quoting;	/* boolean indicating quotes is used */
98int		quote;		/* the current quote */
99int		text_len;	/* message text length */
100int		text_size;	/* the size of allocated text memory */
101char	*text;		/* messsge text */
102
103struct _cat_hdr	hdr;
104int				current_set_no;	/* the current set number */
105struct cat_set	*first_set;	/* the pointer to the first set */
106struct cat_set	*current_set;	/* the pointer to the current set */
107struct 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
165struct cat_set *
166new_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
183void
184find_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
244void
245delete_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
281struct cat_msg *
282new_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
303void
304insert_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
369void
370delete_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
410int
411read_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 */
444int
445cat_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 */
495void
496initcat(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 */
523void
524readcat(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 */
573void
574extend_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
587int
588get_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
626void
627get_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 */
821void
822directive(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 */
955void
956read_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 */
1026void
1027writecat(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
1120int
1121main(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