1219019Sgabor/* $FreeBSD$ */
2264497Stijl/*	$NetBSD: citrus_iconv.c,v 1.10 2011/11/19 18:34:21 tnozaki Exp $	*/
3219019Sgabor
4219019Sgabor/*-
5219019Sgabor * Copyright (c)2003 Citrus Project,
6219019Sgabor * All rights reserved.
7219019Sgabor *
8219019Sgabor * Redistribution and use in source and binary forms, with or without
9219019Sgabor * modification, are permitted provided that the following conditions
10219019Sgabor * are met:
11219019Sgabor * 1. Redistributions of source code must retain the above copyright
12219019Sgabor *    notice, this list of conditions and the following disclaimer.
13219019Sgabor * 2. Redistributions in binary form must reproduce the above copyright
14219019Sgabor *    notice, this list of conditions and the following disclaimer in the
15219019Sgabor *    documentation and/or other materials provided with the distribution.
16219019Sgabor *
17219019Sgabor * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18219019Sgabor * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19219019Sgabor * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20219019Sgabor * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21219019Sgabor * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22219019Sgabor * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23219019Sgabor * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24219019Sgabor * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25219019Sgabor * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26219019Sgabor * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27219019Sgabor * SUCH DAMAGE.
28219019Sgabor */
29219019Sgabor
30219019Sgabor#include <sys/cdefs.h>
31219019Sgabor#include <sys/types.h>
32219019Sgabor#include <sys/queue.h>
33219019Sgabor
34219019Sgabor#include <assert.h>
35219019Sgabor#include <dirent.h>
36219019Sgabor#include <errno.h>
37219019Sgabor#include <iconv.h>
38219019Sgabor#include <langinfo.h>
39219019Sgabor#include <limits.h>
40219019Sgabor#include <paths.h>
41219019Sgabor#include <stdbool.h>
42219019Sgabor#include <stdio.h>
43219019Sgabor#include <stdlib.h>
44219019Sgabor#include <string.h>
45219019Sgabor#include <unistd.h>
46219019Sgabor
47219019Sgabor#include "citrus_namespace.h"
48219019Sgabor#include "citrus_bcs.h"
49219019Sgabor#include "citrus_esdb.h"
50219019Sgabor#include "citrus_region.h"
51219019Sgabor#include "citrus_memstream.h"
52219019Sgabor#include "citrus_mmap.h"
53219019Sgabor#include "citrus_module.h"
54219019Sgabor#include "citrus_lock.h"
55219019Sgabor#include "citrus_lookup.h"
56219019Sgabor#include "citrus_hash.h"
57219019Sgabor#include "citrus_iconv.h"
58219019Sgabor
59219019Sgabor#define _CITRUS_ICONV_DIR	"iconv.dir"
60219019Sgabor#define _CITRUS_ICONV_ALIAS	"iconv.alias"
61219019Sgabor
62219019Sgabor#define CI_HASH_SIZE 101
63219019Sgabor#define CI_INITIAL_MAX_REUSE	5
64219019Sgabor#define CI_ENV_MAX_REUSE	"ICONV_MAX_REUSE"
65219019Sgabor
66219019Sgaborstatic bool			 isinit = false;
67219019Sgaborstatic int			 shared_max_reuse, shared_num_unused;
68219019Sgaborstatic _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool;
69219019Sgaborstatic TAILQ_HEAD(, _citrus_iconv_shared) shared_unused;
70219019Sgabor
71252584Speterstatic pthread_rwlock_t		 ci_lock = PTHREAD_RWLOCK_INITIALIZER;
72252584Speter
73219019Sgaborstatic __inline void
74219019Sgaborinit_cache(void)
75219019Sgabor{
76219019Sgabor
77252584Speter	WLOCK(&ci_lock);
78219019Sgabor	if (!isinit) {
79219019Sgabor		_CITRUS_HASH_INIT(&shared_pool, CI_HASH_SIZE);
80219019Sgabor		TAILQ_INIT(&shared_unused);
81219019Sgabor		shared_max_reuse = -1;
82219019Sgabor		if (!issetugid() && getenv(CI_ENV_MAX_REUSE))
83219019Sgabor			shared_max_reuse = atoi(getenv(CI_ENV_MAX_REUSE));
84219019Sgabor		if (shared_max_reuse < 0)
85219019Sgabor			shared_max_reuse = CI_INITIAL_MAX_REUSE;
86219019Sgabor		isinit = true;
87219019Sgabor	}
88252584Speter	UNLOCK(&ci_lock);
89219019Sgabor}
90219019Sgabor
91219019Sgaborstatic __inline void
92219019Sgaborclose_shared(struct _citrus_iconv_shared *ci)
93219019Sgabor{
94219019Sgabor
95219019Sgabor	if (ci) {
96219019Sgabor		if (ci->ci_module) {
97219019Sgabor			if (ci->ci_ops) {
98219019Sgabor				if (ci->ci_closure)
99219019Sgabor					(*ci->ci_ops->io_uninit_shared)(ci);
100219019Sgabor				free(ci->ci_ops);
101219019Sgabor			}
102219019Sgabor			_citrus_unload_module(ci->ci_module);
103219019Sgabor		}
104219019Sgabor		free(ci);
105219019Sgabor	}
106219019Sgabor}
107219019Sgabor
108219019Sgaborstatic __inline int
109219019Sgaboropen_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
110219019Sgabor    const char * __restrict convname, const char * __restrict src,
111219019Sgabor    const char * __restrict dst)
112219019Sgabor{
113219019Sgabor	struct _citrus_iconv_shared *ci;
114219019Sgabor	_citrus_iconv_getops_t getops;
115219019Sgabor	const char *module;
116219019Sgabor	size_t len_convname;
117219019Sgabor	int ret;
118219019Sgabor
119254080Speter#ifdef INCOMPATIBLE_WITH_GNU_ICONV
120254080Speter	/*
121254080Speter	 * Sadly, the gnu tools expect iconv to actually parse the
122254080Speter	 * byte stream and don't allow for a pass-through when
123254080Speter	 * the (src,dest) encodings are the same.
124254080Speter	 * See gettext-0.18.3+ NEWS:
125254080Speter	 *   msgfmt now checks PO file headers more strictly with less
126254080Speter	 *   false-positives.
127254080Speter	 * NetBSD don't do this either.
128254080Speter	 */
129219019Sgabor	module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none";
130254080Speter#else
131254080Speter	module = "iconv_std";
132254080Speter#endif
133219019Sgabor
134219019Sgabor	/* initialize iconv handle */
135219019Sgabor	len_convname = strlen(convname);
136219019Sgabor	ci = malloc(sizeof(*ci) + len_convname + 1);
137219019Sgabor	if (!ci) {
138219019Sgabor		ret = errno;
139219019Sgabor		goto err;
140219019Sgabor	}
141219019Sgabor	ci->ci_module = NULL;
142219019Sgabor	ci->ci_ops = NULL;
143219019Sgabor	ci->ci_closure = NULL;
144219019Sgabor	ci->ci_convname = (void *)&ci[1];
145219019Sgabor	memcpy(ci->ci_convname, convname, len_convname + 1);
146219019Sgabor
147219019Sgabor	/* load module */
148219019Sgabor	ret = _citrus_load_module(&ci->ci_module, module);
149219019Sgabor	if (ret)
150219019Sgabor		goto err;
151219019Sgabor
152219019Sgabor	/* get operators */
153219019Sgabor	getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module,
154219019Sgabor	    module, "iconv");
155219019Sgabor	if (!getops) {
156219019Sgabor		ret = EOPNOTSUPP;
157219019Sgabor		goto err;
158219019Sgabor	}
159219019Sgabor	ci->ci_ops = malloc(sizeof(*ci->ci_ops));
160219019Sgabor	if (!ci->ci_ops) {
161219019Sgabor		ret = errno;
162219019Sgabor		goto err;
163219019Sgabor	}
164219019Sgabor	ret = (*getops)(ci->ci_ops);
165219019Sgabor	if (ret)
166219019Sgabor		goto err;
167219019Sgabor
168219019Sgabor	if (ci->ci_ops->io_init_shared == NULL ||
169219019Sgabor	    ci->ci_ops->io_uninit_shared == NULL ||
170219019Sgabor	    ci->ci_ops->io_init_context == NULL ||
171219019Sgabor	    ci->ci_ops->io_uninit_context == NULL ||
172264497Stijl	    ci->ci_ops->io_convert == NULL) {
173264497Stijl		ret = EINVAL;
174219019Sgabor		goto err;
175264497Stijl	}
176219019Sgabor
177219019Sgabor	/* initialize the converter */
178219019Sgabor	ret = (*ci->ci_ops->io_init_shared)(ci, src, dst);
179219019Sgabor	if (ret)
180219019Sgabor		goto err;
181219019Sgabor
182219019Sgabor	*rci = ci;
183219019Sgabor
184219019Sgabor	return (0);
185219019Sgaborerr:
186219019Sgabor	close_shared(ci);
187219019Sgabor	return (ret);
188219019Sgabor}
189219019Sgabor
190219019Sgaborstatic __inline int
191219019Sgaborhash_func(const char *key)
192219019Sgabor{
193219019Sgabor
194219019Sgabor	return (_string_hash_func(key, CI_HASH_SIZE));
195219019Sgabor}
196219019Sgabor
197219019Sgaborstatic __inline int
198219019Sgabormatch_func(struct _citrus_iconv_shared * __restrict ci,
199219019Sgabor    const char * __restrict key)
200219019Sgabor{
201219019Sgabor
202219019Sgabor	return (strcmp(ci->ci_convname, key));
203219019Sgabor}
204219019Sgabor
205219019Sgaborstatic int
206219019Sgaborget_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
207219019Sgabor    const char *src, const char *dst)
208219019Sgabor{
209219019Sgabor	struct _citrus_iconv_shared * ci;
210219019Sgabor	char convname[PATH_MAX];
211219019Sgabor	int hashval, ret = 0;
212219019Sgabor
213219019Sgabor	snprintf(convname, sizeof(convname), "%s/%s", src, dst);
214219019Sgabor
215252584Speter	WLOCK(&ci_lock);
216219019Sgabor
217219019Sgabor	/* lookup alread existing entry */
218219019Sgabor	hashval = hash_func(convname);
219219019Sgabor	_CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func,
220219019Sgabor	    convname, hashval);
221219019Sgabor	if (ci != NULL) {
222219019Sgabor		/* found */
223219019Sgabor		if (ci->ci_used_count == 0) {
224219019Sgabor			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
225219019Sgabor			shared_num_unused--;
226219019Sgabor		}
227219019Sgabor		ci->ci_used_count++;
228219019Sgabor		*rci = ci;
229219019Sgabor		goto quit;
230219019Sgabor	}
231219019Sgabor
232219019Sgabor	/* create new entry */
233219019Sgabor	ret = open_shared(&ci, convname, src, dst);
234219019Sgabor	if (ret)
235219019Sgabor		goto quit;
236219019Sgabor
237219019Sgabor	_CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval);
238219019Sgabor	ci->ci_used_count = 1;
239219019Sgabor	*rci = ci;
240219019Sgabor
241219019Sgaborquit:
242252584Speter	UNLOCK(&ci_lock);
243219019Sgabor
244219019Sgabor	return (ret);
245219019Sgabor}
246219019Sgabor
247219019Sgaborstatic void
248219019Sgaborrelease_shared(struct _citrus_iconv_shared * __restrict ci)
249219019Sgabor{
250219019Sgabor
251252584Speter	WLOCK(&ci_lock);
252219019Sgabor	ci->ci_used_count--;
253219019Sgabor	if (ci->ci_used_count == 0) {
254219019Sgabor		/* put it into unused list */
255219019Sgabor		shared_num_unused++;
256219019Sgabor		TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry);
257219019Sgabor		/* flood out */
258219019Sgabor		while (shared_num_unused > shared_max_reuse) {
259219019Sgabor			ci = TAILQ_FIRST(&shared_unused);
260219019Sgabor			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
261219019Sgabor			_CITRUS_HASH_REMOVE(ci, ci_hash_entry);
262219019Sgabor			shared_num_unused--;
263219019Sgabor			close_shared(ci);
264219019Sgabor		}
265219019Sgabor	}
266219019Sgabor
267252584Speter	UNLOCK(&ci_lock);
268219019Sgabor}
269219019Sgabor
270219019Sgabor/*
271219019Sgabor * _citrus_iconv_open:
272219019Sgabor *	open a converter for the specified in/out codes.
273219019Sgabor */
274219019Sgaborint
275219019Sgabor_citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv,
276219019Sgabor    const char * __restrict src, const char * __restrict dst)
277219019Sgabor{
278250980Sed	struct _citrus_iconv *cv = NULL;
279219019Sgabor	struct _citrus_iconv_shared *ci = NULL;
280219019Sgabor	char realdst[PATH_MAX], realsrc[PATH_MAX];
281219019Sgabor	char buf[PATH_MAX], path[PATH_MAX];
282219019Sgabor	int ret;
283219019Sgabor
284219019Sgabor	init_cache();
285219019Sgabor
286219019Sgabor	/* GNU behaviour, using locale encoding if "" or "char" is specified */
287219019Sgabor	if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0))
288219019Sgabor		src = nl_langinfo(CODESET);
289219019Sgabor	if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0))
290219019Sgabor		dst = nl_langinfo(CODESET);
291219019Sgabor
292219019Sgabor	/* resolve codeset name aliases */
293219019Sgabor	strlcpy(realsrc, _lookup_alias(path, src, buf, (size_t)PATH_MAX,
294219019Sgabor	    _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
295219019Sgabor	strlcpy(realdst, _lookup_alias(path, dst, buf, (size_t)PATH_MAX,
296219019Sgabor	    _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
297219019Sgabor
298219019Sgabor	/* sanity check */
299219019Sgabor	if (strchr(realsrc, '/') != NULL || strchr(realdst, '/'))
300219019Sgabor		return (EINVAL);
301219019Sgabor
302219019Sgabor	/* get shared record */
303219019Sgabor	ret = get_shared(&ci, realsrc, realdst);
304219019Sgabor	if (ret)
305219019Sgabor		return (ret);
306219019Sgabor
307219019Sgabor	/* create/init context */
308219019Sgabor	if (*rcv == NULL) {
309219019Sgabor		cv = malloc(sizeof(*cv));
310219019Sgabor		if (cv == NULL) {
311219019Sgabor			ret = errno;
312219019Sgabor			release_shared(ci);
313219019Sgabor			return (ret);
314219019Sgabor		}
315219019Sgabor		*rcv = cv;
316219019Sgabor	}
317219019Sgabor	(*rcv)->cv_shared = ci;
318219019Sgabor	ret = (*ci->ci_ops->io_init_context)(*rcv);
319219019Sgabor	if (ret) {
320219019Sgabor		release_shared(ci);
321250980Sed		free(cv);
322219019Sgabor		return (ret);
323219019Sgabor	}
324219019Sgabor	return (0);
325219019Sgabor}
326219019Sgabor
327219019Sgabor/*
328219019Sgabor * _citrus_iconv_close:
329219019Sgabor *	close the specified converter.
330219019Sgabor */
331219019Sgaborvoid
332219019Sgabor_citrus_iconv_close(struct _citrus_iconv *cv)
333219019Sgabor{
334219019Sgabor
335219019Sgabor	if (cv) {
336219019Sgabor		(*cv->cv_shared->ci_ops->io_uninit_context)(cv);
337219019Sgabor		release_shared(cv->cv_shared);
338219019Sgabor		free(cv);
339219019Sgabor	}
340219019Sgabor}
341219019Sgabor
342219019Sgaborconst char
343219019Sgabor*_citrus_iconv_canonicalize(const char *name)
344219019Sgabor{
345219019Sgabor	char *buf;
346219019Sgabor
347267665Stijl	if ((buf = calloc((size_t)PATH_MAX, sizeof(*buf))) == NULL)
348219019Sgabor		return (NULL);
349219019Sgabor	_citrus_esdb_alias(name, buf, (size_t)PATH_MAX);
350219019Sgabor	return (buf);
351219019Sgabor}
352