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