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