iconv.c revision 100080
1/*
2 * Copyright (c) 2000-2001, Boris Popov
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *    This product includes software developed by Boris Popov.
16 * 4. Neither the name of the author nor the names of any co-contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 * $FreeBSD: head/sys/libkern/iconv.c 100080 2002-07-15 13:34:50Z markm $
33 */
34#include <sys/param.h>
35#include <sys/systm.h>
36#include <sys/kernel.h>
37#include <sys/iconv.h>
38#include <sys/malloc.h>
39
40#include "iconv_converter_if.h"
41
42SYSCTL_DECL(_kern_iconv);
43
44SYSCTL_NODE(_kern, OID_AUTO, iconv, CTLFLAG_RW, NULL, "kernel iconv interface");
45
46MALLOC_DEFINE(M_ICONV, "ICONV", "ICONV structures");
47MALLOC_DEFINE(M_ICONVDATA, "ICONV data", "ICONV data");
48
49MODULE_VERSION(libiconv, 1);
50
51#ifdef notnow
52/*
53 * iconv converter instance
54 */
55struct iconv_converter {
56	KOBJ_FIELDS;
57	void *			c_data;
58};
59#endif
60
61struct sysctl_oid *iconv_oid_hook = &sysctl___kern_iconv;
62
63/*
64 * List of loaded converters
65 */
66static TAILQ_HEAD(iconv_converter_list, iconv_converter_class)
67    iconv_converters = TAILQ_HEAD_INITIALIZER(iconv_converters);
68
69/*
70 * List of supported/loaded charsets pairs
71 */
72static TAILQ_HEAD(, iconv_cspair)
73    iconv_cslist = TAILQ_HEAD_INITIALIZER(iconv_cslist);
74static int iconv_csid = 1;
75
76static char iconv_unicode_string[] = "unicode";	/* save eight bytes when possible */
77
78static void iconv_unregister_cspair(struct iconv_cspair *csp);
79
80static int
81iconv_mod_unload(void)
82{
83	struct iconv_cspair *csp;
84
85	while ((csp = TAILQ_FIRST(&iconv_cslist)) != NULL) {
86		if (csp->cp_refcount)
87			return EBUSY;
88		iconv_unregister_cspair(csp);
89	}
90	return 0;
91}
92
93static int
94iconv_mod_handler(module_t mod, int type, void *data)
95{
96	int error;
97
98	switch (type) {
99	    case MOD_LOAD:
100		error = 0;
101		break;
102	    case MOD_UNLOAD:
103		error = iconv_mod_unload();
104		break;
105	    default:
106		error = EINVAL;
107	}
108	return error;
109}
110
111static 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", ""); /* XXX ISO variadic								macros cannot
473								leave out the
474								variadic args */
475		return ENOENT;
476	}
477	for (; *cpp; cpp++)
478		if (strcmp(*cpp, s) == 0)
479			return 0;
480	return ENOENT;
481}
482