root/usr/src/cmd/gencat/gencat.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1990, 1991, 1994, Sun Microsystems, Inc.
 * All rights reserved.
 */

#ident  "%Z%%M% %I%     %E% 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);
}