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