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