libc/nls: Sync with FreeBSD.
[dragonfly.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 $FreeBSD: head/lib/libc/nls/msgcat.c 304755 2016-08-24 16:44:27Z ache $
33 ******************************************************************/
34
35 #define _NLS_PRIVATE
36
37 #include "namespace.h"
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <sys/mman.h>
41 #include <sys/queue.h>
42
43 #include <arpa/inet.h>          /* for ntohl() */
44
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <limits.h>
48 #include <nl_types.h>
49 #include <pthread.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include "un-namespace.h"
55
56 #include "../locale/xlocale_private.h"
57
58 #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"
59
60 #define RLOCK(fail)     { int ret;                                              \
61                           if (__isthreaded &&                                   \
62                               ((ret = _pthread_rwlock_rdlock(&rwlock)) != 0)) { \
63                                   errno = ret;                                  \
64                                   return (fail);                                \
65                           }}
66 #define WLOCK(fail)     { int ret;                                              \
67                           if (__isthreaded &&                                   \
68                               ((ret = _pthread_rwlock_wrlock(&rwlock)) != 0)) { \
69                                   errno = ret;                                  \
70                                   return (fail);                                \
71                           }}
72 #define UNLOCK          { if (__isthreaded)                                     \
73                               _pthread_rwlock_unlock(&rwlock); }
74
75 #define NLERR           ((nl_catd) -1)
76 #define NLRETERR(errc)  { errno = errc; return (NLERR); }
77 #define SAVEFAIL(n, l, e)       { WLOCK(NLERR);                                 \
78                                   np = malloc(sizeof(struct catentry));         \
79                                   if (np != NULL) {                             \
80                                         np->name = strdup(n);                   \
81                                         np->path = NULL;                        \
82                                         np->catd = NLERR;                       \
83                                         np->refcount = 0;                       \
84                                         np->lang = (l == NULL) ? NULL :         \
85                                             strdup(l);                          \
86                                         np->caterrno = e;                       \
87                                         SLIST_INSERT_HEAD(&cache, np, list);    \
88                                   }                                             \
89                                   UNLOCK;                                       \
90                                   errno = e;                                    \
91                                 }
92
93 static nl_catd load_msgcat(const char *, const char *, const char *);
94
95 static pthread_rwlock_t          rwlock = PTHREAD_RWLOCK_INITIALIZER;
96
97 struct catentry {
98         SLIST_ENTRY(catentry)    list;
99         char                    *name;
100         char                    *path;
101         int                      caterrno;
102         nl_catd                  catd;
103         char                    *lang;
104         int                      refcount;
105 };
106
107 SLIST_HEAD(listhead, catentry) cache =
108     SLIST_HEAD_INITIALIZER(cache);
109
110 nl_catd
111 catopen(const char *name, int type)
112 {
113         struct stat sbuf;
114         struct catentry *np;
115         char *base, *cptr, *cptr1, *nlspath, *pathP, *pcode;
116         char *plang, *pter;
117         int saverr, spcleft;
118         const char *lang, *tmpptr;
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 = querylocale(LC_MESSAGES_MASK, __get_locale());
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 static void
327 catfree(struct catentry *np)
328 {
329
330         if (np->catd != NULL && np->catd != NLERR) {
331                 munmap(np->catd->__data, (size_t)np->catd->__size);
332                 free(np->catd);
333         }
334         SLIST_REMOVE(&cache, np, catentry, list);
335         free(np->name);
336         free(np->path);
337         free(np->lang);
338         free(np);
339 }
340
341 int
342 catclose(nl_catd catd)
343 {
344         struct catentry *np;
345
346         /* sanity checking */
347         if (catd == NULL || catd == NLERR) {
348                 errno = EBADF;
349                 return (-1);
350         }
351
352         /* Remove from cache if not referenced any more */
353         WLOCK(-1);
354         SLIST_FOREACH(np, &cache, list) {
355                 if (catd == np->catd) {
356                         np->refcount--;
357                         if (np->refcount == 0)
358                                 catfree(np);
359                         break;
360                 }
361         }
362         UNLOCK;
363         return (0);
364 }
365
366 /*
367  * Internal support functions
368  */
369
370 static nl_catd
371 load_msgcat(const char *path, const char *name, const char *lang)
372 {
373         struct stat st;
374         nl_catd catd;
375         struct catentry *np;
376         void *data;
377         int fd;
378
379         /* path/name will never be NULL here */
380
381         /*
382          * One more try in cache; if it was not found by name,
383          * it might still be found by absolute path.
384          */
385         RLOCK(NLERR);
386         SLIST_FOREACH(np, &cache, list) {
387                 if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
388                         np->refcount++;
389                         UNLOCK;
390                         return (np->catd);
391                 }
392         }
393         UNLOCK;
394
395         if ((fd = _open(path, O_RDONLY | O_CLOEXEC)) == -1) {
396                 SAVEFAIL(name, lang, errno);
397                 NLRETERR(errno);
398         }
399
400         if (_fstat(fd, &st) != 0) {
401                 _close(fd);
402                 SAVEFAIL(name, lang, EFTYPE);
403                 NLRETERR(EFTYPE);
404         }
405
406         /*
407          * If the file size cannot be held in size_t we cannot mmap()
408          * it to the memory.  Probably, this will not be a problem given
409          * that catalog files are usually small.
410          */
411         if (st.st_size > SIZE_T_MAX) {
412                 _close(fd);
413                 SAVEFAIL(name, lang, EFBIG);
414                 NLRETERR(EFBIG);
415         }
416
417         if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
418             MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
419                 int saved_errno = errno;
420                 _close(fd);
421                 SAVEFAIL(name, lang, saved_errno);
422                 NLRETERR(saved_errno);
423         }
424         _close(fd);
425
426         if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
427             _NLS_MAGIC) {
428                 munmap(data, (size_t)st.st_size);
429                 SAVEFAIL(name, lang, EFTYPE);
430                 NLRETERR(EFTYPE);
431         }
432
433         if ((catd = malloc(sizeof (*catd))) == NULL) {
434                 munmap(data, (size_t)st.st_size);
435                 SAVEFAIL(name, lang, ENOMEM);
436                 NLRETERR(ENOMEM);
437         }
438
439         catd->__data = data;
440         catd->__size = (int)st.st_size;
441
442         /* Caching opened catalog */
443         WLOCK(NLERR);
444         if ((np = malloc(sizeof(struct catentry))) != NULL) {
445                 np->name = strdup(name);
446                 np->path = strdup(path);
447                 np->catd = catd;
448                 np->lang = (lang == NULL) ? NULL : strdup(lang);
449                 np->refcount = 1;
450                 np->caterrno = 0;
451                 SLIST_INSERT_HEAD(&cache, np, list);
452         }
453         UNLOCK;
454         return (catd);
455 }