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