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 don't do this either. 130 */ 131 module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none"; 132 #else 133 module = "iconv_std"; 134 #endif 135 136 /* initialize iconv handle */ 137 len_convname = strlen(convname); 138 ci = malloc(sizeof(*ci) + len_convname + 1); 139 if (!ci) { 140 ret = errno; 141 goto err; 142 } 143 ci->ci_module = NULL; 144 ci->ci_ops = NULL; 145 ci->ci_closure = NULL; 146 ci->ci_convname = (void *)&ci[1]; 147 memcpy(ci->ci_convname, convname, len_convname + 1); 148 149 /* load module */ 150 ret = _citrus_load_module(&ci->ci_module, module); 151 if (ret) 152 goto err; 153 154 /* get operators */ 155 getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module, 156 module, "iconv"); 157 if (!getops) { 158 ret = EOPNOTSUPP; 159 goto err; 160 } 161 ci->ci_ops = malloc(sizeof(*ci->ci_ops)); 162 if (!ci->ci_ops) { 163 ret = errno; 164 goto err; 165 } 166 ret = (*getops)(ci->ci_ops); 167 if (ret) 168 goto err; 169 170 if (ci->ci_ops->io_init_shared == NULL || 171 ci->ci_ops->io_uninit_shared == NULL || 172 ci->ci_ops->io_init_context == NULL || 173 ci->ci_ops->io_uninit_context == NULL || 174 ci->ci_ops->io_convert == NULL) { 175 ret = EINVAL; 176 goto err; 177 } 178 179 /* initialize the converter */ 180 ret = (*ci->ci_ops->io_init_shared)(ci, src, dst); 181 if (ret) 182 goto err; 183 184 *rci = ci; 185 186 return (0); 187 err: 188 close_shared(ci); 189 return (ret); 190 } 191 192 static __inline int 193 hash_func(const char *key) 194 { 195 196 return (_string_hash_func(key, CI_HASH_SIZE)); 197 } 198 199 static __inline int 200 match_func(struct _citrus_iconv_shared * __restrict ci, 201 const char * __restrict key) 202 { 203 204 return (strcmp(ci->ci_convname, key)); 205 } 206 207 static int 208 get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, 209 const char *src, const char *dst) 210 { 211 struct _citrus_iconv_shared * ci; 212 char convname[PATH_MAX]; 213 int hashval, ret = 0; 214 215 snprintf(convname, sizeof(convname), "%s/%s", src, dst); 216 217 WLOCK(&ci_lock); 218 219 /* lookup alread existing entry */ 220 hashval = hash_func(convname); 221 _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func, 222 convname, hashval); 223 if (ci != NULL) { 224 /* found */ 225 if (ci->ci_used_count == 0) { 226 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 227 shared_num_unused--; 228 } 229 ci->ci_used_count++; 230 *rci = ci; 231 goto quit; 232 } 233 234 /* create new entry */ 235 ret = open_shared(&ci, convname, src, dst); 236 if (ret) 237 goto quit; 238 239 _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval); 240 ci->ci_used_count = 1; 241 *rci = ci; 242 243 quit: 244 UNLOCK(&ci_lock); 245 246 return (ret); 247 } 248 249 static void 250 release_shared(struct _citrus_iconv_shared * __restrict ci) 251 { 252 253 WLOCK(&ci_lock); 254 ci->ci_used_count--; 255 if (ci->ci_used_count == 0) { 256 /* put it into unused list */ 257 shared_num_unused++; 258 TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry); 259 /* flood out */ 260 while (shared_num_unused > shared_max_reuse) { 261 ci = TAILQ_FIRST(&shared_unused); 262 TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); 263 _CITRUS_HASH_REMOVE(ci, ci_hash_entry); 264 shared_num_unused--; 265 close_shared(ci); 266 } 267 } 268 269 UNLOCK(&ci_lock); 270 } 271 272 /* 273 * _citrus_iconv_open: 274 * open a converter for the specified in/out codes. 275 */ 276 int 277 _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv, 278 const char * __restrict src, const char * __restrict dst) 279 { 280 struct _citrus_iconv *cv = NULL; 281 struct _citrus_iconv_shared *ci = NULL; 282 char realdst[PATH_MAX], realsrc[PATH_MAX]; 283 #ifdef _PATH_ICONV 284 char buf[PATH_MAX], path[PATH_MAX]; 285 #endif 286 int ret; 287 288 init_cache(); 289 290 /* GNU behaviour, using locale encoding if "" or "char" is specified */ 291 if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0)) 292 src = nl_langinfo(CODESET); 293 if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0)) 294 dst = nl_langinfo(CODESET); 295 296 /* resolve codeset name aliases */ 297 #ifdef _PATH_ICONV 298 snprintf(path, sizeof(path), "%s/%s", _PATH_ICONV, _CITRUS_ICONV_ALIAS); 299 strlcpy(realsrc, _lookup_alias(path, src, buf, (size_t)PATH_MAX, 300 _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); 301 strlcpy(realdst, _lookup_alias(path, dst, buf, (size_t)PATH_MAX, 302 _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); 303 #else 304 strlcpy(realsrc, src, (size_t)PATH_MAX); 305 strlcpy(realdst, dst, (size_t)PATH_MAX); 306 #endif 307 308 /* sanity check */ 309 if (strchr(realsrc, '/') != NULL || strchr(realdst, '/')) 310 return (EINVAL); 311 312 /* get shared record */ 313 ret = get_shared(&ci, realsrc, realdst); 314 if (ret) 315 return (ret); 316 317 /* create/init context */ 318 if (*rcv == NULL) { 319 cv = malloc(sizeof(*cv)); 320 if (cv == NULL) { 321 ret = errno; 322 release_shared(ci); 323 return (ret); 324 } 325 *rcv = cv; 326 } 327 (*rcv)->cv_shared = ci; 328 ret = (*ci->ci_ops->io_init_context)(*rcv); 329 if (ret) { 330 release_shared(ci); 331 free(cv); 332 return (ret); 333 } 334 return (0); 335 } 336 337 /* 338 * _citrus_iconv_close: 339 * close the specified converter. 340 */ 341 void 342 _citrus_iconv_close(struct _citrus_iconv *cv) 343 { 344 345 if (cv) { 346 (*cv->cv_shared->ci_ops->io_uninit_context)(cv); 347 release_shared(cv->cv_shared); 348 free(cv); 349 } 350 } 351 352 const char 353 *_citrus_iconv_canonicalize(const char *name) 354 { 355 char *buf; 356 357 if ((buf = calloc((size_t)PATH_MAX, sizeof(*buf))) == NULL) 358 return (NULL); 359 _citrus_esdb_alias(name, buf, (size_t)PATH_MAX); 360 return (buf); 361 } 362