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