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