diff --git a/lib/libc/iconv/bsd_iconv.c b/lib/libc/iconv/bsd_iconv.c index 0fcff0e2d1b8..5a0f523b9d15 100644 --- a/lib/libc/iconv/bsd_iconv.c +++ b/lib/libc/iconv/bsd_iconv.c @@ -1,327 +1,313 @@ /* $FreeBSD$ */ /* $NetBSD: iconv.c,v 1.11 2009/03/03 16:22:33 explorer Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2003 Citrus Project, * Copyright (c) 2009, 2010 Gabor Kovesdan , * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "citrus_types.h" #include "citrus_module.h" #include "citrus_esdb.h" #include "citrus_hash.h" #include "citrus_iconv.h" #include "iconv-internal.h" #define ISBADF(_h_) (!(_h_) || (_h_) == (iconv_t)-1) static iconv_t __bsd___iconv_open(const char *out, const char *in, struct _citrus_iconv *handle) { - const char *out_slashes; - char *out_noslashes; int ret; /* * Remove anything following a //, as these are options (like * //ignore, //translate, etc) and we just don't handle them. * This is for compatibility with software that uses these * blindly. */ - out_slashes = strstr(out, "//"); - if (out_slashes != NULL) { - out_noslashes = strndup(out, out_slashes - out); - if (out_noslashes == NULL) { - errno = ENOMEM; - return ((iconv_t)-1); - } - ret = _citrus_iconv_open(&handle, in, out_noslashes); - free(out_noslashes); - } else { - ret = _citrus_iconv_open(&handle, in, out); - } - + ret = _citrus_iconv_open(&handle, in, out); if (ret) { errno = ret == ENOENT ? EINVAL : ret; return ((iconv_t)-1); } handle->cv_shared->ci_discard_ilseq = strcasestr(out, "//IGNORE"); handle->cv_shared->ci_ilseq_invalid = false; handle->cv_shared->ci_hooks = NULL; return ((iconv_t)(void *)handle); } iconv_t __bsd_iconv_open(const char *out, const char *in) { return (__bsd___iconv_open(out, in, NULL)); } int __bsd_iconv_open_into(const char *out, const char *in, iconv_allocation_t *ptr) { struct _citrus_iconv *handle; handle = (struct _citrus_iconv *)ptr; return ((__bsd___iconv_open(out, in, handle) == (iconv_t)-1) ? -1 : 0); } int __bsd_iconv_close(iconv_t handle) { if (ISBADF(handle)) { errno = EBADF; return (-1); } _citrus_iconv_close((struct _citrus_iconv *)(void *)handle); return (0); } size_t __bsd_iconv(iconv_t handle, char **in, size_t *szin, char **out, size_t *szout) { size_t ret; int err; if (ISBADF(handle)) { errno = EBADF; return ((size_t)-1); } err = _citrus_iconv_convert((struct _citrus_iconv *)(void *)handle, in, szin, out, szout, 0, &ret); if (err) { errno = err; ret = (size_t)-1; } return (ret); } size_t __bsd___iconv(iconv_t handle, char **in, size_t *szin, char **out, size_t *szout, uint32_t flags, size_t *invalids) { size_t ret; int err; if (ISBADF(handle)) { errno = EBADF; return ((size_t)-1); } err = _citrus_iconv_convert((struct _citrus_iconv *)(void *)handle, in, szin, out, szout, flags, &ret); if (invalids) *invalids = ret; if (err) { errno = err; ret = (size_t)-1; } return (ret); } int __bsd___iconv_get_list(char ***rlist, size_t *rsz, bool sorted) { int ret; ret = _citrus_esdb_get_list(rlist, rsz, sorted); if (ret) { errno = ret; return (-1); } return (0); } void __bsd___iconv_free_list(char **list, size_t sz) { _citrus_esdb_free_list(list, sz); } /* * GNU-compatibile non-standard interfaces. */ static int qsort_helper(const void *first, const void *second) { const char * const *s1; const char * const *s2; s1 = first; s2 = second; return (strcmp(*s1, *s2)); } void __bsd_iconvlist(int (*do_one) (unsigned int, const char * const *, void *), void *data) { char **list, **names; const char * const *np; char *curitem, *curkey, *slashpos; size_t sz; unsigned int i, j, n; i = 0; names = NULL; if (__bsd___iconv_get_list(&list, &sz, true)) { list = NULL; goto out; } qsort((void *)list, sz, sizeof(char *), qsort_helper); while (i < sz) { j = 0; slashpos = strchr(list[i], '/'); names = malloc(sz * sizeof(char *)); if (names == NULL) goto out; curkey = strndup(list[i], slashpos - list[i]); if (curkey == NULL) goto out; names[j++] = curkey; for (; (i < sz) && (memcmp(curkey, list[i], strlen(curkey)) == 0); i++) { slashpos = strchr(list[i], '/'); if (strcmp(curkey, &slashpos[1]) == 0) continue; curitem = strdup(&slashpos[1]); if (curitem == NULL) goto out; names[j++] = curitem; } np = (const char * const *)names; do_one(j, np, data); for (n = 0; n < j; n++) free(names[n]); free(names); names = NULL; } out: if (names != NULL) { for (n = 0; n < j; n++) free(names[n]); free(names); } if (list != NULL) __bsd___iconv_free_list(list, sz); } __inline const char * __bsd_iconv_canonicalize(const char *name) { return (_citrus_iconv_canonicalize(name)); } int __bsd_iconvctl(iconv_t cd, int request, void *argument) { struct _citrus_iconv *cv; struct iconv_hooks *hooks; const char *convname; char *dst; int *i; size_t srclen; cv = (struct _citrus_iconv *)(void *)cd; hooks = (struct iconv_hooks *)argument; i = (int *)argument; if (ISBADF(cd)) { errno = EBADF; return (-1); } switch (request) { case ICONV_TRIVIALP: convname = cv->cv_shared->ci_convname; dst = strchr(convname, '/'); srclen = dst - convname; dst++; *i = (srclen == strlen(dst)) && !memcmp(convname, dst, srclen); return (0); case ICONV_GET_TRANSLITERATE: *i = 1; return (0); case ICONV_SET_TRANSLITERATE: return ((*i == 1) ? 0 : -1); case ICONV_GET_DISCARD_ILSEQ: *i = cv->cv_shared->ci_discard_ilseq ? 1 : 0; return (0); case ICONV_SET_DISCARD_ILSEQ: cv->cv_shared->ci_discard_ilseq = *i; return (0); case ICONV_SET_HOOKS: cv->cv_shared->ci_hooks = hooks; return (0); case ICONV_SET_FALLBACKS: errno = EOPNOTSUPP; return (-1); case ICONV_GET_ILSEQ_INVALID: *i = cv->cv_shared->ci_ilseq_invalid ? 1 : 0; return (0); case ICONV_SET_ILSEQ_INVALID: cv->cv_shared->ci_ilseq_invalid = *i; return (0); default: errno = EINVAL; return (-1); } } void __bsd_iconv_set_relocation_prefix(const char *orig_prefix __unused, const char *curr_prefix __unused) { } diff --git a/lib/libc/iconv/citrus_iconv.c b/lib/libc/iconv/citrus_iconv.c index fbabf6524399..88dfc2deca33 100644 --- a/lib/libc/iconv/citrus_iconv.c +++ b/lib/libc/iconv/citrus_iconv.c @@ -1,361 +1,375 @@ /* $FreeBSD$ */ /* $NetBSD: citrus_iconv.c,v 1.10 2011/11/19 18:34:21 tnozaki Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c)2003 Citrus Project, * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "citrus_namespace.h" #include "citrus_bcs.h" #include "citrus_esdb.h" #include "citrus_region.h" #include "citrus_memstream.h" #include "citrus_mmap.h" #include "citrus_module.h" #include "citrus_lock.h" #include "citrus_lookup.h" #include "citrus_hash.h" #include "citrus_iconv.h" #define _CITRUS_ICONV_DIR "iconv.dir" #define _CITRUS_ICONV_ALIAS "iconv.alias" #define CI_HASH_SIZE 101 #define CI_INITIAL_MAX_REUSE 5 #define CI_ENV_MAX_REUSE "ICONV_MAX_REUSE" static bool isinit = false; static int shared_max_reuse, shared_num_unused; static _CITRUS_HASH_HEAD(, _citrus_iconv_shared, CI_HASH_SIZE) shared_pool; static TAILQ_HEAD(, _citrus_iconv_shared) shared_unused; static pthread_rwlock_t ci_lock = PTHREAD_RWLOCK_INITIALIZER; static __inline void init_cache(void) { WLOCK(&ci_lock); if (!isinit) { _CITRUS_HASH_INIT(&shared_pool, CI_HASH_SIZE); TAILQ_INIT(&shared_unused); shared_max_reuse = -1; if (!issetugid() && getenv(CI_ENV_MAX_REUSE)) shared_max_reuse = atoi(getenv(CI_ENV_MAX_REUSE)); if (shared_max_reuse < 0) shared_max_reuse = CI_INITIAL_MAX_REUSE; isinit = true; } UNLOCK(&ci_lock); } static __inline void close_shared(struct _citrus_iconv_shared *ci) { if (ci) { if (ci->ci_module) { if (ci->ci_ops) { if (ci->ci_closure) (*ci->ci_ops->io_uninit_shared)(ci); free(ci->ci_ops); } _citrus_unload_module(ci->ci_module); } free(ci); } } static __inline int open_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, const char * __restrict convname, const char * __restrict src, const char * __restrict dst) { struct _citrus_iconv_shared *ci; _citrus_iconv_getops_t getops; const char *module; size_t len_convname; int ret; #ifdef INCOMPATIBLE_WITH_GNU_ICONV /* * Sadly, the gnu tools expect iconv to actually parse the * byte stream and don't allow for a pass-through when * the (src,dest) encodings are the same. * See gettext-0.18.3+ NEWS: * msgfmt now checks PO file headers more strictly with less * false-positives. - * NetBSD don't do this either. + * NetBSD, also, doesn't do the below pass-through. + * + * Also note that this currently falls short if dst options have been + * specified. It may be the case that we want to ignore EILSEQ, in which + * case we should also select iconv_std anyways. This trick, while + * clever, may not be worth it. */ module = (strcmp(src, dst) != 0) ? "iconv_std" : "iconv_none"; #else module = "iconv_std"; #endif /* initialize iconv handle */ len_convname = strlen(convname); ci = malloc(sizeof(*ci) + len_convname + 1); if (!ci) { ret = errno; goto err; } ci->ci_module = NULL; ci->ci_ops = NULL; ci->ci_closure = NULL; ci->ci_convname = (void *)&ci[1]; memcpy(ci->ci_convname, convname, len_convname + 1); /* load module */ ret = _citrus_load_module(&ci->ci_module, module); if (ret) goto err; /* get operators */ getops = (_citrus_iconv_getops_t)_citrus_find_getops(ci->ci_module, module, "iconv"); if (!getops) { ret = EOPNOTSUPP; goto err; } ci->ci_ops = malloc(sizeof(*ci->ci_ops)); if (!ci->ci_ops) { ret = errno; goto err; } ret = (*getops)(ci->ci_ops); if (ret) goto err; if (ci->ci_ops->io_init_shared == NULL || ci->ci_ops->io_uninit_shared == NULL || ci->ci_ops->io_init_context == NULL || ci->ci_ops->io_uninit_context == NULL || ci->ci_ops->io_convert == NULL) { ret = EINVAL; goto err; } /* initialize the converter */ ret = (*ci->ci_ops->io_init_shared)(ci, src, dst); if (ret) goto err; *rci = ci; return (0); err: close_shared(ci); return (ret); } static __inline int hash_func(const char *key) { return (_string_hash_func(key, CI_HASH_SIZE)); } static __inline int match_func(struct _citrus_iconv_shared * __restrict ci, const char * __restrict key) { return (strcmp(ci->ci_convname, key)); } static int get_shared(struct _citrus_iconv_shared * __restrict * __restrict rci, const char *src, const char *dst) { struct _citrus_iconv_shared * ci; char convname[PATH_MAX]; int hashval, ret = 0; snprintf(convname, sizeof(convname), "%s/%s", src, dst); WLOCK(&ci_lock); /* lookup alread existing entry */ hashval = hash_func(convname); _CITRUS_HASH_SEARCH(&shared_pool, ci, ci_hash_entry, match_func, convname, hashval); if (ci != NULL) { /* found */ if (ci->ci_used_count == 0) { TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); shared_num_unused--; } ci->ci_used_count++; *rci = ci; goto quit; } /* create new entry */ ret = open_shared(&ci, convname, src, dst); if (ret) goto quit; _CITRUS_HASH_INSERT(&shared_pool, ci, ci_hash_entry, hashval); ci->ci_used_count = 1; *rci = ci; quit: UNLOCK(&ci_lock); return (ret); } static void release_shared(struct _citrus_iconv_shared * __restrict ci) { WLOCK(&ci_lock); ci->ci_used_count--; if (ci->ci_used_count == 0) { /* put it into unused list */ shared_num_unused++; TAILQ_INSERT_TAIL(&shared_unused, ci, ci_tailq_entry); /* flood out */ while (shared_num_unused > shared_max_reuse) { ci = TAILQ_FIRST(&shared_unused); TAILQ_REMOVE(&shared_unused, ci, ci_tailq_entry); _CITRUS_HASH_REMOVE(ci, ci_hash_entry); shared_num_unused--; close_shared(ci); } } UNLOCK(&ci_lock); } /* * _citrus_iconv_open: * open a converter for the specified in/out codes. */ int _citrus_iconv_open(struct _citrus_iconv * __restrict * __restrict rcv, const char * __restrict src, const char * __restrict dst) { struct _citrus_iconv *cv = NULL; struct _citrus_iconv_shared *ci = NULL; - char realdst[PATH_MAX], realsrc[PATH_MAX]; + char realdst[PATH_MAX], realsrc[PATH_MAX], *slashes; #ifdef _PATH_ICONV char buf[PATH_MAX], path[PATH_MAX]; #endif int ret; init_cache(); /* GNU behaviour, using locale encoding if "" or "char" is specified */ if ((strcmp(src, "") == 0) || (strcmp(src, "char") == 0)) src = nl_langinfo(CODESET); if ((strcmp(dst, "") == 0) || (strcmp(dst, "char") == 0)) dst = nl_langinfo(CODESET); + strlcpy(realsrc, src, (size_t)PATH_MAX); + if ((slashes = strstr(realsrc, "//")) != NULL) + *slashes = '\0'; + strlcpy(realdst, dst, (size_t)PATH_MAX); + if ((slashes = strstr(realdst, "//")) != NULL) + *slashes = '\0'; + /* resolve codeset name aliases */ #ifdef _PATH_ICONV + /* + * Note that the below reads from realsrc and realdst while it's + * repopulating (writing to) realsrc and realdst, but it's done so with + * a trip through `buf`. + */ snprintf(path, sizeof(path), "%s/%s", _PATH_ICONV, _CITRUS_ICONV_ALIAS); - strlcpy(realsrc, _lookup_alias(path, src, buf, (size_t)PATH_MAX, + strlcpy(realsrc, _lookup_alias(path, realsrc, buf, (size_t)PATH_MAX, _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); - strlcpy(realdst, _lookup_alias(path, dst, buf, (size_t)PATH_MAX, + strlcpy(realdst, _lookup_alias(path, realdst, buf, (size_t)PATH_MAX, _LOOKUP_CASE_IGNORE), (size_t)PATH_MAX); -#else - strlcpy(realsrc, src, (size_t)PATH_MAX); - strlcpy(realdst, dst, (size_t)PATH_MAX); #endif /* sanity check */ if (strchr(realsrc, '/') != NULL || strchr(realdst, '/')) return (EINVAL); /* get shared record */ ret = get_shared(&ci, realsrc, realdst); if (ret) return (ret); /* create/init context */ if (*rcv == NULL) { cv = malloc(sizeof(*cv)); if (cv == NULL) { ret = errno; release_shared(ci); return (ret); } *rcv = cv; } (*rcv)->cv_shared = ci; ret = (*ci->ci_ops->io_init_context)(*rcv); if (ret) { release_shared(ci); free(cv); return (ret); } return (0); } /* * _citrus_iconv_close: * close the specified converter. */ void _citrus_iconv_close(struct _citrus_iconv *cv) { if (cv) { (*cv->cv_shared->ci_ops->io_uninit_context)(cv); release_shared(cv->cv_shared); free(cv); } } const char *_citrus_iconv_canonicalize(const char *name) { char *buf; if ((buf = calloc((size_t)PATH_MAX, sizeof(*buf))) == NULL) return (NULL); _citrus_esdb_alias(name, buf, (size_t)PATH_MAX); return (buf); }