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