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