1219019Sgabor/* $FreeBSD$ */
2219019Sgabor/* $NetBSD: citrus_iconv.c,v 1.7 2008/07/25 14:05:25 christos 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 ||
172219019Sgabor	    ci->ci_ops->io_convert == NULL)
173219019Sgabor		goto err;
174219019Sgabor
175219019Sgabor	/* initialize the converter */
176219019Sgabor	ret = (*ci->ci_ops->io_init_shared)(ci, src, dst);
177219019Sgabor	if (ret)
178219019Sgabor		goto err;
179219019Sgabor
180219019Sgabor	*rci = ci;
181219019Sgabor
182219019Sgabor	return (0);
183219019Sgaborerr:
184219019Sgabor	close_shared(ci);
185219019Sgabor	return (ret);
186219019Sgabor}
187219019Sgabor
188219019Sgaborstatic __inline int
189219019Sgaborhash_func(const char *key)
190219019Sgabor{
191219019Sgabor
192219019Sgabor	return (_string_hash_func(key, CI_HASH_SIZE));
193219019Sgabor}
194219019Sgabor
195219019Sgaborstatic __inline int
196219019Sgabormatch_func(struct _citrus_iconv_shared * __restrict ci,
197219019Sgabor    const char * __restrict key)
198219019Sgabor{
199219019Sgabor
200219019Sgabor	return (strcmp(ci->ci_convname, key));
201219019Sgabor}
202219019Sgabor
203219019Sgaborstatic int
204219019Sgaborget_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
205219019Sgabor    const char *src, const char *dst)
206219019Sgabor{
207219019Sgabor	struct _citrus_iconv_shared * ci;
208219019Sgabor	char convname[PATH_MAX];
209219019Sgabor	int hashval, ret = 0;
210219019Sgabor
211219019Sgabor	snprintf(convname, sizeof(convname), "%s/%s", src, dst);
212219019Sgabor
213252584Speter	WLOCK(&ci_lock);
214219019Sgabor
215219019Sgabor	/* lookup alread existing entry */
216219019Sgabor	hashval = hash_func(convname);
217219019Sgabor	_CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func,
218219019Sgabor	    convname, hashval);
219219019Sgabor	if (ci != NULL) {
220219019Sgabor		/* found */
221219019Sgabor		if (ci->ci_used_count == 0) {
222219019Sgabor			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
223219019Sgabor			shared_num_unused--;
224219019Sgabor		}
225219019Sgabor		ci->ci_used_count++;
226219019Sgabor		*rci = ci;
227219019Sgabor		goto quit;
228219019Sgabor	}
229219019Sgabor
230219019Sgabor	/* create new entry */
231219019Sgabor	ret = open_shared(&ci, convname, src, dst);
232219019Sgabor	if (ret)
233219019Sgabor		goto quit;
234219019Sgabor
235219019Sgabor	_CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval);
236219019Sgabor	ci->ci_used_count = 1;
237219019Sgabor	*rci = ci;
238219019Sgabor
239219019Sgaborquit:
240252584Speter	UNLOCK(&ci_lock);
241219019Sgabor
242219019Sgabor	return (ret);
243219019Sgabor}
244219019Sgabor
245219019Sgaborstatic void
246219019Sgaborrelease_shared(struct _citrus_iconv_shared * __restrict ci)
247219019Sgabor{
248219019Sgabor
249252584Speter	WLOCK(&ci_lock);
250219019Sgabor	ci->ci_used_count--;
251219019Sgabor	if (ci->ci_used_count == 0) {
252219019Sgabor		/* put it into unused list */
253219019Sgabor		shared_num_unused++;
254219019Sgabor		TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry);
255219019Sgabor		/* flood out */
256219019Sgabor		while (shared_num_unused > shared_max_reuse) {
257219019Sgabor			ci = TAILQ_FIRST(&shared_unused);
258219019Sgabor			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
259219019Sgabor			_CITRUS_HASH_REMOVE(ci, ci_hash_entry);
260219019Sgabor			shared_num_unused--;
261219019Sgabor			close_shared(ci);
262219019Sgabor		}
263219019Sgabor	}
264219019Sgabor
265252584Speter	UNLOCK(&ci_lock);
266219019Sgabor}
267219019Sgabor
268219019Sgabor/*
269219019Sgabor * _citrus_iconv_open:
270219019Sgabor *	open a converter for the specified in/out codes.
271219019Sgabor */
272219019Sgaborint
273219019Sgabor_citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv,
274219019Sgabor    const char * __restrict src, const char * __restrict dst)
275219019Sgabor{
276250980Sed	struct _citrus_iconv *cv = NULL;
277219019Sgabor	struct _citrus_iconv_shared *ci = NULL;
278219019Sgabor	char realdst[PATH_MAX], realsrc[PATH_MAX];
279219019Sgabor	char buf[PATH_MAX], path[PATH_MAX];
280219019Sgabor	int ret;
281219019Sgabor
282219019Sgabor	init_cache();
283219019Sgabor
284219019Sgabor	/* GNU behaviour, using locale encoding if "" or "char" is specified */
285219019Sgabor	if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0))
286219019Sgabor		src = nl_langinfo(CODESET);
287219019Sgabor	if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0))
288219019Sgabor		dst = nl_langinfo(CODESET);
289219019Sgabor
290219019Sgabor	/* resolve codeset name aliases */
291219019Sgabor	strlcpy(realsrc, _lookup_alias(path, src, buf, (size_t)PATH_MAX,
292219019Sgabor	    _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
293219019Sgabor	strlcpy(realdst, _lookup_alias(path, dst, buf, (size_t)PATH_MAX,
294219019Sgabor	    _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
295219019Sgabor
296219019Sgabor	/* sanity check */
297219019Sgabor	if (strchr(realsrc, '/') != NULL || strchr(realdst, '/'))
298219019Sgabor		return (EINVAL);
299219019Sgabor
300219019Sgabor	/* get shared record */
301219019Sgabor	ret = get_shared(&ci, realsrc, realdst);
302219019Sgabor	if (ret)
303219019Sgabor		return (ret);
304219019Sgabor
305219019Sgabor	/* create/init context */
306219019Sgabor	if (*rcv == NULL) {
307219019Sgabor		cv = malloc(sizeof(*cv));
308219019Sgabor		if (cv == NULL) {
309219019Sgabor			ret = errno;
310219019Sgabor			release_shared(ci);
311219019Sgabor			return (ret);
312219019Sgabor		}
313219019Sgabor		*rcv = cv;
314219019Sgabor	}
315219019Sgabor	(*rcv)->cv_shared = ci;
316219019Sgabor	ret = (*ci->ci_ops->io_init_context)(*rcv);
317219019Sgabor	if (ret) {
318219019Sgabor		release_shared(ci);
319250980Sed		free(cv);
320219019Sgabor		return (ret);
321219019Sgabor	}
322219019Sgabor	return (0);
323219019Sgabor}
324219019Sgabor
325219019Sgabor/*
326219019Sgabor * _citrus_iconv_close:
327219019Sgabor *	close the specified converter.
328219019Sgabor */
329219019Sgaborvoid
330219019Sgabor_citrus_iconv_close(struct _citrus_iconv *cv)
331219019Sgabor{
332219019Sgabor
333219019Sgabor	if (cv) {
334219019Sgabor		(*cv->cv_shared->ci_ops->io_uninit_context)(cv);
335219019Sgabor		release_shared(cv->cv_shared);
336219019Sgabor		free(cv);
337219019Sgabor	}
338219019Sgabor}
339219019Sgabor
340219019Sgaborconst char
341219019Sgabor*_citrus_iconv_canonicalize(const char *name)
342219019Sgabor{
343219019Sgabor	char *buf;
344219019Sgabor
345219019Sgabor	if ((buf = malloc((size_t)PATH_MAX)) == NULL)
346219019Sgabor		return (NULL);
347219019Sgabor	memset((void *)buf, 0, (size_t)PATH_MAX);
348219019Sgabor	_citrus_esdb_alias(name, buf, (size_t)PATH_MAX);
349219019Sgabor	return (buf);
350219019Sgabor}
351