17494Sjkh/***********************************************************
27494SjkhCopyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
3202992SgaborCopyright 2010, Gabor Kovesdan <gabor@FreeBSD.org>
47494Sjkh
57494Sjkh                        All Rights Reserved
67494Sjkh
77494SjkhPermission to use, copy, modify, and distribute this software and its
87494Sjkhdocumentation for any purpose and without fee is hereby granted,
97494Sjkhprovided that the above copyright notice appear in all copies and that
107494Sjkhboth that copyright notice and this permission notice appear in
117494Sjkhsupporting documentation, and that Alfalfa's name not be used in
127494Sjkhadvertising or publicity pertaining to distribution of the software
137494Sjkhwithout specific, written prior permission.
147494Sjkh
157494SjkhALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
167494SjkhALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
177494SjkhALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
187494SjkhANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
197494SjkhWHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
207494SjkhARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
217494SjkhSOFTWARE.
227494Sjkh
237494SjkhIf you make any modifications, bugfixes or other changes to this software
247494Sjkhwe'd appreciate it if you could send a copy to us so we can keep things
257494Sjkhup-to-date.  Many thanks.
267494Sjkh				Kee Hinckley
277494Sjkh				Alfalfa Software, Inc.
287494Sjkh				267 Allston St., #3
297494Sjkh				Cambridge, MA 02139  USA
307494Sjkh				nazgul@alfalfa.com
318870Srgrimes
327494Sjkh******************************************************************/
337494Sjkh
3492986Sobrien#include <sys/cdefs.h>
3592986Sobrien__FBSDID("$FreeBSD$");
367494Sjkh
37142664Sphantom#define _NLS_PRIVATE
387494Sjkh
3971579Sdeischen#include "namespace.h"
407494Sjkh#include <sys/types.h>
417494Sjkh#include <sys/stat.h>
42142664Sphantom#include <sys/mman.h>
43202992Sgabor#include <sys/queue.h>
44106053Swollman
45142664Sphantom#include <arpa/inet.h>		/* for ntohl() */
46142664Sphantom
4737643Sache#include <errno.h>
487494Sjkh#include <fcntl.h>
49106053Swollman#include <limits.h>
5035548Sache#include <locale.h>
5165326Sphantom#include <nl_types.h>
52202992Sgabor#include <pthread.h>
537494Sjkh#include <stdio.h>
547494Sjkh#include <stdlib.h>
557494Sjkh#include <string.h>
567494Sjkh#include <unistd.h>
5771579Sdeischen#include "un-namespace.h"
587494Sjkh
59101316Sache#include "../locale/setlocale.h"        /* for ENCODING_LEN */
6065326Sphantom
6165436Sphantom#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"
627494Sjkh
63204110Sgabor#define RLOCK(fail)	{ int ret;						\
64204110Sgabor			  if (__isthreaded &&					\
65204110Sgabor			      ((ret = _pthread_rwlock_rdlock(&rwlock)) != 0)) {	\
66204110Sgabor				  errno = ret;					\
67204110Sgabor				  return (fail);				\
68202992Sgabor			  }}
69204110Sgabor#define WLOCK(fail)	{ int ret;						\
70204110Sgabor			  if (__isthreaded &&					\
71204110Sgabor			      ((ret = _pthread_rwlock_wrlock(&rwlock)) != 0)) {	\
72204110Sgabor				  errno = ret;					\
73204110Sgabor				  return (fail);				\
74202992Sgabor			  }}
75204110Sgabor#define UNLOCK		{ if (__isthreaded)					\
76202992Sgabor			      _pthread_rwlock_unlock(&rwlock); }
77202992Sgabor
7865436Sphantom#define	NLERR		((nl_catd) -1)
79101316Sache#define NLRETERR(errc)  { errno = errc; return (NLERR); }
80204110Sgabor#define SAVEFAIL(n, l, e)	{ WLOCK(NLERR);					\
81204110Sgabor				  np = malloc(sizeof(struct catentry));		\
82204110Sgabor				  if (np != NULL) {				\
83204110Sgabor				  	np->name = strdup(n);			\
84204110Sgabor					np->path = NULL;			\
85244358Seadler					np->catd = NLERR;			\
86204110Sgabor					np->lang = (l == NULL) ? NULL :		\
87204110Sgabor					    strdup(l);				\
88204110Sgabor					np->caterrno = e;			\
89204110Sgabor				  	SLIST_INSERT_HEAD(&cache, np, list);	\
90204110Sgabor				  }						\
91204110Sgabor				  UNLOCK;					\
92204110Sgabor				  errno = e;					\
93203719Sgabor				}
9465436Sphantom
95202992Sgaborstatic nl_catd load_msgcat(const char *, const char *, const char *);
967494Sjkh
97203719Sgaborstatic pthread_rwlock_t		 rwlock = PTHREAD_RWLOCK_INITIALIZER;
98202992Sgabor
99202992Sgaborstruct catentry {
100202992Sgabor	SLIST_ENTRY(catentry)	 list;
101202992Sgabor	char			*name;
102202992Sgabor	char			*path;
103202992Sgabor	int			 caterrno;
104202992Sgabor	nl_catd			 catd;
105202992Sgabor	char			*lang;
106202992Sgabor	int			 refcount;
107202992Sgabor};
108202992Sgabor
109202992SgaborSLIST_HEAD(listhead, catentry) cache =
110202992Sgabor    SLIST_HEAD_INITIALIZER(cache);
111202992Sgabor
11265436Sphantomnl_catd
113142664Sphantomcatopen(const char *name, int type)
1147494Sjkh{
115203174Sgabor	struct stat sbuf;
116203174Sgabor	struct catentry *np;
117203174Sgabor	char *base, *cptr, *cptr1, *lang, *nlspath, *pathP, *pcode;
118203174Sgabor	char *plang, *pter, *tmpptr;
119203174Sgabor	int saverr, spcleft;
120203174Sgabor	char path[PATH_MAX];
1218870Srgrimes
122203719Sgabor	/* sanity checking */
123101316Sache	if (name == NULL || *name == '\0')
124101316Sache		NLRETERR(EINVAL);
1257494Sjkh
126101316Sache	if (strchr(name, '/') != NULL)
127203719Sgabor		/* have a pathname */
128202992Sgabor		lang = NULL;
129202992Sgabor	else {
130202993Sgabor		if (type == NL_CAT_LOCALE)
131202993Sgabor			lang = setlocale(LC_MESSAGES, NULL);
132202993Sgabor		else
133202993Sgabor			lang = getenv("LANG");
13465476Sache
135202993Sgabor		if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
136202993Sgabor		    (lang[0] == '.' &&
137202993Sgabor		    (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
138202993Sgabor		    strchr(lang, '/') != NULL)
139202993Sgabor			lang = "C";
140202992Sgabor	}
14165476Sache
142202992Sgabor	/* Try to get it from the cache first */
143202992Sgabor	RLOCK(NLERR);
144202992Sgabor	SLIST_FOREACH(np, &cache, list) {
145203719Sgabor		if ((strcmp(np->name, name) == 0) &&
146203719Sgabor		    ((lang != NULL && np->lang != NULL &&
147203719Sgabor		    strcmp(np->lang, lang) == 0) || (np->lang == lang))) {
148202992Sgabor			if (np->caterrno != 0) {
149202992Sgabor				/* Found cached failing entry */
150202992Sgabor				UNLOCK;
151202992Sgabor				NLRETERR(np->caterrno);
152203719Sgabor			} else {
153202992Sgabor				/* Found cached successful entry */
154202992Sgabor				np->refcount++;
155202992Sgabor				UNLOCK;
156202992Sgabor				return (np->catd);
157202992Sgabor			}
158202992Sgabor		}
159202992Sgabor	}
160202992Sgabor	UNLOCK;
161202992Sgabor
162202992Sgabor	/* is it absolute path ? if yes, load immediately */
163202992Sgabor	if (strchr(name, '/') != NULL)
164202992Sgabor		return (load_msgcat(name, name, lang));
165202992Sgabor
166203719Sgabor	/* sanity checking */
167101727Sache	if ((plang = cptr1 = strdup(lang)) == NULL)
168101316Sache		return (NLERR);
169101316Sache	if ((cptr = strchr(cptr1, '@')) != NULL)
170101316Sache		*cptr = '\0';
171101316Sache	pter = pcode = "";
172101316Sache	if ((cptr = strchr(cptr1, '_')) != NULL) {
173101316Sache		*cptr++ = '\0';
174101316Sache		pter = cptr1 = cptr;
175101316Sache	}
176101316Sache	if ((cptr = strchr(cptr1, '.')) != NULL) {
177101316Sache		*cptr++ = '\0';
178101316Sache		pcode = cptr;
179101316Sache	}
180101316Sache
181121667Stjr	if ((nlspath = getenv("NLSPATH")) == NULL || issetugid())
182101316Sache		nlspath = _DEFAULT_NLS_PATH;
1838870Srgrimes
184101316Sache	if ((base = cptr = strdup(nlspath)) == NULL) {
185101727Sache		saverr = errno;
186101316Sache		free(plang);
187101727Sache		errno = saverr;
188101316Sache		return (NLERR);
189101316Sache	}
1908870Srgrimes
191101316Sache	while ((nlspath = strsep(&cptr, ":")) != NULL) {
192101316Sache		pathP = path;
193101316Sache		if (*nlspath) {
194101316Sache			for (; *nlspath; ++nlspath) {
195101316Sache				if (*nlspath == '%') {
196101316Sache					switch (*(nlspath + 1)) {
197101316Sache					case 'l':
198101316Sache						tmpptr = plang;
199101316Sache						break;
200101316Sache					case 't':
201101316Sache						tmpptr = pter;
202101316Sache						break;
203101316Sache					case 'c':
204101316Sache						tmpptr = pcode;
205101316Sache						break;
206101316Sache					case 'L':
207101316Sache						tmpptr = lang;
208101316Sache						break;
209101316Sache					case 'N':
210101316Sache						tmpptr = (char *)name;
211101316Sache						break;
212101316Sache					case '%':
213101316Sache						++nlspath;
214204110Sgabor						/* FALLTHROUGH */
215101316Sache					default:
216101316Sache						if (pathP - path >=
217101316Sache						    sizeof(path) - 1)
218101316Sache							goto too_long;
219101316Sache						*(pathP++) = *nlspath;
220101316Sache						continue;
221101316Sache					}
222101316Sache					++nlspath;
223101316Sache			put_tmpptr:
224101316Sache					spcleft = sizeof(path) -
225101316Sache						  (pathP - path) - 1;
226114443Snectar					if (strlcpy(pathP, tmpptr, spcleft) >=
227101316Sache					    spcleft) {
228141118Sphantom			too_long:
229101316Sache						free(plang);
230101316Sache						free(base);
231203719Sgabor						SAVEFAIL(name, lang, ENAMETOOLONG);
232101316Sache						NLRETERR(ENAMETOOLONG);
233101316Sache					}
234101316Sache					pathP += strlen(tmpptr);
235101316Sache				} else {
236101316Sache					if (pathP - path >= sizeof(path) - 1)
237101316Sache						goto too_long;
238101316Sache					*(pathP++) = *nlspath;
239101316Sache				}
24065436Sphantom			}
241101316Sache			*pathP = '\0';
242101316Sache			if (stat(path, &sbuf) == 0) {
24365476Sache				free(plang);
24465323Sphantom				free(base);
245202992Sgabor				return (load_msgcat(path, name, lang));
24665436Sphantom			}
247101316Sache		} else {
248101316Sache			tmpptr = (char *)name;
249101316Sache			--nlspath;
250101316Sache			goto put_tmpptr;
2517494Sjkh		}
2527494Sjkh	}
253101316Sache	free(plang);
254101316Sache	free(base);
255203719Sgabor	SAVEFAIL(name, lang, ENOENT);
256101316Sache	NLRETERR(ENOENT);
2577494Sjkh}
2587494Sjkh
259142664Sphantomchar *
260142664Sphantomcatgets(nl_catd catd, int set_id, int msg_id, const char *s)
261142664Sphantom{
262203174Sgabor	struct _nls_cat_hdr *cat_hdr;
263203174Sgabor	struct _nls_msg_hdr *msg_hdr;
264203174Sgabor	struct _nls_set_hdr *set_hdr;
265203174Sgabor	int i, l, r, u;
26665436Sphantom
267142664Sphantom	if (catd == NULL || catd == NLERR) {
268142664Sphantom		errno = EBADF;
269142664Sphantom		/* LINTED interface problem */
270202992Sgabor		return ((char *)s);
271202992Sgabor	}
27265436Sphantom
273202992Sgabor	cat_hdr = (struct _nls_cat_hdr *)catd->__data;
274202992Sgabor	set_hdr = (struct _nls_set_hdr *)(void *)((char *)catd->__data +
275202992Sgabor	    sizeof(struct _nls_cat_hdr));
2767494Sjkh
277142664Sphantom	/* binary search, see knuth algorithm b */
278142664Sphantom	l = 0;
279142664Sphantom	u = ntohl((u_int32_t)cat_hdr->__nsets) - 1;
280142664Sphantom	while (l <= u) {
281142664Sphantom		i = (l + u) / 2;
282142664Sphantom		r = set_id - ntohl((u_int32_t)set_hdr[i].__setno);
2837494Sjkh
284142664Sphantom		if (r == 0) {
285142664Sphantom			msg_hdr = (struct _nls_msg_hdr *)
286142664Sphantom			    (void *)((char *)catd->__data +
287142664Sphantom			    sizeof(struct _nls_cat_hdr) +
288142664Sphantom			    ntohl((u_int32_t)cat_hdr->__msg_hdr_offset));
2898870Srgrimes
290142664Sphantom			l = ntohl((u_int32_t)set_hdr[i].__index);
291142664Sphantom			u = l + ntohl((u_int32_t)set_hdr[i].__nmsgs) - 1;
292142664Sphantom			while (l <= u) {
293142664Sphantom				i = (l + u) / 2;
294142664Sphantom				r = msg_id -
295142664Sphantom				    ntohl((u_int32_t)msg_hdr[i].__msgno);
296142664Sphantom				if (r == 0) {
297142664Sphantom					return ((char *) catd->__data +
298142664Sphantom					    sizeof(struct _nls_cat_hdr) +
299142664Sphantom					    ntohl((u_int32_t)
300142664Sphantom					    cat_hdr->__msg_txt_offset) +
301142664Sphantom					    ntohl((u_int32_t)
302142664Sphantom					    msg_hdr[i].__offset));
303142664Sphantom				} else if (r < 0) {
304142664Sphantom					u = i - 1;
305142664Sphantom				} else {
306142664Sphantom					l = i + 1;
307142664Sphantom				}
308202992Sgabor			}
3097494Sjkh
310142664Sphantom			/* not found */
311142664Sphantom			goto notfound;
3127494Sjkh
313142664Sphantom		} else if (r < 0) {
314142664Sphantom			u = i - 1;
315142664Sphantom		} else {
316142664Sphantom			l = i + 1;
317142664Sphantom		}
318202992Sgabor	}
3197494Sjkh
320142664Sphantomnotfound:
321142664Sphantom	/* not found */
322142664Sphantom	errno = ENOMSG;
323142664Sphantom	/* LINTED interface problem */
324202992Sgabor	return ((char *)s);
325142664Sphantom}
326142664Sphantom
32765436Sphantomint
328141118Sphantomcatclose(nl_catd catd)
3297494Sjkh{
330203174Sgabor	struct catentry *np;
331202992Sgabor
332203719Sgabor	/* sanity checking */
333101316Sache	if (catd == NULL || catd == NLERR) {
334101316Sache		errno = EBADF;
335101316Sache		return (-1);
336101316Sache	}
337141118Sphantom
338202992Sgabor	/* Remove from cache if not referenced any more */
339202992Sgabor	WLOCK(-1);
340202992Sgabor	SLIST_FOREACH(np, &cache, list) {
341203719Sgabor		if (catd == np->catd) {
342202992Sgabor			np->refcount--;
343202992Sgabor			if (np->refcount == 0) {
344202993Sgabor				munmap(catd->__data, (size_t)catd->__size);
345202993Sgabor				free(catd);
346202992Sgabor				SLIST_REMOVE(&cache, np, catentry, list);
347203719Sgabor				free(np->name);
348203719Sgabor				free(np->path);
349203719Sgabor				free(np->lang);
350202992Sgabor				free(np);
351202992Sgabor			}
352202992Sgabor			break;
353202992Sgabor		}
354202992Sgabor	}
355202992Sgabor	UNLOCK;
356101316Sache	return (0);
3577494Sjkh}
3587494Sjkh
3597494Sjkh/*
360142664Sphantom * Internal support functions
3617494Sjkh */
3627494Sjkh
363142664Sphantomstatic nl_catd
364202992Sgaborload_msgcat(const char *path, const char *name, const char *lang)
3657494Sjkh{
366203174Sgabor	struct stat st;
367203174Sgabor	nl_catd	catd;
368203174Sgabor	struct catentry *np;
369203174Sgabor	void *data;
370203174Sgabor	int fd;
37165436Sphantom
372202992Sgabor	/* path/name will never be NULL here */
37365436Sphantom
374204110Sgabor	/*
375204110Sgabor	 * One more try in cache; if it was not found by name,
376203719Sgabor	 * it might still be found by absolute path.
377203719Sgabor	 */
378202992Sgabor	RLOCK(NLERR);
379202992Sgabor	SLIST_FOREACH(np, &cache, list) {
380203719Sgabor		if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
381202992Sgabor			np->refcount++;
382202992Sgabor			UNLOCK;
383202992Sgabor			return (np->catd);
384202992Sgabor		}
385202992Sgabor	}
386202992Sgabor	UNLOCK;
387202992Sgabor
388241046Sjilles	if ((fd = _open(path, O_RDONLY | O_CLOEXEC)) == -1) {
389203719Sgabor		SAVEFAIL(name, lang, errno);
390203719Sgabor		NLRETERR(errno);
391202992Sgabor	}
3927494Sjkh
393142664Sphantom	if (_fstat(fd, &st) != 0) {
394142664Sphantom		_close(fd);
395203719Sgabor		SAVEFAIL(name, lang, EFTYPE);
396203719Sgabor		NLRETERR(EFTYPE);
397101316Sache	}
3987494Sjkh
399204110Sgabor	/*
400204110Sgabor	 * If the file size cannot be held in size_t we cannot mmap()
401204110Sgabor	 * it to the memory.  Probably, this will not be a problem given
402203719Sgabor	 * that catalog files are usually small.
403203719Sgabor	 */
404203719Sgabor	if (st.st_size > SIZE_T_MAX) {
405203719Sgabor		_close(fd);
406203719Sgabor		SAVEFAIL(name, lang, EFBIG);
407203719Sgabor		NLRETERR(EFBIG);
408203719Sgabor	}
4097494Sjkh
410203719Sgabor	if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
411203719Sgabor	    MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
412203719Sgabor		int saved_errno = errno;
413203719Sgabor		_close(fd);
414203719Sgabor		SAVEFAIL(name, lang, saved_errno);
415203719Sgabor		NLRETERR(saved_errno);
416202992Sgabor	}
417203719Sgabor	_close(fd);
418142664Sphantom
419142664Sphantom	if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
420142664Sphantom	    _NLS_MAGIC) {
421142664Sphantom		munmap(data, (size_t)st.st_size);
422203719Sgabor		SAVEFAIL(name, lang, EFTYPE);
423203719Sgabor		NLRETERR(EFTYPE);
424101316Sache	}
4257494Sjkh
426142664Sphantom	if ((catd = malloc(sizeof (*catd))) == NULL) {
427142664Sphantom		munmap(data, (size_t)st.st_size);
428203719Sgabor		SAVEFAIL(name, lang, ENOMEM);
429203719Sgabor		NLRETERR(ENOMEM);
43025642Sache	}
431141118Sphantom
432142664Sphantom	catd->__data = data;
433142664Sphantom	catd->__size = (int)st.st_size;
434202992Sgabor
435202992Sgabor	/* Caching opened catalog */
436202992Sgabor	WLOCK(NLERR);
437202992Sgabor	if ((np = malloc(sizeof(struct catentry))) != NULL) {
438202992Sgabor		np->name = strdup(name);
439202992Sgabor		np->path = strdup(path);
440202992Sgabor		np->catd = catd;
441202992Sgabor		np->lang = (lang == NULL) ? NULL : strdup(lang);
442202992Sgabor		np->refcount = 1;
443202992Sgabor		np->caterrno = 0;
444202992Sgabor		SLIST_INSERT_HEAD(&cache, np, list);
445202992Sgabor	}
446202992Sgabor	UNLOCK;
447142664Sphantom	return (catd);
4487494Sjkh}
449