iconv.c revision 109623
167754Smsmith/*
267754Smsmith * Copyright (c) 2000-2001, Boris Popov
367754Smsmith * All rights reserved.
477424Smsmith *
591116Smsmith * Redistribution and use in source and binary forms, with or without
667754Smsmith * modification, are permitted provided that the following conditions
767754Smsmith * are met:
867754Smsmith * 1. Redistributions of source code must retain the above copyright
967754Smsmith *    notice, this list of conditions and the following disclaimer.
1067754Smsmith * 2. Redistributions in binary form must reproduce the above copyright
1167754Smsmith *    notice, this list of conditions and the following disclaimer in the
1267754Smsmith *    documentation and/or other materials provided with the distribution.
1391116Smsmith * 3. All advertising materials mentioning features or use of this software
1470243Smsmith *    must display the following acknowledgement:
1567754Smsmith *    This product includes software developed by Boris Popov.
1667754Smsmith * 4. Neither the name of the author nor the names of any co-contributors
1767754Smsmith *    may be used to endorse or promote products derived from this software
1867754Smsmith *    without specific prior written permission.
1967754Smsmith *
2067754Smsmith * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2167754Smsmith * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2267754Smsmith * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2367754Smsmith * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2467754Smsmith * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2567754Smsmith * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2667754Smsmith * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2767754Smsmith * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2867754Smsmith * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2967754Smsmith * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3067754Smsmith * SUCH DAMAGE.
3167754Smsmith *
3267754Smsmith * $FreeBSD: head/sys/libkern/iconv.c 109623 2003-01-21 08:56:16Z alfred $
3367754Smsmith */
3467754Smsmith#include <sys/param.h>
3567754Smsmith#include <sys/systm.h>
3667754Smsmith#include <sys/kernel.h>
3767754Smsmith#include <sys/iconv.h>
3867754Smsmith#include <sys/malloc.h>
3967754Smsmith
4067754Smsmith#include "iconv_converter_if.h"
4167754Smsmith
4267754SmsmithSYSCTL_DECL(_kern_iconv);
4367754Smsmith
4467754SmsmithSYSCTL_NODE(_kern, OID_AUTO, iconv, CTLFLAG_RW, NULL, "kernel iconv interface");
4567754Smsmith
4667754SmsmithMALLOC_DEFINE(M_ICONV, "ICONV", "ICONV structures");
4767754SmsmithMALLOC_DEFINE(M_ICONVDATA, "ICONV data", "ICONV data");
4867754Smsmith
4967754SmsmithMODULE_VERSION(libiconv, 1);
5067754Smsmith
5167754Smsmith#ifdef notnow
5267754Smsmith/*
5367754Smsmith * iconv converter instance
5467754Smsmith */
5567754Smsmithstruct iconv_converter {
5667754Smsmith	KOBJ_FIELDS;
5767754Smsmith	void *			c_data;
5867754Smsmith};
5967754Smsmith#endif
6067754Smsmith
6167754Smsmithstruct sysctl_oid *iconv_oid_hook = &sysctl___kern_iconv;
6267754Smsmith
6367754Smsmith/*
6467754Smsmith * List of loaded converters
6567754Smsmith */
6667754Smsmithstatic TAILQ_HEAD(iconv_converter_list, iconv_converter_class)
6767754Smsmith    iconv_converters = TAILQ_HEAD_INITIALIZER(iconv_converters);
6867754Smsmith
6967754Smsmith/*
7067754Smsmith * List of supported/loaded charsets pairs
7167754Smsmith */
7267754Smsmithstatic TAILQ_HEAD(, iconv_cspair)
7367754Smsmith    iconv_cslist = TAILQ_HEAD_INITIALIZER(iconv_cslist);
7467754Smsmithstatic int iconv_csid = 1;
7567754Smsmith
7667754Smsmithstatic char iconv_unicode_string[] = "unicode";	/* save eight bytes when possible */
7767754Smsmith
7867754Smsmithstatic void iconv_unregister_cspair(struct iconv_cspair *csp);
7967754Smsmith
8067754Smsmithstatic int
8167754Smsmithiconv_mod_unload(void)
8267754Smsmith{
8367754Smsmith	struct iconv_cspair *csp;
8467754Smsmith
8567754Smsmith	while ((csp = TAILQ_FIRST(&iconv_cslist)) != NULL) {
8667754Smsmith		if (csp->cp_refcount)
8767754Smsmith			return EBUSY;
8867754Smsmith		iconv_unregister_cspair(csp);
8967754Smsmith	}
9067754Smsmith	return 0;
9167754Smsmith}
9267754Smsmith
9367754Smsmithstatic int
9467754Smsmithiconv_mod_handler(module_t mod, int type, void *data)
9567754Smsmith{
9667754Smsmith	int error;
9767754Smsmith
9867754Smsmith	switch (type) {
9967754Smsmith	    case MOD_LOAD:
10067754Smsmith		error = 0;
10167754Smsmith		break;
10267754Smsmith	    case MOD_UNLOAD:
10367754Smsmith		error = iconv_mod_unload();
10467754Smsmith		break;
10567754Smsmith	    default:
10667754Smsmith		error = EINVAL;
10767754Smsmith	}
10867754Smsmith	return error;
10967754Smsmith}
11067754Smsmith
11167754Smsmithstatic moduledata_t iconv_mod = {
11267754Smsmith	"iconv", iconv_mod_handler, NULL
11367754Smsmith};
11467754Smsmith
11567754SmsmithDECLARE_MODULE(iconv, iconv_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
11667754Smsmith
11767754Smsmithstatic int
11877424Smsmithiconv_register_converter(struct iconv_converter_class *dcp)
11967754Smsmith{
12083174Smsmith	kobj_class_compile((struct kobj_class*)dcp);
12183174Smsmith	dcp->refs++;
12283174Smsmith	TAILQ_INSERT_TAIL(&iconv_converters, dcp, cc_link);
12383174Smsmith	return 0;
12483174Smsmith}
12583174Smsmith
12683174Smsmithstatic int
12783174Smsmithiconv_unregister_converter(struct iconv_converter_class *dcp)
12883174Smsmith{
12983174Smsmith	if (dcp->refs > 1) {
13083174Smsmith		ICDEBUG("converter have %d referenses left\n", dcp->refs);
13183174Smsmith		return EBUSY;
13283174Smsmith	}
13383174Smsmith	TAILQ_REMOVE(&iconv_converters, dcp, cc_link);
13483174Smsmith	kobj_class_free((struct kobj_class*)dcp);
13583174Smsmith	return 0;
13667754Smsmith}
13767754Smsmith
13867754Smsmithstatic int
13967754Smsmithiconv_lookupconv(const char *name, struct iconv_converter_class **dcpp)
14067754Smsmith{
14167754Smsmith	struct iconv_converter_class *dcp;
14283174Smsmith
14367754Smsmith	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
14477424Smsmith		if (name == NULL)
14591116Smsmith			continue;
14667754Smsmith		if (strcmp(name, ICONV_CONVERTER_NAME(dcp)) == 0) {
14767754Smsmith			if (dcpp)
14867754Smsmith				*dcpp = dcp;
14967754Smsmith			return 0;
15077424Smsmith		}
15167754Smsmith	}
15267754Smsmith	return ENOENT;
15367754Smsmith}
15491116Smsmith
15591116Smsmithstatic int
15667754Smsmithiconv_lookupcs(const char *to, const char *from, struct iconv_cspair **cspp)
15767754Smsmith{
15867754Smsmith	struct iconv_cspair *csp;
15977424Smsmith
16077424Smsmith	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
16167754Smsmith		if (strcmp(csp->cp_to, to) == 0 &&
16277424Smsmith		    strcmp(csp->cp_from, from) == 0) {
16367754Smsmith			if (cspp)
16491116Smsmith				*cspp = csp;
16567754Smsmith			return 0;
16667754Smsmith		}
16777424Smsmith	}
16891116Smsmith	return ENOENT;
16991116Smsmith}
17091116Smsmith
17191116Smsmithstatic int
17291116Smsmithiconv_register_cspair(const char *to, const char *from,
17377424Smsmith	struct iconv_converter_class *dcp, void *data,
17467754Smsmith	struct iconv_cspair **cspp)
17567754Smsmith{
17667754Smsmith	struct iconv_cspair *csp;
17767754Smsmith	char *cp;
17867754Smsmith	int csize, ucsto, ucsfrom;
17977424Smsmith
18067754Smsmith	if (iconv_lookupcs(to, from, NULL) == 0)
18167754Smsmith		return EEXIST;
18267754Smsmith	csize = sizeof(*csp);
18367754Smsmith	ucsto = strcmp(to, iconv_unicode_string) == 0;
18467754Smsmith	if (!ucsto)
18567754Smsmith		csize += strlen(to) + 1;
18667754Smsmith	ucsfrom = strcmp(from, iconv_unicode_string) == 0;
18767754Smsmith	if (!ucsfrom)
18867754Smsmith		csize += strlen(from) + 1;
18967754Smsmith	csp = malloc(csize, M_ICONV, 0);
19067754Smsmith	bzero(csp, csize);
19167754Smsmith	csp->cp_id = iconv_csid++;
19267754Smsmith	csp->cp_dcp = dcp;
19367754Smsmith	cp = (char*)(csp + 1);
19467754Smsmith	if (!ucsto) {
19567754Smsmith		strcpy(cp, to);
19667754Smsmith		csp->cp_to = cp;
19767754Smsmith		cp += strlen(cp) + 1;
19877424Smsmith	} else
19967754Smsmith		csp->cp_to = iconv_unicode_string;
20091116Smsmith	if (!ucsfrom) {
20167754Smsmith		strcpy(cp, from);
20267754Smsmith		csp->cp_from = cp;
20391116Smsmith	} else
20467754Smsmith		csp->cp_from = iconv_unicode_string;
20591116Smsmith	csp->cp_data = data;
20691116Smsmith
20791116Smsmith	TAILQ_INSERT_TAIL(&iconv_cslist, csp, cp_link);
20867754Smsmith	*cspp = csp;
20967754Smsmith	return 0;
21067754Smsmith}
21167754Smsmith
21267754Smsmithstatic void
21367754Smsmithiconv_unregister_cspair(struct iconv_cspair *csp)
21477424Smsmith{
21567754Smsmith	TAILQ_REMOVE(&iconv_cslist, csp, cp_link);
21667754Smsmith	if (csp->cp_data)
21767754Smsmith		free(csp->cp_data, M_ICONVDATA);
21867754Smsmith	free(csp, M_ICONV);
21967754Smsmith}
22067754Smsmith
22167754Smsmith/*
22267754Smsmith * Lookup and create an instance of converter.
22377424Smsmith * Currently this layer didn't have associated 'instance' structure
22467754Smsmith * to avoid unnesessary memory allocation.
22567754Smsmith */
22667754Smsmithint
22791116Smsmithiconv_open(const char *to, const char *from, void **handle)
22883174Smsmith{
22983174Smsmith	struct iconv_cspair *csp, *cspfrom, *cspto;
23067754Smsmith	struct iconv_converter_class *dcp;
23167754Smsmith	const char *cnvname;
23267754Smsmith	int error;
23367754Smsmith
23467754Smsmith	/*
23567754Smsmith	 * First, lookup fully qualified cspairs
23667754Smsmith	 */
23767754Smsmith	error = iconv_lookupcs(to, from, &csp);
23867754Smsmith	if (error == 0)
23967754Smsmith		return ICONV_CONVERTER_OPEN(csp->cp_dcp, csp, NULL, handle);
24067754Smsmith
24167754Smsmith	/*
24277424Smsmith	 * Well, nothing found. Now try to construct a composite conversion
24367754Smsmith	 * ToDo: add a 'capability' field to converter
24469450Smsmith	 */
24569450Smsmith	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
24669450Smsmith		cnvname = ICONV_CONVERTER_NAME(dcp);
24767754Smsmith		if (cnvname == NULL)
24869450Smsmith			continue;
24967754Smsmith		error = iconv_lookupcs(cnvname, from, &cspfrom);
25069450Smsmith		if (error)
25169450Smsmith			continue;
25267754Smsmith		error = iconv_lookupcs(to, cnvname, &cspto);
25367754Smsmith		if (error)
25467754Smsmith			continue;
25569450Smsmith		/*
25677424Smsmith		 * Fine, we're found a pair which can be combined together
25769450Smsmith		 */
25869450Smsmith		return ICONV_CONVERTER_OPEN(dcp, cspto, cspfrom, handle);
25967754Smsmith	}
26067754Smsmith	return ENOENT;
26191116Smsmith}
26283174Smsmith
26383174Smsmithint
26469450Smsmithiconv_close(void *handle)
26569450Smsmith{
26669450Smsmith	return ICONV_CONVERTER_CLOSE(handle);
26769450Smsmith}
26869450Smsmith
26971867Smsmithint
27069450Smsmithiconv_conv(void *handle, const char **inbuf,
27169450Smsmith	size_t *inbytesleft, char **outbuf, size_t *outbytesleft)
27269450Smsmith{
27369450Smsmith	return ICONV_CONVERTER_CONV(handle, inbuf, inbytesleft, outbuf, outbytesleft);
27469450Smsmith}
27569450Smsmith
27669450Smsmith/*
27769450Smsmith * Give a list of loaded converters. Each name terminated with 0.
27869450Smsmith * An empty string terminates the list.
27969450Smsmith */
28069450Smsmithstatic int
28171867Smsmithiconv_sysctl_drvlist(SYSCTL_HANDLER_ARGS)
28269450Smsmith{
28367754Smsmith	struct iconv_converter_class *dcp;
28467754Smsmith	const char *name;
28567754Smsmith	char spc;
28667754Smsmith	int error;
28767754Smsmith
28877424Smsmith	error = 0;
28967754Smsmith
29091116Smsmith	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
29187031Smsmith		name = ICONV_CONVERTER_NAME(dcp);
29267754Smsmith		if (name == NULL)
29367754Smsmith			continue;
29467754Smsmith		error = SYSCTL_OUT(req, name, strlen(name) + 1);
29567754Smsmith		if (error)
29667754Smsmith			break;
29767754Smsmith	}
29867754Smsmith	if (error)
29967754Smsmith		return error;
30067754Smsmith	spc = 0;
30167754Smsmith	error = SYSCTL_OUT(req, &spc, sizeof(spc));
30277424Smsmith	return error;
30387031Smsmith}
30467754Smsmith
30567754SmsmithSYSCTL_PROC(_kern_iconv, OID_AUTO, drvlist, CTLFLAG_RD | CTLTYPE_OPAQUE,
30667754Smsmith	    NULL, 0, iconv_sysctl_drvlist, "S,xlat", "registered converters");
30767754Smsmith
30867754Smsmith/*
30991116Smsmith * List all available charset pairs.
31067754Smsmith */
31167754Smsmithstatic int
31287031Smsmithiconv_sysctl_cslist(SYSCTL_HANDLER_ARGS)
31367754Smsmith{
31487031Smsmith	struct iconv_cspair *csp;
31567754Smsmith	struct iconv_cspair_info csi;
31691116Smsmith	int error;
31767754Smsmith
31891116Smsmith	error = 0;
31977424Smsmith	bzero(&csi, sizeof(csi));
32067754Smsmith	csi.cs_version = ICONV_CSPAIR_INFO_VER;
32177424Smsmith
32267754Smsmith	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
32377424Smsmith		csi.cs_id = csp->cp_id;
32467754Smsmith		csi.cs_refcount = csp->cp_refcount;
32582367Smsmith		csi.cs_base = csp->cp_base ? csp->cp_base->cp_id : 0;
32680062Smsmith		strcpy(csi.cs_to, csp->cp_to);
32767754Smsmith		strcpy(csi.cs_from, csp->cp_from);
32867754Smsmith		error = SYSCTL_OUT(req, &csi, sizeof(csi));
32967754Smsmith		if (error)
33067754Smsmith			break;
33167754Smsmith	}
33267754Smsmith	return error;
33367754Smsmith}
33467754Smsmith
33567754SmsmithSYSCTL_PROC(_kern_iconv, OID_AUTO, cslist, CTLFLAG_RD | CTLTYPE_OPAQUE,
33677424Smsmith	    NULL, 0, iconv_sysctl_cslist, "S,xlat", "registered charset pairs");
33767754Smsmith
33867754Smsmith/*
33967754Smsmith * Add new charset pair
34067754Smsmith */
34167754Smsmithstatic int
34267754Smsmithiconv_sysctl_add(SYSCTL_HANDLER_ARGS)
34367754Smsmith{
34467754Smsmith	struct iconv_converter_class *dcp;
34567754Smsmith	struct iconv_cspair *csp;
34667754Smsmith	struct iconv_add_in din;
34767754Smsmith	struct iconv_add_out dout;
34877424Smsmith	int error;
34967754Smsmith
35067754Smsmith	error = SYSCTL_IN(req, &din, sizeof(din));
35167754Smsmith	if (error)
35291116Smsmith		return error;
35367754Smsmith	if (din.ia_version != ICONV_ADD_VER)
35467754Smsmith		return EINVAL;
35567754Smsmith	if (din.ia_datalen > ICONV_CSMAXDATALEN)
35667754Smsmith		return EINVAL;
35767754Smsmith	if (iconv_lookupconv(din.ia_converter, &dcp) != 0)
35867754Smsmith		return EINVAL;
35977424Smsmith	error = iconv_register_cspair(din.ia_to, din.ia_from, dcp, NULL, &csp);
36067754Smsmith	if (error)
36177424Smsmith		return error;
36267754Smsmith	if (din.ia_datalen) {
36367754Smsmith		csp->cp_data = malloc(din.ia_datalen, M_ICONVDATA, 0);
36467754Smsmith		error = copyin(din.ia_data, csp->cp_data, din.ia_datalen);
36567754Smsmith		if (error)
36667754Smsmith			goto bad;
36767754Smsmith	}
36867754Smsmith	dout.ia_csid = csp->cp_id;
36967754Smsmith	error = SYSCTL_OUT(req, &dout, sizeof(dout));
37077424Smsmith	if (error)
37167754Smsmith		goto bad;
37284491Smsmith	return 0;
37384491Smsmithbad:
37467754Smsmith	iconv_unregister_cspair(csp);
37584491Smsmith	return error;
37667754Smsmith}
37767754Smsmith
37867754SmsmithSYSCTL_PROC(_kern_iconv, OID_AUTO, add, CTLFLAG_RW | CTLTYPE_OPAQUE,
37967754Smsmith	    NULL, 0, iconv_sysctl_add, "S,xlat", "register charset pair");
38077424Smsmith
38184491Smsmith/*
38284491Smsmith * Default stubs for converters
38367754Smsmith */
38467754Smsmithint
38567754Smsmithiconv_converter_initstub(struct iconv_converter_class *dp)
38667754Smsmith{
38791116Smsmith	return 0;
38867754Smsmith}
38967754Smsmith
39084491Smsmithint
39167754Smsmithiconv_converter_donestub(struct iconv_converter_class *dp)
39291116Smsmith{
39367754Smsmith	return 0;
39467754Smsmith}
39567754Smsmith
39677424Smsmithint
39784491Smsmithiconv_converter_handler(module_t mod, int type, void *data)
39877424Smsmith{
39987031Smsmith	struct iconv_converter_class *dcp = data;
40087031Smsmith	int error;
40184491Smsmith
40267754Smsmith	switch (type) {
40367754Smsmith	    case MOD_LOAD:
40467754Smsmith		error = iconv_register_converter(dcp);
40567754Smsmith		if (error)
40667754Smsmith			break;
40767754Smsmith		error = ICONV_CONVERTER_INIT(dcp);
40867754Smsmith		if (error)
40967754Smsmith			iconv_unregister_converter(dcp);
41067754Smsmith		break;
41167754Smsmith	    case MOD_UNLOAD:
41267754Smsmith		ICONV_CONVERTER_DONE(dcp);
41367754Smsmith		error = iconv_unregister_converter(dcp);
41467754Smsmith		break;
41567754Smsmith	    default:
41667754Smsmith		error = EINVAL;
41767754Smsmith	}
41867754Smsmith	return error;
41969450Smsmith}
42067754Smsmith
42167754Smsmith/*
42267754Smsmith * Common used functions
42367754Smsmith */
42467754Smsmithchar *
42567754Smsmithiconv_convstr(void *handle, char *dst, const char *src)
42667754Smsmith{
42767754Smsmith	char *p = dst;
42867754Smsmith	size_t inlen, outlen;
42967754Smsmith	int error;
43067754Smsmith
43167754Smsmith	if (handle == NULL) {
43267754Smsmith		strcpy(dst, src);
43367754Smsmith		return dst;
43467754Smsmith	}
43567754Smsmith	inlen = outlen = strlen(src);
43691116Smsmith	error = iconv_conv(handle, NULL, NULL, &p, &outlen);
43783174Smsmith	if (error)
43883174Smsmith		return NULL;
43967754Smsmith	error = iconv_conv(handle, &src, &inlen, &p, &outlen);
44067754Smsmith	if (error)
44167754Smsmith		return NULL;
44267754Smsmith	*p = 0;
44367754Smsmith	return dst;
44467754Smsmith}
44567754Smsmith
44667754Smsmithvoid *
44767754Smsmithiconv_convmem(void *handle, void *dst, const void *src, int size)
44867754Smsmith{
44967754Smsmith	const char *s = src;
45067754Smsmith	char *d = dst;
45167754Smsmith	size_t inlen, outlen;
45277424Smsmith	int error;
45367754Smsmith
45467754Smsmith	if (size == 0)
45567754Smsmith		return dst;
45667754Smsmith	if (handle == NULL) {
45767754Smsmith		memcpy(dst, src, size);
45867754Smsmith		return dst;
45967754Smsmith	}
46067754Smsmith	inlen = outlen = size;
46167754Smsmith	error = iconv_conv(handle, NULL, NULL, &d, &outlen);
46277424Smsmith	if (error)
46367754Smsmith		return NULL;
46467754Smsmith	error = iconv_conv(handle, &s, &inlen, &d, &outlen);
46567754Smsmith	if (error)
46667754Smsmith		return NULL;
46767754Smsmith	return dst;
46883174Smsmith}
46991116Smsmith
47083174Smsmithint
47183174Smsmithiconv_lookupcp(char **cpp, const char *s)
47267754Smsmith{
47367754Smsmith	if (cpp == NULL) {
47467754Smsmith		ICDEBUG("warning a NULL list passed\n", ""); /* XXX ISO variadic								macros cannot
47567754Smsmith								leave out the
47667754Smsmith								variadic args */
47767754Smsmith		return ENOENT;
47867754Smsmith	}
47982367Smsmith	for (; *cpp; cpp++)
48082367Smsmith		if (strcmp(*cpp, s) == 0)
48182367Smsmith			return 0;
48282367Smsmith	return ENOENT;
48367754Smsmith}
48467754Smsmith