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