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