root/usr/src/cmd/msgfmt/msgfmt.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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "sun_msgfmt.h"

static void     read_psffm(char *);
static void     sortit(char *, char *);
static wchar_t  *consume_whitespace(wchar_t *);
static char     expand_meta(wchar_t **);
static struct domain_struct     *find_domain_node(char *);
static void     insert_message(struct domain_struct *, char *, char *);
static void     output_all_mo_files(void);
static void     output_one_mo_file(struct domain_struct *);
static size_t _mbsntowcs(wchar_t **, char **, size_t *);

#ifdef DEBUG
static void     printlist(void);
#endif

static char     gcurrent_domain[TEXTDOMAINMAX+1];
static char     *gmsgid;                /* Stores msgid when read po file */
static char     *gmsgstr;               /* Stores msgstr when read po file */
static int      gmsgid_size;            /* The current size of msgid buffer */
static int      gmsgstr_size;           /* The current size of msgstr buffer */
static char     *outfile = NULL;
static int      linenum;                /* The line number in the file */
static int      msgid_linenum;          /* The last msgid token line number */
static int      msgstr_linenum;         /* The last msgstr token line number */

static int      oflag = 0;
static int      sun_p = 0;
int     verbose = 0;

static struct domain_struct     *first_domain = NULL;
static struct domain_struct     *last_used_domain = NULL;

static int      mbcurmax;

static char     **oargv;
static char     *inputdir;

extern void     check_gnu(char *, size_t);

#define GNU_MSGFMT      "/usr/lib/gmsgfmt"
void
invoke_gnu_msgfmt(void)
{
        /*
         * Transferring to /usr/lib/gmsgfmt
         */
        char    *gnu_msgfmt;
#ifdef  DEBUG_MSGFMT
        gnu_msgfmt = getenv("GNU_MSGFMT");
        if (!gnu_msgfmt)
                gnu_msgfmt = GNU_MSGFMT;
#else
        gnu_msgfmt = GNU_MSGFMT;
#endif

        if (verbose) {
                diag(gettext(DIAG_INVOKING_GNU));
        }

        (void) execv(gnu_msgfmt, oargv);
        /* exec failed */
        error(gettext(ERR_EXEC_FAILED), gnu_msgfmt);
        /* NOTREACHED */
}

static void
usage(void)
{
        (void) fprintf(stderr, gettext(ERR_USAGE));
        exit(2);
}

/*
 * msgfmt - Generate binary tree for runtime gettext() using psffm: "Portable
 * Source File Format for Messages" file template. This file may have
 * previously been generated by the xgettext filter for c source files.
 */

int
main(int argc, char **argv)
{
        int     ret;
        static struct flags     flag;

        (void) setlocale(LC_ALL, "");
#if     !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN     "SYS_TEST"
#endif
        (void) textdomain(TEXT_DOMAIN);

        oargv = argv;
        ret = parse_option(&argc, &argv, &flag);
        if (ret == -1) {
                usage();
                /* NOTREACHED */
        }

        if (flag.sun_p) {
                /* never invoke gnu msgfmt */
                if (flag.gnu_p) {
                        error(gettext(ERR_GNU_ON_SUN));
                        /* NOTREACHED */
                }
                sun_p = flag.sun_p;
        }
        if (flag.idir) {
                inputdir = flag.idir;
        }
        if (flag.ofile) {
                oflag = 1;
                outfile = flag.ofile;
        }
        if (flag.verbose) {
                verbose = 1;
        }

        if (flag.gnu_p) {
                /* invoke /usr/lib/gmsgfmt */
                invoke_gnu_msgfmt();
                /* NOTREACHED */
        }

        /*
         * read all portable object files specified in command arguments.
         * Allocate initial size for msgid and msgstr. If it needs more
         * spaces, realloc later.
         */
        gmsgid = (char *)Xmalloc(MAX_VALUE_LEN);
        gmsgstr = (char *)Xmalloc(MAX_VALUE_LEN);

        gmsgid_size = gmsgstr_size = MAX_VALUE_LEN;
        (void) memset(gmsgid, 0, gmsgid_size);
        (void) memset(gmsgstr, 0, gmsgstr_size);

        mbcurmax = MB_CUR_MAX;

        while (argc-- > 0) {
                if (verbose) {
                        diag(gettext(DIAG_START_PROC), *argv);
                }
                read_psffm(*argv++);
        }

        output_all_mo_files();

#ifdef DEBUG
        printlist();
#endif

        return (0);

} /* main */



/*
 * read_psffm - read in "psffm" format file, check syntax, printing error
 * messages as needed, output binary tree to file <domain>
 */

static void
read_psffm(char *file)
{
        int     fd;
        static char     msgfile[MAXPATHLEN];
        wchar_t *linebufptr, *p;
        char    *bufptr = 0;
        int     quotefound;     /* double quote was seen */
        int     inmsgid = 0;    /* indicates "msgid" was seen */
        int     inmsgstr = 0;   /* indicates "msgstr" was seen */
        int     indomain = 0;   /* indicates "domain" was seen */
        wchar_t wc;
        char    mb;
        int     n;
        char    token_found;    /* Boolean value */
        unsigned int    bufptr_index = 0; /* current index of bufptr */
        char    *mbuf, *addr;
        size_t  fsize, ln_size, ll;
        wchar_t *linebufhead = NULL;
        struct stat64   statbuf;
        char    *filename;

        /*
         * For each po file to be read,
         * 1) set domain to default and
         * 2) set linenumer to 0.
         */
        (void) strcpy(gcurrent_domain, DEFAULT_DOMAIN);
        linenum = 0;

        if (!inputdir) {
                filename = Xstrdup(file);
        } else {
                size_t  dirlen, filelen, len;

                dirlen = strlen(inputdir);
                filelen = strlen(file);
                len = dirlen + 1 + filelen + 1;
                filename = (char *)Xmalloc(len);
                (void) memcpy(filename, inputdir, dirlen);
                *(filename + dirlen) = '/';
                (void) memcpy(filename + dirlen + 1, file, filelen);
                *(filename + dirlen + 1 + filelen) = '\0';
        }

        fd = open(filename, O_RDONLY);
        if (fd == -1) {
                error(gettext(ERR_OPEN_FAILED), filename);
                /* NOTREACHED */
        }
        if (fstat64(fd, &statbuf) == -1) {
                error(gettext(ERR_STAT_FAILED), filename);
                /* NOTREACHED */
        }
        fsize = (size_t)statbuf.st_size;
        if (fsize == 0) {
                /*
                 * The size of the specified po file is 0.
                 * In Solaris 8 and earlier, msgfmt was silent
                 * for the null po file.  So, just returns
                 * without generating an error message.
                 */
                (void) close(fd);
                free(filename);
                return;
        }
        addr = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0);
        if (addr == MAP_FAILED) {
                error(gettext(ERR_MMAP_FAILED), filename);
                /* NOTREACHED */
        }
        (void) close(fd);

        if (!sun_p)
                check_gnu(addr, fsize);

        mbuf = addr;
        for (;;) {
                if (linebufhead) {
                        free(linebufhead);
                        linebufhead = NULL;
                }
                ln_size = _mbsntowcs(&linebufhead, &mbuf, &fsize);
                if (ln_size == (size_t)-1) {
                        error(gettext(ERR_READ_FAILED), filename);
                        /* NOTREACHED */
                } else if (ln_size == 0) {
                        break;  /* End of File. */
                }
                linenum++;

                linebufptr = linebufhead;
                quotefound = 0;

                switch (*linebufptr) {
                        case L'#':      /* comment    */
                        case L'\n':     /* empty line */
                                continue;
                        case L'\"': /* multiple lines of msgid and msgstr */
                                quotefound = 1;
                                break;
                }

                /*
                 * Process MSGID Tokens.
                 */
                token_found = (wcsncmp(MSGID_TOKEN, linebufptr,
                    MSGID_LEN) == 0) ? 1 : 0;

                if (token_found || (quotefound && inmsgid)) {

                        if (token_found) {
                                if (!CK_NXT_CH(linebufptr, MSGID_LEN+1)) {
                                        diag(gettext(ERR_NOSPC), linenum);
                                        error(gettext(ERR_EXITING));
                                        /* NOTREACHED */
                                }
                        }

                        if (inmsgid && !quotefound) {
                                warning(gettext(WARN_NO_MSGSTR), msgid_linenum);
                                continue;
                        }
                        if (inmsgstr) {
                                sortit(gmsgid, gmsgstr);
                                (void) memset(gmsgid, 0, gmsgid_size);
                                (void) memset(gmsgstr, 0, gmsgstr_size);
                        }

                        if (inmsgid) {
                                /* multiple lines of msgid */
                                /* cancel the previous null termination */
                                bufptr_index--;
                        } else {
                                /*
                                 * The first line of msgid.
                                 * Save linenum of msgid to be used when
                                 * printing warning or error message.
                                 */
                                msgid_linenum = linenum;
                                p = linebufptr;
                                linebufptr = consume_whitespace(
                                    linebufptr + MSGID_LEN);
                                ln_size -= linebufptr - p;
                                bufptr = gmsgid;
                                bufptr_index = 0;
                        }

                        inmsgid = 1;
                        inmsgstr = 0;
                        indomain = 0;
                        goto load_buffer;
                }

                /*
                 * Process MSGSTR Tokens.
                 */
                token_found = (wcsncmp(MSGSTR_TOKEN, linebufptr,
                    MSGSTR_LEN) == 0) ? 1 : 0;
                if (token_found || (quotefound && inmsgstr)) {

                        if (token_found) {
                                if (!CK_NXT_CH(linebufptr, MSGSTR_LEN+1)) {
                                        diag(gettext(ERR_NOSPC), linenum);
                                        error(gettext(ERR_EXITING));
                                        /* NOTREACHED */
                                }
                        }


                        if (inmsgstr && !quotefound) {
                                warning(gettext(WARN_NO_MSGID), msgstr_linenum);
                                continue;
                        }
                        if (inmsgstr) {
                                /* multiple lines of msgstr */
                                /* cancel the previous null termination */
                                bufptr_index--;
                        } else {
                                /*
                                 * The first line of msgstr.
                                 * Save linenum of msgid to be used when
                                 * printing warning or error message.
                                 */
                                msgstr_linenum = linenum;
                                p = linebufptr;
                                linebufptr = consume_whitespace(
                                    linebufptr + MSGSTR_LEN);
                                ln_size -= linebufptr - p;
                                bufptr = gmsgstr;
                                bufptr_index = 0;
                        }

                        inmsgstr = 1;
                        inmsgid = 0;
                        indomain = 0;
                        goto load_buffer;
                }

                /*
                 * Process DOMAIN Tokens.
                 * Add message id and message string to sorted list
                 * if msgstr was processed last time.
                 */
                token_found = (wcsncmp(DOMAIN_TOKEN, linebufptr,
                    DOMAIN_LEN) == 0) ? 1 : 0;
                if ((token_found) || (quotefound && indomain)) {
                        if (token_found) {
                                if (!CK_NXT_CH(linebufptr, DOMAIN_LEN+1)) {
                                        diag(gettext(ERR_NOSPC), linenum);
                                        error(gettext(ERR_EXITING));
                                        /* NOTREACHED */
                                }
                        }


                        /*
                         * process msgid and msgstr pair for previous domain
                         */
                        if (inmsgstr) {
                                sortit(gmsgid, gmsgstr);
                        }

                        /* refresh msgid and msgstr buffer */
                        if (inmsgstr || inmsgid) {
                                (void) memset(gmsgid, 0, gmsgid_size);
                                (void) memset(gmsgstr, 0, gmsgstr_size);
                        }

                        if (indomain) {
                                /* multiple lines of domain */
                                /* cancel the previous null termination */
                                bufptr_index--;
                        } else {
                                p = linebufptr;
                                linebufptr = consume_whitespace(
                                    linebufptr + DOMAIN_LEN);
                                (void) memset(gcurrent_domain, 0,
                                    sizeof (gcurrent_domain));
                                ln_size -= linebufptr - p;
                                bufptr = gcurrent_domain;
                                bufptr_index = 0;
                        }

                        indomain = 1;
                        inmsgid = 0;
                        inmsgstr = 0;
                } /* if */

load_buffer:
                /*
                 * Now, fill up the buffer pointed by bufptr.
                 * At this point bufptr should point to one of
                 * msgid, msgptr, or current_domain.
                 * Otherwise, the entire line is ignored.
                 */

                if (!bufptr) {
                        warning(gettext(WARN_SYNTAX_ERR), linenum);
                        continue;
                }

                if (*linebufptr++ != L'\"') {
                        warning(gettext(WARN_MISSING_QUOTE), linenum);
                        --linebufptr;
                }
                quotefound = 0;

                /*
                 * If there is not enough space in the buffer,
                 * increase buffer by ln_size by realloc.
                 */
                ll = ln_size * mbcurmax;
                if (bufptr == gmsgid) {
                        if (gmsgid_size < (bufptr_index + ll)) {
                                gmsgid = (char *)Xrealloc(gmsgid,
                                    bufptr_index + ll);
                                bufptr = gmsgid;
                                gmsgid_size = bufptr_index + ll;
                        }
                } else if (bufptr == gmsgstr) {
                        if (gmsgstr_size < (bufptr_index + ll)) {
                                gmsgstr = (char *)Xrealloc(gmsgstr,
                                    bufptr_index + ll);
                                bufptr = gmsgstr;
                                gmsgstr_size = bufptr_index + ll;
                        }
                }

                while (wc = *linebufptr++) {
                        switch (wc) {
                        case L'\n':
                                if (!quotefound) {
warning(gettext(WARN_MISSING_QUOTE_AT_EOL), linenum);
                                }
                                break;

                        case L'\"':
                                quotefound = 1;
                                break;

                        case L'\\':
                                if ((mb = expand_meta(&linebufptr)) != '\0')
                                        bufptr[bufptr_index++] = mb;
                                break;

                        default:
                                if ((n = wctomb(&bufptr[bufptr_index], wc)) > 0)
                                        bufptr_index += n;
                        } /* switch */
                        if (quotefound) {
                                /*
                                 * Check if any remaining characters
                                 * after closing quote.
                                 */
                                linebufptr = consume_whitespace(linebufptr);
                                if (*linebufptr != L'\n') {
                                        warning(gettext(WARN_INVALID_STRING),
                                            linenum);
                                }
                                break;
                        }
                } /* while */

                bufptr[bufptr_index++] = '\0';

                (void) strcpy(msgfile, gcurrent_domain);
                (void) strcat(msgfile, ".mo");
        } /* for(;;) */

        if (inmsgstr) {
                sortit(gmsgid, gmsgstr);
        }

        if (linebufhead)
                free(linebufhead);
        if (munmap(addr, statbuf.st_size) == -1) {
                error(gettext(ERR_MUNMAP_FAILED), filename);
                /* NOTREACHED */
        }

        free(filename);
        return;

} /* read_psffm */


/*
 * Skip leading white spaces and tabs.
 */
static wchar_t *
consume_whitespace(wchar_t *buf)
{
        wchar_t *bufptr = buf;
        wchar_t c;

        /*
         * Skip leading white spaces.
         */
        while ((c = *bufptr) != L'\0') {
                if (c == L' ' || c == L'\t') {
                        bufptr++;
                        continue;
                }
                break;
        }
        return (bufptr);
} /* consume_white_space */


/*
 * handle escape sequences.
 */
static char
expand_meta(wchar_t **buf)
{
        wchar_t wc = **buf;
        char    n;

        switch (wc) {
        case L'"':
                (*buf)++;
                return ('\"');
        case L'\\':
                (*buf)++;
                return ('\\');
        case L'b':
                (*buf)++;
                return ('\b');
        case L'f':
                (*buf)++;
                return ('\f');
        case L'n':
                (*buf)++;
                return ('\n');
        case L'r':
                (*buf)++;
                return ('\r');
        case L't':
                (*buf)++;
                return ('\t');
        case L'v':
                (*buf)++;
                return ('\v');
        case L'a':
                (*buf)++;
                return ('\a');
        case L'\'':
                (*buf)++;
                return ('\'');
        case L'?':
                (*buf)++;
                return ('\?');
        case L'0':
        case L'1':
        case L'2':
        case L'3':
        case L'4':
        case L'5':
        case L'6':
        case L'7':
                /*
                 * This case handles \ddd where ddd is octal number.
                 * There could be one, two, or three octal numbers.
                 */
                (*buf)++;
                n = (char)(wc - L'0');
                wc = **buf;
                if (wc >= L'0' && wc <= L'7') {
                        (*buf)++;
                        n = 8*n + (char)(wc - L'0');
                        wc = **buf;
                        if (wc >= L'0' && wc <= L'7') {
                                (*buf)++;
                                n = 8*n + (char)(wc - L'0');
                        }
                }
                return (n);
        default:
                return ('\0');
        }
} /* expand_meta */

/*
 * Finds the head of the current domain linked list and
 * call insert_message() to insert msgid and msgstr pair
 * to the linked list.
 */
static void
sortit(char *msgid, char *msgstr)
{
        struct domain_struct    *dom;

#ifdef DEBUG
        (void) fprintf(stderr,
            "==> sortit(), domain=<%s> msgid=<%s> msgstr=<%s>\n",
            gcurrent_domain, msgid, msgstr);
#endif

        /*
         * If "-o filename" is specified, then all "domain" directive
         * are ignored and, all messages will be stored in domain
         * whose name is filename.
         */
        if (oflag) {
                dom = find_domain_node(outfile);
        } else {
                dom = find_domain_node(gcurrent_domain);
        }

        insert_message(dom, msgid, msgstr);
}

/*
 * This routine inserts message in the current domain message list.
 * It is inserted in ascending order.
 */
static void
insert_message(struct domain_struct *dom,
    char *msgid, char *msgstr)
{
        struct msg_chain        *p1;
        struct msg_chain        *node, *prev_node;
        int                     b;

        /*
         * Find the optimal starting search position.
         * The starting search position is either the first node
         * or the current_elem of domain.
         * The current_elem is the pointer to the node which
         * is most recently accessed in domain.
         */
        if (dom->current_elem != NULL) {
                b = strcmp(msgid, dom->current_elem->msgid);
                if (b == 0) {
                        if (verbose)
                                warning(gettext(WARN_DUP_MSG),
                                    msgid, msgid_linenum);
                        return;
                } else if (b > 0) { /* to implement descending order */
                        p1 = dom->first_elem;
                } else {
                        p1 = dom->current_elem;
                }
        } else {
                p1 = dom->first_elem;
        }

        /*
         * search msgid insert position in the list
         * Search starts from the node pointed by p1.
         */
        prev_node = NULL;
        while (p1) {
                b = strcmp(msgid, p1->msgid);
                if (b == 0) {
                        if (verbose)
                                warning(gettext(WARN_DUP_MSG),
                                    msgid, msgid_linenum);
                        return;
                } else if (b < 0) {  /* to implement descending order */
                        /* move to the next node */
                        prev_node = p1;
                        p1 = p1->next;
                } else {
                        /* insert a new msg node */
                        node = (struct msg_chain *)
                            Xmalloc(sizeof (struct msg_chain));
                        node->next = p1;
                        node->msgid  = Xstrdup(msgid);
                        node->msgstr = Xstrdup(msgstr);

                        if (prev_node) {
                                prev_node->next = node;
                        } else {
                                dom->first_elem = node;
                        }
                        dom->current_elem = node;
                        return;
                }
        } /* while */

        /*
         * msgid is smaller than any of msgid in the list or
         * list is empty.
         * Therefore, append it.
         */
        node = (struct msg_chain *)
            Xmalloc(sizeof (struct msg_chain));
        node->next = NULL;
        node->msgid  = Xstrdup(msgid);
        node->msgstr = Xstrdup(msgstr);

        if (prev_node) {
                prev_node->next = node;
        } else {
                dom->first_elem = node;
        }
        dom->current_elem = node;

        return;

} /* insert_message */


/*
 * This routine will find head of the linked list for the given
 * domain_name. This looks up cache entry first and if cache misses,
 * scans the list.
 * If not found, then create a new node.
 */
static struct domain_struct *
find_domain_node(char *domain_name)
{
        struct domain_struct    *p1;
        struct domain_struct    *node;
        struct domain_struct    *prev_node;
        int                     b;


        /* for perfomance, check cache 'last_used_domain' */
        if (last_used_domain) {
                b = strcmp(domain_name, last_used_domain->domain);
                if (b == 0) {
                        return (last_used_domain);
                } else if (b < 0) {
                        p1 = first_domain;
                } else {
                        p1 = last_used_domain;
                }
        } else {
                p1 = first_domain;
        }

        prev_node = NULL;
        while (p1) {
                b = strcmp(domain_name, p1->domain);
                if (b == 0) {
                        /* node found */
                        last_used_domain = p1;
                        return (p1);
                } else if (b > 0) {
                        /* move to the next node */
                        prev_node = p1;
                        p1 = p1->next;
                } else {
                        /* insert a new domain node */
                        node = (struct domain_struct *)
                            Xmalloc(sizeof (struct domain_struct));
                        node->next = p1;
                        node->domain = Xstrdup(domain_name);
                        node->first_elem = NULL;
                        node->current_elem = NULL;
                        if (prev_node) {
                                /* insert the node in the middle */
                                prev_node->next = node;
                        } else {
                                /* node inserted is the smallest */
                                first_domain = node;
                        }
                        last_used_domain = node;
                        return (node);
                }
        } /* while */

        /*
         * domain_name is larger than any of domain name in the list or
         * list is empty.
         */
        node = (struct domain_struct *)
            Xmalloc(sizeof (struct domain_struct));
        node->next = NULL;
        node->domain = Xstrdup(domain_name);
        node->first_elem = NULL;
        node->current_elem = NULL;
        if (prev_node) {
                /* domain list is not empty */
                prev_node->next = node;
        } else {
                /* domain list is empty */
                first_domain = node;
        }
        last_used_domain = node;

        return (node);

} /* find_domain_node */


/*
 * binary_compute() is used for pre-computing a binary search.
 */
static int
binary_compute(int i, int j, int *more, int *less)
{
        int     k;

        if (i > j) {
                return (LEAFINDICATOR);
        }
        k = (i + j) / 2;

        less[k] = binary_compute(i, k - 1, more, less);
        more[k] = binary_compute(k + 1, j, more, less);

        return (k);

} /* binary_compute */


/*
 * Write all domain data to file.
 * Each domain will create one file.
 */
static void
output_all_mo_files(void)
{
        struct domain_struct    *p;

        p = first_domain;
        while (p) {
                /*
                 * generate message object file only if there is
                 * at least one element.
                 */
                if (p->first_elem) {
                        output_one_mo_file(p);
                }
                p = p->next;
        }
        return;

} /* output_all_mo_files */


/*
 * Write one domain data list to file.
 */
static void
output_one_mo_file(struct domain_struct *dom)
{
        FILE    *fp;
        struct msg_chain        *p;
        int     message_count;
        int     string_count_msgid;
        int     string_count_msg;
        int     msgid_index = 0;
        int     msgstr_index = 0;
        int     *less, *more;
        int     i;
        char    fname [TEXTDOMAINMAX+1];

        if (!dom || !dom->first_elem)
                return;

        /*
         * If -o flag is specified, then file name is used as domain name.
         * If not, ".mo" is appended to the domain name.
         */
        (void) strcpy(fname, dom->domain);
        if (!oflag) {
                (void) strcat(fname, ".mo");
        }
        fp = fopen(fname, "w");
        if (fp == NULL) {
                error(gettext(ERR_OPEN_FAILED), fname);
                /* NOTREACHED */
        }

        /* compute offsets and counts */
        message_count = 0;
        p = dom->first_elem;
        while (p) {
                p->msgid_offset = msgid_index;
                p->msgstr_offset = msgstr_index;
                msgid_index += strlen(p->msgid) + 1;
                msgstr_index += strlen(p->msgstr) + 1;
                message_count++;
                p = p->next;
        }

        /*
         * Fill up less and more entries to be used for binary search.
         */
        string_count_msgid = msgid_index;
        string_count_msg = msgstr_index;
        less = (int *)Xcalloc(message_count, sizeof (int));
        more = (int *)Xcalloc(message_count, sizeof (int));

        (void) binary_compute(0, message_count - 1, more, less);

#ifdef DEBUG
        {
                int i;
                for (i = 0; i < message_count; i++) {
                        (void) fprintf(stderr,
                            "  less[%2d]=%2d, more[%2d]=%2d\n",
                            i, less[i], i, more[i]);
                }
        }
#endif

        /*
         * write out the message object file.
         * The middle one is the first message to check by gettext().
         */
        i = (message_count - 1) / 2;
        (void) fwrite(&i, sizeof (int), 1, fp);
        (void) fwrite(&message_count, sizeof (int), 1, fp);
        (void) fwrite(&string_count_msgid, sizeof (int), 1, fp);
        (void) fwrite(&string_count_msg, sizeof (int), 1, fp);
        i = MSG_STRUCT_SIZE * message_count;
        (void) fwrite(&i, sizeof (int), 1, fp);

        /* march through linked list and write out all nodes. */
        i = 0;
        p = dom->first_elem;
        while (p) {     /* put out message struct */
                (void) fwrite(&less[i], sizeof (int), 1, fp);
                (void) fwrite(&more[i], sizeof (int), 1, fp);
                (void) fwrite(&p->msgid_offset, sizeof (int), 1, fp);
                (void) fwrite(&p->msgstr_offset, sizeof (int), 1, fp);
                i++;
                p = p->next;
        }

        /* put out message id strings */
        p = dom->first_elem;
        while (p) {
                (void) fwrite(p->msgid, strlen(p->msgid)+1, 1, fp);
                p = p->next;
        }

        /* put out message strings */
        p = dom->first_elem;
        while (p) {
                (void) fwrite(p->msgstr, strlen(p->msgstr)+1, 1, fp);
                p = p->next;
        }

        (void) fclose(fp);
        free(less);
        free(more);

        return;

} /* output_one_mo_file */


/*
 * read one line from *mbuf,
 * skip preceding whitespaces,
 * convert the line to wide characters,
 * place the wide characters into *bufhead, and
 * return the number of wide characters placed.
 *
 * INPUT:
 *              **bufhead - address of a variable that is the pointer
 *                      to wchar_t.
 *                      The variable should been initialized to NULL.
 *              **mbuf - address of a variable that is the pointer
 *                      to char.
 *                      The pointer should point to the memory mmapped to
 *                      the file to input.
 *              **fsize - address of a size_t variable that contains
 *                      the size of unread bytes in the file to input.
 * OUTPUT:
 *              return - the number of wide characters placed.
 *              **bufhead - _mbsntowcs allocates the buffer to store
 *                      one line in wchar_t from *mbuf and sets the address
 *                      to *bufhead.
 *              **mbuf - _mbsntowcs reads one line from *mbuf and sets *mbuf
 *                      to the beginning of the next line.
 *              **fsize - *fsize will be set to the size of the unread
 *                      bytes in the file.
 */
static size_t
_mbsntowcs(wchar_t **bufhead, char **mbuf, size_t *fsize)
{
        wchar_t *tp, *th;
        wchar_t wc;
        size_t  tbufsize = LINE_SIZE;
        size_t  ttbufsize, nc;
        char    *pc = *mbuf;
        int     nb;

        if (*fsize == 0) {
                /* eof */
                return (0);
        }

        th = (wchar_t *)Xmalloc(sizeof (wchar_t) * tbufsize);
        nc = tbufsize;

        /* skip preceding whitespaces */
        while ((*pc != '\0')) {
                if ((*pc == ' ') || (*pc == '\t')) {
                        pc++;
                        (*fsize)--;
                } else {
                        break;
                }
        }

        tp = th;
        while (*fsize > 0) {
                nb = mbtowc(&wc, pc, mbcurmax);
                if (nb == -1) {
                        return ((size_t)-1);
                }

                if (*pc == '\n') {
                        /* found eol */
                        if (nc <= 1) {
                                /*
                                 * not enough buffer
                                 * at least 2 more bytes are required for
                                 * L'\n' and L'\0'
                                 */
                                ttbufsize = tbufsize + 2;
                                th = (wchar_t *)Xrealloc(th,
                                    sizeof (wchar_t) * ttbufsize);
                                tp = th + tbufsize - nc;
                                tbufsize = ttbufsize;
                        }
                        *tp++ = L'\n';
                        *tp++ = L'\0';
                        pc += nb;
                        *fsize -= nb;
                        *mbuf = pc;
                        *bufhead = th;
                        return ((size_t)(tp - th));
                }
                if (nc == 0) {
                        ttbufsize = tbufsize + LINE_SIZE;
                        th = (wchar_t *)Xrealloc(th,
                            sizeof (wchar_t) * ttbufsize);
                        tp = th + tbufsize;
                        nc = LINE_SIZE;
                        tbufsize = ttbufsize;
                }
                *tp++ = wc;
                nc--;
                pc += nb;
                *fsize -= nb;
        }       /* while */

        /*
         * At this point, the input file has been consumed,
         * but there is no ending '\n'; we add it to
         * the output file.
         */
        if (nc <= 1) {
                /*
                 * not enough buffer
                 * at least 2 more bytes are required for
                 * L'\n' and L'\0'
                 */
                ttbufsize = tbufsize + 2;
                th = (wchar_t *)Xrealloc(th,
                    sizeof (wchar_t) * ttbufsize);
                tp = th + tbufsize - nc;
                tbufsize = ttbufsize;
        }
        *tp++ = L'\n';
        *tp++ = L'\0';
        *mbuf = pc;
        *bufhead = th;
        return ((size_t)(tp - th));
}


/*
 * This is debug function. Not compiled in the final executable.
 */
#ifdef DEBUG
static void
printlist(void)
{
        struct domain_struct    *p;
        struct msg_chain        *m;

        (void) fprintf(stderr, "\n=== Printing contents of all domains ===\n");
        p = first_domain;
        while (p) {
                (void) fprintf(stderr, "domain name = <%s>\n", p->domain);
                m = p->first_elem;
                while (m) {
                        (void) fprintf(stderr, "   msgid=<%s>, msgstr=<%s>\n",
                            m->msgid, m->msgstr);
                        m = m->next;
                }
                p = p->next;
        }
} /* printlist */
#endif