root/usr/src/lib/libc/port/i18n/gettext_real.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright 2015 Joyent, Inc.
 */

#include "lint.h"
#include "mtlib.h"
#include <ctype.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <libintl.h>
#include <thread.h>
#include <synch.h>
#include <limits.h>
#include <unistd.h>
#include "libc.h"
#include "_loc_path.h"
#include "msgfmt.h"
#include "gettext.h"
#include "nlspath_checks.h"

static int      process_nlspath(const char *, const char *,
    const char *, char **);
static char     *replace_nls_option(char *, const char *, char *,
    char *, char *, char *, char *);

char *
_real_gettext_u(const char *domain, const char *msgid1, const char *msgid2,
    unsigned long int ln, int category, int plural, locale_t loc)
{
        char    msgfile[MAXPATHLEN]; /* 1024 */
        char    mydomain[TEXTDOMAINMAX + 1]; /* 256 + 1 */
        char    *cur_binding;   /* points to current binding in list */
        const char *cur_locale;
        char    *cur_domain, *result, *nlspath;
        char    *msgloc, *cb, *cur_domain_binding;
        char    *language;
        unsigned int    n = (unsigned int)ln;   /* we don't need long for n */
        uint32_t        cur_domain_len, cblen;
        uint32_t        hash_domain;
        struct msg_pack *mp, omp;

#ifdef GETTEXT_DEBUG
        gprintf(0, "*************** _real_gettext_u(\"%s\", \"%s\", "
            "\"%s\", %d, %d, %d)\n",
            domain ? domain : "NULL", msgid1 ? msgid1 : "NULL",
            msgid2 ? msgid2 : "NULL", n, category, plural);
        gprintf(0, "***************** global_gt: 0x%p\n", global_gt);
        printgt(global_gt, 1);
#endif

        if (msgid1 == NULL)
                return (NULL);

        mp = memset(&omp, 0, sizeof (omp));     /* msg pack */

        /*
         * category may be LC_MESSAGES or LC_TIME
         * locale contains the value of 'category'
         */
        if (loc == NULL)
                loc = uselocale(NULL);
        cur_locale = current_locale(loc, category);

        language = getenv("LANGUAGE"); /* for GNU */
        if (language) {
                if (!*language || strchr(language, '/') != NULL) {
                        /*
                         * LANGUAGE is an empty string or
                         * LANGUAGE contains '/'.
                         * Ignore it.
                         */
                        language = NULL;
                }
        }

        /*
         * Query the current domain if domain argument is NULL pointer
         */
        mydomain[0] = '\0';
        if (domain == NULL) {
                /*
                 * if NULL is specified for domainname,
                 * use the currently bound domain.
                 */
                cur_domain = _textdomain_u(NULL, mydomain);
        } else if (!*domain) {
                /*
                 * if an empty string is specified
                 */
                cur_domain = DEFAULT_DOMAIN;
        } else {
                cur_domain = (char *)domain;
        }

        hash_domain = get_hashid(cur_domain, &cur_domain_len);
        if (cur_domain_len > TEXTDOMAINMAX) {
                /* domain is invalid, return msg_id */
                DFLTMSG(result, msgid1, msgid2, n, plural);
                return (result);
        }

        nlspath = getenv("NLSPATH"); /* get the content of NLSPATH */
        if (nlspath == NULL || !*nlspath) {
                /* no NLSPATH is defined in the environ */
                if ((*cur_locale == 'C') && (*(cur_locale + 1) == '\0')) {
                        /*
                         * If C locale,
                         * return the original msgid immediately.
                         */
                        DFLTMSG(result, msgid1, msgid2, n, plural);
                        return (result);
                }
                nlspath = NULL;
        } else {
                /* NLSPATH is set */
                int     ret;

                msgloc = current_locale(loc, LC_MESSAGES);

                ret = process_nlspath(cur_domain, msgloc,
                    (const char *)nlspath, &cur_binding);
                if (ret == -1) {
                        /* error occurred */
                        DFLTMSG(result, msgid1, msgid2, n, plural);
                        return (result);
                } else if (ret == 0) {
                        nlspath = NULL;
                }
        }

        cur_domain_binding = _real_bindtextdomain_u(cur_domain,
            NULL, TP_BINDING);
        if (cur_domain_binding == NULL) {
                DFLTMSG(result, msgid1, msgid2, n, plural);
                return (result);
        }

        mp->msgid1 = msgid1;
        mp->msgid2 = msgid2;
        mp->msgfile = msgfile;
        mp->domain = cur_domain;
        mp->binding = cur_domain_binding;
        mp->locale = cur_locale;
        mp->language = language;
        mp->domain_len = cur_domain_len;
        mp->n = n;
        mp->category = category;
        mp->plural = plural;
        mp->hash_domain = hash_domain;

        /*
         * Spec1170 requires that we use NLSPATH if it's defined, to
         * override any system default variables.  If NLSPATH is not
         * defined or if a message catalog is not found in any of the
         * components (bindings) specified by NLSPATH, dcgettext_u() will
         * search for the message catalog in either a) the binding path set
         * by any previous application calls to bindtextdomain() or
         * b) the default binding path (/usr/lib/locale).  Save the original
         * binding path so that we can search it if the message catalog
         * is not found via NLSPATH.  The original binding is restored before
         * returning from this routine because the gettext routines should
         * not change the binding set by the application.  This allows
         * bindtextdomain() to be called once for all gettext() calls in the
         * application.
         */

        /*
         * First, examine NLSPATH
         */
        if (nlspath) {
                /*
                 * NLSPATH binding has been successfully built
                 */
#ifdef GETTEXT_DEBUG
                gprintf(0, "************************** examining NLSPATH\n");
                gprintf(0, "       cur_binding: \"%s\"\n",
                    cur_binding ? cur_binding : "(null)");
#endif

                mp->nlsp = 1;
                /*
                 * cur_binding always ends with ':' before a null
                 * termination.
                 */
                while (*cur_binding) {
                        cb = cur_binding;
                        while (*cur_binding != ':')
                                cur_binding++;
                        cblen = cur_binding - cb;
                        cur_binding++;
                        if (cblen >= MAXPATHLEN) {
                                /* cur_binding too long */
                                DFLTMSG(result, msgid1, msgid2, n, plural);
                                return (result);
                        }

                        (void) memcpy(mp->msgfile, cb, cblen);
                        *(mp->msgfile + cblen) = '\0';

#ifdef GETTEXT_DEBUG
                        gprintf(0, "*******************"
                            "********************* \n");
                        gprintf(0, "       msgfile: \"%s\"\n",
                            msgfile ? msgfile : "(null)");
                        gprintf(0, "*******************"
                            "********************* \n");
#endif
                        result = handle_mo(mp);
                        if (result) {
                                return (result);
                        }
                }
        }

        mp->nlsp = 0;
        mp->binding = cur_domain_binding;
        /*
         * Next, examine LANGUAGE
         */
        if (language) {
                char    *ret_msg;
                ret_msg = handle_lang(mp);
                if (ret_msg != NULL) {
                        /* valid msg found in GNU MO */
                        return (ret_msg);
                }
                /*
                 * handle_lang() may have overridden locale
                 */
                mp->locale = cur_locale;
                mp->status = 0;
        }

        /*
         * Finally, handle a single binding
         */
#ifdef GETTEXT_DEBUG
        *mp->msgfile = '\0';
#endif
        if (mk_msgfile(mp) == NULL) {
                DFLTMSG(result, msgid1, msgid2, n, plural);
                return (result);
        }

        result = handle_mo(mp);
        if (result) {
                return (result);
        }
        DFLTMSG(result, msgid1, msgid2, n, plural);
        return (result);
} /* _real_gettext_u */

#define ALLFREE \
        free_all(nlstmp, nnp, pathname, ppaths, lang)

static void
free_all(Nlstmp *nlstmp, Nls_node *nnp, char *pathname,
    char *ppaths, char *lang)
{
        Nlstmp  *tp, *tq;

        tp = nlstmp;
        while (tp) {
                tq = tp->next;
                free(tp);
                tp = tq;
        }
        if (nnp->locale)
                free(nnp->locale);
        if (nnp->domain)
                free(nnp->domain);
        if (pathname)
                free(pathname);
        if (ppaths)
                free(ppaths);
        if (lang)
                free(lang);
        free(nnp);
}

/*
 * process_nlspath(): process the NLSPATH environment variable.
 *
 *              this routine looks at NLSPATH in the environment,
 *              and will try to build up the binding list based
 *              on the settings of NLSPATH.
 *
 * RETURN:
 * -1:  Error occurred
 *  0:  No error, but no binding list has been built
 *  1:  No error, and a binding list has been built
 *
 */
static int
process_nlspath(const char *cur_domain, const char *cur_msgloc,
    const char *nlspath, char **binding)
{
        char    *s;                     /* generic string ptr */
        char    *territory;             /* our current territory element */
        char    *codeset;               /* our current codeset element */
        char    *s1;                    /* for handling territory */
        char    *s2;                    /* for handling codeset */
        char    *lang = NULL;   /* our current language element */
        char    *ppaths = NULL; /* ptr to all of the templates */
        char    *pathname = NULL;       /* the full pathname to the file */
        size_t  nlspath_len, domain_len, locale_len, path_len;
        size_t  ppaths_len = 0;
        Nlstmp  *nlstmp = NULL;
        Nlstmp  *pnlstmp, *qnlstmp;
        Nls_node        *cur_nls, *nnp;
        Gettext_t       *gt = global_gt;

#ifdef GETTEXT_DEBUG
        gprintf(0, "*************** process_nlspath(%s, %s, "
            "%s, 0x%p)\n", cur_domain,
            cur_msgloc, nlspath, (void *)binding);
#endif

        cur_nls = gt->c_n_node;
        if (cur_nls &&
            (strcmp(cur_nls->domain, cur_domain) == 0 &&
            strcmp(cur_nls->locale, cur_msgloc) == 0 &&
            strcmp(cur_nls->nlspath, nlspath) == 0)) {
                *binding = cur_nls->ppaths;
                return (1);
        }

        nnp = gt->n_node;
        while (nnp) {
                if (strcmp(nnp->domain, cur_domain) == 0 &&
                    strcmp(nnp->locale, cur_msgloc) == 0 &&
                    strcmp(nnp->nlspath, nlspath) == 0) {
                        /* found */
                        gt->c_n_node = nnp;
                        *binding = nnp->ppaths;
                        return (1);
                }
                nnp = nnp->next;
        }
        /* not found */

        nnp = calloc(1, sizeof (Nls_node));
        if (nnp == NULL) {
                ALLFREE;
                return (-1);
        }

        nlspath_len = strlen(nlspath);
        locale_len = strlen(cur_msgloc);
        domain_len = strlen(cur_domain);

        lang = s = strdup(cur_msgloc);
        if (lang == NULL) {
                ALLFREE;
                return (-1);
        }
        s1 = s2 = NULL;
        while (*s) {
                if (*s == '_') {
                        s1 = s;
                        *s1++ = '\0';
                } else if (*s == '.') {
                        s2 = s;
                        *s2++ = '\0';
                }
                s++;
        }
        territory = s1;
        codeset = s2;

        /*
         * now that we have the name (domain), we first look through NLSPATH,
         * in an attempt to get the locale. A locale may be completely
         * specified as "language_territory.codeset". NLSPATH consists
         * of templates separated by ":" characters. The following are
         * the substitution values within NLSPATH:
         *      %N = DEFAULT_DOMAIN
         *      %L = The value of the LC_MESSAGES category.
         *      %I = The language element from the LC_MESSAGES category.
         *      %t = The territory element from the LC_MESSAGES category.
         *      %c = The codeset element from the LC_MESSAGES category.
         *      %% = A single character.
         * if we find one of these characters, we will carry out the
         * appropriate substitution.
         */
        pathname = malloc(MAXPATHLEN);
        if (pathname == NULL) {
                ALLFREE;
                return (-1);
        }
        s = (char *)nlspath;            /* s has a content of NLSPATH */
        while (*s) {                            /* march through NLSPATH */
                (void) memset(pathname, 0, MAXPATHLEN);
                if (*s == ':') {
                        /*
                         * this loop only occurs if we have to replace
                         * ":" by "name". replace_nls_option() below
                         * will handle the subsequent ":"'s.
                         */
                        pnlstmp = malloc(sizeof (Nlstmp));
                        if (pnlstmp == NULL) {
                                ALLFREE;
                                return (-1);
                        }

                        (void) memcpy(pnlstmp->pathname, cur_domain,
                            domain_len + 1);
                        pnlstmp->len = domain_len;
                        ppaths_len += domain_len + 1; /* 1 for ':' */


                        pnlstmp->next = NULL;

                        if (nlstmp == NULL) {
                                nlstmp = pnlstmp;
                                qnlstmp = pnlstmp;
                        } else {
                                qnlstmp->next = pnlstmp;
                                qnlstmp = pnlstmp;
                        }

                        ++s;
                        continue;
                }
                /* replace Substitution field */
                s = replace_nls_option(s, cur_domain, pathname,
                    (char *)cur_msgloc, lang, territory, codeset);

                if (s == NULL) {
                        ALLFREE;
                        return (-1);
                }

                /* if we've found a valid file: */
                if (*pathname) {
                        /* add template to end of chain of pathnames: */
                        pnlstmp = malloc(sizeof (Nlstmp));
                        if (pnlstmp == NULL) {
                                ALLFREE;
                                return (-1);
                        }

                        path_len = strlen(pathname);
                        (void) memcpy(pnlstmp->pathname, pathname,
                            path_len + 1);
                        pnlstmp->len = path_len;
                        ppaths_len += path_len + 1; /* 1 for ':' */

                        pnlstmp->next = NULL;

                        if (nlstmp == NULL) {
                                nlstmp = pnlstmp;
                                qnlstmp = pnlstmp;
                        } else {
                                qnlstmp->next = pnlstmp;
                                qnlstmp = pnlstmp;
                        }
                }
                if (*s) {
                        ++s;
                }
        }
        /*
         * now that we've handled the pathname templates, concatenate them
         * all into the form "template1:template2:..." for _bindtextdomain_u()
         */

        if (ppaths_len != 0) {
                ppaths = malloc(ppaths_len + 1);
                if (ppaths == NULL) {
                        ALLFREE;
                        return (-1);
                }
                *ppaths = '\0';
        } else {
                ALLFREE;
                return (0);
        }

        /*
         * extract the path templates (fifo), and concatenate them
         * all into a ":" separated string for _bindtextdomain_u()
         */
        pnlstmp = nlstmp;
        s = ppaths;
        while (pnlstmp) {
                (void) memcpy(s, pnlstmp->pathname, pnlstmp->len);
                s += pnlstmp->len;
                *s++ = ':';
                qnlstmp = pnlstmp->next;
                free(pnlstmp);
                pnlstmp = qnlstmp;
        }
        *s = '\0';
        nlstmp = NULL;

        nnp->domain = malloc(domain_len + 1);
        if (nnp->domain == NULL) {
                ALLFREE;
                return (-1);
        } else {
                (void) memcpy(nnp->domain, cur_domain, domain_len + 1);
        }
        nnp->locale = malloc(locale_len + 1);
        if (nnp->locale == NULL) {
                ALLFREE;
                return (-1);
        } else {
                (void) memcpy(nnp->locale, cur_msgloc, locale_len + 1);
        }
        nnp->nlspath = malloc(nlspath_len + 1);
        if (nnp->nlspath == NULL) {
                ALLFREE;
                return (-1);
        } else {
                (void) memcpy(nnp->nlspath, nlspath, nlspath_len + 1);
        }
        nnp->ppaths = ppaths;

        nnp->next = gt->n_node;
        gt->n_node = nnp;
        gt->c_n_node = nnp;

        free(pathname);
        free(lang);
#ifdef GETTEXT_DEBUG
        gprintf(0, "*************** existing process_nlspath with success\n");
        gprintf(0, "       binding: \"%s\"\n", ppaths);
#endif
        *binding = ppaths;
        return (1);
}


/*
 * This routine will replace substitution parameters in NLSPATH
 * with appropiate values.
 */
static char *
replace_nls_option(char *s, const char *name, char *pathname,
    char *locale, char *lang, char *territory, char *codeset)
{
        char    *t, *u;
        char    *limit;

        t = pathname;
        limit = pathname + MAXPATHLEN - 1;

        while (*s && *s != ':') {
                if (t < limit) {
                        /*
                         * %% is considered a single % character (XPG).
                         * %L : LC_MESSAGES (XPG4) LANG(XPG3)
                         * %l : The language element from the current locale.
                         *      (XPG3, XPG4)
                         */
                        if (*s != '%')
                                *t++ = *s;
                        else if (*++s == 'N') {
                                if (name) {
                                        u = (char *)name;
                                        while (*u && (t < limit))
                                                *t++ = *u++;
                                }
                        } else if (*s == 'L') {
                                if (locale) {
                                        u = locale;
                                        while (*u && (t < limit))
                                                *t++ = *u++;
                                }
                        } else if (*s == 'l') {
                                if (lang) {
                                        u = lang;
                                        while (*u && (*u != '_') &&
                                            (t < limit))
                                                *t++ = *u++;
                                }
                        } else if (*s == 't') {
                                if (territory) {
                                        u = territory;
                                        while (*u && (*u != '.') &&
                                            (t < limit))
                                                *t++ = *u++;
                                }
                        } else if (*s == 'c') {
                                if (codeset) {
                                        u = codeset;
                                        while (*u && (t < limit))
                                                *t++ = *u++;
                                }
                        } else {
                                if (t < limit)
                                        *t++ = *s;
                        }
                } else {
                        /* too long pathname */
                        return (NULL);
                }
                ++s;
        }
        *t = '\0';
        return (s);
}


char *
_real_bindtextdomain_u(const char *domain, const char *binding,
    int type)
{
        struct domain_binding   *bind, *prev;
        Gettext_t       *gt = global_gt;
        char    **binding_addr;

#ifdef GETTEXT_DEBUG
        gprintf(0, "*************** _real_bindtextdomain_u(\"%s\", "
            "\"%s\", \"%s\")\n",
            (domain ? domain : ""),
            (binding ? binding : ""),
            (type == TP_BINDING) ? "TP_BINDING" : "TP_CODESET");
#endif

        /*
         * If domain is a NULL pointer, no change will occur regardless
         * of binding value. Just return NULL.
         */
        if (domain == NULL) {
                return (NULL);
        }

        /*
         * Global Binding is not supported any more.
         * Just return NULL if domain is NULL string.
         */
        if (*domain == '\0') {
                return (NULL);
        }

        /* linear search for binding, rebind if found, add if not */
        bind = FIRSTBIND(gt);
        prev = NULL;    /* Two pointers needed for pointer operations */

        while (bind) {
                if (strcmp(domain, bind->domain) == 0) {
                        /*
                         * Domain found.
                         */
                        binding_addr = (type == TP_BINDING) ? &(bind->binding) :
                            &(bind->codeset);
                        if (binding == NULL) {
                                /*
                                 * if binding is null, then query
                                 */
                                return (*binding_addr);
                        }
                        /* replace existing binding with new binding */
                        char *new_binding = strdup(binding);
                        if (new_binding == NULL) {
                                return (NULL);
                        }
                        free(*binding_addr);
                        *binding_addr = new_binding;
#ifdef GETTEXT_DEBUG
                        printlist();
#endif
                        return (*binding_addr);
                }
                prev = bind;
                bind = bind->next;
        } /* while (bind) */

        /* domain has not been found in the list at this point */
        if (binding) {
                /*
                 * domain is not found, but binding is not NULL.
                 * Then add a new node to the end of linked list.
                 */

                if ((bind = malloc(sizeof (Dbinding))) == NULL) {
                        return (NULL);
                }
                if ((bind->domain = strdup(domain)) == NULL) {
                        free(bind);
                        return (NULL);
                }
                bind->binding = NULL;
                bind->codeset = NULL;
                binding_addr = (type == TP_BINDING) ? &(bind->binding) :
                    &(bind->codeset);
                if ((*binding_addr = strdup(binding)) == NULL) {
                        free(bind->domain);
                        free(bind);
                        return (NULL);
                }
                bind->next = NULL;

                if (prev) {
                        /* reached the end of list */
                        prev->next = bind;
                } else {
                        /* list was empty */
                        FIRSTBIND(gt) = bind;
                }

#ifdef GETTEXT_DEBUG
                printlist();
#endif
                return (*binding_addr);
        } else {
                /*
                 * Query of domain which is not found in the list
                 * for bindtextdomain, returns defaultbind
                 * for bind_textdomain_codeset, returns NULL
                 */
                if (type == TP_BINDING) {
                        return ((char *)defaultbind);
                } else {
                        return (NULL);
                }
        } /* if (binding) */

        /* Must not reach here */

} /* _real_bindtextdomain_u */


char *
_textdomain_u(const char *domain, char *result)
{
        char    *p;
        size_t  domain_len;
        Gettext_t       *gt = global_gt;

#ifdef GETTEXT_DEBUG
        gprintf(0, "*************** _textdomain_u(\"%s\", 0x%p)\n",
            (domain ? domain : ""), (void *)result);
#endif

        /* Query is performed for NULL domain pointer */
        if (domain == NULL) {
                (void) strcpy(result, CURRENT_DOMAIN(gt));
                return (result);
        }

        /* check for error. */
        /*
         * domain is limited to TEXTDOMAINMAX bytes
         * excluding a null termination.
         */
        domain_len = strlen(domain);
        if (domain_len > TEXTDOMAINMAX) {
                /* too long */
                return (NULL);
        }

        /*
         * Calling textdomain() with a null domain string sets
         * the domain to the default domain.
         * If non-null string is passwd, current domain is changed
         * to the new domain.
         */

        /* actually this if clause should be protected from signals */
        if (*domain == '\0') {
                if (CURRENT_DOMAIN(gt) != default_domain) {
                        free(CURRENT_DOMAIN(gt));
                        CURRENT_DOMAIN(gt) = (char *)default_domain;
                }
        } else {
                p = malloc(domain_len + 1);
                if (p == NULL)
                        return (NULL);
                (void) strcpy(p, domain);
                if (CURRENT_DOMAIN(gt) != default_domain)
                        free(CURRENT_DOMAIN(gt));
                CURRENT_DOMAIN(gt) = p;
        }

        (void) strcpy(result, CURRENT_DOMAIN(gt));
        return (result);
} /* _textdomain_u */

/*
 * key_2_text() translates msd_id into target string.
 */
static char *
key_2_text(Msg_s_node *messages, const char *key_string)
{
        int     val;
        char    *msg_id_str;
        unsigned char   kc = *(unsigned char *)key_string;
        struct msg_struct       *check_msg_list;

#ifdef GETTEXT_DEBUG
        gprintf(0, "*************** key_2_text(0x%p, \"%s\")\n",
            (void *)messages, key_string ? key_string : "(null)");
        printsunmsg(messages, 1);
#endif

        check_msg_list = messages->msg_list +
            messages->msg_file_info->msg_mid;
        for (;;) {
                msg_id_str = messages->msg_ids +
                    check_msg_list->msgid_offset;
                /*
                 * To maintain the compatibility with Zeus mo file,
                 * msg_id's are stored in descending order.
                 * If the ascending order is desired, change "msgfmt.c"
                 * and switch msg_id_str and key_string in the following
                 * strcmp() statement.
                 */
                val = *(unsigned char *)msg_id_str - kc;
                if ((val == 0) &&
                    (val = strcmp(msg_id_str, key_string)) == 0) {
                        return (messages->msg_strs
                            + check_msg_list->msgstr_offset);
                } else if (val < 0) {
                        if (check_msg_list->less != LEAFINDICATOR) {
                                check_msg_list = messages->msg_list +
                                    check_msg_list->less;
                                continue;
                        }
                        return ((char *)key_string);
                } else {
                        /* val > 0 */
                        if (check_msg_list->more != LEAFINDICATOR) {
                                check_msg_list = messages->msg_list +
                                    check_msg_list->more;
                                continue;
                        }
                        return ((char *)key_string);
                }
        }
}

/*
 * sun_setmsg
 *
 * INPUT
 *   mnp  - message node
 *   addr - address to the mmapped file
 *   size - size of the file
 *
 * RETURN
 *   0   - either T_SUN_MO or T_ILL_MO has been set
 *   1   - not a valid sun mo file
 *  -1   - failed
 */
static int
sun_setmsg(Msg_node *mnp, char *addr, size_t size)
{
        struct msg_info *sun_header;
        Msg_s_node      *p;
        uint32_t        first_4bytes;
        int     mid, count;
        int     struct_size, struct_size_old;
        int     msg_struct_size;

        if (size < sizeof (struct msg_info)) {
                /* invalid mo file */
                mnp->type = T_ILL_MO;
#ifdef GETTEXT_DEBUG
                gprintf(0, "********* exiting sun_setmsg\n");
                printmnp(mnp, 1);
#endif
                return (0);
        }

        first_4bytes = *((uint32_t *)(uintptr_t)addr);
        if (first_4bytes > INT_MAX) {
                /*
                 * Not a valid sun mo file
                 */
                return (1);
        }

        /* candidate for sun mo */

        sun_header = (struct msg_info *)(uintptr_t)addr;
        mid = sun_header->msg_mid;
        count = sun_header->msg_count;
        msg_struct_size = sun_header->msg_struct_size;
        struct_size_old = (int)(OLD_MSG_STRUCT_SIZE * count);
        struct_size = (int)(MSG_STRUCT_SIZE * count);

        if ((((count - 1) / 2) != mid) ||
            ((msg_struct_size != struct_size_old) &&
            (msg_struct_size != struct_size))) {
                /* invalid mo file */
                mnp->type = T_ILL_MO;
#ifdef GETTEXT_DEBUG
                gprintf(0, "********* exiting sun_setmsg\n");
                printmnp(mnp, 1);
#endif
                return (0);
        }
        /* valid sun mo file */

        p = malloc(sizeof (Msg_s_node));
        if (p == NULL) {
                return (-1);
        }

        p->msg_file_info = sun_header;
        p->msg_list = (struct msg_struct *)(uintptr_t)
            (addr + sizeof (struct msg_info));
        p->msg_ids = (char *)(addr + sizeof (struct msg_info) +
            struct_size);
        p->msg_strs = (char *)(addr + sizeof (struct msg_info) +
            struct_size + sun_header->str_count_msgid);

        mnp->msg.sunmsg = p;
        mnp->type = T_SUN_MO;
#ifdef GETTEXT_DEBUG
        gprintf(0, "******** exiting sun_setmsg\n");
        printmnp(mnp, 1);
#endif
        return (0);
}

/*
 * setmsg
 *
 * INPUT
 *   mnp  - message node
 *   addr - address to the mmapped file
 *   size - size of the file
 *
 * RETURN
 *   0   - succeeded
 *  -1   - failed
 */
static int
setmsg(Msg_node *mnp, char *addr, size_t size)
{
        int     ret;
        if ((ret = sun_setmsg(mnp, addr, size)) <= 0)
                return (ret);

        return (gnu_setmsg(mnp, addr, size));
}

static char *
handle_type_mo(Msg_node *mnp, struct msg_pack *mp)
{
        char    *result;

        switch (mnp->type) {
        case T_ILL_MO:
                /* invalid MO */
                return (NULL);
        case T_SUN_MO:
                /* Sun MO found */
                mp->status |= ST_SUN_MO_FOUND;

                if (mp->plural) {
                        /*
                         * *ngettext is called against
                         * Sun MO file
                         */
                        int     exp = (mp->n == 1);
                        result = (char *)mp->msgid1;
                        if (!exp)
                                result = (char *)mp->msgid2;
                        return (result);
                }
                result = key_2_text(mnp->msg.sunmsg, mp->msgid1);
                if (!mnp->trusted) {
                        result = check_format(mp->msgid1, result, 0);
                }
                return (result);
        case T_GNU_MO:
                /* GNU MO found */
                mp->status |= ST_GNU_MO_FOUND;

                result = gnu_key_2_text(mnp->msg.gnumsg,
                    get_codeset(mp->domain), mp);

                if (result == mp->msgid1 || result == mp->msgid2) {
                        /* no valid msg found */
                        return (result);
                }

                /* valid msg found */
                mp->status |= ST_GNU_MSG_FOUND;

                if (!mnp->trusted) {
                        result = check_format(mp->msgid1, result, 0);
                        if (result == mp->msgid1) {
                                DFLTMSG(result, mp->msgid1, mp->msgid2,
                                    mp->n, mp->plural);
                        }
                }
                return (result);
        default:
                /* this should never happen */
                DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
                return (result);
        }
        /* NOTREACHED */
}

/*
 * handle_mo() returns NULL if invalid MO found.
 */
char *
handle_mo(struct msg_pack *mp)
{
        int     fd;
        char    *result;
        struct stat64   statbuf;
        Msg_node        *mnp;
        Gettext_t       *gt = global_gt;

#define CONNECT_ENTRY   \
        mnp->next = gt->m_node; \
        gt->m_node = mnp; \
        gt->c_m_node = mnp

#ifdef GETTEXT_DEBUG
        gprintf(0, "*************** handle_mo(0x%p)\n", (void *)mp);
        printmp(mp, 1);
#endif

        mnp = check_cache(mp);

        if (mnp != NULL) {
                /* cache found */
                return (handle_type_mo(mnp, mp));
        }

        /*
         * Valid entry not found in the cache
         */
        mnp = calloc(1, sizeof (Msg_node));
        if (mnp == NULL) {
                DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
                return (result);
        }
        mnp->hashid = mp->hash_domain;
        mnp->path = strdup(mp->msgfile);
        if (mnp->path == NULL) {
                free(mnp);
                DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
                return (result);
        }

        fd = nls_safe_open(mp->msgfile, &statbuf, &mp->trusted, !mp->nlsp);
        if ((fd == -1) || (statbuf.st_size > LONG_MAX)) {
                if (fd != -1)
                        (void) close(fd);
                mnp->type = T_ILL_MO;
                CONNECT_ENTRY;
                return (NULL);
        }
        mp->fsz = (size_t)statbuf.st_size;
        mp->addr = mmap(NULL, mp->fsz, PROT_READ, MAP_SHARED, fd, 0);
        (void) close(fd);

        if (mp->addr == MAP_FAILED) {
                free(mnp->path);
                free(mnp);
                DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
                return (result);
        }

        if (setmsg(mnp, (char *)mp->addr, mp->fsz) == -1) {
                free(mnp->path);
                free(mnp);
                (void) munmap(mp->addr, mp->fsz);
                DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural);
                return (result);
        }
        mnp->trusted = mp->trusted;
        CONNECT_ENTRY;

        return (handle_type_mo(mnp, mp));
}