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 */
59typedef 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
67typedef 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 */
81typedef 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 */
93static const char *
94msgid_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 */
141static void
142open_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 */
216char *
217dgettext(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 */
285char *
286gettext(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 */
294const char *
295_rtld_msg(Msg mid)
296{
297	return ((char *)dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), MSG_ORIG(mid)));
298}
299