msgcat.c revision 304863
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/11/lib/libc/nls/msgcat.c 304863 2016-08-26 21:23:38Z 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
328static void
329catfree(struct catentry *np)
330{
331
332	if (np->catd != NULL && np->catd != NLERR) {
333		munmap(np->catd->__data, (size_t)np->catd->__size);
334		free(np->catd);
335	}
336	SLIST_REMOVE(&cache, np, catentry, list);
337	free(np->name);
338	free(np->path);
339	free(np->lang);
340	free(np);
341}
342
343int
344catclose(nl_catd catd)
345{
346	struct catentry *np;
347
348	/* sanity checking */
349	if (catd == NULL || catd == NLERR) {
350		errno = EBADF;
351		return (-1);
352	}
353
354	/* Remove from cache if not referenced any more */
355	WLOCK(-1);
356	SLIST_FOREACH(np, &cache, list) {
357		if (catd == np->catd) {
358			np->refcount--;
359			if (np->refcount == 0)
360				catfree(np);
361			break;
362		}
363	}
364	UNLOCK;
365	return (0);
366}
367
368/*
369 * Internal support functions
370 */
371
372static nl_catd
373load_msgcat(const char *path, const char *name, const char *lang)
374{
375	struct stat st;
376	nl_catd	catd;
377	struct catentry *np;
378	void *data;
379	int fd;
380
381	/* path/name will never be NULL here */
382
383	/*
384	 * One more try in cache; if it was not found by name,
385	 * it might still be found by absolute path.
386	 */
387	RLOCK(NLERR);
388	SLIST_FOREACH(np, &cache, list) {
389		if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
390			np->refcount++;
391			UNLOCK;
392			return (np->catd);
393		}
394	}
395	UNLOCK;
396
397	if ((fd = _open(path, O_RDONLY | O_CLOEXEC)) == -1) {
398		SAVEFAIL(name, lang, errno);
399		NLRETERR(errno);
400	}
401
402	if (_fstat(fd, &st) != 0) {
403		_close(fd);
404		SAVEFAIL(name, lang, EFTYPE);
405		NLRETERR(EFTYPE);
406	}
407
408	/*
409	 * If the file size cannot be held in size_t we cannot mmap()
410	 * it to the memory.  Probably, this will not be a problem given
411	 * that catalog files are usually small.
412	 */
413	if (st.st_size > SIZE_T_MAX) {
414		_close(fd);
415		SAVEFAIL(name, lang, EFBIG);
416		NLRETERR(EFBIG);
417	}
418
419	if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
420	    MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
421		int saved_errno = errno;
422		_close(fd);
423		SAVEFAIL(name, lang, saved_errno);
424		NLRETERR(saved_errno);
425	}
426	_close(fd);
427
428	if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
429	    _NLS_MAGIC) {
430		munmap(data, (size_t)st.st_size);
431		SAVEFAIL(name, lang, EFTYPE);
432		NLRETERR(EFTYPE);
433	}
434
435	if ((catd = malloc(sizeof (*catd))) == NULL) {
436		munmap(data, (size_t)st.st_size);
437		SAVEFAIL(name, lang, ENOMEM);
438		NLRETERR(ENOMEM);
439	}
440
441	catd->__data = data;
442	catd->__size = (int)st.st_size;
443
444	/* Caching opened catalog */
445	WLOCK(NLERR);
446	if ((np = malloc(sizeof(struct catentry))) != NULL) {
447		np->name = strdup(name);
448		np->path = strdup(path);
449		np->catd = catd;
450		np->lang = (lang == NULL) ? NULL : strdup(lang);
451		np->refcount = 1;
452		np->caterrno = 0;
453		SLIST_INSERT_HEAD(&cache, np, list);
454	}
455	UNLOCK;
456	return (catd);
457}
458