1 /* $NetBSD: citrus_iconv.c,v 1.7 2008/07/25 14:05:25 christos 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.7 2008/07/25 14:05:25 christos Exp $"); 32 #endif /* LIBC_SCCS and not lint */ 33 34 #include "namespace.h" 35 #include "reentrant.h" 36 #include <assert.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <errno.h> 41 #include <limits.h> 42 #include <unistd.h> 43 #include <paths.h> 44 #include <dirent.h> 45 #include <sys/types.h> 46 #include <sys/queue.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 #ifdef _REENTRANT 66 static rwlock_t lock = RWLOCK_INITIALIZER; 67 #endif 68 static int isinit = 0; 69 static _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool; 70 static TAILQ_HEAD(, _citrus_iconv_shared) shared_unused; 71 static int shared_num_unused, shared_max_reuse; 72 73 static __inline void 74 init_cache(void) 75 { 76 rwlock_wrlock(&lock); 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 rwlock_unlock(&lock); 88 } 89 90 /* 91 * lookup_iconv_entry: 92 * lookup iconv.dir entry in the specified directory. 93 * 94 * line format of iconv.dir file: 95 * key module arg 96 * key : lookup key. 97 * module : iconv module name. 98 * arg : argument for the module (generally, description file name) 99 * 100 */ 101 static __inline int 102 lookup_iconv_entry(const char *curdir, const char *key, 103 char *linebuf, size_t linebufsize, 104 const char **module, const char **variable) 105 { 106 const char *cp, *cq; 107 char *p, path[PATH_MAX]; 108 109 /* iconv.dir path */ 110 snprintf(path, (size_t)PATH_MAX, "%s/" _CITRUS_ICONV_DIR, curdir); 111 112 /* lookup db */ 113 cp = p = _lookup_simple(path, key, linebuf, linebufsize, 114 _LOOKUP_CASE_IGNORE); 115 if (p == NULL) 116 return ENOENT; 117 118 /* get module name */ 119 *module = p; 120 cq = _bcs_skip_nonws(cp); 121 p[cq-cp] = '\0'; 122 p += cq-cp+1; 123 cq++; 124 125 /* get variable */ 126 cp = _bcs_skip_ws(cq); 127 *variable = p += cp - cq; 128 cq = _bcs_skip_nonws(cp); 129 p[cq-cp] = '\0'; 130 131 return 0; 132 } 133 134 static __inline void 135 close_shared(struct _citrus_iconv_shared *ci) 136 { 137 if (ci) { 138 if (ci->ci_module) { 139 if (ci->ci_ops) { 140 if (ci->ci_closure) 141 (*ci->ci_ops->io_uninit_shared)(ci); 142 free(ci->ci_ops); 143 } 144 _citrus_unload_module(ci->ci_module); 145 } 146 free(ci); 147 } 148 } 149 150 static __inline int 151 open_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 152 const char * __restrict basedir, const char * __restrict convname, 153 const char * __restrict src, const char * __restrict dst) 154 { 155 int ret; 156 struct _citrus_iconv_shared *ci; 157 _citrus_iconv_getops_t getops; 158 char linebuf[LINE_MAX]; 159 const char *module, *variable; 160 size_t len_convname; 161 162 /* search converter entry */ 163 ret = lookup_iconv_entry(basedir, convname, linebuf, sizeof(linebuf), 164 &module, &variable); 165 if (ret) { 166 if (ret == ENOENT) 167 /* fallback */ 168 ret = lookup_iconv_entry(basedir, "*", 169 linebuf, sizeof(linebuf), 170 &module, &variable); 171 if (ret) 172 return ret; 173 } 174 175 /* initialize iconv handle */ 176 len_convname = strlen(convname); 177 ci = malloc(sizeof(*ci)+len_convname+1); 178 if (!ci) { 179 ret = errno; 180 goto err; 181 } 182 ci->ci_module = NULL; 183 ci->ci_ops = NULL; 184 ci->ci_closure = NULL; 185 ci->ci_convname = (void *)&ci[1]; 186 memcpy(ci->ci_convname, convname, len_convname+1); 187 188 /* load module */ 189 ret = _citrus_load_module(&ci->ci_module, module); 190 if (ret) 191 goto err; 192 193 /* get operators */ 194 getops = (_citrus_iconv_getops_t) 195 _citrus_find_getops(ci->ci_module, module, "iconv"); 196 if (!getops) { 197 ret = EOPNOTSUPP; 198 goto err; 199 } 200 ci->ci_ops = malloc(sizeof(*ci->ci_ops)); 201 if (!ci->ci_ops) { 202 ret = errno; 203 goto err; 204 } 205 ret = (*getops)(ci->ci_ops, sizeof(*ci->ci_ops), 206 _CITRUS_ICONV_ABI_VERSION); 207 if (ret) 208 goto err; 209 210 /* version check */ 211 if (ci->ci_ops->io_abi_version == 1) { 212 /* binary compatibility broken at ver.2 */ 213 ret = EINVAL; 214 goto err; 215 } 216 217 if (ci->ci_ops->io_init_shared == NULL || 218 ci->ci_ops->io_uninit_shared == NULL || 219 ci->ci_ops->io_init_context == NULL || 220 ci->ci_ops->io_uninit_context == NULL || 221 ci->ci_ops->io_convert == NULL) 222 goto err; 223 224 /* initialize the converter */ 225 ret = (*ci->ci_ops->io_init_shared)(ci, basedir, src, dst, 226 (const void *)variable, 227 strlen(variable)+1); 228 if (ret) 229 goto err; 230 231 *rci = ci; 232 233 return 0; 234 err: 235 close_shared(ci); 236 return ret; 237 } 238 239 static __inline int 240 hash_func(const char *key) 241 { 242 return _string_hash_func(key, CI_HASH_SIZE); 243 } 244 245 static __inline int 246 match_func(struct _citrus_iconv_shared * __restrict ci, 247 const char * __restrict key) 248 { 249 return strcmp(ci->ci_convname, key); 250 } 251 252 static int 253 get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 254 const char *basedir, const char *src, const char *dst) 255 { 256 int ret = 0; 257 int hashval; 258 struct _citrus_iconv_shared * ci; 259 char convname[PATH_MAX]; 260 261 snprintf(convname, sizeof(convname), "%s/%s", src, dst); 262 263 rwlock_wrlock(&lock); 264 265 /* lookup alread existing entry */ 266 hashval = hash_func(convname); 267 _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func, 268 convname, hashval); 269 if (ci != NULL) { 270 /* found */ 271 if (ci->ci_used_count == 0) { 272 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 273 shared_num_unused--; 274 } 275 ci->ci_used_count++; 276 *rci = ci; 277 goto quit; 278 } 279 280 /* create new entry */ 281 ret = open_shared(&ci, basedir, convname, src, dst); 282 if (ret) 283 goto quit; 284 285 _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval); 286 ci->ci_used_count = 1; 287 *rci = ci; 288 289 quit: 290 rwlock_unlock(&lock); 291 292 return ret; 293 } 294 295 static void 296 release_shared(struct _citrus_iconv_shared * __restrict ci) 297 { 298 rwlock_wrlock(&lock); 299 300 ci->ci_used_count--; 301 if (ci->ci_used_count == 0) { 302 /* put it into unused list */ 303 shared_num_unused++; 304 TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry); 305 /* flood out */ 306 while (shared_num_unused > shared_max_reuse) { 307 ci = TAILQ_FIRST(&shared_unused); 308 _DIAGASSERT(ci != NULL); 309 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 310 _CITRUS_HASH_REMOVE(ci, ci_hash_entry); 311 shared_num_unused--; 312 close_shared(ci); 313 } 314 } 315 316 rwlock_unlock(&lock); 317 } 318 319 /* 320 * _citrus_iconv_open: 321 * open a converter for the specified in/out codes. 322 */ 323 int 324 _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv, 325 const char * __restrict basedir, 326 const char * __restrict src, const char * __restrict dst) 327 { 328 int ret; 329 struct _citrus_iconv_shared *ci = NULL; 330 struct _citrus_iconv *cv; 331 char realsrc[PATH_MAX], realdst[PATH_MAX]; 332 char buf[PATH_MAX], path[PATH_MAX]; 333 334 init_cache(); 335 336 /* resolve codeset name aliases */ 337 snprintf(path, sizeof(path), "%s/%s", basedir, _CITRUS_ICONV_ALIAS); 338 strlcpy(realsrc, 339 _lookup_alias(path, src, buf, (size_t)PATH_MAX, 340 _LOOKUP_CASE_IGNORE), 341 (size_t)PATH_MAX); 342 strlcpy(realdst, 343 _lookup_alias(path, dst, buf, (size_t)PATH_MAX, 344 _LOOKUP_CASE_IGNORE), 345 (size_t)PATH_MAX); 346 347 /* sanity check */ 348 if (strchr(realsrc, '/') != NULL || strchr(realdst, '/')) 349 return EINVAL; 350 351 /* get shared record */ 352 ret = get_shared(&ci, basedir, realsrc, realdst); 353 if (ret) 354 return ret; 355 356 /* create/init context */ 357 cv = malloc(sizeof(*cv)); 358 if (cv == NULL) { 359 ret = errno; 360 release_shared(ci); 361 return ret; 362 } 363 cv->cv_shared = ci; 364 ret = (*ci->ci_ops->io_init_context)(cv); 365 if (ret) { 366 release_shared(ci); 367 free(cv); 368 return ret; 369 } 370 *rcv = cv; 371 372 return 0; 373 } 374 375 /* 376 * _citrus_iconv_close: 377 * close the specified converter. 378 */ 379 void 380 _citrus_iconv_close(struct _citrus_iconv *cv) 381 { 382 if (cv) { 383 (*cv->cv_shared->ci_ops->io_uninit_context)(cv); 384 release_shared(cv->cv_shared); 385 free(cv); 386 } 387 } 388