xref: /freebsd/lib/libc/iconv/citrus_iconv.c (revision ff0b75b897dde9a8455f0c199af9a75e424c38cc)
1 /* $FreeBSD$ */
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 	module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none";
120 
121 	/* initialize iconv handle */
122 	len_convname = strlen(convname);
123 	ci = malloc(sizeof(*ci) + len_convname + 1);
124 	if (!ci) {
125 		ret = errno;
126 		goto err;
127 	}
128 	ci->ci_module = NULL;
129 	ci->ci_ops = NULL;
130 	ci->ci_closure = NULL;
131 	ci->ci_convname = (void *)&ci[1];
132 	memcpy(ci->ci_convname, convname, len_convname + 1);
133 
134 	/* load module */
135 	ret = _citrus_load_module(&ci->ci_module, module);
136 	if (ret)
137 		goto err;
138 
139 	/* get operators */
140 	getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module,
141 	    module, "iconv");
142 	if (!getops) {
143 		ret = EOPNOTSUPP;
144 		goto err;
145 	}
146 	ci->ci_ops = malloc(sizeof(*ci->ci_ops));
147 	if (!ci->ci_ops) {
148 		ret = errno;
149 		goto err;
150 	}
151 	ret = (*getops)(ci->ci_ops);
152 	if (ret)
153 		goto err;
154 
155 	if (ci->ci_ops->io_init_shared == NULL ||
156 	    ci->ci_ops->io_uninit_shared == NULL ||
157 	    ci->ci_ops->io_init_context == NULL ||
158 	    ci->ci_ops->io_uninit_context == NULL ||
159 	    ci->ci_ops->io_convert == NULL)
160 		goto err;
161 
162 	/* initialize the converter */
163 	ret = (*ci->ci_ops->io_init_shared)(ci, src, dst);
164 	if (ret)
165 		goto err;
166 
167 	*rci = ci;
168 
169 	return (0);
170 err:
171 	close_shared(ci);
172 	return (ret);
173 }
174 
175 static __inline int
176 hash_func(const char *key)
177 {
178 
179 	return (_string_hash_func(key, CI_HASH_SIZE));
180 }
181 
182 static __inline int
183 match_func(struct _citrus_iconv_shared * __restrict ci,
184     const char * __restrict key)
185 {
186 
187 	return (strcmp(ci->ci_convname, key));
188 }
189 
190 static int
191 get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
192     const char *src, const char *dst)
193 {
194 	struct _citrus_iconv_shared * ci;
195 	char convname[PATH_MAX];
196 	int hashval, ret = 0;
197 
198 	snprintf(convname, sizeof(convname), "%s/%s", src, dst);
199 
200 	WLOCK(&ci_lock);
201 
202 	/* lookup alread existing entry */
203 	hashval = hash_func(convname);
204 	_CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func,
205 	    convname, hashval);
206 	if (ci != NULL) {
207 		/* found */
208 		if (ci->ci_used_count == 0) {
209 			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
210 			shared_num_unused--;
211 		}
212 		ci->ci_used_count++;
213 		*rci = ci;
214 		goto quit;
215 	}
216 
217 	/* create new entry */
218 	ret = open_shared(&ci, convname, src, dst);
219 	if (ret)
220 		goto quit;
221 
222 	_CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval);
223 	ci->ci_used_count = 1;
224 	*rci = ci;
225 
226 quit:
227 	UNLOCK(&ci_lock);
228 
229 	return (ret);
230 }
231 
232 static void
233 release_shared(struct _citrus_iconv_shared * __restrict ci)
234 {
235 
236 	WLOCK(&ci_lock);
237 	ci->ci_used_count--;
238 	if (ci->ci_used_count == 0) {
239 		/* put it into unused list */
240 		shared_num_unused++;
241 		TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry);
242 		/* flood out */
243 		while (shared_num_unused > shared_max_reuse) {
244 			ci = TAILQ_FIRST(&shared_unused);
245 			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
246 			_CITRUS_HASH_REMOVE(ci, ci_hash_entry);
247 			shared_num_unused--;
248 			close_shared(ci);
249 		}
250 	}
251 
252 	UNLOCK(&ci_lock);
253 }
254 
255 /*
256  * _citrus_iconv_open:
257  *	open a converter for the specified in/out codes.
258  */
259 int
260 _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv,
261     const char * __restrict src, const char * __restrict dst)
262 {
263 	struct _citrus_iconv *cv = NULL;
264 	struct _citrus_iconv_shared *ci = NULL;
265 	char realdst[PATH_MAX], realsrc[PATH_MAX];
266 	char buf[PATH_MAX], path[PATH_MAX];
267 	int ret;
268 
269 	init_cache();
270 
271 	/* GNU behaviour, using locale encoding if "" or "char" is specified */
272 	if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0))
273 		src = nl_langinfo(CODESET);
274 	if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0))
275 		dst = nl_langinfo(CODESET);
276 
277 	/* resolve codeset name aliases */
278 	strlcpy(realsrc, _lookup_alias(path, src, buf, (size_t)PATH_MAX,
279 	    _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
280 	strlcpy(realdst, _lookup_alias(path, dst, buf, (size_t)PATH_MAX,
281 	    _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
282 
283 	/* sanity check */
284 	if (strchr(realsrc, '/') != NULL || strchr(realdst, '/'))
285 		return (EINVAL);
286 
287 	/* get shared record */
288 	ret = get_shared(&ci, realsrc, realdst);
289 	if (ret)
290 		return (ret);
291 
292 	/* create/init context */
293 	if (*rcv == NULL) {
294 		cv = malloc(sizeof(*cv));
295 		if (cv == NULL) {
296 			ret = errno;
297 			release_shared(ci);
298 			return (ret);
299 		}
300 		*rcv = cv;
301 	}
302 	(*rcv)->cv_shared = ci;
303 	ret = (*ci->ci_ops->io_init_context)(*rcv);
304 	if (ret) {
305 		release_shared(ci);
306 		free(cv);
307 		return (ret);
308 	}
309 	return (0);
310 }
311 
312 /*
313  * _citrus_iconv_close:
314  *	close the specified converter.
315  */
316 void
317 _citrus_iconv_close(struct _citrus_iconv *cv)
318 {
319 
320 	if (cv) {
321 		(*cv->cv_shared->ci_ops->io_uninit_context)(cv);
322 		release_shared(cv->cv_shared);
323 		free(cv);
324 	}
325 }
326 
327 const char
328 *_citrus_iconv_canonicalize(const char *name)
329 {
330 	char *buf;
331 
332 	if ((buf = malloc((size_t)PATH_MAX)) == NULL)
333 		return (NULL);
334 	memset((void *)buf, 0, (size_t)PATH_MAX);
335 	_citrus_esdb_alias(name, buf, (size_t)PATH_MAX);
336 	return (buf);
337 }
338