xref: /freebsd/lib/libc/nls/msgcat.c (revision 2546665afcaf0d53dc2c7058fee96354b3680f5a)
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 || issetugid())
115 		nlspath = _DEFAULT_NLS_PATH;
116 
117 	if ((base = cptr = strdup(nlspath)) == NULL) {
118 		saverr = errno;
119 		free(plang);
120 		errno = saverr;
121 		return (NLERR);
122 	}
123 
124 	while ((nlspath = strsep(&cptr, ":")) != NULL) {
125 		pathP = path;
126 		if (*nlspath) {
127 			for (; *nlspath; ++nlspath) {
128 				if (*nlspath == '%') {
129 					switch (*(nlspath + 1)) {
130 					case 'l':
131 						tmpptr = plang;
132 						break;
133 					case 't':
134 						tmpptr = pter;
135 						break;
136 					case 'c':
137 						tmpptr = pcode;
138 						break;
139 					case 'L':
140 						tmpptr = lang;
141 						break;
142 					case 'N':
143 						tmpptr = (char *)name;
144 						break;
145 					case '%':
146 						++nlspath;
147 						/* fallthrough */
148 					default:
149 						if (pathP - path >=
150 						    sizeof(path) - 1)
151 							goto too_long;
152 						*(pathP++) = *nlspath;
153 						continue;
154 					}
155 					++nlspath;
156 			put_tmpptr:
157 					spcleft = sizeof(path) -
158 						  (pathP - path) - 1;
159 					if (strlcpy(pathP, tmpptr, spcleft) >=
160 					    spcleft) {
161 				too_long:
162 						free(plang);
163 						free(base);
164 						NLRETERR(ENAMETOOLONG);
165 					}
166 					pathP += strlen(tmpptr);
167 				} else {
168 					if (pathP - path >= sizeof(path) - 1)
169 						goto too_long;
170 					*(pathP++) = *nlspath;
171 				}
172 			}
173 			*pathP = '\0';
174 			if (stat(path, &sbuf) == 0) {
175 				free(plang);
176 				free(base);
177 				return (loadCat(path));
178 			}
179 		} else {
180 			tmpptr = (char *)name;
181 			--nlspath;
182 			goto put_tmpptr;
183 		}
184 	}
185 	free(plang);
186 	free(base);
187 	NLRETERR(ENOENT);
188 }
189 
190 /*
191  * We've got an odd situation here.  The odds are real good that the
192  * number we are looking for is almost the same as the index.  We could
193  * use the index, check the difference and do something intelligent, but
194  * I haven't quite figured out what's intelligent.
195  *
196  * Here's a start.
197  *	Take an id N.  If there are > N items in the list, then N cannot
198  *	be more than N items from the start, since otherwise there would
199  *	have to be duplicate items.  So we can safely set the top to N+1
200  *	(after taking into account that ids start at 1, and arrays at 0)
201  *
202  *	Let's say we are at position P, and we are looking for N, but have
203  *	V.  If N > V, then the furthest away that N could be is
204  *	P + (N-V).  So we can safely set hi to P+(N-V)+1.  For example:
205  *		We are looking for 10, but have 8
206  *		8	?	?	?	?
207  *			>=9	>=10	>=11
208  *
209  */
210 
211 #define LOOKUP(PARENT, CHILD, ID, NUM, SET) {                    \
212 	lo = 0;                                                  \
213 	if (ID - 1 < PARENT->NUM) {                              \
214 		cur = ID - 1;                                    \
215 		hi = ID;                                         \
216 	} else {                                                 \
217 		hi = PARENT->NUM;                                \
218 		cur = (hi - lo) / 2;                             \
219 	}                                                        \
220 	while (TRUE) {                                           \
221 		CHILD = PARENT->SET + cur;                       \
222 		if (CHILD->ID == ID)                             \
223 			break;                                   \
224 		if (CHILD->ID < ID) {                            \
225 			lo = cur + 1;                            \
226 			if (hi > cur + (ID - CHILD->ID) + 1)     \
227 				hi = cur + (ID - CHILD->ID) + 1; \
228 			dir = 1;                                 \
229 		} else {                                         \
230 			hi = cur;                                \
231 			dir = -1;                                \
232 		}                                                \
233 		if (lo >= hi)                                    \
234 			return (NULL);                           \
235 		if (hi - lo == 1)                                \
236 			cur += dir;                              \
237 		else                                             \
238 			cur += ((hi - lo) / 2) * dir;            \
239 	}                                                        \
240 }
241 
242 static MCSetT *
243 MCGetSet(cat, setId)
244 	MCCatT  *cat;
245 	int     setId;
246 {
247 	MCSetT  *set;
248 	long    lo, hi, cur, dir;
249 
250 	if (cat == NULL || setId <= 0)
251 		return (NULL);
252 	LOOKUP(cat, set, setId, numSets, sets);
253 	if (set->invalid && loadSet(cat, set) <= 0)
254 		return (NULL);
255 	return (set);
256 }
257 
258 static MCMsgT *
259 MCGetMsg(set, msgId)
260 	MCSetT  *set;
261 	int     msgId;
262 {
263 	MCMsgT  *msg;
264 	long    lo, hi, cur, dir;
265 
266 	if (set == NULL || set->invalid || msgId <= 0)
267 		return (NULL);
268 	LOOKUP(set, msg, msgId, numMsgs, u.msgs);
269 	return (msg);
270 }
271 
272 char *
273 catgets(catd, setId, msgId, dflt)
274 	nl_catd         catd;
275 	int             setId;
276 	int             msgId;
277 	__const char    *dflt;
278 {
279 	MCMsgT          *msg;
280 	MCCatT          *cat = (MCCatT *)catd;
281 	__const char    *cptr;
282 
283 	if (catd == NULL || catd == NLERR)
284 		return ((char *)dflt);
285 	msg = MCGetMsg(MCGetSet(cat, setId), msgId);
286 	if (msg != NULL)
287 		cptr = msg->msg.str;
288 	else
289 		cptr = dflt;
290 	return ((char *)cptr);
291 }
292 
293 int
294 catclose(catd)
295 	nl_catd catd;
296 {
297 	MCCatT  *cat = (MCCatT *)catd;
298 
299 	if (catd == NULL || catd == NLERR) {
300 		errno = EBADF;
301 		return (-1);
302 	}
303 #if 0
304 	if (cat->loadType != MCLoadAll)
305 #endif
306 		(void)fclose(cat->fp);
307 	__nls_free_resources(cat, cat->numSets);
308 	free(cat);
309 	return (0);
310 }
311 
312 /*
313  * Internal routines
314  */
315 
316 /* Note that only malloc failures are allowed to return an error */
317 static char     *_errowner = "Message Catalog System";
318 
319 #define CORRUPT() {                                            \
320 	(void)fclose(cat->fp);                                 \
321 	(void)fprintf(stderr, "%s: corrupt file.", _errowner); \
322 	free(cat);                                             \
323 	NLRETERR(EFTYPE);                                      \
324 }
325 
326 #define NOSPACE() {                                              \
327 	saverr = errno;                                          \
328 	(void)fclose(cat->fp);                                   \
329 	(void)fprintf(stderr, "%s: no more memory.", _errowner); \
330 	free(cat);                                               \
331 	errno = saverr;                                          \
332 	return (NLERR);                                          \
333 }
334 
335 static void
336 __nls_free_resources(cat, i)
337 	MCCatT  *cat;
338 	int     i;
339 {
340 	MCSetT  *set;
341 	int     j;
342 
343 	for (j = 0; j < i; j++) {
344 		set = cat->sets + j;
345 		if (!set->invalid) {
346 			free(set->data.str);
347 			free(set->u.msgs);
348 		}
349 	}
350 	free(cat->sets);
351 }
352 
353 static nl_catd
354 loadCat(catpath)
355 	__const char    *catpath;
356 {
357 	MCHeaderT       header;
358 	MCCatT          *cat;
359 	MCSetT          *set;
360 	long            i;
361 	off_t           nextSet;
362 	int             saverr;
363 
364 	if ((cat = (MCCatT *)malloc(sizeof(MCCatT))) == NULL)
365 		return (NLERR);
366 	cat->loadType = MCLoadBySet;
367 
368 	if ((cat->fp = fopen(catpath, "r")) == NULL) {
369 		saverr = errno;
370 		free(cat);
371 		errno = saverr;
372 		return (NLERR);
373 	}
374 	(void)_fcntl(fileno(cat->fp), F_SETFD, FD_CLOEXEC);
375 
376 	if (fread(&header, sizeof(header), 1, cat->fp) != 1 ||
377 	    strncmp(header.magic, MCMagic, MCMagicLen) != 0)
378 		CORRUPT();
379 
380 	if (header.majorVer != MCMajorVer) {
381 		(void)fclose(cat->fp);
382 		free(cat);
383 		(void)fprintf(stderr, "%s: %s is version %ld, we need %ld.\n",
384 		    _errowner, catpath, header.majorVer, MCMajorVer);
385 		NLRETERR(EFTYPE);
386 	}
387 	if (header.numSets <= 0) {
388 		(void)fclose(cat->fp);
389 		free(cat);
390 		(void)fprintf(stderr, "%s: %s has %ld sets!\n",
391 		    _errowner, catpath, header.numSets);
392 		NLRETERR(EFTYPE);
393 	}
394 
395 	cat->numSets = header.numSets;
396 	if ((cat->sets = (MCSetT *)malloc(sizeof(MCSetT) * header.numSets)) ==
397 	    NULL)
398 		NOSPACE();
399 
400 	nextSet = 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 = set->nextSet;
418 			continue;
419 		}
420 #if 0
421 		if (cat->loadType == MCLoadAll) {
422 			int     res;
423 
424 			if ((res = loadSet(cat, set)) <= 0) {
425 				saverr = errno;
426 				__nls_free_resources(cat, i);
427 				errno = saverr;
428 				if (res < 0)
429 					NOSPACE();
430 				CORRUPT();
431 			}
432 		} else
433 #endif
434 			set->invalid = TRUE;
435 		nextSet = set->nextSet;
436 	}
437 #if 0
438 	if (cat->loadType == MCLoadAll) {
439 		(void)fclose(cat->fp);
440 		cat->fp = NULL;
441 	}
442 #endif
443 	return ((nl_catd) cat);
444 }
445 
446 static int
447 loadSet(cat, set)
448 	MCCatT  *cat;
449 	MCSetT  *set;
450 {
451 	MCMsgT  *msg;
452 	int     i;
453 	int     saverr;
454 
455 	/* Get the data */
456 	if (fseeko(cat->fp, set->data.off, SEEK_SET) == -1)
457 		return (0);
458 	if ((set->data.str = malloc(set->dataLen)) == NULL)
459 		return (-1);
460 	if (fread(set->data.str, set->dataLen, 1, cat->fp) != 1) {
461 		saverr = errno;
462 		free(set->data.str);
463 		errno = saverr;
464 		return (0);
465 	}
466 
467 	/* Get the messages */
468 	if (fseeko(cat->fp, set->u.firstMsg, SEEK_SET) == -1) {
469 		saverr = errno;
470 		free(set->data.str);
471 		errno = saverr;
472 		return (0);
473 	}
474 	if ((set->u.msgs = (MCMsgT *)malloc(sizeof(MCMsgT) * set->numMsgs)) ==
475 	    NULL) {
476 		saverr = errno;
477 		free(set->data.str);
478 		errno = saverr;
479 		return (-1);
480 	}
481 
482 	for (i = 0; i < set->numMsgs; ++i) {
483 		msg = set->u.msgs + i;
484 		if (fread(msg, sizeof(*msg), 1, cat->fp) != 1) {
485 			saverr = errno;
486 			free(set->u.msgs);
487 			free(set->data.str);
488 			errno = saverr;
489 			return (0);
490 		}
491 		if (msg->invalid) {
492 			--i;
493 			continue;
494 		}
495 		msg->msg.str = (char *)(set->data.str + msg->msg.off);
496 	}
497 	set->invalid = FALSE;
498 	return (1);
499 }
500