/* $NetBSD: citrus_iconv.c,v 1.10 2011/11/19 18:34:21 tnozaki Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c)2003 Citrus Project, * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "citrus_namespace.h" #include "citrus_bcs.h" #include "citrus_esdb.h" #include "citrus_region.h" #include "citrus_memstream.h" #include "citrus_mmap.h" #include "citrus_module.h" #include "citrus_lock.h" #include "citrus_lookup.h" #include "citrus_hash.h" #include "citrus_iconv.h" #define _CITRUS_ICONV_DIR "iconv.dir" #define _CITRUS_ICONV_ALIAS "iconv.alias" #define CI_HASH_SIZE 101 #define CI_INITIAL_MAX_REUSE 5 #define CI_ENV_MAX_REUSE "ICONV_MAX_REUSE" static bool isinit = false; static int shared_max_reuse, shared_num_unused; static _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool; static TAILQ_HEAD(, _citrus_iconv_shared) shared_unused; static pthread_rwlock_t ci_lock = PTHREAD_RWLOCK_INITIALIZER; static __inline void init_cache(void) { WLOCK(&ci_lock); if (!isinit) { _CITRUS_HASH_INIT(&shared_pool, CI_HASH_SIZE); TAILQ_INIT(&shared_unused); shared_max_reuse = -1; if (secure_getenv(CI_ENV_MAX_REUSE) != NULL) shared_max_reuse = atoi(secure_getenv(CI_ENV_MAX_REUSE)); if (shared_max_reuse < 0) shared_max_reuse = CI_INITIAL_MAX_REUSE; isinit = true; } UNLOCK(&ci_lock); } static __inline void close_shared(struct _citrus_iconv_shared *ci) { if (ci) { if (ci->ci_module) { if (ci->ci_ops) { if (ci->ci_closure) (*ci->ci_ops->io_uninit_shared)(ci); free(ci->ci_ops); } _citrus_unload_module(ci->ci_module); } free(ci); } } static __inline int open_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, const char * __restrict convname, const char * __restrict src, const char * __restrict dst) { struct _citrus_iconv_shared *ci; _citrus_iconv_getops_t getops; const char *module; size_t len_convname; int ret; #ifdef INCOMPATIBLE_WITH_GNU_ICONV /* * Sadly, the gnu tools expect iconv to actually parse the * byte stream and don't allow for a pass-through when * the (src,dest) encodings are the same. * See gettext-0.18.3+ NEWS: * msgfmt now checks PO file headers more strictly with less * false-positives. * NetBSD, also, doesn't do the below pass-through. * * Also note that this currently falls short if dst options have been * specified. It may be the case that we want to ignore EILSEQ, in which * case we should also select iconv_std anyways. This trick, while * clever, may not be worth it. */ module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none"; #else module = "iconv_std"; #endif /* initialize iconv handle */ len_convname = strlen(convname); ci = calloc(1, sizeof(*ci) + len_convname + 1); if (!ci) { ret = errno; goto err; } ci->ci_convname = (void *)&ci[1]; memcpy(ci->ci_convname, convname, len_convname + 1); /* load module */ ret = _citrus_load_module(&ci->ci_module, module); if (ret) goto err; /* get operators */ getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module, module, "iconv"); if (!getops) { ret = EOPNOTSUPP; goto err; } ci->ci_ops = malloc(sizeof(*ci->ci_ops)); if (!ci->ci_ops) { ret = errno; goto err; } ret = (*getops)(ci->ci_ops); if (ret) goto err; if (ci->ci_ops->io_init_shared == NULL || ci->ci_ops->io_uninit_shared == NULL || ci->ci_ops->io_init_context == NULL || ci->ci_ops->io_uninit_context == NULL || ci->ci_ops->io_convert == NULL) { ret = EINVAL; goto err; } /* initialize the converter */ ret = (*ci->ci_ops->io_init_shared)(ci, src, dst); if (ret) goto err; *rci = ci; return (0); err: close_shared(ci); return (ret); } static __inline int hash_func(const char *key) { return (_string_hash_func(key, CI_HASH_SIZE)); } static __inline int match_func(struct _citrus_iconv_shared * __restrict ci, const char * __restrict key) { return (strcmp(ci->ci_convname, key)); } static int get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, const char *src, const char *dst) { struct _citrus_iconv_shared * ci; char convname[PATH_MAX]; int hashval, ret = 0; snprintf(convname, sizeof(convname), "%s/%s", src, dst); WLOCK(&ci_lock); /* lookup alread existing entry */ hashval = hash_func(convname); _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func, convname, hashval); if (ci != NULL) { /* found */ if (ci->ci_used_count == 0) { TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); shared_num_unused--; } ci->ci_used_count++; *rci = ci; goto quit; } /* create new entry */ ret = open_shared(&ci, convname, src, dst); if (ret) goto quit; _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval); ci->ci_used_count = 1; *rci = ci; quit: UNLOCK(&ci_lock); return (ret); } static void release_shared(struct _citrus_iconv_shared * __restrict ci) { WLOCK(&ci_lock); ci->ci_used_count--; if (ci->ci_used_count == 0) { /* put it into unused list */ shared_num_unused++; TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry); /* flood out */ while (shared_num_unused > shared_max_reuse) { ci = TAILQ_FIRST(&shared_unused); TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); _CITRUS_HASH_REMOVE(ci, ci_hash_entry); shared_num_unused--; close_shared(ci); } } UNLOCK(&ci_lock); } /* * _citrus_iconv_open: * open a converter for the specified in/out codes. */ int _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv, const char * __restrict src, const char * __restrict dst) { struct _citrus_iconv *cv = NULL; struct _citrus_iconv_shared *ci = NULL; char realdst[PATH_MAX], realsrc[PATH_MAX], *slashes; #ifdef _PATH_ICONV char buf[PATH_MAX], path[PATH_MAX]; #endif int ret; init_cache(); /* GNU behaviour, using locale encoding if "" or "char" is specified */ if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0)) src = nl_langinfo(CODESET); if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0)) dst = nl_langinfo(CODESET); strlcpy(realsrc, src, (size_t)PATH_MAX); if ((slashes = strstr(realsrc, "//")) != NULL) *slashes = '\0'; strlcpy(realdst, dst, (size_t)PATH_MAX); if ((slashes = strstr(realdst, "//")) != NULL) *slashes = '\0'; /* resolve codeset name aliases */ #ifdef _PATH_ICONV /* * Note that the below reads from realsrc and realdst while it's * repopulating (writing to) realsrc and realdst, but it's done so with * a trip through `buf`. */ snprintf(path, sizeof(path), "%s/%s", _PATH_ICONV, _CITRUS_ICONV_ALIAS); strlcpy(realsrc, _lookup_alias(path, realsrc, buf, (size_t)PATH_MAX, _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); strlcpy(realdst, _lookup_alias(path, realdst, buf, (size_t)PATH_MAX, _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); #endif /* sanity check */ if (strchr(realsrc, '/') != NULL || strchr(realdst, '/')) return (EINVAL); /* get shared record */ ret = get_shared(&ci, realsrc, realdst); if (ret) return (ret); /* create/init context */ if (*rcv == NULL) { cv = malloc(sizeof(*cv)); if (cv == NULL) { ret = errno; release_shared(ci); return (ret); } *rcv = cv; } (*rcv)->cv_shared = ci; ret = (*ci->ci_ops->io_init_context)(*rcv); if (ret) { release_shared(ci); free(cv); return (ret); } return (0); } /* * _citrus_iconv_close: * close the specified converter. */ void _citrus_iconv_close(struct _citrus_iconv *cv) { if (cv) { (*cv->cv_shared->ci_ops->io_uninit_context)(cv); release_shared(cv->cv_shared); free(cv); } } const char *_citrus_iconv_canonicalize(const char *name) { char *buf; if ((buf = calloc((size_t)PATH_MAX, sizeof(*buf))) == NULL) return (NULL); _citrus_esdb_alias(name, buf, (size_t)PATH_MAX); return (buf); }