xref: /dflybsd-src/contrib/grep/lib/striconv.c (revision 91b9ed38d3db6a8a8ac5b66da1d43e6e331e259a)
195b7b453SJohn Marino /* Charset conversion.
2*09d4459fSDaniel Fojt    Copyright (C) 2001-2007, 2010-2020 Free Software Foundation, Inc.
395b7b453SJohn Marino    Written by Bruno Haible and Simon Josefsson.
495b7b453SJohn Marino 
595b7b453SJohn Marino    This program is free software; you can redistribute it and/or modify
695b7b453SJohn Marino    it under the terms of the GNU General Public License as published by
795b7b453SJohn Marino    the Free Software Foundation; either version 3, or (at your option)
895b7b453SJohn Marino    any later version.
995b7b453SJohn Marino 
1095b7b453SJohn Marino    This program is distributed in the hope that it will be useful,
1195b7b453SJohn Marino    but WITHOUT ANY WARRANTY; without even the implied warranty of
1295b7b453SJohn Marino    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1395b7b453SJohn Marino    GNU General Public License for more details.
1495b7b453SJohn Marino 
1595b7b453SJohn Marino    You should have received a copy of the GNU General Public License
16*09d4459fSDaniel Fojt    along with this program; if not, see <https://www.gnu.org/licenses/>.  */
1795b7b453SJohn Marino 
1895b7b453SJohn Marino #include <config.h>
1995b7b453SJohn Marino 
2095b7b453SJohn Marino /* Specification.  */
2195b7b453SJohn Marino #include "striconv.h"
2295b7b453SJohn Marino 
2395b7b453SJohn Marino #include <errno.h>
2495b7b453SJohn Marino #include <stdlib.h>
2595b7b453SJohn Marino #include <string.h>
2695b7b453SJohn Marino 
2795b7b453SJohn Marino #if HAVE_ICONV
2895b7b453SJohn Marino # include <iconv.h>
2995b7b453SJohn Marino /* Get MB_LEN_MAX, CHAR_BIT.  */
3095b7b453SJohn Marino # include <limits.h>
3195b7b453SJohn Marino #endif
3295b7b453SJohn Marino 
3395b7b453SJohn Marino #include "c-strcase.h"
3495b7b453SJohn Marino 
3595b7b453SJohn Marino #ifndef SIZE_MAX
3695b7b453SJohn Marino # define SIZE_MAX ((size_t) -1)
3795b7b453SJohn Marino #endif
3895b7b453SJohn Marino 
3995b7b453SJohn Marino 
4095b7b453SJohn Marino #if HAVE_ICONV
4195b7b453SJohn Marino 
4295b7b453SJohn Marino int
mem_cd_iconv(const char * src,size_t srclen,iconv_t cd,char ** resultp,size_t * lengthp)4395b7b453SJohn Marino mem_cd_iconv (const char *src, size_t srclen, iconv_t cd,
4495b7b453SJohn Marino               char **resultp, size_t *lengthp)
4595b7b453SJohn Marino {
4695b7b453SJohn Marino # define tmpbufsize 4096
4795b7b453SJohn Marino   size_t length;
4895b7b453SJohn Marino   char *result;
4995b7b453SJohn Marino 
5095b7b453SJohn Marino   /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug.  */
5195b7b453SJohn Marino # if defined _LIBICONV_VERSION \
52200fbe8dSJohn Marino      || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
53200fbe8dSJohn Marino           || defined __sun)
5495b7b453SJohn Marino   /* Set to the initial state.  */
5595b7b453SJohn Marino   iconv (cd, NULL, NULL, NULL, NULL);
5695b7b453SJohn Marino # endif
5795b7b453SJohn Marino 
5895b7b453SJohn Marino   /* Determine the length we need.  */
5995b7b453SJohn Marino   {
6095b7b453SJohn Marino     size_t count = 0;
6195b7b453SJohn Marino     /* The alignment is needed when converting e.g. to glibc's WCHAR_T or
6295b7b453SJohn Marino        libiconv's UCS-4-INTERNAL encoding.  */
6395b7b453SJohn Marino     union { unsigned int align; char buf[tmpbufsize]; } tmp;
6495b7b453SJohn Marino # define tmpbuf tmp.buf
6595b7b453SJohn Marino     const char *inptr = src;
6695b7b453SJohn Marino     size_t insize = srclen;
6795b7b453SJohn Marino 
6895b7b453SJohn Marino     while (insize > 0)
6995b7b453SJohn Marino       {
7095b7b453SJohn Marino         char *outptr = tmpbuf;
7195b7b453SJohn Marino         size_t outsize = tmpbufsize;
7295b7b453SJohn Marino         size_t res = iconv (cd,
7395b7b453SJohn Marino                             (ICONV_CONST char **) &inptr, &insize,
7495b7b453SJohn Marino                             &outptr, &outsize);
7595b7b453SJohn Marino 
7695b7b453SJohn Marino         if (res == (size_t)(-1))
7795b7b453SJohn Marino           {
7895b7b453SJohn Marino             if (errno == E2BIG)
7995b7b453SJohn Marino               ;
8095b7b453SJohn Marino             else if (errno == EINVAL)
8195b7b453SJohn Marino               break;
8295b7b453SJohn Marino             else
8395b7b453SJohn Marino               return -1;
8495b7b453SJohn Marino           }
85200fbe8dSJohn Marino # if !defined _LIBICONV_VERSION && !(defined __GLIBC__ && !defined __UCLIBC__)
8695b7b453SJohn Marino         /* Irix iconv() inserts a NUL byte if it cannot convert.
8795b7b453SJohn Marino            NetBSD iconv() inserts a question mark if it cannot convert.
8895b7b453SJohn Marino            Only GNU libiconv and GNU libc are known to prefer to fail rather
8995b7b453SJohn Marino            than doing a lossy conversion.  */
9095b7b453SJohn Marino         else if (res > 0)
9195b7b453SJohn Marino           {
9295b7b453SJohn Marino             errno = EILSEQ;
9395b7b453SJohn Marino             return -1;
9495b7b453SJohn Marino           }
9595b7b453SJohn Marino # endif
9695b7b453SJohn Marino         count += outptr - tmpbuf;
9795b7b453SJohn Marino       }
9895b7b453SJohn Marino     /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
9995b7b453SJohn Marino # if defined _LIBICONV_VERSION \
100200fbe8dSJohn Marino      || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
101200fbe8dSJohn Marino           || defined __sun)
10295b7b453SJohn Marino     {
10395b7b453SJohn Marino       char *outptr = tmpbuf;
10495b7b453SJohn Marino       size_t outsize = tmpbufsize;
10595b7b453SJohn Marino       size_t res = iconv (cd, NULL, NULL, &outptr, &outsize);
10695b7b453SJohn Marino 
10795b7b453SJohn Marino       if (res == (size_t)(-1))
10895b7b453SJohn Marino         return -1;
10995b7b453SJohn Marino       count += outptr - tmpbuf;
11095b7b453SJohn Marino     }
11195b7b453SJohn Marino # endif
11295b7b453SJohn Marino     length = count;
11395b7b453SJohn Marino # undef tmpbuf
11495b7b453SJohn Marino   }
11595b7b453SJohn Marino 
11695b7b453SJohn Marino   if (length == 0)
11795b7b453SJohn Marino     {
11895b7b453SJohn Marino       *lengthp = 0;
11995b7b453SJohn Marino       return 0;
12095b7b453SJohn Marino     }
12195b7b453SJohn Marino   if (*resultp != NULL && *lengthp >= length)
12295b7b453SJohn Marino     result = *resultp;
12395b7b453SJohn Marino   else
12495b7b453SJohn Marino     {
12595b7b453SJohn Marino       result = (char *) malloc (length);
12695b7b453SJohn Marino       if (result == NULL)
12795b7b453SJohn Marino         {
12895b7b453SJohn Marino           errno = ENOMEM;
12995b7b453SJohn Marino           return -1;
13095b7b453SJohn Marino         }
13195b7b453SJohn Marino     }
13295b7b453SJohn Marino 
13395b7b453SJohn Marino   /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug.  */
13495b7b453SJohn Marino # if defined _LIBICONV_VERSION \
135200fbe8dSJohn Marino      || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
136200fbe8dSJohn Marino           || defined __sun)
13795b7b453SJohn Marino   /* Return to the initial state.  */
13895b7b453SJohn Marino   iconv (cd, NULL, NULL, NULL, NULL);
13995b7b453SJohn Marino # endif
14095b7b453SJohn Marino 
14195b7b453SJohn Marino   /* Do the conversion for real.  */
14295b7b453SJohn Marino   {
14395b7b453SJohn Marino     const char *inptr = src;
14495b7b453SJohn Marino     size_t insize = srclen;
14595b7b453SJohn Marino     char *outptr = result;
14695b7b453SJohn Marino     size_t outsize = length;
14795b7b453SJohn Marino 
14895b7b453SJohn Marino     while (insize > 0)
14995b7b453SJohn Marino       {
15095b7b453SJohn Marino         size_t res = iconv (cd,
15195b7b453SJohn Marino                             (ICONV_CONST char **) &inptr, &insize,
15295b7b453SJohn Marino                             &outptr, &outsize);
15395b7b453SJohn Marino 
15495b7b453SJohn Marino         if (res == (size_t)(-1))
15595b7b453SJohn Marino           {
15695b7b453SJohn Marino             if (errno == EINVAL)
15795b7b453SJohn Marino               break;
15895b7b453SJohn Marino             else
15995b7b453SJohn Marino               goto fail;
16095b7b453SJohn Marino           }
161200fbe8dSJohn Marino # if !defined _LIBICONV_VERSION && !(defined __GLIBC__ && !defined __UCLIBC__)
16295b7b453SJohn Marino         /* Irix iconv() inserts a NUL byte if it cannot convert.
16395b7b453SJohn Marino            NetBSD iconv() inserts a question mark if it cannot convert.
16495b7b453SJohn Marino            Only GNU libiconv and GNU libc are known to prefer to fail rather
16595b7b453SJohn Marino            than doing a lossy conversion.  */
16695b7b453SJohn Marino         else if (res > 0)
16795b7b453SJohn Marino           {
16895b7b453SJohn Marino             errno = EILSEQ;
16995b7b453SJohn Marino             goto fail;
17095b7b453SJohn Marino           }
17195b7b453SJohn Marino # endif
17295b7b453SJohn Marino       }
17395b7b453SJohn Marino     /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
17495b7b453SJohn Marino # if defined _LIBICONV_VERSION \
175200fbe8dSJohn Marino      || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
176200fbe8dSJohn Marino           || defined __sun)
17795b7b453SJohn Marino     {
17895b7b453SJohn Marino       size_t res = iconv (cd, NULL, NULL, &outptr, &outsize);
17995b7b453SJohn Marino 
18095b7b453SJohn Marino       if (res == (size_t)(-1))
18195b7b453SJohn Marino         goto fail;
18295b7b453SJohn Marino     }
18395b7b453SJohn Marino # endif
18495b7b453SJohn Marino     if (outsize != 0)
18595b7b453SJohn Marino       abort ();
18695b7b453SJohn Marino   }
18795b7b453SJohn Marino 
18895b7b453SJohn Marino   *resultp = result;
18995b7b453SJohn Marino   *lengthp = length;
19095b7b453SJohn Marino 
19195b7b453SJohn Marino   return 0;
19295b7b453SJohn Marino 
19395b7b453SJohn Marino  fail:
19495b7b453SJohn Marino   {
19595b7b453SJohn Marino     if (result != *resultp)
19695b7b453SJohn Marino       {
19795b7b453SJohn Marino         int saved_errno = errno;
19895b7b453SJohn Marino         free (result);
19995b7b453SJohn Marino         errno = saved_errno;
20095b7b453SJohn Marino       }
20195b7b453SJohn Marino     return -1;
20295b7b453SJohn Marino   }
20395b7b453SJohn Marino # undef tmpbufsize
20495b7b453SJohn Marino }
20595b7b453SJohn Marino 
20695b7b453SJohn Marino char *
str_cd_iconv(const char * src,iconv_t cd)20795b7b453SJohn Marino str_cd_iconv (const char *src, iconv_t cd)
20895b7b453SJohn Marino {
20995b7b453SJohn Marino   /* For most encodings, a trailing NUL byte in the input will be converted
21095b7b453SJohn Marino      to a trailing NUL byte in the output.  But not for UTF-7.  So that this
21195b7b453SJohn Marino      function is usable for UTF-7, we have to exclude the NUL byte from the
21295b7b453SJohn Marino      conversion and add it by hand afterwards.  */
213200fbe8dSJohn Marino # if !defined _LIBICONV_VERSION && !(defined __GLIBC__ && !defined __UCLIBC__)
21495b7b453SJohn Marino   /* Irix iconv() inserts a NUL byte if it cannot convert.
21595b7b453SJohn Marino      NetBSD iconv() inserts a question mark if it cannot convert.
21695b7b453SJohn Marino      Only GNU libiconv and GNU libc are known to prefer to fail rather
21795b7b453SJohn Marino      than doing a lossy conversion.  For other iconv() implementations,
21895b7b453SJohn Marino      we have to look at the number of irreversible conversions returned;
21995b7b453SJohn Marino      but this information is lost when iconv() returns for an E2BIG reason.
22095b7b453SJohn Marino      Therefore we cannot use the second, faster algorithm.  */
22195b7b453SJohn Marino 
22295b7b453SJohn Marino   char *result = NULL;
22395b7b453SJohn Marino   size_t length = 0;
22495b7b453SJohn Marino   int retval = mem_cd_iconv (src, strlen (src), cd, &result, &length);
22595b7b453SJohn Marino   char *final_result;
22695b7b453SJohn Marino 
22795b7b453SJohn Marino   if (retval < 0)
22895b7b453SJohn Marino     {
22995b7b453SJohn Marino       if (result != NULL)
23095b7b453SJohn Marino         abort ();
23195b7b453SJohn Marino       return NULL;
23295b7b453SJohn Marino     }
23395b7b453SJohn Marino 
23495b7b453SJohn Marino   /* Add the terminating NUL byte.  */
23595b7b453SJohn Marino   final_result =
23695b7b453SJohn Marino     (result != NULL ? realloc (result, length + 1) : malloc (length + 1));
23795b7b453SJohn Marino   if (final_result == NULL)
23895b7b453SJohn Marino     {
23995b7b453SJohn Marino       free (result);
24095b7b453SJohn Marino       errno = ENOMEM;
24195b7b453SJohn Marino       return NULL;
24295b7b453SJohn Marino     }
24395b7b453SJohn Marino   final_result[length] = '\0';
24495b7b453SJohn Marino 
24595b7b453SJohn Marino   return final_result;
24695b7b453SJohn Marino 
24795b7b453SJohn Marino # else
24895b7b453SJohn Marino   /* This algorithm is likely faster than the one above.  But it may produce
24995b7b453SJohn Marino      iconv() returns for an E2BIG reason, when the output size guess is too
25095b7b453SJohn Marino      small.  Therefore it can only be used when we don't need the number of
25195b7b453SJohn Marino      irreversible conversions performed.  */
25295b7b453SJohn Marino   char *result;
25395b7b453SJohn Marino   size_t result_size;
25495b7b453SJohn Marino   size_t length;
25595b7b453SJohn Marino   const char *inptr = src;
25695b7b453SJohn Marino   size_t inbytes_remaining = strlen (src);
25795b7b453SJohn Marino 
25895b7b453SJohn Marino   /* Make a guess for the worst-case output size, in order to avoid a
25995b7b453SJohn Marino      realloc.  It's OK if the guess is wrong as long as it is not zero and
26095b7b453SJohn Marino      doesn't lead to an integer overflow.  */
26195b7b453SJohn Marino   result_size = inbytes_remaining;
26295b7b453SJohn Marino   {
26395b7b453SJohn Marino     size_t approx_sqrt_SIZE_MAX = SIZE_MAX >> (sizeof (size_t) * CHAR_BIT / 2);
26495b7b453SJohn Marino     if (result_size <= approx_sqrt_SIZE_MAX / MB_LEN_MAX)
26595b7b453SJohn Marino       result_size *= MB_LEN_MAX;
26695b7b453SJohn Marino   }
26795b7b453SJohn Marino   result_size += 1; /* for the terminating NUL */
26895b7b453SJohn Marino 
26995b7b453SJohn Marino   result = (char *) malloc (result_size);
27095b7b453SJohn Marino   if (result == NULL)
27195b7b453SJohn Marino     {
27295b7b453SJohn Marino       errno = ENOMEM;
27395b7b453SJohn Marino       return NULL;
27495b7b453SJohn Marino     }
27595b7b453SJohn Marino 
27695b7b453SJohn Marino   /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug.  */
27795b7b453SJohn Marino # if defined _LIBICONV_VERSION \
278200fbe8dSJohn Marino      || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
279200fbe8dSJohn Marino           || defined __sun)
28095b7b453SJohn Marino   /* Set to the initial state.  */
28195b7b453SJohn Marino   iconv (cd, NULL, NULL, NULL, NULL);
28295b7b453SJohn Marino # endif
28395b7b453SJohn Marino 
28495b7b453SJohn Marino   /* Do the conversion.  */
28595b7b453SJohn Marino   {
28695b7b453SJohn Marino     char *outptr = result;
28795b7b453SJohn Marino     size_t outbytes_remaining = result_size - 1;
28895b7b453SJohn Marino 
28995b7b453SJohn Marino     for (;;)
29095b7b453SJohn Marino       {
29195b7b453SJohn Marino         /* Here inptr + inbytes_remaining = src + strlen (src),
29295b7b453SJohn Marino                 outptr + outbytes_remaining = result + result_size - 1.  */
29395b7b453SJohn Marino         size_t res = iconv (cd,
29495b7b453SJohn Marino                             (ICONV_CONST char **) &inptr, &inbytes_remaining,
29595b7b453SJohn Marino                             &outptr, &outbytes_remaining);
29695b7b453SJohn Marino 
29795b7b453SJohn Marino         if (res == (size_t)(-1))
29895b7b453SJohn Marino           {
29995b7b453SJohn Marino             if (errno == EINVAL)
30095b7b453SJohn Marino               break;
30195b7b453SJohn Marino             else if (errno == E2BIG)
30295b7b453SJohn Marino               {
30395b7b453SJohn Marino                 size_t used = outptr - result;
30495b7b453SJohn Marino                 size_t newsize = result_size * 2;
30595b7b453SJohn Marino                 char *newresult;
30695b7b453SJohn Marino 
30795b7b453SJohn Marino                 if (!(newsize > result_size))
30895b7b453SJohn Marino                   {
30995b7b453SJohn Marino                     errno = ENOMEM;
31095b7b453SJohn Marino                     goto failed;
31195b7b453SJohn Marino                   }
31295b7b453SJohn Marino                 newresult = (char *) realloc (result, newsize);
31395b7b453SJohn Marino                 if (newresult == NULL)
31495b7b453SJohn Marino                   {
31595b7b453SJohn Marino                     errno = ENOMEM;
31695b7b453SJohn Marino                     goto failed;
31795b7b453SJohn Marino                   }
31895b7b453SJohn Marino                 result = newresult;
31995b7b453SJohn Marino                 result_size = newsize;
32095b7b453SJohn Marino                 outptr = result + used;
32195b7b453SJohn Marino                 outbytes_remaining = result_size - 1 - used;
32295b7b453SJohn Marino               }
32395b7b453SJohn Marino             else
32495b7b453SJohn Marino               goto failed;
32595b7b453SJohn Marino           }
32695b7b453SJohn Marino         else
32795b7b453SJohn Marino           break;
32895b7b453SJohn Marino       }
32995b7b453SJohn Marino     /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
33095b7b453SJohn Marino # if defined _LIBICONV_VERSION \
331200fbe8dSJohn Marino      || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
332200fbe8dSJohn Marino           || defined __sun)
33395b7b453SJohn Marino     for (;;)
33495b7b453SJohn Marino       {
33595b7b453SJohn Marino         /* Here outptr + outbytes_remaining = result + result_size - 1.  */
33695b7b453SJohn Marino         size_t res = iconv (cd, NULL, NULL, &outptr, &outbytes_remaining);
33795b7b453SJohn Marino 
33895b7b453SJohn Marino         if (res == (size_t)(-1))
33995b7b453SJohn Marino           {
34095b7b453SJohn Marino             if (errno == E2BIG)
34195b7b453SJohn Marino               {
34295b7b453SJohn Marino                 size_t used = outptr - result;
34395b7b453SJohn Marino                 size_t newsize = result_size * 2;
34495b7b453SJohn Marino                 char *newresult;
34595b7b453SJohn Marino 
34695b7b453SJohn Marino                 if (!(newsize > result_size))
34795b7b453SJohn Marino                   {
34895b7b453SJohn Marino                     errno = ENOMEM;
34995b7b453SJohn Marino                     goto failed;
35095b7b453SJohn Marino                   }
35195b7b453SJohn Marino                 newresult = (char *) realloc (result, newsize);
35295b7b453SJohn Marino                 if (newresult == NULL)
35395b7b453SJohn Marino                   {
35495b7b453SJohn Marino                     errno = ENOMEM;
35595b7b453SJohn Marino                     goto failed;
35695b7b453SJohn Marino                   }
35795b7b453SJohn Marino                 result = newresult;
35895b7b453SJohn Marino                 result_size = newsize;
35995b7b453SJohn Marino                 outptr = result + used;
36095b7b453SJohn Marino                 outbytes_remaining = result_size - 1 - used;
36195b7b453SJohn Marino               }
36295b7b453SJohn Marino             else
36395b7b453SJohn Marino               goto failed;
36495b7b453SJohn Marino           }
36595b7b453SJohn Marino         else
36695b7b453SJohn Marino           break;
36795b7b453SJohn Marino       }
36895b7b453SJohn Marino # endif
36995b7b453SJohn Marino 
37095b7b453SJohn Marino     /* Add the terminating NUL byte.  */
37195b7b453SJohn Marino     *outptr++ = '\0';
37295b7b453SJohn Marino 
37395b7b453SJohn Marino     length = outptr - result;
37495b7b453SJohn Marino   }
37595b7b453SJohn Marino 
37695b7b453SJohn Marino   /* Give away unused memory.  */
37795b7b453SJohn Marino   if (length < result_size)
37895b7b453SJohn Marino     {
37995b7b453SJohn Marino       char *smaller_result = (char *) realloc (result, length);
38095b7b453SJohn Marino 
38195b7b453SJohn Marino       if (smaller_result != NULL)
38295b7b453SJohn Marino         result = smaller_result;
38395b7b453SJohn Marino     }
38495b7b453SJohn Marino 
38595b7b453SJohn Marino   return result;
38695b7b453SJohn Marino 
38795b7b453SJohn Marino  failed:
38895b7b453SJohn Marino   {
38995b7b453SJohn Marino     int saved_errno = errno;
39095b7b453SJohn Marino     free (result);
39195b7b453SJohn Marino     errno = saved_errno;
39295b7b453SJohn Marino     return NULL;
39395b7b453SJohn Marino   }
39495b7b453SJohn Marino 
39595b7b453SJohn Marino # endif
39695b7b453SJohn Marino }
39795b7b453SJohn Marino 
39895b7b453SJohn Marino #endif
39995b7b453SJohn Marino 
40095b7b453SJohn Marino char *
str_iconv(const char * src,const char * from_codeset,const char * to_codeset)40195b7b453SJohn Marino str_iconv (const char *src, const char *from_codeset, const char *to_codeset)
40295b7b453SJohn Marino {
40395b7b453SJohn Marino   if (*src == '\0' || c_strcasecmp (from_codeset, to_codeset) == 0)
40495b7b453SJohn Marino     {
40595b7b453SJohn Marino       char *result = strdup (src);
40695b7b453SJohn Marino 
40795b7b453SJohn Marino       if (result == NULL)
40895b7b453SJohn Marino         errno = ENOMEM;
40995b7b453SJohn Marino       return result;
41095b7b453SJohn Marino     }
41195b7b453SJohn Marino   else
41295b7b453SJohn Marino     {
41395b7b453SJohn Marino #if HAVE_ICONV
41495b7b453SJohn Marino       iconv_t cd;
41595b7b453SJohn Marino       char *result;
41695b7b453SJohn Marino 
41795b7b453SJohn Marino       /* Avoid glibc-2.1 bug with EUC-KR.  */
418200fbe8dSJohn Marino # if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
419200fbe8dSJohn Marino      && !defined _LIBICONV_VERSION
42095b7b453SJohn Marino       if (c_strcasecmp (from_codeset, "EUC-KR") == 0
42195b7b453SJohn Marino           || c_strcasecmp (to_codeset, "EUC-KR") == 0)
42295b7b453SJohn Marino         {
42395b7b453SJohn Marino           errno = EINVAL;
42495b7b453SJohn Marino           return NULL;
42595b7b453SJohn Marino         }
42695b7b453SJohn Marino # endif
42795b7b453SJohn Marino       cd = iconv_open (to_codeset, from_codeset);
42895b7b453SJohn Marino       if (cd == (iconv_t) -1)
42995b7b453SJohn Marino         return NULL;
43095b7b453SJohn Marino 
43195b7b453SJohn Marino       result = str_cd_iconv (src, cd);
43295b7b453SJohn Marino 
43395b7b453SJohn Marino       if (result == NULL)
43495b7b453SJohn Marino         {
43595b7b453SJohn Marino           /* Close cd, but preserve the errno from str_cd_iconv.  */
43695b7b453SJohn Marino           int saved_errno = errno;
43795b7b453SJohn Marino           iconv_close (cd);
43895b7b453SJohn Marino           errno = saved_errno;
43995b7b453SJohn Marino         }
44095b7b453SJohn Marino       else
44195b7b453SJohn Marino         {
44295b7b453SJohn Marino           if (iconv_close (cd) < 0)
44395b7b453SJohn Marino             {
44495b7b453SJohn Marino               /* Return NULL, but free the allocated memory, and while doing
44595b7b453SJohn Marino                  that, preserve the errno from iconv_close.  */
44695b7b453SJohn Marino               int saved_errno = errno;
44795b7b453SJohn Marino               free (result);
44895b7b453SJohn Marino               errno = saved_errno;
44995b7b453SJohn Marino               return NULL;
45095b7b453SJohn Marino             }
45195b7b453SJohn Marino         }
45295b7b453SJohn Marino       return result;
45395b7b453SJohn Marino #else
45495b7b453SJohn Marino       /* This is a different error code than if iconv_open existed but didn't
45595b7b453SJohn Marino          support from_codeset and to_codeset, so that the caller can emit
45695b7b453SJohn Marino          an error message such as
45795b7b453SJohn Marino            "iconv() is not supported. Installing GNU libiconv and
45895b7b453SJohn Marino             then reinstalling this package would fix this."  */
45995b7b453SJohn Marino       errno = ENOSYS;
46095b7b453SJohn Marino       return NULL;
46195b7b453SJohn Marino #endif
46295b7b453SJohn Marino     }
46395b7b453SJohn Marino }
464