iconv.c revision 227650
1272343Sngie/*-
2272343Sngie * Copyright (c) 2000-2001 Boris Popov
3272343Sngie * All rights reserved.
4272343Sngie *
5272343Sngie * Redistribution and use in source and binary forms, with or without
6272343Sngie * modification, are permitted provided that the following conditions
7272343Sngie * are met:
8272343Sngie * 1. Redistributions of source code must retain the above copyright
9272343Sngie *    notice, this list of conditions and the following disclaimer.
10272343Sngie * 2. Redistributions in binary form must reproduce the above copyright
11272343Sngie *    notice, this list of conditions and the following disclaimer in the
12272343Sngie *    documentation and/or other materials provided with the distribution.
13272343Sngie *
14272343Sngie * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15272343Sngie * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16272343Sngie * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17272343Sngie * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18272343Sngie * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19272343Sngie * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20272343Sngie * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21272343Sngie * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22272343Sngie * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23272343Sngie * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24272343Sngie * SUCH DAMAGE.
25272343Sngie */
26272343Sngie
27272343Sngie#include <sys/cdefs.h>
28272343Sngie__FBSDID("$FreeBSD: head/sys/libkern/iconv.c 227650 2011-11-18 03:05:20Z kevlo $");
29272343Sngie
30272343Sngie#include <sys/param.h>
31272343Sngie#include <sys/systm.h>
32272343Sngie#include <sys/kernel.h>
33272343Sngie#include <sys/iconv.h>
34272343Sngie#include <sys/malloc.h>
35272343Sngie#include <sys/mount.h>
36272343Sngie#include <sys/sx.h>
37272343Sngie#include <sys/syslog.h>
38272343Sngie
39272343Sngie#include "iconv_converter_if.h"
40272343Sngie
41272343SngieSYSCTL_DECL(_kern_iconv);
42272343Sngie
43272343SngieSYSCTL_NODE(_kern, OID_AUTO, iconv, CTLFLAG_RW, NULL, "kernel iconv interface");
44272343Sngie
45272343SngieMALLOC_DEFINE(M_ICONV, "iconv", "ICONV structures");
46272343Sngiestatic MALLOC_DEFINE(M_ICONVDATA, "iconv_data", "ICONV data");
47272343Sngie
48272343SngieMODULE_VERSION(libiconv, 2);
49272343Sngie
50272343Sngiestatic struct sx iconv_lock;
51272343Sngie
52272343Sngie#ifdef notnow
53272343Sngie/*
54272343Sngie * iconv converter instance
55272343Sngie */
56272343Sngiestruct iconv_converter {
57272343Sngie	KOBJ_FIELDS;
58272343Sngie	void *			c_data;
59272343Sngie};
60272343Sngie#endif
61272343Sngie
62272343Sngiestruct sysctl_oid *iconv_oid_hook = &sysctl___kern_iconv;
63272343Sngie
64272343Sngie/*
65272343Sngie * List of loaded converters
66272343Sngie */
67272343Sngiestatic TAILQ_HEAD(iconv_converter_list, iconv_converter_class)
68272343Sngie    iconv_converters = TAILQ_HEAD_INITIALIZER(iconv_converters);
69272343Sngie
70272343Sngie/*
71272343Sngie * List of supported/loaded charsets pairs
72272343Sngie */
73272343Sngiestatic TAILQ_HEAD(, iconv_cspair)
74272343Sngie    iconv_cslist = TAILQ_HEAD_INITIALIZER(iconv_cslist);
75272343Sngiestatic int iconv_csid = 1;
76272343Sngie
77272343Sngiestatic char iconv_unicode_string[] = "unicode";	/* save eight bytes when possible */
78272343Sngie
79272343Sngiestatic void iconv_unregister_cspair(struct iconv_cspair *csp);
80272343Sngie
81272343Sngiestatic int
82272343Sngieiconv_mod_unload(void)
83272343Sngie{
84272343Sngie	struct iconv_cspair *csp;
85272343Sngie
86272343Sngie	sx_xlock(&iconv_lock);
87272343Sngie	while ((csp = TAILQ_FIRST(&iconv_cslist)) != NULL) {
88272343Sngie		if (csp->cp_refcount)
89272343Sngie			return EBUSY;
90272343Sngie	}
91272343Sngie
92272343Sngie	while ((csp = TAILQ_FIRST(&iconv_cslist)) != NULL)
93272343Sngie		iconv_unregister_cspair(csp);
94272343Sngie	sx_xunlock(&iconv_lock);
95272343Sngie	sx_destroy(&iconv_lock);
96272343Sngie	return 0;
97272343Sngie}
98272343Sngie
99272343Sngiestatic int
100272343Sngieiconv_mod_handler(module_t mod, int type, void *data)
101272343Sngie{
102272343Sngie	int error;
103272343Sngie
104272343Sngie	switch (type) {
105272343Sngie	    case MOD_LOAD:
106272343Sngie		error = 0;
107272343Sngie		sx_init(&iconv_lock, "iconv");
108272343Sngie		break;
109272343Sngie	    case MOD_UNLOAD:
110272343Sngie		error = iconv_mod_unload();
111272343Sngie		break;
112272343Sngie	    default:
113272343Sngie		error = EINVAL;
114272343Sngie	}
115272343Sngie	return error;
116272343Sngie}
117272343Sngie
118272343Sngiestatic moduledata_t iconv_mod = {
119272343Sngie	"iconv", iconv_mod_handler, NULL
120272343Sngie};
121272343Sngie
122272343SngieDECLARE_MODULE(iconv, iconv_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
123272343Sngie
124272343Sngiestatic int
125272343Sngieiconv_register_converter(struct iconv_converter_class *dcp)
126272343Sngie{
127272343Sngie	kobj_class_compile((struct kobj_class*)dcp);
128272343Sngie	dcp->refs++;
129272343Sngie	TAILQ_INSERT_TAIL(&iconv_converters, dcp, cc_link);
130272343Sngie	return 0;
131272343Sngie}
132272343Sngie
133272343Sngiestatic int
134272343Sngieiconv_unregister_converter(struct iconv_converter_class *dcp)
135272343Sngie{
136272343Sngie	if (dcp->refs > 1) {
137272343Sngie		ICDEBUG("converter have %d referenses left\n", dcp->refs);
138272343Sngie		return EBUSY;
139272343Sngie	}
140272343Sngie	TAILQ_REMOVE(&iconv_converters, dcp, cc_link);
141272343Sngie	kobj_class_free((struct kobj_class*)dcp);
142272343Sngie	return 0;
143272343Sngie}
144272343Sngie
145272343Sngiestatic int
146272343Sngieiconv_lookupconv(const char *name, struct iconv_converter_class **dcpp)
147272343Sngie{
148272343Sngie	struct iconv_converter_class *dcp;
149272343Sngie
150272343Sngie	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
151272343Sngie		if (name == NULL)
152272343Sngie			continue;
153272343Sngie		if (strcmp(name, ICONV_CONVERTER_NAME(dcp)) == 0) {
154272343Sngie			if (dcpp)
155272343Sngie				*dcpp = dcp;
156272343Sngie			return 0;
157272343Sngie		}
158272343Sngie	}
159272343Sngie	return ENOENT;
160272343Sngie}
161272343Sngie
162272343Sngiestatic int
163272343Sngieiconv_lookupcs(const char *to, const char *from, struct iconv_cspair **cspp)
164272343Sngie{
165272343Sngie	struct iconv_cspair *csp;
166272343Sngie
167272343Sngie	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
168272343Sngie		if (strcmp(csp->cp_to, to) == 0 &&
169272343Sngie		    strcmp(csp->cp_from, from) == 0) {
170272343Sngie			if (cspp)
171272343Sngie				*cspp = csp;
172272343Sngie			return 0;
173272343Sngie		}
174272343Sngie	}
175272343Sngie	return ENOENT;
176272343Sngie}
177272343Sngie
178272343Sngiestatic int
179272343Sngieiconv_register_cspair(const char *to, const char *from,
180272343Sngie	struct iconv_converter_class *dcp, void *data,
181272343Sngie	struct iconv_cspair **cspp)
182272343Sngie{
183272343Sngie	struct iconv_cspair *csp;
184272343Sngie	char *cp;
185272343Sngie	int csize, ucsto, ucsfrom;
186272343Sngie
187272343Sngie	if (iconv_lookupcs(to, from, NULL) == 0)
188272343Sngie		return EEXIST;
189272343Sngie	csize = sizeof(*csp);
190272343Sngie	ucsto = strcmp(to, iconv_unicode_string) == 0;
191272343Sngie	if (!ucsto)
192272343Sngie		csize += strlen(to) + 1;
193272343Sngie	ucsfrom = strcmp(from, iconv_unicode_string) == 0;
194272343Sngie	if (!ucsfrom)
195272343Sngie		csize += strlen(from) + 1;
196272343Sngie	csp = malloc(csize, M_ICONV, M_WAITOK);
197272343Sngie	bzero(csp, csize);
198272343Sngie	csp->cp_id = iconv_csid++;
199272343Sngie	csp->cp_dcp = dcp;
200272343Sngie	cp = (char*)(csp + 1);
201272343Sngie	if (!ucsto) {
202272343Sngie		strcpy(cp, to);
203272343Sngie		csp->cp_to = cp;
204272343Sngie		cp += strlen(cp) + 1;
205272343Sngie	} else
206272343Sngie		csp->cp_to = iconv_unicode_string;
207272343Sngie	if (!ucsfrom) {
208272343Sngie		strcpy(cp, from);
209272343Sngie		csp->cp_from = cp;
210272343Sngie	} else
211272343Sngie		csp->cp_from = iconv_unicode_string;
212272343Sngie	csp->cp_data = data;
213272343Sngie
214272343Sngie	TAILQ_INSERT_TAIL(&iconv_cslist, csp, cp_link);
215272343Sngie	*cspp = csp;
216272343Sngie	return 0;
217272343Sngie}
218272343Sngie
219272343Sngiestatic void
220272343Sngieiconv_unregister_cspair(struct iconv_cspair *csp)
221272343Sngie{
222272343Sngie	TAILQ_REMOVE(&iconv_cslist, csp, cp_link);
223272343Sngie	if (csp->cp_data)
224272343Sngie		free(csp->cp_data, M_ICONVDATA);
225272343Sngie	free(csp, M_ICONV);
226272343Sngie}
227272343Sngie
228272343Sngie/*
229272343Sngie * Lookup and create an instance of converter.
230272343Sngie * Currently this layer didn't have associated 'instance' structure
231272343Sngie * to avoid unnesessary memory allocation.
232272343Sngie */
233272343Sngieint
234272343Sngieiconv_open(const char *to, const char *from, void **handle)
235272343Sngie{
236272343Sngie	struct iconv_cspair *csp, *cspfrom, *cspto;
237272343Sngie	struct iconv_converter_class *dcp;
238272343Sngie	const char *cnvname;
239272343Sngie	int error;
240272343Sngie
241272343Sngie	/*
242272343Sngie	 * First, lookup fully qualified cspairs
243272343Sngie	 */
244272343Sngie	error = iconv_lookupcs(to, from, &csp);
245272343Sngie	if (error == 0)
246272343Sngie		return ICONV_CONVERTER_OPEN(csp->cp_dcp, csp, NULL, handle);
247272343Sngie
248272343Sngie	/*
249272343Sngie	 * Well, nothing found. Now try to construct a composite conversion
250272343Sngie	 * ToDo: add a 'capability' field to converter
251272343Sngie	 */
252272343Sngie	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
253272343Sngie		cnvname = ICONV_CONVERTER_NAME(dcp);
254272343Sngie		if (cnvname == NULL)
255272343Sngie			continue;
256272343Sngie		error = iconv_lookupcs(cnvname, from, &cspfrom);
257272343Sngie		if (error)
258272343Sngie			continue;
259272343Sngie		error = iconv_lookupcs(to, cnvname, &cspto);
260272343Sngie		if (error)
261272343Sngie			continue;
262272343Sngie		/*
263272343Sngie		 * Fine, we're found a pair which can be combined together
264272343Sngie		 */
265272343Sngie		return ICONV_CONVERTER_OPEN(dcp, cspto, cspfrom, handle);
266272343Sngie	}
267272343Sngie	return ENOENT;
268272343Sngie}
269272343Sngie
270272343Sngieint
271272343Sngieiconv_close(void *handle)
272272343Sngie{
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
380int
381iconv_add(const char *converter, const char *to, const char *from)
382{
383	struct iconv_converter_class *dcp;
384	struct iconv_cspair *csp;
385
386	if (iconv_lookupconv(converter, &dcp) != 0)
387		return EINVAL;
388
389	return iconv_register_cspair(to, from, dcp, NULL, &csp);
390}
391
392/*
393 * Add new charset pair
394 */
395static int
396iconv_sysctl_add(SYSCTL_HANDLER_ARGS)
397{
398	struct iconv_converter_class *dcp;
399	struct iconv_cspair *csp;
400	struct iconv_add_in din;
401	struct iconv_add_out dout;
402	int error;
403
404	error = SYSCTL_IN(req, &din, sizeof(din));
405	if (error)
406		return error;
407	if (din.ia_version != ICONV_ADD_VER)
408		return EINVAL;
409	if (din.ia_datalen > ICONV_CSMAXDATALEN)
410		return EINVAL;
411	if (strlen(din.ia_from) >= ICONV_CSNMAXLEN)
412		return EINVAL;
413	if (strlen(din.ia_to) >= ICONV_CSNMAXLEN)
414		return EINVAL;
415	if (strlen(din.ia_converter) >= ICONV_CNVNMAXLEN)
416		return EINVAL;
417	if (iconv_lookupconv(din.ia_converter, &dcp) != 0)
418		return EINVAL;
419	sx_xlock(&iconv_lock);
420	error = iconv_register_cspair(din.ia_to, din.ia_from, dcp, NULL, &csp);
421	if (error) {
422		sx_xunlock(&iconv_lock);
423		return error;
424	}
425	if (din.ia_datalen) {
426		csp->cp_data = malloc(din.ia_datalen, M_ICONVDATA, M_WAITOK);
427		error = copyin(din.ia_data, csp->cp_data, din.ia_datalen);
428		if (error)
429			goto bad;
430	}
431	dout.ia_csid = csp->cp_id;
432	error = SYSCTL_OUT(req, &dout, sizeof(dout));
433	if (error)
434		goto bad;
435	sx_xunlock(&iconv_lock);
436	ICDEBUG("%s => %s, %d bytes\n",din.ia_from, din.ia_to, din.ia_datalen);
437	return 0;
438bad:
439	iconv_unregister_cspair(csp);
440	sx_xunlock(&iconv_lock);
441	return error;
442}
443
444SYSCTL_PROC(_kern_iconv, OID_AUTO, add, CTLFLAG_RW | CTLTYPE_OPAQUE,
445	    NULL, 0, iconv_sysctl_add, "S,xlat", "register charset pair");
446
447/*
448 * Default stubs for converters
449 */
450int
451iconv_converter_initstub(struct iconv_converter_class *dp)
452{
453	return 0;
454}
455
456int
457iconv_converter_donestub(struct iconv_converter_class *dp)
458{
459	return 0;
460}
461
462int
463iconv_converter_tolowerstub(int c, void *handle)
464{
465	return (c);
466}
467
468int
469iconv_converter_handler(module_t mod, int type, void *data)
470{
471	struct iconv_converter_class *dcp = data;
472	int error;
473
474	switch (type) {
475	    case MOD_LOAD:
476		sx_xlock(&iconv_lock);
477		error = iconv_register_converter(dcp);
478		if (error) {
479			sx_xunlock(&iconv_lock);
480			break;
481		}
482		error = ICONV_CONVERTER_INIT(dcp);
483		if (error)
484			iconv_unregister_converter(dcp);
485		sx_xunlock(&iconv_lock);
486		break;
487	    case MOD_UNLOAD:
488		sx_xlock(&iconv_lock);
489		ICONV_CONVERTER_DONE(dcp);
490		error = iconv_unregister_converter(dcp);
491		sx_xunlock(&iconv_lock);
492		break;
493	    default:
494		error = EINVAL;
495	}
496	return error;
497}
498
499/*
500 * Common used functions (don't use with unicode)
501 */
502char *
503iconv_convstr(void *handle, char *dst, const char *src)
504{
505	char *p = dst;
506	size_t inlen, outlen;
507	int error;
508
509	if (handle == NULL) {
510		strcpy(dst, src);
511		return dst;
512	}
513	inlen = outlen = strlen(src);
514	error = iconv_conv(handle, NULL, NULL, &p, &outlen);
515	if (error)
516		return NULL;
517	error = iconv_conv(handle, &src, &inlen, &p, &outlen);
518	if (error)
519		return NULL;
520	*p = 0;
521	return dst;
522}
523
524void *
525iconv_convmem(void *handle, void *dst, const void *src, int size)
526{
527	const char *s = src;
528	char *d = dst;
529	size_t inlen, outlen;
530	int error;
531
532	if (size == 0)
533		return dst;
534	if (handle == NULL) {
535		memcpy(dst, src, size);
536		return dst;
537	}
538	inlen = outlen = size;
539	error = iconv_conv(handle, NULL, NULL, &d, &outlen);
540	if (error)
541		return NULL;
542	error = iconv_conv(handle, &s, &inlen, &d, &outlen);
543	if (error)
544		return NULL;
545	return dst;
546}
547
548int
549iconv_lookupcp(char **cpp, const char *s)
550{
551	if (cpp == NULL) {
552		ICDEBUG("warning a NULL list passed\n", ""); /* XXX ISO variadic								macros cannot
553								leave out the
554								variadic args */
555		return ENOENT;
556	}
557	for (; *cpp; cpp++)
558		if (strcmp(*cpp, s) == 0)
559			return 0;
560	return ENOENT;
561}
562
563/*
564 * Return if fsname is in use of not
565 */
566int
567iconv_vfs_refcount(const char *fsname)
568{
569	struct vfsconf *vfsp;
570
571	vfsp = vfs_byname(fsname);
572	if (vfsp != NULL && vfsp->vfc_refcount > 0)
573		return (EBUSY);
574	return (0);
575}
576