xref: /dragonfly/lib/libc/nls/msgcat.c (revision 65cc0652)
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 
58 #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"
59 
60 #define RLOCK(fail)	{ int ret;						\
61 			  if (__isthreaded &&					\
62 			      ((ret = _pthread_rwlock_rdlock(&rwlock)) != 0)) {	\
63 				  errno = ret;					\
64 				  return (fail);				\
65 			  }}
66 #define WLOCK(fail)	{ int ret;						\
67 			  if (__isthreaded &&					\
68 			      ((ret = _pthread_rwlock_wrlock(&rwlock)) != 0)) {	\
69 				  errno = ret;					\
70 				  return (fail);				\
71 			  }}
72 #define UNLOCK		{ if (__isthreaded)					\
73 			      _pthread_rwlock_unlock(&rwlock); }
74 
75 #define	NLERR		((nl_catd) -1)
76 #define NLRETERR(errc)  { errno = errc; return (NLERR); }
77 #define SAVEFAIL(n, l, e)	{ WLOCK(NLERR);					\
78 				  np = malloc(sizeof(struct catentry));		\
79 				  if (np != NULL) {				\
80 				  	np->name = strdup(n);			\
81 					np->path = NULL;			\
82 					np->catd = NLERR;			\
83 					np->refcount = 0;			\
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, *nlspath, *pathP, *pcode;
116 	char *plang, *pter;
117 	int saverr, spcleft;
118 	const char *lang, *tmpptr;
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 = querylocale(LC_MESSAGES_MASK, __get_locale());
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 static void
327 catfree(struct catentry *np)
328 {
329 
330 	if (np->catd != NULL && np->catd != NLERR) {
331 		munmap(np->catd->__data, (size_t)np->catd->__size);
332 		free(np->catd);
333 	}
334 	SLIST_REMOVE(&cache, np, catentry, list);
335 	free(np->name);
336 	free(np->path);
337 	free(np->lang);
338 	free(np);
339 }
340 
341 int
342 catclose(nl_catd catd)
343 {
344 	struct catentry *np;
345 
346 	/* sanity checking */
347 	if (catd == NULL || catd == NLERR) {
348 		errno = EBADF;
349 		return (-1);
350 	}
351 
352 	/* Remove from cache if not referenced any more */
353 	WLOCK(-1);
354 	SLIST_FOREACH(np, &cache, list) {
355 		if (catd == np->catd) {
356 			np->refcount--;
357 			if (np->refcount == 0)
358 				catfree(np);
359 			break;
360 		}
361 	}
362 	UNLOCK;
363 	return (0);
364 }
365 
366 /*
367  * Internal support functions
368  */
369 
370 static nl_catd
371 load_msgcat(const char *path, const char *name, const char *lang)
372 {
373 	struct stat st;
374 	nl_catd	catd;
375 	struct catentry *np;
376 	void *data;
377 	int fd;
378 
379 	/* path/name will never be NULL here */
380 
381 	/*
382 	 * One more try in cache; if it was not found by name,
383 	 * it might still be found by absolute path.
384 	 */
385 	RLOCK(NLERR);
386 	SLIST_FOREACH(np, &cache, list) {
387 		if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
388 			np->refcount++;
389 			UNLOCK;
390 			return (np->catd);
391 		}
392 	}
393 	UNLOCK;
394 
395 	if ((fd = _open(path, O_RDONLY | O_CLOEXEC)) == -1) {
396 		SAVEFAIL(name, lang, errno);
397 		NLRETERR(errno);
398 	}
399 
400 	if (_fstat(fd, &st) != 0) {
401 		_close(fd);
402 		SAVEFAIL(name, lang, EFTYPE);
403 		NLRETERR(EFTYPE);
404 	}
405 
406 	/*
407 	 * If the file size cannot be held in size_t we cannot mmap()
408 	 * it to the memory.  Probably, this will not be a problem given
409 	 * that catalog files are usually small.
410 	 */
411 	if (st.st_size > SIZE_T_MAX) {
412 		_close(fd);
413 		SAVEFAIL(name, lang, EFBIG);
414 		NLRETERR(EFBIG);
415 	}
416 
417 	if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
418 	    MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
419 		int saved_errno = errno;
420 		_close(fd);
421 		SAVEFAIL(name, lang, saved_errno);
422 		NLRETERR(saved_errno);
423 	}
424 	_close(fd);
425 
426 	if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
427 	    _NLS_MAGIC) {
428 		munmap(data, (size_t)st.st_size);
429 		SAVEFAIL(name, lang, EFTYPE);
430 		NLRETERR(EFTYPE);
431 	}
432 
433 	if ((catd = malloc(sizeof (*catd))) == NULL) {
434 		munmap(data, (size_t)st.st_size);
435 		SAVEFAIL(name, lang, ENOMEM);
436 		NLRETERR(ENOMEM);
437 	}
438 
439 	catd->__data = data;
440 	catd->__size = (int)st.st_size;
441 
442 	/* Caching opened catalog */
443 	WLOCK(NLERR);
444 	if ((np = malloc(sizeof(struct catentry))) != NULL) {
445 		np->name = strdup(name);
446 		np->path = strdup(path);
447 		np->catd = catd;
448 		np->lang = (lang == NULL) ? NULL : strdup(lang);
449 		np->refcount = 1;
450 		np->caterrno = 0;
451 		SLIST_INSERT_HEAD(&cache, np, list);
452 	}
453 	UNLOCK;
454 	return (catd);
455 }
456