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