1 /* $NetBSD: citrus_iconv.c,v 1.10 2011/11/19 18:34:21 tnozaki Exp $ */ 2 3 /*- 4 * SPDX-License-Identifier: BSD-2-Clause 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 <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 66 static bool isinit = false; 67 static int shared_max_reuse, shared_num_unused; 68 static _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool; 69 static TAILQ_HEAD(, _citrus_iconv_shared) shared_unused; 70 71 static pthread_rwlock_t ci_lock = PTHREAD_RWLOCK_INITIALIZER; 72 73 static __inline void 74 init_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 (secure_getenv(CI_ENV_MAX_REUSE) != NULL) 83 shared_max_reuse = 84 atoi(secure_getenv(CI_ENV_MAX_REUSE)); 85 if (shared_max_reuse < 0) 86 shared_max_reuse = CI_INITIAL_MAX_REUSE; 87 isinit = true; 88 } 89 UNLOCK(&ci_lock); 90 } 91 92 static __inline void 93 close_shared(struct _citrus_iconv_shared *ci) 94 { 95 96 if (ci) { 97 if (ci->ci_module) { 98 if (ci->ci_ops) { 99 if (ci->ci_closure) 100 (*ci->ci_ops->io_uninit_shared)(ci); 101 free(ci->ci_ops); 102 } 103 _citrus_unload_module(ci->ci_module); 104 } 105 free(ci); 106 } 107 } 108 109 static __inline int 110 open_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 111 const char * __restrict convname, const char * __restrict src, 112 const char * __restrict dst) 113 { 114 struct _citrus_iconv_shared *ci; 115 _citrus_iconv_getops_t getops; 116 const char *module; 117 size_t len_convname; 118 int ret; 119 120 #ifdef INCOMPATIBLE_WITH_GNU_ICONV 121 /* 122 * Sadly, the gnu tools expect iconv to actually parse the 123 * byte stream and don't allow for a pass-through when 124 * the (src,dest) encodings are the same. 125 * See gettext-0.18.3+ NEWS: 126 * msgfmt now checks PO file headers more strictly with less 127 * false-positives. 128 * NetBSD, also, doesn't do the below pass-through. 129 * 130 * Also note that this currently falls short if dst options have been 131 * specified. It may be the case that we want to ignore EILSEQ, in which 132 * case we should also select iconv_std anyways. This trick, while 133 * clever, may not be worth it. 134 */ 135 module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none"; 136 #else 137 module = "iconv_std"; 138 #endif 139 140 /* initialize iconv handle */ 141 len_convname = strlen(convname); 142 ci = calloc(1, sizeof(*ci) + len_convname + 1); 143 if (!ci) { 144 ret = errno; 145 goto err; 146 } 147 ci->ci_convname = (void *)&ci[1]; 148 memcpy(ci->ci_convname, convname, len_convname + 1); 149 150 /* load module */ 151 ret = _citrus_load_module(&ci->ci_module, module); 152 if (ret) 153 goto err; 154 155 /* get operators */ 156 getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module, 157 module, "iconv"); 158 if (!getops) { 159 ret = EOPNOTSUPP; 160 goto err; 161 } 162 ci->ci_ops = malloc(sizeof(*ci->ci_ops)); 163 if (!ci->ci_ops) { 164 ret = errno; 165 goto err; 166 } 167 ret = (*getops)(ci->ci_ops); 168 if (ret) 169 goto err; 170 171 if (ci->ci_ops->io_init_shared == NULL || 172 ci->ci_ops->io_uninit_shared == NULL || 173 ci->ci_ops->io_init_context == NULL || 174 ci->ci_ops->io_uninit_context == NULL || 175 ci->ci_ops->io_convert == NULL) { 176 ret = EINVAL; 177 goto err; 178 } 179 180 /* initialize the converter */ 181 ret = (*ci->ci_ops->io_init_shared)(ci, src, dst); 182 if (ret) 183 goto err; 184 185 *rci = ci; 186 187 return (0); 188 err: 189 close_shared(ci); 190 return (ret); 191 } 192 193 static __inline int 194 hash_func(const char *key) 195 { 196 197 return (_string_hash_func(key, CI_HASH_SIZE)); 198 } 199 200 static __inline int 201 match_func(struct _citrus_iconv_shared * __restrict ci, 202 const char * __restrict key) 203 { 204 205 return (strcmp(ci->ci_convname, key)); 206 } 207 208 static int 209 get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 210 const char *src, const char *dst) 211 { 212 struct _citrus_iconv_shared * ci; 213 char convname[PATH_MAX]; 214 int hashval, ret = 0; 215 216 snprintf(convname, sizeof(convname), "%s/%s", src, dst); 217 218 WLOCK(&ci_lock); 219 220 /* lookup alread existing entry */ 221 hashval = hash_func(convname); 222 _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func, 223 convname, hashval); 224 if (ci != NULL) { 225 /* found */ 226 if (ci->ci_used_count == 0) { 227 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 228 shared_num_unused--; 229 } 230 ci->ci_used_count++; 231 *rci = ci; 232 goto quit; 233 } 234 235 /* create new entry */ 236 ret = open_shared(&ci, convname, src, dst); 237 if (ret) 238 goto quit; 239 240 _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval); 241 ci->ci_used_count = 1; 242 *rci = ci; 243 244 quit: 245 UNLOCK(&ci_lock); 246 247 return (ret); 248 } 249 250 static void 251 release_shared(struct _citrus_iconv_shared * __restrict ci) 252 { 253 254 WLOCK(&ci_lock); 255 ci->ci_used_count--; 256 if (ci->ci_used_count == 0) { 257 /* put it into unused list */ 258 shared_num_unused++; 259 TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry); 260 /* flood out */ 261 while (shared_num_unused > shared_max_reuse) { 262 ci = TAILQ_FIRST(&shared_unused); 263 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 264 _CITRUS_HASH_REMOVE(ci, ci_hash_entry); 265 shared_num_unused--; 266 close_shared(ci); 267 } 268 } 269 270 UNLOCK(&ci_lock); 271 } 272 273 /* 274 * _citrus_iconv_open: 275 * open a converter for the specified in/out codes. 276 */ 277 int 278 _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv, 279 const char * __restrict src, const char * __restrict dst) 280 { 281 struct _citrus_iconv *cv = NULL; 282 struct _citrus_iconv_shared *ci = NULL; 283 char realdst[PATH_MAX], realsrc[PATH_MAX], *slashes; 284 #ifdef _PATH_ICONV 285 char buf[PATH_MAX], path[PATH_MAX]; 286 #endif 287 int ret; 288 289 init_cache(); 290 291 /* GNU behaviour, using locale encoding if "" or "char" is specified */ 292 if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0)) 293 src = nl_langinfo(CODESET); 294 if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0)) 295 dst = nl_langinfo(CODESET); 296 297 strlcpy(realsrc, src, (size_t)PATH_MAX); 298 if ((slashes = strstr(realsrc, "//")) != NULL) 299 *slashes = '\0'; 300 strlcpy(realdst, dst, (size_t)PATH_MAX); 301 if ((slashes = strstr(realdst, "//")) != NULL) 302 *slashes = '\0'; 303 304 /* resolve codeset name aliases */ 305 #ifdef _PATH_ICONV 306 /* 307 * Note that the below reads from realsrc and realdst while it's 308 * repopulating (writing to) realsrc and realdst, but it's done so with 309 * a trip through `buf`. 310 */ 311 snprintf(path, sizeof(path), "%s/%s", _PATH_ICONV, _CITRUS_ICONV_ALIAS); 312 strlcpy(realsrc, _lookup_alias(path, realsrc, buf, (size_t)PATH_MAX, 313 _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); 314 strlcpy(realdst, _lookup_alias(path, realdst, buf, (size_t)PATH_MAX, 315 _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); 316 #endif 317 318 /* sanity check */ 319 if (strchr(realsrc, '/') != NULL || strchr(realdst, '/')) 320 return (EINVAL); 321 322 /* get shared record */ 323 ret = get_shared(&ci, realsrc, realdst); 324 if (ret) 325 return (ret); 326 327 /* create/init context */ 328 if (*rcv == NULL) { 329 cv = malloc(sizeof(*cv)); 330 if (cv == NULL) { 331 ret = errno; 332 release_shared(ci); 333 return (ret); 334 } 335 *rcv = cv; 336 } 337 (*rcv)->cv_shared = ci; 338 ret = (*ci->ci_ops->io_init_context)(*rcv); 339 if (ret) { 340 release_shared(ci); 341 free(cv); 342 return (ret); 343 } 344 return (0); 345 } 346 347 /* 348 * _citrus_iconv_close: 349 * close the specified converter. 350 */ 351 void 352 _citrus_iconv_close(struct _citrus_iconv *cv) 353 { 354 355 if (cv) { 356 (*cv->cv_shared->ci_ops->io_uninit_context)(cv); 357 release_shared(cv->cv_shared); 358 free(cv); 359 } 360 } 361 362 const char 363 *_citrus_iconv_canonicalize(const char *name) 364 { 365 char *buf; 366 367 if ((buf = calloc((size_t)PATH_MAX, sizeof(*buf))) == NULL) 368 return (NULL); 369 _citrus_esdb_alias(name, buf, (size_t)PATH_MAX); 370 return (buf); 371 } 372