]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - lib/libc/nls/msgcat.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / lib / libc / nls / msgcat.c
1 /***********************************************************
2 Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
3 Copyright 2010, Gabor Kovesdan <gabor@FreeBSD.org>
4
5                         All Rights Reserved
6
7 Permission to use, copy, modify, and distribute this software and its
8 documentation for any purpose and without fee is hereby granted,
9 provided that the above copyright notice appear in all copies and that
10 both that copyright notice and this permission notice appear in
11 supporting documentation, and that Alfalfa's name not be used in
12 advertising or publicity pertaining to distribution of the software
13 without specific, written prior permission.
14
15 ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
16 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
17 ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
18 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
19 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
20 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
21 SOFTWARE.
22
23 If you make any modifications, bugfixes or other changes to this software
24 we'd appreciate it if you could send a copy to us so we can keep things
25 up-to-date.  Many thanks.
26                                 Kee Hinckley
27                                 Alfalfa Software, Inc.
28                                 267 Allston St., #3
29                                 Cambridge, MA 02139  USA
30                                 nazgul@alfalfa.com
31
32 ******************************************************************/
33
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
36
37 #define _NLS_PRIVATE
38
39 #include "namespace.h"
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <sys/mman.h>
43 #include <sys/queue.h>
44
45 #include <arpa/inet.h>          /* for ntohl() */
46
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <limits.h>
50 #include <locale.h>
51 #include <nl_types.h>
52 #include <pthread.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include "un-namespace.h"
58
59 #include "../locale/setlocale.h"        /* for ENCODING_LEN */
60
61 #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"
62
63 #define RLOCK(fail)     { int ret;                                              \
64                           if (__isthreaded &&                                   \
65                               ((ret = _pthread_rwlock_rdlock(&rwlock)) != 0)) { \
66                                   errno = ret;                                  \
67                                   return (fail);                                \
68                           }}
69 #define WLOCK(fail)     { int ret;                                              \
70                           if (__isthreaded &&                                   \
71                               ((ret = _pthread_rwlock_wrlock(&rwlock)) != 0)) { \
72                                   errno = ret;                                  \
73                                   return (fail);                                \
74                           }}
75 #define UNLOCK          { if (__isthreaded)                                     \
76                               _pthread_rwlock_unlock(&rwlock); }
77
78 #define NLERR           ((nl_catd) -1)
79 #define NLRETERR(errc)  { errno = errc; return (NLERR); }
80 #define SAVEFAIL(n, l, e)       { WLOCK(NLERR);                                 \
81                                   np = malloc(sizeof(struct catentry));         \
82                                   if (np != NULL) {                             \
83                                         np->name = strdup(n);                   \
84                                         np->path = NULL;                        \
85                                         np->lang = (l == NULL) ? NULL :         \
86                                             strdup(l);                          \
87                                         np->caterrno = e;                       \
88                                         SLIST_INSERT_HEAD(&cache, np, list);    \
89                                   }                                             \
90                                   UNLOCK;                                       \
91                                   errno = e;                                    \
92                                 }
93
94 static nl_catd load_msgcat(const char *, const char *, const char *);
95
96 static pthread_rwlock_t          rwlock = PTHREAD_RWLOCK_INITIALIZER;
97
98 struct catentry {
99         SLIST_ENTRY(catentry)    list;
100         char                    *name;
101         char                    *path;
102         int                      caterrno;
103         nl_catd                  catd;
104         char                    *lang;
105         int                      refcount;
106 };
107
108 SLIST_HEAD(listhead, catentry) cache =
109     SLIST_HEAD_INITIALIZER(cache);
110
111 nl_catd
112 catopen(const char *name, int type)
113 {
114         struct stat sbuf;
115         struct catentry *np;
116         char *base, *cptr, *cptr1, *lang, *nlspath, *pathP, *pcode;
117         char *plang, *pter, *tmpptr;
118         int saverr, spcleft;
119         char path[PATH_MAX];
120
121         /* sanity checking */
122         if (name == NULL || *name == '\0')
123                 NLRETERR(EINVAL);
124
125         if (strchr(name, '/') != NULL)
126                 /* have a pathname */
127                 lang = NULL;
128         else {
129                 if (type == NL_CAT_LOCALE)
130                         lang = setlocale(LC_MESSAGES, NULL);
131                 else
132                         lang = getenv("LANG");
133
134                 if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
135                     (lang[0] == '.' &&
136                     (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
137                     strchr(lang, '/') != NULL)
138                         lang = "C";
139         }
140
141         /* Try to get it from the cache first */
142         RLOCK(NLERR);
143         SLIST_FOREACH(np, &cache, list) {
144                 if ((strcmp(np->name, name) == 0) &&
145                     ((lang != NULL && np->lang != NULL &&
146                     strcmp(np->lang, lang) == 0) || (np->lang == lang))) {
147                         if (np->caterrno != 0) {
148                                 /* Found cached failing entry */
149                                 UNLOCK;
150                                 NLRETERR(np->caterrno);
151                         } else {
152                                 /* Found cached successful entry */
153                                 np->refcount++;
154                                 UNLOCK;
155                                 return (np->catd);
156                         }
157                 }
158         }
159         UNLOCK;
160
161         /* is it absolute path ? if yes, load immediately */
162         if (strchr(name, '/') != NULL)
163                 return (load_msgcat(name, name, lang));
164
165         /* sanity checking */
166         if ((plang = cptr1 = strdup(lang)) == NULL)
167                 return (NLERR);
168         if ((cptr = strchr(cptr1, '@')) != NULL)
169                 *cptr = '\0';
170         pter = pcode = "";
171         if ((cptr = strchr(cptr1, '_')) != NULL) {
172                 *cptr++ = '\0';
173                 pter = cptr1 = cptr;
174         }
175         if ((cptr = strchr(cptr1, '.')) != NULL) {
176                 *cptr++ = '\0';
177                 pcode = cptr;
178         }
179
180         if ((nlspath = getenv("NLSPATH")) == NULL || issetugid())
181                 nlspath = _DEFAULT_NLS_PATH;
182
183         if ((base = cptr = strdup(nlspath)) == NULL) {
184                 saverr = errno;
185                 free(plang);
186                 errno = saverr;
187                 return (NLERR);
188         }
189
190         while ((nlspath = strsep(&cptr, ":")) != NULL) {
191                 pathP = path;
192                 if (*nlspath) {
193                         for (; *nlspath; ++nlspath) {
194                                 if (*nlspath == '%') {
195                                         switch (*(nlspath + 1)) {
196                                         case 'l':
197                                                 tmpptr = plang;
198                                                 break;
199                                         case 't':
200                                                 tmpptr = pter;
201                                                 break;
202                                         case 'c':
203                                                 tmpptr = pcode;
204                                                 break;
205                                         case 'L':
206                                                 tmpptr = lang;
207                                                 break;
208                                         case 'N':
209                                                 tmpptr = (char *)name;
210                                                 break;
211                                         case '%':
212                                                 ++nlspath;
213                                                 /* FALLTHROUGH */
214                                         default:
215                                                 if (pathP - path >=
216                                                     sizeof(path) - 1)
217                                                         goto too_long;
218                                                 *(pathP++) = *nlspath;
219                                                 continue;
220                                         }
221                                         ++nlspath;
222                         put_tmpptr:
223                                         spcleft = sizeof(path) -
224                                                   (pathP - path) - 1;
225                                         if (strlcpy(pathP, tmpptr, spcleft) >=
226                                             spcleft) {
227                         too_long:
228                                                 free(plang);
229                                                 free(base);
230                                                 SAVEFAIL(name, lang, ENAMETOOLONG);
231                                                 NLRETERR(ENAMETOOLONG);
232                                         }
233                                         pathP += strlen(tmpptr);
234                                 } else {
235                                         if (pathP - path >= sizeof(path) - 1)
236                                                 goto too_long;
237                                         *(pathP++) = *nlspath;
238                                 }
239                         }
240                         *pathP = '\0';
241                         if (stat(path, &sbuf) == 0) {
242                                 free(plang);
243                                 free(base);
244                                 return (load_msgcat(path, name, lang));
245                         }
246                 } else {
247                         tmpptr = (char *)name;
248                         --nlspath;
249                         goto put_tmpptr;
250                 }
251         }
252         free(plang);
253         free(base);
254         SAVEFAIL(name, lang, ENOENT);
255         NLRETERR(ENOENT);
256 }
257
258 char *
259 catgets(nl_catd catd, int set_id, int msg_id, const char *s)
260 {
261         struct _nls_cat_hdr *cat_hdr;
262         struct _nls_msg_hdr *msg_hdr;
263         struct _nls_set_hdr *set_hdr;
264         int i, l, r, u;
265
266         if (catd == NULL || catd == NLERR) {
267                 errno = EBADF;
268                 /* LINTED interface problem */
269                 return ((char *)s);
270         }
271
272         cat_hdr = (struct _nls_cat_hdr *)catd->__data;
273         set_hdr = (struct _nls_set_hdr *)(void *)((char *)catd->__data +
274             sizeof(struct _nls_cat_hdr));
275
276         /* binary search, see knuth algorithm b */
277         l = 0;
278         u = ntohl((u_int32_t)cat_hdr->__nsets) - 1;
279         while (l <= u) {
280                 i = (l + u) / 2;
281                 r = set_id - ntohl((u_int32_t)set_hdr[i].__setno);
282
283                 if (r == 0) {
284                         msg_hdr = (struct _nls_msg_hdr *)
285                             (void *)((char *)catd->__data +
286                             sizeof(struct _nls_cat_hdr) +
287                             ntohl((u_int32_t)cat_hdr->__msg_hdr_offset));
288
289                         l = ntohl((u_int32_t)set_hdr[i].__index);
290                         u = l + ntohl((u_int32_t)set_hdr[i].__nmsgs) - 1;
291                         while (l <= u) {
292                                 i = (l + u) / 2;
293                                 r = msg_id -
294                                     ntohl((u_int32_t)msg_hdr[i].__msgno);
295                                 if (r == 0) {
296                                         return ((char *) catd->__data +
297                                             sizeof(struct _nls_cat_hdr) +
298                                             ntohl((u_int32_t)
299                                             cat_hdr->__msg_txt_offset) +
300                                             ntohl((u_int32_t)
301                                             msg_hdr[i].__offset));
302                                 } else if (r < 0) {
303                                         u = i - 1;
304                                 } else {
305                                         l = i + 1;
306                                 }
307                         }
308
309                         /* not found */
310                         goto notfound;
311
312                 } else if (r < 0) {
313                         u = i - 1;
314                 } else {
315                         l = i + 1;
316                 }
317         }
318
319 notfound:
320         /* not found */
321         errno = ENOMSG;
322         /* LINTED interface problem */
323         return ((char *)s);
324 }
325
326 int
327 catclose(nl_catd catd)
328 {
329         struct catentry *np;
330
331         /* sanity checking */
332         if (catd == NULL || catd == NLERR) {
333                 errno = EBADF;
334                 return (-1);
335         }
336
337         /* Remove from cache if not referenced any more */
338         WLOCK(-1);
339         SLIST_FOREACH(np, &cache, list) {
340                 if (catd == np->catd) {
341                         np->refcount--;
342                         if (np->refcount == 0) {
343                                 munmap(catd->__data, (size_t)catd->__size);
344                                 free(catd);
345                                 SLIST_REMOVE(&cache, np, catentry, list);
346                                 free(np->name);
347                                 free(np->path);
348                                 free(np->lang);
349                                 free(np);
350                         }
351                         break;
352                 }
353         }
354         UNLOCK;
355         return (0);
356 }
357
358 /*
359  * Internal support functions
360  */
361
362 static nl_catd
363 load_msgcat(const char *path, const char *name, const char *lang)
364 {
365         struct stat st;
366         nl_catd catd;
367         struct catentry *np;
368         void *data;
369         int fd;
370
371         /* path/name will never be NULL here */
372
373         /*
374          * One more try in cache; if it was not found by name,
375          * it might still be found by absolute path.
376          */
377         RLOCK(NLERR);
378         SLIST_FOREACH(np, &cache, list) {
379                 if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
380                         np->refcount++;
381                         UNLOCK;
382                         return (np->catd);
383                 }
384         }
385         UNLOCK;
386
387         if ((fd = _open(path, O_RDONLY)) == -1) {
388                 SAVEFAIL(name, lang, errno);
389                 NLRETERR(errno);
390         }
391
392         if (_fstat(fd, &st) != 0) {
393                 _close(fd);
394                 SAVEFAIL(name, lang, EFTYPE);
395                 NLRETERR(EFTYPE);
396         }
397
398         /*
399          * If the file size cannot be held in size_t we cannot mmap()
400          * it to the memory.  Probably, this will not be a problem given
401          * that catalog files are usually small.
402          */
403         if (st.st_size > SIZE_T_MAX) {
404                 _close(fd);
405                 SAVEFAIL(name, lang, EFBIG);
406                 NLRETERR(EFBIG);
407         }
408
409         if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
410             MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
411                 int saved_errno = errno;
412                 _close(fd);
413                 SAVEFAIL(name, lang, saved_errno);
414                 NLRETERR(saved_errno);
415         }
416         _close(fd);
417
418         if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
419             _NLS_MAGIC) {
420                 munmap(data, (size_t)st.st_size);
421                 SAVEFAIL(name, lang, EFTYPE);
422                 NLRETERR(EFTYPE);
423         }
424
425         if ((catd = malloc(sizeof (*catd))) == NULL) {
426                 munmap(data, (size_t)st.st_size);
427                 SAVEFAIL(name, lang, ENOMEM);
428                 NLRETERR(ENOMEM);
429         }
430
431         catd->__data = data;
432         catd->__size = (int)st.st_size;
433
434         /* Caching opened catalog */
435         WLOCK(NLERR);
436         if ((np = malloc(sizeof(struct catentry))) != NULL) {
437                 np->name = strdup(name);
438                 np->path = strdup(path);
439                 np->catd = catd;
440                 np->lang = (lang == NULL) ? NULL : strdup(lang);
441                 np->refcount = 1;
442                 np->caterrno = 0;
443                 SLIST_INSERT_HEAD(&cache, np, list);
444         }
445         UNLOCK;
446         return (catd);
447 }