e354a5
commit 91927b7c76437db860cd86a7714476b56bb39d07
e354a5
Author: Arjun Shankar <arjun@redhat.com>
e354a5
Date:   Tue Jul 7 20:31:48 2020 +0200
e354a5
e354a5
    Rewrite iconv option parsing [BZ #19519]
e354a5
    
e354a5
    This commit replaces string manipulation during `iconv_open' and iconv_prog
e354a5
    option parsing with a structured, flag based conversion specification.  In
e354a5
    doing so, it alters the internal `__gconv_open' interface and accordingly
e354a5
    adjusts its uses.
e354a5
    
e354a5
    This change fixes several hangs in the iconv program and therefore includes
e354a5
    a new test to exercise iconv_prog options that originally led to these hangs.
e354a5
    It also includes a new regression test for option handling in the iconv
e354a5
    function.
e354a5
    
e354a5
    Reviewed-by: Florian Weimer <fweimer@redhat.com>
e354a5
    Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
e354a5
    Reviewed-by: Carlos O'Donell <carlos@redhat.com>
e354a5
e354a5
diff --git a/iconv/Makefile b/iconv/Makefile
e354a5
index d71319b39e772fde..d09b8ac842731780 100644
e354a5
--- a/iconv/Makefile
e354a5
+++ b/iconv/Makefile
e354a5
@@ -26,7 +26,7 @@ headers		= iconv.h gconv.h
e354a5
 routines	= iconv_open iconv iconv_close \
e354a5
 		  gconv_open gconv gconv_close gconv_db gconv_conf \
e354a5
 		  gconv_builtin gconv_simple gconv_trans gconv_cache
e354a5
-routines	+= gconv_dl
e354a5
+routines	+= gconv_dl gconv_charset
e354a5
 
e354a5
 vpath %.c ../locale/programs ../intl
e354a5
 
e354a5
@@ -43,7 +43,8 @@ CFLAGS-charmap.c += -DCHARMAP_PATH='"$(i18ndir)/charmaps"' \
e354a5
 CFLAGS-linereader.c += -DNO_TRANSLITERATION
e354a5
 CFLAGS-simple-hash.c += -I../locale
e354a5
 
e354a5
-tests	= tst-iconv1 tst-iconv2 tst-iconv3 tst-iconv4 tst-iconv5 tst-iconv6
e354a5
+tests	= tst-iconv1 tst-iconv2 tst-iconv3 tst-iconv4 tst-iconv5 tst-iconv6 \
e354a5
+	  tst-iconv-opt
e354a5
 
e354a5
 others		= iconv_prog iconvconfig
e354a5
 install-others-programs	= $(inst_bindir)/iconv
e354a5
@@ -60,6 +61,7 @@ include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left))
e354a5
 
e354a5
 ifeq ($(run-built-tests),yes)
e354a5
 xtests-special += $(objpfx)test-iconvconfig.out
e354a5
+tests-special += $(objpfx)tst-iconv_prog.out
e354a5
 endif
e354a5
 
e354a5
 # Make a copy of the file because gconv module names are constructed
e354a5
@@ -78,6 +80,13 @@ endif
e354a5
 
e354a5
 include ../Rules
e354a5
 
e354a5
+ifeq ($(run-built-tests),yes)
e354a5
+LOCALES := en_US.UTF-8
e354a5
+include ../gen-locales.mk
e354a5
+
e354a5
+$(objpfx)tst-iconv-opt.out: $(gen-locales)
e354a5
+endif
e354a5
+
e354a5
 $(inst_bindir)/iconv: $(objpfx)iconv_prog $(+force)
e354a5
 	$(do-install-program)
e354a5
 
e354a5
@@ -92,3 +101,8 @@ $(objpfx)test-iconvconfig.out: /dev/null $(objpfx)iconvconfig
e354a5
 	 cmp $$tmp $(inst_gconvdir)/gconv-modules.cache; \
e354a5
 	 rm -f $$tmp) > $@; \
e354a5
 	$(evaluate-test)
e354a5
+
e354a5
+$(objpfx)tst-iconv_prog.out: tst-iconv_prog.sh $(objpfx)iconv_prog
e354a5
+	$(BASH) $< $(common-objdir) '$(test-wrapper-env)' \
e354a5
+		 '$(run-program-env)' > $@; \
e354a5
+	$(evaluate-test)
e354a5
diff --git a/iconv/Versions b/iconv/Versions
e354a5
index 60ab10a277588515..8a5f4cf780b18925 100644
e354a5
--- a/iconv/Versions
e354a5
+++ b/iconv/Versions
e354a5
@@ -6,6 +6,7 @@ libc {
e354a5
   GLIBC_PRIVATE {
e354a5
     # functions shared with iconv program
e354a5
     __gconv_get_alias_db; __gconv_get_cache; __gconv_get_modules_db;
e354a5
+    __gconv_open; __gconv_create_spec;
e354a5
 
e354a5
     # function used by the gconv modules
e354a5
     __gconv_transliterate;
e354a5
diff --git a/iconv/gconv_charset.c b/iconv/gconv_charset.c
e354a5
new file mode 100644
e354a5
index 0000000000000000..6ccd0773ccb6cd27
e354a5
--- /dev/null
e354a5
+++ b/iconv/gconv_charset.c
e354a5
@@ -0,0 +1,218 @@
e354a5
+/* Charset name normalization.
e354a5
+   Copyright (C) 2020 Free Software Foundation, Inc.
e354a5
+   This file is part of the GNU C Library.
e354a5
+
e354a5
+   The GNU C Library is free software; you can redistribute it and/or
e354a5
+   modify it under the terms of the GNU Lesser General Public
e354a5
+   License as published by the Free Software Foundation; either
e354a5
+   version 2.1 of the License, or (at your option) any later version.
e354a5
+
e354a5
+   The GNU C Library is distributed in the hope that it will be useful,
e354a5
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
e354a5
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
e354a5
+   Lesser General Public License for more details.
e354a5
+
e354a5
+   You should have received a copy of the GNU Lesser General Public
e354a5
+   License along with the GNU C Library; if not, see
e354a5
+   <http://www.gnu.org/licenses/>.  */
e354a5
+
e354a5
+
e354a5
+#include <stdlib.h>
e354a5
+#include <ctype.h>
e354a5
+#include <locale.h>
e354a5
+#include <stdbool.h>
e354a5
+#include <string.h>
e354a5
+#include <sys/stat.h>
e354a5
+#include "gconv_int.h"
e354a5
+#include "gconv_charset.h"
e354a5
+
e354a5
+
e354a5
+/* This function returns a pointer to the last suffix in a conversion code
e354a5
+   string.  Valid suffixes matched by this function are of the form: '/' or ','
e354a5
+   followed by arbitrary text that doesn't contain '/' or ','.  It does not
e354a5
+   edit the string in any way.  The caller is expected to parse the suffix and
e354a5
+   remove it (by e.g. truncating the string) before the next call.  */
e354a5
+static char *
e354a5
+find_suffix (char *s)
e354a5
+{
e354a5
+  /* The conversion code is in the form of a triplet, separated by '/' chars.
e354a5
+     The third component of the triplet contains suffixes. If we don't have two
e354a5
+     slashes, we don't have a suffix.  */
e354a5
+
e354a5
+  int slash_count = 0;
e354a5
+  char *suffix_term = NULL;
e354a5
+
e354a5
+  for (int i = 0; s[i] != '\0'; i++)
e354a5
+    switch (s[i])
e354a5
+      {
e354a5
+        case '/':
e354a5
+          slash_count++;
e354a5
+          /* Fallthrough */
e354a5
+        case ',':
e354a5
+          suffix_term = &s[i];
e354a5
+      }
e354a5
+
e354a5
+  if (slash_count >= 2)
e354a5
+    return suffix_term;
e354a5
+
e354a5
+  return NULL;
e354a5
+}
e354a5
+
e354a5
+
e354a5
+struct gconv_parsed_code
e354a5
+{
e354a5
+  char *code;
e354a5
+  bool translit;
e354a5
+  bool ignore;
e354a5
+};
e354a5
+
e354a5
+
e354a5
+/* This function parses an iconv_open encoding PC.CODE, strips any suffixes
e354a5
+   (such as TRANSLIT or IGNORE) from it and sets corresponding flags in it.  */
e354a5
+static void
e354a5
+gconv_parse_code (struct gconv_parsed_code *pc)
e354a5
+{
e354a5
+  pc->translit = false;
e354a5
+  pc->ignore = false;
e354a5
+
e354a5
+  while (1)
e354a5
+    {
e354a5
+      /* First drop any trailing whitespaces and separators.  */
e354a5
+      size_t len = strlen (pc->code);
e354a5
+      while ((len > 0)
e354a5
+             && (isspace (pc->code[len - 1])
e354a5
+                 || pc->code[len - 1] == ','
e354a5
+                 || pc->code[len - 1] == '/'))
e354a5
+        len--;
e354a5
+
e354a5
+      pc->code[len] = '\0';
e354a5
+
e354a5
+      if (len == 0)
e354a5
+        return;
e354a5
+
e354a5
+      char * suffix = find_suffix (pc->code);
e354a5
+      if (suffix == NULL)
e354a5
+        {
e354a5
+          /* At this point, we have processed and removed all suffixes from the
e354a5
+             code and what remains of the code is suffix free.  */
e354a5
+          return;
e354a5
+        }
e354a5
+      else
e354a5
+        {
e354a5
+          /* A suffix is processed from the end of the code array going
e354a5
+             backwards, one suffix at a time.  The suffix is an index into the
e354a5
+             code character array and points to: one past the end of the code
e354a5
+             and any unprocessed suffixes, and to the beginning of the suffix
e354a5
+             currently being processed during this iteration.  We must process
e354a5
+             this suffix and then drop it from the code by terminating the
e354a5
+             preceding text with NULL.
e354a5
+
e354a5
+             We want to allow and recognize suffixes such as:
e354a5
+
e354a5
+             "/TRANSLIT"         i.e. single suffix
e354a5
+             "//TRANSLIT"        i.e. single suffix and multiple separators
e354a5
+             "//TRANSLIT/IGNORE" i.e. suffixes separated by "/"
e354a5
+             "/TRANSLIT//IGNORE" i.e. suffixes separated by "//"
e354a5
+             "//IGNORE,TRANSLIT" i.e. suffixes separated by ","
e354a5
+             "//IGNORE,"         i.e. trailing ","
e354a5
+             "//TRANSLIT/"       i.e. trailing "/"
e354a5
+             "//TRANSLIT//"      i.e. trailing "//"
e354a5
+             "/"                 i.e. empty suffix.
e354a5
+
e354a5
+             Unknown suffixes are silently discarded and ignored.  */
e354a5
+
e354a5
+          if ((__strcasecmp_l (suffix,
e354a5
+                               GCONV_TRIPLE_SEPARATOR
e354a5
+                               GCONV_TRANSLIT_SUFFIX,
e354a5
+                               _nl_C_locobj_ptr) == 0)
e354a5
+              || (__strcasecmp_l (suffix,
e354a5
+                                  GCONV_SUFFIX_SEPARATOR
e354a5
+                                  GCONV_TRANSLIT_SUFFIX,
e354a5
+                                  _nl_C_locobj_ptr) == 0))
e354a5
+            pc->translit = true;
e354a5
+
e354a5
+          if ((__strcasecmp_l (suffix,
e354a5
+                               GCONV_TRIPLE_SEPARATOR
e354a5
+                               GCONV_IGNORE_ERRORS_SUFFIX,
e354a5
+                               _nl_C_locobj_ptr) == 0)
e354a5
+              || (__strcasecmp_l (suffix,
e354a5
+                                  GCONV_SUFFIX_SEPARATOR
e354a5
+                                  GCONV_IGNORE_ERRORS_SUFFIX,
e354a5
+                                  _nl_C_locobj_ptr) == 0))
e354a5
+            pc->ignore = true;
e354a5
+
e354a5
+          /* We just processed this suffix.  We can now drop it from the
e354a5
+             code string by truncating it at the suffix's position.  */
e354a5
+          suffix[0] = '\0';
e354a5
+        }
e354a5
+    }
e354a5
+}
e354a5
+
e354a5
+
e354a5
+/* This function accepts the charset names of the source and destination of the
e354a5
+   conversion and populates *conv_spec with an equivalent conversion
e354a5
+   specification that may later be used by __gconv_open.  The charset names
e354a5
+   might contain options in the form of suffixes that alter the conversion,
e354a5
+   e.g. "ISO-10646/UTF-8/TRANSLIT".  It processes the charset names, ignoring
e354a5
+   and truncating any suffix options in fromcode, and processing and truncating
e354a5
+   any suffix options in tocode.  Supported suffix options ("TRANSLIT" or
e354a5
+   "IGNORE") when found in tocode lead to the corresponding flag in *conv_spec
e354a5
+   to be set to true.  Unrecognized suffix options are silently discarded.  If
e354a5
+   the function succeeds, it returns conv_spec back to the caller.  It returns
e354a5
+   NULL upon failure.  conv_spec must be allocated and freed by the caller.  */
e354a5
+struct gconv_spec *
e354a5
+__gconv_create_spec (struct gconv_spec *conv_spec, const char *fromcode,
e354a5
+                   const char *tocode)
e354a5
+{
e354a5
+  struct gconv_parsed_code pfc, ptc;
e354a5
+  struct gconv_spec *ret = NULL;
e354a5
+
e354a5
+  pfc.code = __strdup (fromcode);
e354a5
+  ptc.code = __strdup (tocode);
e354a5
+
e354a5
+  if ((pfc.code == NULL)
e354a5
+      || (ptc.code == NULL))
e354a5
+    goto out;
e354a5
+
e354a5
+  gconv_parse_code (&pfc;;
e354a5
+  gconv_parse_code (&ptc);
e354a5
+
e354a5
+  /* We ignore suffixes in the fromcode because that is how the current
e354a5
+     implementation has always handled them.  Only suffixes in the tocode are
e354a5
+     processed and handled.  The reality is that invalid input in the input
e354a5
+     character set should only be ignored if the fromcode specifies IGNORE.
e354a5
+     The current implementation ignores invalid intput in the input character
e354a5
+     set if the tocode contains IGNORE.  We preserve this behavior for
e354a5
+     backwards compatibility.  In the future we may split the handling of
e354a5
+     IGNORE to allow a finer grained specification of ignorning invalid input
e354a5
+     and/or ignoring invalid output.  */
e354a5
+  conv_spec->translit = ptc.translit;
e354a5
+  conv_spec->ignore = ptc.ignore;
e354a5
+
e354a5
+  /* 3 extra bytes because 1 extra for '\0', and 2 extra so strip might
e354a5
+     be able to add one or two trailing '/' characters if necessary.  */
e354a5
+  conv_spec->fromcode = malloc (strlen (fromcode) + 3);
e354a5
+  if (conv_spec->fromcode == NULL)
e354a5
+    goto out;
e354a5
+
e354a5
+  conv_spec->tocode = malloc (strlen (tocode) + 3);
e354a5
+  if (conv_spec->tocode == NULL)
e354a5
+    {
e354a5
+      free (conv_spec->fromcode);
e354a5
+      conv_spec->fromcode = NULL;
e354a5
+      goto out;
e354a5
+    }
e354a5
+
e354a5
+  /* Strip unrecognized characters and ensure that the code has two '/'
e354a5
+     characters as per conversion code triplet specification.  */
e354a5
+  strip (conv_spec->fromcode, pfc.code);
e354a5
+  strip (conv_spec->tocode, ptc.code);
e354a5
+  ret = conv_spec;
e354a5
+
e354a5
+out:
e354a5
+  free (pfc.code);
e354a5
+  free (ptc.code);
e354a5
+
e354a5
+  return ret;
e354a5
+}
e354a5
+libc_hidden_def (__gconv_create_spec)
e354a5
diff --git a/iconv/gconv_charset.h b/iconv/gconv_charset.h
e354a5
index 123e2a62cefdc017..b85d80313030b649 100644
e354a5
--- a/iconv/gconv_charset.h
e354a5
+++ b/iconv/gconv_charset.h
e354a5
@@ -19,9 +19,68 @@
e354a5
 
e354a5
 #include <ctype.h>
e354a5
 #include <locale.h>
e354a5
+#include <stdbool.h>
e354a5
+#include <string.h>
e354a5
+#include <sys/stat.h>
e354a5
+#include <stdlib.h>
e354a5
+#include "gconv_int.h"
e354a5
 
e354a5
 
e354a5
-static void
e354a5
+/* An iconv encoding is in the form of a triplet, with parts separated by
e354a5
+   a '/' character.  The first part is the standard name, the second part is
e354a5
+   the character set, and the third part is the error handler.  If the first
e354a5
+   part is sufficient to identify both the standard and the character set
e354a5
+   then the second part can be empty e.g. UTF-8//.  If the first part is not
e354a5
+   sufficient to identify both the standard and the character set then the
e354a5
+   second part is required e.g. ISO-10646/UTF8/.  If neither the first or
e354a5
+   second parts are provided e.g. //, then the current locale is used.
e354a5
+   The actual values used in the first and second parts are not entirely
e354a5
+   relevant to the implementation.  The values themselves are used in a hash
e354a5
+   table to lookup modules and so the naming convention of the first two parts
e354a5
+   is somewhat arbitrary and only helps locate the entries in the cache.
e354a5
+   The third part is the error handler and is comprised of a ',' or '/'
e354a5
+   separated list of suffixes.  Currently, we support "TRANSLIT" for
e354a5
+   transliteration and "IGNORE" for ignoring conversion errors due to
e354a5
+   unrecognized input characters.  */
e354a5
+#define GCONV_TRIPLE_SEPARATOR "/"
e354a5
+#define GCONV_SUFFIX_SEPARATOR ","
e354a5
+#define GCONV_TRANSLIT_SUFFIX "TRANSLIT"
e354a5
+#define GCONV_IGNORE_ERRORS_SUFFIX "IGNORE"
e354a5
+
e354a5
+
e354a5
+/* This function accepts the charset names of the source and destination of the
e354a5
+   conversion and populates *conv_spec with an equivalent conversion
e354a5
+   specification that may later be used by __gconv_open.  The charset names
e354a5
+   might contain options in the form of suffixes that alter the conversion,
e354a5
+   e.g. "ISO-10646/UTF-8/TRANSLIT".  It processes the charset names, ignoring
e354a5
+   and truncating any suffix options in fromcode, and processing and truncating
e354a5
+   any suffix options in tocode.  Supported suffix options ("TRANSLIT" or
e354a5
+   "IGNORE") when found in tocode lead to the corresponding flag in *conv_spec
e354a5
+   to be set to true.  Unrecognized suffix options are silently discarded.  If
e354a5
+   the function succeeds, it returns conv_spec back to the caller.  It returns
e354a5
+   NULL upon failure.  */
e354a5
+struct gconv_spec *
e354a5
+__gconv_create_spec (struct gconv_spec *conv_spec, const char *fromcode,
e354a5
+                     const char *tocode);
e354a5
+libc_hidden_proto (__gconv_create_spec)
e354a5
+
e354a5
+
e354a5
+/* This function frees all heap memory allocated by __gconv_create_spec.  */
e354a5
+static void __attribute__ ((unused))
e354a5
+gconv_destroy_spec (struct gconv_spec *conv_spec)
e354a5
+{
e354a5
+  free (conv_spec->fromcode);
e354a5
+  free (conv_spec->tocode);
e354a5
+  return;
e354a5
+}
e354a5
+
e354a5
+
e354a5
+/* This function copies in-order, characters from the source 's' that are
e354a5
+   either alpha-numeric or one in one of these: "_-.,:/" - into the destination
e354a5
+   'wp' while dropping all other characters.  In the process, it converts all
e354a5
+   alphabetical characters to upper case.  It then appends up to two '/'
e354a5
+   characters so that the total number of '/'es in the destination is 2.  */
e354a5
+static inline void __attribute__ ((unused, always_inline))
e354a5
 strip (char *wp, const char *s)
e354a5
 {
e354a5
   int slash_count = 0;
e354a5
diff --git a/iconv/gconv_int.h b/iconv/gconv_int.h
e354a5
index 3742557caed05c9a..4748e9b1fa3b5426 100644
e354a5
--- a/iconv/gconv_int.h
e354a5
+++ b/iconv/gconv_int.h
e354a5
@@ -92,6 +92,15 @@ struct gconv_module
e354a5
 };
e354a5
 
e354a5
 
e354a5
+/* The specification of the conversion that needs to be performed.  */
e354a5
+struct gconv_spec
e354a5
+{
e354a5
+  char *fromcode;
e354a5
+  char *tocode;
e354a5
+  bool translit;
e354a5
+  bool ignore;
e354a5
+};
e354a5
+
e354a5
 /* Flags for `gconv_open'.  */
e354a5
 enum
e354a5
 {
e354a5
@@ -154,10 +163,12 @@ __libc_lock_define (extern, __gconv_lock attribute_hidden)
e354a5
   })
e354a5
 
e354a5
 
e354a5
-/* Return in *HANDLE decriptor for transformation from FROMSET to TOSET.  */
e354a5
-extern int __gconv_open (const char *toset, const char *fromset,
e354a5
-			 __gconv_t *handle, int flags)
e354a5
-     attribute_hidden;
e354a5
+/* Return in *HANDLE, a decriptor for the transformation.  The function expects
e354a5
+   the specification of the transformation in the structure pointed to by
e354a5
+   CONV_SPEC.  It only reads *CONV_SPEC and does not take ownership of it.  */
e354a5
+extern int __gconv_open (struct gconv_spec *conv_spec,
e354a5
+                         __gconv_t *handle, int flags);
e354a5
+libc_hidden_proto (__gconv_open)
e354a5
 
e354a5
 /* Free resources associated with transformation descriptor CD.  */
e354a5
 extern int __gconv_close (__gconv_t cd)
e354a5
diff --git a/iconv/gconv_open.c b/iconv/gconv_open.c
e354a5
index f739561f6edba8a8..002faa111a0b9016 100644
e354a5
--- a/iconv/gconv_open.c
e354a5
+++ b/iconv/gconv_open.c
e354a5
@@ -27,7 +27,7 @@
e354a5
 
e354a5
 
e354a5
 int
e354a5
-__gconv_open (const char *toset, const char *fromset, __gconv_t *handle,
e354a5
+__gconv_open (struct gconv_spec *conv_spec, __gconv_t *handle,
e354a5
 	      int flags)
e354a5
 {
e354a5
   struct __gconv_step *steps;
e354a5
@@ -36,77 +36,38 @@ __gconv_open (const char *toset, const char *fromset, __gconv_t *handle,
e354a5
   size_t cnt = 0;
e354a5
   int res;
e354a5
   int conv_flags = 0;
e354a5
-  const char *errhand;
e354a5
-  const char *ignore;
e354a5
   bool translit = false;
e354a5
+  char *tocode, *fromcode;
e354a5
 
e354a5
   /* Find out whether any error handling method is specified.  */
e354a5
-  errhand = strchr (toset, '/');
e354a5
-  if (errhand != NULL)
e354a5
-    errhand = strchr (errhand + 1, '/');
e354a5
-  if (__glibc_likely (errhand != NULL))
e354a5
-    {
e354a5
-      if (*++errhand == '\0')
e354a5
-	errhand = NULL;
e354a5
-      else
e354a5
-	{
e354a5
-	  /* Make copy without the error handling description.  */
e354a5
-	  char *newtoset = (char *) alloca (errhand - toset + 1);
e354a5
-	  char *tok;
e354a5
-	  char *ptr = NULL /* Work around a bogus warning */;
e354a5
-
e354a5
-	  newtoset[errhand - toset] = '\0';
e354a5
-	  toset = memcpy (newtoset, toset, errhand - toset);
e354a5
+  translit = conv_spec->translit;
e354a5
 
e354a5
-	  /* Find the appropriate transliteration handlers.  */
e354a5
-	  tok = strdupa (errhand);
e354a5
+  if (conv_spec->ignore)
e354a5
+    conv_flags |= __GCONV_IGNORE_ERRORS;
e354a5
 
e354a5
-	  tok = __strtok_r (tok, ",", &ptr);
e354a5
-	  while (tok != NULL)
e354a5
-	    {
e354a5
-	      if (__strcasecmp_l (tok, "TRANSLIT", _nl_C_locobj_ptr) == 0)
e354a5
-		translit = true;
e354a5
-	      else if (__strcasecmp_l (tok, "IGNORE", _nl_C_locobj_ptr) == 0)
e354a5
-		/* Set the flag to ignore all errors.  */
e354a5
-		conv_flags |= __GCONV_IGNORE_ERRORS;
e354a5
-
e354a5
-	      tok = __strtok_r (NULL, ",", &ptr);
e354a5
-	    }
e354a5
-	}
e354a5
-    }
e354a5
-
e354a5
-  /* For the source character set we ignore the error handler specification.
e354a5
-     XXX Is this really always the best?  */
e354a5
-  ignore = strchr (fromset, '/');
e354a5
-  if (ignore != NULL && (ignore = strchr (ignore + 1, '/')) != NULL
e354a5
-      && *++ignore != '\0')
e354a5
-    {
e354a5
-      char *newfromset = (char *) alloca (ignore - fromset + 1);
e354a5
-
e354a5
-      newfromset[ignore - fromset] = '\0';
e354a5
-      fromset = memcpy (newfromset, fromset, ignore - fromset);
e354a5
-    }
e354a5
+  tocode = conv_spec->tocode;
e354a5
+  fromcode = conv_spec->fromcode;
e354a5
 
e354a5
   /* If the string is empty define this to mean the charset of the
e354a5
      currently selected locale.  */
e354a5
-  if (strcmp (toset, "//") == 0)
e354a5
+  if (strcmp (tocode, "//") == 0)
e354a5
     {
e354a5
       const char *codeset = _NL_CURRENT (LC_CTYPE, CODESET);
e354a5
       size_t len = strlen (codeset);
e354a5
       char *dest;
e354a5
-      toset = dest = (char *) alloca (len + 3);
e354a5
+      tocode = dest = (char *) alloca (len + 3);
e354a5
       memcpy (__mempcpy (dest, codeset, len), "//", 3);
e354a5
     }
e354a5
-  if (strcmp (fromset, "//") == 0)
e354a5
+  if (strcmp (fromcode, "//") == 0)
e354a5
     {
e354a5
       const char *codeset = _NL_CURRENT (LC_CTYPE, CODESET);
e354a5
       size_t len = strlen (codeset);
e354a5
       char *dest;
e354a5
-      fromset = dest = (char *) alloca (len + 3);
e354a5
+      fromcode = dest = (char *) alloca (len + 3);
e354a5
       memcpy (__mempcpy (dest, codeset, len), "//", 3);
e354a5
     }
e354a5
 
e354a5
-  res = __gconv_find_transform (toset, fromset, &steps, &nsteps, flags);
e354a5
+  res = __gconv_find_transform (tocode, fromcode, &steps, &nsteps, flags);
e354a5
   if (res == __GCONV_OK)
e354a5
     {
e354a5
       /* Allocate room for handle.  */
e354a5
@@ -205,3 +166,4 @@ __gconv_open (const char *toset, const char *fromset, __gconv_t *handle,
e354a5
   *handle = result;
e354a5
   return res;
e354a5
 }
e354a5
+libc_hidden_def (__gconv_open)
e354a5
diff --git a/iconv/iconv_open.c b/iconv/iconv_open.c
e354a5
index 9f5c32c02096254a..59d1ef4f07ed1022 100644
e354a5
--- a/iconv/iconv_open.c
e354a5
+++ b/iconv/iconv_open.c
e354a5
@@ -31,49 +31,15 @@
e354a5
 iconv_t
e354a5
 iconv_open (const char *tocode, const char *fromcode)
e354a5
 {
e354a5
-  /* Normalize the name.  We remove all characters beside alpha-numeric,
e354a5
-     '_', '-', '/', '.', and ':'.  */
e354a5
-  size_t tocode_len = strlen (tocode) + 3;
e354a5
-  char *tocode_conv;
e354a5
-  bool tocode_usealloca = __libc_use_alloca (tocode_len);
e354a5
-  if (tocode_usealloca)
e354a5
-    tocode_conv = (char *) alloca (tocode_len);
e354a5
-  else
e354a5
-    {
e354a5
-      tocode_conv = (char *) malloc (tocode_len);
e354a5
-      if (tocode_conv == NULL)
e354a5
-	return (iconv_t) -1;
e354a5
-    }
e354a5
-  strip (tocode_conv, tocode);
e354a5
-  tocode = (tocode_conv[2] == '\0' && tocode[0] != '\0'
e354a5
-	    ? upstr (tocode_conv, tocode) : tocode_conv);
e354a5
+  __gconv_t cd;
e354a5
+  struct gconv_spec conv_spec;
e354a5
 
e354a5
-  size_t fromcode_len = strlen (fromcode) + 3;
e354a5
-  char *fromcode_conv;
e354a5
-  bool fromcode_usealloca = __libc_use_alloca (fromcode_len);
e354a5
-  if (fromcode_usealloca)
e354a5
-    fromcode_conv = (char *) alloca (fromcode_len);
e354a5
-  else
e354a5
-    {
e354a5
-      fromcode_conv = (char *) malloc (fromcode_len);
e354a5
-      if (fromcode_conv == NULL)
e354a5
-	{
e354a5
-	  if (! tocode_usealloca)
e354a5
-	    free (tocode_conv);
e354a5
-	  return (iconv_t) -1;
e354a5
-	}
e354a5
-    }
e354a5
-  strip (fromcode_conv, fromcode);
e354a5
-  fromcode = (fromcode_conv[2] == '\0' && fromcode[0] != '\0'
e354a5
-	      ? upstr (fromcode_conv, fromcode) : fromcode_conv);
e354a5
+  if (__gconv_create_spec (&conv_spec, fromcode, tocode) == NULL)
e354a5
+    return (iconv_t) -1;
e354a5
 
e354a5
-  __gconv_t cd;
e354a5
-  int res = __gconv_open (tocode, fromcode, &cd, 0);
e354a5
+  int res = __gconv_open (&conv_spec, &cd, 0);
e354a5
 
e354a5
-  if (! fromcode_usealloca)
e354a5
-    free (fromcode_conv);
e354a5
-  if (! tocode_usealloca)
e354a5
-    free (tocode_conv);
e354a5
+  gconv_destroy_spec (&conv_spec);
e354a5
 
e354a5
   if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
e354a5
     {
e354a5
diff --git a/iconv/iconv_prog.c b/iconv/iconv_prog.c
e354a5
index 52e9d3f3ddec3b2e..552efac81660e82a 100644
e354a5
--- a/iconv/iconv_prog.c
e354a5
+++ b/iconv/iconv_prog.c
e354a5
@@ -39,6 +39,7 @@
e354a5
 #include <gconv_int.h>
e354a5
 #include "iconv_prog.h"
e354a5
 #include "iconvconfig.h"
e354a5
+#include "gconv_charset.h"
e354a5
 
e354a5
 /* Get libc version number.  */
e354a5
 #include "../version.h"
e354a5
@@ -118,8 +119,7 @@ main (int argc, char *argv[])
e354a5
 {
e354a5
   int status = EXIT_SUCCESS;
e354a5
   int remaining;
e354a5
-  iconv_t cd;
e354a5
-  const char *orig_to_code;
e354a5
+  __gconv_t cd;
e354a5
   struct charmap_t *from_charmap = NULL;
e354a5
   struct charmap_t *to_charmap = NULL;
e354a5
 
e354a5
@@ -139,39 +139,6 @@ main (int argc, char *argv[])
e354a5
       exit (EXIT_SUCCESS);
e354a5
     }
e354a5
 
e354a5
-  /* If we have to ignore errors make sure we use the appropriate name for
e354a5
-     the to-character-set.  */
e354a5
-  orig_to_code = to_code;
e354a5
-  if (omit_invalid)
e354a5
-    {
e354a5
-      const char *errhand = strchrnul (to_code, '/');
e354a5
-      int nslash = 2;
e354a5
-      char *newp;
e354a5
-      char *cp;
e354a5
-
e354a5
-      if (*errhand == '/')
e354a5
-	{
e354a5
-	  --nslash;
e354a5
-	  errhand = strchrnul (errhand + 1, '/');
e354a5
-
e354a5
-	  if (*errhand == '/')
e354a5
-	    {
e354a5
-	      --nslash;
e354a5
-	      errhand = strchr (errhand, '\0');
e354a5
-	    }
e354a5
-	}
e354a5
-
e354a5
-      newp = (char *) alloca (errhand - to_code + nslash + 7 + 1);
e354a5
-      cp = mempcpy (newp, to_code, errhand - to_code);
e354a5
-      while (nslash-- > 0)
e354a5
-	*cp++ = '/';
e354a5
-      if (cp[-1] != '/')
e354a5
-	*cp++ = ',';
e354a5
-      memcpy (cp, "IGNORE", sizeof ("IGNORE"));
e354a5
-
e354a5
-      to_code = newp;
e354a5
-    }
e354a5
-
e354a5
   /* POSIX 1003.2b introduces a silly thing: the arguments to -t anf -f
e354a5
      can be file names of charmaps.  In this case iconv will have to read
e354a5
      those charmaps and use them to do the conversion.  But there are
e354a5
@@ -184,10 +151,10 @@ main (int argc, char *argv[])
e354a5
        file.  */
e354a5
     from_charmap = charmap_read (from_code, /*0, 1*/1, 0, 0, 0);
e354a5
 
e354a5
-  if (strchr (orig_to_code, '/') != NULL)
e354a5
+  if (strchr (to_code, '/') != NULL)
e354a5
     /* The to-name might be a charmap file name.  Try reading the
e354a5
        file.  */
e354a5
-    to_charmap = charmap_read (orig_to_code, /*0, 1,*/1, 0, 0, 0);
e354a5
+    to_charmap = charmap_read (to_code, /*0, 1,*/1, 0, 0, 0);
e354a5
 
e354a5
 
e354a5
   /* At this point we have to handle two cases.  The first one is
e354a5
@@ -201,9 +168,25 @@ main (int argc, char *argv[])
e354a5
 				 argc, remaining, argv, output_file);
e354a5
   else
e354a5
     {
e354a5
+      struct gconv_spec conv_spec;
e354a5
+      int res;
e354a5
+
e354a5
+      if (__gconv_create_spec (&conv_spec, from_code, to_code) == NULL)
e354a5
+        {
e354a5
+          error (EXIT_FAILURE, errno,
e354a5
+                 _("failed to start conversion processing"));
e354a5
+          exit (1);
e354a5
+        }
e354a5
+
e354a5
+      if (omit_invalid)
e354a5
+        conv_spec.ignore = true;
e354a5
+
e354a5
       /* Let's see whether we have these coded character sets.  */
e354a5
-      cd = iconv_open (to_code, from_code);
e354a5
-      if (cd == (iconv_t) -1)
e354a5
+      res = __gconv_open (&conv_spec, &cd, 0);
e354a5
+
e354a5
+      gconv_destroy_spec (&conv_spec);
e354a5
+
e354a5
+      if (res != __GCONV_OK)
e354a5
 	{
e354a5
 	  if (errno == EINVAL)
e354a5
 	    {
e354a5
@@ -221,7 +204,7 @@ main (int argc, char *argv[])
e354a5
 	      const char *from_pretty =
e354a5
 		(from_code[0] ? from_code : nl_langinfo (CODESET));
e354a5
 	      const char *to_pretty =
e354a5
-		(orig_to_code[0] ? orig_to_code : nl_langinfo (CODESET));
e354a5
+		(to_code[0] ? to_code : nl_langinfo (CODESET));
e354a5
 
e354a5
 	      if (from_wrong)
e354a5
 		{
e354a5
diff --git a/iconv/tst-iconv-opt.c b/iconv/tst-iconv-opt.c
e354a5
new file mode 100644
e354a5
index 0000000000000000..669d812a6a9b8749
e354a5
--- /dev/null
e354a5
+++ b/iconv/tst-iconv-opt.c
e354a5
@@ -0,0 +1,347 @@
e354a5
+/* Test iconv's TRANSLIT and IGNORE option handling
e354a5
+
e354a5
+   Copyright (C) 2020 Free Software Foundation, Inc.
e354a5
+   This file is part of the GNU C Library.
e354a5
+
e354a5
+   The GNU C Library is free software; you can redistribute it and/or
e354a5
+   modify it under the terms of the GNU Lesser General Public
e354a5
+   License as published by the Free Software Foundation; either
e354a5
+   version 2.1 of the License, or (at your option) any later version.
e354a5
+
e354a5
+   The GNU C Library is distributed in the hope that it will be useful,
e354a5
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
e354a5
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
e354a5
+   Lesser General Public License for more details.
e354a5
+
e354a5
+   You should have received a copy of the GNU Lesser General Public
e354a5
+   License along with the GNU C Library; if not, see
e354a5
+   <https://www.gnu.org/licenses/>.  */
e354a5
+
e354a5
+
e354a5
+#include <iconv.h>
e354a5
+#include <locale.h>
e354a5
+#include <errno.h>
e354a5
+#include <string.h>
e354a5
+#include <support/support.h>
e354a5
+#include <support/check.h>
e354a5
+
e354a5
+
e354a5
+/* Run one iconv test.  Arguments:
e354a5
+   to: destination character set and options
e354a5
+   from: source character set
e354a5
+   input: input string to be converted
e354a5
+   exp_in: expected number of bytes consumed
e354a5
+   exp_ret: expected return value (error or number of irreversible conversions)
e354a5
+   exp_out: expected output string
e354a5
+   exp_err: expected value of `errno' after iconv returns.  */
e354a5
+static void
e354a5
+test_iconv (const char *to, const char *from, char *input, size_t exp_in,
e354a5
+            size_t exp_ret, const char *exp_out, int exp_err)
e354a5
+{
e354a5
+  iconv_t cd;
e354a5
+  char outbuf[500];
e354a5
+  size_t inlen, outlen;
e354a5
+  char *inptr, *outptr;
e354a5
+  size_t n;
e354a5
+
e354a5
+  cd = iconv_open (to, from);
e354a5
+  TEST_VERIFY (cd != (iconv_t) -1);
e354a5
+
e354a5
+  inlen = strlen (input);
e354a5
+  outlen = sizeof (outbuf);
e354a5
+  inptr = input;
e354a5
+  outptr = outbuf;
e354a5
+
e354a5
+  errno = 0;
e354a5
+  n = iconv (cd, &inptr, &inlen, &outptr, &outlen);
e354a5
+
e354a5
+  TEST_COMPARE (n, exp_ret);
e354a5
+  TEST_VERIFY (inptr == input + exp_in);
e354a5
+  TEST_COMPARE (errno, exp_err);
e354a5
+  TEST_COMPARE_BLOB (outbuf, outptr - outbuf, exp_out, strlen (exp_out));
e354a5
+  TEST_VERIFY (iconv_close (cd) == 0);
e354a5
+}
e354a5
+
e354a5
+
e354a5
+/* We test option parsing by converting UTF-8 inputs to ASCII under various
e354a5
+   option combinations. The UTF-8 inputs fall into three categories:
e354a5
+   - ASCII-only,
e354a5
+   - non-ASCII,
e354a5
+   - non-ASCII with invalid UTF-8 characters.  */
e354a5
+
e354a5
+/* 1.  */
e354a5
+char ascii[] = "Just some ASCII text";
e354a5
+
e354a5
+/* 2. Valid UTF-8 input and some corresponding expected outputs with various
e354a5
+   options.  The two non-ASCII characters below are accented alphabets:
e354a5
+   an `a' then an `o'.  */
e354a5
+char utf8[] = "UTF-8 text with \u00E1 couple \u00F3f non-ASCII characters";
e354a5
+char u2a[] = "UTF-8 text with ";
e354a5
+char u2a_translit[] = "UTF-8 text with a couple of non-ASCII characters";
e354a5
+char u2a_ignore[] = "UTF-8 text with  couple f non-ASCII characters";
e354a5
+
e354a5
+/* 3. Invalid UTF-8 input and some corresponding expected outputs.  \xff is
e354a5
+   invalid UTF-8. It's followed by some valid but non-ASCII UTF-8.  */
e354a5
+char iutf8[] = "Invalid UTF-8 \xff\u27E6text\u27E7";
e354a5
+char iu2a[] = "Invalid UTF-8 ";
e354a5
+char iu2a_ignore[] = "Invalid UTF-8 text";
e354a5
+char iu2a_both[] = "Invalid UTF-8 [|text|]";
e354a5
+
e354a5
+/* 4. Another invalid UTF-8 input and corresponding expected outputs. This time
e354a5
+   the valid non-ASCII UTF-8 characters appear before the invalid \xff.  */
e354a5
+char jutf8[] = "Invalid \u27E6UTF-8\u27E7 \xfftext";
e354a5
+char ju2a[] = "Invalid ";
e354a5
+char ju2a_translit[] = "Invalid [|UTF-8|] ";
e354a5
+char ju2a_ignore[] = "Invalid UTF-8 text";
e354a5
+char ju2a_both[] = "Invalid [|UTF-8|] text";
e354a5
+
e354a5
+/* We also test option handling for character set names that have the form
e354a5
+   "A/B".  In this test, we test conversions "ISO-10646/UTF-8", and either
e354a5
+   ISO-8859-1 or ASCII.  */
e354a5
+
e354a5
+/* 5. Accented 'A' and 'a' characters in ISO-8859-1 and UTF-8, and an
e354a5
+   equivalent ASCII transliteration.  */
e354a5
+char iso8859_1_a[] = {0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, /* Accented A's.  */
e354a5
+                      0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, /* Accented a's.  */
e354a5
+                      0x00};
e354a5
+char utf8_a[] = "\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5"
e354a5
+                "\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5";
e354a5
+char ascii_a[] = "AAAAAAaaaaaa";
e354a5
+
e354a5
+/* 6. An invalid ASCII string where [0] is invalid and [1] is '~'.  */
e354a5
+char iascii [] = {0x80, '~', '\0'};
e354a5
+char empty[] = "";
e354a5
+char ia2u_ignore[] = "~";
e354a5
+
e354a5
+static int
e354a5
+do_test (void)
e354a5
+{
e354a5
+  xsetlocale (LC_ALL, "en_US.UTF-8");
e354a5
+
e354a5
+
e354a5
+  /* 0. iconv_open should gracefully fail for invalid character sets.  */
e354a5
+
e354a5
+  TEST_VERIFY (iconv_open ("INVALID", "UTF-8") == (iconv_t) -1);
e354a5
+  TEST_VERIFY (iconv_open ("UTF-8", "INVALID") == (iconv_t) -1);
e354a5
+  TEST_VERIFY (iconv_open ("INVALID", "INVALID") == (iconv_t) -1);
e354a5
+
e354a5
+
e354a5
+  /* 1. ASCII-only UTF-8 input should convert to ASCII with no changes:  */
e354a5
+
e354a5
+  test_iconv ("ASCII", "UTF-8", ascii, strlen (ascii), 0, ascii, 0);
e354a5
+  test_iconv ("ASCII//", "UTF-8", ascii, strlen (ascii), 0, ascii, 0);
e354a5
+  test_iconv ("ASCII//TRANSLIT", "UTF-8", ascii, strlen (ascii), 0, ascii, 0);
e354a5
+  test_iconv ("ASCII//TRANSLIT//", "UTF-8", ascii, strlen (ascii), 0, ascii,
e354a5
+              0);
e354a5
+  test_iconv ("ASCII//IGNORE", "UTF-8", ascii, strlen (ascii), 0, ascii, 0);
e354a5
+  test_iconv ("ASCII//IGNORE//", "UTF-8", ascii, strlen (ascii), 0, ascii, 0);
e354a5
+
e354a5
+
e354a5
+  /* 2. Valid UTF-8 input with non-ASCII characters:  */
e354a5
+
e354a5
+  /* EILSEQ when converted to ASCII.  */
e354a5
+  test_iconv ("ASCII", "UTF-8", utf8, strlen (u2a), (size_t) -1, u2a, EILSEQ);
e354a5
+
e354a5
+  /* Converted without error with TRANSLIT enabled.  */
e354a5
+  test_iconv ("ASCII//TRANSLIT", "UTF-8", utf8, strlen (utf8), 2, u2a_translit,
e354a5
+              0);
e354a5
+
e354a5
+  /* EILSEQ with IGNORE enabled.  Non-ASCII chars dropped from output.  */
e354a5
+  test_iconv ("ASCII//IGNORE", "UTF-8", utf8, strlen (utf8), (size_t) -1,
e354a5
+              u2a_ignore, EILSEQ);
e354a5
+
e354a5
+  /* With TRANSLIT and IGNORE enabled, transliterated without error.  We test
e354a5
+     four combinations.  */
e354a5
+
e354a5
+  test_iconv ("ASCII//TRANSLIT,IGNORE", "UTF-8", utf8, strlen (utf8), 2,
e354a5
+              u2a_translit, 0);
e354a5
+  test_iconv ("ASCII//TRANSLIT//IGNORE", "UTF-8", utf8, strlen (utf8), 2,
e354a5
+              u2a_translit, 0);
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT", "UTF-8", utf8, strlen (utf8), 2,
e354a5
+              u2a_translit, 0);
e354a5
+  /* Due to bug 19519, iconv was ignoring TRANSLIT for the following input.  */
e354a5
+  test_iconv ("ASCII//IGNORE//TRANSLIT", "UTF-8", utf8, strlen (utf8), 2,
e354a5
+              u2a_translit, 0);
e354a5
+
e354a5
+  /* Misspellings of TRANSLIT and IGNORE are ignored, but conversion still
e354a5
+     works while respecting any other correctly spelled options.  */
e354a5
+
e354a5
+  test_iconv ("ASCII//T", "UTF-8", utf8, strlen (u2a), (size_t) -1, u2a,
e354a5
+              EILSEQ);
e354a5
+  test_iconv ("ASCII//TRANSLITERATE", "UTF-8", utf8, strlen (u2a), (size_t) -1,
e354a5
+              u2a, EILSEQ);
e354a5
+  test_iconv ("ASCII//I", "UTF-8", utf8, strlen (u2a), (size_t) -1, u2a,
e354a5
+              EILSEQ);
e354a5
+  test_iconv ("ASCII//IGNORED", "UTF-8", utf8, strlen (u2a), (size_t) -1, u2a,
e354a5
+              EILSEQ);
e354a5
+  test_iconv ("ASCII//TRANSLITERATE//IGNORED", "UTF-8", utf8, strlen (u2a),
e354a5
+              (size_t) -1, u2a, EILSEQ);
e354a5
+  test_iconv ("ASCII//IGNORED,TRANSLITERATE", "UTF-8", utf8, strlen (u2a),
e354a5
+              (size_t) -1, u2a, EILSEQ);
e354a5
+  test_iconv ("ASCII//T//I", "UTF-8", utf8, strlen (u2a), (size_t) -1, u2a,
e354a5
+              EILSEQ);
e354a5
+
e354a5
+  test_iconv ("ASCII//TRANSLIT//I", "UTF-8", utf8, strlen (utf8), 2,
e354a5
+              u2a_translit, 0);
e354a5
+  /* Due to bug 19519, iconv was ignoring TRANSLIT for the following input.  */
e354a5
+  test_iconv ("ASCII//I//TRANSLIT", "UTF-8", utf8, strlen (utf8), 2,
e354a5
+              u2a_translit, 0);
e354a5
+  test_iconv ("ASCII//IGNORED,TRANSLIT", "UTF-8", utf8, strlen (utf8), 2,
e354a5
+              u2a_translit, 0);
e354a5
+  test_iconv ("ASCII//TRANSLIT,IGNORED", "UTF-8", utf8, strlen (utf8), 2,
e354a5
+              u2a_translit, 0);
e354a5
+
e354a5
+  test_iconv ("ASCII//IGNORE,T", "UTF-8", utf8, strlen (utf8), (size_t) -1,
e354a5
+              u2a_ignore, EILSEQ);
e354a5
+  test_iconv ("ASCII//T,IGNORE", "UTF-8", utf8, strlen (utf8), (size_t) -1,
e354a5
+              u2a_ignore, EILSEQ);
e354a5
+  /* Due to bug 19519, iconv was ignoring IGNORE for the following input.  */
e354a5
+  test_iconv ("ASCII//TRANSLITERATE//IGNORE", "UTF-8", utf8, strlen (utf8),
e354a5
+              (size_t) -1, u2a_ignore, EILSEQ);
e354a5
+  test_iconv ("ASCII//IGNORE//TRANSLITERATE", "UTF-8", utf8, strlen (utf8),
e354a5
+              (size_t) -1, u2a_ignore, EILSEQ);
e354a5
+
e354a5
+
e354a5
+  /* 3. Invalid UTF-8 followed by some valid non-ASCII UTF-8 characters:  */
e354a5
+
e354a5
+  /* EILSEQ; output is truncated at the first invalid UTF-8 character.  */
e354a5
+  test_iconv ("ASCII", "UTF-8", iutf8, strlen (iu2a), (size_t) -1, iu2a,
e354a5
+              EILSEQ);
e354a5
+
e354a5
+  /* With TRANSLIT enabled: EILSEQ; output still truncated at the first invalid
e354a5
+     UTF-8 character.  */
e354a5
+  test_iconv ("ASCII//TRANSLIT", "UTF-8", iutf8, strlen (iu2a), (size_t) -1,
e354a5
+              iu2a, EILSEQ);
e354a5
+
e354a5
+  /* With IGNORE enabled: EILSEQ; output omits invalid UTF-8 characters and
e354a5
+     valid UTF-8 non-ASCII characters.  */
e354a5
+  test_iconv ("ASCII//IGNORE", "UTF-8", iutf8, strlen (iutf8), (size_t) -1,
e354a5
+              iu2a_ignore, EILSEQ);
e354a5
+
e354a5
+  /* With TRANSLIT and IGNORE enabled, output omits only invalid UTF-8
e354a5
+     characters and transliterates valid non-ASCII UTF-8 characters.  We test
e354a5
+     four combinations.  */
e354a5
+
e354a5
+  test_iconv ("ASCII//TRANSLIT,IGNORE", "UTF-8", iutf8, strlen (iutf8), 2,
e354a5
+              iu2a_both, 0);
e354a5
+  /* Due to bug 19519, iconv was ignoring IGNORE for the following input.  */
e354a5
+  test_iconv ("ASCII//TRANSLIT//IGNORE", "UTF-8", iutf8, strlen (iutf8), 2,
e354a5
+              iu2a_both, 0);
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT", "UTF-8", iutf8, strlen (iutf8), 2,
e354a5
+              iu2a_both, 0);
e354a5
+  /* Due to bug 19519, iconv was ignoring TRANSLIT for the following input.  */
e354a5
+  test_iconv ("ASCII//IGNORE//TRANSLIT", "UTF-8", iutf8, strlen (iutf8), 2,
e354a5
+              iu2a_both, 0);
e354a5
+
e354a5
+
e354a5
+  /* 4. Invalid UTF-8 with valid non-ASCII UTF-8 chars appearing first:  */
e354a5
+
e354a5
+  /* EILSEQ; output is truncated at the first non-ASCII character.  */
e354a5
+  test_iconv ("ASCII", "UTF-8", jutf8, strlen (ju2a), (size_t) -1, ju2a,
e354a5
+              EILSEQ);
e354a5
+
e354a5
+  /* With TRANSLIT enabled: EILSEQ; output now truncated at the first invalid
e354a5
+     UTF-8 character.  */
e354a5
+  test_iconv ("ASCII//TRANSLIT", "UTF-8", jutf8, strlen (jutf8) - 5,
e354a5
+              (size_t) -1, ju2a_translit, EILSEQ);
e354a5
+  test_iconv ("ASCII//translit", "UTF-8", jutf8, strlen (jutf8) - 5,
e354a5
+              (size_t) -1, ju2a_translit, EILSEQ);
e354a5
+
e354a5
+  /* With IGNORE enabled: EILSEQ; output omits invalid UTF-8 characters and
e354a5
+     valid UTF-8 non-ASCII characters.  */
e354a5
+  test_iconv ("ASCII//IGNORE", "UTF-8", jutf8, strlen (jutf8), (size_t) -1,
e354a5
+              ju2a_ignore, EILSEQ);
e354a5
+  test_iconv ("ASCII//ignore", "UTF-8", jutf8, strlen (jutf8), (size_t) -1,
e354a5
+              ju2a_ignore, EILSEQ);
e354a5
+
e354a5
+  /* With TRANSLIT and IGNORE enabled, output omits only invalid UTF-8
e354a5
+     characters and transliterates valid non-ASCII UTF-8 characters.  We test
e354a5
+     several combinations.  */
e354a5
+
e354a5
+  test_iconv ("ASCII//TRANSLIT,IGNORE", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  /* Due to bug 19519, iconv was ignoring IGNORE for the following input.  */
e354a5
+  test_iconv ("ASCII//TRANSLIT//IGNORE", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  /* Due to bug 19519, iconv was ignoring TRANSLIT for the following input.  */
e354a5
+  test_iconv ("ASCII//IGNORE//TRANSLIT", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  test_iconv ("ASCII//translit,ignore", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  /* Trailing whitespace and separators should be ignored.  */
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT ", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT/", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT//", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT,", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT,,", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+  test_iconv ("ASCII//IGNORE,TRANSLIT /,", "UTF-8", jutf8, strlen (jutf8), 2,
e354a5
+              ju2a_both, 0);
e354a5
+
e354a5
+  /* TRANSLIT or IGNORE suffixes in fromcode should be ignored.  */
e354a5
+  test_iconv ("ASCII", "UTF-8//TRANSLIT", jutf8, strlen (ju2a), (size_t) -1,
e354a5
+              ju2a, EILSEQ);
e354a5
+  test_iconv ("ASCII", "UTF-8//IGNORE", jutf8, strlen (ju2a), (size_t) -1,
e354a5
+              ju2a, EILSEQ);
e354a5
+  test_iconv ("ASCII", "UTF-8//TRANSLIT,IGNORE", jutf8, strlen (ju2a),
e354a5
+              (size_t) -1, ju2a, EILSEQ);
e354a5
+
e354a5
+
e354a5
+  /* 5. Charset names of the form "A/B/":  */
e354a5
+
e354a5
+  /* ISO-8859-1 is converted to UTF-8 without needing transliteration.  */
e354a5
+  test_iconv ("ISO-10646/UTF-8", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+  test_iconv ("ISO-10646/UTF-8/", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+  test_iconv ("ISO-10646/UTF-8/IGNORE", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+  test_iconv ("ISO-10646/UTF-8//IGNORE", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+  test_iconv ("ISO-10646/UTF-8/TRANSLIT", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+  test_iconv ("ISO-10646/UTF-8//TRANSLIT", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+  test_iconv ("ISO-10646/UTF-8//TRANSLIT/IGNORE", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+  test_iconv ("ISO-10646/UTF-8//TRANSLIT//IGNORE", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+  test_iconv ("ISO-10646/UTF-8/TRANSLIT,IGNORE", "ISO-8859-1", iso8859_1_a,
e354a5
+              strlen (iso8859_1_a), 0, utf8_a, 0);
e354a5
+
e354a5
+  /* UTF-8 with accented A's is converted to ASCII with transliteration.  */
e354a5
+  test_iconv ("ASCII", "ISO-10646/UTF-8", utf8_a,
e354a5
+              0, (size_t) -1, empty, EILSEQ);
e354a5
+  test_iconv ("ASCII//IGNORE", "ISO-10646/UTF-8", utf8_a,
e354a5
+              strlen (utf8_a), (size_t) -1, empty, EILSEQ);
e354a5
+  test_iconv ("ASCII//TRANSLIT", "ISO-10646/UTF-8", utf8_a,
e354a5
+              strlen (utf8_a), 12, ascii_a, 0);
e354a5
+
e354a5
+  /* Invalid ASCII is converted to UTF-8 only with IGNORE.  */
e354a5
+  test_iconv ("ISO-10646/UTF-8", "ASCII", iascii, strlen (empty), (size_t) -1,
e354a5
+              empty, EILSEQ);
e354a5
+  test_iconv ("ISO-10646/UTF-8/TRANSLIT", "ASCII", iascii, strlen (empty),
e354a5
+              (size_t) -1, empty, EILSEQ);
e354a5
+  test_iconv ("ISO-10646/UTF-8/IGNORE", "ASCII", iascii, strlen (iascii),
e354a5
+              (size_t) -1, ia2u_ignore, EILSEQ);
e354a5
+  test_iconv ("ISO-10646/UTF-8/TRANSLIT,IGNORE", "ASCII", iascii,
e354a5
+              strlen (iascii), (size_t) -1, ia2u_ignore, EILSEQ);
e354a5
+  /* Due to bug 19519, iconv was ignoring IGNORE for the following three
e354a5
+     inputs: */
e354a5
+  test_iconv ("ISO-10646/UTF-8/TRANSLIT/IGNORE", "ASCII", iascii,
e354a5
+              strlen (iascii), (size_t) -1, ia2u_ignore, EILSEQ);
e354a5
+  test_iconv ("ISO-10646/UTF-8//TRANSLIT,IGNORE", "ASCII", iascii,
e354a5
+              strlen (iascii), (size_t) -1, ia2u_ignore, EILSEQ);
e354a5
+  test_iconv ("ISO-10646/UTF-8//TRANSLIT//IGNORE", "ASCII", iascii,
e354a5
+              strlen (iascii), (size_t) -1, ia2u_ignore, EILSEQ);
e354a5
+
e354a5
+  return 0;
e354a5
+}
e354a5
+
e354a5
+#include <support/test-driver.c>
e354a5
diff --git a/iconv/tst-iconv_prog.sh b/iconv/tst-iconv_prog.sh
e354a5
new file mode 100644
e354a5
index 0000000000000000..8298136b7f45d855
e354a5
--- /dev/null
e354a5
+++ b/iconv/tst-iconv_prog.sh
e354a5
@@ -0,0 +1,280 @@
e354a5
+#!/bin/bash
e354a5
+# Test for some known iconv(1) hangs from bug 19519, and miscellaneous
e354a5
+# iconv(1) program error conditions.
e354a5
+# Copyright (C) 2020 Free Software Foundation, Inc.
e354a5
+# This file is part of the GNU C Library.
e354a5
+
e354a5
+# The GNU C Library is free software; you can redistribute it and/or
e354a5
+# modify it under the terms of the GNU Lesser General Public
e354a5
+# License as published by the Free Software Foundation; either
e354a5
+# version 2.1 of the License, or (at your option) any later version.
e354a5
+
e354a5
+# The GNU C Library is distributed in the hope that it will be useful,
e354a5
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
e354a5
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
e354a5
+# Lesser General Public License for more details.
e354a5
+
e354a5
+# You should have received a copy of the GNU Lesser General Public
e354a5
+# License along with the GNU C Library; if not, see
e354a5
+# <https://www.gnu.org/licenses/>.
e354a5
+
e354a5
+codir=$1
e354a5
+test_wrapper_env="$2"
e354a5
+run_program_env="$3"
e354a5
+
e354a5
+# We have to have some directories in the library path.
e354a5
+LIBPATH=$codir:$codir/iconvdata
e354a5
+
e354a5
+# How the start the iconv(1) program.  $from is not defined/expanded yet.
e354a5
+ICONV='
e354a5
+$codir/elf/ld.so --library-path $LIBPATH --inhibit-rpath ${from}.so
e354a5
+$codir/iconv/iconv_prog
e354a5
+'
e354a5
+ICONV="$test_wrapper_env $run_program_env $ICONV"
e354a5
+
e354a5
+# List of known hangs;
e354a5
+# Gathered by running an exhaustive 2 byte input search against glibc-2.28
e354a5
+hangarray=(
e354a5
+"\x00\x23;-c;ANSI_X3.110;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xa1;-c;ARMSCII-8;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xa1;-c;ASMO_449;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;BIG5;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xff;-c;BIG5HKSCS;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xff;-c;BRF;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xff;-c;BS_4730;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;CP1250;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x98;-c;CP1251;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;CP1252;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;CP1253;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;CP1254;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;CP1255;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;CP1257;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;CP1258;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;CP932;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;CSA_Z243.4-1985-1;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;CSA_Z243.4-1985-2;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;DEC-MCS;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;DIN_66003;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;DS_2089;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-AT-DE;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-AT-DE-A;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-CA-FR;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-DK-NO;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-DK-NO-A;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-ES;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-ES-A;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-ES-S;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-FI-SE;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-FI-SE-A;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-FR;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-IS-FRISS;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-IT;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-PT;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-UK;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;EBCDIC-US;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ES;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ES2;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;EUC-CN;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;EUC-JISX0213;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;EUC-JP;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;EUC-JP-MS;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;EUC-KR;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;EUC-TW;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;GB18030;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;GB_1988-80;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;GBK;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;GOST_19768-74;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;GREEK7;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;GREEK7-OLD;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;GREEK-CCITT;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;HP-GREEK8;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;HP-ROMAN8;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;HP-ROMAN9;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;HP-THAI8;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;HP-TURKISH8;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM038;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;IBM1004;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xff;-c;IBM1008;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;IBM1046;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x51;-c;IBM1132;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xa0;-c;IBM1133;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xce;-c;IBM1137;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;IBM1161;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xdb;-c;IBM1162;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x70;-c;IBM12712;UTF-8//TRANSLIT//IGNORE"
e354a5
+# These are known hangs that are yet to be fixed:
e354a5
+# "\x00\x0f;-c;IBM1364;UTF-8"
e354a5
+# "\x00\x0f;-c;IBM1371;UTF-8"
e354a5
+# "\x00\x0f;-c;IBM1388;UTF-8"
e354a5
+# "\x00\x0f;-c;IBM1390;UTF-8"
e354a5
+# "\x00\x0f;-c;IBM1399;UTF-8"
e354a5
+"\x00\x53;-c;IBM16804;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM274;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM275;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM281;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x57;-c;IBM290;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x45;-c;IBM420;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x68;-c;IBM423;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x70;-c;IBM424;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x53;-c;IBM4517;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x53;-c;IBM4899;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xa5;-c;IBM4909;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xdc;-c;IBM4971;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM803;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x91;-c;IBM851;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x9b;-c;IBM856;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xd5;-c;IBM857;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;IBM864;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x94;-c;IBM868;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x94;-c;IBM869;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;IBM874;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x6a;-c;IBM875;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM880;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;IBM891;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;IBM903;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;IBM904;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM905;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;IBM9066;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x48;-c;IBM918;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x57;-c;IBM930;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;IBM932;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM933;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM935;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM937;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x41;-c;IBM939;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;IBM943;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;INIS;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;INIS-8;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;INIS-CYRILLIC;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xec;-c;ISIRI-3342;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xec;-c;ISO_10367-BOX;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-2022-CN;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-2022-CN-EXT;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-2022-JP;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-2022-JP-2;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-2022-JP-3;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-2022-KR;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO_2033;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO_5427;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO_5427-EXT;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO_5428;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xa4;-c;ISO_6937;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xa0;-c;ISO_6937-2;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-8859-11;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xa5;-c;ISO-8859-3;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-8859-6;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-8859-7;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;ISO-8859-8;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;ISO-IR-197;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;ISO-IR-209;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;IT;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;JIS_C6220-1969-RO;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;JIS_C6229-1984-B;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;JOHAB;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;JUS_I.B1.002;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;KOI-8;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x88;-c;KOI8-T;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;KSC5636;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;LATIN-GREEK;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;LATIN-GREEK-1;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xf6;-c;MAC-IS;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;MSZ_7795.3;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;NATS-DANO;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;NATS-SEFI;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;NC_NC00-10;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;NF_Z_62-010;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;NF_Z_62-010_1973;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;NS_4551-1;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;NS_4551-2;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;PT;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;PT2;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x98;-c;RK1048;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x98;-c;SEN_850200_B;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x98;-c;SEN_850200_C;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;Shift_JISX0213;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x80;-c;SJIS;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x23;-c;T.61-8BIT;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;TIS-620;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;TSCII;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;UHC;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xd8;-c;UNICODE;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xdc;-c;UTF-16;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xdc\x00;-c;UTF-16BE;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\xdc;-c;UTF-16LE;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\xff\xff;-c;UTF-7;UTF-8//TRANSLIT//IGNORE"
e354a5
+"\x00\x81;-c;WIN-SAMI-2;UTF-8//TRANSLIT//IGNORE"
e354a5
+)
e354a5
+
e354a5
+# List of option combinations that *should* lead to an error
e354a5
+errorarray=(
e354a5
+# Converting from/to invalid character sets should cause error
e354a5
+"\x00\x00;;INVALID;INVALID"
e354a5
+"\x00\x00;;INVALID;UTF-8"
e354a5
+"\x00\x00;;UTF-8;INVALID"
e354a5
+)
e354a5
+
e354a5
+# Requires $twobyte input, $c flag, $from, and $to to be set; sets $ret
e354a5
+execute_test ()
e354a5
+{
e354a5
+  eval PROG=\"$ICONV\"
e354a5
+  echo -en "$twobyte" \
e354a5
+    | timeout -k 4 3 $PROG $c -f $from -t "$to" &>/dev/null
e354a5
+  ret=$?
e354a5
+}
e354a5
+
e354a5
+check_hangtest_result ()
e354a5
+{
e354a5
+  if [ "$ret" -eq "124" ] || [ "$ret" -eq "137" ]; then # timeout/hang
e354a5
+    result="HANG"
e354a5
+  else
e354a5
+    if [ "$ret" -eq "139" ]; then # segfault
e354a5
+      result="SEGFAULT"
e354a5
+    else
e354a5
+      if [ "$ret" -gt "127" ]; then # unexpected error
e354a5
+        result="UNEXPECTED"
e354a5
+      else
e354a5
+        result="OK"
e354a5
+      fi
e354a5
+    fi
e354a5
+  fi
e354a5
+
e354a5
+  echo -n "$result: from: \"$from\", to: \"$to\","
e354a5
+  echo    " input \"$twobyte\", flags \"$c\""
e354a5
+
e354a5
+  if [ "$result" != "OK" ]; then
e354a5
+    exit 1
e354a5
+  fi
e354a5
+}
e354a5
+
e354a5
+for hangcommand in "${hangarray[@]}"; do
e354a5
+  twobyte="$(echo "$hangcommand" | cut -d";" -f 1)"
e354a5
+  c="$(echo "$hangcommand" | cut -d";" -f 2)"
e354a5
+  from="$(echo "$hangcommand" | cut -d";" -f 3)"
e354a5
+  to="$(echo "$hangcommand" | cut -d";" -f 4)"
e354a5
+  execute_test
e354a5
+  check_hangtest_result
e354a5
+done
e354a5
+
e354a5
+check_errtest_result ()
e354a5
+{
e354a5
+  if [ "$ret" -eq "1" ]; then # we errored out as expected
e354a5
+    result="PASS"
e354a5
+  else
e354a5
+    result="FAIL"
e354a5
+  fi
e354a5
+  echo -n "$result: from: \"$from\", to: \"$to\","
e354a5
+  echo    " input \"$twobyte\", flags \"$c\", return code $ret"
e354a5
+
e354a5
+  if [ "$result" != "PASS" ]; then
e354a5
+    exit 1
e354a5
+  fi
e354a5
+}
e354a5
+
e354a5
+for errorcommand in "${errorarray[@]}"; do
e354a5
+  twobyte="$(echo "$errorcommand" | cut -d";" -f 1)"
e354a5
+  c="$(echo "$errorcommand" | cut -d";" -f 2)"
e354a5
+  from="$(echo "$errorcommand" | cut -d";" -f 3)"
e354a5
+  to="$(echo "$errorcommand" | cut -d";" -f 4)"
e354a5
+  execute_test
e354a5
+  check_errtest_result
e354a5
+done
e354a5
diff --git a/intl/dcigettext.c b/intl/dcigettext.c
e354a5
index 25f47c5bd3b0ea04..ed48fc8d3e96c7ba 100644
e354a5
--- a/intl/dcigettext.c
e354a5
+++ b/intl/dcigettext.c
e354a5
@@ -1120,11 +1120,16 @@ _nl_find_msg (struct loaded_l10nfile *domain_file,
e354a5
 		      outcharset = encoding;
e354a5
 
e354a5
 # ifdef _LIBC
e354a5
-		      /* We always want to use transliteration.  */
e354a5
-		      outcharset = norm_add_slashes (outcharset, "TRANSLIT");
e354a5
-		      charset = norm_add_slashes (charset, "");
e354a5
-		      int r = __gconv_open (outcharset, charset, &convd->conv,
e354a5
-					    GCONV_AVOID_NOCONV);
e354a5
+
e354a5
+		      struct gconv_spec conv_spec
e354a5
+		        = { .fromcode = norm_add_slashes (charset, ""),
e354a5
+		            .tocode = norm_add_slashes (outcharset, ""),
e354a5
+		            /* We always want to use transliteration.  */
e354a5
+		            .translit = true,
e354a5
+		            .ignore = false
e354a5
+		          };
e354a5
+		      int r = __gconv_open (&conv_spec, &convd->conv,
e354a5
+		                            GCONV_AVOID_NOCONV);
e354a5
 		      if (__builtin_expect (r != __GCONV_OK, 0))
e354a5
 			{
e354a5
 			  /* If the output encoding is the same there is