1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 /*
  28  * Messaging support.  To minimize ld.so.1's overhead, messaging support isn't
  29  * enabled until we need to contruct a message - Note that we don't rely on the
  30  * application to signify whether messaging is applicable, as many message
  31  * conditions (such as relocations) are generated before the application gains
  32  * control.
  33  *
  34  * This code implements a very trimmed down version of the capabilities found
  35  * via setlocale(3c), textdomain(3i) and gettext(3i).  Dragging in the original
  36  * routines from libc/libintl isn't possible as they cause all i18n support to
  37  * be included which is far too expensive for ld.so.1.
  38  */
  39 
  40 #include <sys/types.h>
  41 #include <sys/mman.h>
  42 #include <sys/stat.h>
  43 #include <string.h>
  44 #include <stdio.h>
  45 #include <stdlib.h>
  46 #include <unistd.h>
  47 #include <fcntl.h>
  48 #include <limits.h>
  49 #include <libintl.h>
  50 #include "_rtld.h"
  51 #include "msg.h"
  52 
  53 /*
  54  * A message object file (as generated by msgfmt(1)) consists of a message
  55  * header, followed by a message list, followed by the msgid strings and then
  56  * the msgstr strings.  None of this is defined in any OSNET available headers
  57  * so we have our own local definitions :-(
  58  */
  59 typedef struct {
  60         int     hdr_midlst;             /* middle message no. */
  61         int     hdr_lstcnt;             /* total no. of message in the file */
  62         int     hdr_msgidsz;            /* size of msgids (in bytes) */
  63         int     hdr_msgstrsz;           /* size of msgstrs (in bytes) */
  64         int     hdr_lstsz;              /* size of message list (in bytes) */
  65 } Msghdr;
  66 
  67 typedef struct {
  68         int     lst_less;
  69         int     lst_more;
  70         int     lst_idoff;
  71         int     lst_stroff;
  72 } Msglst;
  73 
  74 #define LEAFINDICATOR           -99
  75 #define OLD_MSG_STRUCT_SIZE     20
  76 #define NEW_MSG_STRUCT_SIZE     (sizeof (Msglst))
  77 
  78 /*
  79  * Define a local structure for maintaining the domains we care about.
  80  */
  81 typedef struct {
  82         const char      *dom_name;
  83         const Msghdr    *dom_msghdr;
  84         size_t          dom_msgsz;
  85 } Domain;
  86 
  87 
  88 /*
  89  * Perform a binary search of a message file (described by the Msghdr) for a
  90  * msgid (string).  Given a match return the associated msgstr, otherwise
  91  * return the original msgid.
  92  */
  93 static const char *
  94 msgid_to_msgstr(const Msghdr *msghdr, const char *msgid)
  95 {
  96         const Msglst    *list, *_list;
  97         const char      *ids, *strs, *_msgid;
  98         int             off, var;
  99 
 100         /*
 101          * Establish pointers to the message list (we actually start the search
 102          * in the middle of this list (hdr->midlst), the msgid strings (ids)
 103          * and the msgstr strings (strs).
 104          */
 105         list = (const Msglst *)&msghdr[1];
 106         ids = (const char *)&list[msghdr->hdr_lstcnt];
 107         strs = (const char *)&ids[msghdr->hdr_msgidsz];
 108 
 109         off = msghdr->hdr_midlst;
 110 
 111         for (;;) {
 112                 _list = list + off;
 113                 _msgid = ids + _list->lst_idoff;
 114 
 115                 if ((var = strcmp(_msgid, msgid)) == 0)
 116                         return (strs + _list->lst_stroff);
 117 
 118                 if (var < 0) {
 119                         if ((off = _list->lst_less) == LEAFINDICATOR)
 120                                 return (msgid);
 121                 } else {
 122                         if ((off = _list->lst_more) == LEAFINDICATOR)
 123                                 return (msgid);
 124                 }
 125         }
 126         /* NOTREACHED */
 127         return (NULL);  /* keep gcc happy */
 128 }
 129 
 130 /*
 131  * Open a message file. Following the model of setlocale(3c) we obtain the
 132  * message file for the specified locale.  Normally this is:
 133  *
 134  *      /usr/lib/locale/`locale'/LC_MESSAGES/`domain'.mo
 135  *
 136  * The locale was determined during initial environment processing (see
 137  * readenv()), which was determined from an LC_ALL, LC_MESSAGES or LANG
 138  * setting.  If no locale has been specified, or any file processing errors
 139  * occur, internationalization is basically disabled.
 140  */
 141 static void
 142 open_mofile(Domain * dom)
 143 {
 144         const char      *domain = dom->dom_name;
 145         char            path[PATH_MAX];
 146         int             fd;
 147         rtld_stat_t     status;
 148         const Msghdr    *msghdr;
 149         int             count;
 150         size_t          size_tot, size_old, size_new;
 151 
 152         dom->dom_msghdr = (Msghdr *)-1;
 153 
 154         (void) snprintf(path, PATH_MAX, MSG_ORIG(MSG_FMT_MSGFILE),
 155             glcs[CI_LCMESSAGES].lc_un.lc_ptr, domain);
 156 
 157         if ((fd = open(path, O_RDONLY, 0)) == -1)
 158                 return;
 159 
 160         if ((rtld_fstat(fd, &status) == -1) ||
 161             (status.st_size < sizeof (Msghdr))) {
 162                 (void) close(fd);
 163                 return;
 164         }
 165 
 166         /* LINTED */
 167         if ((msghdr = (Msghdr *)mmap(0, status.st_size, PROT_READ, MAP_SHARED,
 168             fd, 0)) == (Msghdr *)-1) {
 169                 (void) close(fd);
 170                 return;
 171         }
 172         (void) close(fd);
 173 
 174         /* checks if opened file is msg file */
 175 
 176         count = msghdr->hdr_lstcnt;
 177         if (((count - 1) / 2) != msghdr->hdr_midlst) {
 178                 (void) munmap((caddr_t)msghdr, status.st_size);
 179                 return;
 180         }
 181 
 182         size_tot = msghdr->hdr_lstsz;
 183         size_old = OLD_MSG_STRUCT_SIZE * count;
 184         size_new = (int)NEW_MSG_STRUCT_SIZE * count;
 185         if ((size_tot != size_old) && (size_tot != size_new)) {
 186                 (void) munmap((caddr_t)msghdr, status.st_size);
 187                 return;
 188         }
 189 
 190         size_tot = msghdr->hdr_msgidsz + msghdr->hdr_msgstrsz +
 191             (int)sizeof (Msghdr);
 192         if ((size_tot + size_old < status.st_size) &&
 193             (size_tot + size_new < status.st_size)) {
 194                 (void) munmap((caddr_t)msghdr, status.st_size);
 195                 return;
 196         }
 197 
 198         /*
 199          * We have a good message file, initialize the Domain information.
 200          */
 201         dom->dom_msghdr = msghdr;
 202         dom->dom_msgsz = status.st_size;
 203 }
 204 
 205 
 206 /*
 207  * Two interfaces are established to support our internationalization.
 208  * gettext(3i) calls originate from all link-editor libraries, and thus the
 209  * SUNW_OST_SGS domain is assumed.  dgettext() calls originate from
 210  * dependencies such as libelf and libc.
 211  *
 212  * Presently we support two domains (libc's strerror() uses SUNW_OST_OSLIB).
 213  * If ld.so.1's dependencies evolve to require more then the `domain' array
 214  * maintained below can be enlarged or made more dynamic in nature.
 215  */
 216 char *
 217 dgettext(const char *domain, const char *msgid)
 218 {
 219         static int      domaincnt = 0;
 220         static Domain   *domains;
 221         Domain          *_domain;
 222         int             cnt;
 223 
 224         if (glcs[CI_LCMESSAGES].lc_un.lc_val == 0)
 225                 return ((char *)msgid);
 226 
 227         /*
 228          * Determine if we've initialized any domains yet.
 229          */
 230         if (domaincnt == 0) {
 231                 if ((domains = calloc(2, sizeof (Domain))) == NULL)
 232                         return ((char *)msgid);
 233                 domains[0].dom_name = MSG_ORIG(MSG_SUNW_OST_SGS);
 234                 domains[1].dom_name = MSG_ORIG(MSG_SUNW_OST_OSLIB);
 235                 domaincnt = 2;
 236         }
 237 
 238         /*
 239          * If this is a new locale make sure we clean up any old ones.
 240          */
 241         if (rtld_flags & RT_FL_NEWLOCALE) {
 242                 cnt = 0;
 243 
 244                 for (_domain = domains; cnt < domaincnt; _domain++, cnt++) {
 245                         if (_domain->dom_msghdr == 0)
 246                                 continue;
 247 
 248                         if (_domain->dom_msghdr != (Msghdr *)-1)
 249                                 (void) munmap((caddr_t)_domain->dom_msghdr,
 250                                     _domain->dom_msgsz);
 251 
 252                         _domain->dom_msghdr = 0;
 253                 }
 254                 rtld_flags &= ~RT_FL_NEWLOCALE;
 255         }
 256 
 257         /*
 258          * Determine which domain we need.
 259          */
 260         for (cnt = 0, _domain = domains; cnt < domaincnt; _domain++, cnt++) {
 261                 if (_domain->dom_name == domain)
 262                         break;
 263                 if (strcmp(_domain->dom_name, domain) == 0)
 264                         break;
 265         }
 266         if (cnt == domaincnt)
 267                 return ((char *)msgid);
 268 
 269         /*
 270          * Determine if the domain has been initialized yet.
 271          */
 272         if (_domain->dom_msghdr == 0)
 273                 open_mofile(_domain);
 274         if (_domain->dom_msghdr == (Msghdr *)-1)
 275                 return ((char *)msgid);
 276 
 277         return ((char *)msgid_to_msgstr(_domain->dom_msghdr, msgid));
 278 }
 279 
 280 /*
 281  * This satisfies any dependencies of code dragged in from libc, as we don't
 282  * want libc's gettext implementation in ld.so.1.  This routine may not be
 283  * referenced, in which case -zignore will discard it.
 284  */
 285 char *
 286 gettext(const char *msgid)
 287 {
 288         return ((char *)dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), msgid));
 289 }
 290 
 291 /*
 292  * The sgsmsg.1l use requires the following interface.
 293  */
 294 const char *
 295 _rtld_msg(Msg mid)
 296 {
 297         return ((char *)dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), MSG_ORIG(mid)));
 298 }