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