xref: /illumos-gate/usr/src/cmd/iconv/iconv_list.c (revision 1f606c5b8e527ed924f5cbdbbce612887c7dbe32)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
14  */
15 
16 /*
17  * implement "iconv -l"
18  */
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <limits.h>
25 #include <unistd.h>
26 #include <alloca.h>
27 #include <sys/avl.h>
28 #include <sys/list.h>
29 #include <sys/param.h>
30 #include <stddef.h>
31 #include <dirent.h>
32 #include <unistd.h>
33 
34 #define	PATH_LIBICONV	"/usr/lib/iconv"
35 #define	PATH_BTABLES	"/usr/lib/iconv/geniconvtbl/binarytables"
36 #define	PATH_ALIASES	"/usr/lib/iconv/alias"
37 
38 typedef struct codeset {
39 	avl_node_t cs_node;
40 	char *cs_name;
41 	list_t cs_aliases;
42 } codeset_t;
43 
44 typedef struct csalias {
45 	list_node_t a_node;
46 	char *a_name;
47 } csalias_t;
48 
49 static avl_tree_t	cs_avl;
50 
51 static void alias_destroy(csalias_t *);
52 
53 /*
54  * codesets
55  */
56 
57 static int
58 cs_compare(const void *n1, const void *n2)
59 {
60 	const codeset_t *c1 = n1;
61 	const codeset_t *c2 = n2;
62 	int rv;
63 
64 	rv = strcmp(c1->cs_name, c2->cs_name);
65 	return ((rv < 0) ? -1 : (rv > 0) ? 1 : 0);
66 }
67 
68 static void
69 cs_insert(char *key)
70 {
71 	codeset_t tmp, *cs;
72 	avl_index_t where;
73 
74 	(void) memset(&tmp, 0, sizeof (tmp));
75 	tmp.cs_name = key;
76 
77 	cs = avl_find(&cs_avl, &tmp, &where);
78 	if (cs != NULL)
79 		return; /* already there */
80 
81 	cs = calloc(1, sizeof (*cs));
82 	if (cs == NULL) {
83 		perror("cs_insert:calloc");
84 		exit(1);
85 	}
86 	cs->cs_name = strdup(key);
87 	if (cs->cs_name == NULL) {
88 		perror("cs_insert:strdup");
89 		exit(1);
90 	}
91 	list_create(&cs->cs_aliases, sizeof (csalias_t),
92 	    offsetof(csalias_t, a_node));
93 
94 	avl_insert(&cs_avl, cs, where);
95 }
96 
97 const char topmatter[] =
98 	"The following are all supported code set names.  All combinations\n"
99 	"of those names are not necessarily available for the pair of the\n"
100 	"fromcode-tocode.  Some of those code set names have aliases, which\n"
101 	"are case-insensitive and described in parentheses following the\n"
102 	"canonical name:\n";
103 
104 
105 static void
106 cs_dump(void)
107 {
108 	codeset_t *cs;
109 	csalias_t *a;
110 
111 	(void) puts(topmatter);
112 
113 	for (cs = avl_first(&cs_avl); cs != NULL;
114 	    cs = AVL_NEXT(&cs_avl, cs)) {
115 
116 		(void) printf("    %s", cs->cs_name);
117 		if (!list_is_empty(&cs->cs_aliases)) {
118 			a = list_head(&cs->cs_aliases);
119 			(void) printf(" (%s", a->a_name);
120 			while ((a = list_next(&cs->cs_aliases, a)) != NULL) {
121 				(void) printf(", %s", a->a_name);
122 			}
123 			(void) printf(")");
124 		}
125 		(void) printf(",\n");
126 	}
127 }
128 
129 static void
130 cs_destroy(void)
131 {
132 	void *cookie = NULL;
133 	codeset_t *cs;
134 	csalias_t *a;
135 
136 	while ((cs = avl_destroy_nodes(&cs_avl, &cookie)) != NULL) {
137 		while ((a = list_remove_head(&cs->cs_aliases)) != NULL) {
138 			alias_destroy(a);
139 		}
140 		free(cs->cs_name);
141 		free(cs);
142 	}
143 	avl_destroy(&cs_avl);
144 }
145 
146 /*
147  * aliases
148  */
149 
150 static void
151 alias_insert(char *codeset, char *alias)
152 {
153 	codeset_t tcs, *cs;
154 	csalias_t *a;
155 
156 	/*
157 	 * Find the codeset.  If non-existent,
158 	 * ignore aliases of this codeset.
159 	 */
160 	(void) memset(&tcs, 0, sizeof (tcs));
161 	tcs.cs_name = codeset;
162 	cs = avl_find(&cs_avl, &tcs, NULL);
163 	if (cs == NULL)
164 		return;
165 
166 	/*
167 	 * Add this alias
168 	 */
169 	a = calloc(1, sizeof (*a));
170 	if (a == NULL) {
171 		perror("alias_insert:calloc");
172 		exit(1);
173 	}
174 	a->a_name = strdup(alias);
175 	if (a->a_name == NULL) {
176 		perror("alias_insert:strdup");
177 		exit(1);
178 	}
179 
180 	list_insert_tail(&cs->cs_aliases, a);
181 }
182 
183 static void
184 alias_destroy(csalias_t *a)
185 {
186 	free(a->a_name);
187 	free(a);
188 }
189 
190 
191 static void
192 scan_dir(DIR *dh, char sep, char *suffix)
193 {
194 	char namebuf[MAXNAMELEN];
195 	struct dirent *de;
196 
197 	while ((de = readdir(dh)) != NULL) {
198 		char *p2, *p1;
199 
200 		/*
201 		 * We'll modify, so let's copy.  If the dirent name is
202 		 * longer than MAXNAMELEN, then it can't possibly be a
203 		 * valid pair of codeset names, so just skip it.
204 		 */
205 		if (strlcpy(namebuf, de->d_name, sizeof (namebuf)) >=
206 		    sizeof (namebuf))
207 			continue;
208 
209 		/* Find suffix */
210 		p2 = strrchr(namebuf, *suffix);
211 		if (p2 == NULL)
212 			continue;
213 		if (strcmp(p2, suffix) != 0)
214 			continue;
215 		*p2 = '\0';
216 
217 		p1 = strchr(namebuf, sep);
218 		if (p1 == NULL)
219 			continue;
220 		*p1++ = '\0';
221 
222 		/* More than one sep? */
223 		if (strchr(p1, sep) != NULL)
224 			continue;
225 
226 		/* Empty strings? */
227 		if (*namebuf == '\0' || *p1 == '\0')
228 			continue;
229 
230 		/* OK, add both to the map. */
231 		cs_insert(namebuf);
232 		cs_insert(p1);
233 	}
234 }
235 
236 static void
237 scan_aliases(FILE *fh)
238 {
239 	char linebuf[256];
240 	char *p1, *p2;
241 
242 	while (fgets(linebuf, sizeof (linebuf), fh) != NULL) {
243 		if (linebuf[0] == '#')
244 			continue;
245 		p1 = strchr(linebuf, ' ');
246 		if (p1 == NULL)
247 			continue;
248 		*p1++ = '\0';
249 		p2 = strchr(p1, '\n');
250 		if (p2 == NULL)
251 			continue;
252 		*p2 = '\0';
253 		alias_insert(p1, linebuf);
254 	}
255 }
256 
257 int
258 list_codesets(void)
259 {
260 	DIR *dh;
261 	FILE *fh;
262 
263 	avl_create(&cs_avl, cs_compare, sizeof (codeset_t),
264 	    offsetof(codeset_t, cs_node));
265 
266 	dh = opendir(PATH_LIBICONV);
267 	if (dh == NULL) {
268 		perror(PATH_LIBICONV);
269 		return (1);
270 	}
271 	scan_dir(dh, '%', ".so");
272 	(void) closedir(dh);
273 
274 	dh = opendir(PATH_BTABLES);
275 	if (dh == NULL) {
276 		perror(PATH_BTABLES);
277 		return (1);
278 	}
279 	scan_dir(dh, '%', ".bt");
280 	(void) closedir(dh);
281 
282 	fh = fopen(PATH_ALIASES, "r");
283 	if (fh == NULL) {
284 		perror(PATH_ALIASES);
285 		/* let's continue */
286 	} else {
287 		scan_aliases(fh);
288 		(void) fclose(fh);
289 	}
290 
291 	cs_dump();
292 
293 	cs_destroy();
294 
295 	return (0);
296 }
297