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