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 (.so | .t) */ 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 rewinddir(dh); 273 scan_dir(dh, '.', ".t"); 274 (void) closedir(dh); 275 276 dh = opendir(PATH_BTABLES); 277 if (dh == NULL) { 278 perror(PATH_BTABLES); 279 return (1); 280 } 281 scan_dir(dh, '%', ".bt"); 282 (void) closedir(dh); 283 284 fh = fopen(PATH_ALIASES, "r"); 285 if (fh == NULL) { 286 perror(PATH_ALIASES); 287 /* let's continue */ 288 } else { 289 scan_aliases(fh); 290 (void) fclose(fh); 291 } 292 293 cs_dump(); 294 295 cs_destroy(); 296 297 return (0); 298 } 299