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