1/***********************************************************
2Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
3
4                        All Rights Reserved
5
6Permission to use, copy, modify, and distribute this software and its
7documentation for any purpose and without fee is hereby granted,
8provided that the above copyright notice appear in all copies and that
9both that copyright notice and this permission notice appear in
10supporting documentation, and that Alfalfa's name not be used in
11advertising or publicity pertaining to distribution of the software
12without specific, written prior permission.
13
14ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
15ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
16ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
17ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
18WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
19ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
20SOFTWARE.
21
22If you make any modifications, bugfixes or other changes to this software
23we'd appreciate it if you could send a copy to us so we can keep things
24up-to-date.  Many thanks.
25				Kee Hinckley
26				Alfalfa Software, Inc.
27				267 Allston St., #3
28				Cambridge, MA 02139  USA
29				nazgul@alfalfa.com
30
31******************************************************************/
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: src/lib/libc/nls/msgcat.c,v 1.49 2005/02/01 16:04:55 phantom Exp $");
35
36/*
37 * We need a better way of handling errors than printing text.  I need
38 * to add an error handling routine.
39 */
40
41#include "namespace.h"
42#include <sys/types.h>
43#include <sys/stat.h>
44
45#include <errno.h>
46#include <fcntl.h>
47#include <limits.h>
48#include <xlocale.h>
49#include <nl_types.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <unistd.h>
54#include <machine/endian.h>
55#include <libkern/OSByteOrder.h>
56#include "un-namespace.h"
57
58#include "msgcat.h"
59#include "setlocale.h"        /* for ENCODING_LEN */
60
61#ifndef ntohll
62#define ntohll(x) OSSwapBigToHostInt64(x)
63#endif
64
65#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"
66
67#define	TRUE	1
68#define	FALSE	0
69
70#define	NLERR		((nl_catd) -1)
71#define NLRETERR(errc)  { errno = errc; return (NLERR); }
72
73static nl_catd  loadCat(__const char *);
74static int      loadSet(MCCatT *, MCSetT *);
75static void     __nls_free_resources(MCCatT *, int);
76
77nl_catd
78catopen(__const char *name, int type)
79{
80	int             spcleft, saverr;
81	char            path[PATH_MAX];
82	char            *nlspath, *lang, *base, *cptr, *pathP, *tmpptr;
83	char            *cptr1, *plang, *pter, *pcode;
84	struct stat     sbuf;
85
86	if (name == NULL || *name == '\0')
87		NLRETERR(EINVAL);
88
89	/* is it absolute path ? if yes, load immediately */
90	if (strchr(name, '/') != NULL)
91		return (loadCat(name));
92
93	if (type == NL_CAT_LOCALE)
94		lang = (char *)querylocale(LC_MESSAGES_MASK, NULL);
95	else
96		lang = getenv("LANG");
97
98	if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
99	    (lang[0] == '.' &&
100	     (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
101	    strchr(lang, '/') != NULL)
102		lang = "C";
103
104	if ((plang = cptr1 = strdup(lang)) == NULL)
105		return (NLERR);
106	if ((cptr = strchr(cptr1, '@')) != NULL)
107		*cptr = '\0';
108	pter = pcode = "";
109	if ((cptr = strchr(cptr1, '_')) != NULL) {
110		*cptr++ = '\0';
111		pter = cptr1 = cptr;
112	}
113	if ((cptr = strchr(cptr1, '.')) != NULL) {
114		*cptr++ = '\0';
115		pcode = cptr;
116	}
117
118	if ((nlspath = getenv("NLSPATH")) == NULL || issetugid())
119		nlspath = _DEFAULT_NLS_PATH;
120
121	if ((base = cptr = strdup(nlspath)) == NULL) {
122		saverr = errno;
123		free(plang);
124		errno = saverr;
125		return (NLERR);
126	}
127
128	while ((nlspath = strsep(&cptr, ":")) != NULL) {
129		pathP = path;
130		if (*nlspath) {
131			for (; *nlspath; ++nlspath) {
132				if (*nlspath == '%') {
133					switch (*(nlspath + 1)) {
134					case 'l':
135						tmpptr = plang;
136						break;
137					case 't':
138						tmpptr = pter;
139						break;
140					case 'c':
141						tmpptr = pcode;
142						break;
143					case 'L':
144						tmpptr = lang;
145						break;
146					case 'N':
147						tmpptr = (char *)name;
148						break;
149					case '%':
150						++nlspath;
151						/* fallthrough */
152					default:
153						if (pathP - path >=
154						    sizeof(path) - 1)
155							goto too_long;
156						*(pathP++) = *nlspath;
157						continue;
158					}
159					++nlspath;
160			put_tmpptr:
161					spcleft = sizeof(path) -
162						  (pathP - path) - 1;
163					if (strlcpy(pathP, tmpptr, spcleft) >=
164					    spcleft) {
165			too_long:
166						free(plang);
167						free(base);
168						NLRETERR(ENAMETOOLONG);
169					}
170					pathP += strlen(tmpptr);
171				} else {
172					if (pathP - path >= sizeof(path) - 1)
173						goto too_long;
174					*(pathP++) = *nlspath;
175				}
176			}
177			*pathP = '\0';
178			if (stat(path, &sbuf) == 0) {
179				free(plang);
180				free(base);
181				return (loadCat(path));
182			}
183		} else {
184			tmpptr = (char *)name;
185			--nlspath;
186			goto put_tmpptr;
187		}
188	}
189	free(plang);
190	free(base);
191	NLRETERR(ENOENT);
192}
193
194/*
195 * We've got an odd situation here.  The odds are real good that the
196 * number we are looking for is almost the same as the index.  We could
197 * use the index, check the difference and do something intelligent, but
198 * I haven't quite figured out what's intelligent.
199 *
200 * Here's a start.
201 *	Take an id N.  If there are > N items in the list, then N cannot
202 *	be more than N items from the start, since otherwise there would
203 *	have to be duplicate items.  So we can safely set the top to N+1
204 *	(after taking into account that ids start at 1, and arrays at 0)
205 *
206 *	Let's say we are at position P, and we are looking for N, but have
207 *	V.  If N > V, then the furthest away that N could be is
208 *	P + (N-V).  So we can safely set hi to P+(N-V)+1.  For example:
209 *		We are looking for 10, but have 8
210 *		8	?	?	?	?
211 *			>=9	>=10	>=11
212 *
213 */
214
215#define LOOKUP(PARENT, CHILD, ID, NUM, SET) {                    \
216	lo = 0;                                                  \
217	if (ID - 1 < NUM) {                              \
218		cur = ID - 1;                                    \
219		hi = ID;                                         \
220	} else {                                                 \
221		hi = NUM;                                \
222		cur = (hi - lo) / 2;                             \
223	}                                                        \
224	while (TRUE) {                                           \
225		CHILD = PARENT->SET + cur;                       \
226		if (ntohl(CHILD->ID) == ID)                             \
227			break;                                   \
228		if (ntohl(CHILD->ID) < ID) {                            \
229			lo = cur + 1;                            \
230			if (hi > cur + (ID - ntohl(CHILD->ID)) + 1)     \
231				hi = cur + (ID - ntohl(CHILD->ID)) + 1; \
232			dir = 1;                                 \
233		} else {                                         \
234			hi = cur;                                \
235			dir = -1;                                \
236		}                                                \
237		if (lo >= hi)                                    \
238			return (NULL);                           \
239		if (hi - lo == 1)                                \
240			cur += dir;                              \
241		else                                             \
242			cur += ((hi - lo) / 2) * dir;            \
243	}                                                        \
244}
245
246static MCSetT *
247MCGetSet(MCCatT *cat, int setId)
248{
249	MCSetT  *set;
250	int32_t    lo, hi, cur, dir;
251
252	if (cat == NULL || setId <= 0)
253		return (NULL);
254	LOOKUP(cat, set, setId, cat->numSets, sets);
255	if (set->invalid && loadSet(cat, set) <= 0)
256		return (NULL);
257	return (set);
258}
259
260static MCMsgT *
261MCGetMsg(MCSetT *set, int msgId)
262{
263	MCMsgT  *msg;
264	int32_t    lo, hi, cur, dir;
265
266	if (set == NULL || set->invalid || msgId <= 0)
267		return (NULL);
268	LOOKUP(set, msg, msgId, ntohl(set->numMsgs), u.msgs);
269	return (msg);
270}
271
272char *
273catgets(nl_catd catd, int setId, int msgId, __const char *dflt)
274{
275	MCMsgT          *msg;
276	MCCatT          *cat = (MCCatT *)catd;
277	__const char    *cptr;
278
279	if (catd == NULL || catd == NLERR)
280		return ((char *)dflt);
281	msg = MCGetMsg(MCGetSet(cat, setId), msgId);
282	if (msg != NULL)
283		cptr = msg->msg.str;
284	else
285		cptr = dflt;
286	return ((char *)cptr);
287}
288
289int
290catclose(nl_catd catd)
291{
292	MCCatT  *cat = (MCCatT *)catd;
293
294	if (catd == NULL || catd == NLERR) {
295		errno = EBADF;
296		return (-1);
297	}
298
299	(void)fclose(cat->fp);
300	__nls_free_resources(cat, cat->numSets);
301	free(cat);
302	return (0);
303}
304
305/*
306 * Internal routines
307 */
308
309/* Note that only malloc failures are allowed to return an error */
310static char     *_errowner = "Message Catalog System";
311
312#define CORRUPT() {                                            \
313	(void)fclose(cat->fp);                                 \
314	(void)fprintf(stderr, "%s: corrupt file.", _errowner); \
315	free(cat);                                             \
316	NLRETERR(EFTYPE);                                      \
317}
318
319#define NOSPACE() {                                              \
320	saverr = errno;                                          \
321	(void)fclose(cat->fp);                                   \
322	(void)fprintf(stderr, "%s: no more memory.", _errowner); \
323	free(cat);                                               \
324	errno = saverr;                                          \
325	return (NLERR);                                          \
326}
327
328static void
329__nls_free_resources(MCCatT *cat, int i)
330{
331	MCSetT  *set;
332	int     j;
333
334	for (j = 0; j < i; j++) {
335		set = cat->sets + j;
336		if (!set->invalid) {
337			free(set->data.str);
338			free(set->u.msgs);
339		}
340	}
341	free(cat->sets);
342}
343
344static nl_catd
345loadCat(__const char *catpath)
346{
347	MCHeaderT       header;
348	MCCatT          *cat;
349	MCSetT          *set;
350	int32_t         i;
351	off_t           nextSet;
352	int             saverr;
353	int		fd;
354
355	if ((cat = (MCCatT *)malloc(sizeof(MCCatT))) == NULL)
356		return (NLERR);
357
358	if ((fd = open(catpath, O_RDONLY | O_CLOEXEC)) == -1) {
359		saverr = errno;
360		free(cat);
361		errno = saverr;
362		return (NLERR);
363	}
364
365	if ((cat->fp = fdopen(fd, "r")) == NULL) {
366		saverr = errno;
367		close(fd);
368		free(cat);
369		errno = saverr;
370		return (NLERR);
371	}
372
373	if (fread(&header, sizeof(header), 1, cat->fp) != 1 ||
374	    strncmp(header.magic, MCMagic, MCMagicLen) != 0)
375		CORRUPT();
376
377	if (ntohl(header.majorVer) != MCMajorVer) {
378		(void)fclose(cat->fp);
379		free(cat);
380		if (OSSwapInt32(ntohl(header.majorVer)) == MCMajorVer) {
381		    (void)fprintf(stderr, "%s: %s is the wrong byte ordering.\n", _errowner, catpath);
382		} else {
383		    (void)fprintf(stderr, "%s: %s is version %d, we need %d.\n", _errowner, catpath, (int)ntohl(header.majorVer), MCMajorVer);
384		}
385		NLRETERR(EFTYPE);
386	}
387	if (ntohl(header.numSets) <= 0) {
388		(void)fclose(cat->fp);
389		free(cat);
390		(void)fprintf(stderr, "%s: %s has %d sets!\n",
391		    _errowner, catpath, (int)ntohl(header.numSets));
392		NLRETERR(EFTYPE);
393	}
394
395	cat->numSets = ntohl(header.numSets);
396	if ((cat->sets = (MCSetT *)malloc(sizeof(MCSetT) * cat->numSets)) ==
397	    NULL)
398		NOSPACE();
399
400	nextSet = ntohll(header.firstSet);
401	for (i = 0; i < cat->numSets; ++i) {
402		if (fseeko(cat->fp, nextSet, SEEK_SET) == -1) {
403			__nls_free_resources(cat, i);
404			CORRUPT();
405		}
406
407		/* read in the set header */
408		set = cat->sets + i;
409		if (fread(set, sizeof(*set), 1, cat->fp) != 1) {
410			__nls_free_resources(cat, i);
411			CORRUPT();
412		}
413
414		/* if it's invalid, skip over it (and backup 'i') */
415		if (set->invalid) {
416			--i;
417			nextSet = ntohll(set->nextSet);
418			continue;
419		}
420		set->invalid = TRUE;
421		nextSet = ntohll(set->nextSet);
422	}
423
424	return ((nl_catd) cat);
425}
426
427static int
428loadSet(MCCatT *cat, MCSetT *set)
429{
430	MCMsgT  *msg;
431	int     i;
432	int     saverr;
433
434	/* Get the data */
435	if (fseeko(cat->fp, ntohll(set->data.off), SEEK_SET) == -1)
436		return (0);
437	if ((set->data.str = malloc(ntohl(set->dataLen))) == NULL)
438		return (-1);
439	if (fread(set->data.str, ntohl(set->dataLen), 1, cat->fp) != 1) {
440		saverr = errno;
441		free(set->data.str);
442		errno = saverr;
443		return (0);
444	}
445
446	/* Get the messages */
447	if (fseeko(cat->fp, ntohll(set->u.firstMsg), SEEK_SET) == -1) {
448		saverr = errno;
449		free(set->data.str);
450		errno = saverr;
451		return (0);
452	}
453	if ((set->u.msgs = (MCMsgT *)malloc(sizeof(MCMsgT) * ntohl(set->numMsgs))) ==
454	    NULL) {
455		saverr = errno;
456		free(set->data.str);
457		errno = saverr;
458		return (-1);
459	}
460
461	for (i = 0; i < ntohl(set->numMsgs); ++i) {
462		msg = set->u.msgs + i;
463		if (fread(msg, sizeof(*msg), 1, cat->fp) != 1) {
464			saverr = errno;
465			free(set->u.msgs);
466			free(set->data.str);
467			errno = saverr;
468			return (0);
469		}
470		if (msg->invalid) {
471			--i;
472			continue;
473		}
474		msg->msg.str = (char *)(set->data.str + ntohll(msg->msg.off));
475	}
476	set->invalid = FALSE;
477	return (1);
478}
479