Make it possible to use the list in signal-handlers.
[gnulib.git] / lib / iconvme.c
1 /* Recode strings between character sets, using iconv.
2    Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License along
15    with this program; if not, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 /* Get prototype. */
23 #include "iconvme.h"
24
25 /* Get malloc. */
26 #include <stdlib.h>
27
28 /* Get strcmp. */
29 #include <string.h>
30
31 /* Get errno. */
32 #include <errno.h>
33
34 #ifdef _LIBC
35 # define HAVE_ICONV 1
36 #else
37 /* Get strdup. */
38 # include "strdup.h"
39 #endif
40
41 #if HAVE_ICONV
42 /* Get iconv etc. */
43 # include <iconv.h>
44 /* Get MB_LEN_MAX, CHAR_BIT.  */
45 # include <limits.h>
46 #endif
47
48 #ifndef SIZE_MAX
49 # define SIZE_MAX ((size_t) -1)
50 #endif
51
52 /* Convert a zero-terminated string STR from the FROM_CODSET code set
53    to the TO_CODESET code set.  The returned string is allocated using
54    malloc, and must be dellocated by the caller using free.  On
55    failure, NULL is returned and errno holds the error reason.  Note
56    that if TO_CODESET uses \0 for anything but to terminate the
57    string, the caller of this function may have difficulties finding
58    out the length of the output string.  */
59 char *
60 iconv_string (const char *str, const char *from_codeset,
61               const char *to_codeset)
62 {
63   char *dest = NULL;
64 #if HAVE_ICONV
65   iconv_t cd;
66 #endif
67
68   if (strcmp (to_codeset, from_codeset) == 0)
69     return strdup (str);
70
71 #if HAVE_ICONV
72   cd = iconv_open (to_codeset, from_codeset);
73   if (cd == (iconv_t) -1)
74     return NULL;
75
76   dest = iconv_alloc (cd, str);
77
78   {
79     int save_errno = errno;
80
81     if (iconv_close (cd) < 0 && dest)
82       {
83         int save_errno2 = errno;
84         /* If we didn't have a real error before, make sure we restore
85            the iconv_close error below. */
86         free (dest);
87         dest = NULL;
88         errno = save_errno2;
89       }
90     else
91       errno = save_errno;
92   }
93 #else
94   errno = ENOSYS;
95 #endif
96
97   return dest;
98 }
99
100 /* Convert a zero-terminated string STR using iconv descriptor CD.
101    The returned string is allocated using malloc, and must be
102    dellocated by the caller using free.  On failure, NULL is returned
103    and errno holds the error reason.  Note that if the target
104    character set uses \0 for anything but to terminate the string,
105    the caller of this function may have difficulties finding
106    out the length of the output string.  */
107 #if HAVE_ICONV
108 char *
109 iconv_alloc (iconv_t cd, const char *str)
110 {
111   char *dest;
112   char *p = (char *) str;
113   char *outp;
114   size_t inbytes_remaining = strlen (p);
115   /* Guess the maximum length the output string can have.  */
116   size_t outbuf_size = inbytes_remaining + 1;
117   size_t outbytes_remaining;
118   size_t err;
119   int have_error = 0;
120
121   /* Use a worst-case output size guess, so long as that wouldn't be
122      too large for comfort.  It's OK if the guess is wrong so long as
123      it's nonzero.  */
124   size_t approx_sqrt_SIZE_MAX = SIZE_MAX >> (sizeof (size_t) * CHAR_BIT / 2);
125   if (outbuf_size <= approx_sqrt_SIZE_MAX / MB_LEN_MAX)
126     outbuf_size *= MB_LEN_MAX;
127   outbytes_remaining = outbuf_size - 1;
128
129   outp = dest = (char *) malloc (outbuf_size);
130   if (dest == NULL)
131     return NULL;
132
133 again:
134   err = iconv (cd, &p, &inbytes_remaining, &outp, &outbytes_remaining);
135
136   if (err == (size_t) -1)
137     {
138       switch (errno)
139         {
140         case EINVAL:
141           /* Incomplete text, do not report an error */
142           break;
143
144         case E2BIG:
145           {
146             size_t used = outp - dest;
147             size_t newsize = outbuf_size * 2;
148             char *newdest;
149
150             if (newsize <= outbuf_size)
151               {
152                 errno = ENOMEM;
153                 have_error = 1;
154                 goto out;
155               }
156             newdest = (char *) realloc (dest, newsize);
157             if (newdest == NULL)
158               {
159                 have_error = 1;
160                 goto out;
161               }
162             dest = newdest;
163             outbuf_size = newsize;
164
165             outp = dest + used;
166             outbytes_remaining = outbuf_size - used - 1;        /* -1 for NUL */
167
168             goto again;
169           }
170           break;
171
172         case EILSEQ:
173           have_error = 1;
174           break;
175
176         default:
177           have_error = 1;
178           break;
179         }
180     }
181
182   *outp = '\0';
183
184 out:
185   if (have_error && dest)
186     {
187       int save_errno = errno;
188       free (dest);
189       errno = save_errno;
190       dest = NULL;
191     }
192
193   return dest;
194 }
195 #endif