/*
 * Copyright (c) 1990, 1991, 1994, Sun Microsystems, Inc.
 * All rights reserved.
 */

#ident	"@(#)gencat.c	1.11	95/03/30 SMI"

#include <nl_types.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <libintl.h>

#ifndef NL_MSGMAX
#define	NL_MSGMAX 32767
#endif

#ifndef NL_SETMAX
#define	NL_SETMAX 255
#endif

#ifndef NL_TEXTMAX
#define	NL_TEXTMAX 2048
#endif

#define	BS		'\b'
#define	CR		'\r'
#define	DOLLAR	'$'
#define	FF		'\f'
#define	NEWLINE	'\n'
#define	NUL		'\000'
#define	REVERSE_SOLIDUS '\\'
#define	SPACE	' '
#define	TAB		'\t'
#define	VTAB	'\v'

#define	FPRINTF			(void) fprintf
#define	FREE(x)			free((char *)(x))
#define	MALLOC(n)		malloc((unsigned)(n))
#define	MEMCPY(dst, src, n) \
		(void) memcpy((char *)(dst), (char *)(src), (int)(n))
#define	MEMSET(s, c, n)	(void) memset((char *)(s), (int)(c), (int)(n));
#define	MSG(n)			gettext(MSG ## n)
#define	READ(fd, p, n)	read((int)(fd), (char *)(p), (unsigned)(n))
#define	REALLOC(x, n)	realloc((char *)(x), (unsigned)(n))

/* double linked list */
struct cat_set {
	struct cat_set	*prev;
	struct cat_set	*next;
	int				set_no;
	struct cat_msg	*first_msg;
};

/* double linked list */
struct cat_msg {
	struct cat_msg	*prev;
	struct cat_msg	*next;
	int				msg_no;
	int				msg_len;
	char			s[1];
};

int		catfd;		/* File descriptor of catalog file */
char	*catfname;	/* Catalog file name */
char	*msgfname;	/* message source file name */
int		ateof;		/* boolean indicating END-OF-FILE */
int		lineno;		/* the line number of message source file */
int		quoting;	/* boolean indicating quotes is used */
int		quote;		/* the current quote */
int		text_len;	/* message text length */
int		text_size;	/* the size of allocated text memory */
char	*text;		/* messsge text */

struct _cat_hdr	hdr;
int				current_set_no;	/* the current set number */
struct cat_set	*first_set;	/* the pointer to the first set */
struct cat_set	*current_set;	/* the pointer to the current set */
struct cat_msg	*current_msg;	/* the pointer to the first message */


/* Error message */
/* 0 */
#define	MSG0	""
/* 1 */
#define	MSG1	"usage: gencat catfile msgfile ...\n"
/* 2 */
#define	MSG2	"gencat: cannot open \"%s\"\n"
/* 3 */
#define	MSG3	"gencat: read error on \"%s\"\n"
/* 4 */
#define	MSG4	"gencat: bad magic number (%#lx)\n"
/* 5 */
#define	MSG5	"gencat: corrupt catalogue file \"%s\"\n"
/* 6 */
#define	MSG6	"gencat: memory limit exceeded\n"
/* 7 */
#define	MSG7	"gencat: seek error on \"%s\"\n"
/* 8 */
#define	MSG8	"gencat: write error on \"%s\"\n"
/* 9 */
#define	MSG9	"gencat: \"%s\", line %d: number too large (%s)\n"
/* 10 */
#define	MSG10	"gencat: \"%s\", line %d: 0 is not a permissible " \
				"message number\n"
/* 11 */
#define	MSG11	"gencat: \"%s\", line %d: warning, message number %d " \
				"exceeds limit (%d)\n"
/* 12 */
#define	MSG12	"gencat: \"%s\", line %d: missing quote (%wc)\n"
/* 13 */
#define	MSG13	"gencat: \"%s\", line %d: character value too large ('\\%o')\n"
/* 14 */
#define	MSG14	"gencat: \"%s\", line %d: extra characters following " \
				"message text\n"
/* 15 */
#define	MSG15	"gencat: \"%s\", line %d: extra characters following " \
				"$quote directive\n"
/* 16 */
#define	MSG16	"gencat: \"%s\", line %d: no set number specified in " \
				"$set directive\n"
/* 17 */
#define	MSG17	"getcat: \"%s\", line %d: 0 is not a permissible set number\n"
/* 18 */
#define	MSG18	"gencat: \"%s\", line %d: warning, set number %d " \
				"exceeds limit (%d)\n"
/* 19 */
#define	MSG19	"gencat: \"%s\", line %d: unknown directive %s\n"
/* 20 */
#define	MSG20	"gencat: \"%s\", line %d: no set number specified in " \
				"$delset directive\n"
/* 21 */
#define	MSG21	"stdin"
/* 22 */
#define	MSG22	"gencat: \"%s\", line %d: number or $ expected\n"

struct cat_set *
new_set(n)
	int		n;
{
	struct cat_set *p;

	p = (struct cat_set *) MALLOC(sizeof (struct cat_set));
	if (p == NULL) {
		FPRINTF(stderr, MSG(6));
		exit(1);
	}
	p->next = NULL;
	p->prev = NULL;
	p->set_no = n;
	p->first_msg = NULL;
	return (p);
}

void
find_set(no)
	int		no;
{
	struct cat_set	*prev, *next;

	if (current_set && current_set->set_no == no) {
		return;
	}

	current_set_no = no;
	current_msg = NULL;
	/* if no set exists, create a new set */
	if (current_set == NULL) {
		if (first_set == NULL) {
			current_set = first_set = new_set(no);
			return;
		}
		current_set = first_set;
		if (current_set->set_no == no)
			return;
	}

	if (current_set->set_no > no) {
		if (first_set->set_no > no) {
			/* prepend a new set */
			current_set = new_set(no);
			current_set->next = first_set;
			first_set->prev = current_set;
			first_set = current_set;
			return;
		}
		current_set = first_set;
		if (current_set->set_no == no)
			return;
	}

	/* search for the set number 'no' */
	while (current_set->next && current_set->next->set_no < no)
		current_set = current_set->next;

	if (current_set->next && current_set->next->set_no == no) {
		/* set number 'no' found */
		current_set = current_set->next;
		return;
	}

	/* If set number is not found, insert a new set in the middle */
	prev = current_set;
	next = current_set->next;
	current_set = new_set(no);
	current_set->prev = prev;
	current_set->next = next;
	if (prev)
		prev->next = current_set;
	else
		first_set = current_set;
	if (next)
		next->prev = current_set;
}

void
delete_set(no)
	int		no;
{
	struct cat_set	*prev, *next, *setp;
	struct cat_msg	*p, *q;

	for (setp = first_set; setp && setp->set_no < no; setp = setp->next)
		continue;

	if (setp == NULL || setp->set_no != no)	/* set not found */
		return;

	if (setp == current_set) {
		current_set = NULL;
		current_msg = NULL;
	}

	/* free all messages in the set */
	for (p = setp->first_msg; p; p) {
		q = p->next;
		FREE(p);
		p = q;
	}

	/* do the link operation to delete the set */
	prev = setp->prev;
	next = setp->next;
	FREE(setp);
	if (prev)
		prev->next = next;
	else
		first_set = next;
	if (next)
		next->prev = prev;
}

struct cat_msg *
new_msg(no, len, text)
	int		no;
	int		len;
	char	*text;
{
	struct cat_msg	*p;

	p = (struct cat_msg *) MALLOC(sizeof (struct cat_msg) + len);
	if (p == NULL) {
		FPRINTF(stderr, MSG(6));
		exit(1);
	}
	p->next = NULL;
	p->prev = NULL;
	p->msg_no = no;
	p->msg_len = len;
	MEMCPY(p->s, text, len);
	return (p);
}


void
insert_msg(no, len, text)
	int		no;
	int		len;
	char	*text;
{
	struct cat_msg	*prev, *next;

	if (current_msg == NULL) {
		if (current_set == NULL)
			find_set(current_set_no);
		current_msg = current_set->first_msg;
		if (current_msg == NULL) {
			current_msg = new_msg(no, len, text);
			current_set->first_msg = current_msg;
			return;
		}
	}
	if (current_msg->msg_no >= no) {
		current_msg = current_set->first_msg;
		if (current_msg->msg_no > no) {
			current_msg = new_msg(no, len, text);
			current_msg->next = current_set->first_msg;
			current_set->first_msg->prev = current_msg;
			current_set->first_msg = current_msg;
			return;
		}
		if (current_msg->msg_no == no) {
			current_msg = new_msg(no, len, text);
			current_msg->next = current_set->first_msg->next;
			if (current_set->first_msg->next)
				current_set->first_msg->next->prev =
					current_msg;
			FREE(current_set->first_msg);
			current_set->first_msg = current_msg;
			return;
		}
	}
	while (current_msg->next && current_msg->next->msg_no < no)
		current_msg = current_msg->next;

	/*
	 * if the same msg number is found, then delte the message and
	 * insert the new message. This is same as replacing message.
	 */
	if (current_msg->next && current_msg->next->msg_no == no) {
		current_msg = current_msg->next;
		prev = current_msg->prev;
		next = current_msg->next;
		FREE(current_msg);
	} else {
		prev = current_msg;
		next = current_msg->next;
	}

	current_msg = new_msg(no, len, text);
	current_msg->prev = prev;
	current_msg->next = next;
	if (prev)
		prev->next = current_msg;
	else
		current_set->first_msg = current_msg;
	if (next)
		next->prev = current_msg;
}

void
delete_msg(no)
	int		no;
{
	struct cat_set	*p = current_set;
	struct cat_msg	*prev, *next;

	if (current_msg == NULL) {
		if (current_set == NULL)
			for (p = first_set; p && p->set_no < current_set_no;
							p = p->next)
				continue;
		if (p == NULL || p->set_no != current_set_no)
			return;
		current_set = p;
		current_msg = current_set->first_msg;
		if (current_msg == NULL)
			return;
	}
	if (current_msg->msg_no > no)
		current_msg = current_set->first_msg;

	while (current_msg && current_msg->msg_no != no)
		current_msg = current_msg->next;

	if (current_msg && current_msg->msg_no == no) {
		prev = current_msg->prev;
		next = current_msg->next;
		FREE(current_msg);
		if (prev) {
			current_msg = prev;
			prev->next = next;
		} else {
			current_set->first_msg = next;
			current_msg = next;
		}
		if (next)
			next->prev = prev;
	}
}

int
read_block(fd, p, n, pathname)
	int		fd;
	char	*p;
	int		n;
	char	*pathname;
{
	int		nbytes, bytes_read;

	if (n == 0)
		return (0);

	nbytes = 0;
	while (nbytes < n) {
		bytes_read = READ(fd, p + nbytes, n - nbytes);
		if (bytes_read < 0) {
			if (errno != EINTR) {
				FPRINTF(stderr, MSG(3), pathname);
				perror("");
				exit(1);
			}
		} else if (bytes_read == 0)
			break;
		else
			nbytes += bytes_read;
	}

	return (nbytes);
}

/*
 * Check if catalog file read is valid
 *
 */
int
cat_ok(cat)
	char	*cat;
{
	int		i, j;
	int		nmsgs;
	int		msg_no;
	struct	_cat_msg_hdr	*msg;
	int		set_no;
	int		first_msg_hdr;
	struct	_cat_set_hdr	*set;

	set = (struct _cat_set_hdr *) cat;
	set_no = 0;
	for (i = 0; i < hdr.__nsets; ++set, ++i) {
		if (set->__set_no < set_no)
			return (0);
		set_no = set->__set_no;
		nmsgs = set->__nmsgs;
		if (nmsgs < 0)
			return (0);
		if (nmsgs == 0)
			continue;
		first_msg_hdr = set->__first_msg_hdr;
		if (first_msg_hdr < 0)
			return (0);
		if (hdr.__msg_hdr_offset + (first_msg_hdr + nmsgs) *
					_CAT_MSG_HDR_SIZE > hdr.__mem)
			return (0);

		msg = (struct _cat_msg_hdr *) (cat + hdr.__msg_hdr_offset) +
						first_msg_hdr;
		msg_no = 0;
		for (j = 0; j < nmsgs; ++msg, ++j) {
			if (msg->__msg_no < msg_no)
				return (0);
			msg_no = msg->__msg_no;
			if (msg->__msg_offset < 0)
				return (0);
			if (hdr.__msg_text_offset + msg->__msg_offset +
						msg->__msg_len > hdr.__mem)
				return (0);
		}
	}

	return (1);
}

/*
 * convert a chunk of catalog file into double linked list format
 */
void
initcat(cat)
	char	*cat;
{
	int		i, j;
	int		nmsgs;
	struct	_cat_set_hdr	*set;
	struct	_cat_msg_hdr	*msg;

	set = (struct _cat_set_hdr *) cat;
	for (i = 0; i < hdr.__nsets; ++set, ++i) {
		nmsgs = set->__nmsgs;
		if (nmsgs == 0)
			continue;
		find_set(set->__set_no);
		msg = (struct _cat_msg_hdr *) (cat + hdr.__msg_hdr_offset)
			+ set->__first_msg_hdr;
		current_msg = current_set->first_msg;
		for (j = 0; j < nmsgs; ++msg, ++j) {
			insert_msg(msg->__msg_no, msg->__msg_len,
			    cat + hdr.__msg_text_offset + msg->__msg_offset);
		}
	}
}

/*
 * read a catalog file in a chunk and convert it to double linked list.
 */
void
readcat(fd, pathname)
	int		fd;
	char	*pathname;
{
	int		i;
	char	*cat;

	i = read_block(fd, (char *) &hdr, _CAT_HDR_SIZE, pathname);
	if (i == 0)
		return;

	if (i >= 4 && hdr.__hdr_magic != _CAT_MAGIC) {
		FPRINTF(stderr, MSG(4), hdr.__hdr_magic);
		exit(1);
	}
	if (i < _CAT_HDR_SIZE || hdr.__nsets < 0) {
		FPRINTF(stderr, MSG(5), pathname);
		exit(1);
	}
	if (hdr.__nsets == 0)
		return;

	if (hdr.__mem < 0 ||
	    hdr.__msg_hdr_offset < 0 ||
	    hdr.__msg_text_offset < 0 ||
	    hdr.__mem < hdr.__nsets * _CAT_SET_HDR_SIZE ||
	    hdr.__mem < hdr.__msg_hdr_offset ||
	    hdr.__mem < hdr.__msg_text_offset) {
		FPRINTF(stderr, MSG(5), pathname);
		exit(1);
	}
	cat = MALLOC(hdr.__mem);
	if (cat == NULL) {
		FPRINTF(stderr, MSG(6));
		exit(1);
	}
	i = read_block(fd, cat, hdr.__mem, pathname);
	if (i < hdr.__mem || !cat_ok(cat)) {
		FPRINTF(stderr, MSG(5), pathname);
		exit(1);
	}
	initcat(cat);

	FREE(cat);
}

/*
 * Extend the memory in 1000 byte chunks whenever runs out of text space.
 */
void
extend_text()
{
	text_size += 1000;
	if (text)
		text = REALLOC(text, text_size);
	else
		text = MALLOC(text_size);
	if (text == NULL) {
		FPRINTF(stderr, MSG(6));
		exit(1);
	}
}

int
get_number(fp, c)
	FILE	*fp;
	int		c;
{
	int		i, n;
	char	*s, *t;

	i = 0;
	do {
		while (i >= text_size)
			extend_text();
		text[i] = c;
		++i;
		c = getc(fp);
	}
	while (isdigit(c));
	(void) ungetc(c, fp);

	while (i >= text_size)
		extend_text();
	text[i] = NUL;

	for (s = text; *s == '0'; ++s)
		continue;

	n = 0;
	for (t = s; isdigit(*t); ++t) {
		if (n > INT_MAX / 10 ||
			(n == INT_MAX / 10 && *t > '0' + INT_MAX % 10)) {
			FPRINTF(stderr, MSG(9), msgfname, lineno, s);
			exit(1);
		}
		n = 10 * n + (*t - '0');
	}

	return (n);
}

void
get_text(fp)
	FILE	*fp;
{
	int		c;
	int		n;

	text_len = 0;
	c = fgetwc(fp);
	if (quoting && c == quote) {	/* quote is used */
		c = fgetwc(fp);
		while (c != quote) {
			if (c == NEWLINE || c == EOF) {
				FPRINTF(stderr, MSG(12), msgfname, lineno,
								quote);
				exit(1);
			}
			if (c == REVERSE_SOLIDUS) {
				c = fgetwc(fp);
				switch (c) {
				case EOF:
					FPRINTF(stderr, MSG(12), msgfname,
						lineno, quote);
					exit(1);
					break;
				case NEWLINE:
					++lineno;
					c = fgetwc(fp);
					continue;
					/* NOTREACHED */
					break;
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
					n = (c - '0');
					c = fgetwc(fp);
					if (c >= '0' && c <= '7') {
						n = 8 * n + (c - '0');
						c = fgetwc(fp);
						if (c >= '0' && c <= '7')
							n = 8 * n + (c - '0');
						else
							(void) ungetwc(c, fp);
					} else
						(void) ungetwc(c, fp);
					if (n > UCHAR_MAX) {
						FPRINTF(stderr, MSG(13),
							msgfname, lineno, n);
						exit(1);
					}
					c = n;
					break;

				case 'n':
					c = NEWLINE;
					break;

				case 't':
					c = TAB;
					break;

				case 'v':
					c = VTAB;
					break;

				case 'b':
					c = BS;
					break;

				case 'r':
					c = CR;
					break;

				case 'f':
					c = FF;
					break;
				}
			}
			while ((text_len + (int)MB_CUR_MAX + 1) >= text_size)
				extend_text();
			if ((n = wctomb(&text[text_len], c)) > 0)
				text_len += n;
			c = fgetwc(fp);
		}

		while ((text_len + 1) >= text_size)
			extend_text();
		text[text_len] = '\0';
		++text_len;

		do {
			c = getc(fp);
		} while (c == SPACE || c == TAB);
		if (c == NEWLINE) {
			++lineno;
			return;
		}
		if (c == EOF) {
			ateof = 1;
			return;
		}
		FPRINTF(stderr, MSG(14), msgfname, lineno);
		exit(1);
	}

	while (c != NEWLINE && c != EOF) {	/* quote is not used */
		if (c == REVERSE_SOLIDUS) {
			c = fgetwc(fp);
			switch (c) {
			case EOF:
				return;

			case NEWLINE:
				++lineno;
				c = fgetwc(fp);
				continue;

			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
				n = (c - '0');
				c = fgetwc(fp);
				if (c >= '0' && c <= '7') {
					n = 8 * n + (c - '0');
					c = fgetwc(fp);
					if (c >= '0' && c <= '7')
						n = 8 * n + (c - '0');
					else
						(void) ungetwc(c, fp);
				} else
					(void) ungetwc(c, fp);
				if (n > UCHAR_MAX) {
					FPRINTF(stderr, MSG(13), msgfname,
							lineno, n);
					exit(1);
				}
				c = n;
				break;

			case 'n':
				c = NEWLINE;
				break;

			case 't':
				c = TAB;
				break;

			case 'v':
				c = VTAB;
				break;

			case 'b':
				c = BS;
				break;

			case 'r':
				c = CR;
				break;

			case 'f':
				c = FF;
				break;
			}
		}
		while ((text_len + (int)MB_CUR_MAX + 1) >= text_size)
			extend_text();
		if ((n = wctomb(&text[text_len], c)) > 0)
			text_len += n;
		c = fgetwc(fp);
	}

	while ((text_len + 1) >= text_size)
		extend_text();
	text[text_len] = '\0';
	++text_len;

	if (c == NEWLINE)
		++lineno;
	else
		ateof = 1;
}

/*
 * This routine handles $ <comment>, $set, $delset, $quote
 */
void
directive(fp)
	FILE	*fp;
{
	int		c;
	int		n;

	c = fgetwc(fp);
	if (c == SPACE || c == TAB) {	/* $ <comment */
		do {
			c = fgetwc(fp);
		} while (c != NEWLINE && c != EOF);
	}
	if (c == NEWLINE) {
		++lineno;
		return;
	}
	if (c == EOF) {
		ateof = 1;
		return;
	}
	text_len = 1;
	while (text_len >= text_size)
		extend_text();
	text[0] = DOLLAR;
	while (isascii(c) && isalpha(c)) {
		while ((text_len + 1) >= text_size)
			extend_text();
		text[text_len] = c;
		++text_len;
		c = fgetwc(fp);
	}

	while ((text_len + 1) >= text_size)
		extend_text();
	text[text_len] = NUL;

	if (strcmp(text, "$set") == 0) {
		while (c == SPACE || c == TAB)
			c = fgetwc(fp);
		if (!isascii(c) || !isdigit(c)) {
			FPRINTF(stderr, MSG(16), msgfname, lineno);
			exit(1);
		}
		n = get_number(fp, c);
		if (n == 0) {
			FPRINTF(stderr, MSG(17), msgfname, lineno);
			exit(1);
		}
		if (n > NL_SETMAX) {
			FPRINTF(stderr, MSG(18), msgfname, lineno,
						n, NL_SETMAX);
		}
		find_set(n);
		do {	/* skip comment */
			c = getc(fp);
		} while (c != NEWLINE && c != EOF);
		if (c == NEWLINE)
			++lineno;
		else
			ateof = 1;
		return;
	} else if (strcmp(text, "$delset") == 0) {
		while (c == SPACE || c == TAB)
			c = fgetwc(fp);
		if (!isascii(c) || !isdigit(c)) {
			FPRINTF(stderr, MSG(20), msgfname, lineno);
			exit(1);
		}
		n = get_number(fp, c);
		if (n == 0) {
			FPRINTF(stderr, MSG(17), msgfname, lineno);
			exit(1);
		}
		if (n > NL_SETMAX) {
			FPRINTF(stderr, MSG(18), msgfname, lineno,
						n, NL_SETMAX);
		}
		delete_set(n);
		do {	/* skip comment */
			c = getc(fp);
		} while (c != NEWLINE && c != EOF);
		if (c == NEWLINE)
			++lineno;
		else
			ateof = 1;
		return;
	} else if (strcmp(text, "$quote") == 0) {
		if (c == NEWLINE) {
			quoting = 0;
			++lineno;
			return;
		}
		if (c == EOF) {
			quoting = 0;
			ateof = 1;
			return;
		}
		if (c == SPACE || c == TAB)
			c = fgetwc(fp);
		if (c == NEWLINE) {
			quoting = 0;
			++lineno;
			return;
		}
		if (c == EOF) {
			quoting = 0;
			ateof = 1;
			return;
		}
		quoting = 1;
		quote = c;
		do {	/* skip comment */
			c = getc(fp);
		} while (c == SPACE || c == TAB);
		if (c == NEWLINE) {
			++lineno;
			return;
		}
		if (c == EOF) {
			ateof = 1;
			return;
		}
		FPRINTF(stderr, MSG(15), msgfname, lineno);
		exit(1);
	} else {
		FPRINTF(stderr, MSG(19), msgfname, lineno, text);
		exit(1);
	}
}

/*
 * Read message source file and update double linked list message catalog.
 */
void
read_msgfile(fp, pathname)
	FILE	*fp;
	char	*pathname;
{
	int		c;
	int		no;

	ateof = 0;
	msgfname = pathname;
	lineno = 1;
	quoting = 0;
	current_set_no = NL_SETD;
	current_set = NULL;
	current_msg = NULL;

	for (;;) {
		if (ateof)
			return;
		do {
			c = fgetwc(fp);
		} while (c == SPACE || c == TAB);
		if (c == DOLLAR) {
			directive(fp);
			continue;
		}

		if (isascii(c) && isdigit(c)) {
			no = get_number(fp, c);
			if (no == 0) {
				FPRINTF(stderr, MSG(10), msgfname, lineno);
				exit(1);
			}
			if (no > NL_MSGMAX) {
				FPRINTF(stderr, MSG(11), msgfname,
					lineno, no, NL_MSGMAX);
			}
			c = fgetwc(fp);
			if (c == NEWLINE || c == EOF) {
				delete_msg(no);
				if (c == NEWLINE)
					++lineno;
				else
					return;
				continue;
			} else {
				if (c != SPACE && c != TAB)
					(void) ungetwc(c, fp);
				get_text(fp);
				insert_msg(no, text_len, text);
				continue;
			}
		}

		if (c == NEWLINE) {
			++lineno;
			continue;
		}
		if (c == EOF)
			return;

		FPRINTF(stderr, MSG(22), msgfname, lineno);
		exit(1);
	}
}

/*
 * Write double linked list to the file.
 * It first converts a linked list to one chunk of memory and
 * write it to file.
 */
void
writecat(fd, pathname)
	int		fd;
	char	*pathname;
{
	int		i, n;
	int		nsets;
	int		mem;
	int		nmsgs;
	int		text_size;
	int		first_msg_hdr;
	int		msg_offset;
	unsigned	nbytes;
	char	*cat;
	struct	_cat_hdr	*hdrp;
	struct	cat_set		*setp;
	struct	cat_msg		*msgp;
	struct	_cat_set_hdr	*set;
	struct	_cat_msg_hdr	*msg;
	char	*text;

	/* compute number of sets, number of messages, the total text size */
	nsets = 0;
	nmsgs = 0;
	text_size = 0;
	for (setp = first_set; setp; setp = setp->next) {
		++nsets;
		for (msgp = setp->first_msg; msgp; msgp = msgp->next) {
			++nmsgs;
			text_size += msgp->msg_len;
		}
	}

	mem = nsets * _CAT_SET_HDR_SIZE + nmsgs * _CAT_MSG_HDR_SIZE + text_size;
	n = _CAT_HDR_SIZE + mem;
	cat = MALLOC(n);
	if (cat == 0) {
		FPRINTF(stderr, MSG(6));
		exit(1);
	}
	MEMSET(cat, 0, n);

	hdrp = (struct _cat_hdr *) cat;
	hdrp->__hdr_magic = _CAT_MAGIC;
	hdrp->__nsets = nsets;
	hdrp->__mem = mem;
	hdrp->__msg_hdr_offset = nsets * _CAT_SET_HDR_SIZE;
	hdrp->__msg_text_offset = nsets * _CAT_SET_HDR_SIZE +
				nmsgs * _CAT_MSG_HDR_SIZE;

	set = (struct _cat_set_hdr *) (cat + _CAT_HDR_SIZE);
	msg = (struct _cat_msg_hdr *) (set + nsets);
	text = (char *) (msg + nmsgs);

	/* convert linked list to one chunk of memory */
	first_msg_hdr = 0;
	msg_offset = 0;
	for (setp = first_set; setp; ++set, setp = setp->next) {
		set->__set_no = setp->set_no;
		set->__first_msg_hdr = first_msg_hdr;
		nmsgs = 0;
		for (msgp = setp->first_msg; msgp; ++msg, msgp = msgp->next) {
			++nmsgs;
			msg->__msg_no = msgp->msg_no;
			msg->__msg_len = msgp->msg_len;
			msg->__msg_offset = msg_offset;
			if (msgp->msg_len > 0) {
				MEMCPY(text, msgp->s, msgp->msg_len);
				text += msgp->msg_len;
				msg_offset += msgp->msg_len;
			}
		}
		set->__nmsgs = nmsgs;
		first_msg_hdr += nmsgs;
	}

	/* write one chunk of memory to file */
	nbytes = 0;
	while (nbytes < n) {
		i = write(fd, cat + nbytes, n - nbytes);
		if (i < 0) {
			if (errno != EINTR) {
				FPRINTF(stderr, MSG(8), pathname);
				perror("");
				exit(1);
			}
		} else {
			nbytes += n;
		}
	}

	free(cat);
}

int
main(argc, argv)
	int		argc;
	char	*argv[];
{
	int		i;
	int		cat_exists;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
#define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it weren't */
#endif
	(void) textdomain(TEXT_DOMAIN);

	if (argc < 3) {
		FPRINTF(stderr, MSG(1));
		exit(1);
	}
	catfname = argv[1];
	cat_exists = 0;
	if ((*catfname == '-') && (*(catfname + 1) == '\0')) {
		catfd = 1;				/* Use stdout */
	} else {
		catfd = open(catfname, O_WRONLY | O_CREAT | O_EXCL, 0666);
		if (catfd < 0) {	/* file exists */
			if (errno != EEXIST ||
			    (catfd = open(catfname, O_RDWR)) < 0) {
				/* cannot open file */
				FPRINTF(stderr, MSG(2), catfname);
				perror("");
				exit(1);
			}
			cat_exists = 1;
			/* read catalog file into memory */
			readcat(catfd, catfname);
			if (lseek(catfd, 0L, 0) < 0) {
				FPRINTF(stderr, MSG(7), catfname);
				perror("");
				exit(1);
			}
		}
	}

	/* process all message source files */
	if ((**(argv + 2) == '-') && (*(*(argv + 2) + 1) == '\0')) {
		if (argc != 3) {
			FPRINTF(stderr, MSG(1));
			exit(1);
		} else {
			read_msgfile(stdin, MSG(21));
		}
	} else {
		for (i = 2; i < argc; ++i) {
			FILE	*fp;
			fp = fopen(*(argv + i), "r");
			if (fp == NULL) {
				FPRINTF(stderr, MSG(2), *(argv + i));
				perror("");
				exit(1);
			}
			read_msgfile(fp, *(argv + i));
			(void) fclose(fp);
		}
	}

	if (cat_exists)
		(void) ftruncate(catfd, 0L);

	/* write catalog to file */
	writecat(catfd, catfname);
	return (0);
}
