Home | History | Annotate | Line # | Download | only in citrus
citrus_iconv.c revision 1.8
      1  1.8  dholland /*	$NetBSD: citrus_iconv.c,v 1.8 2009/09/05 06:44:27 dholland Exp $	*/
      2  1.1  tshiozak 
      3  1.1  tshiozak /*-
      4  1.1  tshiozak  * Copyright (c)2003 Citrus Project,
      5  1.1  tshiozak  * All rights reserved.
      6  1.1  tshiozak  *
      7  1.1  tshiozak  * Redistribution and use in source and binary forms, with or without
      8  1.1  tshiozak  * modification, are permitted provided that the following conditions
      9  1.1  tshiozak  * are met:
     10  1.1  tshiozak  * 1. Redistributions of source code must retain the above copyright
     11  1.1  tshiozak  *    notice, this list of conditions and the following disclaimer.
     12  1.1  tshiozak  * 2. Redistributions in binary form must reproduce the above copyright
     13  1.1  tshiozak  *    notice, this list of conditions and the following disclaimer in the
     14  1.1  tshiozak  *    documentation and/or other materials provided with the distribution.
     15  1.1  tshiozak  *
     16  1.1  tshiozak  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  1.1  tshiozak  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  1.1  tshiozak  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  1.1  tshiozak  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  1.1  tshiozak  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  1.1  tshiozak  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  1.1  tshiozak  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  1.1  tshiozak  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  1.1  tshiozak  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  1.1  tshiozak  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  1.1  tshiozak  * SUCH DAMAGE.
     27  1.1  tshiozak  */
     28  1.1  tshiozak 
     29  1.1  tshiozak #include <sys/cdefs.h>
     30  1.1  tshiozak #if defined(LIBC_SCCS) && !defined(lint)
     31  1.8  dholland __RCSID("$NetBSD: citrus_iconv.c,v 1.8 2009/09/05 06:44:27 dholland Exp $");
     32  1.1  tshiozak #endif /* LIBC_SCCS and not lint */
     33  1.1  tshiozak 
     34  1.1  tshiozak #include "namespace.h"
     35  1.2  tshiozak #include "reentrant.h"
     36  1.1  tshiozak #include <assert.h>
     37  1.1  tshiozak #include <stdio.h>
     38  1.1  tshiozak #include <stdlib.h>
     39  1.1  tshiozak #include <string.h>
     40  1.1  tshiozak #include <errno.h>
     41  1.1  tshiozak #include <limits.h>
     42  1.1  tshiozak #include <unistd.h>
     43  1.1  tshiozak #include <paths.h>
     44  1.1  tshiozak #include <dirent.h>
     45  1.1  tshiozak #include <sys/types.h>
     46  1.2  tshiozak #include <sys/queue.h>
     47  1.1  tshiozak 
     48  1.1  tshiozak #include "citrus_namespace.h"
     49  1.1  tshiozak #include "citrus_bcs.h"
     50  1.1  tshiozak #include "citrus_region.h"
     51  1.1  tshiozak #include "citrus_memstream.h"
     52  1.1  tshiozak #include "citrus_mmap.h"
     53  1.1  tshiozak #include "citrus_module.h"
     54  1.1  tshiozak #include "citrus_lookup.h"
     55  1.2  tshiozak #include "citrus_hash.h"
     56  1.1  tshiozak #include "citrus_iconv.h"
     57  1.1  tshiozak 
     58  1.1  tshiozak #define _CITRUS_ICONV_DIR	"iconv.dir"
     59  1.1  tshiozak #define _CITRUS_ICONV_ALIAS	"iconv.alias"
     60  1.1  tshiozak 
     61  1.2  tshiozak #define CI_HASH_SIZE 101
     62  1.2  tshiozak #define CI_INITIAL_MAX_REUSE	5
     63  1.2  tshiozak #define CI_ENV_MAX_REUSE	"ICONV_MAX_REUSE"
     64  1.2  tshiozak 
     65  1.2  tshiozak #ifdef _REENTRANT
     66  1.2  tshiozak static rwlock_t lock = RWLOCK_INITIALIZER;
     67  1.2  tshiozak #endif
     68  1.2  tshiozak static int isinit = 0;
     69  1.2  tshiozak static _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool;
     70  1.2  tshiozak static TAILQ_HEAD(, _citrus_iconv_shared) shared_unused;
     71  1.2  tshiozak static int shared_num_unused, shared_max_reuse;
     72  1.2  tshiozak 
     73  1.2  tshiozak static __inline void
     74  1.2  tshiozak init_cache(void)
     75  1.2  tshiozak {
     76  1.2  tshiozak 	rwlock_wrlock(&lock);
     77  1.2  tshiozak 	if (!isinit) {
     78  1.2  tshiozak 		_CITRUS_HASH_INIT(&shared_pool, CI_HASH_SIZE);
     79  1.2  tshiozak 		TAILQ_INIT(&shared_unused);
     80  1.2  tshiozak 		shared_max_reuse = -1;
     81  1.2  tshiozak 		if (!issetugid() && getenv(CI_ENV_MAX_REUSE))
     82  1.2  tshiozak 			shared_max_reuse = atoi(getenv(CI_ENV_MAX_REUSE));
     83  1.2  tshiozak 		if (shared_max_reuse < 0)
     84  1.2  tshiozak 			shared_max_reuse = CI_INITIAL_MAX_REUSE;
     85  1.2  tshiozak 		isinit = 1;
     86  1.2  tshiozak 	}
     87  1.2  tshiozak 	rwlock_unlock(&lock);
     88  1.2  tshiozak }
     89  1.2  tshiozak 
     90  1.1  tshiozak /*
     91  1.1  tshiozak  * lookup_iconv_entry:
     92  1.1  tshiozak  *	lookup iconv.dir entry in the specified directory.
     93  1.1  tshiozak  *
     94  1.1  tshiozak  * line format of iconv.dir file:
     95  1.1  tshiozak  *	key  module  arg
     96  1.1  tshiozak  * key    : lookup key.
     97  1.1  tshiozak  * module : iconv module name.
     98  1.1  tshiozak  * arg    : argument for the module (generally, description file name)
     99  1.1  tshiozak  *
    100  1.1  tshiozak  */
    101  1.1  tshiozak static __inline int
    102  1.1  tshiozak lookup_iconv_entry(const char *curdir, const char *key,
    103  1.1  tshiozak 		   char *linebuf, size_t linebufsize,
    104  1.1  tshiozak 		   const char **module, const char **variable)
    105  1.1  tshiozak {
    106  1.1  tshiozak 	const char *cp, *cq;
    107  1.1  tshiozak 	char *p, path[PATH_MAX];
    108  1.1  tshiozak 
    109  1.1  tshiozak 	/* iconv.dir path */
    110  1.8  dholland 	snprintf(path, (size_t)PATH_MAX, ("%s/" _CITRUS_ICONV_DIR), curdir);
    111  1.1  tshiozak 
    112  1.1  tshiozak 	/* lookup db */
    113  1.4  tshiozak 	cp = p = _lookup_simple(path, key, linebuf, linebufsize,
    114  1.4  tshiozak 				_LOOKUP_CASE_IGNORE);
    115  1.1  tshiozak 	if (p == NULL)
    116  1.1  tshiozak 		return ENOENT;
    117  1.1  tshiozak 
    118  1.1  tshiozak 	/* get module name */
    119  1.1  tshiozak 	*module = p;
    120  1.1  tshiozak 	cq = _bcs_skip_nonws(cp);
    121  1.1  tshiozak 	p[cq-cp] = '\0';
    122  1.1  tshiozak 	p += cq-cp+1;
    123  1.1  tshiozak 	cq++;
    124  1.1  tshiozak 
    125  1.1  tshiozak 	/* get variable */
    126  1.1  tshiozak 	cp = _bcs_skip_ws(cq);
    127  1.1  tshiozak 	*variable = p += cp - cq;
    128  1.1  tshiozak 	cq = _bcs_skip_nonws(cp);
    129  1.1  tshiozak 	p[cq-cp] = '\0';
    130  1.1  tshiozak 
    131  1.1  tshiozak 	return 0;
    132  1.1  tshiozak }
    133  1.1  tshiozak 
    134  1.2  tshiozak static __inline void
    135  1.2  tshiozak close_shared(struct _citrus_iconv_shared *ci)
    136  1.2  tshiozak {
    137  1.2  tshiozak 	if (ci) {
    138  1.2  tshiozak 		if (ci->ci_module) {
    139  1.2  tshiozak 			if (ci->ci_ops) {
    140  1.2  tshiozak 				if (ci->ci_closure)
    141  1.2  tshiozak 					(*ci->ci_ops->io_uninit_shared)(ci);
    142  1.2  tshiozak 				free(ci->ci_ops);
    143  1.2  tshiozak 			}
    144  1.2  tshiozak 			_citrus_unload_module(ci->ci_module);
    145  1.2  tshiozak 		}
    146  1.2  tshiozak 		free(ci);
    147  1.2  tshiozak 	}
    148  1.2  tshiozak }
    149  1.2  tshiozak 
    150  1.2  tshiozak static __inline int
    151  1.2  tshiozak open_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
    152  1.2  tshiozak 	    const char * __restrict basedir, const char * __restrict convname,
    153  1.2  tshiozak 	    const char * __restrict src, const char * __restrict dst)
    154  1.1  tshiozak {
    155  1.1  tshiozak 	int ret;
    156  1.2  tshiozak 	struct _citrus_iconv_shared *ci;
    157  1.1  tshiozak 	_citrus_iconv_getops_t getops;
    158  1.2  tshiozak 	char linebuf[LINE_MAX];
    159  1.1  tshiozak 	const char *module, *variable;
    160  1.2  tshiozak 	size_t len_convname;
    161  1.1  tshiozak 
    162  1.1  tshiozak 	/* search converter entry */
    163  1.2  tshiozak 	ret = lookup_iconv_entry(basedir, convname, linebuf, sizeof(linebuf),
    164  1.1  tshiozak 				 &module, &variable);
    165  1.1  tshiozak 	if (ret) {
    166  1.1  tshiozak 		if (ret == ENOENT)
    167  1.1  tshiozak 			/* fallback */
    168  1.1  tshiozak 			ret = lookup_iconv_entry(basedir, "*",
    169  1.5      yamt 						 linebuf, sizeof(linebuf),
    170  1.1  tshiozak 						 &module, &variable);
    171  1.1  tshiozak 		if (ret)
    172  1.1  tshiozak 			return ret;
    173  1.1  tshiozak 	}
    174  1.1  tshiozak 
    175  1.1  tshiozak 	/* initialize iconv handle */
    176  1.2  tshiozak 	len_convname = strlen(convname);
    177  1.2  tshiozak 	ci = malloc(sizeof(*ci)+len_convname+1);
    178  1.1  tshiozak 	if (!ci) {
    179  1.1  tshiozak 		ret = errno;
    180  1.1  tshiozak 		goto err;
    181  1.1  tshiozak 	}
    182  1.1  tshiozak 	ci->ci_module = NULL;
    183  1.1  tshiozak 	ci->ci_ops = NULL;
    184  1.1  tshiozak 	ci->ci_closure = NULL;
    185  1.2  tshiozak 	ci->ci_convname = (void *)&ci[1];
    186  1.2  tshiozak 	memcpy(ci->ci_convname, convname, len_convname+1);
    187  1.1  tshiozak 
    188  1.1  tshiozak 	/* load module */
    189  1.1  tshiozak 	ret = _citrus_load_module(&ci->ci_module, module);
    190  1.1  tshiozak 	if (ret)
    191  1.1  tshiozak 		goto err;
    192  1.1  tshiozak 
    193  1.1  tshiozak 	/* get operators */
    194  1.1  tshiozak 	getops = (_citrus_iconv_getops_t)
    195  1.1  tshiozak 	    _citrus_find_getops(ci->ci_module, module, "iconv");
    196  1.1  tshiozak 	if (!getops) {
    197  1.1  tshiozak 		ret = EOPNOTSUPP;
    198  1.1  tshiozak 		goto err;
    199  1.1  tshiozak 	}
    200  1.1  tshiozak 	ci->ci_ops = malloc(sizeof(*ci->ci_ops));
    201  1.1  tshiozak 	if (!ci->ci_ops) {
    202  1.1  tshiozak 		ret = errno;
    203  1.1  tshiozak 		goto err;
    204  1.1  tshiozak 	}
    205  1.1  tshiozak 	ret = (*getops)(ci->ci_ops, sizeof(*ci->ci_ops),
    206  1.1  tshiozak 			_CITRUS_ICONV_ABI_VERSION);
    207  1.1  tshiozak 	if (ret)
    208  1.1  tshiozak 		goto err;
    209  1.1  tshiozak 
    210  1.2  tshiozak 	/* version check */
    211  1.2  tshiozak 	if (ci->ci_ops->io_abi_version == 1) {
    212  1.2  tshiozak 		/* binary compatibility broken at ver.2 */
    213  1.2  tshiozak 		ret = EINVAL;
    214  1.2  tshiozak 		goto err;
    215  1.2  tshiozak 	}
    216  1.2  tshiozak 
    217  1.2  tshiozak 	if (ci->ci_ops->io_init_shared == NULL ||
    218  1.2  tshiozak 	    ci->ci_ops->io_uninit_shared == NULL ||
    219  1.2  tshiozak 	    ci->ci_ops->io_init_context == NULL ||
    220  1.2  tshiozak 	    ci->ci_ops->io_uninit_context == NULL ||
    221  1.2  tshiozak 	    ci->ci_ops->io_convert == NULL)
    222  1.1  tshiozak 		goto err;
    223  1.1  tshiozak 
    224  1.1  tshiozak 	/* initialize the converter */
    225  1.2  tshiozak 	ret = (*ci->ci_ops->io_init_shared)(ci, basedir, src, dst,
    226  1.2  tshiozak 					    (const void *)variable,
    227  1.2  tshiozak 					    strlen(variable)+1);
    228  1.1  tshiozak 	if (ret)
    229  1.1  tshiozak 		goto err;
    230  1.1  tshiozak 
    231  1.1  tshiozak 	*rci = ci;
    232  1.1  tshiozak 
    233  1.1  tshiozak 	return 0;
    234  1.1  tshiozak err:
    235  1.2  tshiozak 	close_shared(ci);
    236  1.2  tshiozak 	return ret;
    237  1.2  tshiozak }
    238  1.2  tshiozak 
    239  1.2  tshiozak static __inline int
    240  1.2  tshiozak hash_func(const char *key)
    241  1.2  tshiozak {
    242  1.2  tshiozak 	return _string_hash_func(key, CI_HASH_SIZE);
    243  1.2  tshiozak }
    244  1.2  tshiozak 
    245  1.2  tshiozak static __inline int
    246  1.2  tshiozak match_func(struct _citrus_iconv_shared * __restrict ci,
    247  1.2  tshiozak 	   const char * __restrict key)
    248  1.2  tshiozak {
    249  1.2  tshiozak 	return strcmp(ci->ci_convname, key);
    250  1.2  tshiozak }
    251  1.2  tshiozak 
    252  1.2  tshiozak static int
    253  1.2  tshiozak get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci,
    254  1.2  tshiozak 	   const char *basedir, const char *src, const char *dst)
    255  1.2  tshiozak {
    256  1.2  tshiozak 	int ret = 0;
    257  1.2  tshiozak 	int hashval;
    258  1.2  tshiozak 	struct _citrus_iconv_shared * ci;
    259  1.2  tshiozak 	char convname[PATH_MAX];
    260  1.2  tshiozak 
    261  1.2  tshiozak 	snprintf(convname, sizeof(convname), "%s/%s", src, dst);
    262  1.2  tshiozak 
    263  1.2  tshiozak 	rwlock_wrlock(&lock);
    264  1.2  tshiozak 
    265  1.2  tshiozak 	/* lookup alread existing entry */
    266  1.2  tshiozak 	hashval = hash_func(convname);
    267  1.2  tshiozak 	_CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func,
    268  1.2  tshiozak 			    convname, hashval);
    269  1.2  tshiozak 	if (ci != NULL) {
    270  1.2  tshiozak 		/* found */
    271  1.2  tshiozak 		if (ci->ci_used_count == 0) {
    272  1.2  tshiozak 			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
    273  1.2  tshiozak 			shared_num_unused--;
    274  1.2  tshiozak 		}
    275  1.2  tshiozak 		ci->ci_used_count++;
    276  1.2  tshiozak 		*rci = ci;
    277  1.2  tshiozak 		goto quit;
    278  1.2  tshiozak 	}
    279  1.2  tshiozak 
    280  1.2  tshiozak 	/* create new entry */
    281  1.2  tshiozak 	ret = open_shared(&ci, basedir, convname, src, dst);
    282  1.2  tshiozak 	if (ret)
    283  1.2  tshiozak 		goto quit;
    284  1.2  tshiozak 
    285  1.2  tshiozak 	_CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval);
    286  1.2  tshiozak 	ci->ci_used_count = 1;
    287  1.2  tshiozak 	*rci = ci;
    288  1.2  tshiozak 
    289  1.2  tshiozak quit:
    290  1.2  tshiozak 	rwlock_unlock(&lock);
    291  1.2  tshiozak 
    292  1.1  tshiozak 	return ret;
    293  1.1  tshiozak }
    294  1.1  tshiozak 
    295  1.2  tshiozak static void
    296  1.2  tshiozak release_shared(struct _citrus_iconv_shared * __restrict ci)
    297  1.2  tshiozak {
    298  1.2  tshiozak 	rwlock_wrlock(&lock);
    299  1.2  tshiozak 
    300  1.2  tshiozak 	ci->ci_used_count--;
    301  1.2  tshiozak 	if (ci->ci_used_count == 0) {
    302  1.2  tshiozak 		/* put it into unused list */
    303  1.2  tshiozak 		shared_num_unused++;
    304  1.2  tshiozak 		TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry);
    305  1.2  tshiozak 		/* flood out */
    306  1.2  tshiozak 		while (shared_num_unused > shared_max_reuse) {
    307  1.2  tshiozak 			ci = TAILQ_FIRST(&shared_unused);
    308  1.2  tshiozak 			_DIAGASSERT(ci != NULL);
    309  1.2  tshiozak 			TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry);
    310  1.3  tshiozak 			_CITRUS_HASH_REMOVE(ci, ci_hash_entry);
    311  1.2  tshiozak 			shared_num_unused--;
    312  1.2  tshiozak 			close_shared(ci);
    313  1.2  tshiozak 		}
    314  1.2  tshiozak 	}
    315  1.2  tshiozak 
    316  1.2  tshiozak 	rwlock_unlock(&lock);
    317  1.2  tshiozak }
    318  1.2  tshiozak 
    319  1.2  tshiozak /*
    320  1.2  tshiozak  * _citrus_iconv_open:
    321  1.2  tshiozak  *	open a converter for the specified in/out codes.
    322  1.2  tshiozak  */
    323  1.2  tshiozak int
    324  1.2  tshiozak _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv,
    325  1.2  tshiozak 		   const char * __restrict basedir,
    326  1.2  tshiozak 		   const char * __restrict src, const char * __restrict dst)
    327  1.2  tshiozak {
    328  1.2  tshiozak 	int ret;
    329  1.7  christos 	struct _citrus_iconv_shared *ci = NULL;
    330  1.2  tshiozak 	struct _citrus_iconv *cv;
    331  1.2  tshiozak 	char realsrc[PATH_MAX], realdst[PATH_MAX];
    332  1.2  tshiozak 	char buf[PATH_MAX], path[PATH_MAX];
    333  1.2  tshiozak 
    334  1.2  tshiozak 	init_cache();
    335  1.2  tshiozak 
    336  1.2  tshiozak 	/* resolve codeset name aliases */
    337  1.2  tshiozak 	snprintf(path, sizeof(path), "%s/%s", basedir, _CITRUS_ICONV_ALIAS);
    338  1.4  tshiozak 	strlcpy(realsrc,
    339  1.6  christos 		_lookup_alias(path, src, buf, (size_t)PATH_MAX,
    340  1.6  christos 		_LOOKUP_CASE_IGNORE),
    341  1.6  christos 		(size_t)PATH_MAX);
    342  1.4  tshiozak 	strlcpy(realdst,
    343  1.6  christos 		_lookup_alias(path, dst, buf, (size_t)PATH_MAX,
    344  1.6  christos 		_LOOKUP_CASE_IGNORE),
    345  1.6  christos 		(size_t)PATH_MAX);
    346  1.2  tshiozak 
    347  1.2  tshiozak 	/* sanity check */
    348  1.2  tshiozak 	if (strchr(realsrc, '/') != NULL || strchr(realdst, '/'))
    349  1.2  tshiozak 		return EINVAL;
    350  1.2  tshiozak 
    351  1.2  tshiozak 	/* get shared record */
    352  1.2  tshiozak 	ret = get_shared(&ci, basedir, realsrc, realdst);
    353  1.2  tshiozak 	if (ret)
    354  1.2  tshiozak 		return ret;
    355  1.2  tshiozak 
    356  1.2  tshiozak 	/* create/init context */
    357  1.2  tshiozak 	cv = malloc(sizeof(*cv));
    358  1.2  tshiozak 	if (cv == NULL) {
    359  1.2  tshiozak 		ret = errno;
    360  1.2  tshiozak 		release_shared(ci);
    361  1.2  tshiozak 		return ret;
    362  1.2  tshiozak 	}
    363  1.2  tshiozak 	cv->cv_shared = ci;
    364  1.2  tshiozak 	ret = (*ci->ci_ops->io_init_context)(cv);
    365  1.2  tshiozak 	if (ret) {
    366  1.2  tshiozak 		release_shared(ci);
    367  1.2  tshiozak 		free(cv);
    368  1.2  tshiozak 		return ret;
    369  1.2  tshiozak 	}
    370  1.2  tshiozak 	*rcv = cv;
    371  1.2  tshiozak 
    372  1.2  tshiozak 	return 0;
    373  1.2  tshiozak }
    374  1.2  tshiozak 
    375  1.1  tshiozak /*
    376  1.1  tshiozak  * _citrus_iconv_close:
    377  1.1  tshiozak  *	close the specified converter.
    378  1.1  tshiozak  */
    379  1.1  tshiozak void
    380  1.2  tshiozak _citrus_iconv_close(struct _citrus_iconv *cv)
    381  1.1  tshiozak {
    382  1.2  tshiozak 	if (cv) {
    383  1.2  tshiozak 		(*cv->cv_shared->ci_ops->io_uninit_context)(cv);
    384  1.2  tshiozak 		release_shared(cv->cv_shared);
    385  1.2  tshiozak 		free(cv);
    386  1.1  tshiozak 	}
    387  1.1  tshiozak }
    388