1219019Sgabor/* $FreeBSD: releng/11.0/lib/libc/iconv/citrus_iconv.c 279404 2015-02-28 20:30:25Z kan $ */ 2263986Stijl/* $NetBSD: citrus_iconv.c,v 1.10 2011/11/19 18:34:21 tnozaki 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 || 172263986Stijl ci->ci_ops->io_convert == NULL) { 173263986Stijl ret = EINVAL; 174219019Sgabor goto err; 175263986Stijl } 176219019Sgabor 177219019Sgabor /* initialize the converter */ 178219019Sgabor ret = (*ci->ci_ops->io_init_shared)(ci, src, dst); 179219019Sgabor if (ret) 180219019Sgabor goto err; 181219019Sgabor 182219019Sgabor *rci = ci; 183219019Sgabor 184219019Sgabor return (0); 185219019Sgaborerr: 186219019Sgabor close_shared(ci); 187219019Sgabor return (ret); 188219019Sgabor} 189219019Sgabor 190219019Sgaborstatic __inline int 191219019Sgaborhash_func(const char *key) 192219019Sgabor{ 193219019Sgabor 194219019Sgabor return (_string_hash_func(key, CI_HASH_SIZE)); 195219019Sgabor} 196219019Sgabor 197219019Sgaborstatic __inline int 198219019Sgabormatch_func(struct _citrus_iconv_shared * __restrict ci, 199219019Sgabor const char * __restrict key) 200219019Sgabor{ 201219019Sgabor 202219019Sgabor return (strcmp(ci->ci_convname, key)); 203219019Sgabor} 204219019Sgabor 205219019Sgaborstatic int 206219019Sgaborget_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 207219019Sgabor const char *src, const char *dst) 208219019Sgabor{ 209219019Sgabor struct _citrus_iconv_shared * ci; 210219019Sgabor char convname[PATH_MAX]; 211219019Sgabor int hashval, ret = 0; 212219019Sgabor 213219019Sgabor snprintf(convname, sizeof(convname), "%s/%s", src, dst); 214219019Sgabor 215252584Speter WLOCK(&ci_lock); 216219019Sgabor 217219019Sgabor /* lookup alread existing entry */ 218219019Sgabor hashval = hash_func(convname); 219219019Sgabor _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func, 220219019Sgabor convname, hashval); 221219019Sgabor if (ci != NULL) { 222219019Sgabor /* found */ 223219019Sgabor if (ci->ci_used_count == 0) { 224219019Sgabor TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 225219019Sgabor shared_num_unused--; 226219019Sgabor } 227219019Sgabor ci->ci_used_count++; 228219019Sgabor *rci = ci; 229219019Sgabor goto quit; 230219019Sgabor } 231219019Sgabor 232219019Sgabor /* create new entry */ 233219019Sgabor ret = open_shared(&ci, convname, src, dst); 234219019Sgabor if (ret) 235219019Sgabor goto quit; 236219019Sgabor 237219019Sgabor _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval); 238219019Sgabor ci->ci_used_count = 1; 239219019Sgabor *rci = ci; 240219019Sgabor 241219019Sgaborquit: 242252584Speter UNLOCK(&ci_lock); 243219019Sgabor 244219019Sgabor return (ret); 245219019Sgabor} 246219019Sgabor 247219019Sgaborstatic void 248219019Sgaborrelease_shared(struct _citrus_iconv_shared * __restrict ci) 249219019Sgabor{ 250219019Sgabor 251252584Speter WLOCK(&ci_lock); 252219019Sgabor ci->ci_used_count--; 253219019Sgabor if (ci->ci_used_count == 0) { 254219019Sgabor /* put it into unused list */ 255219019Sgabor shared_num_unused++; 256219019Sgabor TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry); 257219019Sgabor /* flood out */ 258219019Sgabor while (shared_num_unused > shared_max_reuse) { 259219019Sgabor ci = TAILQ_FIRST(&shared_unused); 260219019Sgabor TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 261219019Sgabor _CITRUS_HASH_REMOVE(ci, ci_hash_entry); 262219019Sgabor shared_num_unused--; 263219019Sgabor close_shared(ci); 264219019Sgabor } 265219019Sgabor } 266219019Sgabor 267252584Speter UNLOCK(&ci_lock); 268219019Sgabor} 269219019Sgabor 270219019Sgabor/* 271219019Sgabor * _citrus_iconv_open: 272219019Sgabor * open a converter for the specified in/out codes. 273219019Sgabor */ 274219019Sgaborint 275219019Sgabor_citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv, 276219019Sgabor const char * __restrict src, const char * __restrict dst) 277219019Sgabor{ 278250980Sed struct _citrus_iconv *cv = NULL; 279219019Sgabor struct _citrus_iconv_shared *ci = NULL; 280219019Sgabor char realdst[PATH_MAX], realsrc[PATH_MAX]; 281279404Skan#ifdef _PATH_ICONV 282219019Sgabor char buf[PATH_MAX], path[PATH_MAX]; 283279404Skan#endif 284219019Sgabor int ret; 285219019Sgabor 286219019Sgabor init_cache(); 287219019Sgabor 288219019Sgabor /* GNU behaviour, using locale encoding if "" or "char" is specified */ 289219019Sgabor if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0)) 290219019Sgabor src = nl_langinfo(CODESET); 291219019Sgabor if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0)) 292219019Sgabor dst = nl_langinfo(CODESET); 293219019Sgabor 294219019Sgabor /* resolve codeset name aliases */ 295279404Skan#ifdef _PATH_ICONV 296279404Skan snprintf(path, sizeof(path), "%s/%s", _PATH_ICONV, _CITRUS_ICONV_ALIAS); 297219019Sgabor strlcpy(realsrc, _lookup_alias(path, src, buf, (size_t)PATH_MAX, 298219019Sgabor _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); 299219019Sgabor strlcpy(realdst, _lookup_alias(path, dst, buf, (size_t)PATH_MAX, 300219019Sgabor _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); 301279404Skan#else 302279404Skan strlcpy(realsrc, src, (size_t)PATH_MAX); 303279404Skan strlcpy(realdst, dst, (size_t)PATH_MAX); 304279404Skan#endif 305219019Sgabor 306219019Sgabor /* sanity check */ 307219019Sgabor if (strchr(realsrc, '/') != NULL || strchr(realdst, '/')) 308219019Sgabor return (EINVAL); 309219019Sgabor 310219019Sgabor /* get shared record */ 311219019Sgabor ret = get_shared(&ci, realsrc, realdst); 312219019Sgabor if (ret) 313219019Sgabor return (ret); 314219019Sgabor 315219019Sgabor /* create/init context */ 316219019Sgabor if (*rcv == NULL) { 317219019Sgabor cv = malloc(sizeof(*cv)); 318219019Sgabor if (cv == NULL) { 319219019Sgabor ret = errno; 320219019Sgabor release_shared(ci); 321219019Sgabor return (ret); 322219019Sgabor } 323219019Sgabor *rcv = cv; 324219019Sgabor } 325219019Sgabor (*rcv)->cv_shared = ci; 326219019Sgabor ret = (*ci->ci_ops->io_init_context)(*rcv); 327219019Sgabor if (ret) { 328219019Sgabor release_shared(ci); 329250980Sed free(cv); 330219019Sgabor return (ret); 331219019Sgabor } 332219019Sgabor return (0); 333219019Sgabor} 334219019Sgabor 335219019Sgabor/* 336219019Sgabor * _citrus_iconv_close: 337219019Sgabor * close the specified converter. 338219019Sgabor */ 339219019Sgaborvoid 340219019Sgabor_citrus_iconv_close(struct _citrus_iconv *cv) 341219019Sgabor{ 342219019Sgabor 343219019Sgabor if (cv) { 344219019Sgabor (*cv->cv_shared->ci_ops->io_uninit_context)(cv); 345219019Sgabor release_shared(cv->cv_shared); 346219019Sgabor free(cv); 347219019Sgabor } 348219019Sgabor} 349219019Sgabor 350219019Sgaborconst char 351219019Sgabor*_citrus_iconv_canonicalize(const char *name) 352219019Sgabor{ 353219019Sgabor char *buf; 354219019Sgabor 355267437Stijl if ((buf = calloc((size_t)PATH_MAX, sizeof(*buf))) == NULL) 356219019Sgabor return (NULL); 357219019Sgabor _citrus_esdb_alias(name, buf, (size_t)PATH_MAX); 358219019Sgabor return (buf); 359219019Sgabor} 360