locales, libconv: Sync with FreeBSD (extensive reach)
[dragonfly.git] / lib / libc / citrus / citrus_iconv.c
1 /* $FreeBSD: head/lib/libc/iconv/citrus_iconv.c 254080 2013-08-08 01:53:27Z peter $ */
2 /* $NetBSD: citrus_iconv.c,v 1.7 2008/07/25 14:05:25 christos Exp $ */
3
4 /*-
5  * Copyright (c)2003 Citrus Project,
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <sys/cdefs.h>
31 #include <sys/types.h>
32 #include <sys/queue.h>
33
34 #include <assert.h>
35 #include <dirent.h>
36 #include <errno.h>
37 #include <iconv.h>
38 #include <langinfo.h>
39 #include <limits.h>
40 #include <paths.h>
41 #include <stdbool.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46
47 #include "citrus_namespace.h"
48 #include "citrus_bcs.h"
49 #include "citrus_esdb.h"
50 #include "citrus_region.h"
51 #include "citrus_memstream.h"
52 #include "citrus_mmap.h"
53 #include "citrus_module.h"
54 #include "citrus_lock.h"
55 #include "citrus_lookup.h"
56 #include "citrus_hash.h"
57 #include "citrus_iconv.h"
58
59 #define _CITRUS_ICONV_DIR       "iconv.dir"
60 #define _CITRUS_ICONV_ALIAS     "iconv.alias"
61
62 #define CI_HASH_SIZE 101
63 #define CI_INITIAL_MAX_REUSE    5
64 #define CI_ENV_MAX_REUSE        "ICONV_MAX_REUSE"
65
66 static bool                      isinit = false;
67 static int                       shared_max_reuse, shared_num_unused;
68 static _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool;
69 static TAILQ_HEAD(, _citrus_iconv_shared) shared_unused;
70
71 static pthread_rwlock_t          ci_lock = PTHREAD_RWLOCK_INITIALIZER;
72
73 static __inline void
74 init_cache(void)
75 {
76
77         WLOCK(&ci_lock);
78         if (!isinit) {
79                 _CITRUS_HASH_INIT(&shared_pool, CI_HASH_SIZE);
80                 TAILQ_INIT(&shared_unused);
81                 shared_max_reuse = -1;
82                 if (!issetugid() && getenv(CI_ENV_MAX_REUSE))
83                         shared_max_reuse = atoi(getenv(CI_ENV_MAX_REUSE));
84                 if (shared_max_reuse < 0)
85                         shared_max_reuse = CI_INITIAL_MAX_REUSE;
86                 isinit = true;
87         }
88         UNLOCK(&ci_lock);
89 }
90
91 static __inline void
92 close_shared(struct _citrus_iconv_shared *ci)
93 {
94
95         if (ci) {
96                 if (ci->ci_module) {
97                         if (ci->ci_ops) {
98                                 if (ci->ci_closure)
99                                         (*ci->ci_ops->io_uninit_shared)(ci);
100                                 free(ci->ci_ops);
101                         }
102                         _citrus_unload_module(ci->ci_module);
103                 }
104                 free(ci);
105         }
106 }
107
108 static __inline int
109 open_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
110     const char * __restrict convname, const char * __restrict src,
111     const char * __restrict dst)
112 {
113         struct _citrus_iconv_shared *ci;
114         _citrus_iconv_getops_t getops;
115         const char *module;
116         size_t len_convname;
117         int ret;
118
119 #ifdef INCOMPATIBLE_WITH_GNU_ICONV
120         /*
121          * Sadly, the gnu tools expect iconv to actually parse the
122          * byte stream and don't allow for a pass-through when
123          * the (src,dest) encodings are the same.
124          * See gettext-0.18.3+ NEWS:
125          *   msgfmt now checks PO file headers more strictly with less
126          *   false-positives.
127          * NetBSD don't do this either.
128          */
129         module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none";
130 #else
131         module = "iconv_std";
132 #endif
133
134         /* initialize iconv handle */
135         len_convname = strlen(convname);
136         ci = malloc(sizeof(*ci) + len_convname + 1);
137         if (!ci) {
138                 ret = errno;
139                 goto err;
140         }
141         ci->ci_module = NULL;
142         ci->ci_ops = NULL;
143         ci->ci_closure = NULL;
144         ci->ci_convname = (void *)&ci[1];
145         memcpy(ci->ci_convname, convname, len_convname + 1);
146
147         /* load module */
148         ret = _citrus_load_module(&ci->ci_module, module);
149         if (ret)
150                 goto err;
151
152         /* get operators */
153         getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module,
154             module, "iconv");
155         if (!getops) {
156                 ret = EOPNOTSUPP;
157                 goto err;
158         }
159         ci->ci_ops = malloc(sizeof(*ci->ci_ops));
160         if (!ci->ci_ops) {
161                 ret = errno;
162                 goto err;
163         }
164         ret = (*getops)(ci->ci_ops);
165         if (ret)
166                 goto err;
167
168         if (ci->ci_ops->io_init_shared == NULL ||
169             ci->ci_ops->io_uninit_shared == NULL ||
170             ci->ci_ops->io_init_context == NULL ||
171             ci->ci_ops->io_uninit_context == NULL ||
172             ci->ci_ops->io_convert == NULL)
173                 goto err;
174
175         /* initialize the converter */
176         ret = (*ci->ci_ops->io_init_shared)(ci, src, dst);
177         if (ret)
178                 goto err;
179
180         *rci = ci;
181
182         return (0);
183 err:
184         close_shared(ci);
185         return (ret);
186 }
187
188 static __inline int
189 hash_func(const char *key)
190 {
191
192         return (_string_hash_func(key, CI_HASH_SIZE));
193 }
194
195 static __inline int
196 match_func(struct _citrus_iconv_shared * __restrict ci,
197     const char * __restrict key)
198 {
199
200         return (strcmp(ci->ci_convname, key));
201 }
202
203 static int
204 get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
205     const char *src, const char *dst)
206 {
207         struct _citrus_iconv_shared * ci;
208         char convname[PATH_MAX];
209         int hashval, ret = 0;
210
211         snprintf(convname, sizeof(convname), "%s/%s", src, dst);
212
213         WLOCK(&ci_lock);
214
215         /* lookup alread existing entry */
216         hashval = hash_func(convname);
217         _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func,
218             convname, hashval);
219         if (ci != NULL) {
220                 /* found */
221                 if (ci->ci_used_count == 0) {
222                         TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
223                         shared_num_unused--;
224                 }
225                 ci->ci_used_count++;
226                 *rci = ci;
227                 goto quit;
228         }
229
230         /* create new entry */
231         ret = open_shared(&ci, convname, src, dst);
232         if (ret)
233                 goto quit;
234
235         _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval);
236         ci->ci_used_count = 1;
237         *rci = ci;
238
239 quit:
240         UNLOCK(&ci_lock);
241
242         return (ret);
243 }
244
245 static void
246 release_shared(struct _citrus_iconv_shared * __restrict ci)
247 {
248
249         WLOCK(&ci_lock);
250         ci->ci_used_count--;
251         if (ci->ci_used_count == 0) {
252                 /* put it into unused list */
253                 shared_num_unused++;
254                 TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry);
255                 /* flood out */
256                 while (shared_num_unused > shared_max_reuse) {
257                         ci = TAILQ_FIRST(&shared_unused);
258                         TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
259                         _CITRUS_HASH_REMOVE(ci, ci_hash_entry);
260                         shared_num_unused--;
261                         close_shared(ci);
262                 }
263         }
264
265         UNLOCK(&ci_lock);
266 }
267
268 /*
269  * _citrus_iconv_open:
270  *      open a converter for the specified in/out codes.
271  */
272 int
273 _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv,
274     const char * __restrict src, const char * __restrict dst)
275 {
276         struct _citrus_iconv *cv = NULL;
277         struct _citrus_iconv_shared *ci = NULL;
278         char realdst[PATH_MAX], realsrc[PATH_MAX];
279         char buf[PATH_MAX], path[PATH_MAX];
280         int ret;
281
282         init_cache();
283
284         /* GNU behaviour, using locale encoding if "" or "char" is specified */
285         if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0))
286                 src = nl_langinfo(CODESET);
287         if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0))
288                 dst = nl_langinfo(CODESET);
289
290         /* resolve codeset name aliases */
291         strlcpy(realsrc, _lookup_alias(path, src, buf, (size_t)PATH_MAX,
292             _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
293         strlcpy(realdst, _lookup_alias(path, dst, buf, (size_t)PATH_MAX,
294             _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
295
296         /* sanity check */
297         if (strchr(realsrc, '/') != NULL || strchr(realdst, '/'))
298                 return (EINVAL);
299
300         /* get shared record */
301         ret = get_shared(&ci, realsrc, realdst);
302         if (ret)
303                 return (ret);
304
305         /* create/init context */
306         if (*rcv == NULL) {
307                 cv = malloc(sizeof(*cv));
308                 if (cv == NULL) {
309                         ret = errno;
310                         release_shared(ci);
311                         return (ret);
312                 }
313                 *rcv = cv;
314         }
315         (*rcv)->cv_shared = ci;
316         ret = (*ci->ci_ops->io_init_context)(*rcv);
317         if (ret) {
318                 release_shared(ci);
319                 free(cv);
320                 return (ret);
321         }
322         return (0);
323 }
324
325 /*
326  * _citrus_iconv_close:
327  *      close the specified converter.
328  */
329 void
330 _citrus_iconv_close(struct _citrus_iconv *cv)
331 {
332
333         if (cv) {
334                 (*cv->cv_shared->ci_ops->io_uninit_context)(cv);
335                 release_shared(cv->cv_shared);
336                 free(cv);
337         }
338 }
339
340 const char
341 *_citrus_iconv_canonicalize(const char *name)
342 {
343         char *buf;
344
345         if ((buf = malloc((size_t)PATH_MAX)) == NULL)
346                 return (NULL);
347         memset((void *)buf, 0, (size_t)PATH_MAX);
348         _citrus_esdb_alias(name, buf, (size_t)PATH_MAX);
349         return (buf);
350 }