xref: /freebsd/lib/libc/nls/msgcat.c (revision 4436b51dff5736e74da464946049ea6899a88938)
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->refcount = 0;			\
87 					np->lang = (l == NULL) ? NULL :		\
88 					    strdup(l);				\
89 					np->caterrno = e;			\
90 				  	SLIST_INSERT_HEAD(&cache, np, list);	\
91 				  }						\
92 				  UNLOCK;					\
93 				  errno = e;					\
94 				}
95 
96 static nl_catd load_msgcat(const char *, const char *, const char *);
97 
98 static pthread_rwlock_t		 rwlock = PTHREAD_RWLOCK_INITIALIZER;
99 
100 struct catentry {
101 	SLIST_ENTRY(catentry)	 list;
102 	char			*name;
103 	char			*path;
104 	int			 caterrno;
105 	nl_catd			 catd;
106 	char			*lang;
107 	int			 refcount;
108 };
109 
110 SLIST_HEAD(listhead, catentry) cache =
111     SLIST_HEAD_INITIALIZER(cache);
112 
113 nl_catd
114 catopen(const char *name, int type)
115 {
116 	struct stat sbuf;
117 	struct catentry *np;
118 	char *base, *cptr, *cptr1, *lang, *nlspath, *pathP, *pcode;
119 	char *plang, *pter, *tmpptr;
120 	int saverr, spcleft;
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 = setlocale(LC_MESSAGES, NULL);
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 int
329 catclose(nl_catd catd)
330 {
331 	struct catentry *np;
332 
333 	/* sanity checking */
334 	if (catd == NULL || catd == NLERR) {
335 		errno = EBADF;
336 		return (-1);
337 	}
338 
339 	/* Remove from cache if not referenced any more */
340 	WLOCK(-1);
341 	SLIST_FOREACH(np, &cache, list) {
342 		if (catd == np->catd) {
343 			np->refcount--;
344 			if (np->refcount == 0) {
345 				munmap(catd->__data, (size_t)catd->__size);
346 				free(catd);
347 				SLIST_REMOVE(&cache, np, catentry, list);
348 				free(np->name);
349 				free(np->path);
350 				free(np->lang);
351 				free(np);
352 			}
353 			break;
354 		}
355 	}
356 	UNLOCK;
357 	return (0);
358 }
359 
360 /*
361  * Internal support functions
362  */
363 
364 static nl_catd
365 load_msgcat(const char *path, const char *name, const char *lang)
366 {
367 	struct stat st;
368 	nl_catd	catd;
369 	struct catentry *np;
370 	void *data;
371 	int fd;
372 
373 	/* path/name will never be NULL here */
374 
375 	/*
376 	 * One more try in cache; if it was not found by name,
377 	 * it might still be found by absolute path.
378 	 */
379 	RLOCK(NLERR);
380 	SLIST_FOREACH(np, &cache, list) {
381 		if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
382 			np->refcount++;
383 			UNLOCK;
384 			return (np->catd);
385 		}
386 	}
387 	UNLOCK;
388 
389 	if ((fd = _open(path, O_RDONLY | O_CLOEXEC)) == -1) {
390 		SAVEFAIL(name, lang, errno);
391 		NLRETERR(errno);
392 	}
393 
394 	if (_fstat(fd, &st) != 0) {
395 		_close(fd);
396 		SAVEFAIL(name, lang, EFTYPE);
397 		NLRETERR(EFTYPE);
398 	}
399 
400 	/*
401 	 * If the file size cannot be held in size_t we cannot mmap()
402 	 * it to the memory.  Probably, this will not be a problem given
403 	 * that catalog files are usually small.
404 	 */
405 	if (st.st_size > SIZE_T_MAX) {
406 		_close(fd);
407 		SAVEFAIL(name, lang, EFBIG);
408 		NLRETERR(EFBIG);
409 	}
410 
411 	if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
412 	    MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
413 		int saved_errno = errno;
414 		_close(fd);
415 		SAVEFAIL(name, lang, saved_errno);
416 		NLRETERR(saved_errno);
417 	}
418 	_close(fd);
419 
420 	if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
421 	    _NLS_MAGIC) {
422 		munmap(data, (size_t)st.st_size);
423 		SAVEFAIL(name, lang, EFTYPE);
424 		NLRETERR(EFTYPE);
425 	}
426 
427 	if ((catd = malloc(sizeof (*catd))) == NULL) {
428 		munmap(data, (size_t)st.st_size);
429 		SAVEFAIL(name, lang, ENOMEM);
430 		NLRETERR(ENOMEM);
431 	}
432 
433 	catd->__data = data;
434 	catd->__size = (int)st.st_size;
435 
436 	/* Caching opened catalog */
437 	WLOCK(NLERR);
438 	if ((np = malloc(sizeof(struct catentry))) != NULL) {
439 		np->name = strdup(name);
440 		np->path = strdup(path);
441 		np->catd = catd;
442 		np->lang = (lang == NULL) ? NULL : strdup(lang);
443 		np->refcount = 1;
444 		np->caterrno = 0;
445 		SLIST_INSERT_HEAD(&cache, np, list);
446 	}
447 	UNLOCK;
448 	return (catd);
449 }
450