Home | History | Annotate | Line # | Download | only in modules
      1 /*	$NetBSD: citrus_iconv_std.c,v 1.18 2026/07/05 20:41:23 riastradh Exp $	*/
      2 
      3 /*-
      4  * Copyright (c)2003 Citrus Project,
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  */
     28 
     29 #include <sys/cdefs.h>
     30 #if defined(LIBC_SCCS) && !defined(lint)
     31 __RCSID("$NetBSD: citrus_iconv_std.c,v 1.18 2026/07/05 20:41:23 riastradh Exp $");
     32 #endif /* LIBC_SCCS and not lint */
     33 
     34 #include <sys/param.h>
     35 
     36 #include <sys/queue.h>
     37 
     38 #include <assert.h>
     39 #include <endian.h>
     40 #include <errno.h>
     41 #include <limits.h>
     42 #include <stdalign.h>
     43 #include <stdio.h>
     44 #include <stdlib.h>
     45 #include <string.h>
     46 #include <wchar.h>
     47 
     48 #include "citrus_namespace.h"
     49 #include "citrus_types.h"
     50 #include "citrus_module.h"
     51 #include "citrus_region.h"
     52 #include "citrus_mmap.h"
     53 #include "citrus_hash.h"
     54 #include "citrus_iconv.h"
     55 #include "citrus_stdenc.h"
     56 #include "citrus_mapper.h"
     57 #include "citrus_csmapper.h"
     58 #include "citrus_memstream.h"
     59 #include "citrus_iconv_std.h"
     60 #include "citrus_esdb.h"
     61 
     62 /* ---------------------------------------------------------------------- */
     63 
     64 _CITRUS_ICONV_DECLS(iconv_std);
     65 _CITRUS_ICONV_DEF_OPS(iconv_std);
     66 
     67 
     68 /* ---------------------------------------------------------------------- */
     69 
     70 int
     71 _citrus_iconv_std_iconv_getops(struct _citrus_iconv_ops *ops, size_t lenops,
     72 			       u_int32_t expected_version)
     73 {
     74 	if (expected_version<_CITRUS_ICONV_ABI_VERSION || lenops<sizeof(*ops))
     75 		return (EINVAL);
     76 
     77 	memcpy(ops, &_citrus_iconv_std_iconv_ops,
     78 	       sizeof(_citrus_iconv_std_iconv_ops));
     79 
     80 	return (0);
     81 }
     82 
     83 /* ---------------------------------------------------------------------- */
     84 
     85 /*
     86  * convenience routines for stdenc.
     87  */
     88 static __inline void
     89 save_encoding_state(struct _citrus_iconv_std_encoding *se)
     90 {
     91 	if (se->se_ps)
     92 		memcpy(se->se_pssaved, se->se_ps,
     93 		       _stdenc_get_state_size(se->se_handle));
     94 }
     95 
     96 static __inline void
     97 restore_encoding_state(struct _citrus_iconv_std_encoding *se)
     98 {
     99 	if (se->se_ps)
    100 		memcpy(se->se_ps, se->se_pssaved,
    101 		       _stdenc_get_state_size(se->se_handle));
    102 }
    103 
    104 static __inline void
    105 init_encoding_state(struct _citrus_iconv_std_encoding *se)
    106 {
    107 	if (se->se_ps)
    108 		_stdenc_init_state(se->se_handle, se->se_ps);
    109 }
    110 
    111 static __inline int
    112 mbtocsx(struct _citrus_iconv_std_encoding *se,
    113 	_csid_t *csid, _index_t *idx, const char **s, size_t n,
    114 	size_t *nresult)
    115 {
    116 	return _stdenc_mbtocs(se->se_handle, csid, idx, s, n, se->se_ps,
    117 			      nresult);
    118 }
    119 
    120 static __inline int
    121 cstombx(struct _citrus_iconv_std_encoding *se,
    122 	char *s, size_t n, _csid_t csid, _index_t idx, size_t *nresult)
    123 {
    124 	return _stdenc_cstomb(se->se_handle, s, n, csid, idx, se->se_ps,
    125 			      nresult);
    126 }
    127 
    128 static __inline int
    129 wctombx(struct _citrus_iconv_std_encoding *se,
    130 	char *s, size_t n, _wc_t wc, size_t *nresult)
    131 {
    132 	return _stdenc_wctomb(se->se_handle, s, n, wc, se->se_ps, nresult);
    133 }
    134 
    135 static __inline int
    136 put_state_resetx(struct _citrus_iconv_std_encoding *se,
    137 		 char *s, size_t n, size_t *nresult)
    138 {
    139 	return _stdenc_put_state_reset(se->se_handle, s, n, se->se_ps, nresult);
    140 }
    141 
    142 static __inline int
    143 get_state_desc_gen(struct _citrus_iconv_std_encoding *se, int *rstate)
    144 {
    145 	int ret;
    146 	struct _stdenc_state_desc ssd;
    147 
    148 	ret = _stdenc_get_state_desc(se->se_handle, se->se_ps,
    149 				     _STDENC_SDID_GENERIC, &ssd);
    150 	if (!ret)
    151 		*rstate = ssd.u.generic.state;
    152 
    153 	return ret;
    154 }
    155 
    156 /*
    157  * init encoding context
    158  */
    159 static int
    160 init_encoding(struct _citrus_iconv_std_encoding *se, struct _stdenc *cs,
    161 	      void *ps1, void *ps2)
    162 {
    163 	int ret = -1;
    164 
    165 	_DIAGASSERT((uintptr_t)ps1 % alignof(mbstate_t) == 0);
    166 	_DIAGASSERT((uintptr_t)ps2 % alignof(mbstate_t) == 0);
    167 
    168 	se->se_handle = cs;
    169 	se->se_ps = ps1;
    170 	se->se_pssaved = ps2;
    171 
    172 	if (se->se_ps)
    173 		ret = _stdenc_init_state(cs, se->se_ps);
    174 	if (!ret && se->se_pssaved)
    175 		ret = _stdenc_init_state(cs, se->se_pssaved);
    176 
    177 	return ret;
    178 }
    179 
    180 static int
    181 open_csmapper(struct _csmapper **rcm, const char *src, const char *dst,
    182 	      unsigned long *rnorm)
    183 {
    184 	int ret;
    185 	struct _csmapper *cm;
    186 
    187 	ret = _csmapper_open(&cm, src, dst, 0, rnorm);
    188 	if (ret)
    189 		return ret;
    190 	if (_csmapper_get_src_max(cm) != 1 || _csmapper_get_dst_max(cm) != 1 ||
    191 	    _csmapper_get_state_size(cm) != 0) {
    192 		_csmapper_close(cm);
    193 		return EINVAL;
    194 	}
    195 
    196 	*rcm = cm;
    197 
    198 	return 0;
    199 }
    200 
    201 static void
    202 close_dsts(struct _citrus_iconv_std_dst_list *dl)
    203 {
    204 	struct _citrus_iconv_std_dst *sd;
    205 
    206 	while ((sd=TAILQ_FIRST(dl)) != NULL) {
    207 		TAILQ_REMOVE(dl, sd, sd_entry);
    208 		_csmapper_close(sd->sd_mapper);
    209 		free(sd);
    210 	}
    211 }
    212 
    213 static int
    214 open_dsts(struct _citrus_iconv_std_dst_list *dl,
    215 	  const struct _esdb_charset *ec, const struct _esdb *dbdst)
    216 {
    217 	int i, ret;
    218 	struct _citrus_iconv_std_dst *sd, *sdtmp;
    219 	unsigned long norm;
    220 
    221 	sd = malloc(sizeof(*sd));
    222 	if (sd == NULL)
    223 		return errno;
    224 
    225 	for (i=0; i<dbdst->db_num_charsets; i++) {
    226 		ret = open_csmapper(&sd->sd_mapper, ec->ec_csname,
    227 				    dbdst->db_charsets[i].ec_csname, &norm);
    228 		if (ret == 0) {
    229 			sd->sd_csid = dbdst->db_charsets[i].ec_csid;
    230 			sd->sd_norm = norm;
    231 			/* insert this mapper by sorted order. */
    232 			TAILQ_FOREACH(sdtmp, dl, sd_entry) {
    233 				if (sdtmp->sd_norm > norm) {
    234 					TAILQ_INSERT_BEFORE(sdtmp, sd,
    235 							    sd_entry);
    236 					sd = NULL;
    237 					break;
    238 				}
    239 			}
    240 			if (sd)
    241 				TAILQ_INSERT_TAIL(dl, sd, sd_entry);
    242 			sd = malloc(sizeof(*sd));
    243 			if (sd == NULL) {
    244 				ret = errno;
    245 				close_dsts(dl);
    246 				return ret;
    247 			}
    248 		} else if (ret != ENOENT) {
    249 			close_dsts(dl);
    250 			free(sd);
    251 			return ret;
    252 		}
    253 	}
    254 	free(sd);
    255 	return 0;
    256 }
    257 
    258 static void
    259 close_srcs(struct _citrus_iconv_std_src_list *sl)
    260 {
    261 	struct _citrus_iconv_std_src *ss;
    262 
    263 	while ((ss=TAILQ_FIRST(sl)) != NULL) {
    264 		TAILQ_REMOVE(sl, ss, ss_entry);
    265 		close_dsts(&ss->ss_dsts);
    266 		free(ss);
    267 	}
    268 }
    269 
    270 static int
    271 open_srcs(struct _citrus_iconv_std_src_list *sl,
    272 	  const struct _esdb *dbsrc, const struct _esdb *dbdst)
    273 {
    274 	int i, ret, count = 0;
    275 	struct _citrus_iconv_std_src *ss;
    276 
    277 	ss = malloc(sizeof(*ss));
    278 	if (ss == NULL)
    279 		return errno;
    280 
    281 	TAILQ_INIT(&ss->ss_dsts);
    282 
    283 	for (i=0; i<dbsrc->db_num_charsets; i++) {
    284 		ret = open_dsts(&ss->ss_dsts, &dbsrc->db_charsets[i], dbdst);
    285 		if (ret)
    286 			goto err;
    287 		if (!TAILQ_EMPTY(&ss->ss_dsts)) {
    288 			ss->ss_csid = dbsrc->db_charsets[i].ec_csid;
    289 			TAILQ_INSERT_TAIL(sl, ss, ss_entry);
    290 			ss = malloc(sizeof(*ss));
    291 			if (ss == NULL) {
    292 				ret = errno;
    293 				goto err;
    294 			}
    295 			count++;
    296 			TAILQ_INIT(&ss->ss_dsts);
    297 		}
    298 	}
    299 	free(ss);
    300 
    301 	return count ? 0 : ENOENT;
    302 
    303 err:
    304 	free(ss);
    305 	close_srcs(sl);
    306 	return ret;
    307 }
    308 
    309 /* do convert a character */
    310 #define E_NO_CORRESPONDING_CHAR ENOENT /* XXX */
    311 static int
    312 /*ARGSUSED*/
    313 do_conv(const struct _citrus_iconv_std_shared *is,
    314 	struct _citrus_iconv_std_context *sc, _csid_t *csid, _index_t *idx)
    315 {
    316 	_index_t tmpidx;
    317 	int ret;
    318 	struct _citrus_iconv_std_src *ss;
    319 	struct _citrus_iconv_std_dst *sd;
    320 
    321 	TAILQ_FOREACH(ss, &is->is_srcs, ss_entry) {
    322 		if (ss->ss_csid == *csid) {
    323 			TAILQ_FOREACH(sd, &ss->ss_dsts, sd_entry) {
    324 				ret = _csmapper_convert(sd->sd_mapper,
    325 							&tmpidx, *idx, NULL);
    326 				switch (ret) {
    327 				case _MAPPER_CONVERT_SUCCESS:
    328 					*csid = sd->sd_csid;
    329 					*idx = tmpidx;
    330 					return 0;
    331 				case _MAPPER_CONVERT_NONIDENTICAL:
    332 					break;
    333 				case _MAPPER_CONVERT_SRC_MORE:
    334 					/*FALLTHROUGH*/
    335 				case _MAPPER_CONVERT_DST_MORE:
    336 					/*FALLTHROUGH*/
    337 				case _MAPPER_CONVERT_FATAL:
    338 					return EINVAL;
    339 				case _MAPPER_CONVERT_ILSEQ:
    340 					return EILSEQ;
    341 				}
    342 			}
    343 			break;
    344 		}
    345 	}
    346 
    347 	return E_NO_CORRESPONDING_CHAR;
    348 }
    349 /* ---------------------------------------------------------------------- */
    350 
    351 static int
    352 /*ARGSUSED*/
    353 _citrus_iconv_std_iconv_init_shared(struct _citrus_iconv_shared *ci,
    354 				    const char * __restrict curdir,
    355 				    const char * __restrict src,
    356 				    const char * __restrict dst,
    357 				    const void * __restrict var, size_t lenvar)
    358 {
    359 	int ret;
    360 	struct _citrus_iconv_std_shared *is;
    361 	struct _citrus_esdb esdbsrc, esdbdst;
    362 
    363 	is = malloc(sizeof(*is));
    364 	if (is==NULL) {
    365 		ret = errno;
    366 		goto err0;
    367 	}
    368 	ret = _citrus_esdb_open(&esdbsrc, src);
    369 	if (ret)
    370 		goto err1;
    371 	ret = _citrus_esdb_open(&esdbdst, dst);
    372 	if (ret)
    373 		goto err2;
    374 	ret = _stdenc_open(&is->is_src_encoding, esdbsrc.db_encname,
    375 			   esdbsrc.db_variable, esdbsrc.db_len_variable);
    376 	if (ret)
    377 		goto err3;
    378 	ret = _stdenc_open(&is->is_dst_encoding, esdbdst.db_encname,
    379 			   esdbdst.db_variable, esdbdst.db_len_variable);
    380 	if (ret)
    381 		goto err4;
    382 	is->is_use_invalid = esdbdst.db_use_invalid;
    383 	is->is_invalid = esdbdst.db_invalid;
    384 
    385 	TAILQ_INIT(&is->is_srcs);
    386 	ret = open_srcs(&is->is_srcs, &esdbsrc, &esdbdst);
    387 	if (ret)
    388 		goto err5;
    389 
    390 	_esdb_close(&esdbsrc);
    391 	_esdb_close(&esdbdst);
    392 	ci->ci_closure = is;
    393 
    394 	return 0;
    395 
    396 err5:
    397 	_stdenc_close(is->is_dst_encoding);
    398 err4:
    399 	_stdenc_close(is->is_src_encoding);
    400 err3:
    401 	_esdb_close(&esdbdst);
    402 err2:
    403 	_esdb_close(&esdbsrc);
    404 err1:
    405 	free(is);
    406 err0:
    407 	return ret;
    408 }
    409 
    410 static void
    411 _citrus_iconv_std_iconv_uninit_shared(struct _citrus_iconv_shared *ci)
    412 {
    413 	struct _citrus_iconv_std_shared *is = ci->ci_closure;
    414 
    415 	if (is == NULL)
    416 		return;
    417 
    418 	_stdenc_close(is->is_src_encoding);
    419 	_stdenc_close(is->is_dst_encoding);
    420 	close_srcs(&is->is_srcs);
    421 	free(is);
    422 }
    423 
    424 static int
    425 _citrus_iconv_std_iconv_init_context(struct _citrus_iconv *cv)
    426 {
    427 	const struct _citrus_iconv_std_shared *is = cv->cv_shared->ci_closure;
    428 	struct _citrus_iconv_std_context *sc;
    429 	size_t szpssrc, szpsdst, sz;
    430 	char *ptr;
    431 
    432 	/*
    433 	 * Allocate space for:
    434 	 *
    435 	 *	struct {
    436 	 *		struct _citrus_iconv_std_context sc;
    437 	 *		struct <src_state> src_ps;
    438 	 *		struct <src_state> src_pssaved;
    439 	 *		struct <dst_state> dst_ps;
    440 	 *		struct <dst_state> dst_pssaved;
    441 	 *	};
    442 	 *
    443 	 * Note that `struct <src_state>' and `struct <dst_state>' are
    444 	 * required to be aligned like mbstate_t.
    445 	 */
    446 	szpssrc = roundup(_stdenc_get_state_size(is->is_src_encoding),
    447 	    alignof(mbstate_t));
    448 	szpsdst = roundup(_stdenc_get_state_size(is->is_dst_encoding),
    449 	    alignof(mbstate_t));
    450 	_DIAGASSERT(szpssrc % alignof(mbstate_t) == 0);
    451 	_DIAGASSERT(szpsdst % alignof(mbstate_t) == 0);
    452 
    453 	sz = roundup(sizeof(*sc), alignof(mbstate_t)) +
    454 	    (szpssrc + szpsdst)*2;
    455 	sc = malloc(sz);
    456 	if (sc == NULL)
    457 		return errno;
    458 
    459 	ptr = (char *)sc + roundup(sizeof(*sc), alignof(mbstate_t));
    460 	_DIAGASSERT((uintptr_t)ptr % alignof(mbstate_t) == 0);
    461 	if (szpssrc)
    462 		init_encoding(&sc->sc_src_encoding, is->is_src_encoding,
    463 			      ptr, ptr+szpssrc);
    464 	else
    465 		init_encoding(&sc->sc_src_encoding, is->is_src_encoding,
    466 			      NULL, NULL);
    467 	ptr += szpssrc*2;
    468 	_DIAGASSERT((uintptr_t)ptr % alignof(mbstate_t) == 0);
    469 	if (szpsdst)
    470 		init_encoding(&sc->sc_dst_encoding, is->is_dst_encoding,
    471 			      ptr, ptr+szpsdst);
    472 	else
    473 		init_encoding(&sc->sc_dst_encoding, is->is_dst_encoding,
    474 			      NULL, NULL);
    475 
    476 	cv->cv_closure = (void *)sc;
    477 
    478 	return 0;
    479 }
    480 
    481 static void
    482 _citrus_iconv_std_iconv_uninit_context(struct _citrus_iconv *cv)
    483 {
    484 	free(cv->cv_closure);
    485 }
    486 
    487 static int
    488 _citrus_iconv_std_iconv_convert(struct _citrus_iconv * __restrict cv,
    489 				const char * __restrict * __restrict in,
    490 				size_t * __restrict inbytes,
    491 				char * __restrict * __restrict out,
    492 				size_t * __restrict outbytes, u_int32_t flags,
    493 				size_t * __restrict invalids)
    494 {
    495 	const struct _citrus_iconv_std_shared *is = cv->cv_shared->ci_closure;
    496 	struct _citrus_iconv_std_context *sc = cv->cv_closure;
    497 	_index_t idx;
    498 	_csid_t csid;
    499 	int ret, state;
    500 	size_t szrin, szrout;
    501 	size_t inval;
    502 	const char *tmpin;
    503 
    504 	inval = 0;
    505 	if (in==NULL || *in==NULL) {
    506 		/* special cases */
    507 		if (out!=NULL && *out!=NULL) {
    508 			/* init output state and store the shift sequence */
    509 			save_encoding_state(&sc->sc_src_encoding);
    510 			save_encoding_state(&sc->sc_dst_encoding);
    511 			szrout = 0;
    512 
    513 			ret = put_state_resetx(&sc->sc_dst_encoding,
    514 					       *out, *outbytes,
    515 					       &szrout);
    516 			if (ret)
    517 				goto err;
    518 
    519 			if (szrout == (size_t)-2) {
    520 				/* too small to store the character */
    521 				ret = EINVAL;
    522 				goto err;
    523 			}
    524 			*out += szrout;
    525 			*outbytes -= szrout;
    526 		} else
    527 			/* otherwise, discard the shift sequence */
    528 			init_encoding_state(&sc->sc_dst_encoding);
    529 		init_encoding_state(&sc->sc_src_encoding);
    530 		*invalids = 0;
    531 		return 0;
    532 	}
    533 
    534 	/* normal case */
    535 	for (;;) {
    536 		if (*inbytes==0) {
    537 			ret = get_state_desc_gen(&sc->sc_src_encoding, &state);
    538 			if (state == _STDENC_SDGEN_INITIAL ||
    539 			    state == _STDENC_SDGEN_STABLE)
    540 				break;
    541 		}
    542 
    543 		/* save the encoding states for the error recovery */
    544 		save_encoding_state(&sc->sc_src_encoding);
    545 		save_encoding_state(&sc->sc_dst_encoding);
    546 
    547 		/* mb -> csid/index */
    548 		tmpin = *in;
    549 		szrin = szrout = 0;
    550 		ret = mbtocsx(&sc->sc_src_encoding, &csid, &idx,
    551 			      &tmpin, *inbytes, &szrin);
    552 		if (ret)
    553 			goto err;
    554 
    555 		if (szrin == (size_t)-2) {
    556 			/* incompleted character */
    557 			ret = get_state_desc_gen(&sc->sc_src_encoding, &state);
    558 			if (ret) {
    559 				ret = EINVAL;
    560 				goto err;
    561 			}
    562 			switch (state) {
    563 			case _STDENC_SDGEN_INITIAL:
    564 			case _STDENC_SDGEN_STABLE:
    565 				/* fetch shift sequences only. */
    566 				goto next;
    567 			}
    568 			ret = EINVAL;
    569 			goto err;
    570 		}
    571 		/* convert the character */
    572 		ret = do_conv(is, sc, &csid, &idx);
    573 		if (ret) {
    574 			if (ret == E_NO_CORRESPONDING_CHAR) {
    575 				inval++;
    576 				szrout = 0;
    577 				if ((flags&_CITRUS_ICONV_F_HIDE_INVALID)==0 &&
    578 				    is->is_use_invalid) {
    579 					ret = wctombx(&sc->sc_dst_encoding,
    580 						      *out, *outbytes,
    581 						      is->is_invalid,
    582 						      &szrout);
    583 					if (ret)
    584 						goto err;
    585 				}
    586 				goto next;
    587 			} else {
    588 				goto err;
    589 			}
    590 		}
    591 		/* csid/index -> mb */
    592 		ret = cstombx(&sc->sc_dst_encoding,
    593 			      *out, *outbytes, csid, idx, &szrout);
    594 		if (ret)
    595 			goto err;
    596 next:
    597 		_DIAGASSERT(tmpin >= *in);
    598 		_DIAGASSERT(*inbytes >= tmpin - *in);
    599 		_DIAGASSERT(*outbytes >= szrout);
    600 		*inbytes -= tmpin - *in;  /* szrin is insufficient on \0. */
    601 		*in = tmpin;
    602 		*outbytes -= szrout;
    603 		*out += szrout;
    604 	}
    605 	*invalids = inval;
    606 
    607 	return 0;
    608 
    609 err:
    610 	restore_encoding_state(&sc->sc_src_encoding);
    611 	restore_encoding_state(&sc->sc_dst_encoding);
    612 err_norestore:
    613 	*invalids = inval;
    614 
    615 	return ret;
    616 }
    617