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
cs_compare(const void * n1,const void * n2)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
cs_insert(char * key)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
cs_dump(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
cs_destroy(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
alias_insert(char * codeset,char * alias)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
alias_destroy(csalias_t * a)184 alias_destroy(csalias_t *a)
185 {
186 free(a->a_name);
187 free(a);
188 }
189
190
191 static void
scan_dir(DIR * dh,char sep,char * suffix)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
scan_aliases(FILE * fh)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
list_codesets(void)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