citrus_iconv.c revision 252584
1/* $FreeBSD: head/lib/libc/iconv/citrus_iconv.c 252584 2013-07-03 18:35:21Z 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
66static bool			 isinit = false;
67static int			 shared_max_reuse, shared_num_unused;
68static _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool;
69static TAILQ_HEAD(, _citrus_iconv_shared) shared_unused;
70
71static pthread_rwlock_t		 ci_lock = PTHREAD_RWLOCK_INITIALIZER;
72
73static __inline void
74init_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
91static __inline void
92close_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
108static __inline int
109open_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);
170err:
171	close_shared(ci);
172	return (ret);
173}
174
175static __inline int
176hash_func(const char *key)
177{
178
179	return (_string_hash_func(key, CI_HASH_SIZE));
180}
181
182static __inline int
183match_func(struct _citrus_iconv_shared * __restrict ci,
184    const char * __restrict key)
185{
186
187	return (strcmp(ci->ci_convname, key));
188}
189
190static int
191get_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
226quit:
227	UNLOCK(&ci_lock);
228
229	return (ret);
230}
231
232static void
233release_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 */
259int
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 */
316void
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
327const 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