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