xref: /illumos-gate/usr/src/lib/libc/port/gen/gtxt.c (revision 3299f39fdcbdab4be7a9c70daa3873f2b78a398d)
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/"
55 static const char *def_locale = "C";
56 static const char *not_found = "Message not found!!\n";
57 static struct db_info *db_info;
58 static int db_count, maxdb;
59 
60 struct 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 
74 char cur_cat[DB_NAME_LEN];
75 rwlock_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  */
82 const char *
83 setcat(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 	}
95 out:
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  */
104 static struct db_info *
105 load_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  */
177 static void
178 unload_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  */
198 static struct db_info *
199 lookup_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 
223 static int
224 valid_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 
237 static char *
238 msg(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  */
255 const 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