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