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
71219019Sgaborstatic __inline void
72219019Sgaborinit_cache(void)
73219019Sgabor{
74219019Sgabor
75219019Sgabor	WLOCK;
76219019Sgabor	if (!isinit) {
77219019Sgabor		_CITRUS_HASH_INIT(&shared_pool, CI_HASH_SIZE);
78219019Sgabor		TAILQ_INIT(&shared_unused);
79219019Sgabor		shared_max_reuse = -1;
80219019Sgabor		if (!issetugid() && getenv(CI_ENV_MAX_REUSE))
81219019Sgabor			shared_max_reuse = atoi(getenv(CI_ENV_MAX_REUSE));
82219019Sgabor		if (shared_max_reuse < 0)
83219019Sgabor			shared_max_reuse = CI_INITIAL_MAX_REUSE;
84219019Sgabor		isinit = true;
85219019Sgabor	}
86219019Sgabor	UNLOCK;
87219019Sgabor}
88219019Sgabor
89219019Sgaborstatic __inline void
90219019Sgaborclose_shared(struct _citrus_iconv_shared *ci)
91219019Sgabor{
92219019Sgabor
93219019Sgabor	if (ci) {
94219019Sgabor		if (ci->ci_module) {
95219019Sgabor			if (ci->ci_ops) {
96219019Sgabor				if (ci->ci_closure)
97219019Sgabor					(*ci->ci_ops->io_uninit_shared)(ci);
98219019Sgabor				free(ci->ci_ops);
99219019Sgabor			}
100219019Sgabor			_citrus_unload_module(ci->ci_module);
101219019Sgabor		}
102219019Sgabor		free(ci);
103219019Sgabor	}
104219019Sgabor}
105219019Sgabor
106219019Sgaborstatic __inline int
107219019Sgaboropen_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
108219019Sgabor    const char * __restrict convname, const char * __restrict src,
109219019Sgabor    const char * __restrict dst)
110219019Sgabor{
111219019Sgabor	struct _citrus_iconv_shared *ci;
112219019Sgabor	_citrus_iconv_getops_t getops;
113219019Sgabor	const char *module;
114219019Sgabor	size_t len_convname;
115219019Sgabor	int ret;
116219019Sgabor
117219019Sgabor	module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none";
118219019Sgabor
119219019Sgabor	/* initialize iconv handle */
120219019Sgabor	len_convname = strlen(convname);
121219019Sgabor	ci = malloc(sizeof(*ci) + len_convname + 1);
122219019Sgabor	if (!ci) {
123219019Sgabor		ret = errno;
124219019Sgabor		goto err;
125219019Sgabor	}
126219019Sgabor	ci->ci_module = NULL;
127219019Sgabor	ci->ci_ops = NULL;
128219019Sgabor	ci->ci_closure = NULL;
129219019Sgabor	ci->ci_convname = (void *)&ci[1];
130219019Sgabor	memcpy(ci->ci_convname, convname, len_convname + 1);
131219019Sgabor
132219019Sgabor	/* load module */
133219019Sgabor	ret = _citrus_load_module(&ci->ci_module, module);
134219019Sgabor	if (ret)
135219019Sgabor		goto err;
136219019Sgabor
137219019Sgabor	/* get operators */
138219019Sgabor	getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module,
139219019Sgabor	    module, "iconv");
140219019Sgabor	if (!getops) {
141219019Sgabor		ret = EOPNOTSUPP;
142219019Sgabor		goto err;
143219019Sgabor	}
144219019Sgabor	ci->ci_ops = malloc(sizeof(*ci->ci_ops));
145219019Sgabor	if (!ci->ci_ops) {
146219019Sgabor		ret = errno;
147219019Sgabor		goto err;
148219019Sgabor	}
149219019Sgabor	ret = (*getops)(ci->ci_ops);
150219019Sgabor	if (ret)
151219019Sgabor		goto err;
152219019Sgabor
153219019Sgabor	if (ci->ci_ops->io_init_shared == NULL ||
154219019Sgabor	    ci->ci_ops->io_uninit_shared == NULL ||
155219019Sgabor	    ci->ci_ops->io_init_context == NULL ||
156219019Sgabor	    ci->ci_ops->io_uninit_context == NULL ||
157219019Sgabor	    ci->ci_ops->io_convert == NULL)
158219019Sgabor		goto err;
159219019Sgabor
160219019Sgabor	/* initialize the converter */
161219019Sgabor	ret = (*ci->ci_ops->io_init_shared)(ci, src, dst);
162219019Sgabor	if (ret)
163219019Sgabor		goto err;
164219019Sgabor
165219019Sgabor	*rci = ci;
166219019Sgabor
167219019Sgabor	return (0);
168219019Sgaborerr:
169219019Sgabor	close_shared(ci);
170219019Sgabor	return (ret);
171219019Sgabor}
172219019Sgabor
173219019Sgaborstatic __inline int
174219019Sgaborhash_func(const char *key)
175219019Sgabor{
176219019Sgabor
177219019Sgabor	return (_string_hash_func(key, CI_HASH_SIZE));
178219019Sgabor}
179219019Sgabor
180219019Sgaborstatic __inline int
181219019Sgabormatch_func(struct _citrus_iconv_shared * __restrict ci,
182219019Sgabor    const char * __restrict key)
183219019Sgabor{
184219019Sgabor
185219019Sgabor	return (strcmp(ci->ci_convname, key));
186219019Sgabor}
187219019Sgabor
188219019Sgaborstatic int
189219019Sgaborget_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
190219019Sgabor    const char *src, const char *dst)
191219019Sgabor{
192219019Sgabor	struct _citrus_iconv_shared * ci;
193219019Sgabor	char convname[PATH_MAX];
194219019Sgabor	int hashval, ret = 0;
195219019Sgabor
196219019Sgabor	snprintf(convname, sizeof(convname), "%s/%s", src, dst);
197219019Sgabor
198219019Sgabor	WLOCK;
199219019Sgabor
200219019Sgabor	/* lookup alread existing entry */
201219019Sgabor	hashval = hash_func(convname);
202219019Sgabor	_CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func,
203219019Sgabor	    convname, hashval);
204219019Sgabor	if (ci != NULL) {
205219019Sgabor		/* found */
206219019Sgabor		if (ci->ci_used_count == 0) {
207219019Sgabor			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
208219019Sgabor			shared_num_unused--;
209219019Sgabor		}
210219019Sgabor		ci->ci_used_count++;
211219019Sgabor		*rci = ci;
212219019Sgabor		goto quit;
213219019Sgabor	}
214219019Sgabor
215219019Sgabor	/* create new entry */
216219019Sgabor	ret = open_shared(&ci, convname, src, dst);
217219019Sgabor	if (ret)
218219019Sgabor		goto quit;
219219019Sgabor
220219019Sgabor	_CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval);
221219019Sgabor	ci->ci_used_count = 1;
222219019Sgabor	*rci = ci;
223219019Sgabor
224219019Sgaborquit:
225219019Sgabor	UNLOCK;
226219019Sgabor
227219019Sgabor	return (ret);
228219019Sgabor}
229219019Sgabor
230219019Sgaborstatic void
231219019Sgaborrelease_shared(struct _citrus_iconv_shared * __restrict ci)
232219019Sgabor{
233219019Sgabor
234219019Sgabor	WLOCK;
235219019Sgabor	ci->ci_used_count--;
236219019Sgabor	if (ci->ci_used_count == 0) {
237219019Sgabor		/* put it into unused list */
238219019Sgabor		shared_num_unused++;
239219019Sgabor		TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry);
240219019Sgabor		/* flood out */
241219019Sgabor		while (shared_num_unused > shared_max_reuse) {
242219019Sgabor			ci = TAILQ_FIRST(&shared_unused);
243219019Sgabor			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
244219019Sgabor			_CITRUS_HASH_REMOVE(ci, ci_hash_entry);
245219019Sgabor			shared_num_unused--;
246219019Sgabor			close_shared(ci);
247219019Sgabor		}
248219019Sgabor	}
249219019Sgabor
250219019Sgabor	UNLOCK;
251219019Sgabor}
252219019Sgabor
253219019Sgabor/*
254219019Sgabor * _citrus_iconv_open:
255219019Sgabor *	open a converter for the specified in/out codes.
256219019Sgabor */
257219019Sgaborint
258219019Sgabor_citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv,
259219019Sgabor    const char * __restrict src, const char * __restrict dst)
260219019Sgabor{
261219019Sgabor	struct _citrus_iconv *cv;
262219019Sgabor	struct _citrus_iconv_shared *ci = NULL;
263219019Sgabor	char realdst[PATH_MAX], realsrc[PATH_MAX];
264219019Sgabor	char buf[PATH_MAX], path[PATH_MAX];
265219019Sgabor	int ret;
266219019Sgabor
267219019Sgabor	init_cache();
268219019Sgabor
269219019Sgabor	/* GNU behaviour, using locale encoding if "" or "char" is specified */
270219019Sgabor	if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0))
271219019Sgabor		src = nl_langinfo(CODESET);
272219019Sgabor	if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0))
273219019Sgabor		dst = nl_langinfo(CODESET);
274219019Sgabor
275219019Sgabor	/* resolve codeset name aliases */
276219019Sgabor	strlcpy(realsrc, _lookup_alias(path, src, buf, (size_t)PATH_MAX,
277219019Sgabor	    _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
278219019Sgabor	strlcpy(realdst, _lookup_alias(path, dst, buf, (size_t)PATH_MAX,
279219019Sgabor	    _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX);
280219019Sgabor
281219019Sgabor	/* sanity check */
282219019Sgabor	if (strchr(realsrc, '/') != NULL || strchr(realdst, '/'))
283219019Sgabor		return (EINVAL);
284219019Sgabor
285219019Sgabor	/* get shared record */
286219019Sgabor	ret = get_shared(&ci, realsrc, realdst);
287219019Sgabor	if (ret)
288219019Sgabor		return (ret);
289219019Sgabor
290219019Sgabor	/* create/init context */
291219019Sgabor	if (*rcv == NULL) {
292219019Sgabor		cv = malloc(sizeof(*cv));
293219019Sgabor		if (cv == NULL) {
294219019Sgabor			ret = errno;
295219019Sgabor			release_shared(ci);
296219019Sgabor			return (ret);
297219019Sgabor		}
298219019Sgabor		*rcv = cv;
299219019Sgabor	}
300219019Sgabor	(*rcv)->cv_shared = ci;
301219019Sgabor	ret = (*ci->ci_ops->io_init_context)(*rcv);
302219019Sgabor	if (ret) {
303219019Sgabor		release_shared(ci);
304219019Sgabor		free(*rcv);
305219019Sgabor		return (ret);
306219019Sgabor	}
307219019Sgabor	return (0);
308219019Sgabor}
309219019Sgabor
310219019Sgabor/*
311219019Sgabor * _citrus_iconv_close:
312219019Sgabor *	close the specified converter.
313219019Sgabor */
314219019Sgaborvoid
315219019Sgabor_citrus_iconv_close(struct _citrus_iconv *cv)
316219019Sgabor{
317219019Sgabor
318219019Sgabor	if (cv) {
319219019Sgabor		(*cv->cv_shared->ci_ops->io_uninit_context)(cv);
320219019Sgabor		release_shared(cv->cv_shared);
321219019Sgabor		free(cv);
322219019Sgabor	}
323219019Sgabor}
324219019Sgabor
325219019Sgaborconst char
326219019Sgabor*_citrus_iconv_canonicalize(const char *name)
327219019Sgabor{
328219019Sgabor	char *buf;
329219019Sgabor
330219019Sgabor	if ((buf = malloc((size_t)PATH_MAX)) == NULL)
331219019Sgabor		return (NULL);
332219019Sgabor	memset((void *)buf, 0, (size_t)PATH_MAX);
333219019Sgabor	_citrus_esdb_alias(name, buf, (size_t)PATH_MAX);
334219019Sgabor	return (buf);
335219019Sgabor}
336