| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- /* Convert using charmaps and possibly iconv().
- Copyright (C) 2001-2026 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published
- by the Free Software Foundation; version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <https://www.gnu.org/licenses/>. */
- #include <assert.h>
- #include <errno.h>
- #include <error.h>
- #include <fcntl.h>
- #include <iconv.h>
- #include <libintl.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <stdint.h>
- #include <sys/mman.h>
- #include <sys/stat.h>
- #include "iconv_prog.h"
- /* Prototypes for a few program-wide used functions. */
- #include <programs/xmalloc.h>
- struct convtable
- {
- int term[256 / 8];
- union
- {
- struct convtable *sub;
- struct charseq *out;
- } val[256];
- };
- static inline struct convtable *
- allocate_table (void)
- {
- return (struct convtable *) xcalloc (1, sizeof (struct convtable));
- }
- static inline void
- free_table (struct convtable *tbl)
- {
- free (tbl);
- }
- static inline int
- is_term (struct convtable *tbl, unsigned int idx)
- {
- return tbl->term[idx / 8] & (1 << (idx % 8));
- }
- static inline void
- clear_term (struct convtable *tbl, unsigned int idx)
- {
- tbl->term[idx / 8] &= ~(1 << (idx % 8));
- }
- static inline void
- set_term (struct convtable *tbl, unsigned int idx)
- {
- tbl->term[idx / 8] |= 1 << (idx % 8);
- }
- /* Generate the conversion table. */
- static struct convtable *use_from_charmap (struct charmap_t *from_charmap,
- const char *to_code);
- static struct convtable *use_to_charmap (const char *from_code,
- struct charmap_t *to_charmap);
- static struct convtable *use_both_charmaps (struct charmap_t *from_charmap,
- struct charmap_t *to_charmap);
- /* Prototypes for the functions doing the actual work. */
- static int process_block (struct convtable *tbl, char *addr, size_t len,
- FILE *output);
- static int process_fd (struct convtable *tbl, int fd, FILE *output);
- static int process_file (struct convtable *tbl, FILE *input, FILE *output);
- int
- charmap_conversion (const char *from_code, struct charmap_t *from_charmap,
- const char *to_code, struct charmap_t *to_charmap,
- int argc, int remaining, char *argv[],
- const char *output_file)
- {
- struct convtable *cvtbl;
- int status = EXIT_SUCCESS;
- /* We have three different cases to handle:
- - both, from_charmap and to_charmap, are available. This means we
- can assume that the symbolic names match and use them to create
- the mapping.
- - only from_charmap is available. In this case we can only hope that
- the symbolic names used are of the <Uxxxx> form in which case we
- can use a UCS4->"to_code" iconv() conversion for the second step.
- - only to_charmap is available. This is similar, only that we would
- use iconv() for the "to_code"->UCS4 conversion.
- We first create a table which maps input bytes into output bytes.
- Once this is done we can handle all three of the cases above
- equally. */
- if (from_charmap != NULL)
- {
- if (to_charmap == NULL)
- cvtbl = use_from_charmap (from_charmap, to_code);
- else
- cvtbl = use_both_charmaps (from_charmap, to_charmap);
- }
- else
- {
- assert (to_charmap != NULL);
- cvtbl = use_to_charmap (from_code, to_charmap);
- }
- /* If we couldn't generate a table stop now. */
- if (cvtbl == NULL)
- return EXIT_FAILURE;
- /* Determine output file. */
- FILE *output;
- if (output_file != NULL && strcmp (output_file, "-") != 0)
- {
- output = fopen (output_file, "w");
- if (output == NULL)
- error (EXIT_FAILURE, errno, _("cannot open output file"));
- }
- else
- output = stdout;
- /* We can now start the conversion. */
- if (remaining == argc)
- {
- if (process_file (cvtbl, stdin, output) != 0)
- status = EXIT_FAILURE;
- }
- else
- do
- {
- int fd;
- if (verbose)
- printf ("%s:\n", argv[remaining]);
- if (strcmp (argv[remaining], "-") == 0)
- fd = 0;
- else
- {
- fd = open (argv[remaining], O_RDONLY);
- if (fd == -1)
- {
- error (0, errno, _("cannot open input file `%s'"),
- argv[remaining]);
- status = EXIT_FAILURE;
- continue;
- }
- }
- #ifdef _POSIX_MAPPED_FILES
- struct stat64 st;
- char *addr;
- /* We have possibilities for reading the input file. First try
- to mmap() it since this will provide the fastest solution. */
- if (fstat64 (fd, &st) == 0
- && ((addr = mmap (NULL, st.st_size, PROT_READ, MAP_PRIVATE,
- fd, 0)) != MAP_FAILED))
- {
- /* Yes, we can use mmap(). The descriptor is not needed
- anymore. */
- if (close (fd) != 0)
- error (EXIT_FAILURE, errno,
- _("error while closing input `%s'"), argv[remaining]);
- if (process_block (cvtbl, addr, st.st_size, output) < 0)
- {
- /* Something went wrong. */
- status = EXIT_FAILURE;
- /* We don't need the input data anymore. */
- munmap ((void *) addr, st.st_size);
- /* We cannot go on with producing output since it might
- lead to problem because the last output might leave
- the output stream in an undefined state. */
- break;
- }
- /* We don't need the input data anymore. */
- munmap ((void *) addr, st.st_size);
- }
- else
- #endif /* _POSIX_MAPPED_FILES */
- {
- /* Read the file in pieces. */
- if (process_fd (cvtbl, fd, output) != 0)
- {
- /* Something went wrong. */
- status = EXIT_FAILURE;
- /* We don't need the input file anymore. */
- close (fd);
- /* We cannot go on with producing output since it might
- lead to problem because the last output might leave
- the output stream in an undefined state. */
- break;
- }
- /* Now close the file. */
- close (fd);
- }
- }
- while (++remaining < argc);
- /* All done. */
- if (output != stdout)
- fclose (output);
- free_table (cvtbl);
- return status;
- }
- /* Add the IN->OUT mapping to TBL. OUT is potentially stored in the table.
- IN is used only here, so it need not be kept live afterwards. */
- static void
- add_bytes (struct convtable *tbl, const struct charseq *in, struct charseq *out)
- {
- int n = 0;
- unsigned int byte;
- assert (in->nbytes > 0);
- byte = ((unsigned char *) in->bytes)[n];
- while (n + 1 < in->nbytes)
- {
- if (is_term (tbl, byte) || tbl->val[byte].sub == NULL)
- {
- /* Note that we simply ignore a definition for a byte sequence
- which is also the prefix for a longer one. */
- clear_term (tbl, byte);
- tbl->val[byte].sub =
- (struct convtable *) xcalloc (1, sizeof (struct convtable));
- }
- tbl = tbl->val[byte].sub;
- byte = ((unsigned char *) in->bytes)[++n];
- }
- /* Only add the new sequence if there is none yet and the byte sequence
- is not part of an even longer one. */
- if (! is_term (tbl, byte) && tbl->val[byte].sub == NULL)
- {
- set_term (tbl, byte);
- tbl->val[byte].out = out;
- }
- }
- /* Try to convert SEQ from WCHAR_T format using CD.
- Returns a malloc'd struct or NULL. */
- static struct charseq *
- convert_charseq (iconv_t cd, const struct charseq *seq)
- {
- struct charseq *result = NULL;
- if (seq->ucs4 != UNINITIALIZED_CHAR_VALUE)
- {
- /* There is a chance. Try the iconv module. */
- wchar_t inbuf[1] = { seq->ucs4 };
- unsigned char outbuf[64];
- char *inptr = (char *) inbuf;
- size_t inlen = sizeof (inbuf);
- char *outptr = (char *) outbuf;
- size_t outlen = sizeof (outbuf);
- (void) iconv (cd, &inptr, &inlen, &outptr, &outlen);
- if (outptr != (char *) outbuf)
- {
- /* We got some output. Good, use it. */
- outlen = sizeof (outbuf) - outlen;
- assert ((char *) outbuf + outlen == outptr);
- result = xmalloc (sizeof (struct charseq) + outlen);
- result->name = seq->name;
- result->ucs4 = seq->ucs4;
- result->nbytes = outlen;
- memcpy (result->bytes, outbuf, outlen);
- }
- /* Clear any possible state left behind. */
- (void) iconv (cd, NULL, NULL, NULL, NULL);
- }
- return result;
- }
- static struct convtable *
- use_from_charmap (struct charmap_t *from_charmap, const char *to_code)
- {
- /* We iterate over all entries in the from_charmap and for those which
- have a known UCS4 representation we use an iconv() call to determine
- the mapping to the to_code charset. */
- struct convtable *rettbl;
- iconv_t cd;
- void *ptr = NULL;
- const void *key;
- size_t keylen;
- void *data;
- cd = iconv_open (to_code, "WCHAR_T");
- if (cd == (iconv_t) -1)
- /* We cannot do anything. */
- return NULL;
- rettbl = allocate_table ();
- while (iterate_table (&from_charmap->char_table, &ptr, &key, &keylen, &data)
- >= 0)
- {
- struct charseq *in = data;
- struct charseq *newp = convert_charseq (cd, in);
- if (newp != NULL)
- add_bytes (rettbl, in, newp);
- }
- iconv_close (cd);
- return rettbl;
- }
- static struct convtable *
- use_to_charmap (const char *from_code, struct charmap_t *to_charmap)
- {
- /* We iterate over all entries in the to_charmap and for those which
- have a known UCS4 representation we use an iconv() call to determine
- the mapping to the from_code charset. */
- struct convtable *rettbl;
- iconv_t cd;
- void *ptr = NULL;
- const void *key;
- size_t keylen;
- void *data;
- /* Note that the conversion we use here is the reverse direction. Without
- exhaustive search we cannot figure out which input yields the UCS4
- character we are looking for. Therefore we determine it the other
- way round. */
- cd = iconv_open (from_code, "WCHAR_T");
- if (cd == (iconv_t) -1)
- /* We cannot do anything. */
- return NULL;
- rettbl = allocate_table ();
- while (iterate_table (&to_charmap->char_table, &ptr, &key, &keylen, &data)
- >= 0)
- {
- struct charseq *out = data;
- struct charseq *newp = convert_charseq (cd, out);
- if (newp != NULL)
- {
- add_bytes (rettbl, newp, out);
- free (newp);
- }
- }
- iconv_close (cd);
- return rettbl;
- }
- static struct convtable *
- use_both_charmaps (struct charmap_t *from_charmap,
- struct charmap_t *to_charmap)
- {
- /* In this case we iterate over all the entries in the from_charmap,
- determine the internal name, and find an appropriate entry in the
- to_charmap (if it exists). */
- struct convtable *rettbl = allocate_table ();
- void *ptr = NULL;
- const void *key;
- size_t keylen;
- void *data;
- while (iterate_table (&from_charmap->char_table, &ptr, &key, &keylen, &data)
- >= 0)
- {
- struct charseq *in = (struct charseq *) data;
- struct charseq *out = charmap_find_value (to_charmap, key, keylen);
- if (out != NULL)
- add_bytes (rettbl, in, out);
- }
- return rettbl;
- }
- static int
- process_block (struct convtable *tbl, char *addr, size_t len, FILE *output)
- {
- size_t n = 0;
- while (n < len)
- {
- struct convtable *cur = tbl;
- unsigned char *curp = (unsigned char *) addr;
- unsigned int byte = *curp;
- int cnt;
- struct charseq *out;
- while (! is_term (cur, byte))
- if (cur->val[byte].sub == NULL)
- {
- /* This is an invalid sequence. Skip the first byte if we are
- ignoring errors. Otherwise punt. */
- if (! omit_invalid)
- {
- error (0, 0, _("illegal input sequence at position %zd"), n);
- return -1;
- }
- n -= curp - (unsigned char *) addr;
- byte = *(curp = (unsigned char *) ++addr);
- if (++n >= len)
- /* All converted. */
- return 0;
- cur = tbl;
- }
- else
- {
- cur = cur->val[byte].sub;
- if (++n >= len)
- {
- error (0, 0, _("\
- incomplete character or shift sequence at end of buffer"));
- return -1;
- }
- byte = *++curp;
- }
- /* We found a final byte. Write the output bytes. */
- out = cur->val[byte].out;
- for (cnt = 0; cnt < out->nbytes; ++cnt)
- fputc_unlocked (out->bytes[cnt], output);
- addr = (char *) curp + 1;
- ++n;
- }
- return 0;
- }
- static int
- process_fd (struct convtable *tbl, int fd, FILE *output)
- {
- /* We have a problem with reading from a descriptor since we must not
- provide the iconv() function an incomplete character or shift
- sequence at the end of the buffer. Since we have to deal with
- arbitrary encodings we must read the whole text in a buffer and
- process it in one step. */
- static char *inbuf = NULL;
- static size_t maxlen = 0;
- char *inptr = inbuf;
- size_t actlen = 0;
- while (actlen < maxlen)
- {
- ssize_t n = read (fd, inptr, maxlen - actlen);
- if (n == 0)
- /* No more text to read. */
- break;
- if (n == -1)
- {
- /* Error while reading. */
- error (0, errno, _("error while reading the input"));
- return -1;
- }
- inptr += n;
- actlen += n;
- }
- if (actlen == maxlen)
- while (1)
- {
- ssize_t n;
- char *new_inbuf;
- /* Increase the buffer. */
- new_inbuf = (char *) realloc (inbuf, maxlen + 32768);
- if (new_inbuf == NULL)
- {
- error (0, errno, _("unable to allocate buffer for input"));
- return -1;
- }
- inbuf = new_inbuf;
- maxlen += 32768;
- inptr = inbuf + actlen;
- do
- {
- n = read (fd, inptr, maxlen - actlen);
- if (n == 0)
- /* No more text to read. */
- break;
- if (n == -1)
- {
- /* Error while reading. */
- error (0, errno, _("error while reading the input"));
- return -1;
- }
- inptr += n;
- actlen += n;
- }
- while (actlen < maxlen);
- if (n == 0)
- /* Break again so we leave both loops. */
- break;
- }
- /* Now we have all the input in the buffer. Process it in one run. */
- return process_block (tbl, inbuf, actlen, output);
- }
- static int
- process_file (struct convtable *tbl, FILE *input, FILE *output)
- {
- /* This should be safe since we use this function only for `stdin' and
- we haven't read anything so far. */
- return process_fd (tbl, fileno (input), output);
- }
|