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