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