1 /* $FreeBSD$ */ 2 /* $NetBSD: citrus_iconv.c,v 1.10 2011/11/19 18:34:21 tnozaki Exp $ */ 3 4 /*- 5 * SPDX-License-Identifier: BSD-2-Clause 6 * 7 * Copyright (c)2003 Citrus Project, 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #include <sys/types.h> 34 #include <sys/queue.h> 35 36 #include <assert.h> 37 #include <dirent.h> 38 #include <errno.h> 39 #include <iconv.h> 40 #include <langinfo.h> 41 #include <limits.h> 42 #include <paths.h> 43 #include <stdbool.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <unistd.h> 48 49 #include "citrus_namespace.h" 50 #include "citrus_bcs.h" 51 #include "citrus_esdb.h" 52 #include "citrus_region.h" 53 #include "citrus_memstream.h" 54 #include "citrus_mmap.h" 55 #include "citrus_module.h" 56 #include "citrus_lock.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 static bool isinit = false; 69 static int shared_max_reuse, shared_num_unused; 70 static _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool; 71 static TAILQ_HEAD(, _citrus_iconv_shared) shared_unused; 72 73 static pthread_rwlock_t ci_lock = PTHREAD_RWLOCK_INITIALIZER; 74 75 static __inline void 76 init_cache(void) 77 { 78 79 WLOCK(&ci_lock); 80 if (!isinit) { 81 _CITRUS_HASH_INIT(&shared_pool, CI_HASH_SIZE); 82 TAILQ_INIT(&shared_unused); 83 shared_max_reuse = -1; 84 if (!issetugid() && getenv(CI_ENV_MAX_REUSE)) 85 shared_max_reuse = atoi(getenv(CI_ENV_MAX_REUSE)); 86 if (shared_max_reuse < 0) 87 shared_max_reuse = CI_INITIAL_MAX_REUSE; 88 isinit = true; 89 } 90 UNLOCK(&ci_lock); 91 } 92 93 static __inline void 94 close_shared(struct _citrus_iconv_shared *ci) 95 { 96 97 if (ci) { 98 if (ci->ci_module) { 99 if (ci->ci_ops) { 100 if (ci->ci_closure) 101 (*ci->ci_ops->io_uninit_shared)(ci); 102 free(ci->ci_ops); 103 } 104 _citrus_unload_module(ci->ci_module); 105 } 106 free(ci); 107 } 108 } 109 110 static __inline int 111 open_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 112 const char * __restrict convname, const char * __restrict src, 113 const char * __restrict dst) 114 { 115 struct _citrus_iconv_shared *ci; 116 _citrus_iconv_getops_t getops; 117 const char *module; 118 size_t len_convname; 119 int ret; 120 121 #ifdef INCOMPATIBLE_WITH_GNU_ICONV 122 /* 123 * Sadly, the gnu tools expect iconv to actually parse the 124 * byte stream and don't allow for a pass-through when 125 * the (src,dest) encodings are the same. 126 * See gettext-0.18.3+ NEWS: 127 * msgfmt now checks PO file headers more strictly with less 128 * false-positives. 129 * NetBSD, also, doesn't do the below pass-through. 130 * 131 * Also note that this currently falls short if dst options have been 132 * specified. It may be the case that we want to ignore EILSEQ, in which 133 * case we should also select iconv_std anyways. This trick, while 134 * clever, may not be worth it. 135 */ 136 module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none"; 137 #else 138 module = "iconv_std"; 139 #endif 140 141 /* initialize iconv handle */ 142 len_convname = strlen(convname); 143 ci = malloc(sizeof(*ci) + len_convname + 1); 144 if (!ci) { 145 ret = errno; 146 goto err; 147 } 148 ci->ci_module = NULL; 149 ci->ci_ops = NULL; 150 ci->ci_closure = NULL; 151 ci->ci_convname = (void *)&ci[1]; 152 memcpy(ci->ci_convname, convname, len_convname + 1); 153 154 /* load module */ 155 ret = _citrus_load_module(&ci->ci_module, module); 156 if (ret) 157 goto err; 158 159 /* get operators */ 160 getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module, 161 module, "iconv"); 162 if (!getops) { 163 ret = EOPNOTSUPP; 164 goto err; 165 } 166 ci->ci_ops = malloc(sizeof(*ci->ci_ops)); 167 if (!ci->ci_ops) { 168 ret = errno; 169 goto err; 170 } 171 ret = (*getops)(ci->ci_ops); 172 if (ret) 173 goto err; 174 175 if (ci->ci_ops->io_init_shared == NULL || 176 ci->ci_ops->io_uninit_shared == NULL || 177 ci->ci_ops->io_init_context == NULL || 178 ci->ci_ops->io_uninit_context == NULL || 179 ci->ci_ops->io_convert == NULL) { 180 ret = EINVAL; 181 goto err; 182 } 183 184 /* initialize the converter */ 185 ret = (*ci->ci_ops->io_init_shared)(ci, src, dst); 186 if (ret) 187 goto err; 188 189 *rci = ci; 190 191 return (0); 192 err: 193 close_shared(ci); 194 return (ret); 195 } 196 197 static __inline int 198 hash_func(const char *key) 199 { 200 201 return (_string_hash_func(key, CI_HASH_SIZE)); 202 } 203 204 static __inline int 205 match_func(struct _citrus_iconv_shared * __restrict ci, 206 const char * __restrict key) 207 { 208 209 return (strcmp(ci->ci_convname, key)); 210 } 211 212 static int 213 get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 214 const char *src, const char *dst) 215 { 216 struct _citrus_iconv_shared * ci; 217 char convname[PATH_MAX]; 218 int hashval, ret = 0; 219 220 snprintf(convname, sizeof(convname), "%s/%s", src, dst); 221 222 WLOCK(&ci_lock); 223 224 /* lookup alread existing entry */ 225 hashval = hash_func(convname); 226 _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func, 227 convname, hashval); 228 if (ci != NULL) { 229 /* found */ 230 if (ci->ci_used_count == 0) { 231 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 232 shared_num_unused--; 233 } 234 ci->ci_used_count++; 235 *rci = ci; 236 goto quit; 237 } 238 239 /* create new entry */ 240 ret = open_shared(&ci, convname, src, dst); 241 if (ret) 242 goto quit; 243 244 _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval); 245 ci->ci_used_count = 1; 246 *rci = ci; 247 248 quit: 249 UNLOCK(&ci_lock); 250 251 return (ret); 252 } 253 254 static void 255 release_shared(struct _citrus_iconv_shared * __restrict ci) 256 { 257 258 WLOCK(&ci_lock); 259 ci->ci_used_count--; 260 if (ci->ci_used_count == 0) { 261 /* put it into unused list */ 262 shared_num_unused++; 263 TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry); 264 /* flood out */ 265 while (shared_num_unused > shared_max_reuse) { 266 ci = TAILQ_FIRST(&shared_unused); 267 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 268 _CITRUS_HASH_REMOVE(ci, ci_hash_entry); 269 shared_num_unused--; 270 close_shared(ci); 271 } 272 } 273 274 UNLOCK(&ci_lock); 275 } 276 277 /* 278 * _citrus_iconv_open: 279 * open a converter for the specified in/out codes. 280 */ 281 int 282 _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv, 283 const char * __restrict src, const char * __restrict dst) 284 { 285 struct _citrus_iconv *cv = NULL; 286 struct _citrus_iconv_shared *ci = NULL; 287 char realdst[PATH_MAX], realsrc[PATH_MAX], *slashes; 288 #ifdef _PATH_ICONV 289 char buf[PATH_MAX], path[PATH_MAX]; 290 #endif 291 int ret; 292 293 init_cache(); 294 295 /* GNU behaviour, using locale encoding if "" or "char" is specified */ 296 if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0)) 297 src = nl_langinfo(CODESET); 298 if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0)) 299 dst = nl_langinfo(CODESET); 300 301 strlcpy(realsrc, src, (size_t)PATH_MAX); 302 if ((slashes = strstr(realsrc, "//")) != NULL) 303 *slashes = '\0'; 304 strlcpy(realdst, dst, (size_t)PATH_MAX); 305 if ((slashes = strstr(realdst, "//")) != NULL) 306 *slashes = '\0'; 307 308 /* resolve codeset name aliases */ 309 #ifdef _PATH_ICONV 310 /* 311 * Note that the below reads from realsrc and realdst while it's 312 * repopulating (writing to) realsrc and realdst, but it's done so with 313 * a trip through `buf`. 314 */ 315 snprintf(path, sizeof(path), "%s/%s", _PATH_ICONV, _CITRUS_ICONV_ALIAS); 316 strlcpy(realsrc, _lookup_alias(path, realsrc, buf, (size_t)PATH_MAX, 317 _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); 318 strlcpy(realdst, _lookup_alias(path, realdst, buf, (size_t)PATH_MAX, 319 _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); 320 #endif 321 322 /* sanity check */ 323 if (strchr(realsrc, '/') != NULL || strchr(realdst, '/')) 324 return (EINVAL); 325 326 /* get shared record */ 327 ret = get_shared(&ci, realsrc, realdst); 328 if (ret) 329 return (ret); 330 331 /* create/init context */ 332 if (*rcv == NULL) { 333 cv = malloc(sizeof(*cv)); 334 if (cv == NULL) { 335 ret = errno; 336 release_shared(ci); 337 return (ret); 338 } 339 *rcv = cv; 340 } 341 (*rcv)->cv_shared = ci; 342 ret = (*ci->ci_ops->io_init_context)(*rcv); 343 if (ret) { 344 release_shared(ci); 345 free(cv); 346 return (ret); 347 } 348 return (0); 349 } 350 351 /* 352 * _citrus_iconv_close: 353 * close the specified converter. 354 */ 355 void 356 _citrus_iconv_close(struct _citrus_iconv *cv) 357 { 358 359 if (cv) { 360 (*cv->cv_shared->ci_ops->io_uninit_context)(cv); 361 release_shared(cv->cv_shared); 362 free(cv); 363 } 364 } 365 366 const char 367 *_citrus_iconv_canonicalize(const char *name) 368 { 369 char *buf; 370 371 if ((buf = calloc((size_t)PATH_MAX, sizeof(*buf))) == NULL) 372 return (NULL); 373 _citrus_esdb_alias(name, buf, (size_t)PATH_MAX); 374 return (buf); 375 } 376