xref: /freebsd/lib/libc/nls/msgcat.c (revision a3e8fd0b7f663db7eafff527d5c3ca3bcfa8a537)
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 #include <sys/syslimits.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <locale.h>
48 #include <nl_types.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include "un-namespace.h"
54 
55 #include "msgcat.h"
56 #include "../locale/setlocale.h"        /* for ENCODING_LEN */
57 
58 #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"
59 
60 #define	TRUE	1
61 #define	FALSE	0
62 
63 #define	NLERR		((nl_catd) -1)
64 #define NLRETERR(errc)  { errno = errc; return (NLERR); }
65 
66 static nl_catd  loadCat();
67 static int      loadSet();
68 static void     __nls_free_resources();
69 
70 nl_catd
71 catopen(name, type)
72 	__const char    *name;
73 	int             type;
74 {
75 	int             spcleft, saverr;
76 	char            path[PATH_MAX];
77 	char            *nlspath, *lang, *base, *cptr, *pathP, *tmpptr;
78 	char            *cptr1, *plang, *pter, *pcode;
79 	struct stat     sbuf;
80 
81 	if (name == NULL || *name == '\0')
82 		NLRETERR(EINVAL);
83 
84 	/* is it absolute path ? if yes, load immediately */
85 	if (strchr(name, '/') != NULL)
86 		return (loadCat(name));
87 
88 	if (type == NL_CAT_LOCALE)
89 		lang = setlocale(LC_MESSAGES, NULL);
90 	else
91 		lang = getenv("LANG");
92 
93 	if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
94 	    (lang[0] == '.' &&
95 	     (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
96 	    strchr(lang, '/') != NULL)
97 		lang = "C";
98 
99 	if ((plang = cptr1 = strdup(lang)) == NULL)
100 		return (NLERR);
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 		saverr = errno;
122 		free(plang);
123 		errno = saverr;
124 		return (NLERR);
125 	}
126 
127 	while ((nlspath = strsep(&cptr, ":")) != NULL) {
128 		pathP = path;
129 		if (*nlspath) {
130 			for (; *nlspath; ++nlspath) {
131 				if (*nlspath == '%') {
132 					switch (*(nlspath + 1)) {
133 					case 'l':
134 						tmpptr = plang;
135 						break;
136 					case 't':
137 						tmpptr = pter;
138 						break;
139 					case 'c':
140 						tmpptr = pcode;
141 						break;
142 					case 'L':
143 						tmpptr = lang;
144 						break;
145 					case 'N':
146 						tmpptr = (char *)name;
147 						break;
148 					case '%':
149 						++nlspath;
150 						/* fallthrough */
151 					default:
152 						if (pathP - path >=
153 						    sizeof(path) - 1)
154 							goto too_long;
155 						*(pathP++) = *nlspath;
156 						continue;
157 					}
158 					++nlspath;
159 			put_tmpptr:
160 					spcleft = sizeof(path) -
161 						  (pathP - path) - 1;
162 					if (strlcpy(pathP, tmpptr, spcleft) >=
163 					    spcleft) {
164 				too_long:
165 						free(plang);
166 						free(base);
167 						NLRETERR(ENAMETOOLONG);
168 					}
169 					pathP += strlen(tmpptr);
170 				} else {
171 					if (pathP - path >= sizeof(path) - 1)
172 						goto too_long;
173 					*(pathP++) = *nlspath;
174 				}
175 			}
176 			*pathP = '\0';
177 			if (stat(path, &sbuf) == 0) {
178 				free(plang);
179 				free(base);
180 				return (loadCat(path));
181 			}
182 		} else {
183 			tmpptr = (char *)name;
184 			--nlspath;
185 			goto put_tmpptr;
186 		}
187 	}
188 	free(plang);
189 	free(base);
190 	NLRETERR(ENOENT);
191 }
192 
193 /*
194  * We've got an odd situation here.  The odds are real good that the
195  * number we are looking for is almost the same as the index.  We could
196  * use the index, check the difference and do something intelligent, but
197  * I haven't quite figured out what's intelligent.
198  *
199  * Here's a start.
200  *	Take an id N.  If there are > N items in the list, then N cannot
201  *	be more than N items from the start, since otherwise there would
202  *	have to be duplicate items.  So we can safely set the top to N+1
203  *	(after taking into account that ids start at 1, and arrays at 0)
204  *
205  *	Let's say we are at position P, and we are looking for N, but have
206  *	V.  If N > V, then the furthest away that N could be is
207  *	P + (N-V).  So we can safely set hi to P+(N-V)+1.  For example:
208  *		We are looking for 10, but have 8
209  *		8	?	?	?	?
210  *			>=9	>=10	>=11
211  *
212  */
213 
214 #define LOOKUP(PARENT, CHILD, ID, NUM, SET) {                    \
215 	lo = 0;                                                  \
216 	if (ID - 1 < PARENT->NUM) {                              \
217 		cur = ID - 1;                                    \
218 		hi = ID;                                         \
219 	} else {                                                 \
220 		hi = PARENT->NUM;                                \
221 		cur = (hi - lo) / 2;                             \
222 	}                                                        \
223 	while (TRUE) {                                           \
224 		CHILD = PARENT->SET + cur;                       \
225 		if (CHILD->ID == ID)                             \
226 			break;                                   \
227 		if (CHILD->ID < ID) {                            \
228 			lo = cur + 1;                            \
229 			if (hi > cur + (ID - CHILD->ID) + 1)     \
230 				hi = cur + (ID - CHILD->ID) + 1; \
231 			dir = 1;                                 \
232 		} else {                                         \
233 			hi = cur;                                \
234 			dir = -1;                                \
235 		}                                                \
236 		if (lo >= hi)                                    \
237 			return (NULL);                           \
238 		if (hi - lo == 1)                                \
239 			cur += dir;                              \
240 		else                                             \
241 			cur += ((hi - lo) / 2) * dir;            \
242 	}                                                        \
243 }
244 
245 static MCSetT *
246 MCGetSet(cat, setId)
247 	MCCatT  *cat;
248 	int     setId;
249 {
250 	MCSetT  *set;
251 	long    lo, hi, cur, dir;
252 
253 	if (cat == NULL || setId <= 0)
254 		return (NULL);
255 	LOOKUP(cat, set, setId, numSets, sets);
256 	if (set->invalid && loadSet(cat, set) <= 0)
257 		return (NULL);
258 	return (set);
259 }
260 
261 static MCMsgT *
262 MCGetMsg(set, msgId)
263 	MCSetT  *set;
264 	int     msgId;
265 {
266 	MCMsgT  *msg;
267 	long    lo, hi, cur, dir;
268 
269 	if (set == NULL || set->invalid || msgId <= 0)
270 		return (NULL);
271 	LOOKUP(set, msg, msgId, numMsgs, u.msgs);
272 	return (msg);
273 }
274 
275 char *
276 catgets(catd, setId, msgId, dflt)
277 	nl_catd         catd;
278 	int             setId;
279 	int             msgId;
280 	__const char    *dflt;
281 {
282 	MCMsgT          *msg;
283 	MCCatT          *cat = (MCCatT *)catd;
284 	__const char    *cptr;
285 
286 	if (catd == NULL || catd == NLERR)
287 		return ((char *)dflt);
288 	msg = MCGetMsg(MCGetSet(cat, setId), msgId);
289 	if (msg != NULL)
290 		cptr = msg->msg.str;
291 	else
292 		cptr = dflt;
293 	return ((char *)cptr);
294 }
295 
296 int
297 catclose(catd)
298 	nl_catd catd;
299 {
300 	MCCatT  *cat = (MCCatT *)catd;
301 
302 	if (catd == NULL || catd == NLERR) {
303 		errno = EBADF;
304 		return (-1);
305 	}
306 #if 0
307 	if (cat->loadType != MCLoadAll)
308 #endif
309 		(void)fclose(cat->fp);
310 	__nls_free_resources(cat, cat->numSets);
311 	free(cat);
312 	return (0);
313 }
314 
315 /*
316  * Internal routines
317  */
318 
319 /* Note that only malloc failures are allowed to return an error */
320 static char     *_errowner = "Message Catalog System";
321 
322 #define CORRUPT() {                                            \
323 	(void)fclose(cat->fp);                                 \
324 	(void)fprintf(stderr, "%s: corrupt file.", _errowner); \
325 	free(cat);                                             \
326 	NLRETERR(EFTYPE);                                      \
327 }
328 
329 #define NOSPACE() {                                              \
330 	saverr = errno;                                          \
331 	(void)fclose(cat->fp);                                   \
332 	(void)fprintf(stderr, "%s: no more memory.", _errowner); \
333 	free(cat);                                               \
334 	errno = saverr;                                          \
335 	return (NLERR);                                          \
336 }
337 
338 static void
339 __nls_free_resources(cat, i)
340 	MCCatT  *cat;
341 	int     i;
342 {
343 	MCSetT  *set;
344 	int     j;
345 
346 	for (j = 0; j < i; j++) {
347 		set = cat->sets + j;
348 		if (!set->invalid) {
349 			free(set->data.str);
350 			free(set->u.msgs);
351 		}
352 	}
353 	free(cat->sets);
354 }
355 
356 static nl_catd
357 loadCat(catpath)
358 	__const char    *catpath;
359 {
360 	MCHeaderT       header;
361 	MCCatT          *cat;
362 	MCSetT          *set;
363 	long            i;
364 	off_t           nextSet;
365 	int             saverr;
366 
367 	if ((cat = (MCCatT *)malloc(sizeof(MCCatT))) == NULL)
368 		return (NLERR);
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 				saverr = errno;
429 				__nls_free_resources(cat, i);
430 				errno = saverr;
431 				if (res < 0)
432 					NOSPACE();
433 				CORRUPT();
434 			}
435 		} else
436 #endif
437 			set->invalid = TRUE;
438 		nextSet = set->nextSet;
439 	}
440 #if 0
441 	if (cat->loadType == MCLoadAll) {
442 		(void)fclose(cat->fp);
443 		cat->fp = NULL;
444 	}
445 #endif
446 	return ((nl_catd) cat);
447 }
448 
449 static int
450 loadSet(cat, set)
451 	MCCatT  *cat;
452 	MCSetT  *set;
453 {
454 	MCMsgT  *msg;
455 	int     i;
456 	int     saverr;
457 
458 	/* Get the data */
459 	if (fseeko(cat->fp, set->data.off, SEEK_SET) == -1)
460 		return (0);
461 	if ((set->data.str = malloc(set->dataLen)) == NULL)
462 		return (-1);
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 		saverr = errno;
480 		free(set->data.str);
481 		errno = saverr;
482 		return (-1);
483 	}
484 
485 	for (i = 0; i < set->numMsgs; ++i) {
486 		msg = set->u.msgs + i;
487 		if (fread(msg, sizeof(*msg), 1, cat->fp) != 1) {
488 			saverr = errno;
489 			free(set->u.msgs);
490 			free(set->data.str);
491 			errno = saverr;
492 			return (0);
493 		}
494 		if (msg->invalid) {
495 			--i;
496 			continue;
497 		}
498 		msg->msg.str = (char *)(set->data.str + msg->msg.off);
499 	}
500 	set->invalid = FALSE;
501 	return (1);
502 }
503