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