xref: /freebsd/lib/libc/nls/msgcat.c (revision 6af83ee0d2941d18880b6aaa2b4facd1d30c6106)
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(__const char *);
68 static int      loadSet(MCCatT *, MCSetT *);
69 static void     __nls_free_resources(MCCatT *, int);
70 
71 nl_catd
72 catopen(__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 
240 static MCSetT *
241 MCGetSet(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 
254 static MCMsgT *
255 MCGetMsg(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 
266 char *
267 catgets(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 
283 int
284 catclose(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 */
304 static 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 
322 static 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 
338 static nl_catd
339 loadCat(__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 
410 static int
411 loadSet(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