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