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/*	Copyright (c) 1988 AT&T	*/
28/*	  All Rights Reserved  	*/
29
30/* __gtxt(): Common part to gettxt() and pfmt()	*/
31
32#pragma	weak _setcat = setcat
33
34#include "lint.h"
35#include "libc.h"
36#include <mtlib.h>
37#include <sys/types.h>
38#include <string.h>
39#include <locale.h>
40#include <fcntl.h>
41#include <sys/types.h>
42#include <sys/stat.h>
43#include <sys/mman.h>
44#include <stdlib.h>
45#include <synch.h>
46#include <pfmt.h>
47#include <thread.h>
48#include <unistd.h>
49#include <errno.h>
50#include <limits.h>
51#include "../i18n/_locale.h"
52#include "../i18n/_loc_path.h"
53
54#define	MESSAGES "/LC_MESSAGES/"
55static const char *def_locale = "C";
56static const char *not_found = "Message not found!!\n";
57static struct db_info *db_info;
58static int db_count, maxdb;
59
60struct db_info {
61	char	db_name[DB_NAME_LEN];	/* Name of the message file */
62	uintptr_t	addr;		/* Virtual memory address   */
63	size_t	length;
64	char	*saved_locale;
65	char	flag;
66};
67
68#define	DB_EXIST	1		/* The catalogue exists	   */
69#define	DB_OPEN		2		/* Already tried to open   */
70
71/* Minimum number of open catalogues */
72#define	MINDB		3
73
74char cur_cat[DB_NAME_LEN];
75rwlock_t _rw_cur_cat = DEFAULTRWLOCK;
76
77
78/*
79 * setcat(cat): Specify the default catalogue.
80 * Return a pointer to the local copy of the default catalogue
81 */
82const char *
83setcat(const char *cat)
84{
85	lrw_wrlock(&_rw_cur_cat);
86	if (cat) {
87		if (((strchr(cat, '/') != NULL)) ||
88		    ((strchr(cat, ':') != NULL))) {
89			cur_cat[0] = '\0';
90			goto out;
91		}
92		(void) strncpy(cur_cat, cat, sizeof (cur_cat) - 1);
93		cur_cat[sizeof (cur_cat) - 1] = '\0';
94	}
95out:
96	lrw_unlock(&_rw_cur_cat);
97	return (cur_cat[0] ? cur_cat : NULL);
98}
99
100/*
101 * load a message catalog which specified with current locale,
102 * and catalog name.
103 */
104static struct db_info *
105load_db(const char *curloc, const char *catname, int *err)
106{
107	char pathname[PATH_MAX];
108	struct	stat64 sb;
109	caddr_t	addr;
110	struct db_info *db;
111	int fd;
112	int i;
113
114	*err = 0;
115
116	/* First time called, allocate space */
117	if (!db_info) {
118		if ((db_info =
119		    libc_malloc(MINDB * sizeof (struct db_info))) == NULL) {
120			*err = 1;
121			return (NULL);
122		}
123		maxdb = MINDB;
124	}
125
126	for (i = 0; i < db_count; i++) {
127		if (db_info[i].flag == 0)
128			break;
129	}
130	/* New catalogue */
131	if (i == db_count) {
132		if (db_count == maxdb) {
133			if ((db = libc_realloc(db_info,
134			    ++maxdb * sizeof (struct db_info))) == NULL) {
135				*err = 1;
136				return (NULL);
137			}
138			db_info = db;
139		}
140		db_count++;
141	}
142	db = &db_info[i];
143	db->flag = 0;
144	(void) strcpy(db->db_name, catname);
145	db->saved_locale = libc_strdup(curloc);
146	if (db->saved_locale == NULL) {
147		*err = 1;
148		return (NULL);
149	}
150	db->flag = DB_OPEN;
151	if (snprintf(pathname, sizeof (pathname),
152	    _DFLT_LOC_PATH "%s" MESSAGES "%s",
153	    db->saved_locale, db->db_name) >= sizeof (pathname)) {
154		/*
155		 * We won't set err here, because an invalid locale is not
156		 * the fatal condition, but we can fall back to "C"
157		 * locale.
158		 */
159		return (NULL);
160	}
161	if ((fd = open(pathname, O_RDONLY)) != -1 &&
162	    fstat64(fd, &sb) != -1 &&
163	    (addr = mmap(0, (size_t)sb.st_size, PROT_READ, MAP_SHARED,
164	    fd, 0)) != MAP_FAILED) {
165		db->flag |= DB_EXIST;
166		db->addr = (uintptr_t)addr;
167		db->length = (size_t)sb.st_size;
168	}
169	if (fd != -1)
170		(void) close(fd);
171	return (db);
172}
173
174/*
175 * unmap the message catalog, and release the db_info slot.
176 */
177static void
178unload_db(struct db_info *db)
179{
180	if ((db->flag & (DB_OPEN|DB_EXIST)) ==
181	    (DB_OPEN|DB_EXIST)) {
182		(void) munmap((caddr_t)db->addr, db->length);
183	}
184	db->flag = 0;
185	if (db->saved_locale)
186		libc_free(db->saved_locale);
187	db->saved_locale = NULL;
188}
189
190/*
191 * go through the db_info, and find out a db_info slot regarding
192 * the given current locale and catalog name.
193 * If db is not NULL, then search will start from top of the array,
194 * otherwise it will start from the next of given db.
195 * If curloc is set to NULL, then return a cache without regards of
196 * locale.
197 */
198static struct db_info *
199lookup_cache(struct db_info *db, const char *curloc, const char *catname)
200{
201	if (db_info == NULL)
202		return (NULL);
203
204	if (db == NULL)
205		db = db_info;
206	else
207		db++;
208
209	for (; db < &db_info[db_count]; db++) {
210		if (db->flag == 0)
211			continue;
212		if (strcmp(db->db_name, catname) == 0) {
213			if (curloc == NULL ||
214			    (db->saved_locale != NULL &&
215			    strcmp(db->saved_locale, curloc) == 0)) {
216				return (db);
217			}
218		}
219	}
220	return (NULL);
221}
222
223static int
224valid_msg(struct db_info *db, int id)
225{
226	if (db == NULL || (db->flag & DB_EXIST) == 0)
227		return (0);
228
229	/* catalog has been loaded */
230	if (id != 0 && id <= *(int *)(db->addr))
231		return (1);
232
233	/* not a valid id */
234	return (0);
235}
236
237static char *
238msg(struct db_info *db, int id)
239{
240	return ((char *)(db->addr + *(int *)(db->addr +
241	    id * sizeof (int))));
242}
243
244/*
245 * __gtxt(catname, id, dflt): Return a pointer to a message.
246 *	catname is the name of the catalog. If null, the default catalog is
247 *		used.
248 *	id is the numeric id of the message in the catalogue
249 *	dflt is the default message.
250 *
251 *	Information about non-existent catalogues is kept in db_info, in
252 *	such a way that subsequent calls with the same catalogue do not
253 *	try to open the catalogue again.
254 */
255const char *
256__gtxt(const char *catname, int id, const char *dflt)
257{
258	char	*curloc;
259	struct db_info *db;
260	int	err;
261	locale_t loc;
262
263	/* Check for invalid message id */
264	if (id < 0)
265		return (not_found);
266	if (id == 0)
267		return ((dflt && *dflt) ? dflt : not_found);
268
269	/*
270	 * If catalogue is unspecified, use default catalogue.
271	 * No catalogue at all is an error
272	 */
273	if (!catname || !*catname) {
274		lrw_rdlock(&_rw_cur_cat);
275		if (cur_cat == NULL || !*cur_cat) {
276			lrw_unlock(&_rw_cur_cat);
277			return (not_found);
278		}
279		catname = cur_cat;
280		lrw_unlock(&_rw_cur_cat);
281	}
282
283	loc = uselocale(NULL);
284	curloc = current_locale(loc, LC_MESSAGES);
285
286	/* First look up the cache */
287	db = lookup_cache(NULL, curloc, catname);
288	if (db != NULL) {
289		/*
290		 * The catalog has been loaded, and if id seems valid,
291		 * then just return.
292		 */
293		if (valid_msg(db, id))
294			return (msg(db, id));
295
296		/*
297		 * seems given id is out of bound or does not exist. In this
298		 * case, we need to look up a message for the "C" locale as
299		 * documented in the man page.
300		 */
301		db = lookup_cache(NULL, def_locale, catname);
302		if (db == NULL) {
303			/*
304			 * Even the message catalog for the "C" has not been
305			 * loaded.
306			 */
307			db = load_db(def_locale, catname, &err);
308			if (err)
309				return (not_found);
310		}
311		if (valid_msg(db, id))
312			return (msg(db, id));
313		/* no message found */
314		return ((dflt && *dflt) ? dflt : not_found);
315	}
316
317	/*
318	 * The catalog has not been loaded or even has not
319	 * attempted to be loaded, invalidate all caches related to
320	 * the catname for possibly different locale.
321	 */
322	db = NULL;
323	while ((db = lookup_cache(db, NULL, catname)) != NULL)
324		unload_db(db);
325
326	/*
327	 * load a message catalog for the requested locale.
328	 */
329	db = load_db(curloc, catname, &err);
330	if (err)
331		return (not_found);
332	if (valid_msg(db, id))
333		return (msg(db, id));
334
335	/*
336	 * If the requested catalog is either not exist or message
337	 * id is invalid, then try to load from "C" locale.
338	 */
339	db = load_db(def_locale, catname, &err);
340	if (err)
341		return (not_found);
342
343	if (valid_msg(db, id))
344		return (msg(db, id));
345
346	/* no message found */
347	return ((dflt && *dflt) ? dflt : not_found);
348}
349