iconv.c revision 75332
190926Snectar/*
2178825Sdfr * Copyright (c) 2000-2001, Boris Popov
390926Snectar * All rights reserved.
490926Snectar *
590926Snectar * Redistribution and use in source and binary forms, with or without
690926Snectar * modification, are permitted provided that the following conditions
790926Snectar * are met:
890926Snectar * 1. Redistributions of source code must retain the above copyright
990926Snectar *    notice, this list of conditions and the following disclaimer.
1090926Snectar * 2. Redistributions in binary form must reproduce the above copyright
1190926Snectar *    notice, this list of conditions and the following disclaimer in the
1290926Snectar *    documentation and/or other materials provided with the distribution.
1390926Snectar * 3. All advertising materials mentioning features or use of this software
1490926Snectar *    must display the following acknowledgement:
1590926Snectar *    This product includes software developed by Boris Popov.
1690926Snectar * 4. Neither the name of the author nor the names of any co-contributors
1790926Snectar *    may be used to endorse or promote products derived from this software
1890926Snectar *    without specific prior written permission.
1990926Snectar *
2090926Snectar * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2190926Snectar * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2290926Snectar * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2390926Snectar * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2490926Snectar * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2590926Snectar * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2690926Snectar * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2790926Snectar * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2890926Snectar * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2990926Snectar * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3090926Snectar * SUCH DAMAGE.
3190926Snectar *
3290926Snectar * $FreeBSD: head/sys/libkern/iconv.c 75332 2001-04-09 09:39:29Z bp $
3390926Snectar */
3490926Snectar#include <sys/param.h>
3590926Snectar#include <sys/systm.h>
36178825Sdfr#include <sys/kernel.h>
3790926Snectar#include <sys/iconv.h>
3890926Snectar#include <sys/malloc.h>
39178825Sdfr
4090926Snectar#include "iconv_converter_if.h"
4190926Snectar
4290926SnectarSYSCTL_DECL(_kern_iconv);
4390926Snectar
4490926SnectarSYSCTL_NODE(_kern, OID_AUTO, iconv, CTLFLAG_RW, NULL, "kernel iconv interface");
4590926Snectar
4690926SnectarMALLOC_DEFINE(M_ICONV, "ICONV", "ICONV structures");
4790926SnectarMALLOC_DEFINE(M_ICONVDATA, "ICONV data", "ICONV data");
4890926Snectar
4990926SnectarMODULE_VERSION(libiconv, 1);
50178825Sdfr
5190926Snectar#ifdef notnow
5290926Snectar/*
5390926Snectar * iconv converter instance
5490926Snectar */
5590926Snectarstruct iconv_converter {
5690926Snectar	KOBJ_FIELDS;
57178825Sdfr	void *			c_data;
5890926Snectar};
5990926Snectar#endif
6090926Snectar
6190926Snectarstruct sysctl_oid *iconv_oid_hook = &sysctl___kern_iconv;
6290926Snectar
6390926Snectar/*
6490926Snectar * List of loaded converters
6590926Snectar */
6690926Snectarstatic TAILQ_HEAD(iconv_converter_list, iconv_converter_class)
6790926Snectar    iconv_converters = TAILQ_HEAD_INITIALIZER(iconv_converters);
6890926Snectar
6990926Snectar/*
7090926Snectar * List of supported/loaded charsets pairs
7190926Snectar */
7290926Snectarstatic TAILQ_HEAD(, iconv_cspair)
7390926Snectar    iconv_cslist = TAILQ_HEAD_INITIALIZER(iconv_cslist);
7490926Snectarstatic int iconv_csid = 1;
7590926Snectar
7690926Snectarstatic char iconv_unicode_string[] = "unicode";	/* save eight bytes when possible */
7790926Snectar
78178825Sdfrstatic void iconv_unregister_cspair(struct iconv_cspair *csp);
79178825Sdfr
8090926Snectarstatic int
8190926Snectariconv_mod_unload(void)
8290926Snectar{
8390926Snectar	struct iconv_cspair *csp;
8490926Snectar
8590926Snectar	while ((csp = TAILQ_FIRST(&iconv_cslist)) != NULL) {
8690926Snectar		if (csp->cp_refcount)
8790926Snectar			return EBUSY;
8890926Snectar		iconv_unregister_cspair(csp);
8990926Snectar	}
9090926Snectar	return 0;
9190926Snectar}
9290926Snectar
9390926Snectarstatic int
9490926Snectariconv_mod_handler(module_t mod, int type, void *data)
9590926Snectar{
9690926Snectar	int error;
9790926Snectar
9890926Snectar	switch (type) {
9990926Snectar	    case MOD_LOAD:
10090926Snectar		error = 0;
10190926Snectar		break;
10290926Snectar	    case MOD_UNLOAD:
10390926Snectar		error = iconv_mod_unload();
10490926Snectar		break;
10590926Snectar	    default:
10690926Snectar		error = EINVAL;
10790926Snectar	}
10890926Snectar	return error;
109178825Sdfr}
11090926Snectar
11190926Snectarstatic moduledata_t iconv_mod = {
112	"iconv", iconv_mod_handler, NULL
113};
114
115DECLARE_MODULE(iconv, iconv_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
116
117static int
118iconv_register_converter(struct iconv_converter_class *dcp)
119{
120	kobj_class_compile((struct kobj_class*)dcp);
121	dcp->refs++;
122	TAILQ_INSERT_TAIL(&iconv_converters, dcp, cc_link);
123	return 0;
124}
125
126static int
127iconv_unregister_converter(struct iconv_converter_class *dcp)
128{
129	if (dcp->refs > 1) {
130		ICDEBUG("converter have %d referenses left\n", dcp->refs);
131		return EBUSY;
132	}
133	TAILQ_REMOVE(&iconv_converters, dcp, cc_link);
134	kobj_class_free((struct kobj_class*)dcp);
135	return 0;
136}
137
138static int
139iconv_lookupconv(const char *name, struct iconv_converter_class **dcpp)
140{
141	struct iconv_converter_class *dcp;
142
143	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
144		if (name == NULL)
145			continue;
146		if (strcmp(name, ICONV_CONVERTER_NAME(dcp)) == 0) {
147			if (dcpp)
148				*dcpp = dcp;
149			return 0;
150		}
151	}
152	return ENOENT;
153}
154
155static int
156iconv_lookupcs(const char *to, const char *from, struct iconv_cspair **cspp)
157{
158	struct iconv_cspair *csp;
159
160	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
161		if (strcmp(csp->cp_to, to) == 0 &&
162		    strcmp(csp->cp_from, from) == 0) {
163			if (cspp)
164				*cspp = csp;
165			return 0;
166		}
167	}
168	return ENOENT;
169}
170
171static int
172iconv_register_cspair(const char *to, const char *from,
173	struct iconv_converter_class *dcp, void *data,
174	struct iconv_cspair **cspp)
175{
176	struct iconv_cspair *csp;
177	char *cp;
178	int csize, ucsto, ucsfrom;
179
180	if (iconv_lookupcs(to, from, NULL) == 0)
181		return EEXIST;
182	csize = sizeof(*csp);
183	ucsto = strcmp(to, iconv_unicode_string) == 0;
184	if (!ucsto)
185		csize += strlen(to) + 1;
186	ucsfrom = strcmp(from, iconv_unicode_string) == 0;
187	if (!ucsfrom)
188		csize += strlen(from) + 1;
189	csp = malloc(csize, M_ICONV, M_WAITOK);
190	bzero(csp, csize);
191	csp->cp_id = iconv_csid++;
192	csp->cp_dcp = dcp;
193	cp = (char*)(csp + 1);
194	if (!ucsto) {
195		strcpy(cp, to);
196		csp->cp_to = cp;
197		cp += strlen(cp) + 1;
198	} else
199		csp->cp_to = iconv_unicode_string;
200	if (!ucsfrom) {
201		strcpy(cp, from);
202		csp->cp_from = cp;
203	} else
204		csp->cp_from = iconv_unicode_string;
205	csp->cp_data = data;
206
207	TAILQ_INSERT_TAIL(&iconv_cslist, csp, cp_link);
208	*cspp = csp;
209	return 0;
210}
211
212static void
213iconv_unregister_cspair(struct iconv_cspair *csp)
214{
215	TAILQ_REMOVE(&iconv_cslist, csp, cp_link);
216	if (csp->cp_data)
217		free(csp->cp_data, M_ICONVDATA);
218	free(csp, M_ICONV);
219}
220
221/*
222 * Lookup and create an instance of converter.
223 * Currently this layer didn't have associated 'instance' structure
224 * to avoid unnesessary memory allocation.
225 */
226int
227iconv_open(const char *to, const char *from, void **handle)
228{
229	struct iconv_cspair *csp, *cspfrom, *cspto;
230	struct iconv_converter_class *dcp;
231	const char *cnvname;
232	int error;
233
234	/*
235	 * First, lookup fully qualified cspairs
236	 */
237	error = iconv_lookupcs(to, from, &csp);
238	if (error == 0)
239		return ICONV_CONVERTER_OPEN(csp->cp_dcp, csp, NULL, handle);
240
241	/*
242	 * Well, nothing found. Now try to construct a composite conversion
243	 * ToDo: add a 'capability' field to converter
244	 */
245	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
246		cnvname = ICONV_CONVERTER_NAME(dcp);
247		if (cnvname == NULL)
248			continue;
249		error = iconv_lookupcs(cnvname, from, &cspfrom);
250		if (error)
251			continue;
252		error = iconv_lookupcs(to, cnvname, &cspto);
253		if (error)
254			continue;
255		/*
256		 * Fine, we're found a pair which can be combined together
257		 */
258		return ICONV_CONVERTER_OPEN(dcp, cspto, cspfrom, handle);
259	}
260	return ENOENT;
261}
262
263int
264iconv_close(void *handle)
265{
266	return ICONV_CONVERTER_CLOSE(handle);
267}
268
269int
270iconv_conv(void *handle, const char **inbuf,
271	size_t *inbytesleft, char **outbuf, size_t *outbytesleft)
272{
273	return ICONV_CONVERTER_CONV(handle, inbuf, inbytesleft, outbuf, outbytesleft);
274}
275
276/*
277 * Give a list of loaded converters. Each name terminated with 0.
278 * An empty string terminates the list.
279 */
280static int
281iconv_sysctl_drvlist(SYSCTL_HANDLER_ARGS)
282{
283	struct iconv_converter_class *dcp;
284	const char *name;
285	char spc;
286	int error;
287
288	error = 0;
289
290	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
291		name = ICONV_CONVERTER_NAME(dcp);
292		if (name == NULL)
293			continue;
294		error = SYSCTL_OUT(req, name, strlen(name) + 1);
295		if (error)
296			break;
297	}
298	if (error)
299		return error;
300	spc = 0;
301	error = SYSCTL_OUT(req, &spc, sizeof(spc));
302	return error;
303}
304
305SYSCTL_PROC(_kern_iconv, OID_AUTO, drvlist, CTLFLAG_RD | CTLTYPE_OPAQUE,
306	    NULL, 0, iconv_sysctl_drvlist, "S,xlat", "registered converters");
307
308/*
309 * List all available charset pairs.
310 */
311static int
312iconv_sysctl_cslist(SYSCTL_HANDLER_ARGS)
313{
314	struct iconv_cspair *csp;
315	struct iconv_cspair_info csi;
316	int error;
317
318	error = 0;
319	bzero(&csi, sizeof(csi));
320	csi.cs_version = ICONV_CSPAIR_INFO_VER;
321
322	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
323		csi.cs_id = csp->cp_id;
324		csi.cs_refcount = csp->cp_refcount;
325		csi.cs_base = csp->cp_base ? csp->cp_base->cp_id : 0;
326		strcpy(csi.cs_to, csp->cp_to);
327		strcpy(csi.cs_from, csp->cp_from);
328		error = SYSCTL_OUT(req, &csi, sizeof(csi));
329		if (error)
330			break;
331	}
332	return error;
333}
334
335SYSCTL_PROC(_kern_iconv, OID_AUTO, cslist, CTLFLAG_RD | CTLTYPE_OPAQUE,
336	    NULL, 0, iconv_sysctl_cslist, "S,xlat", "registered charset pairs");
337
338/*
339 * Add new charset pair
340 */
341static int
342iconv_sysctl_add(SYSCTL_HANDLER_ARGS)
343{
344	struct iconv_converter_class *dcp;
345	struct iconv_cspair *csp;
346	struct iconv_add_in din;
347	struct iconv_add_out dout;
348	int error;
349
350	error = SYSCTL_IN(req, &din, sizeof(din));
351	if (error)
352		return error;
353	if (din.ia_version != ICONV_ADD_VER)
354		return EINVAL;
355	if (din.ia_datalen > ICONV_CSMAXDATALEN)
356		return EINVAL;
357	if (iconv_lookupconv(din.ia_converter, &dcp) != 0)
358		return EINVAL;
359	error = iconv_register_cspair(din.ia_to, din.ia_from, dcp, NULL, &csp);
360	if (error)
361		return error;
362	if (din.ia_datalen) {
363		csp->cp_data = malloc(din.ia_datalen, M_ICONVDATA, M_WAITOK);
364		error = copyin(din.ia_data, csp->cp_data, din.ia_datalen);
365		if (error)
366			goto bad;
367	}
368	dout.ia_csid = csp->cp_id;
369	error = SYSCTL_OUT(req, &dout, sizeof(dout));
370	if (error)
371		goto bad;
372	return 0;
373bad:
374	iconv_unregister_cspair(csp);
375	return error;
376}
377
378SYSCTL_PROC(_kern_iconv, OID_AUTO, add, CTLFLAG_RW | CTLTYPE_OPAQUE,
379	    NULL, 0, iconv_sysctl_add, "S,xlat", "register charset pair");
380
381/*
382 * Default stubs for converters
383 */
384int
385iconv_converter_initstub(struct iconv_converter_class *dp)
386{
387	return 0;
388}
389
390int
391iconv_converter_donestub(struct iconv_converter_class *dp)
392{
393	return 0;
394}
395
396int
397iconv_converter_handler(module_t mod, int type, void *data)
398{
399	struct iconv_converter_class *dcp = data;
400	int error;
401
402	switch (type) {
403	    case MOD_LOAD:
404		error = iconv_register_converter(dcp);
405		if (error)
406			break;
407		error = ICONV_CONVERTER_INIT(dcp);
408		if (error)
409			iconv_unregister_converter(dcp);
410		break;
411	    case MOD_UNLOAD:
412		ICONV_CONVERTER_DONE(dcp);
413		error = iconv_unregister_converter(dcp);
414		break;
415	    default:
416		error = EINVAL;
417	}
418	return error;
419}
420
421/*
422 * Common used functions
423 */
424char *
425iconv_convstr(void *handle, char *dst, const char *src)
426{
427	char *p = dst;
428	int inlen, outlen, error;
429
430	if (handle == NULL) {
431		strcpy(dst, src);
432		return dst;
433	}
434	inlen = outlen = strlen(src);
435	error = iconv_conv(handle, NULL, NULL, &p, &outlen);
436	if (error)
437		return NULL;
438	error = iconv_conv(handle, &src, &inlen, &p, &outlen);
439	if (error)
440		return NULL;
441	*p = 0;
442	return dst;
443}
444
445void *
446iconv_convmem(void *handle, void *dst, const void *src, int size)
447{
448	const char *s = src;
449	char *d = dst;
450	int inlen, outlen, error;
451
452	if (size == 0)
453		return dst;
454	if (handle == NULL) {
455		memcpy(dst, src, size);
456		return dst;
457	}
458	inlen = outlen = size;
459	error = iconv_conv(handle, NULL, NULL, &d, &outlen);
460	if (error)
461		return NULL;
462	error = iconv_conv(handle, &s, &inlen, &d, &outlen);
463	if (error)
464		return NULL;
465	return dst;
466}
467
468int
469iconv_lookupcp(char **cpp, const char *s)
470{
471	if (cpp == NULL) {
472		ICDEBUG("warning a NULL list passed\n");
473		return ENOENT;
474	}
475	for (; *cpp; cpp++)
476		if (strcmp(*cpp, s) == 0)
477			return 0;
478	return ENOENT;
479}
480