/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2016 Nexenta Systems, Inc. All rights reserved. */ /* * implement "iconv -l" */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define PATH_LIBICONV "/usr/lib/iconv" #define PATH_BTABLES "/usr/lib/iconv/geniconvtbl/binarytables" #define PATH_ALIASES "/usr/lib/iconv/alias" typedef struct codeset { avl_node_t cs_node; char *cs_name; list_t cs_aliases; } codeset_t; typedef struct csalias { list_node_t a_node; char *a_name; } csalias_t; static avl_tree_t cs_avl; static void alias_destroy(csalias_t *); /* * codesets */ static int cs_compare(const void *n1, const void *n2) { const codeset_t *c1 = n1; const codeset_t *c2 = n2; int rv; rv = strcmp(c1->cs_name, c2->cs_name); return ((rv < 0) ? -1 : (rv > 0) ? 1 : 0); } static void cs_insert(char *key) { codeset_t tmp, *cs; avl_index_t where; (void) memset(&tmp, 0, sizeof (tmp)); tmp.cs_name = key; cs = avl_find(&cs_avl, &tmp, &where); if (cs != NULL) return; /* already there */ cs = calloc(1, sizeof (*cs)); if (cs == NULL) { perror("cs_insert:calloc"); exit(1); } cs->cs_name = strdup(key); if (cs->cs_name == NULL) { perror("cs_insert:strdup"); exit(1); } list_create(&cs->cs_aliases, sizeof (csalias_t), offsetof(csalias_t, a_node)); avl_insert(&cs_avl, cs, where); } const char topmatter[] = "The following are all supported code set names. All combinations\n" "of those names are not necessarily available for the pair of the\n" "fromcode-tocode. Some of those code set names have aliases, which\n" "are case-insensitive and described in parentheses following the\n" "canonical name:\n"; static void cs_dump(void) { codeset_t *cs; csalias_t *a; (void) puts(topmatter); for (cs = avl_first(&cs_avl); cs != NULL; cs = AVL_NEXT(&cs_avl, cs)) { (void) printf(" %s", cs->cs_name); if (!list_is_empty(&cs->cs_aliases)) { a = list_head(&cs->cs_aliases); (void) printf(" (%s", a->a_name); while ((a = list_next(&cs->cs_aliases, a)) != NULL) { (void) printf(", %s", a->a_name); } (void) printf(")"); } (void) printf(",\n"); } } static void cs_destroy(void) { void *cookie = NULL; codeset_t *cs; csalias_t *a; while ((cs = avl_destroy_nodes(&cs_avl, &cookie)) != NULL) { while ((a = list_remove_head(&cs->cs_aliases)) != NULL) { alias_destroy(a); } free(cs->cs_name); free(cs); } avl_destroy(&cs_avl); } /* * aliases */ static void alias_insert(char *codeset, char *alias) { codeset_t tcs, *cs; csalias_t *a; /* * Find the codeset. If non-existent, * ignore aliases of this codeset. */ (void) memset(&tcs, 0, sizeof (tcs)); tcs.cs_name = codeset; cs = avl_find(&cs_avl, &tcs, NULL); if (cs == NULL) return; /* * Add this alias */ a = calloc(1, sizeof (*a)); if (a == NULL) { perror("alias_insert:calloc"); exit(1); } a->a_name = strdup(alias); if (a->a_name == NULL) { perror("alias_insert:strdup"); exit(1); } list_insert_tail(&cs->cs_aliases, a); } static void alias_destroy(csalias_t *a) { free(a->a_name); free(a); } static void scan_dir(DIR *dh, char sep, char *suffix) { char namebuf[MAXNAMELEN]; struct dirent *de; while ((de = readdir(dh)) != NULL) { char *p2, *p1; /* * We'll modify, so let's copy. If the dirent name is * longer than MAXNAMELEN, then it can't possibly be a * valid pair of codeset names, so just skip it. */ if (strlcpy(namebuf, de->d_name, sizeof (namebuf)) >= sizeof (namebuf)) continue; /* Find suffix */ p2 = strrchr(namebuf, *suffix); if (p2 == NULL) continue; if (strcmp(p2, suffix) != 0) continue; *p2 = '\0'; p1 = strchr(namebuf, sep); if (p1 == NULL) continue; *p1++ = '\0'; /* More than one sep? */ if (strchr(p1, sep) != NULL) continue; /* Empty strings? */ if (*namebuf == '\0' || *p1 == '\0') continue; /* OK, add both to the map. */ cs_insert(namebuf); cs_insert(p1); } } static void scan_aliases(FILE *fh) { char linebuf[256]; char *p1, *p2; while (fgets(linebuf, sizeof (linebuf), fh) != NULL) { if (linebuf[0] == '#') continue; p1 = strchr(linebuf, ' '); if (p1 == NULL) continue; *p1++ = '\0'; p2 = strchr(p1, '\n'); if (p2 == NULL) continue; *p2 = '\0'; alias_insert(p1, linebuf); } } int list_codesets(void) { DIR *dh; FILE *fh; avl_create(&cs_avl, cs_compare, sizeof (codeset_t), offsetof(codeset_t, cs_node)); dh = opendir(PATH_LIBICONV); if (dh == NULL) { perror(PATH_LIBICONV); return (1); } scan_dir(dh, '%', ".so"); (void) closedir(dh); dh = opendir(PATH_BTABLES); if (dh == NULL) { perror(PATH_BTABLES); return (1); } scan_dir(dh, '%', ".bt"); (void) closedir(dh); fh = fopen(PATH_ALIASES, "r"); if (fh == NULL) { perror(PATH_ALIASES); /* let's continue */ } else { scan_aliases(fh); (void) fclose(fh); } cs_dump(); cs_destroy(); return (0); }