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