xref: /illumos-gate/usr/src/cmd/sgs/rtld/common/locale.c (revision 9ac6ca4d)
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 *
msgid_to_msgstr(const Msghdr * msghdr,const char * msgid)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
open_mofile(Domain * dom)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 *
dgettext(const char * domain,const char * msgid)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(sizeof (Domain), 2)) == 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 *
gettext(const char * msgid)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 *
_rtld_msg(Msg mid)295 _rtld_msg(Msg mid)
296 {
297 	return ((char *)dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), MSG_ORIG(mid)));
298 }
299