root/usr/src/cmd/msgfmt/gnu_handle.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "gnu_msgfmt.h"

static int      next_entry_is_fuzzy = 0;
static int      next_entry_is_c_format = 0;
static struct catalog   *cur_catalog = NULL;
static char     *cur_mo = NULL;

FILE    *fp;
iconv_t cd = (iconv_t)-1;
struct catalog  *catalog_head = NULL;
int     cur_po_index = 0;

static size_t
search_alias(char **paddr, size_t size, const char *variant)
{
        char    *addr = *paddr;
        char    *p, *sp, *q;
        size_t  var_len, can_len;

        var_len = strlen(variant);
        p = addr;
        q = addr + size;
        while (q > p) {
                if (*p == '#') {
                        /*
                         * Line beginning with '#' is a comment
                         */
                        p++;
                        while ((q > p) && (*p++ != '\n'))
                                ;
                        continue;
                }
                /* skip leading spaces */
                while ((q > p) &&
                    ((*p == ' ') || (*p == '\t')))
                        p++;
                if (q <= p)
                        break;
                sp = p;
                while ((q > p) && (*p != ' ') &&
                    (*p != '\t') && (*p != '\n'))
                        p++;
                if (q <= p) {
                        /* invalid entry */
                        break;
                }
                if (*p == '\n') {
                        /* invalid entry */
                        p++;
                        continue;
                }

                if (((p - sp) != var_len) ||
                    ((strncmp(sp, variant, var_len) != 0) &&
                    (strncasecmp(sp, variant, var_len) != 0))) {
                        /*
                         * didn't match
                         */

                        /* skip remaining chars in this line */
                        p++;
                        while ((q > p) && (*p++ != '\n'))
                                ;
                        continue;
                }

                /* matching entry found */

                /* skip spaces */
                while ((q > p) &&
                    ((*p == ' ') || (*p == '\t')))
                        p++;
                if (q <= p)
                        break;
                sp = p;
                while ((q > p) && (*p != ' ') &&
                    (*p != '\t') && (*p != '\n'))
                        p++;
                can_len = p - sp;
                if (can_len == 0) {
                        while ((q > p) && (*p++ != '\n'))
                                ;
                        continue;
                }
                *paddr = sp;
                return (can_len);
        }
        return (0);
}

/*
 * Checks if the specified charset is equivalent to UTF-8.
 * If it's equivalent to UTF-8, returns 1; Otherwise, returns 0.
 */
static int
check_utf8(const char *charset)
{
        int     fd;
        struct stat64   statbuf;
        caddr_t addr;
        size_t  buflen, charset_len, utf8_len;
        char    *c_charset, *c_utf8, *p;

        if (strcmp(charset, DEST_CHARSET) == 0)
                return (1);

        fd = open(_ENCODING_ALIAS_PATH, O_RDONLY);
        if (fd == -1) {
                /* no alias file found */
                return (0);
        }
        if (fstat64(fd, &statbuf) == -1) {
                (void) close(fd);
                return (0);
        }
        buflen = (size_t)statbuf.st_size;
        addr = mmap(NULL, buflen, PROT_READ, MAP_SHARED, fd, 0);
        (void) close(fd);
        if (addr == MAP_FAILED) {
                warning("mmap() for %s failed.", _ENCODING_ALIAS_PATH);
                return (0);
        }
        p = (char *)addr;
        charset_len = search_alias(&p, buflen, charset);
        if (charset_len) {
                c_charset = alloca(charset_len + 1);
                (void) memcpy(c_charset, p, charset_len);
                c_charset[charset_len] = '\0';
        } else {
                c_charset = (char *)charset;
        }
        p = (char *)addr;
        utf8_len = search_alias(&p, buflen, DEST_CHARSET);
        if (utf8_len) {
                c_utf8 = alloca(utf8_len + 1);
                (void) memcpy(c_utf8, p, utf8_len);
                c_utf8[utf8_len] = '\0';
        } else {
                c_utf8 = DEST_CHARSET;
        }
        (void) munmap(addr, buflen);
        if (charset_len == 0 && utf8_len == 0) {
                /*
                 * Entry for neither charset nor utf8 found
                 */
                return (0);
        }

        if (strcmp(c_charset, c_utf8) == 0)
                return (1);
        else
                return (0);
}

static void
conv_init(const char *charset)
{
        if (charset == NULL) {
                /*
                 * No conversion
                 */
                cd = (iconv_t)-1;
                return;
        }
        if (check_utf8(charset)) {
                /*
                 * Charset is UTF-8.
                 * No conversion is required.
                 */
                cd = (iconv_t)-1;
                return;
        }
        cd = iconv_open(DEST_CHARSET, charset);
        if (cd == (iconv_t)-1) {
                /*
                 * No such a conversion
                 */
                warning(gettext(WARN_NOCONV),
                        cur_line, cur_po, charset, DEST_CHARSET);
                return;
        }
}

void
clear_state(void)
{
        next_entry_is_fuzzy = 0;
        next_entry_is_c_format = 0;
}

void
handle_domain(char *domainname)
{
        if (outfile) {
                /*
                 * outfile has been specified by -o option
                 * ignore all domain directives
                 */
                if (verbose_flag) {
                        diag(gettext(DIAG_IGNORE_DOMAIN),
                                cur_line, cur_po, domainname);
                }
                free(domainname);
                return;
        }

        if (strict_flag) {
                /*
                 * add ".mo" to the domain
                 */
                char    *tmp;
                tmp = Xrealloc(domainname, strlen(domainname) + 3 + 1);
                (void) strcat(tmp, ".mo");
                domainname = tmp;
        }
        catalog_init(domainname);
        free(domainname);
}

void
catalog_init(const char *filename)
{
        struct catalog  *p;

        if (!catalog_head) {
                p = Xcalloc(1, sizeof (struct catalog));
                p->fname = Xstrdup(filename);
                p->msg_size = DEF_MSG_NUM;
                p->nmsg = 0;
                p->msg = Xcalloc(p->msg_size, sizeof (struct messages));
                p->thash_size = find_prime(DEF_MSG_NUM);
                p->thash = Xcalloc(p->thash_size, sizeof (unsigned int));
                catalog_head = p;
        } else {
                p = catalog_head;
                for (; ; ) {
                        struct catalog  *tmp;
                        if (strcmp(p->fname, filename) == 0) {
                                /* already registered */
                                break;
                        }
                        if (p->next) {
                                p = p->next;
                                continue;
                        }
                        /*
                         * this domain hasn't been registered
                         */
                        tmp = Xcalloc(1, sizeof (struct catalog));
                        tmp->fname = Xstrdup(filename);
                        tmp->msg_size = DEF_MSG_NUM;
                        tmp->nmsg = 0;
                        tmp->msg = Xcalloc(tmp->msg_size,
                            sizeof (struct messages));
                        tmp->thash_size = find_prime(DEF_MSG_NUM);
                        tmp->thash = Xcalloc(tmp->thash_size,
                            sizeof (unsigned int));
                        p->next = tmp;
                        p = tmp;
                        break;
                }
        }
        cur_catalog = p;
        cur_mo = p->fname;
}


void
handle_comment(char *comment)
{
        char    *p;

        p = comment;

        if (*p != ',') {
                /*
                 * This comment is just informative only.
                 */
                free(comment);
                return;
        }
        /*
         * Checks "fuzzy", "c-format", and "no-c-format"
         */
        p++;
        if (strstr(p, "fuzzy") != NULL) {
                next_entry_is_fuzzy = 1;
        }
        if (strstr(p, "no-c-format") != NULL) {
                next_entry_is_c_format = 0;
        } else if (strstr(p, "c-format") != NULL) {
                next_entry_is_c_format = 1;
        }

        free(comment);
}

void
handle_message(struct entry *id, struct entry *str)
{
        char    *charset, *nplurals, *tmp, *p;
        struct messages *msg, *dupmsg;
        size_t  len;
        unsigned int    hash_val;
        unsigned int    nmsg, n, thash_idx;

        if (cur_mo == NULL) {
                /*
                 * output file hasn't been specified, nor
                 * no domain directive found
                 */
                char    *default_domain;

                default_domain = strict_flag ? DEFAULT_DOMAIN_MO :
                    DEFAULT_DOMAIN;
                catalog_init(default_domain);
        }

        /*
         * cur_catalog should be valid, at this point
         */

        hash_val = hashpjw(id->str);
        dupmsg = search_msg(cur_catalog, id->str, hash_val);

        if (dupmsg) {
                if ((dupmsg->str_len == str->len) &&
                    (memcmp(dupmsg->str, str->str, str->len) == 0)) {
                        /* totally same entry */
                        if (verbose_flag) {
                                warning(gettext(WARN_DUP_ENTRIES),
                                    dupmsg->num, po_names[dupmsg->po],
                                    id->num, cur_po);
                        }
                        free(id->str);
                        if (id->pos)
                                free(id->pos);
                        free(str->str);
                        if (str->pos)
                                free(str->pos);
                        return;
                }
                /* duplicate msgid */
                if (verbose_flag) {
                        diag(gettext(ERR_DUP_ENTRIES),
                            dupmsg->num, po_names[dupmsg->po],
                            id->num, cur_po);
                        po_error++;
                }
                /* ignore this etnry */
                free(id->str);
                if (id->pos)
                        free(id->pos);
                free(str->str);
                if (str->pos)
                        free(str->pos);
                return;
        }

        if (next_entry_is_fuzzy) {
                /* fuzzy entry */
                cur_catalog->fnum++;
                if (!fuzzy_flag) {
                        /* ignore this entry */
                        free(id->str);
                        if (id->pos)
                                free(id->pos);
                        free(str->str);
                        if (str->pos)
                                free(str->pos);
                        return;
                }
        }

        if (str->len == str->no) {
                /* this entry is not translated */
                cur_catalog->unum++;
                free(id->str);
                if (id->pos)
                        free(id->pos);
                free(str->str);
                if (str->pos)
                        free(str->pos);
                return;
        }

        /* Checks if this is the header entry */
        if ((id->no == 1) && (id->len == 1)) {
                /*
                 * Header entry
                 */
                cur_catalog->header++;

                /*
                 * Need to extract the charset information
                 */
                charset = strstr(str->str, CHARSET_STR);
                if (charset == NULL) {
                        /* no charset information */
                        warning(gettext(WARN_NOCHARSET),
                            id->num, cur_po, str->num);
                        conv_init(NULL);
                } else {
                        charset += CHARSET_LEN;
                        p = strpbrk(charset, " \t\n");
                        if (p != NULL) {
                                /* p points to a space, tab or new line char */
                                len = p - charset;
                        } else {
                                /* not found */
                                len = strlen(charset);
                        }
                        tmp = Xmalloc(len + 1);
                        (void) memcpy(tmp, charset, len);
                        *(tmp + len) = '\0';
                        charset = tmp;
                        conv_init(charset);
                        free(charset);
                }
                nplurals = strstr(str->str, NPLURALS_STR);
                if (nplurals == NULL) {
                        cur_catalog->nplurals = 0;
                } else {
                        unsigned int    num;
                        nplurals += NPLURALS_LEN;
                        p = nplurals;
                        num = 0;
                        while (isdigit((unsigned char)*p)) {
                                num = num * 10 + *p++ - '0';
                        }
                        cur_catalog->nplurals = num;
                }
        }

        if (verbose_flag)
                check_format(id, str, next_entry_is_c_format);

        if (id->pos)
                free(id->pos);
        if (str->pos)
                free(str->pos);

        msg = cur_catalog->msg;
        nmsg = cur_catalog->nmsg;

        msg[nmsg].po = cur_po_index;
        msg[nmsg].num = id->num;
        msg[nmsg].id = id->str;
        msg[nmsg].id_len = id->len;
        msg[nmsg].str = str->str;
        msg[nmsg].str_len = str->len;
        msg[nmsg].hash = hash_val;

        thash_idx = get_hash_index(cur_catalog->thash,
            hash_val, cur_catalog->thash_size);
        cur_catalog->thash[thash_idx] = nmsg + 1;
        cur_catalog->nmsg++;

        if (cur_catalog->nmsg >= cur_catalog->msg_size) {
                /* no vacancy in message array */
                cur_catalog->msg_size += DEF_MSG_NUM;
                cur_catalog->msg = Xrealloc(cur_catalog->msg,
                    cur_catalog->msg_size * sizeof (struct messages));

                cur_catalog->thash_size =
                        find_prime(cur_catalog->msg_size);
                free(cur_catalog->thash);
                cur_catalog->thash = Xcalloc(cur_catalog->thash_size,
                    sizeof (unsigned int));

                for (n = 0; n < cur_catalog->nmsg; n++) {
                        thash_idx = get_hash_index(cur_catalog->thash,
                            cur_catalog->msg[n].hash,
                            cur_catalog->thash_size);
                        cur_catalog->thash[thash_idx] = n + 1;
                }
        }
}

void
po_init(const char *file)
{
        char    *filename;

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

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

        fp = fopen(filename, "r");
        if (fp == NULL) {
                error(gettext(ERR_OPEN_FAILED), filename);
                /* NOTREACHED */
        }

        po_names[cur_po_index] = filename;
        cur_line = 1;
        cd = (iconv_t)-1;
        if (!outfile)
                cur_mo = NULL;
}

void
po_fini(void)
{
        cur_po_index++;
        (void) fclose(fp);
        if (cd != (iconv_t)-1)
                (void) iconv_close(cd);
}