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