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