iconv.c revision 116189
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
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: head/sys/libkern/iconv.c 116189 2003-06-11 05:37:42Z obrien $");
35
36#include <sys/param.h>
37#include <sys/systm.h>
38#include <sys/kernel.h>
39#include <sys/iconv.h>
40#include <sys/malloc.h>
41
42#include "iconv_converter_if.h"
43
44SYSCTL_DECL(_kern_iconv);
45
46SYSCTL_NODE(_kern, OID_AUTO, iconv, CTLFLAG_RW, NULL, "kernel iconv interface");
47
48MALLOC_DEFINE(M_ICONV, "ICONV", "ICONV structures");
49MALLOC_DEFINE(M_ICONVDATA, "ICONV data", "ICONV data");
50
51MODULE_VERSION(libiconv, 1);
52
53#ifdef notnow
54/*
55 * iconv converter instance
56 */
57struct iconv_converter {
58	KOBJ_FIELDS;
59	void *			c_data;
60};
61#endif
62
63struct sysctl_oid *iconv_oid_hook = &sysctl___kern_iconv;
64
65/*
66 * List of loaded converters
67 */
68static TAILQ_HEAD(iconv_converter_list, iconv_converter_class)
69    iconv_converters = TAILQ_HEAD_INITIALIZER(iconv_converters);
70
71/*
72 * List of supported/loaded charsets pairs
73 */
74static TAILQ_HEAD(, iconv_cspair)
75    iconv_cslist = TAILQ_HEAD_INITIALIZER(iconv_cslist);
76static int iconv_csid = 1;
77
78static char iconv_unicode_string[] = "unicode";	/* save eight bytes when possible */
79
80static void iconv_unregister_cspair(struct iconv_cspair *csp);
81
82static int
83iconv_mod_unload(void)
84{
85	struct iconv_cspair *csp;
86
87	while ((csp = TAILQ_FIRST(&iconv_cslist)) != NULL) {
88		if (csp->cp_refcount)
89			return EBUSY;
90		iconv_unregister_cspair(csp);
91	}
92	return 0;
93}
94
95static int
96iconv_mod_handler(module_t mod, int type, void *data)
97{
98	int error;
99
100	switch (type) {
101	    case MOD_LOAD:
102		error = 0;
103		break;
104	    case MOD_UNLOAD:
105		error = iconv_mod_unload();
106		break;
107	    default:
108		error = EINVAL;
109	}
110	return error;
111}
112
113static moduledata_t iconv_mod = {
114	"iconv", iconv_mod_handler, NULL
115};
116
117DECLARE_MODULE(iconv, iconv_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
118
119static int
120iconv_register_converter(struct iconv_converter_class *dcp)
121{
122	kobj_class_compile((struct kobj_class*)dcp);
123	dcp->refs++;
124	TAILQ_INSERT_TAIL(&iconv_converters, dcp, cc_link);
125	return 0;
126}
127
128static int
129iconv_unregister_converter(struct iconv_converter_class *dcp)
130{
131	if (dcp->refs > 1) {
132		ICDEBUG("converter have %d referenses left\n", dcp->refs);
133		return EBUSY;
134	}
135	TAILQ_REMOVE(&iconv_converters, dcp, cc_link);
136	kobj_class_free((struct kobj_class*)dcp);
137	return 0;
138}
139
140static int
141iconv_lookupconv(const char *name, struct iconv_converter_class **dcpp)
142{
143	struct iconv_converter_class *dcp;
144
145	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
146		if (name == NULL)
147			continue;
148		if (strcmp(name, ICONV_CONVERTER_NAME(dcp)) == 0) {
149			if (dcpp)
150				*dcpp = dcp;
151			return 0;
152		}
153	}
154	return ENOENT;
155}
156
157static int
158iconv_lookupcs(const char *to, const char *from, struct iconv_cspair **cspp)
159{
160	struct iconv_cspair *csp;
161
162	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
163		if (strcmp(csp->cp_to, to) == 0 &&
164		    strcmp(csp->cp_from, from) == 0) {
165			if (cspp)
166				*cspp = csp;
167			return 0;
168		}
169	}
170	return ENOENT;
171}
172
173static int
174iconv_register_cspair(const char *to, const char *from,
175	struct iconv_converter_class *dcp, void *data,
176	struct iconv_cspair **cspp)
177{
178	struct iconv_cspair *csp;
179	char *cp;
180	int csize, ucsto, ucsfrom;
181
182	if (iconv_lookupcs(to, from, NULL) == 0)
183		return EEXIST;
184	csize = sizeof(*csp);
185	ucsto = strcmp(to, iconv_unicode_string) == 0;
186	if (!ucsto)
187		csize += strlen(to) + 1;
188	ucsfrom = strcmp(from, iconv_unicode_string) == 0;
189	if (!ucsfrom)
190		csize += strlen(from) + 1;
191	csp = malloc(csize, M_ICONV, M_WAITOK);
192	bzero(csp, csize);
193	csp->cp_id = iconv_csid++;
194	csp->cp_dcp = dcp;
195	cp = (char*)(csp + 1);
196	if (!ucsto) {
197		strcpy(cp, to);
198		csp->cp_to = cp;
199		cp += strlen(cp) + 1;
200	} else
201		csp->cp_to = iconv_unicode_string;
202	if (!ucsfrom) {
203		strcpy(cp, from);
204		csp->cp_from = cp;
205	} else
206		csp->cp_from = iconv_unicode_string;
207	csp->cp_data = data;
208
209	TAILQ_INSERT_TAIL(&iconv_cslist, csp, cp_link);
210	*cspp = csp;
211	return 0;
212}
213
214static void
215iconv_unregister_cspair(struct iconv_cspair *csp)
216{
217	TAILQ_REMOVE(&iconv_cslist, csp, cp_link);
218	if (csp->cp_data)
219		free(csp->cp_data, M_ICONVDATA);
220	free(csp, M_ICONV);
221}
222
223/*
224 * Lookup and create an instance of converter.
225 * Currently this layer didn't have associated 'instance' structure
226 * to avoid unnesessary memory allocation.
227 */
228int
229iconv_open(const char *to, const char *from, void **handle)
230{
231	struct iconv_cspair *csp, *cspfrom, *cspto;
232	struct iconv_converter_class *dcp;
233	const char *cnvname;
234	int error;
235
236	/*
237	 * First, lookup fully qualified cspairs
238	 */
239	error = iconv_lookupcs(to, from, &csp);
240	if (error == 0)
241		return ICONV_CONVERTER_OPEN(csp->cp_dcp, csp, NULL, handle);
242
243	/*
244	 * Well, nothing found. Now try to construct a composite conversion
245	 * ToDo: add a 'capability' field to converter
246	 */
247	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
248		cnvname = ICONV_CONVERTER_NAME(dcp);
249		if (cnvname == NULL)
250			continue;
251		error = iconv_lookupcs(cnvname, from, &cspfrom);
252		if (error)
253			continue;
254		error = iconv_lookupcs(to, cnvname, &cspto);
255		if (error)
256			continue;
257		/*
258		 * Fine, we're found a pair which can be combined together
259		 */
260		return ICONV_CONVERTER_OPEN(dcp, cspto, cspfrom, handle);
261	}
262	return ENOENT;
263}
264
265int
266iconv_close(void *handle)
267{
268	return ICONV_CONVERTER_CLOSE(handle);
269}
270
271int
272iconv_conv(void *handle, const char **inbuf,
273	size_t *inbytesleft, char **outbuf, size_t *outbytesleft)
274{
275	return ICONV_CONVERTER_CONV(handle, inbuf, inbytesleft, outbuf, outbytesleft);
276}
277
278/*
279 * Give a list of loaded converters. Each name terminated with 0.
280 * An empty string terminates the list.
281 */
282static int
283iconv_sysctl_drvlist(SYSCTL_HANDLER_ARGS)
284{
285	struct iconv_converter_class *dcp;
286	const char *name;
287	char spc;
288	int error;
289
290	error = 0;
291
292	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
293		name = ICONV_CONVERTER_NAME(dcp);
294		if (name == NULL)
295			continue;
296		error = SYSCTL_OUT(req, name, strlen(name) + 1);
297		if (error)
298			break;
299	}
300	if (error)
301		return error;
302	spc = 0;
303	error = SYSCTL_OUT(req, &spc, sizeof(spc));
304	return error;
305}
306
307SYSCTL_PROC(_kern_iconv, OID_AUTO, drvlist, CTLFLAG_RD | CTLTYPE_OPAQUE,
308	    NULL, 0, iconv_sysctl_drvlist, "S,xlat", "registered converters");
309
310/*
311 * List all available charset pairs.
312 */
313static int
314iconv_sysctl_cslist(SYSCTL_HANDLER_ARGS)
315{
316	struct iconv_cspair *csp;
317	struct iconv_cspair_info csi;
318	int error;
319
320	error = 0;
321	bzero(&csi, sizeof(csi));
322	csi.cs_version = ICONV_CSPAIR_INFO_VER;
323
324	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
325		csi.cs_id = csp->cp_id;
326		csi.cs_refcount = csp->cp_refcount;
327		csi.cs_base = csp->cp_base ? csp->cp_base->cp_id : 0;
328		strcpy(csi.cs_to, csp->cp_to);
329		strcpy(csi.cs_from, csp->cp_from);
330		error = SYSCTL_OUT(req, &csi, sizeof(csi));
331		if (error)
332			break;
333	}
334	return error;
335}
336
337SYSCTL_PROC(_kern_iconv, OID_AUTO, cslist, CTLFLAG_RD | CTLTYPE_OPAQUE,
338	    NULL, 0, iconv_sysctl_cslist, "S,xlat", "registered charset pairs");
339
340/*
341 * Add new charset pair
342 */
343static int
344iconv_sysctl_add(SYSCTL_HANDLER_ARGS)
345{
346	struct iconv_converter_class *dcp;
347	struct iconv_cspair *csp;
348	struct iconv_add_in din;
349	struct iconv_add_out dout;
350	int error;
351
352	error = SYSCTL_IN(req, &din, sizeof(din));
353	if (error)
354		return error;
355	if (din.ia_version != ICONV_ADD_VER)
356		return EINVAL;
357	if (din.ia_datalen > ICONV_CSMAXDATALEN)
358		return EINVAL;
359	if (iconv_lookupconv(din.ia_converter, &dcp) != 0)
360		return EINVAL;
361	error = iconv_register_cspair(din.ia_to, din.ia_from, dcp, NULL, &csp);
362	if (error)
363		return error;
364	if (din.ia_datalen) {
365		csp->cp_data = malloc(din.ia_datalen, M_ICONVDATA, M_WAITOK);
366		error = copyin(din.ia_data, csp->cp_data, din.ia_datalen);
367		if (error)
368			goto bad;
369	}
370	dout.ia_csid = csp->cp_id;
371	error = SYSCTL_OUT(req, &dout, sizeof(dout));
372	if (error)
373		goto bad;
374	return 0;
375bad:
376	iconv_unregister_cspair(csp);
377	return error;
378}
379
380SYSCTL_PROC(_kern_iconv, OID_AUTO, add, CTLFLAG_RW | CTLTYPE_OPAQUE,
381	    NULL, 0, iconv_sysctl_add, "S,xlat", "register charset pair");
382
383/*
384 * Default stubs for converters
385 */
386int
387iconv_converter_initstub(struct iconv_converter_class *dp)
388{
389	return 0;
390}
391
392int
393iconv_converter_donestub(struct iconv_converter_class *dp)
394{
395	return 0;
396}
397
398int
399iconv_converter_handler(module_t mod, int type, void *data)
400{
401	struct iconv_converter_class *dcp = data;
402	int error;
403
404	switch (type) {
405	    case MOD_LOAD:
406		error = iconv_register_converter(dcp);
407		if (error)
408			break;
409		error = ICONV_CONVERTER_INIT(dcp);
410		if (error)
411			iconv_unregister_converter(dcp);
412		break;
413	    case MOD_UNLOAD:
414		ICONV_CONVERTER_DONE(dcp);
415		error = iconv_unregister_converter(dcp);
416		break;
417	    default:
418		error = EINVAL;
419	}
420	return error;
421}
422
423/*
424 * Common used functions
425 */
426char *
427iconv_convstr(void *handle, char *dst, const char *src)
428{
429	char *p = dst;
430	size_t inlen, outlen;
431	int error;
432
433	if (handle == NULL) {
434		strcpy(dst, src);
435		return dst;
436	}
437	inlen = outlen = strlen(src);
438	error = iconv_conv(handle, NULL, NULL, &p, &outlen);
439	if (error)
440		return NULL;
441	error = iconv_conv(handle, &src, &inlen, &p, &outlen);
442	if (error)
443		return NULL;
444	*p = 0;
445	return dst;
446}
447
448void *
449iconv_convmem(void *handle, void *dst, const void *src, int size)
450{
451	const char *s = src;
452	char *d = dst;
453	size_t inlen, outlen;
454	int error;
455
456	if (size == 0)
457		return dst;
458	if (handle == NULL) {
459		memcpy(dst, src, size);
460		return dst;
461	}
462	inlen = outlen = size;
463	error = iconv_conv(handle, NULL, NULL, &d, &outlen);
464	if (error)
465		return NULL;
466	error = iconv_conv(handle, &s, &inlen, &d, &outlen);
467	if (error)
468		return NULL;
469	return dst;
470}
471
472int
473iconv_lookupcp(char **cpp, const char *s)
474{
475	if (cpp == NULL) {
476		ICDEBUG("warning a NULL list passed\n", ""); /* XXX ISO variadic								macros cannot
477								leave out the
478								variadic args */
479		return ENOENT;
480	}
481	for (; *cpp; cpp++)
482		if (strcmp(*cpp, s) == 0)
483			return 0;
484	return ENOENT;
485}
486