1 /* $NetBSD: citrus_iconv.c,v 1.11 2019/10/09 23:24:00 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.11 2019/10/09 23:24:00 christos 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 ret = EINVAL; 227 goto err; 228 } 229 230 /* initialize the converter */ 231 ret = (*ci->ci_ops->io_init_shared)(ci, basedir, src, dst, 232 (const void *)variable, 233 strlen(variable)+1); 234 if (ret) 235 goto err; 236 237 *rci = ci; 238 239 return 0; 240 err: 241 close_shared(ci); 242 return ret; 243 } 244 245 static __inline int 246 hash_func(const char *key) 247 { 248 return _string_hash_func(key, CI_HASH_SIZE); 249 } 250 251 static __inline int 252 match_func(struct _citrus_iconv_shared * __restrict ci, 253 const char * __restrict key) 254 { 255 return strcmp(ci->ci_convname, key); 256 } 257 258 static int 259 get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 260 const char *basedir, const char *src, const char *dst) 261 { 262 int ret = 0; 263 int hashval; 264 struct _citrus_iconv_shared * ci; 265 char convname[2 * PATH_MAX]; 266 267 snprintf(convname, sizeof(convname), "%s/%s", src, dst); 268 269 rwlock_wrlock(&lock); 270 271 /* lookup alread existing entry */ 272 hashval = hash_func(convname); 273 _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func, 274 convname, hashval); 275 if (ci != NULL) { 276 /* found */ 277 if (ci->ci_used_count == 0) { 278 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 279 shared_num_unused--; 280 } 281 ci->ci_used_count++; 282 *rci = ci; 283 goto quit; 284 } 285 286 /* create new entry */ 287 ret = open_shared(&ci, basedir, convname, src, dst); 288 if (ret) 289 goto quit; 290 291 _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval); 292 ci->ci_used_count = 1; 293 *rci = ci; 294 295 quit: 296 rwlock_unlock(&lock); 297 298 return ret; 299 } 300 301 static void 302 release_shared(struct _citrus_iconv_shared * __restrict ci) 303 { 304 rwlock_wrlock(&lock); 305 306 ci->ci_used_count--; 307 if (ci->ci_used_count == 0) { 308 /* put it into unused list */ 309 shared_num_unused++; 310 TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry); 311 /* flood out */ 312 while (shared_num_unused > shared_max_reuse) { 313 ci = TAILQ_FIRST(&shared_unused); 314 _DIAGASSERT(ci != NULL); 315 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 316 _CITRUS_HASH_REMOVE(ci, ci_hash_entry); 317 shared_num_unused--; 318 close_shared(ci); 319 } 320 } 321 322 rwlock_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 = NULL; 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, (size_t)PATH_MAX, 346 _LOOKUP_CASE_IGNORE), 347 (size_t)PATH_MAX); 348 strlcpy(realdst, 349 _lookup_alias(path, dst, buf, (size_t)PATH_MAX, 350 _LOOKUP_CASE_IGNORE), 351 (size_t)PATH_MAX); 352 353 /* sanity check */ 354 if (strchr(realsrc, '/') != NULL || strchr(realdst, '/')) 355 return EINVAL; 356 357 /* get shared record */ 358 ret = get_shared(&ci, basedir, realsrc, realdst); 359 if (ret) 360 return ret; 361 362 /* create/init context */ 363 cv = malloc(sizeof(*cv)); 364 if (cv == NULL) { 365 ret = errno; 366 release_shared(ci); 367 return ret; 368 } 369 cv->cv_shared = ci; 370 ret = (*ci->ci_ops->io_init_context)(cv); 371 if (ret) { 372 release_shared(ci); 373 free(cv); 374 return ret; 375 } 376 *rcv = cv; 377 378 return 0; 379 } 380 381 /* 382 * _citrus_iconv_close: 383 * close the specified converter. 384 */ 385 void 386 _citrus_iconv_close(struct _citrus_iconv *cv) 387 { 388 if (cv) { 389 (*cv->cv_shared->ci_ops->io_uninit_context)(cv); 390 release_shared(cv->cv_shared); 391 free(cv); 392 } 393 } 394