xref: /freebsd/lib/libc/nls/msgcat.c (revision 23f282aa31e9b6fceacd449020e936e98d6f2298)
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))
332     	CORRUPT();
333 
334     if (strncmp(header.magic, MCMagic, MCMagicLen) != 0) CORRUPT();
335 
336     if (header.majorVer != MCMajorVer) {
337 	free(cat);
338 	fprintf(stderr, "%s: %s is version %ld, we need %ld.\n", ERRNAME,
339 		catpath, header.majorVer, MCMajorVer);
340 	errno = EINVAL;
341 	return(NLERR);
342     }
343 
344     if (header.numSets <= 0) {
345 	free(cat);
346 	fprintf(stderr, "%s: %s has %ld sets!\n", ERRNAME, catpath,
347 		header.numSets);
348 	errno = EINVAL;
349 	return(NLERR);
350     }
351 
352     cat->numSets = header.numSets;
353     cat->sets = (MCSetT *) malloc(sizeof(MCSetT) * header.numSets);
354     if (!cat->sets) NOSPACE();
355 
356     nextSet = header.firstSet;
357     for (i = 0; i < cat->numSets; ++i) {
358 	if (lseek(cat->fd, nextSet, 0) == -1) {
359 		for (j = 0; j < i; j++) {
360 			set = cat->sets + j;
361 			if (!set->invalid) {
362 			    free(set->data.str);
363 			    free(set->u.msgs);
364 			}
365 		}
366 		free(cat->sets);
367 		CORRUPT();
368 	}
369 
370 	/* read in the set header */
371 	set = cat->sets + i;
372 	if (_read(cat->fd, set, sizeof(*set)) != sizeof(*set)) {
373 		for (j = 0; j < i; j++) {
374 			set = cat->sets + j;
375 			if (!set->invalid) {
376 			    free(set->data.str);
377 			    free(set->u.msgs);
378 			}
379 		}
380 		free(cat->sets);
381 		CORRUPT();
382 	}
383 
384 	/* if it's invalid, skip over it (and backup 'i') */
385 
386 	if (set->invalid) {
387 	    --i;
388 	    nextSet = set->nextSet;
389 	    continue;
390 	}
391 
392 	if (cat->loadType == MCLoadAll) {
393 	    int res;
394 
395 	    if ((res = loadSet(cat, set)) <= 0) {
396 		for (j = 0; j < i; j++) {
397 			set = cat->sets + j;
398 			if (!set->invalid) {
399 			    free(set->data.str);
400 			    free(set->u.msgs);
401 			}
402 		}
403 		free(cat->sets);
404 		if (res < 0) NOSPACE();
405 		CORRUPT();
406 	    }
407 	} else set->invalid = True;
408 	nextSet = set->nextSet;
409     }
410     if (cat->loadType == MCLoadAll) {
411 	_close(cat->fd);
412 	cat->fd = -1;
413     }
414     return((nl_catd) cat);
415 }
416 
417 static int loadSet(cat, set)
418 MCCatT *cat;
419 MCSetT *set;
420 {
421     MCMsgT	*msg;
422     int		i;
423 
424     /* Get the data */
425     if (lseek(cat->fd, set->data.off, 0) == -1) return(0);
426     if ((set->data.str = malloc(set->dataLen)) == NULL) return(-1);
427     if (_read(cat->fd, set->data.str, set->dataLen) != set->dataLen) {
428 	free(set->data.str); return(0);
429     }
430 
431     /* Get the messages */
432     if (lseek(cat->fd, set->u.firstMsg, 0) == -1) {
433 	free(set->data.str); return(0);
434     }
435     if ((set->u.msgs = (MCMsgT *) malloc(sizeof(MCMsgT) * set->numMsgs)) == NULL) {
436 	free(set->data.str); return(-1);
437     }
438 
439     for (i = 0; i < set->numMsgs; ++i) {
440 	msg = set->u.msgs + i;
441 	if (_read(cat->fd, msg, sizeof(*msg)) != sizeof(*msg)) {
442 	    free(set->u.msgs); free(set->data.str); return(0);
443 	}
444 	if (msg->invalid) {
445 	    --i;
446 	    continue;
447 	}
448 	msg->msg.str = (char *) (set->data.str + msg->msg.off);
449     }
450     set->invalid = False;
451     return(1);
452 }
453