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