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