xref: /freebsd/lib/libc/nls/msgcat.c (revision 5773cccf19ef7b97e56c1101aa481c43149224da)
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$");
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 
67 static nl_catd  loadCat();
68 static int      loadSet();
69 static void     __nls_free_resources();
70 
71 nl_catd
72 catopen(name, type)
73 	__const char    *name;
74 	int             type;
75 {
76 	int             spcleft, saverr;
77 	char            path[PATH_MAX];
78 	char            *nlspath, *lang, *base, *cptr, *pathP, *tmpptr;
79 	char            *cptr1, *plang, *pter, *pcode;
80 	struct stat     sbuf;
81 
82 	if (name == NULL || *name == '\0')
83 		NLRETERR(EINVAL);
84 
85 	/* is it absolute path ? if yes, load immediately */
86 	if (strchr(name, '/') != NULL)
87 		return (loadCat(name));
88 
89 	if (type == NL_CAT_LOCALE)
90 		lang = setlocale(LC_MESSAGES, NULL);
91 	else
92 		lang = getenv("LANG");
93 
94 	if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
95 	    (lang[0] == '.' &&
96 	     (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
97 	    strchr(lang, '/') != NULL)
98 		lang = "C";
99 
100 	if ((plang = cptr1 = strdup(lang)) == NULL)
101 		return (NLERR);
102 	if ((cptr = strchr(cptr1, '@')) != NULL)
103 		*cptr = '\0';
104 	pter = pcode = "";
105 	if ((cptr = strchr(cptr1, '_')) != NULL) {
106 		*cptr++ = '\0';
107 		pter = cptr1 = cptr;
108 	}
109 	if ((cptr = strchr(cptr1, '.')) != NULL) {
110 		*cptr++ = '\0';
111 		pcode = cptr;
112 	}
113 
114 	if ((nlspath = getenv("NLSPATH")) == NULL
115 #ifndef __NETBSD_SYSCALLS
116 	    || issetugid()
117 #endif
118 	   )
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 < PARENT->NUM) {                              \
218 		cur = ID - 1;                                    \
219 		hi = ID;                                         \
220 	} else {                                                 \
221 		hi = PARENT->NUM;                                \
222 		cur = (hi - lo) / 2;                             \
223 	}                                                        \
224 	while (TRUE) {                                           \
225 		CHILD = PARENT->SET + cur;                       \
226 		if (CHILD->ID == ID)                             \
227 			break;                                   \
228 		if (CHILD->ID < ID) {                            \
229 			lo = cur + 1;                            \
230 			if (hi > cur + (ID - CHILD->ID) + 1)     \
231 				hi = cur + (ID - 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 
246 static MCSetT *
247 MCGetSet(cat, setId)
248 	MCCatT  *cat;
249 	int     setId;
250 {
251 	MCSetT  *set;
252 	long    lo, hi, cur, dir;
253 
254 	if (cat == NULL || setId <= 0)
255 		return (NULL);
256 	LOOKUP(cat, set, setId, numSets, sets);
257 	if (set->invalid && loadSet(cat, set) <= 0)
258 		return (NULL);
259 	return (set);
260 }
261 
262 static MCMsgT *
263 MCGetMsg(set, msgId)
264 	MCSetT  *set;
265 	int     msgId;
266 {
267 	MCMsgT  *msg;
268 	long    lo, hi, cur, dir;
269 
270 	if (set == NULL || set->invalid || msgId <= 0)
271 		return (NULL);
272 	LOOKUP(set, msg, msgId, numMsgs, u.msgs);
273 	return (msg);
274 }
275 
276 char *
277 catgets(catd, setId, msgId, dflt)
278 	nl_catd         catd;
279 	int             setId;
280 	int             msgId;
281 	__const char    *dflt;
282 {
283 	MCMsgT          *msg;
284 	MCCatT          *cat = (MCCatT *)catd;
285 	__const char    *cptr;
286 
287 	if (catd == NULL || catd == NLERR)
288 		return ((char *)dflt);
289 	msg = MCGetMsg(MCGetSet(cat, setId), msgId);
290 	if (msg != NULL)
291 		cptr = msg->msg.str;
292 	else
293 		cptr = dflt;
294 	return ((char *)cptr);
295 }
296 
297 int
298 catclose(catd)
299 	nl_catd catd;
300 {
301 	MCCatT  *cat = (MCCatT *)catd;
302 
303 	if (catd == NULL || catd == NLERR) {
304 		errno = EBADF;
305 		return (-1);
306 	}
307 #if 0
308 	if (cat->loadType != MCLoadAll)
309 #endif
310 		(void)fclose(cat->fp);
311 	__nls_free_resources(cat, cat->numSets);
312 	free(cat);
313 	return (0);
314 }
315 
316 /*
317  * Internal routines
318  */
319 
320 /* Note that only malloc failures are allowed to return an error */
321 static char     *_errowner = "Message Catalog System";
322 
323 #define CORRUPT() {                                            \
324 	(void)fclose(cat->fp);                                 \
325 	(void)fprintf(stderr, "%s: corrupt file.", _errowner); \
326 	free(cat);                                             \
327 	NLRETERR(EFTYPE);                                      \
328 }
329 
330 #define NOSPACE() {                                              \
331 	saverr = errno;                                          \
332 	(void)fclose(cat->fp);                                   \
333 	(void)fprintf(stderr, "%s: no more memory.", _errowner); \
334 	free(cat);                                               \
335 	errno = saverr;                                          \
336 	return (NLERR);                                          \
337 }
338 
339 static void
340 __nls_free_resources(cat, i)
341 	MCCatT  *cat;
342 	int     i;
343 {
344 	MCSetT  *set;
345 	int     j;
346 
347 	for (j = 0; j < i; j++) {
348 		set = cat->sets + j;
349 		if (!set->invalid) {
350 			free(set->data.str);
351 			free(set->u.msgs);
352 		}
353 	}
354 	free(cat->sets);
355 }
356 
357 static nl_catd
358 loadCat(catpath)
359 	__const char    *catpath;
360 {
361 	MCHeaderT       header;
362 	MCCatT          *cat;
363 	MCSetT          *set;
364 	long            i;
365 	off_t           nextSet;
366 	int             saverr;
367 
368 	if ((cat = (MCCatT *)malloc(sizeof(MCCatT))) == NULL)
369 		return (NLERR);
370 	cat->loadType = MCLoadBySet;
371 
372 	if ((cat->fp = fopen(catpath, "r")) == NULL) {
373 		saverr = errno;
374 		free(cat);
375 		errno = saverr;
376 		return (NLERR);
377 	}
378 	(void)_fcntl(fileno(cat->fp), F_SETFD, FD_CLOEXEC);
379 
380 	if (fread(&header, sizeof(header), 1, cat->fp) != 1 ||
381 	    strncmp(header.magic, MCMagic, MCMagicLen) != 0)
382 		CORRUPT();
383 
384 	if (header.majorVer != MCMajorVer) {
385 		(void)fclose(cat->fp);
386 		free(cat);
387 		(void)fprintf(stderr, "%s: %s is version %ld, we need %ld.\n",
388 		    _errowner, catpath, header.majorVer, MCMajorVer);
389 		NLRETERR(EFTYPE);
390 	}
391 	if (header.numSets <= 0) {
392 		(void)fclose(cat->fp);
393 		free(cat);
394 		(void)fprintf(stderr, "%s: %s has %ld sets!\n",
395 		    _errowner, catpath, header.numSets);
396 		NLRETERR(EFTYPE);
397 	}
398 
399 	cat->numSets = header.numSets;
400 	if ((cat->sets = (MCSetT *)malloc(sizeof(MCSetT) * header.numSets)) ==
401 	    NULL)
402 		NOSPACE();
403 
404 	nextSet = header.firstSet;
405 	for (i = 0; i < cat->numSets; ++i) {
406 		if (fseeko(cat->fp, nextSet, SEEK_SET) == -1) {
407 			__nls_free_resources(cat, i);
408 			CORRUPT();
409 		}
410 
411 		/* read in the set header */
412 		set = cat->sets + i;
413 		if (fread(set, sizeof(*set), 1, cat->fp) != 1) {
414 			__nls_free_resources(cat, i);
415 			CORRUPT();
416 		}
417 
418 		/* if it's invalid, skip over it (and backup 'i') */
419 		if (set->invalid) {
420 			--i;
421 			nextSet = set->nextSet;
422 			continue;
423 		}
424 #if 0
425 		if (cat->loadType == MCLoadAll) {
426 			int     res;
427 
428 			if ((res = loadSet(cat, set)) <= 0) {
429 				saverr = errno;
430 				__nls_free_resources(cat, i);
431 				errno = saverr;
432 				if (res < 0)
433 					NOSPACE();
434 				CORRUPT();
435 			}
436 		} else
437 #endif
438 			set->invalid = TRUE;
439 		nextSet = set->nextSet;
440 	}
441 #if 0
442 	if (cat->loadType == MCLoadAll) {
443 		(void)fclose(cat->fp);
444 		cat->fp = NULL;
445 	}
446 #endif
447 	return ((nl_catd) cat);
448 }
449 
450 static int
451 loadSet(cat, set)
452 	MCCatT  *cat;
453 	MCSetT  *set;
454 {
455 	MCMsgT  *msg;
456 	int     i;
457 	int     saverr;
458 
459 	/* Get the data */
460 	if (fseeko(cat->fp, set->data.off, SEEK_SET) == -1)
461 		return (0);
462 	if ((set->data.str = malloc(set->dataLen)) == NULL)
463 		return (-1);
464 	if (fread(set->data.str, set->dataLen, 1, cat->fp) != 1) {
465 		saverr = errno;
466 		free(set->data.str);
467 		errno = saverr;
468 		return (0);
469 	}
470 
471 	/* Get the messages */
472 	if (fseeko(cat->fp, set->u.firstMsg, SEEK_SET) == -1) {
473 		saverr = errno;
474 		free(set->data.str);
475 		errno = saverr;
476 		return (0);
477 	}
478 	if ((set->u.msgs = (MCMsgT *)malloc(sizeof(MCMsgT) * set->numMsgs)) ==
479 	    NULL) {
480 		saverr = errno;
481 		free(set->data.str);
482 		errno = saverr;
483 		return (-1);
484 	}
485 
486 	for (i = 0; i < set->numMsgs; ++i) {
487 		msg = set->u.msgs + i;
488 		if (fread(msg, sizeof(*msg), 1, cat->fp) != 1) {
489 			saverr = errno;
490 			free(set->u.msgs);
491 			free(set->data.str);
492 			errno = saverr;
493 			return (0);
494 		}
495 		if (msg->invalid) {
496 			--i;
497 			continue;
498 		}
499 		msg->msg.str = (char *)(set->data.str + msg->msg.off);
500 	}
501 	set->invalid = FALSE;
502 	return (1);
503 }
504