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