msgcat.c revision 304862
1/***********************************************************
2Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
3Copyright 2010, Gabor Kovesdan <gabor@FreeBSD.org>
4
5                        All Rights Reserved
6
7Permission to use, copy, modify, and distribute this software and its
8documentation for any purpose and without fee is hereby granted,
9provided that the above copyright notice appear in all copies and that
10both that copyright notice and this permission notice appear in
11supporting documentation, and that Alfalfa's name not be used in
12advertising or publicity pertaining to distribution of the software
13without specific, written prior permission.
14
15ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
16ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
17ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
18ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
19WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
20ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
21SOFTWARE.
22
23If you make any modifications, bugfixes or other changes to this software
24we'd appreciate it if you could send a copy to us so we can keep things
25up-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: stable/10/lib/libc/nls/msgcat.c 304862 2016-08-26 21:19:23Z ache $");
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 <nl_types.h>
51#include <pthread.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56#include "un-namespace.h"
57
58#include "../locale/xlocale_private.h"
59
60#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"
61
62#define RLOCK(fail)	{ int ret;						\
63			  if (__isthreaded &&					\
64			      ((ret = _pthread_rwlock_rdlock(&rwlock)) != 0)) {	\
65				  errno = ret;					\
66				  return (fail);				\
67			  }}
68#define WLOCK(fail)	{ int ret;						\
69			  if (__isthreaded &&					\
70			      ((ret = _pthread_rwlock_wrlock(&rwlock)) != 0)) {	\
71				  errno = ret;					\
72				  return (fail);				\
73			  }}
74#define UNLOCK		{ if (__isthreaded)					\
75			      _pthread_rwlock_unlock(&rwlock); }
76
77#define	NLERR		((nl_catd) -1)
78#define NLRETERR(errc)  { errno = errc; return (NLERR); }
79#define SAVEFAIL(n, l, e)	{ WLOCK(NLERR);					\
80				  np = malloc(sizeof(struct catentry));		\
81				  if (np != NULL) {				\
82				  	np->name = strdup(n);			\
83					np->path = NULL;			\
84					np->catd = NLERR;			\
85					np->refcount = 0;			\
86					np->lang = (l == NULL) ? NULL :		\
87					    strdup(l);				\
88					np->caterrno = e;			\
89				  	SLIST_INSERT_HEAD(&cache, np, list);	\
90				  }						\
91				  UNLOCK;					\
92				  errno = e;					\
93				}
94
95static nl_catd load_msgcat(const char *, const char *, const char *);
96
97static pthread_rwlock_t		 rwlock = PTHREAD_RWLOCK_INITIALIZER;
98
99struct catentry {
100	SLIST_ENTRY(catentry)	 list;
101	char			*name;
102	char			*path;
103	int			 caterrno;
104	nl_catd			 catd;
105	char			*lang;
106	int			 refcount;
107};
108
109SLIST_HEAD(listhead, catentry) cache =
110    SLIST_HEAD_INITIALIZER(cache);
111
112nl_catd
113catopen(const char *name, int type)
114{
115	struct stat sbuf;
116	struct catentry *np;
117	char *base, *cptr, *cptr1, *nlspath, *pathP, *pcode;
118	char *plang, *pter;
119	int saverr, spcleft;
120	const char *lang, *tmpptr;
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 = querylocale(LC_MESSAGES_MASK, __get_locale());
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
260char *
261catgets(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
321notfound:
322	/* not found */
323	errno = ENOMSG;
324	/* LINTED interface problem */
325	return ((char *)s);
326}
327
328int
329catclose(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
364static nl_catd
365load_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