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

/*
 * Messaging support.  To minimize ld.so.1's overhead, messaging support isn't
 * enabled until we need to contruct a message - Note that we don't rely on the
 * application to signify whether messaging is applicable, as many message
 * conditions (such as relocations) are generated before the application gains
 * control.
 *
 * This code implements a very trimmed down version of the capabilities found
 * via setlocale(3C), textdomain(3C) and gettext(3C).  Dragging in the original
 * routines from libc/libintl isn't possible as they cause all i18n support to
 * be included which is far too expensive for ld.so.1.
 */

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <libintl.h>
#include "_rtld.h"
#include "msg.h"

/*
 * A message object file (as generated by msgfmt(1)) consists of a message
 * header, followed by a message list, followed by the msgid strings and then
 * the msgstr strings.  None of this is defined in any OSNET available headers
 * so we have our own local definitions :-(
 */
typedef struct {
        int     hdr_midlst;             /* middle message no. */
        int     hdr_lstcnt;             /* total no. of message in the file */
        int     hdr_msgidsz;            /* size of msgids (in bytes) */
        int     hdr_msgstrsz;           /* size of msgstrs (in bytes) */
        int     hdr_lstsz;              /* size of message list (in bytes) */
} Msghdr;

typedef struct {
        int     lst_less;
        int     lst_more;
        int     lst_idoff;
        int     lst_stroff;
} Msglst;

#define LEAFINDICATOR           -99
#define OLD_MSG_STRUCT_SIZE     20
#define NEW_MSG_STRUCT_SIZE     (sizeof (Msglst))

/*
 * Define a local structure for maintaining the domains we care about.
 */
typedef struct {
        const char      *dom_name;
        const Msghdr    *dom_msghdr;
        size_t          dom_msgsz;
} Domain;


/*
 * Perform a binary search of a message file (described by the Msghdr) for a
 * msgid (string).  Given a match return the associated msgstr, otherwise
 * return the original msgid.
 */
static const char *
msgid_to_msgstr(const Msghdr *msghdr, const char *msgid)
{
        const Msglst    *list, *_list;
        const char      *ids, *strs, *_msgid;
        int             off, var;

        /*
         * Establish pointers to the message list (we actually start the search
         * in the middle of this list (hdr->midlst), the msgid strings (ids)
         * and the msgstr strings (strs).
         */
        list = (const Msglst *)&msghdr[1];
        ids = (const char *)&list[msghdr->hdr_lstcnt];
        strs = (const char *)&ids[msghdr->hdr_msgidsz];

        off = msghdr->hdr_midlst;

        for (;;) {
                _list = list + off;
                _msgid = ids + _list->lst_idoff;

                if ((var = strcmp(_msgid, msgid)) == 0)
                        return (strs + _list->lst_stroff);

                if (var < 0) {
                        if ((off = _list->lst_less) == LEAFINDICATOR)
                                return (msgid);
                } else {
                        if ((off = _list->lst_more) == LEAFINDICATOR)
                                return (msgid);
                }
        }
        /* NOTREACHED */
        return (NULL);  /* keep gcc happy */
}

/*
 * Open a message file. Following the model of setlocale(3c) we obtain the
 * message file for the specified locale.  Normally this is:
 *
 *      /usr/lib/locale/`locale'/LC_MESSAGES/`domain'.mo
 *
 * The locale was determined during initial environment processing (see
 * readenv()), which was determined from an LC_ALL, LC_MESSAGES or LANG
 * setting.  If no locale has been specified, or any file processing errors
 * occur, internationalization is basically disabled.
 */
static void
open_mofile(Domain * dom)
{
        const char      *domain = dom->dom_name;
        char            path[PATH_MAX];
        int             fd;
        rtld_stat_t     status;
        const Msghdr    *msghdr;
        int             count;
        size_t          size_tot, size_old, size_new;

        dom->dom_msghdr = (Msghdr *)-1;

        (void) snprintf(path, PATH_MAX, MSG_ORIG(MSG_FMT_MSGFILE),
            glcs[CI_LCMESSAGES].lc_un.lc_ptr, domain);

        if ((fd = open(path, O_RDONLY, 0)) == -1)
                return;

        if ((rtld_fstat(fd, &status) == -1) ||
            (status.st_size < sizeof (Msghdr))) {
                (void) close(fd);
                return;
        }

        /* LINTED */
        if ((msghdr = (Msghdr *)mmap(0, status.st_size, PROT_READ, MAP_SHARED,
            fd, 0)) == (Msghdr *)-1) {
                (void) close(fd);
                return;
        }
        (void) close(fd);

        /* checks if opened file is msg file */

        count = msghdr->hdr_lstcnt;
        if (((count - 1) / 2) != msghdr->hdr_midlst) {
                (void) munmap((caddr_t)msghdr, status.st_size);
                return;
        }

        size_tot = msghdr->hdr_lstsz;
        size_old = OLD_MSG_STRUCT_SIZE * count;
        size_new = (int)NEW_MSG_STRUCT_SIZE * count;
        if ((size_tot != size_old) && (size_tot != size_new)) {
                (void) munmap((caddr_t)msghdr, status.st_size);
                return;
        }

        size_tot = msghdr->hdr_msgidsz + msghdr->hdr_msgstrsz +
            (int)sizeof (Msghdr);
        if ((size_tot + size_old < status.st_size) &&
            (size_tot + size_new < status.st_size)) {
                (void) munmap((caddr_t)msghdr, status.st_size);
                return;
        }

        /*
         * We have a good message file, initialize the Domain information.
         */
        dom->dom_msghdr = msghdr;
        dom->dom_msgsz = status.st_size;
}


/*
 * Two interfaces are established to support our internationalization.
 * gettext(3C) calls originate from all link-editor libraries, and thus the
 * SUNW_OST_SGS domain is assumed.  dgettext() calls originate from
 * dependencies such as libelf and libc.
 *
 * Presently we support two domains (libc's strerror() uses SUNW_OST_OSLIB).
 * If ld.so.1's dependencies evolve to require more then the `domain' array
 * maintained below can be enlarged or made more dynamic in nature.
 */
char *
dgettext(const char *domain, const char *msgid)
{
        static int      domaincnt = 0;
        static Domain   *domains;
        Domain          *_domain;
        int             cnt;

        if (glcs[CI_LCMESSAGES].lc_un.lc_val == 0)
                return ((char *)msgid);

        /*
         * Determine if we've initialized any domains yet.
         */
        if (domaincnt == 0) {
                if ((domains = calloc(2, sizeof (Domain))) == NULL)
                        return ((char *)msgid);
                domains[0].dom_name = MSG_ORIG(MSG_SUNW_OST_SGS);
                domains[1].dom_name = MSG_ORIG(MSG_SUNW_OST_OSLIB);
                domaincnt = 2;
        }

        /*
         * If this is a new locale make sure we clean up any old ones.
         */
        if (rtld_flags & RT_FL_NEWLOCALE) {
                cnt = 0;

                for (_domain = domains; cnt < domaincnt; _domain++, cnt++) {
                        if (_domain->dom_msghdr == 0)
                                continue;

                        if (_domain->dom_msghdr != (Msghdr *)-1)
                                (void) munmap((caddr_t)_domain->dom_msghdr,
                                    _domain->dom_msgsz);

                        _domain->dom_msghdr = 0;
                }
                rtld_flags &= ~RT_FL_NEWLOCALE;
        }

        /*
         * Determine which domain we need.
         */
        for (cnt = 0, _domain = domains; cnt < domaincnt; _domain++, cnt++) {
                if (_domain->dom_name == domain)
                        break;
                if (strcmp(_domain->dom_name, domain) == 0)
                        break;
        }
        if (cnt == domaincnt)
                return ((char *)msgid);

        /*
         * Determine if the domain has been initialized yet.
         */
        if (_domain->dom_msghdr == 0)
                open_mofile(_domain);
        if (_domain->dom_msghdr == (Msghdr *)-1)
                return ((char *)msgid);

        return ((char *)msgid_to_msgstr(_domain->dom_msghdr, msgid));
}

/*
 * This satisfies any dependencies of code dragged in from libc, as we don't
 * want libc's gettext implementation in ld.so.1.  This routine may not be
 * referenced, in which case -zignore will discard it.
 */
char *
gettext(const char *msgid)
{
        return ((char *)dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), msgid));
}

/*
 * The sgsmsg.1l use requires the following interface.
 */
const char *
_rtld_msg(Msg mid)
{
        return ((char *)dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), MSG_ORIG(mid)));
}