msgcat.c revision 141118
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: head/lib/libc/nls/msgcat.c 141118 2005-02-01 16:04:55Z phantom $");
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 <locale.h>
49#include <nl_types.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <unistd.h>
54#include "un-namespace.h"
55
56#include "msgcat.h"
57#include "../locale/setlocale.h"        /* for ENCODING_LEN */
58
59#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"
60
61#define	TRUE	1
62#define	FALSE	0
63
64#define	NLERR		((nl_catd) -1)
65#define NLRETERR(errc)  { errno = errc; return (NLERR); }
66
67static nl_catd  loadCat(__const char *);
68static int      loadSet(MCCatT *, MCSetT *);
69static void     __nls_free_resources(MCCatT *, int);
70
71nl_catd
72catopen(__const char *name, int type)
73{
74	int             spcleft, saverr;
75	char            path[PATH_MAX];
76	char            *nlspath, *lang, *base, *cptr, *pathP, *tmpptr;
77	char            *cptr1, *plang, *pter, *pcode;
78	struct stat     sbuf;
79
80	if (name == NULL || *name == '\0')
81		NLRETERR(EINVAL);
82
83	/* is it absolute path ? if yes, load immediately */
84	if (strchr(name, '/') != NULL)
85		return (loadCat(name));
86
87	if (type == NL_CAT_LOCALE)
88		lang = setlocale(LC_MESSAGES, NULL);
89	else
90		lang = getenv("LANG");
91
92	if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
93	    (lang[0] == '.' &&
94	     (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
95	    strchr(lang, '/') != NULL)
96		lang = "C";
97
98	if ((plang = cptr1 = strdup(lang)) == NULL)
99		return (NLERR);
100	if ((cptr = strchr(cptr1, '@')) != NULL)
101		*cptr = '\0';
102	pter = pcode = "";
103	if ((cptr = strchr(cptr1, '_')) != NULL) {
104		*cptr++ = '\0';
105		pter = cptr1 = cptr;
106	}
107	if ((cptr = strchr(cptr1, '.')) != NULL) {
108		*cptr++ = '\0';
109		pcode = cptr;
110	}
111
112	if ((nlspath = getenv("NLSPATH")) == NULL || issetugid())
113		nlspath = _DEFAULT_NLS_PATH;
114
115	if ((base = cptr = strdup(nlspath)) == NULL) {
116		saverr = errno;
117		free(plang);
118		errno = saverr;
119		return (NLERR);
120	}
121
122	while ((nlspath = strsep(&cptr, ":")) != NULL) {
123		pathP = path;
124		if (*nlspath) {
125			for (; *nlspath; ++nlspath) {
126				if (*nlspath == '%') {
127					switch (*(nlspath + 1)) {
128					case 'l':
129						tmpptr = plang;
130						break;
131					case 't':
132						tmpptr = pter;
133						break;
134					case 'c':
135						tmpptr = pcode;
136						break;
137					case 'L':
138						tmpptr = lang;
139						break;
140					case 'N':
141						tmpptr = (char *)name;
142						break;
143					case '%':
144						++nlspath;
145						/* fallthrough */
146					default:
147						if (pathP - path >=
148						    sizeof(path) - 1)
149							goto too_long;
150						*(pathP++) = *nlspath;
151						continue;
152					}
153					++nlspath;
154			put_tmpptr:
155					spcleft = sizeof(path) -
156						  (pathP - path) - 1;
157					if (strlcpy(pathP, tmpptr, spcleft) >=
158					    spcleft) {
159			too_long:
160						free(plang);
161						free(base);
162						NLRETERR(ENAMETOOLONG);
163					}
164					pathP += strlen(tmpptr);
165				} else {
166					if (pathP - path >= sizeof(path) - 1)
167						goto too_long;
168					*(pathP++) = *nlspath;
169				}
170			}
171			*pathP = '\0';
172			if (stat(path, &sbuf) == 0) {
173				free(plang);
174				free(base);
175				return (loadCat(path));
176			}
177		} else {
178			tmpptr = (char *)name;
179			--nlspath;
180			goto put_tmpptr;
181		}
182	}
183	free(plang);
184	free(base);
185	NLRETERR(ENOENT);
186}
187
188/*
189 * We've got an odd situation here.  The odds are real good that the
190 * number we are looking for is almost the same as the index.  We could
191 * use the index, check the difference and do something intelligent, but
192 * I haven't quite figured out what's intelligent.
193 *
194 * Here's a start.
195 *	Take an id N.  If there are > N items in the list, then N cannot
196 *	be more than N items from the start, since otherwise there would
197 *	have to be duplicate items.  So we can safely set the top to N+1
198 *	(after taking into account that ids start at 1, and arrays at 0)
199 *
200 *	Let's say we are at position P, and we are looking for N, but have
201 *	V.  If N > V, then the furthest away that N could be is
202 *	P + (N-V).  So we can safely set hi to P+(N-V)+1.  For example:
203 *		We are looking for 10, but have 8
204 *		8	?	?	?	?
205 *			>=9	>=10	>=11
206 *
207 */
208
209#define LOOKUP(PARENT, CHILD, ID, NUM, SET) {                    \
210	lo = 0;                                                  \
211	if (ID - 1 < PARENT->NUM) {                              \
212		cur = ID - 1;                                    \
213		hi = ID;                                         \
214	} else {                                                 \
215		hi = PARENT->NUM;                                \
216		cur = (hi - lo) / 2;                             \
217	}                                                        \
218	while (TRUE) {                                           \
219		CHILD = PARENT->SET + cur;                       \
220		if (CHILD->ID == ID)                             \
221			break;                                   \
222		if (CHILD->ID < ID) {                            \
223			lo = cur + 1;                            \
224			if (hi > cur + (ID - CHILD->ID) + 1)     \
225				hi = cur + (ID - CHILD->ID) + 1; \
226			dir = 1;                                 \
227		} else {                                         \
228			hi = cur;                                \
229			dir = -1;                                \
230		}                                                \
231		if (lo >= hi)                                    \
232			return (NULL);                           \
233		if (hi - lo == 1)                                \
234			cur += dir;                              \
235		else                                             \
236			cur += ((hi - lo) / 2) * dir;            \
237	}                                                        \
238}
239
240static MCSetT *
241MCGetSet(MCCatT *cat, int setId)
242{
243	MCSetT  *set;
244	long    lo, hi, cur, dir;
245
246	if (cat == NULL || setId <= 0)
247		return (NULL);
248	LOOKUP(cat, set, setId, numSets, sets);
249	if (set->invalid && loadSet(cat, set) <= 0)
250		return (NULL);
251	return (set);
252}
253
254static MCMsgT *
255MCGetMsg(MCSetT *set, int msgId)
256{
257	MCMsgT  *msg;
258	long    lo, hi, cur, dir;
259
260	if (set == NULL || set->invalid || msgId <= 0)
261		return (NULL);
262	LOOKUP(set, msg, msgId, numMsgs, u.msgs);
263	return (msg);
264}
265
266char *
267catgets(nl_catd catd, int setId, int msgId, __const char *dflt)
268{
269	MCMsgT          *msg;
270	MCCatT          *cat = (MCCatT *)catd;
271	__const char    *cptr;
272
273	if (catd == NULL || catd == NLERR)
274		return ((char *)dflt);
275	msg = MCGetMsg(MCGetSet(cat, setId), msgId);
276	if (msg != NULL)
277		cptr = msg->msg.str;
278	else
279		cptr = dflt;
280	return ((char *)cptr);
281}
282
283int
284catclose(nl_catd catd)
285{
286	MCCatT  *cat = (MCCatT *)catd;
287
288	if (catd == NULL || catd == NLERR) {
289		errno = EBADF;
290		return (-1);
291	}
292
293	(void)fclose(cat->fp);
294	__nls_free_resources(cat, cat->numSets);
295	free(cat);
296	return (0);
297}
298
299/*
300 * Internal routines
301 */
302
303/* Note that only malloc failures are allowed to return an error */
304static char     *_errowner = "Message Catalog System";
305
306#define CORRUPT() {                                            \
307	(void)fclose(cat->fp);                                 \
308	(void)fprintf(stderr, "%s: corrupt file.", _errowner); \
309	free(cat);                                             \
310	NLRETERR(EFTYPE);                                      \
311}
312
313#define NOSPACE() {                                              \
314	saverr = errno;                                          \
315	(void)fclose(cat->fp);                                   \
316	(void)fprintf(stderr, "%s: no more memory.", _errowner); \
317	free(cat);                                               \
318	errno = saverr;                                          \
319	return (NLERR);                                          \
320}
321
322static void
323__nls_free_resources(MCCatT *cat, int i)
324{
325	MCSetT  *set;
326	int     j;
327
328	for (j = 0; j < i; j++) {
329		set = cat->sets + j;
330		if (!set->invalid) {
331			free(set->data.str);
332			free(set->u.msgs);
333		}
334	}
335	free(cat->sets);
336}
337
338static nl_catd
339loadCat(__const char *catpath)
340{
341	MCHeaderT       header;
342	MCCatT          *cat;
343	MCSetT          *set;
344	long            i;
345	off_t           nextSet;
346	int             saverr;
347
348	if ((cat = (MCCatT *)malloc(sizeof(MCCatT))) == NULL)
349		return (NLERR);
350
351	if ((cat->fp = fopen(catpath, "r")) == NULL) {
352		saverr = errno;
353		free(cat);
354		errno = saverr;
355		return (NLERR);
356	}
357	(void)_fcntl(fileno(cat->fp), F_SETFD, FD_CLOEXEC);
358
359	if (fread(&header, sizeof(header), 1, cat->fp) != 1 ||
360	    strncmp(header.magic, MCMagic, MCMagicLen) != 0)
361		CORRUPT();
362
363	if (header.majorVer != MCMajorVer) {
364		(void)fclose(cat->fp);
365		free(cat);
366		(void)fprintf(stderr, "%s: %s is version %ld, we need %ld.\n",
367		    _errowner, catpath, header.majorVer, MCMajorVer);
368		NLRETERR(EFTYPE);
369	}
370	if (header.numSets <= 0) {
371		(void)fclose(cat->fp);
372		free(cat);
373		(void)fprintf(stderr, "%s: %s has %ld sets!\n",
374		    _errowner, catpath, header.numSets);
375		NLRETERR(EFTYPE);
376	}
377
378	cat->numSets = header.numSets;
379	if ((cat->sets = (MCSetT *)malloc(sizeof(MCSetT) * header.numSets)) ==
380	    NULL)
381		NOSPACE();
382
383	nextSet = header.firstSet;
384	for (i = 0; i < cat->numSets; ++i) {
385		if (fseeko(cat->fp, nextSet, SEEK_SET) == -1) {
386			__nls_free_resources(cat, i);
387			CORRUPT();
388		}
389
390		/* read in the set header */
391		set = cat->sets + i;
392		if (fread(set, sizeof(*set), 1, cat->fp) != 1) {
393			__nls_free_resources(cat, i);
394			CORRUPT();
395		}
396
397		/* if it's invalid, skip over it (and backup 'i') */
398		if (set->invalid) {
399			--i;
400			nextSet = set->nextSet;
401			continue;
402		}
403		set->invalid = TRUE;
404		nextSet = set->nextSet;
405	}
406
407	return ((nl_catd) cat);
408}
409
410static int
411loadSet(MCCatT *cat, MCSetT *set)
412{
413	MCMsgT  *msg;
414	int     i;
415	int     saverr;
416
417	/* Get the data */
418	if (fseeko(cat->fp, set->data.off, SEEK_SET) == -1)
419		return (0);
420	if ((set->data.str = malloc(set->dataLen)) == NULL)
421		return (-1);
422	if (fread(set->data.str, set->dataLen, 1, cat->fp) != 1) {
423		saverr = errno;
424		free(set->data.str);
425		errno = saverr;
426		return (0);
427	}
428
429	/* Get the messages */
430	if (fseeko(cat->fp, set->u.firstMsg, SEEK_SET) == -1) {
431		saverr = errno;
432		free(set->data.str);
433		errno = saverr;
434		return (0);
435	}
436	if ((set->u.msgs = (MCMsgT *)malloc(sizeof(MCMsgT) * set->numMsgs)) ==
437	    NULL) {
438		saverr = errno;
439		free(set->data.str);
440		errno = saverr;
441		return (-1);
442	}
443
444	for (i = 0; i < set->numMsgs; ++i) {
445		msg = set->u.msgs + i;
446		if (fread(msg, sizeof(*msg), 1, cat->fp) != 1) {
447			saverr = errno;
448			free(set->u.msgs);
449			free(set->data.str);
450			errno = saverr;
451			return (0);
452		}
453		if (msg->invalid) {
454			--i;
455			continue;
456		}
457		msg->msg.str = (char *)(set->data.str + msg->msg.off);
458	}
459	set->invalid = FALSE;
460	return (1);
461}
462