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