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