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