iconv_charmap: Close output file when done
[platform/upstream/glibc.git] / iconv / iconv_charmap.c
1 /* Convert using charmaps and possibly iconv().
2    Copyright (C) 2001-2021 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@redhat.com>, 2001.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published
8    by the Free Software Foundation; version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, see <https://www.gnu.org/licenses/>.  */
18
19 #include <assert.h>
20 #include <errno.h>
21 #include <error.h>
22 #include <fcntl.h>
23 #include <iconv.h>
24 #include <libintl.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdint.h>
29 #include <sys/mman.h>
30 #include <sys/stat.h>
31
32 #include "iconv_prog.h"
33
34
35 /* Prototypes for a few program-wide used functions.  */
36 #include <programs/xmalloc.h>
37
38
39 struct convtable
40 {
41   int term[256 / 8];
42   union
43   {
44     struct convtable *sub;
45     struct charseq *out;
46   } val[256];
47 };
48
49
50 static inline struct convtable *
51 allocate_table (void)
52 {
53   return (struct convtable *) xcalloc (1, sizeof (struct convtable));
54 }
55
56 static inline void
57 free_table (struct convtable *tbl)
58 {
59   free (tbl);
60 }
61
62
63 static inline int
64 is_term (struct convtable *tbl, unsigned int idx)
65 {
66   return tbl->term[idx / 8] & (1 << (idx % 8));
67 }
68
69
70 static inline void
71 clear_term (struct convtable *tbl, unsigned int idx)
72 {
73   tbl->term[idx / 8] &= ~(1 << (idx % 8));
74 }
75
76
77 static inline void
78 set_term (struct convtable *tbl, unsigned int idx)
79 {
80   tbl->term[idx / 8] |= 1 << (idx % 8);
81 }
82
83
84 /* Generate the conversion table.  */
85 static struct convtable *use_from_charmap (struct charmap_t *from_charmap,
86                                            const char *to_code);
87 static struct convtable *use_to_charmap (const char *from_code,
88                                          struct charmap_t *to_charmap);
89 static struct convtable *use_both_charmaps (struct charmap_t *from_charmap,
90                                             struct charmap_t *to_charmap);
91
92 /* Prototypes for the functions doing the actual work.  */
93 static int process_block (struct convtable *tbl, char *addr, size_t len,
94                           FILE *output);
95 static int process_fd (struct convtable *tbl, int fd, FILE *output);
96 static int process_file (struct convtable *tbl, FILE *input, FILE *output);
97
98
99 int
100 charmap_conversion (const char *from_code, struct charmap_t *from_charmap,
101                     const char *to_code, struct charmap_t *to_charmap,
102                     int argc, int remaining, char *argv[],
103                     const char *output_file)
104 {
105   struct convtable *cvtbl;
106   int status = EXIT_SUCCESS;
107
108   /* We have three different cases to handle:
109
110      - both, from_charmap and to_charmap, are available.  This means we
111        can assume that the symbolic names match and use them to create
112        the mapping.
113
114      - only from_charmap is available.  In this case we can only hope that
115        the symbolic names used are of the <Uxxxx> form in which case we
116        can use a UCS4->"to_code" iconv() conversion for the second step.
117
118      - only to_charmap is available.  This is similar, only that we would
119        use iconv() for the "to_code"->UCS4 conversion.
120
121        We first create a table which maps input bytes into output bytes.
122        Once this is done we can handle all three of the cases above
123        equally.  */
124   if (from_charmap != NULL)
125     {
126       if (to_charmap == NULL)
127         cvtbl = use_from_charmap (from_charmap, to_code);
128       else
129         cvtbl = use_both_charmaps (from_charmap, to_charmap);
130     }
131   else
132     {
133       assert (to_charmap != NULL);
134       cvtbl = use_to_charmap (from_code, to_charmap);
135     }
136
137   /* If we couldn't generate a table stop now.  */
138   if (cvtbl == NULL)
139     return EXIT_FAILURE;
140
141   /* Determine output file.  */
142   FILE *output;
143   if (output_file != NULL && strcmp (output_file, "-") != 0)
144     {
145       output = fopen (output_file, "w");
146       if (output == NULL)
147         error (EXIT_FAILURE, errno, _("cannot open output file"));
148     }
149   else
150     output = stdout;
151
152   /* We can now start the conversion.  */
153   if (remaining == argc)
154     {
155       if (process_file (cvtbl, stdin, output) != 0)
156         status = EXIT_FAILURE;
157     }
158   else
159     do
160       {
161         int fd;
162
163         if (verbose)
164           printf ("%s:\n", argv[remaining]);
165         if (strcmp (argv[remaining], "-") == 0)
166           fd = 0;
167         else
168           {
169             fd = open (argv[remaining], O_RDONLY);
170
171             if (fd == -1)
172               {
173                 error (0, errno, _("cannot open input file `%s'"),
174                        argv[remaining]);
175                 status = EXIT_FAILURE;
176                 continue;
177               }
178           }
179
180 #ifdef _POSIX_MAPPED_FILES
181         struct stat64 st;
182         char *addr;
183         /* We have possibilities for reading the input file.  First try
184            to mmap() it since this will provide the fastest solution.  */
185         if (fstat64 (fd, &st) == 0
186             && ((addr = mmap (NULL, st.st_size, PROT_READ, MAP_PRIVATE,
187                               fd, 0)) != MAP_FAILED))
188           {
189             /* Yes, we can use mmap().  The descriptor is not needed
190                anymore.  */
191             if (close (fd) != 0)
192               error (EXIT_FAILURE, errno,
193                      _("error while closing input `%s'"), argv[remaining]);
194
195             if (process_block (cvtbl, addr, st.st_size, output) < 0)
196               {
197                 /* Something went wrong.  */
198                 status = EXIT_FAILURE;
199
200                 /* We don't need the input data anymore.  */
201                 munmap ((void *) addr, st.st_size);
202
203                 /* We cannot go on with producing output since it might
204                    lead to problem because the last output might leave
205                    the output stream in an undefined state.  */
206                 break;
207               }
208
209             /* We don't need the input data anymore.  */
210             munmap ((void *) addr, st.st_size);
211           }
212         else
213 #endif  /* _POSIX_MAPPED_FILES */
214           {
215             /* Read the file in pieces.  */
216             if (process_fd (cvtbl, fd, output) != 0)
217               {
218                 /* Something went wrong.  */
219                 status = EXIT_FAILURE;
220
221                 /* We don't need the input file anymore.  */
222                 close (fd);
223
224                 /* We cannot go on with producing output since it might
225                    lead to problem because the last output might leave
226                    the output stream in an undefined state.  */
227                 break;
228               }
229
230             /* Now close the file.  */
231             close (fd);
232           }
233       }
234     while (++remaining < argc);
235
236   /* All done.  */
237   if (output != stdout)
238     fclose (output);
239   free_table (cvtbl);
240   return status;
241 }
242
243
244 /* Add the IN->OUT mapping to TBL.  OUT is potentially stored in the table.
245    IN is used only here, so it need not be kept live afterwards.  */
246 static void
247 add_bytes (struct convtable *tbl, const struct charseq *in, struct charseq *out)
248 {
249   int n = 0;
250   unsigned int byte;
251
252   assert (in->nbytes > 0);
253
254   byte = ((unsigned char *) in->bytes)[n];
255   while (n + 1 < in->nbytes)
256     {
257       if (is_term (tbl, byte) || tbl->val[byte].sub == NULL)
258         {
259           /* Note that we simply ignore a definition for a byte sequence
260              which is also the prefix for a longer one.  */
261           clear_term (tbl, byte);
262           tbl->val[byte].sub =
263             (struct convtable *) xcalloc (1, sizeof (struct convtable));
264         }
265
266       tbl = tbl->val[byte].sub;
267
268       byte = ((unsigned char *) in->bytes)[++n];
269     }
270
271   /* Only add the new sequence if there is none yet and the byte sequence
272      is not part of an even longer one.  */
273   if (! is_term (tbl, byte) && tbl->val[byte].sub == NULL)
274     {
275       set_term (tbl, byte);
276       tbl->val[byte].out = out;
277     }
278 }
279
280 /* Try to convert SEQ from WCHAR_T format using CD.
281    Returns a malloc'd struct or NULL.  */
282 static struct charseq *
283 convert_charseq (iconv_t cd, const struct charseq *seq)
284 {
285   struct charseq *result = NULL;
286
287   if (seq->ucs4 != UNINITIALIZED_CHAR_VALUE)
288     {
289       /* There is a chance.  Try the iconv module.  */
290       wchar_t inbuf[1] = { seq->ucs4 };
291       unsigned char outbuf[64];
292       char *inptr = (char *) inbuf;
293       size_t inlen = sizeof (inbuf);
294       char *outptr = (char *) outbuf;
295       size_t outlen = sizeof (outbuf);
296
297       (void) iconv (cd, &inptr, &inlen, &outptr, &outlen);
298
299       if (outptr != (char *) outbuf)
300         {
301           /* We got some output.  Good, use it.  */
302           outlen = sizeof (outbuf) - outlen;
303           assert ((char *) outbuf + outlen == outptr);
304
305           result = xmalloc (sizeof (struct charseq) + outlen);
306           result->name = seq->name;
307           result->ucs4 = seq->ucs4;
308           result->nbytes = outlen;
309           memcpy (result->bytes, outbuf, outlen);
310         }
311
312       /* Clear any possible state left behind.  */
313       (void) iconv (cd, NULL, NULL, NULL, NULL);
314     }
315
316   return result;
317 }
318
319
320 static struct convtable *
321 use_from_charmap (struct charmap_t *from_charmap, const char *to_code)
322 {
323   /* We iterate over all entries in the from_charmap and for those which
324      have a known UCS4 representation we use an iconv() call to determine
325      the mapping to the to_code charset.  */
326   struct convtable *rettbl;
327   iconv_t cd;
328   void *ptr = NULL;
329   const void *key;
330   size_t keylen;
331   void *data;
332
333   cd = iconv_open (to_code, "WCHAR_T");
334   if (cd == (iconv_t) -1)
335     /* We cannot do anything.  */
336     return NULL;
337
338   rettbl = allocate_table ();
339
340   while (iterate_table (&from_charmap->char_table, &ptr, &key, &keylen, &data)
341          >= 0)
342     {
343       struct charseq *in = data;
344       struct charseq *newp = convert_charseq (cd, in);
345       if (newp != NULL)
346         add_bytes (rettbl, in, newp);
347     }
348
349   iconv_close (cd);
350
351   return rettbl;
352 }
353
354
355 static struct convtable *
356 use_to_charmap (const char *from_code, struct charmap_t *to_charmap)
357 {
358   /* We iterate over all entries in the to_charmap and for those which
359      have a known UCS4 representation we use an iconv() call to determine
360      the mapping to the from_code charset.  */
361   struct convtable *rettbl;
362   iconv_t cd;
363   void *ptr = NULL;
364   const void *key;
365   size_t keylen;
366   void *data;
367
368   /* Note that the conversion we use here is the reverse direction.  Without
369      exhaustive search we cannot figure out which input yields the UCS4
370      character we are looking for.  Therefore we determine it the other
371      way round.  */
372   cd = iconv_open (from_code, "WCHAR_T");
373   if (cd == (iconv_t) -1)
374     /* We cannot do anything.  */
375     return NULL;
376
377   rettbl = allocate_table ();
378
379   while (iterate_table (&to_charmap->char_table, &ptr, &key, &keylen, &data)
380          >= 0)
381     {
382       struct charseq *out = data;
383       struct charseq *newp = convert_charseq (cd, out);
384       if (newp != NULL)
385         {
386           add_bytes (rettbl, newp, out);
387           free (newp);
388         }
389     }
390
391   iconv_close (cd);
392
393   return rettbl;
394 }
395
396
397 static struct convtable *
398 use_both_charmaps (struct charmap_t *from_charmap,
399                    struct charmap_t *to_charmap)
400 {
401   /* In this case we iterate over all the entries in the from_charmap,
402      determine the internal name, and find an appropriate entry in the
403      to_charmap (if it exists).  */
404   struct convtable *rettbl = allocate_table ();
405   void *ptr = NULL;
406   const void *key;
407   size_t keylen;
408   void *data;
409
410   while (iterate_table (&from_charmap->char_table, &ptr, &key, &keylen, &data)
411          >= 0)
412     {
413       struct charseq *in = (struct charseq *) data;
414       struct charseq *out = charmap_find_value (to_charmap, key, keylen);
415
416       if (out != NULL)
417         add_bytes (rettbl, in, out);
418     }
419
420   return rettbl;
421 }
422
423
424 static int
425 process_block (struct convtable *tbl, char *addr, size_t len, FILE *output)
426 {
427   size_t n = 0;
428
429   while (n < len)
430     {
431       struct convtable *cur = tbl;
432       unsigned char *curp = (unsigned char *) addr;
433       unsigned int byte = *curp;
434       int cnt;
435       struct charseq *out;
436
437       while (! is_term (cur, byte))
438         if (cur->val[byte].sub == NULL)
439           {
440             /* This is an invalid sequence.  Skip the first byte if we are
441                ignoring errors.  Otherwise punt.  */
442             if (! omit_invalid)
443               {
444                 error (0, 0, _("illegal input sequence at position %Zd"), n);
445                 return -1;
446               }
447
448             n -= curp - (unsigned char *) addr;
449
450             byte = *(curp = (unsigned char *) ++addr);
451             if (++n >= len)
452               /* All converted.  */
453               return 0;
454
455             cur = tbl;
456           }
457         else
458           {
459             cur = cur->val[byte].sub;
460
461             if (++n >= len)
462               {
463                 error (0, 0, _("\
464 incomplete character or shift sequence at end of buffer"));
465                 return -1;
466               }
467
468             byte = *++curp;
469           }
470
471       /* We found a final byte.  Write the output bytes.  */
472       out = cur->val[byte].out;
473       for (cnt = 0; cnt < out->nbytes; ++cnt)
474         fputc_unlocked (out->bytes[cnt], output);
475
476       addr = (char *) curp + 1;
477       ++n;
478     }
479
480   return 0;
481 }
482
483
484 static int
485 process_fd (struct convtable *tbl, int fd, FILE *output)
486 {
487   /* We have a problem with reading from a descriptor since we must not
488      provide the iconv() function an incomplete character or shift
489      sequence at the end of the buffer.  Since we have to deal with
490      arbitrary encodings we must read the whole text in a buffer and
491      process it in one step.  */
492   static char *inbuf = NULL;
493   static size_t maxlen = 0;
494   char *inptr = inbuf;
495   size_t actlen = 0;
496
497   while (actlen < maxlen)
498     {
499       ssize_t n = read (fd, inptr, maxlen - actlen);
500
501       if (n == 0)
502         /* No more text to read.  */
503         break;
504
505       if (n == -1)
506         {
507           /* Error while reading.  */
508           error (0, errno, _("error while reading the input"));
509           return -1;
510         }
511
512       inptr += n;
513       actlen += n;
514     }
515
516   if (actlen == maxlen)
517     while (1)
518       {
519         ssize_t n;
520         char *new_inbuf;
521
522         /* Increase the buffer.  */
523         new_inbuf = (char *) realloc (inbuf, maxlen + 32768);
524         if (new_inbuf == NULL)
525           {
526             error (0, errno, _("unable to allocate buffer for input"));
527             return -1;
528           }
529         inbuf = new_inbuf;
530         maxlen += 32768;
531         inptr = inbuf + actlen;
532
533         do
534           {
535             n = read (fd, inptr, maxlen - actlen);
536
537             if (n == 0)
538               /* No more text to read.  */
539               break;
540
541             if (n == -1)
542               {
543                 /* Error while reading.  */
544                 error (0, errno, _("error while reading the input"));
545                 return -1;
546               }
547
548             inptr += n;
549             actlen += n;
550           }
551         while (actlen < maxlen);
552
553         if (n == 0)
554           /* Break again so we leave both loops.  */
555           break;
556       }
557
558   /* Now we have all the input in the buffer.  Process it in one run.  */
559   return process_block (tbl, inbuf, actlen, output);
560 }
561
562
563 static int
564 process_file (struct convtable *tbl, FILE *input, FILE *output)
565 {
566   /* This should be safe since we use this function only for `stdin' and
567      we haven't read anything so far.  */
568   return process_fd (tbl, fileno (input), output);
569 }