| /* |
| * 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 2011 Nexenta Systems, Inc. All rights reserved. |
| */ |
| |
| /* |
| * iconv(1) command. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <iconv.h> |
| #include <libintl.h> |
| #include <langinfo.h> |
| #include <locale.h> |
| #include "charmap.h" |
| |
| #include <assert.h> |
| |
| const char *progname; |
| |
| char *from_cs; |
| char *to_cs; |
| int debug; |
| int cflag; /* skip invalid characters */ |
| int sflag; /* silent */ |
| int lflag; /* list conversions */ |
| |
| void iconv_file(FILE *, const char *); |
| extern int list_codesets(void); |
| |
| iconv_t ich; /* iconv(3c) lib handle */ |
| size_t (*pconv)(const char **iptr, size_t *ileft, |
| char **optr, size_t *oleft); |
| |
| size_t |
| lib_iconv(const char **iptr, size_t *ileft, char **optr, size_t *oleft) |
| { |
| return (iconv(ich, iptr, ileft, optr, oleft)); |
| } |
| |
| void |
| usage(void) |
| { |
| (void) fprintf(stderr, gettext( |
| "usage: %s [-cs] [-f from-codeset] [-t to-codeset] " |
| "[file ...]\n"), progname); |
| (void) fprintf(stderr, gettext("\t%s -l\n"), progname); |
| exit(1); |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| FILE *fp; |
| char *fslash, *tslash; |
| int c; |
| |
| yydebug = 0; |
| progname = getprogname(); |
| |
| (void) setlocale(LC_ALL, ""); |
| |
| #if !defined(TEXT_DOMAIN) |
| #define TEXT_DOMAIN "SYS_TEST" |
| #endif |
| (void) textdomain(TEXT_DOMAIN); |
| |
| while ((c = getopt(argc, argv, "cdlsf:t:")) != EOF) { |
| switch (c) { |
| case 'c': |
| cflag++; |
| break; |
| case 'd': |
| debug++; |
| break; |
| case 'l': |
| lflag++; |
| break; |
| case 's': |
| sflag++; |
| break; |
| case 'f': |
| from_cs = optarg; |
| break; |
| case 't': |
| to_cs = optarg; |
| break; |
| case '?': |
| usage(); |
| } |
| } |
| |
| if (lflag) { |
| if (from_cs != NULL || to_cs != NULL || optind != argc) |
| usage(); |
| exit(list_codesets()); |
| } |
| |
| if (from_cs == NULL) |
| from_cs = nl_langinfo(CODESET); |
| if (to_cs == NULL) |
| to_cs = nl_langinfo(CODESET); |
| |
| /* |
| * If either "from" or "to" contains a slash, |
| * then we're using charmaps. |
| */ |
| fslash = strchr(from_cs, '/'); |
| tslash = strchr(to_cs, '/'); |
| if (fslash != NULL || tslash != NULL) { |
| charmap_init(to_cs, from_cs); |
| pconv = cm_iconv; |
| if (debug) |
| charmap_dump(); |
| } else { |
| ich = iconv_open(to_cs, from_cs); |
| if (ich == ((iconv_t)-1)) { |
| switch (errno) { |
| case EINVAL: |
| (void) fprintf(stderr, |
| _("Not supported %s to %s\n"), |
| from_cs, to_cs); |
| break; |
| default: |
| (void) fprintf(stderr, |
| _("iconv_open failed: %s\n"), |
| strerror(errno)); |
| break; |
| } |
| exit(1); |
| } |
| pconv = lib_iconv; |
| } |
| |
| if (optind == argc || |
| (optind == argc - 1 && 0 == strcmp(argv[optind], "-"))) { |
| iconv_file(stdin, "stdin"); |
| exit(warnings ? 1 : 0); |
| } |
| |
| for (; optind < argc; optind++) { |
| fp = fopen(argv[optind], "r"); |
| if (fp == NULL) { |
| perror(argv[optind]); |
| exit(1); |
| } |
| iconv_file(fp, argv[optind]); |
| (void) fclose(fp); |
| } |
| exit(warnings ? 1 : 0); |
| } |
| |
| /* |
| * Conversion buffer sizes: |
| * |
| * The input buffer has room to prepend one mbs character if needed for |
| * handling a left-over at the end of a previous conversion buffer. |
| * |
| * Conversions may grow or shrink data, so using a larger output buffer |
| * to reduce the likelihood of leftover input buffer data in each pass. |
| */ |
| #define IBUFSIZ (MB_LEN_MAX + BUFSIZ) |
| #define OBUFSIZ (2 * BUFSIZ) |
| |
| void |
| iconv_file(FILE *fp, const char *fname) |
| { |
| static char ibuf[IBUFSIZ]; |
| static char obuf[OBUFSIZ]; |
| const char *iptr; |
| char *optr; |
| off64_t offset; |
| size_t ileft, oleft, ocnt; |
| int iconv_errno; |
| int nr, nw, rc; |
| |
| offset = 0; |
| ileft = 0; |
| iptr = ibuf + MB_LEN_MAX; |
| |
| while ((nr = fread(ibuf+MB_LEN_MAX, 1, BUFSIZ, fp)) > 0) { |
| |
| assert(iptr <= ibuf+MB_LEN_MAX); |
| assert(ileft <= MB_LEN_MAX); |
| ileft += nr; |
| offset += nr; |
| |
| optr = obuf; |
| oleft = OBUFSIZ; |
| |
| /* |
| * Note: the *pconv function is either iconv(3c) or our |
| * private equivalent when using charmaps. Both update |
| * ileft, oleft etc. even when conversion stops due to |
| * an illegal sequence or whatever, so we need to copy |
| * the partially converted buffer even on error. |
| */ |
| iconv_again: |
| rc = (*pconv)(&iptr, &ileft, &optr, &oleft); |
| iconv_errno = errno; |
| |
| ocnt = OBUFSIZ - oleft; |
| if (ocnt > 0) { |
| nw = fwrite(obuf, 1, ocnt, stdout); |
| if (nw != ocnt) { |
| perror("fwrite"); |
| exit(1); |
| } |
| } |
| optr = obuf; |
| oleft = OBUFSIZ; |
| |
| if (rc == (size_t)-1) { |
| switch (iconv_errno) { |
| |
| case E2BIG: /* no room in output buffer */ |
| goto iconv_again; |
| |
| case EINVAL: /* incomplete sequence on input */ |
| if (debug) { |
| (void) fprintf(stderr, |
| _("Incomplete sequence in %s at offset %lld\n"), |
| fname, offset - ileft); |
| } |
| /* |
| * Copy the remainder to the space reserved |
| * at the start of the input buffer. |
| */ |
| assert(ileft > 0); |
| if (ileft <= MB_LEN_MAX) { |
| char *p = ibuf+MB_LEN_MAX-ileft; |
| (void) memmove(p, iptr, ileft); |
| iptr = p; |
| continue; /* read again */ |
| } |
| /* |
| * Should not see ileft > MB_LEN_MAX, |
| * but if we do, handle as EILSEQ. |
| */ |
| /* FALLTHROUGH */ |
| |
| case EILSEQ: /* invalid sequence on input */ |
| if (!sflag) { |
| (void) fprintf(stderr, |
| _("Illegal sequence in %s at offset %lld\n"), |
| fname, offset - ileft); |
| (void) fprintf(stderr, |
| _("bad seq: \\x%02x\\x%02x\\x%02x\n"), |
| iptr[0] & 0xff, |
| iptr[1] & 0xff, |
| iptr[2] & 0xff); |
| } |
| assert(ileft > 0); |
| /* skip one */ |
| iptr++; |
| ileft--; |
| assert(oleft > 0); |
| if (!cflag) { |
| *optr++ = '?'; |
| oleft--; |
| } |
| goto iconv_again; |
| |
| default: |
| (void) fprintf(stderr, |
| _("iconv error (%s) in file $s at offset %lld\n"), |
| strerror(iconv_errno), fname, |
| offset - ileft); |
| break; |
| } |
| } |
| |
| /* normal iconv return */ |
| ileft = 0; |
| iptr = ibuf + MB_LEN_MAX; |
| } |
| |
| /* |
| * End of file |
| * Flush any shift encodings. |
| */ |
| iptr = NULL; |
| ileft = 0; |
| optr = obuf; |
| oleft = OBUFSIZ; |
| (*pconv)(&iptr, &ileft, &optr, &oleft); |
| ocnt = OBUFSIZ - oleft; |
| if (ocnt > 0) { |
| nw = fwrite(obuf, 1, ocnt, stdout); |
| if (nw != ocnt) { |
| perror("fwrite"); |
| exit(1); |
| } |
| } |
| } |