xref: /freebsd/lib/libc/nls/msgcat.c (revision 2a58b312)
1 /***********************************************************
2 Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
3 Copyright 2010, Gabor Kovesdan <gabor@FreeBSD.org>
4 
5                         All Rights Reserved
6 
7 Permission to use, copy, modify, and distribute this software and its
8 documentation for any purpose and without fee is hereby granted,
9 provided that the above copyright notice appear in all copies and that
10 both that copyright notice and this permission notice appear in
11 supporting documentation, and that Alfalfa's name not be used in
12 advertising or publicity pertaining to distribution of the software
13 without specific, written prior permission.
14 
15 ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
16 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
17 ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
18 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
19 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
20 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
21 SOFTWARE.
22 
23 If you make any modifications, bugfixes or other changes to this software
24 we'd appreciate it if you could send a copy to us so we can keep things
25 up-to-date.  Many thanks.
26 				Kee Hinckley
27 				Alfalfa Software, Inc.
28 				267 Allston St., #3
29 				Cambridge, MA 02139  USA
30 				nazgul@alfalfa.com
31 
32 ******************************************************************/
33 
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
36 
37 #define _NLS_PRIVATE
38 
39 #include "namespace.h"
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <sys/mman.h>
43 #include <sys/queue.h>
44 
45 #include <arpa/inet.h>		/* for ntohl() */
46 #include <machine/atomic.h>
47 
48 #include <errno.h>
49 #include <fcntl.h>
50 #include <limits.h>
51 #include <nl_types.h>
52 #include <paths.h>
53 #include <pthread.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <unistd.h>
58 #include "un-namespace.h"
59 
60 #include "../locale/xlocale_private.h"
61 #include "libc_private.h"
62 
63 #define _DEFAULT_NLS_PATH "/usr/share/nls/%L/%N.cat:/usr/share/nls/%N/%L:"	\
64 				_PATH_LOCALBASE "/share/nls/%L/%N.cat:"		\
65 				_PATH_LOCALBASE "/share/nls/%N/%L"
66 
67 #define RLOCK(fail)	{ int ret;						\
68 			  if (__isthreaded &&					\
69 			      ((ret = _pthread_rwlock_rdlock(&rwlock)) != 0)) {	\
70 				  errno = ret;					\
71 				  return (fail);				\
72 			  }}
73 #define WLOCK(fail)	{ int ret;						\
74 			  if (__isthreaded &&					\
75 			      ((ret = _pthread_rwlock_wrlock(&rwlock)) != 0)) {	\
76 				  errno = ret;					\
77 				  return (fail);				\
78 			  }}
79 #define UNLOCK		{ if (__isthreaded)					\
80 			      _pthread_rwlock_unlock(&rwlock); }
81 
82 #define	NLERR		((nl_catd) -1)
83 #define NLRETERR(errc)  { errno = errc; return (NLERR); }
84 #define SAVEFAIL(n, l, e)	{ np = calloc(1, sizeof(struct catentry));	\
85 				  if (np != NULL) {				\
86 				  	np->name = strdup(n);			\
87 					np->catd = NLERR;			\
88 					np->lang = (l == NULL) ? NULL :		\
89 					    strdup(l);				\
90 					np->caterrno = e;			\
91 					if (np->name == NULL ||			\
92 					    (l != NULL && np->lang == NULL)) {	\
93 						free(np->name);			\
94 						free(np->lang);			\
95 						free(np);			\
96 					} else {				\
97 						WLOCK(NLERR);			\
98 						SLIST_INSERT_HEAD(&cache, np,	\
99 						    list);			\
100 						UNLOCK;				\
101 					}					\
102 				  }						\
103 				  errno = e;					\
104 				}
105 
106 static nl_catd load_msgcat(const char *, const char *, const char *);
107 
108 static pthread_rwlock_t		 rwlock = PTHREAD_RWLOCK_INITIALIZER;
109 
110 struct catentry {
111 	SLIST_ENTRY(catentry)	 list;
112 	char			*name;
113 	char			*path;
114 	int			 caterrno;
115 	nl_catd			 catd;
116 	char			*lang;
117 	int			 refcount;
118 };
119 
120 SLIST_HEAD(listhead, catentry) cache =
121     SLIST_HEAD_INITIALIZER(cache);
122 
123 nl_catd
124 catopen(const char *name, int type)
125 {
126 	return (__catopen_l(name, type, __get_locale()));
127 }
128 
129 nl_catd
130 __catopen_l(const char *name, int type, locale_t locale)
131 {
132 	struct stat sbuf;
133 	struct catentry *np;
134 	char *base, *cptr, *cptr1, *nlspath, *pathP, *pcode;
135 	char *plang, *pter;
136 	int saverr, spcleft;
137 	const char *lang, *tmpptr;
138 	char path[PATH_MAX];
139 
140 	/* sanity checking */
141 	if (name == NULL || *name == '\0')
142 		NLRETERR(EINVAL);
143 
144 	if (strchr(name, '/') != NULL)
145 		/* have a pathname */
146 		lang = NULL;
147 	else {
148 		if (type == NL_CAT_LOCALE)
149 			lang = querylocale(LC_MESSAGES_MASK, locale);
150 		else
151 			lang = getenv("LANG");
152 
153 		if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
154 		    (lang[0] == '.' &&
155 		    (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
156 		    strchr(lang, '/') != NULL)
157 			lang = "C";
158 	}
159 
160 	/* Try to get it from the cache first */
161 	RLOCK(NLERR);
162 	SLIST_FOREACH(np, &cache, list) {
163 		if ((strcmp(np->name, name) == 0) &&
164 		    ((lang != NULL && np->lang != NULL &&
165 		    strcmp(np->lang, lang) == 0) || (np->lang == lang))) {
166 			if (np->caterrno != 0) {
167 				/* Found cached failing entry */
168 				UNLOCK;
169 				NLRETERR(np->caterrno);
170 			} else {
171 				/* Found cached successful entry */
172 				atomic_add_int(&np->refcount, 1);
173 				UNLOCK;
174 				return (np->catd);
175 			}
176 		}
177 	}
178 	UNLOCK;
179 
180 	/* is it absolute path ? if yes, load immediately */
181 	if (strchr(name, '/') != NULL)
182 		return (load_msgcat(name, name, lang));
183 
184 	/* sanity checking */
185 	if ((plang = cptr1 = strdup(lang)) == NULL)
186 		return (NLERR);
187 	if ((cptr = strchr(cptr1, '@')) != NULL)
188 		*cptr = '\0';
189 	pter = pcode = "";
190 	if ((cptr = strchr(cptr1, '_')) != NULL) {
191 		*cptr++ = '\0';
192 		pter = cptr1 = cptr;
193 	}
194 	if ((cptr = strchr(cptr1, '.')) != NULL) {
195 		*cptr++ = '\0';
196 		pcode = cptr;
197 	}
198 
199 	if ((nlspath = secure_getenv("NLSPATH")) == NULL)
200 		nlspath = _DEFAULT_NLS_PATH;
201 
202 	if ((base = cptr = strdup(nlspath)) == NULL) {
203 		saverr = errno;
204 		free(plang);
205 		errno = saverr;
206 		return (NLERR);
207 	}
208 
209 	while ((nlspath = strsep(&cptr, ":")) != NULL) {
210 		pathP = path;
211 		if (*nlspath) {
212 			for (; *nlspath; ++nlspath) {
213 				if (*nlspath == '%') {
214 					switch (*(nlspath + 1)) {
215 					case 'l':
216 						tmpptr = plang;
217 						break;
218 					case 't':
219 						tmpptr = pter;
220 						break;
221 					case 'c':
222 						tmpptr = pcode;
223 						break;
224 					case 'L':
225 						tmpptr = lang;
226 						break;
227 					case 'N':
228 						tmpptr = (char *)name;
229 						break;
230 					case '%':
231 						++nlspath;
232 						/* FALLTHROUGH */
233 					default:
234 						if (pathP - path >=
235 						    sizeof(path) - 1)
236 							goto too_long;
237 						*(pathP++) = *nlspath;
238 						continue;
239 					}
240 					++nlspath;
241 			put_tmpptr:
242 					spcleft = sizeof(path) -
243 						  (pathP - path) - 1;
244 					if (strlcpy(pathP, tmpptr, spcleft) >=
245 					    spcleft) {
246 			too_long:
247 						free(plang);
248 						free(base);
249 						SAVEFAIL(name, lang, ENAMETOOLONG);
250 						NLRETERR(ENAMETOOLONG);
251 					}
252 					pathP += strlen(tmpptr);
253 				} else {
254 					if (pathP - path >= sizeof(path) - 1)
255 						goto too_long;
256 					*(pathP++) = *nlspath;
257 				}
258 			}
259 			*pathP = '\0';
260 			if (stat(path, &sbuf) == 0) {
261 				free(plang);
262 				free(base);
263 				return (load_msgcat(path, name, lang));
264 			}
265 		} else {
266 			tmpptr = (char *)name;
267 			--nlspath;
268 			goto put_tmpptr;
269 		}
270 	}
271 	free(plang);
272 	free(base);
273 	SAVEFAIL(name, lang, ENOENT);
274 	NLRETERR(ENOENT);
275 }
276 
277 char *
278 catgets(nl_catd catd, int set_id, int msg_id, const char *s)
279 {
280 	struct _nls_cat_hdr *cat_hdr;
281 	struct _nls_msg_hdr *msg_hdr;
282 	struct _nls_set_hdr *set_hdr;
283 	int i, l, r, u;
284 
285 	if (catd == NULL || catd == NLERR) {
286 		errno = EBADF;
287 		/* LINTED interface problem */
288 		return ((char *)s);
289 	}
290 
291 	cat_hdr = (struct _nls_cat_hdr *)catd->__data;
292 	set_hdr = (struct _nls_set_hdr *)(void *)((char *)catd->__data +
293 	    sizeof(struct _nls_cat_hdr));
294 
295 	/* binary search, see knuth algorithm b */
296 	l = 0;
297 	u = ntohl((u_int32_t)cat_hdr->__nsets) - 1;
298 	while (l <= u) {
299 		i = (l + u) / 2;
300 		r = set_id - ntohl((u_int32_t)set_hdr[i].__setno);
301 
302 		if (r == 0) {
303 			msg_hdr = (struct _nls_msg_hdr *)
304 			    (void *)((char *)catd->__data +
305 			    sizeof(struct _nls_cat_hdr) +
306 			    ntohl((u_int32_t)cat_hdr->__msg_hdr_offset));
307 
308 			l = ntohl((u_int32_t)set_hdr[i].__index);
309 			u = l + ntohl((u_int32_t)set_hdr[i].__nmsgs) - 1;
310 			while (l <= u) {
311 				i = (l + u) / 2;
312 				r = msg_id -
313 				    ntohl((u_int32_t)msg_hdr[i].__msgno);
314 				if (r == 0) {
315 					return ((char *) catd->__data +
316 					    sizeof(struct _nls_cat_hdr) +
317 					    ntohl((u_int32_t)
318 					    cat_hdr->__msg_txt_offset) +
319 					    ntohl((u_int32_t)
320 					    msg_hdr[i].__offset));
321 				} else if (r < 0) {
322 					u = i - 1;
323 				} else {
324 					l = i + 1;
325 				}
326 			}
327 
328 			/* not found */
329 			goto notfound;
330 
331 		} else if (r < 0) {
332 			u = i - 1;
333 		} else {
334 			l = i + 1;
335 		}
336 	}
337 
338 notfound:
339 	/* not found */
340 	errno = ENOMSG;
341 	/* LINTED interface problem */
342 	return ((char *)s);
343 }
344 
345 static void
346 catfree(struct catentry *np)
347 {
348 
349 	if (np->catd != NULL && np->catd != NLERR) {
350 		munmap(np->catd->__data, (size_t)np->catd->__size);
351 		free(np->catd);
352 	}
353 	SLIST_REMOVE(&cache, np, catentry, list);
354 	free(np->name);
355 	free(np->path);
356 	free(np->lang);
357 	free(np);
358 }
359 
360 int
361 catclose(nl_catd catd)
362 {
363 	struct catentry *np;
364 
365 	/* sanity checking */
366 	if (catd == NULL || catd == NLERR) {
367 		errno = EBADF;
368 		return (-1);
369 	}
370 
371 	/* Remove from cache if not referenced any more */
372 	WLOCK(-1);
373 	SLIST_FOREACH(np, &cache, list) {
374 		if (catd == np->catd) {
375 			if (atomic_fetchadd_int(&np->refcount, -1) == 1)
376 				catfree(np);
377 			break;
378 		}
379 	}
380 	UNLOCK;
381 	return (0);
382 }
383 
384 /*
385  * Internal support functions
386  */
387 
388 static nl_catd
389 load_msgcat(const char *path, const char *name, const char *lang)
390 {
391 	struct stat st;
392 	nl_catd	catd;
393 	struct catentry *np;
394 	void *data;
395 	char *copy_path, *copy_name, *copy_lang;
396 	int fd;
397 
398 	/* path/name will never be NULL here */
399 
400 	/*
401 	 * One more try in cache; if it was not found by name,
402 	 * it might still be found by absolute path.
403 	 */
404 	RLOCK(NLERR);
405 	SLIST_FOREACH(np, &cache, list) {
406 		if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
407 			atomic_add_int(&np->refcount, 1);
408 			UNLOCK;
409 			return (np->catd);
410 		}
411 	}
412 	UNLOCK;
413 
414 	if ((fd = _open(path, O_RDONLY | O_CLOEXEC)) == -1) {
415 		SAVEFAIL(name, lang, errno);
416 		NLRETERR(errno);
417 	}
418 
419 	if (_fstat(fd, &st) != 0) {
420 		_close(fd);
421 		SAVEFAIL(name, lang, EFTYPE);
422 		NLRETERR(EFTYPE);
423 	}
424 
425 	/*
426 	 * If the file size cannot be held in size_t we cannot mmap()
427 	 * it to the memory.  Probably, this will not be a problem given
428 	 * that catalog files are usually small.
429 	 */
430 	if (st.st_size > SIZE_T_MAX) {
431 		_close(fd);
432 		SAVEFAIL(name, lang, EFBIG);
433 		NLRETERR(EFBIG);
434 	}
435 
436 	if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
437 	    MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
438 		int saved_errno = errno;
439 		_close(fd);
440 		SAVEFAIL(name, lang, saved_errno);
441 		NLRETERR(saved_errno);
442 	}
443 	_close(fd);
444 
445 	if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
446 	    _NLS_MAGIC) {
447 		munmap(data, (size_t)st.st_size);
448 		SAVEFAIL(name, lang, EFTYPE);
449 		NLRETERR(EFTYPE);
450 	}
451 
452 	copy_name = strdup(name);
453 	copy_path = strdup(path);
454 	copy_lang = (lang == NULL) ? NULL : strdup(lang);
455 	catd = malloc(sizeof (*catd));
456 	np = calloc(1, sizeof(struct catentry));
457 
458 	if (copy_name == NULL || copy_path == NULL ||
459 	    (lang != NULL && copy_lang == NULL) ||
460 	    catd == NULL || np == NULL) {
461 		free(copy_name);
462 		free(copy_path);
463 		free(copy_lang);
464 		free(catd);
465 		free(np);
466 		munmap(data, (size_t)st.st_size);
467 		SAVEFAIL(name, lang, ENOMEM);
468 		NLRETERR(ENOMEM);
469 	}
470 
471 	catd->__data = data;
472 	catd->__size = (int)st.st_size;
473 
474 	/* Caching opened catalog */
475 	np->name = copy_name;
476 	np->path = copy_path;
477 	np->catd = catd;
478 	np->lang = copy_lang;
479 	atomic_store_int(&np->refcount, 1);
480 	WLOCK(NLERR);
481 	SLIST_INSERT_HEAD(&cache, np, list);
482 	UNLOCK;
483 	return (catd);
484 }
485