public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 00/26] vfprintf rework to remove vtables
@ 2022-03-17 19:28 Florian Weimer
  2022-03-17 19:28 ` [PATCH 01/26] libio: Convert tst_swprintf to the test framework Florian Weimer
                   ` (25 more replies)
  0 siblings, 26 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:28 UTC (permalink / raw)
  To: libc-alpha

This started out as an attempt to fix bug 27124, the vtable hardening
bypass involving obstack_printf.

The new buffers are modeled after the write pointer/end pointer
abstraction used for inline putc_unlocked.  They have considerably lower
setup overhead than the internal stdio-compatible streams used by
sprintf.  Performance seems to be at least on par with the old
implementation, and a bit better for floating point formatting.

The new internal interfaces are added in commit “stdio-common: Introduce
buffers for implementing printf”.  The bulk of the conversions happen in
“stdio-common: Convert vfprintf and related functions to buffers”.
Initially, I converted things piece by piece, but that required
implementing additional bounce buffers for translation, so this one
commit converts the majority of the functions.  The commit message of
this large commit has some suggestions for review order.

Tested on i686-linux-gnu and x86_64-linux-gnu.  Built with
build-many-glibcs.py.

Thanks,
Florian

Florian Weimer (26):
  libio: Convert tst_swprintf to the test framework
  libio: Flush-only _IO_str_overflow must not return EOF (bug 28949)
  stdio-common: Add wide stream coverage to tst-vfprintf-user-type
  stdio-common: Add tst-printf-width-i18n to cover numeric field width
  vfprintf: Move argument processing into vfprintf-process-arg.c
  vfprintf: Consolidate some multibyte/wide character processing
  __printf_fphex always uses LC_NUMERIC
  stdio-common: Add tst-memstream-string for open_memstream overflow
  stdio-common: Add printf specifier registry to <printf.h>
  stdio-common: Move union printf_arg int <printf.h>
  stdio-common: Simplify printf_unknown interface in vfprintf-internal.c
  locale: Call _nl_unload_locale from _nl_archive_subfreeres
  locale: Remove cleanup function pointer from struct __localedata
  locale: Remove private union from struct __locale_data
  locale: Add more cached data to LC_CTYPE
  locale: Implement struct grouping_iterator
  stdio-common: Introduce buffers for implementing printf
  stdio-common: Add __printf_function_invoke
  stdio-common: Add __translated_number_width
  stdio-common: Convert vfprintf and related functions to buffers
  stdio-common: Add lock optimization to vfprintf and vfwprintf
  libio: Convert __vsprintf_internal to buffers
  libio: Convert __vasprintf_internal to buffers
  libio: Convert __vdprintf_internal to buffers
  libio: Convert __obstack_vprintf_internal to buffers (bug 27124)
  libio: Convert __vswprintf_internal to buffers (bug 27857)

 include/printf.h                              |   79 +-
 include/printf_buffer.h                       |  332 +++++
 libio/iovdprintf.c                            |   69 +-
 libio/iovsprintf.c                            |   70 +-
 libio/obprintf.c                              |  170 +--
 libio/strops.c                                |    5 +-
 libio/tst-vtables-common.c                    |    9 +-
 libio/tst_swprintf.c                          |  110 +-
 libio/vasprintf.c                             |  141 +-
 libio/vsnprintf.c                             |  131 +-
 libio/vswprintf.c                             |  100 +-
 libio/wstrops.c                               |    5 +-
 locale/C-address.c                            |    2 +-
 locale/C-collate.c                            |    2 +-
 locale/C-ctype.c                              |    9 +-
 locale/C-identification.c                     |    2 +-
 locale/C-measurement.c                        |    2 +-
 locale/C-messages.c                           |    2 +-
 locale/C-monetary.c                           |    2 +-
 locale/C-name.c                               |    2 +-
 locale/C-numeric.c                            |    2 +-
 locale/C-paper.c                              |    2 +-
 locale/C-telephone.c                          |    2 +-
 locale/C-time.c                               |    2 +-
 locale/findlocale.c                           |    2 +-
 locale/loadarchive.c                          |    8 +-
 locale/loadlocale.c                           |   89 +-
 locale/localeinfo.h                           |   48 +-
 locale/setlocale.c                            |    2 +-
 manual/stdio.texi                             |    7 +-
 stdio-common/Makefile                         |   29 +-
 stdio-common/Xprintf_buffer_done.c            |   40 +
 stdio-common/Xprintf_buffer_flush.c           |   72 +
 stdio-common/Xprintf_buffer_pad_1.c           |   44 +
 stdio-common/Xprintf_buffer_putc_1.c          |   29 +
 stdio-common/Xprintf_buffer_puts_1.c          |   37 +
 stdio-common/Xprintf_buffer_write.c           |   43 +
 stdio-common/Xprintf_function_invoke.c        |   42 +
 stdio-common/grouping_iterator.c              |  125 ++
 stdio-common/grouping_iterator.h              |   65 +
 stdio-common/printf-parse.h                   |   23 -
 stdio-common/printf_buffer-char.h             |   24 +
 stdio-common/printf_buffer-wchar_t.h          |   24 +
 stdio-common/printf_buffer_as_file.c          |  148 ++
 stdio-common/printf_buffer_as_file.h          |   87 ++
 stdio-common/printf_buffer_done.c             |   21 +
 stdio-common/printf_buffer_flush.c            |   81 ++
 stdio-common/printf_buffer_pad_1.c            |   21 +
 stdio-common/printf_buffer_putc_1.c           |   21 +
 stdio-common/printf_buffer_puts_1.c           |   21 +
 stdio-common/printf_buffer_to_file.c          |  122 ++
 stdio-common/printf_buffer_to_file.h          |   57 +
 stdio-common/printf_buffer_write.c            |   21 +
 stdio-common/printf_fp.c                      |  736 +++++-----
 stdio-common/printf_fphex.c                   |  271 ++--
 stdio-common/printf_function_invoke.c         |   22 +
 stdio-common/reg-printf.c                     |    7 -
 stdio-common/translated_number_width.c        |   42 +
 stdio-common/tst-grouping_iterator.c          |  262 ++++
 stdio-common/tst-memstream-string.c           |   85 ++
 stdio-common/tst-printf-width-i18n.c          |   95 ++
 stdio-common/tst-vfprintf-user-type.c         |  128 +-
 stdio-common/vfprintf-internal.c              | 1189 +++--------------
 stdio-common/vfprintf-process-arg.c           |  489 +++++++
 stdio-common/wprintf_buffer_as_file.c         |  153 +++
 stdio-common/wprintf_buffer_done.c            |   21 +
 stdio-common/wprintf_buffer_flush.c           |   42 +
 stdio-common/wprintf_buffer_pad_1.c           |   21 +
 stdio-common/wprintf_buffer_putc_1.c          |   21 +
 stdio-common/wprintf_buffer_puts_1.c          |   21 +
 stdio-common/wprintf_buffer_to_file.c         |   55 +
 stdio-common/wprintf_buffer_write.c           |   21 +
 stdio-common/wprintf_function_invoke.c        |   22 +
 stdlib/strfmon_l.c                            |  196 ++-
 stdlib/strfrom-skeleton.c                     |   38 +-
 sysdeps/ia64/fpu/printf_fphex.c               |    8 +-
 .../ieee754/ldbl-128/printf_fphex_macros.h    |   36 +-
 sysdeps/ieee754/ldbl-128ibm/printf_fphex.c    |   36 +-
 sysdeps/ieee754/ldbl-96/printf_fphex.c        |   22 +-
 sysdeps/x86_64/fpu/printf_fphex.c             |   21 +-
 time/alt_digit.c                              |   49 +-
 time/era.c                                    |   49 +-
 time/lc-time-cleanup.c                        |    5 +-
 wcsmbs/wcsmbsload.c                           |   27 +-
 wcsmbs/wcsmbsload.h                           |   11 +-
 85 files changed, 4275 insertions(+), 2531 deletions(-)
 create mode 100644 include/printf_buffer.h
 create mode 100644 stdio-common/Xprintf_buffer_done.c
 create mode 100644 stdio-common/Xprintf_buffer_flush.c
 create mode 100644 stdio-common/Xprintf_buffer_pad_1.c
 create mode 100644 stdio-common/Xprintf_buffer_putc_1.c
 create mode 100644 stdio-common/Xprintf_buffer_puts_1.c
 create mode 100644 stdio-common/Xprintf_buffer_write.c
 create mode 100644 stdio-common/Xprintf_function_invoke.c
 create mode 100644 stdio-common/grouping_iterator.c
 create mode 100644 stdio-common/grouping_iterator.h
 create mode 100644 stdio-common/printf_buffer-char.h
 create mode 100644 stdio-common/printf_buffer-wchar_t.h
 create mode 100644 stdio-common/printf_buffer_as_file.c
 create mode 100644 stdio-common/printf_buffer_as_file.h
 create mode 100644 stdio-common/printf_buffer_done.c
 create mode 100644 stdio-common/printf_buffer_flush.c
 create mode 100644 stdio-common/printf_buffer_pad_1.c
 create mode 100644 stdio-common/printf_buffer_putc_1.c
 create mode 100644 stdio-common/printf_buffer_puts_1.c
 create mode 100644 stdio-common/printf_buffer_to_file.c
 create mode 100644 stdio-common/printf_buffer_to_file.h
 create mode 100644 stdio-common/printf_buffer_write.c
 create mode 100644 stdio-common/printf_function_invoke.c
 create mode 100644 stdio-common/translated_number_width.c
 create mode 100644 stdio-common/tst-grouping_iterator.c
 create mode 100644 stdio-common/tst-memstream-string.c
 create mode 100644 stdio-common/tst-printf-width-i18n.c
 create mode 100644 stdio-common/vfprintf-process-arg.c
 create mode 100644 stdio-common/wprintf_buffer_as_file.c
 create mode 100644 stdio-common/wprintf_buffer_done.c
 create mode 100644 stdio-common/wprintf_buffer_flush.c
 create mode 100644 stdio-common/wprintf_buffer_pad_1.c
 create mode 100644 stdio-common/wprintf_buffer_putc_1.c
 create mode 100644 stdio-common/wprintf_buffer_puts_1.c
 create mode 100644 stdio-common/wprintf_buffer_to_file.c
 create mode 100644 stdio-common/wprintf_buffer_write.c
 create mode 100644 stdio-common/wprintf_function_invoke.c


base-commit: e297d33c73aa22c86228170343c7b2f9bfac6f1e
-- 
2.35.1


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 01/26] libio: Convert tst_swprintf to the test framework
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
@ 2022-03-17 19:28 ` Florian Weimer
  2022-03-18 17:40   ` Adhemerval Zanella
  2022-03-17 19:28 ` [PATCH 02/26] libio: Flush-only _IO_str_overflow must not return EOF (bug 28949) Florian Weimer
                   ` (24 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:28 UTC (permalink / raw)
  To: libc-alpha

And increase test coverage slightly.
---
 libio/tst_swprintf.c | 79 ++++++++++++++++----------------------------
 1 file changed, 28 insertions(+), 51 deletions(-)

diff --git a/libio/tst_swprintf.c b/libio/tst_swprintf.c
index e4bd7f022a..5ede402fff 100644
--- a/libio/tst_swprintf.c
+++ b/libio/tst_swprintf.c
@@ -1,10 +1,11 @@
+#include <array_length.h>
 #include <stdio.h>
-#include <wchar.h>
+#include <support/check.h>
 #include <sys/types.h>
+#include <wchar.h>
 
 
 static wchar_t buf[100];
-#define nbuf (sizeof (buf) / sizeof (buf[0]))
 static const struct
 {
   size_t n;
@@ -12,81 +13,57 @@ static const struct
   ssize_t exp;
 } tests[] =
   {
-    { nbuf, "hello world", 11 },
+    { array_length (buf), "hello world", 11 },
     { 0, "hello world", -1 },
+    { 1, "hello world", -1 },
+    { 2, "hello world", -1 },
+    { 11, "hello world", -1 },
+    { 12, "hello world", 11 },
     { 0, "", -1 },
-    { nbuf, "", 0 }
+    { array_length (buf), "", 0 }
   };
 
-int
-main (int argc, char *argv[])
+static int
+do_test (void)
 {
   size_t n;
-  int result = 0;
 
-  puts ("test 1");
-  n = swprintf (buf, nbuf, L"Hello %s", "world");
-  if (n != 11)
-    {
-      printf ("incorrect return value: %zd instead of 11\n", n);
-      result = 1;
-    }
-  else if (wcscmp (buf, L"Hello world") != 0)
-    {
-      printf ("incorrect string: L\"%ls\" instead of L\"Hello world\"\n", buf);
-      result = 1;
-    }
+  TEST_COMPARE (swprintf (buf, array_length (buf), L"Hello %s", "world"), 11);
+  TEST_COMPARE_STRING_WIDE (buf, L"Hello world");
 
-  puts ("test 2");
-  n = swprintf (buf, nbuf, L"Is this >%g< 3.1?", 3.1);
-  if (n != 18)
-    {
-      printf ("incorrect return value: %zd instead of 18\n", n);
-      result = 1;
-    }
-  else if (wcscmp (buf, L"Is this >3.1< 3.1?") != 0)
-    {
-      printf ("incorrect string: L\"%ls\" instead of L\"Is this >3.1< 3.1?\"\n",
-	      buf);
-      result = 1;
-    }
+  TEST_COMPARE (swprintf (buf, array_length (buf), L"Is this >%g< 3.1?", 3.1),
+		18);
+  TEST_COMPARE_STRING_WIDE (buf, L"Is this >3.1< 3.1?");
 
-  for (n = 0; n < sizeof (tests) / sizeof (tests[0]); ++n)
+  for (n = 0; n < array_length(tests); ++n)
     {
       ssize_t res = swprintf (buf, tests[n].n, L"%s", tests[n].str);
 
       if (tests[n].exp < 0 && res >= 0)
 	{
+	  support_record_failure ();
 	  printf ("swprintf (buf, %Zu, L\"%%s\", \"%s\") expected to fail\n",
 		  tests[n].n, tests[n].str);
-	  result = 1;
 	}
       else if (tests[n].exp >= 0 && tests[n].exp != res)
 	{
-	  printf ("swprintf (buf, %Zu, L\"%%s\", \"%s\") expected to return %Zd, but got %Zd\n",
+	  support_record_failure ();
+	  printf ("\
+swprintf (buf, %Zu, L\"%%s\", \"%s\") expected to return %Zd, but got %Zd\n",
 		  tests[n].n, tests[n].str, tests[n].exp, res);
-	  result = 1;
 	}
       else
 	printf ("swprintf (buf, %Zu, L\"%%s\", \"%s\") OK\n",
 		tests[n].n, tests[n].str);
     }
 
-  if (swprintf (buf, nbuf, L"%.0s", "foo") != 0
-      || wcslen (buf) != 0)
-    {
-      printf ("swprintf (buf, %Zu, L\"%%.0s\", \"foo\") create some output\n",
-	      nbuf);
-      result = 1;
-    }
+  TEST_COMPARE (swprintf (buf, array_length (buf), L"%.0s", "foo"), 0);
+  TEST_COMPARE_STRING_WIDE (buf, L"");
 
-  if (swprintf (buf, nbuf, L"%.0ls", L"foo") != 0
-      || wcslen (buf) != 0)
-    {
-      printf ("swprintf (buf, %Zu, L\"%%.0ls\", L\"foo\") create some output\n",
-	      nbuf);
-      result = 1;
-    }
+  TEST_COMPARE (swprintf (buf, array_length (buf), L"%.0ls", L"foo"), 0);
+  TEST_COMPARE_STRING_WIDE (buf, L"");
 
-  return result;
+  return 0;
 }
+
+#include <support/test-driver.c>
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 02/26] libio: Flush-only _IO_str_overflow must not return EOF (bug 28949)
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
  2022-03-17 19:28 ` [PATCH 01/26] libio: Convert tst_swprintf to the test framework Florian Weimer
@ 2022-03-17 19:28 ` Florian Weimer
  2022-03-18 18:11   ` Adhemerval Zanella
  2022-03-17 19:28 ` [PATCH 03/26] stdio-common: Add wide stream coverage to tst-vfprintf-user-type Florian Weimer
                   ` (23 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:28 UTC (permalink / raw)
  To: libc-alpha

In general, _IO_str_overflow returns the character passed as an argument
on success.  However, if flush-only operation is requested by passing
EOF, returning EOF looks like an error, and the caller cannot tell
whether the operation was successful or not.

_IO_wstr_overflow had the same bug regarding WEOF.
---
 libio/strops.c  | 5 ++++-
 libio/wstrops.c | 5 ++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/libio/strops.c b/libio/strops.c
index 6a9a8846c4..1cd0bf6c3d 100644
--- a/libio/strops.c
+++ b/libio/strops.c
@@ -133,7 +133,10 @@ _IO_str_overflow (FILE *fp, int c)
     *fp->_IO_write_ptr++ = (unsigned char) c;
   if (fp->_IO_write_ptr > fp->_IO_read_end)
     fp->_IO_read_end = fp->_IO_write_ptr;
-  return c;
+  if (flush_only)
+    return 0;
+  else
+    return c;
 }
 libc_hidden_def (_IO_str_overflow)
 
diff --git a/libio/wstrops.c b/libio/wstrops.c
index 8e44f86c35..2aec314937 100644
--- a/libio/wstrops.c
+++ b/libio/wstrops.c
@@ -130,7 +130,10 @@ _IO_wstr_overflow (FILE *fp, wint_t c)
     *fp->_wide_data->_IO_write_ptr++ = c;
   if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_read_end)
     fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_write_ptr;
-  return c;
+  if (flush_only)
+    return 0;
+  else
+    return c;
 }
 
 
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 03/26] stdio-common: Add wide stream coverage to tst-vfprintf-user-type
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
  2022-03-17 19:28 ` [PATCH 01/26] libio: Convert tst_swprintf to the test framework Florian Weimer
  2022-03-17 19:28 ` [PATCH 02/26] libio: Flush-only _IO_str_overflow must not return EOF (bug 28949) Florian Weimer
@ 2022-03-17 19:28 ` Florian Weimer
  2022-03-18 18:30   ` Adhemerval Zanella
  2022-03-17 19:28 ` [PATCH 04/26] stdio-common: Add tst-printf-width-i18n to cover numeric field width Florian Weimer
                   ` (22 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:28 UTC (permalink / raw)
  To: libc-alpha

And use TEST_COMPARE_STRING for the narrow tests.
---
 stdio-common/tst-vfprintf-user-type.c | 128 ++++++++++++--------------
 1 file changed, 61 insertions(+), 67 deletions(-)

diff --git a/stdio-common/tst-vfprintf-user-type.c b/stdio-common/tst-vfprintf-user-type.c
index f2650ac018..4aa78a6b60 100644
--- a/stdio-common/tst-vfprintf-user-type.c
+++ b/stdio-common/tst-vfprintf-user-type.c
@@ -21,6 +21,7 @@
    this indicates the number of such pairs which constitute the
    argument.  */
 
+#include <array_length.h>
 #include <locale.h>
 #include <printf.h>
 #include <stdio.h>
@@ -60,13 +61,23 @@ my_printf_function (FILE *fp, const struct printf_info *info,
             __func__, fp, info, args[0], args, (wint_t) info->spec,
             info->prec);
 
+  TEST_COMPARE (info->wide, fwide (fp, 0) > 0);
+
   TEST_VERIFY (info->spec == 'P');
   size_t nargs;
   int printed;
   if (info->prec >= 0)
     {
-      if (fputc ('{', fp) < 0)
-        return -1;
+      if (info->wide)
+        {
+          if (fputwc (L'{', fp) < 0)
+            return -1;
+          }
+      else
+        {
+          if (fputc ('{', fp) < 0)
+            return -1;
+        }
       nargs = info->prec;
       printed = 1;
     }
@@ -80,8 +91,16 @@ my_printf_function (FILE *fp, const struct printf_info *info,
     {
       if (i != 0)
         {
-          if (fputc (',', fp) < 0)
-            return -1;
+          if (info->wide)
+            {
+              if (fputwc (L',', fp) < 0)
+                return -1;
+            }
+          else
+            {
+              if (fputc (',', fp) < 0)
+                return -1;
+            }
           ++printed;
         }
 
@@ -89,15 +108,27 @@ my_printf_function (FILE *fp, const struct printf_info *info,
          and those pointers point to a pointer to the memory area
          supplied to my_va_arg_function.  */
       struct two_argument *pair = *(void **) args[i];
-      int ret = fprintf (fp, "(%ld, %f)", pair->i, pair->d);
+      int ret;
+      if (info->wide)
+        ret = fwprintf (fp, L"(%ld, %f)", pair->i, pair->d);
+      else
+        ret = fprintf (fp, "(%ld, %f)", pair->i, pair->d);
       if (ret < 0)
         return -1;
       printed += ret;
     }
   if (info->prec >= 0)
     {
-      if (fputc ('}', fp) < 0)
-        return -1;
+      if (info->wide)
+        {
+          if (fputwc (L'}', fp) < 0)
+            return -1;
+        }
+      else
+        {
+          if (fputc ('}', fp) < 0)
+            return -1;
+        }
       ++printed;
     }
   return printed;
@@ -145,77 +176,40 @@ do_test (void)
   TEST_VERIFY_EXIT (register_printf_specifier
                     ('P', my_printf_function, my_arginfo_function) >= 0);
 
-  /* Alias declaration for asprintf, to avoid the format string
-     attribute and the associated warning.  */
-#if __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI == 1
-  extern int asprintf_alias (char **, const char *, ...) __asm__ ("__asprintfieee128");
-#else
-  extern int asprintf_alias (char **, const char *, ...) __asm__ ("asprintf");
-#endif
-  TEST_VERIFY (asprintf_alias == asprintf);
-  char *str = NULL;
-  TEST_VERIFY (asprintf_alias (&str, "[[%P]]", 123L, 456.0) >= 0);
-  if (test_verbose > 0)
-    printf ("info: %s\n", str);
-  TEST_VERIFY (strcmp (str, "[[(123, 456.000000)]]") == 0);
-  free (str);
-
-  str = NULL;
-  TEST_VERIFY (asprintf_alias (&str, "[[%1$P %1$P]]", 123L, 457.0) >= 0);
-  if (test_verbose > 0)
-    printf ("info: %s\n", str);
-  TEST_VERIFY (strcmp (str, "[[(123, 457.000000) (123, 457.000000)]]") == 0);
-  free (str);
-
-  str = NULL;
-  TEST_VERIFY (asprintf_alias (&str, "[[%.1P]]", 1L, 2.0) >= 0);
-  if (test_verbose > 0)
-    printf ("info: %s\n", str);
-  TEST_VERIFY (strcmp (str, "[[{(1, 2.000000)}]]") == 0);
-  free (str);
+  /* Wide variants of the tests above.  */
 
-  str = NULL;
-  TEST_VERIFY (asprintf_alias (&str, "[[%.2P]]", 1L, 2.0, 3L, 4.0) >= 0);
-  if (test_verbose > 0)
-    printf ("info: %s\n", str);
-  TEST_VERIFY (strcmp (str, "[[{(1, 2.000000),(3, 4.000000)}]]") == 0);
-  free (str);
+  wchar_t buf[200];
+  TEST_VERIFY (swprintf (buf, array_length (buf), L"[[%.2P]]",
+                         1L, 2.0, 3L, 4.0) >= 0);
+  TEST_COMPARE_STRING_WIDE (buf, L"[[{(1, 2.000000),(3, 4.000000)}]]");
 
-  str = NULL;
-  TEST_VERIFY (asprintf_alias
-               (&str, "[[%.2P | %.3P]]",
+  TEST_VERIFY (swprintf
+               (buf, array_length (buf), L"[[%.2P | %.3P]]",
                 /* argument 1: */ 1L, 2.0, 3L, 4.0,
                 /* argument 2: */ 5L, 6.0, 7L, 8.0, 9L, 10.0)
                >= 0);
-  if (test_verbose > 0)
-    printf ("info: %s\n", str);
-  TEST_VERIFY (strcmp (str,
-                       "[["
-                       "{(1, 2.000000),(3, 4.000000)}"
-                       " | "
-                       "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
-                       "]]") == 0);
-  free (str);
+  TEST_COMPARE_STRING_WIDE (buf,
+                            L"[["
+                            "{(1, 2.000000),(3, 4.000000)}"
+                            " | "
+                            "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
+                            "]]");
 
   /* The following subtest fails due to bug 21534.  */
 #if 0
-  str = NULL;
-  TEST_VERIFY (asprintf_alias
-               (&str, "[[%1$.2P | %2$.3P | %1$.2P]]",
+  TEST_VERIFY (swprintf
+               (&buf, array_length (buf), L"[[%1$.2P | %2$.3P | %1$.2P]]",
                 /* argument 1: */ 1L, 2.0, 3L, 4.0,
                 /* argument 2: */ 5L, 6.0, 7L, 8.0, 9L, 10.0)
                >= 0);
-  if (test_verbose > 0)
-    printf ("info: %s\n", str);
-  TEST_VERIFY (strcmp (str,
-                       "[["
-                       "{(1, 2.000000),(3, 4.000000)}"
-                       " | "
-                       "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
-                       " | "
-                       "{(1, 2.000000),(3, 4.000000)}"
-                       "]]") == 0);
-  free (str);
+  TEST_COMPARE_STRING_WIDE (buf,
+                            L"[["
+                            "{(1, 2.000000),(3, 4.000000)}"
+                            " | "
+                            "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
+                            " | "
+                            "{(1, 2.000000),(3, 4.000000)}"
+                            "]]");
 #endif
 
   return 0;
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 04/26] stdio-common: Add tst-printf-width-i18n to cover numeric field width
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (2 preceding siblings ...)
  2022-03-17 19:28 ` [PATCH 03/26] stdio-common: Add wide stream coverage to tst-vfprintf-user-type Florian Weimer
@ 2022-03-17 19:28 ` Florian Weimer
  2022-05-20 13:22   ` Adhemerval Zanella
  2022-03-17 19:28 ` [PATCH 05/26] vfprintf: Move argument processing into vfprintf-process-arg.c Florian Weimer
                   ` (21 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:28 UTC (permalink / raw)
  To: libc-alpha

Related to bug 28943 and bug 28944.
---
 stdio-common/Makefile                |  1 +
 stdio-common/tst-printf-width-i18n.c | 95 ++++++++++++++++++++++++++++
 2 files changed, 96 insertions(+)
 create mode 100644 stdio-common/tst-printf-width-i18n.c

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 435cd8904f..f0e65f0dcd 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -250,6 +250,7 @@ LOCALES := \
   de_DE.ISO-8859-1 \
   de_DE.UTF-8 \
   en_US.ISO-8859-1 \
+  hi_IN.UTF-8 \
   ja_JP.EUC-JP \
   ps_AF.UTF-8 \
   # LOCALES
diff --git a/stdio-common/tst-printf-width-i18n.c b/stdio-common/tst-printf-width-i18n.c
new file mode 100644
index 0000000000..2355f30d67
--- /dev/null
+++ b/stdio-common/tst-printf-width-i18n.c
@@ -0,0 +1,95 @@
+/* Test for width of non-ASCII digit sequences.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* Behavior is currently inconsistent between %d and %f (bug 28943,
+   bug 28944).  This test intends to capture the status quo.  */
+
+#include <monetary.h>
+#include <stdio.h>
+#include <support/support.h>
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+  char buf[40];
+
+  xsetlocale (LC_ALL, "hi_IN.UTF-8");
+
+  /* Ungrouped, not translated.  */
+  TEST_COMPARE (sprintf (buf, "%7d", 12345), 7);
+  TEST_COMPARE_STRING (buf, "  12345");
+  TEST_COMPARE (sprintf (buf, "%10.2f", 12345.67), 10);
+  TEST_COMPARE_STRING (buf, "  12345.67");
+  TEST_COMPARE (strfmon (buf, sizeof (buf), "%^13i", 12345.67), 13);
+  TEST_COMPARE_STRING (buf, "  INR12345.67");
+
+  /* Grouped.  */
+  TEST_COMPARE (sprintf (buf, "%'8d", 12345), 8);
+  TEST_COMPARE_STRING (buf, "  12,345");
+  TEST_COMPARE (sprintf (buf, "%'11.2f", 12345.67), 11);
+  TEST_COMPARE_STRING (buf, "  12,345.67");
+  TEST_COMPARE (strfmon (buf, sizeof (buf), "%13i", 12345.67), 13);
+  TEST_COMPARE_STRING (buf, " INR12,345.67");
+
+  /* Translated.  */
+  TEST_COMPARE (sprintf (buf, "%I16d", 12345), 16);
+  TEST_COMPARE_STRING (buf, " १२३४५");
+  TEST_COMPARE (sprintf (buf, "%I12.2f", 12345.67), 26);
+  TEST_COMPARE_STRING (buf, "    १२३४५.६७");
+
+  /* Translated and grouped.  */
+  TEST_COMPARE (sprintf (buf, "%'I17d", 12345), 17);
+  TEST_COMPARE_STRING (buf, " १२,३४५");
+  TEST_COMPARE (sprintf (buf, "%'I12.2f", 12345.67), 26);
+  TEST_COMPARE_STRING (buf, "   १२,३४५.६७");
+
+  xsetlocale (LC_ALL, "ps_AF.UTF-8");
+
+  /* Ungrouped, not translated.  */
+  TEST_COMPARE (sprintf (buf, "%7d", 12345), 7);
+  TEST_COMPARE_STRING (buf, "  12345");
+  TEST_COMPARE (sprintf (buf, "%10.2f", 12345.67), 11);
+  TEST_COMPARE_STRING (buf, "  12345٫67");
+  TEST_COMPARE (strfmon (buf, sizeof (buf), "%^13i", 12345.67), 13);
+  TEST_COMPARE_STRING (buf, "    12346 AFN");
+
+  /* Grouped.  */
+  TEST_COMPARE (sprintf (buf, "%'8d", 12345), 8);
+  TEST_COMPARE_STRING (buf, " 12٬345");
+  TEST_COMPARE (sprintf (buf, "%'11.2f", 12345.67), 13);
+  TEST_COMPARE_STRING (buf, "  12٬345٫67"); /* Counts characters.  */
+  TEST_COMPARE (strfmon (buf, sizeof (buf), "%13i", 12345.67), 13);
+  TEST_COMPARE_STRING (buf, "  12٬346 AFN"); /* Counts bytes.   */
+
+  /* Translated.  */
+  TEST_COMPARE (sprintf (buf, "%I11d", 12345), 11);
+  TEST_COMPARE_STRING (buf, " ١٢٣۴٥");
+  TEST_COMPARE (sprintf (buf, "%I12.2f", 12345.67), 20);
+  TEST_COMPARE_STRING (buf, "    ١٢٣۴٥٫٦٧");
+
+  /* Translated and grouped.  */
+  TEST_COMPARE (sprintf (buf, "%'I13d", 12345), 13);
+  TEST_COMPARE_STRING (buf, " ١٢٬٣۴٥");
+  TEST_COMPARE (sprintf (buf, "%'I12.2f", 12345.67), 21);
+  TEST_COMPARE_STRING (buf, "   ١٢٬٣۴٥٫٦٧");
+
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 05/26] vfprintf: Move argument processing into vfprintf-process-arg.c
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (3 preceding siblings ...)
  2022-03-17 19:28 ` [PATCH 04/26] stdio-common: Add tst-printf-width-i18n to cover numeric field width Florian Weimer
@ 2022-03-17 19:28 ` Florian Weimer
  2022-05-20 13:28   ` Adhemerval Zanella
  2022-03-17 19:28 ` [PATCH 06/26] vfprintf: Consolidate some multibyte/wide character processing Florian Weimer
                   ` (20 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:28 UTC (permalink / raw)
  To: libc-alpha

This simplies formatting and helps with debugging.  It also allows
the use of localized COMPILE_WPRINTF preprocessor conditionals.
---
 stdio-common/vfprintf-internal.c    | 501 +--------------------------
 stdio-common/vfprintf-process-arg.c | 515 ++++++++++++++++++++++++++++
 2 files changed, 517 insertions(+), 499 deletions(-)
 create mode 100644 stdio-common/vfprintf-process-arg.c

diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
index 59bd76c890..1986c4bdb5 100644
--- a/stdio-common/vfprintf-internal.c
+++ b/stdio-common/vfprintf-internal.c
@@ -656,501 +656,6 @@ static const uint8_t jump_table[] =
       REF (form_binary),	/* for 'B', 'b' */			      \
     }
 
-/* Before invoking this macro, process_arg_int etc. macros have to be
-   defined to extract one argument of the appropriate type.  */
-#define process_arg()						              \
-      /* Start real work.  We know about all flags and modifiers and	      \
-	 now process the wanted format specifier.  */			      \
-    LABEL (form_percent):						      \
-      /* Write a literal "%".  */					      \
-      outchar (L_('%'));						      \
-      break;								      \
-									      \
-    LABEL (form_integer):						      \
-      /* Signed decimal integer.  */					      \
-      base = 10;							      \
-									      \
-      if (is_longlong)							      \
-	{								      \
-	  long long int signed_number = process_arg_long_long_int ();	      \
-	  is_negative = signed_number < 0;				      \
-	  number.longlong = is_negative ? (- signed_number) : signed_number;  \
-									      \
-	  goto LABEL (longlong_number);					      \
-	}								      \
-      else								      \
-	{								      \
-	  long int signed_number;					      \
-	  if (is_long_num)						      \
-	    signed_number = process_arg_long_int ();			      \
-	  else if (is_char)						      \
-	    signed_number = (signed char) process_arg_unsigned_int ();	      \
-	  else if (!is_short)						      \
-	    signed_number = process_arg_int ();				      \
-	  else								      \
-	    signed_number = (short int) process_arg_unsigned_int ();	      \
-									      \
-	  is_negative = signed_number < 0;				      \
-	  number.word = is_negative ? (- signed_number) : signed_number;      \
-									      \
-	  goto LABEL (number);						      \
-	}								      \
-      /* NOTREACHED */							      \
-									      \
-    LABEL (form_unsigned):						      \
-      /* Unsigned decimal integer.  */					      \
-      base = 10;							      \
-      goto LABEL (unsigned_number);					      \
-      /* NOTREACHED */							      \
-									      \
-    LABEL (form_octal):							      \
-      /* Unsigned octal integer.  */					      \
-      base = 8;								      \
-      goto LABEL (unsigned_number);					      \
-      /* NOTREACHED */							      \
-									      \
-    LABEL (form_hexa):							      \
-      /* Unsigned hexadecimal integer.  */				      \
-      base = 16;							      \
-      goto LABEL (unsigned_number);					      \
-      /* NOTREACHED */							      \
-									      \
-    LABEL (form_binary):						      \
-      /* Unsigned binary integer.  */					      \
-      base = 2;								      \
-      goto LABEL (unsigned_number);					      \
-      /* NOTREACHED */							      \
-									      \
-    LABEL (unsigned_number):	  /* Unsigned number of base BASE.  */	      \
-									      \
-      /* ISO specifies the `+' and ` ' flags only for signed		      \
-	 conversions.  */						      \
-      is_negative = 0;							      \
-      showsign = 0;							      \
-      space = 0;							      \
-									      \
-      if (is_longlong)							      \
-	{								      \
-	  number.longlong = process_arg_unsigned_long_long_int ();	      \
-									      \
-	LABEL (longlong_number):					      \
-	  if (prec < 0)							      \
-	    /* Supply a default precision if none was given.  */	      \
-	    prec = 1;							      \
-	  else								      \
-	    /* We have to take care for the '0' flag.  If a precision	      \
-	       is given it must be ignored.  */				      \
-	    pad = L_(' ');						      \
-									      \
-	  /* If the precision is 0 and the number is 0 nothing has to	      \
-	     be written for the number, except for the 'o' format in	      \
-	     alternate form.  */					      \
-	  if (prec == 0 && number.longlong == 0)			      \
-	    {								      \
-	      string = workend;						      \
-	      if (base == 8 && alt)					      \
-		*--string = L_('0');					      \
-	    }								      \
-	  else								      \
-	    {								      \
-	      /* Put the number in WORK.  */				      \
-	      string = _itoa (number.longlong, workend, base,		      \
-			      spec == L_('X'));				      \
-	      if (group && grouping)					      \
-		string = group_number (work_buffer, string, workend,	      \
-				       grouping, thousands_sep);	      \
-	      if (use_outdigits && base == 10)				      \
-		string = _i18n_number_rewrite (string, workend, workend);     \
-	    }								      \
-	  /* Simplify further test for num != 0.  */			      \
-	  number.word = number.longlong != 0;				      \
-	}								      \
-      else								      \
-	{								      \
-	  if (is_long_num)						      \
-	    number.word = process_arg_unsigned_long_int ();		      \
-	  else if (is_char)						      \
-	    number.word = (unsigned char) process_arg_unsigned_int ();	      \
-	  else if (!is_short)						      \
-	    number.word = process_arg_unsigned_int ();			      \
-	  else								      \
-	    number.word = (unsigned short int) process_arg_unsigned_int ();   \
-									      \
-	LABEL (number):							      \
-	  if (prec < 0)							      \
-	    /* Supply a default precision if none was given.  */	      \
-	    prec = 1;							      \
-	  else								      \
-	    /* We have to take care for the '0' flag.  If a precision	      \
-	       is given it must be ignored.  */				      \
-	    pad = L_(' ');						      \
-									      \
-	  /* If the precision is 0 and the number is 0 nothing has to	      \
-	     be written for the number, except for the 'o' format in	      \
-	     alternate form.  */					      \
-	  if (prec == 0 && number.word == 0)				      \
-	    {								      \
-	      string = workend;						      \
-	      if (base == 8 && alt)					      \
-		*--string = L_('0');					      \
-	    }								      \
-	  else								      \
-	    {								      \
-	      /* Put the number in WORK.  */				      \
-	      string = _itoa_word (number.word, workend, base,		      \
-				   spec == L_('X'));			      \
-	      if (group && grouping)					      \
-		string = group_number (work_buffer, string, workend,	      \
-				       grouping, thousands_sep);	      \
-	      if (use_outdigits && base == 10)				      \
-		string = _i18n_number_rewrite (string, workend, workend);     \
-	    }								      \
-	}								      \
-									      \
-      if (prec <= workend - string && number.word != 0 && alt && base == 8)   \
-	/* Add octal marker.  */					      \
-	*--string = L_('0');						      \
-									      \
-      prec = MAX (0, prec - (workend - string));			      \
-									      \
-      if (!left)							      \
-	{								      \
-	  width -= workend - string + prec;				      \
-									      \
-	  if (number.word != 0 && alt && (base == 16 || base == 2))	      \
-	    /* Account for 0X, 0x, 0B or 0b hex or binary marker.  */	      \
-	    width -= 2;							      \
-									      \
-	  if (is_negative || showsign || space)				      \
-	    --width;							      \
-									      \
-	  if (pad == L_(' '))						      \
-	    {								      \
-	      PAD (L_(' '));						      \
-	      width = 0;						      \
-	    }								      \
-									      \
-	  if (is_negative)						      \
-	    outchar (L_('-'));						      \
-	  else if (showsign)						      \
-	    outchar (L_('+'));						      \
-	  else if (space)						      \
-	    outchar (L_(' '));						      \
-									      \
-	  if (number.word != 0 && alt && (base == 16 || base == 2))	      \
-	    {								      \
-	      outchar (L_('0'));					      \
-	      outchar (spec);						      \
-	    }								      \
-									      \
-	  width += prec;						      \
-	  PAD (L_('0'));						      \
-									      \
-	  outstring (string, workend - string);				      \
-									      \
-	  break;							      \
-	}								      \
-      else								      \
-	{								      \
-	  if (is_negative)						      \
-	    {								      \
-	      outchar (L_('-'));					      \
-	      --width;							      \
-	    }								      \
-	  else if (showsign)						      \
-	    {								      \
-	      outchar (L_('+'));					      \
-	      --width;							      \
-	    }								      \
-	  else if (space)						      \
-	    {								      \
-	      outchar (L_(' '));					      \
-	      --width;							      \
-	    }								      \
-									      \
-	  if (number.word != 0 && alt && (base == 16 || base == 2))	      \
-	    {								      \
-	      outchar (L_('0'));					      \
-	      outchar (spec);						      \
-	      width -= 2;						      \
-	    }								      \
-									      \
-	  width -= workend - string + prec;				      \
-									      \
-	  if (prec > 0)							      \
-	    {								      \
-	      int temp = width;						      \
-	      width = prec;						      \
-	      PAD (L_('0'));						      \
-	      width = temp;						      \
-	    }								      \
-									      \
-	  outstring (string, workend - string);				      \
-									      \
-	  PAD (L_(' '));						      \
-	  break;							      \
-	}								      \
-									      \
-    LABEL (form_pointer):						      \
-      /* Generic pointer.  */						      \
-      {									      \
-	const void *ptr = process_arg_pointer ();			      \
-	if (ptr != NULL)						      \
-	  {								      \
-	    /* If the pointer is not NULL, write it as a %#x spec.  */	      \
-	    base = 16;							      \
-	    number.word = (unsigned long int) ptr;			      \
-	    is_negative = 0;						      \
-	    alt = 1;							      \
-	    group = 0;							      \
-	    spec = L_('x');						      \
-	    goto LABEL (number);					      \
-	  }								      \
-	else								      \
-	  {								      \
-	    /* Write "(nil)" for a nil pointer.  */			      \
-	    string = (CHAR_T *) L_("(nil)");				      \
-	    /* Make sure the full string "(nil)" is printed.  */	      \
-	    if (prec < 5)						      \
-	      prec = 5;							      \
-	    /* This is a wide string iff compiling wprintf.  */		      \
-	    is_long = sizeof (CHAR_T) > 1;				      \
-	    goto LABEL (print_string);					      \
-	  }								      \
-      }									      \
-      /* NOTREACHED */							      \
-									      \
-    LABEL (form_number):						      \
-      if ((mode_flags & PRINTF_FORTIFY) != 0)				      \
-	{								      \
-	  if (! readonly_format)					      \
-	    {								      \
-	      extern int __readonly_area (const void *, size_t)		      \
-		attribute_hidden;					      \
-	      readonly_format						      \
-		= __readonly_area (format, ((STR_LEN (format) + 1)	      \
-					    * sizeof (CHAR_T)));	      \
-	    }								      \
-	  if (readonly_format < 0)					      \
-	    __libc_fatal ("*** %n in writable segment detected ***\n");	      \
-	}								      \
-      /* Answer the count of characters written.  */			      \
-      void *ptrptr = process_arg_pointer ();				      \
-      if (is_longlong)							      \
-	*(long long int *) ptrptr = done;				      \
-      else if (is_long_num)						      \
-	*(long int *) ptrptr = done;					      \
-      else if (is_char)							      \
-	*(char *) ptrptr = done;					      \
-      else if (!is_short)						      \
-	*(int *) ptrptr = done;						      \
-      else								      \
-	*(short int *) ptrptr = done;					      \
-      break;								      \
-									      \
-    LABEL (form_strerror):						      \
-      /* Print description of error ERRNO.  */				      \
-      if (alt)								      \
-	string = (CHAR_T *) __get_errname (save_errno);			      \
-      else								      \
-	string = (CHAR_T *) __strerror_r (save_errno, (char *) work_buffer,   \
-					  WORK_BUFFER_SIZE * sizeof (CHAR_T));\
-      if (string == NULL)						\
-	{								      \
-          /* Print as a decimal number. */				      \
-          base = 10;							      \
-	  is_negative = save_errno < 0;					      \
-	  number.word = save_errno;					      \
-	  if (is_negative)						      \
-	    number.word = -number.word;					      \
-	  goto LABEL (number);						      \
-	}								      \
-      else								      \
-	{								      \
-	  is_long = 0;	/* This is no wide-char string.  */		      \
-	  goto LABEL (print_string);					      \
-	}
-
-#ifdef COMPILE_WPRINTF
-# define process_string_arg()						      \
-    LABEL (form_character):						      \
-      /* Character.  */							      \
-      if (is_long)							      \
-	goto LABEL (form_wcharacter);					      \
-      --width;	/* Account for the character itself.  */		      \
-      if (!left)							      \
-	PAD (L' ');							      \
-      outchar (__btowc ((unsigned char) process_arg_int ())); /* Promoted. */ \
-      if (left)								      \
-	PAD (L' ');							      \
-      break;								      \
-									      \
-    LABEL (form_wcharacter):						      \
-      {									      \
-	/* Wide character.  */						      \
-	--width;							      \
-	if (!left)							      \
-	  PAD (L' ');							      \
-	outchar (process_arg_wchar_t ());				      \
-	if (left)							      \
-	  PAD (L' ');							      \
-      }									      \
-      break;								      \
-									      \
-    LABEL (form_string):						      \
-      {									      \
-	size_t len;							      \
-									      \
-	/* The string argument could in fact be `char *' or `wchar_t *'.      \
-	   But this should not make a difference here.  */		      \
-	string = (CHAR_T *) process_arg_wstring ();			      \
-									      \
-	/* Entry point for printing other strings.  */			      \
-      LABEL (print_string):						      \
-									      \
-	if (string == NULL)						      \
-	  {								      \
-	    /* Write "(null)" if there's space.  */			      \
-	    if (prec == -1 || prec >= (int) array_length (null) - 1)          \
-	      {								      \
-		string = (CHAR_T *) null;				      \
-		len = array_length (null) - 1;				      \
-	      }								      \
-	    else							      \
-	      {								      \
-		string = (CHAR_T *) L"";				      \
-		len = 0;						      \
-	      }								      \
-	  }								      \
-	else if (!is_long && spec != L_('S'))				      \
-	  {								      \
-	    done = outstring_converted_wide_string			      \
-	      (s, (const char *) string, prec, width, left, done);	      \
-	    if (done < 0)						      \
-	      goto all_done;						      \
-	    /* The padding has already been written.  */		      \
-	    break;							      \
-	  }								      \
-	else								      \
-	  {								      \
-	    if (prec != -1)						      \
-	      /* Search for the end of the string, but don't search past      \
-		 the length specified by the precision.  */		      \
-	      len = __wcsnlen (string, prec);				      \
-	    else							      \
-	      len = __wcslen (string);					      \
-	  }								      \
-									      \
-	if ((width -= len) < 0)						      \
-	  {								      \
-	    outstring (string, len);					      \
-	    break;							      \
-	  }								      \
-									      \
-	if (!left)							      \
-	  PAD (L' ');							      \
-	outstring (string, len);					      \
-	if (left)							      \
-	  PAD (L' ');							      \
-      }									      \
-      break;
-#else
-# define process_string_arg()						      \
-    LABEL (form_character):						      \
-      /* Character.  */							      \
-      if (is_long)							      \
-	goto LABEL (form_wcharacter);					      \
-      --width;	/* Account for the character itself.  */		      \
-      if (!left)							      \
-	PAD (' ');							      \
-      outchar ((unsigned char) process_arg_int ()); /* Promoted.  */	      \
-      if (left)								      \
-	PAD (' ');							      \
-      break;								      \
-									      \
-    LABEL (form_wcharacter):						      \
-      {									      \
-	/* Wide character.  */						      \
-	char buf[MB_LEN_MAX];						      \
-	mbstate_t mbstate;						      \
-	size_t len;							      \
-									      \
-	memset (&mbstate, '\0', sizeof (mbstate_t));			      \
-	len = __wcrtomb (buf, process_arg_wchar_t (), &mbstate);	      \
-	if (len == (size_t) -1)						      \
-	  {								      \
-	    /* Something went wrong during the conversion.  Bail out.  */     \
-	    done = -1;							      \
-	    goto all_done;						      \
-	  }								      \
-	width -= len;							      \
-	if (!left)							      \
-	  PAD (' ');							      \
-	outstring (buf, len);						      \
-	if (left)							      \
-	  PAD (' ');							      \
-      }									      \
-      break;								      \
-									      \
-    LABEL (form_string):						      \
-      {									      \
-	size_t len;							      \
-									      \
-	/* The string argument could in fact be `char *' or `wchar_t *'.      \
-	   But this should not make a difference here.  */		      \
-	string = (char *) process_arg_string ();			      \
-									      \
-	/* Entry point for printing other strings.  */			      \
-      LABEL (print_string):						      \
-									      \
-	if (string == NULL)						      \
-	  {								      \
-	    /* Write "(null)" if there's space.  */			      \
-	    if (prec == -1 || prec >= (int) sizeof (null) - 1)		      \
-	      {								      \
-		string = (char *) null;					      \
-		len = sizeof (null) - 1;				      \
-	      }								      \
-	    else							      \
-	      {								      \
-		string = (char *) "";					      \
-		len = 0;						      \
-	      }								      \
-	  }								      \
-	else if (!is_long && spec != L_('S'))				      \
-	  {								      \
-	    if (prec != -1)						      \
-	      /* Search for the end of the string, but don't search past      \
-		 the length (in bytes) specified by the precision.  */	      \
-	      len = __strnlen (string, prec);				      \
-	    else							      \
-	      len = strlen (string);					      \
-	  }								      \
-	else								      \
-	  {								      \
-	    done = outstring_converted_wide_string			      \
-	      (s, (const wchar_t *) string, prec, width, left, done);	      \
-	    if (done < 0)						      \
-	      goto all_done;						      \
-	    /* The padding has already been written.  */		      \
-	    break;							      \
-	  }								      \
-									      \
-	if ((width -= len) < 0)						      \
-	  {								      \
-	    outstring (string, len);					      \
-	    break;							      \
-	  }								      \
-									      \
-	if (!left)							      \
-	  PAD (' ');							      \
-	outstring (string, len);					      \
-	if (left)							      \
-	  PAD (' ');							      \
-      }									      \
-      break;
-#endif
-
 /* Helper function to provide temporary buffering for unbuffered streams.  */
 static int buffered_vfprintf (FILE *stream, const CHAR_T *fmt, va_list,
 			      unsigned int)
@@ -1513,8 +1018,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 #define process_arg_unsigned_long_long_int() va_arg (ap, unsigned long long int)
 #define process_arg_wchar_t() va_arg (ap, wchar_t)
 #define process_arg_wstring() va_arg (ap, const wchar_t *)
-	  process_arg ();
-	  process_string_arg ();
+#include "vfprintf-process-arg.c"
 #undef process_arg_int
 #undef process_arg_long_int
 #undef process_arg_long_long_int
@@ -1923,8 +1427,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 #define process_arg_unsigned_long_long_int() process_arg_data.pa_u_long_long_int
 #define process_arg_wchar_t() process_arg_data.pa_wchar
 #define process_arg_wstring() process_arg_data.pa_wstring
-	  process_arg ();
-	  process_string_arg ();
+#include "vfprintf-process-arg.c"
 #undef process_arg_data
 #undef process_arg_int
 #undef process_arg_long_int
diff --git a/stdio-common/vfprintf-process-arg.c b/stdio-common/vfprintf-process-arg.c
new file mode 100644
index 0000000000..a28afce7de
--- /dev/null
+++ b/stdio-common/vfprintf-process-arg.c
@@ -0,0 +1,515 @@
+/* Argument-processing fragment for vfprintf.
+   Copyright (C) 1991-2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This file is included twice from vfprintf-internal.c, for standard
+   and GNU-style positional (%N$) arguments.  Before that,
+   process_arg_int etc. macros have to be defined to extract one
+   argument of the appropriate type, in addition to the file-specific
+   macros in vfprintf-internal.c.  */
+
+{
+  /* Start real work.  We know about all flags and modifiers and
+     now process the wanted format specifier.  */
+LABEL (form_percent):
+  /* Write a literal "%".  */
+  outchar (L_('%'));
+  break;
+
+LABEL (form_integer):
+  /* Signed decimal integer.  */
+  base = 10;
+
+  if (is_longlong)
+    {
+      long long int signed_number = process_arg_long_long_int ();
+      is_negative = signed_number < 0;
+      number.longlong = is_negative ? (- signed_number) : signed_number;
+
+      goto LABEL (longlong_number);
+    }
+  else
+    {
+      long int signed_number;
+      if (is_long_num)
+        signed_number = process_arg_long_int ();
+      else if (is_char)
+        signed_number = (signed char) process_arg_unsigned_int ();
+      else if (!is_short)
+        signed_number = process_arg_int ();
+      else
+        signed_number = (short int) process_arg_unsigned_int ();
+
+      is_negative = signed_number < 0;
+      number.word = is_negative ? (- signed_number) : signed_number;
+
+      goto LABEL (number);
+    }
+  /* NOTREACHED */
+
+LABEL (form_unsigned):
+  /* Unsigned decimal integer.  */
+  base = 10;
+  goto LABEL (unsigned_number);
+  /* NOTREACHED */
+
+LABEL (form_octal):
+  /* Unsigned octal integer.  */
+  base = 8;
+  goto LABEL (unsigned_number);
+  /* NOTREACHED */
+
+LABEL (form_hexa):
+  /* Unsigned hexadecimal integer.  */
+  base = 16;
+  goto LABEL (unsigned_number);
+  /* NOTREACHED */
+
+LABEL (form_binary):
+  /* Unsigned binary integer.  */
+  base = 2;
+  goto LABEL (unsigned_number);
+  /* NOTREACHED */
+
+LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
+
+  /* ISO specifies the `+' and ` ' flags only for signed
+     conversions.  */
+  is_negative = 0;
+  showsign = 0;
+  space = 0;
+
+  if (is_longlong)
+    {
+      number.longlong = process_arg_unsigned_long_long_int ();
+
+      LABEL (longlong_number):
+      if (prec < 0)
+        /* Supply a default precision if none was given.  */
+        prec = 1;
+      else
+        /* We have to take care for the '0' flag.  If a precision
+           is given it must be ignored.  */
+        pad = L_(' ');
+
+      /* If the precision is 0 and the number is 0 nothing has to
+         be written for the number, except for the 'o' format in
+         alternate form.  */
+      if (prec == 0 && number.longlong == 0)
+        {
+          string = workend;
+          if (base == 8 && alt)
+            *--string = L_('0');
+        }
+      else
+        {
+          /* Put the number in WORK.  */
+          string = _itoa (number.longlong, workend, base,
+                          spec == L_('X'));
+          if (group && grouping)
+            string = group_number (work_buffer, string, workend,
+                                   grouping, thousands_sep);
+          if (use_outdigits && base == 10)
+            string = _i18n_number_rewrite (string, workend, workend);
+        }
+      /* Simplify further test for num != 0.  */
+      number.word = number.longlong != 0;
+    }
+  else
+    {
+      if (is_long_num)
+        number.word = process_arg_unsigned_long_int ();
+      else if (is_char)
+        number.word = (unsigned char) process_arg_unsigned_int ();
+      else if (!is_short)
+        number.word = process_arg_unsigned_int ();
+      else
+        number.word = (unsigned short int) process_arg_unsigned_int ();
+
+      LABEL (number):
+      if (prec < 0)
+        /* Supply a default precision if none was given.  */
+        prec = 1;
+      else
+        /* We have to take care for the '0' flag.  If a precision
+           is given it must be ignored.  */
+        pad = L_(' ');
+
+      /* If the precision is 0 and the number is 0 nothing has to
+         be written for the number, except for the 'o' format in
+         alternate form.  */
+      if (prec == 0 && number.word == 0)
+        {
+          string = workend;
+          if (base == 8 && alt)
+            *--string = L_('0');
+        }
+      else
+        {
+          /* Put the number in WORK.  */
+          string = _itoa_word (number.word, workend, base,
+                               spec == L_('X'));
+          if (group && grouping)
+            string = group_number (work_buffer, string, workend,
+                                   grouping, thousands_sep);
+          if (use_outdigits && base == 10)
+            string = _i18n_number_rewrite (string, workend, workend);
+        }
+    }
+
+  if (prec <= workend - string && number.word != 0 && alt && base == 8)
+    /* Add octal marker.  */
+    *--string = L_('0');
+
+  prec = MAX (0, prec - (workend - string));
+
+  if (!left)
+    {
+      width -= workend - string + prec;
+
+      if (number.word != 0 && alt && (base == 16 || base == 2))
+        /* Account for 0X, 0x, 0B or 0b hex or binary marker.  */
+        width -= 2;
+
+      if (is_negative || showsign || space)
+        --width;
+
+      if (pad == L_(' '))
+        {
+          PAD (L_(' '));
+          width = 0;
+        }
+
+      if (is_negative)
+        outchar (L_('-'));
+      else if (showsign)
+        outchar (L_('+'));
+      else if (space)
+        outchar (L_(' '));
+
+      if (number.word != 0 && alt && (base == 16 || base == 2))
+        {
+          outchar (L_('0'));
+          outchar (spec);
+        }
+
+      width += prec;
+      PAD (L_('0'));
+
+      outstring (string, workend - string);
+
+      break;
+    }
+  else
+    {
+      if (is_negative)
+        {
+          outchar (L_('-'));
+          --width;
+        }
+      else if (showsign)
+        {
+          outchar (L_('+'));
+          --width;
+        }
+      else if (space)
+        {
+          outchar (L_(' '));
+          --width;
+        }
+
+      if (number.word != 0 && alt && (base == 16 || base == 2))
+        {
+          outchar (L_('0'));
+          outchar (spec);
+          width -= 2;
+        }
+
+      width -= workend - string + prec;
+
+      if (prec > 0)
+        {
+          int temp = width;
+          width = prec;
+          PAD (L_('0'));
+          width = temp;
+        }
+
+      outstring (string, workend - string);
+
+      PAD (L_(' '));
+      break;
+    }
+
+LABEL (form_pointer):
+  /* Generic pointer.  */
+  {
+    const void *ptr = process_arg_pointer ();
+    if (ptr != NULL)
+      {
+        /* If the pointer is not NULL, write it as a %#x spec.  */
+        base = 16;
+        number.word = (unsigned long int) ptr;
+        is_negative = 0;
+        alt = 1;
+        group = 0;
+        spec = L_('x');
+        goto LABEL (number);
+      }
+    else
+      {
+        /* Write "(nil)" for a nil pointer.  */
+        string = (CHAR_T *) L_("(nil)");
+        /* Make sure the full string "(nil)" is printed.  */
+        if (prec < 5)
+          prec = 5;
+        /* This is a wide string iff compiling wprintf.  */
+        is_long = sizeof (CHAR_T) > 1;
+        goto LABEL (print_string);
+      }
+  }
+  /* NOTREACHED */
+
+LABEL (form_number):
+  if ((mode_flags & PRINTF_FORTIFY) != 0)
+    {
+      if (! readonly_format)
+        {
+          extern int __readonly_area (const void *, size_t)
+            attribute_hidden;
+          readonly_format
+            = __readonly_area (format, ((STR_LEN (format) + 1)
+                                        * sizeof (CHAR_T)));
+        }
+      if (readonly_format < 0)
+        __libc_fatal ("*** %n in writable segment detected ***\n");
+    }
+  /* Answer the count of characters written.  */
+  void *ptrptr = process_arg_pointer ();
+  if (is_longlong)
+    *(long long int *) ptrptr = done;
+  else if (is_long_num)
+    *(long int *) ptrptr = done;
+  else if (is_char)
+    *(char *) ptrptr = done;
+  else if (!is_short)
+    *(int *) ptrptr = done;
+  else
+    *(short int *) ptrptr = done;
+  break;
+
+LABEL (form_strerror):
+  /* Print description of error ERRNO.  */
+  if (alt)
+    string = (CHAR_T *) __get_errname (save_errno);
+  else
+    string = (CHAR_T *) __strerror_r (save_errno, (char *) work_buffer,
+                                      WORK_BUFFER_SIZE * sizeof (CHAR_T));
+  if (string == NULL)
+    {
+      /* Print as a decimal number. */
+      base = 10;
+      is_negative = save_errno < 0;
+      number.word = save_errno;
+      if (is_negative)
+        number.word = -number.word;
+      goto LABEL (number);
+    }
+  else
+    {
+      is_long = 0;  /* This is no wide-char string.  */
+      goto LABEL (print_string);
+    }
+
+#ifdef COMPILE_WPRINTF
+LABEL (form_character):
+  /* Character.  */
+  if (is_long)
+    goto LABEL (form_wcharacter);
+  --width;  /* Account for the character itself.  */
+  if (!left)
+    PAD (L' ');
+  outchar (__btowc ((unsigned char) process_arg_int ())); /* Promoted. */
+  if (left)
+    PAD (L' ');
+  break;
+
+LABEL (form_wcharacter):
+  {
+    /* Wide character.  */
+    --width;
+    if (!left)
+      PAD (L' ');
+    outchar (process_arg_wchar_t ());
+    if (left)
+      PAD (L' ');
+  }
+  break;
+
+LABEL (form_string):
+  {
+    size_t len;
+
+    /* The string argument could in fact be `char *' or `wchar_t *'.
+       But this should not make a difference here.  */
+    string = (CHAR_T *) process_arg_wstring ();
+
+    /* Entry point for printing other strings.  */
+    LABEL (print_string):
+
+    if (string == NULL)
+      {
+        /* Write "(null)" if there's space.  */
+        if (prec == -1 || prec >= (int) array_length (null) - 1)
+          {
+            string = (CHAR_T *) null;
+            len = array_length (null) - 1;
+          }
+        else
+          {
+            string = (CHAR_T *) L"";
+            len = 0;
+          }
+      }
+    else if (!is_long && spec != L_('S'))
+      {
+        done = outstring_converted_wide_string
+          (s, (const char *) string, prec, width, left, done);
+        if (done < 0)
+          goto all_done;
+        /* The padding has already been written.  */
+        break;
+      }
+    else
+      {
+        if (prec != -1)
+          /* Search for the end of the string, but don't search past
+             the length specified by the precision.  */
+          len = __wcsnlen (string, prec);
+        else
+          len = __wcslen (string);
+      }
+
+    if ((width -= len) < 0)
+      {
+        outstring (string, len);
+        break;
+      }
+
+    if (!left)
+      PAD (L' ');
+    outstring (string, len);
+    if (left)
+      PAD (L' ');
+  }
+  break;
+#else /* !COMPILE_WPRINTF */
+LABEL (form_character):
+  /* Character.  */
+  if (is_long)
+    goto LABEL (form_wcharacter);
+  --width;  /* Account for the character itself.  */
+  if (!left)
+    PAD (' ');
+  outchar ((unsigned char) process_arg_int ()); /* Promoted.  */
+  if (left)
+    PAD (' ');
+  break;
+
+LABEL (form_wcharacter):
+  {
+    /* Wide character.  */
+    char buf[MB_LEN_MAX];
+    mbstate_t mbstate;
+    size_t len;
+
+    memset (&mbstate, '\0', sizeof (mbstate_t));
+    len = __wcrtomb (buf, process_arg_wchar_t (), &mbstate);
+    if (len == (size_t) -1)
+      {
+        /* Something went wrong during the conversion.  Bail out.  */
+        done = -1;
+        goto all_done;
+      }
+    width -= len;
+    if (!left)
+      PAD (' ');
+    outstring (buf, len);
+    if (left)
+      PAD (' ');
+  }
+  break;
+
+LABEL (form_string):
+  {
+    size_t len;
+
+    /* The string argument could in fact be `char *' or `wchar_t *'.
+       But this should not make a difference here.  */
+    string = (char *) process_arg_string ();
+
+    /* Entry point for printing other strings.  */
+    LABEL (print_string):
+
+    if (string == NULL)
+      {
+        /* Write "(null)" if there's space.  */
+        if (prec == -1 || prec >= (int) sizeof (null) - 1)
+          {
+            string = (char *) null;
+            len = sizeof (null) - 1;
+          }
+        else
+          {
+            string = (char *) "";
+            len = 0;
+          }
+      }
+    else if (!is_long && spec != L_('S'))
+      {
+        if (prec != -1)
+          /* Search for the end of the string, but don't search past
+             the length (in bytes) specified by the precision.  */
+          len = __strnlen (string, prec);
+        else
+          len = strlen (string);
+      }
+    else
+      {
+        done = outstring_converted_wide_string
+          (s, (const wchar_t *) string, prec, width, left, done);
+        if (done < 0)
+          goto all_done;
+        /* The padding has already been written.  */
+        break;
+      }
+
+    if ((width -= len) < 0)
+      {
+        outstring (string, len);
+        break;
+      }
+
+    if (!left)
+      PAD (' ');
+    outstring (string, len);
+    if (left)
+      PAD (' ');
+  }
+  break;
+#endif /* !COMPILE_WPRINTF */
+}
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 06/26] vfprintf: Consolidate some multibyte/wide character processing
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (4 preceding siblings ...)
  2022-03-17 19:28 ` [PATCH 05/26] vfprintf: Move argument processing into vfprintf-process-arg.c Florian Weimer
@ 2022-03-17 19:28 ` Florian Weimer
  2022-05-20 14:16   ` Adhemerval Zanella
  2022-03-17 19:29 ` [PATCH 07/26] __printf_fphex always uses LC_NUMERIC Florian Weimer
                   ` (19 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:28 UTC (permalink / raw)
  To: libc-alpha

form_character and form_string processing a sufficiently similar
that the logic can be shared.
---
 stdio-common/vfprintf-process-arg.c | 130 +++++++++-------------------
 1 file changed, 43 insertions(+), 87 deletions(-)

diff --git a/stdio-common/vfprintf-process-arg.c b/stdio-common/vfprintf-process-arg.c
index a28afce7de..4fe369e111 100644
--- a/stdio-common/vfprintf-process-arg.c
+++ b/stdio-common/vfprintf-process-arg.c
@@ -335,29 +335,20 @@ LABEL (form_strerror):
       goto LABEL (print_string);
     }
 
-#ifdef COMPILE_WPRINTF
 LABEL (form_character):
   /* Character.  */
   if (is_long)
     goto LABEL (form_wcharacter);
   --width;  /* Account for the character itself.  */
   if (!left)
-    PAD (L' ');
+    PAD (L_(' '));
+#ifdef COMPILE_WPRINTF
   outchar (__btowc ((unsigned char) process_arg_int ())); /* Promoted. */
+#else
+  outchar ((unsigned char) process_arg_int ()); /* Promoted.  */
+#endif
   if (left)
-    PAD (L' ');
-  break;
-
-LABEL (form_wcharacter):
-  {
-    /* Wide character.  */
-    --width;
-    if (!left)
-      PAD (L' ');
-    outchar (process_arg_wchar_t ());
-    if (left)
-      PAD (L' ');
-  }
+    PAD (L_(' '));
   break;
 
 LABEL (form_string):
@@ -366,8 +357,11 @@ LABEL (form_string):
 
     /* The string argument could in fact be `char *' or `wchar_t *'.
        But this should not make a difference here.  */
+#ifdef COMPILE_WPRINTF
     string = (CHAR_T *) process_arg_wstring ();
-
+#else
+    string = (CHAR_T *) process_arg_string ();
+#endif
     /* Entry point for printing other strings.  */
     LABEL (print_string):
 
@@ -387,21 +381,39 @@ LABEL (form_string):
       }
     else if (!is_long && spec != L_('S'))
       {
+#ifdef COMPILE_WPRINTF
         done = outstring_converted_wide_string
           (s, (const char *) string, prec, width, left, done);
         if (done < 0)
           goto all_done;
         /* The padding has already been written.  */
         break;
+#else
+        if (prec != -1)
+          /* Search for the end of the string, but don't search past
+             the length (in bytes) specified by the precision.  */
+          len = __strnlen (string, prec);
+        else
+          len = strlen (string);
+#endif
       }
     else
       {
+#ifdef COMPILE_WPRINTF
         if (prec != -1)
           /* Search for the end of the string, but don't search past
              the length specified by the precision.  */
           len = __wcsnlen (string, prec);
         else
           len = __wcslen (string);
+#else
+        done = outstring_converted_wide_string
+          (s, (const wchar_t *) string, prec, width, left, done);
+        if (done < 0)
+          goto all_done;
+        /* The padding has already been written.  */
+        break;
+#endif
       }
 
     if ((width -= len) < 0)
@@ -411,25 +423,27 @@ LABEL (form_string):
       }
 
     if (!left)
-      PAD (L' ');
+      PAD (L_(' '));
     outstring (string, len);
     if (left)
-      PAD (L' ');
+      PAD (L_(' '));
   }
   break;
-#else /* !COMPILE_WPRINTF */
-LABEL (form_character):
-  /* Character.  */
-  if (is_long)
-    goto LABEL (form_wcharacter);
-  --width;  /* Account for the character itself.  */
-  if (!left)
-    PAD (' ');
-  outchar ((unsigned char) process_arg_int ()); /* Promoted.  */
-  if (left)
-    PAD (' ');
+
+#ifdef COMPILE_WPRINTF
+LABEL (form_wcharacter):
+  {
+    /* Wide character.  */
+    --width;
+    if (!left)
+      PAD (L' ');
+    outchar (process_arg_wchar_t ());
+    if (left)
+      PAD (L' ');
+  }
   break;
 
+#else /* !COMPILE_WPRINTF */
 LABEL (form_wcharacter):
   {
     /* Wide character.  */
@@ -453,63 +467,5 @@ LABEL (form_wcharacter):
       PAD (' ');
   }
   break;
-
-LABEL (form_string):
-  {
-    size_t len;
-
-    /* The string argument could in fact be `char *' or `wchar_t *'.
-       But this should not make a difference here.  */
-    string = (char *) process_arg_string ();
-
-    /* Entry point for printing other strings.  */
-    LABEL (print_string):
-
-    if (string == NULL)
-      {
-        /* Write "(null)" if there's space.  */
-        if (prec == -1 || prec >= (int) sizeof (null) - 1)
-          {
-            string = (char *) null;
-            len = sizeof (null) - 1;
-          }
-        else
-          {
-            string = (char *) "";
-            len = 0;
-          }
-      }
-    else if (!is_long && spec != L_('S'))
-      {
-        if (prec != -1)
-          /* Search for the end of the string, but don't search past
-             the length (in bytes) specified by the precision.  */
-          len = __strnlen (string, prec);
-        else
-          len = strlen (string);
-      }
-    else
-      {
-        done = outstring_converted_wide_string
-          (s, (const wchar_t *) string, prec, width, left, done);
-        if (done < 0)
-          goto all_done;
-        /* The padding has already been written.  */
-        break;
-      }
-
-    if ((width -= len) < 0)
-      {
-        outstring (string, len);
-        break;
-      }
-
-    if (!left)
-      PAD (' ');
-    outstring (string, len);
-    if (left)
-      PAD (' ');
-  }
-  break;
 #endif /* !COMPILE_WPRINTF */
 }
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 07/26] __printf_fphex always uses LC_NUMERIC
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (5 preceding siblings ...)
  2022-03-17 19:28 ` [PATCH 06/26] vfprintf: Consolidate some multibyte/wide character processing Florian Weimer
@ 2022-03-17 19:29 ` Florian Weimer
  2022-05-20 14:21   ` Adhemerval Zanella
  2022-03-17 19:29 ` [PATCH 08/26] stdio-common: Add tst-memstream-string for open_memstream overflow Florian Weimer
                   ` (18 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:29 UTC (permalink / raw)
  To: libc-alpha

There is no hexadecimal currency printing.  strfmon uses
__printf_fp_l exclusively.
---
 stdio-common/printf_fphex.c | 26 +++++++-------------------
 1 file changed, 7 insertions(+), 19 deletions(-)

diff --git a/stdio-common/printf_fphex.c b/stdio-common/printf_fphex.c
index 3dbbefd972..df11b4a166 100644
--- a/stdio-common/printf_fphex.c
+++ b/stdio-common/printf_fphex.c
@@ -103,9 +103,13 @@ __printf_fphex (FILE *fp,
     }
   fpnum;
 
-  /* Locale-dependent representation of decimal point.	*/
-  const char *decimal;
-  wchar_t decimalwc;
+  /* Locale-dependent representation of decimal point. Hexadecimal
+     formatting always using LC_NUMERIC (disregarding info->extra).  */
+  const char *decimal = _NL_CURRENT (LC_NUMERIC, DECIMAL_POINT);
+  wchar_t decimalwc = _NL_CURRENT_WORD (LC_NUMERIC, _NL_NUMERIC_DECIMAL_POINT_WC);
+
+  /* The decimal point character must never be zero.  */
+  assert (*decimal != '\0' && decimalwc != L'\0');
 
   /* "NaN" or "Inf" for the special cases.  */
   const char *special = NULL;
@@ -147,22 +151,6 @@ __printf_fphex (FILE *fp,
   /* Nonzero if this is output on a wide character stream.  */
   int wide = info->wide;
 
-
-  /* Figure out the decimal point character.  */
-  if (info->extra == 0)
-    {
-      decimal = _NL_CURRENT (LC_NUMERIC, DECIMAL_POINT);
-      decimalwc = _NL_CURRENT_WORD (LC_NUMERIC, _NL_NUMERIC_DECIMAL_POINT_WC);
-    }
-  else
-    {
-      decimal = _NL_CURRENT (LC_MONETARY, MON_DECIMAL_POINT);
-      decimalwc = _NL_CURRENT_WORD (LC_MONETARY,
-				    _NL_MONETARY_DECIMAL_POINT_WC);
-    }
-  /* The decimal point character must never be zero.  */
-  assert (*decimal != '\0' && decimalwc != L'\0');
-
 #define PRINTF_FPHEX_FETCH(FLOAT, VAR)					\
   {									\
     (VAR) = *(const FLOAT *) args[0];					\
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 08/26] stdio-common: Add tst-memstream-string for open_memstream overflow
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (6 preceding siblings ...)
  2022-03-17 19:29 ` [PATCH 07/26] __printf_fphex always uses LC_NUMERIC Florian Weimer
@ 2022-03-17 19:29 ` Florian Weimer
  2022-05-20 17:44   ` Adhemerval Zanella
  2022-03-17 19:29 ` [PATCH 09/26] stdio-common: Add printf specifier registry to <printf.h> Florian Weimer
                   ` (17 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:29 UTC (permalink / raw)
  To: libc-alpha

This code path is exercised indirectly by some of the DNS stub
resolver tests, via their own use of xopen_memstream for constructing
strings describing result data.  The relative lack of test suite
coverage became apparent when these tests starting failing after a
printf changes uncovered bug 28949.
---
 stdio-common/Makefile               |  1 +
 stdio-common/tst-memstream-string.c | 85 +++++++++++++++++++++++++++++
 2 files changed, 86 insertions(+)
 create mode 100644 stdio-common/tst-memstream-string.c

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index f0e65f0dcd..222c9ea63d 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -173,6 +173,7 @@ tests := \
   tst-gets \
   tst-grouping \
   tst-long-dbl-fphex \
+  tst-memstream-string \
   tst-obprintf \
   tst-perror \
   tst-popen \
diff --git a/stdio-common/tst-memstream-string.c b/stdio-common/tst-memstream-string.c
new file mode 100644
index 0000000000..1c1bf0154a
--- /dev/null
+++ b/stdio-common/tst-memstream-string.c
@@ -0,0 +1,85 @@
+/* Test writing differently sized strings to a memstream.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xmemstream.h>
+
+/* Returns a printable ASCII character based on INDEX.   */
+static inline char
+char_from_index (unsigned int index)
+{
+  return ' ' + (index % 95);
+}
+
+/* Hide fprintf behind a compiler barrier, to avoid the fputs
+   transformation.  */
+void __attribute__ ((weak))
+fprintf_compiler_barrier (FILE *fp, const char *format, const char *arg)
+{
+  fprintf (fp, format, arg);
+}
+
+enum { result_size = 25000 };
+
+static void
+run_one_size (unsigned int chunk_size)
+{
+  char *chunk = xmalloc (chunk_size + 1);
+
+  struct xmemstream mem;
+  xopen_memstream (&mem);
+  unsigned int written = 0;
+  for (unsigned int i = 0; i < result_size; )
+    {
+      unsigned int to_print = result_size - i;
+      if (to_print > chunk_size)
+        to_print = chunk_size;
+      for (unsigned int j = 0; j < to_print; ++j)
+        chunk[j] = char_from_index(i + j);
+      chunk[to_print] = '\0';
+      fprintf_compiler_barrier (mem.out, "%s", chunk);
+      i += to_print;
+      written += strlen(chunk);
+    }
+  xfclose_memstream (&mem);
+
+  TEST_COMPARE (written, result_size);
+  TEST_COMPARE (mem.length, result_size);
+  TEST_COMPARE (strlen (mem.buffer), result_size);
+
+  for (unsigned int i = 0; i < result_size; ++i)
+    TEST_COMPARE (mem.buffer[i], char_from_index (i));
+
+  free (mem.buffer);
+  free (chunk);
+}
+
+static int
+do_test (void)
+{
+  for (unsigned int chunk_size = 1; chunk_size <= 30; ++ chunk_size)
+    run_one_size (chunk_size);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 09/26] stdio-common: Add printf specifier registry to <printf.h>
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (7 preceding siblings ...)
  2022-03-17 19:29 ` [PATCH 08/26] stdio-common: Add tst-memstream-string for open_memstream overflow Florian Weimer
@ 2022-03-17 19:29 ` Florian Weimer
  2022-05-20 17:49   ` Adhemerval Zanella
  2022-03-17 19:30 ` [PATCH 10/26] stdio-common: Move union printf_arg int <printf.h> Florian Weimer
                   ` (16 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:29 UTC (permalink / raw)
  To: libc-alpha

Add  __printf_function_table, __register_printf_specifier to
include/printf.h, where they belong.
---
 include/printf.h                 | 6 ++++++
 stdio-common/reg-printf.c        | 7 -------
 stdio-common/vfprintf-internal.c | 1 -
 3 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/include/printf.h b/include/printf.h
index 0ed6e87387..e1ac20807b 100644
--- a/include/printf.h
+++ b/include/printf.h
@@ -12,6 +12,12 @@
 
 # ifndef _ISOMAC
 
+/* Internal interfaces for registered specifiers.  */
+extern printf_function **__printf_function_table attribute_hidden;
+int __register_printf_specifier (int, printf_function,
+				 printf_arginfo_size_function);
+libc_hidden_proto (__register_printf_specifier)
+
 #include <bits/types/locale_t.h>
 
 /* Now define the internal interfaces.  */
diff --git a/stdio-common/reg-printf.c b/stdio-common/reg-printf.c
index 400b99d2f6..5f4c6a24c2 100644
--- a/stdio-common/reg-printf.c
+++ b/stdio-common/reg-printf.c
@@ -30,13 +30,6 @@ printf_function **__printf_function_table attribute_hidden;
 
 __libc_lock_define_initialized (static, lock)
 
-int __register_printf_specifier (int, printf_function,
-				 printf_arginfo_size_function);
-libc_hidden_proto (__register_printf_specifier)
-int __register_printf_function (int, printf_function,
-				printf_arginfo_function);
-
-
 /* Register FUNC to be called to format SPEC specifiers.  */
 int
 __register_printf_specifier (int spec, printf_function converter,
diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
index 1986c4bdb5..f8aa2fdafb 100644
--- a/stdio-common/vfprintf-internal.c
+++ b/stdio-common/vfprintf-internal.c
@@ -1379,7 +1379,6 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
       /* Process format specifiers.  */
       while (1)
 	{
-	  extern printf_function **__printf_function_table;
 	  int function_done;
 
 	  if (spec <= UCHAR_MAX
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 10/26] stdio-common: Move union printf_arg int <printf.h>
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (8 preceding siblings ...)
  2022-03-17 19:29 ` [PATCH 09/26] stdio-common: Add printf specifier registry to <printf.h> Florian Weimer
@ 2022-03-17 19:30 ` Florian Weimer
  2022-05-20 17:51   ` Adhemerval Zanella
  2022-03-17 19:30 ` [PATCH 11/26] stdio-common: Simplify printf_unknown interface in vfprintf-internal.c Florian Weimer
                   ` (15 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:30 UTC (permalink / raw)
  To: libc-alpha

The type does not depend on wide vs narrow preprocessor macros,
so it does not need to be customized in stdio-common/printf-parse.h.
---
 include/printf.h            | 21 +++++++++++++++++++++
 stdio-common/printf-parse.h | 23 -----------------------
 2 files changed, 21 insertions(+), 23 deletions(-)

diff --git a/include/printf.h b/include/printf.h
index e1ac20807b..770c32d8f8 100644
--- a/include/printf.h
+++ b/include/printf.h
@@ -18,6 +18,27 @@ int __register_printf_specifier (int, printf_function,
 				 printf_arginfo_size_function);
 libc_hidden_proto (__register_printf_specifier)
 
+/* The various kinds of arguments that can be passed to printf.  */
+union printf_arg
+  {
+    wchar_t pa_wchar;
+    int pa_int;
+    long int pa_long_int;
+    long long int pa_long_long_int;
+    unsigned int pa_u_int;
+    unsigned long int pa_u_long_int;
+    unsigned long long int pa_u_long_long_int;
+    double pa_double;
+    long double pa_long_double;
+#if __HAVE_FLOAT128_UNLIKE_LDBL
+    _Float128 pa_float128;
+#endif
+    const char *pa_string;
+    const wchar_t *pa_wstring;
+    void *pa_pointer;
+    void *pa_user;
+};
+
 #include <bits/types/locale_t.h>
 
 /* Now define the internal interfaces.  */
diff --git a/stdio-common/printf-parse.h b/stdio-common/printf-parse.h
index de0f289c1f..d00c165131 100644
--- a/stdio-common/printf-parse.h
+++ b/stdio-common/printf-parse.h
@@ -44,29 +44,6 @@ struct printf_spec
     int size;
   };
 
-
-/* The various kinds off arguments that can be passed to printf.  */
-union printf_arg
-  {
-    wchar_t pa_wchar;
-    int pa_int;
-    long int pa_long_int;
-    long long int pa_long_long_int;
-    unsigned int pa_u_int;
-    unsigned long int pa_u_long_int;
-    unsigned long long int pa_u_long_long_int;
-    double pa_double;
-    long double pa_long_double;
-#if __HAVE_FLOAT128_UNLIKE_LDBL
-    _Float128 pa_float128;
-#endif
-    const char *pa_string;
-    const wchar_t *pa_wstring;
-    void *pa_pointer;
-    void *pa_user;
-  };
-
-
 #ifndef DONT_NEED_READ_INT
 /* Read a simple integer from a string and update the string pointer.
    It is assumed that the first character is a digit.  */
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 11/26] stdio-common: Simplify printf_unknown interface in vfprintf-internal.c
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (9 preceding siblings ...)
  2022-03-17 19:30 ` [PATCH 10/26] stdio-common: Move union printf_arg int <printf.h> Florian Weimer
@ 2022-03-17 19:30 ` Florian Weimer
  2022-05-20 18:07   ` Adhemerval Zanella
  2022-03-17 19:30 ` [PATCH 12/26] locale: Call _nl_unload_locale from _nl_archive_subfreeres Florian Weimer
                   ` (14 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:30 UTC (permalink / raw)
  To: libc-alpha

The called function does not use the args array, so there is no need
to produce it.
---
 stdio-common/vfprintf-internal.c | 21 +++------------------
 1 file changed, 3 insertions(+), 18 deletions(-)

diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
index f8aa2fdafb..fb94961f37 100644
--- a/stdio-common/vfprintf-internal.c
+++ b/stdio-common/vfprintf-internal.c
@@ -672,8 +672,7 @@ static int printf_positional (FILE *s,
 			      unsigned int mode_flags);
 
 /* Handle unknown format specifier.  */
-static int printf_unknown (FILE *, const struct printf_info *,
-			   const void *const *) __THROW;
+static int printf_unknown (FILE *, const struct printf_info *) __THROW;
 
 /* Group digits of number string.  */
 static CHAR_T *group_number (CHAR_T *, CHAR_T *, CHAR_T *, const char *,
@@ -1465,19 +1464,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 
 	  LABEL (form_unknown):
 	  {
-	    unsigned int i;
-	    const void **ptr;
-
-	    ptr = alloca (specs[nspecs_done].ndata_args
-			  * sizeof (const void *));
-
-	    /* Fill in an array of pointers to the argument values.  */
-	    for (i = 0; i < specs[nspecs_done].ndata_args; ++i)
-	      ptr[i] = &args_value[specs[nspecs_done].data_arg + i];
-
-	    /* Call the function.  */
-	    function_done = printf_unknown (s, &specs[nspecs_done].info,
-					    ptr);
+	    int function_done = printf_unknown (s, &specs[nspecs_done].info);
 
 	    /* If an error occurred we don't have information about #
 	       of chars.  */
@@ -1507,9 +1494,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 /* Handle an unknown format specifier.  This prints out a canonicalized
    representation of the format spec itself.  */
 static int
-printf_unknown (FILE *s, const struct printf_info *info,
-		const void *const *args)
-
+printf_unknown (FILE *s, const struct printf_info *info)
 {
   int done = 0;
   CHAR_T work_buffer[MAX (sizeof (info->width), sizeof (info->prec)) * 3];
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 12/26] locale: Call _nl_unload_locale from _nl_archive_subfreeres
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (10 preceding siblings ...)
  2022-03-17 19:30 ` [PATCH 11/26] stdio-common: Simplify printf_unknown interface in vfprintf-internal.c Florian Weimer
@ 2022-03-17 19:30 ` Florian Weimer
  2022-05-20 18:09   ` Adhemerval Zanella
  2022-03-17 19:30 ` [PATCH 13/26] locale: Remove cleanup function pointer from struct __localedata Florian Weimer
                   ` (13 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:30 UTC (permalink / raw)
  To: libc-alpha

The function performs the same teps for ld_archive locales
(mapped from an archive), and this code is not performance-critical,
so the specialization does not add vale.
---
 locale/loadarchive.c | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/locale/loadarchive.c b/locale/loadarchive.c
index e7c797bc70..5a2356707f 100644
--- a/locale/loadarchive.c
+++ b/locale/loadarchive.c
@@ -515,13 +515,7 @@ _nl_archive_subfreeres (void)
       free (dead->name);
       for (category = 0; category < __LC_LAST; ++category)
 	if (category != LC_ALL && dead->data[category] != NULL)
-	  {
-	    /* _nl_unload_locale just does this free for the archive case.  */
-	    if (dead->data[category]->private.cleanup)
-	      (*dead->data[category]->private.cleanup) (dead->data[category]);
-
-	    free (dead->data[category]);
-	  }
+	  _nl_unload_locale (dead->data[category]);
       free (dead);
     }
   archloaded = NULL;
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 13/26] locale: Remove cleanup function pointer from struct __localedata
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (11 preceding siblings ...)
  2022-03-17 19:30 ` [PATCH 12/26] locale: Call _nl_unload_locale from _nl_archive_subfreeres Florian Weimer
@ 2022-03-17 19:30 ` Florian Weimer
  2022-05-20 18:16   ` Adhemerval Zanella
  2022-03-17 19:30 ` [PATCH 14/26] locale: Remove private union from struct __locale_data Florian Weimer
                   ` (12 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:30 UTC (permalink / raw)
  To: libc-alpha

We can call the cleanup functions directly from _nl_unload_locale
if we pass the category to it.
---
 locale/findlocale.c    |  2 +-
 locale/loadarchive.c   |  2 +-
 locale/loadlocale.c    | 17 ++++++++++++-----
 locale/localeinfo.h    | 23 ++++++++++-------------
 locale/setlocale.c     |  2 +-
 time/alt_digit.c       |  2 --
 time/era.c             |  1 -
 time/lc-time-cleanup.c |  1 -
 wcsmbs/wcsmbsload.c    |  8 ++------
 9 files changed, 27 insertions(+), 31 deletions(-)

diff --git a/locale/findlocale.c b/locale/findlocale.c
index 64f687ea9d..fc433b61d8 100644
--- a/locale/findlocale.c
+++ b/locale/findlocale.c
@@ -348,6 +348,6 @@ _nl_remove_locale (int locale, struct __locale_data *data)
 	}
 
       /* This does the real work.  */
-      _nl_unload_locale (data);
+      _nl_unload_locale (locale, data);
     }
 }
diff --git a/locale/loadarchive.c b/locale/loadarchive.c
index 5a2356707f..fcc4913319 100644
--- a/locale/loadarchive.c
+++ b/locale/loadarchive.c
@@ -515,7 +515,7 @@ _nl_archive_subfreeres (void)
       free (dead->name);
       for (category = 0; category < __LC_LAST; ++category)
 	if (category != LC_ALL && dead->data[category] != NULL)
-	  _nl_unload_locale (dead->data[category]);
+	  _nl_unload_locale (category, dead->data[category]);
       free (dead);
     }
   archloaded = NULL;
diff --git a/locale/loadlocale.c b/locale/loadlocale.c
index b8cd1aa441..9069bafcd8 100644
--- a/locale/loadlocale.c
+++ b/locale/loadlocale.c
@@ -101,8 +101,7 @@ _nl_intern_locale_data (int category, const void *data, size_t datasize)
 
   newdata->filedata = (void *) filedata;
   newdata->filesize = datasize;
-  newdata->private.data = NULL;
-  newdata->private.cleanup = NULL;
+  memset (&newdata->private, 0, sizeof (newdata->private));
   newdata->usage_count = 0;
   newdata->use_translit = 0;
   newdata->nstrings = filedata->nstrings;
@@ -282,10 +281,18 @@ _nl_load_locale (struct loaded_l10nfile *file, int category)
 }
 
 void
-_nl_unload_locale (struct __locale_data *locale)
+_nl_unload_locale (int category, struct __locale_data *locale)
 {
-  if (locale->private.cleanup)
-    (*locale->private.cleanup) (locale);
+  /* Deallocate locale->private.  */
+  switch (category)
+    {
+    case LC_CTYPE:
+      _nl_cleanup_ctype (locale);
+      break;
+    case LC_TIME:
+      _nl_cleanup_time (locale);
+      break;
+    }
 
   switch (__builtin_expect (locale->alloc, ld_mapped))
     {
diff --git a/locale/localeinfo.h b/locale/localeinfo.h
index 87d3b48c16..8ce072b7b4 100644
--- a/locale/localeinfo.h
+++ b/locale/localeinfo.h
@@ -58,18 +58,13 @@ struct __locale_data
     ld_archive			/* Both point into mmap'd archive regions.  */
   } alloc;
 
-  /* This provides a slot for category-specific code to cache data computed
-     about this locale.  That code can set a cleanup function to deallocate
-     the data.  */
-  struct
+  /* This provides a slot for category-specific code to cache data
+     computed about this locale.  This is deallocated at the start of
+     _nl_unload_locale.  */
+  union
   {
-    void (*cleanup) (struct __locale_data *);
-    union
-    {
-      void *data;
-      struct lc_time_data *time;
-      const struct gconv_fcts *ctype;
-    };
+    struct lc_time_data *time;
+    const struct gconv_fcts *ctype;
   } private;
 
   unsigned int usage_count;	/* Counter for users.  */
@@ -349,7 +344,8 @@ extern void _nl_load_locale (struct loaded_l10nfile *file, int category)
      attribute_hidden;
 
 /* Free all resource.  */
-extern void _nl_unload_locale (struct __locale_data *locale) attribute_hidden;
+extern void _nl_unload_locale (int category, struct __locale_data *locale)
+  attribute_hidden;
 
 /* Free the locale and give back all memory if the usage count is one.  */
 extern void _nl_remove_locale (int locale, struct __locale_data *data)
@@ -409,7 +405,8 @@ extern int _nl_parse_alt_digit (const char **strp,
 /* Postload processing.  */
 extern void _nl_postload_ctype (void);
 
-/* Functions used for the `private.cleanup' hook.  */
+/* Deallocate category-specific data.  Used in _nl_unload_locale.  */
+extern void _nl_cleanup_ctype (struct __locale_data *) attribute_hidden;
 extern void _nl_cleanup_time (struct __locale_data *) attribute_hidden;
 
 
diff --git a/locale/setlocale.c b/locale/setlocale.c
index 38e9bec6bb..56c14d8533 100644
--- a/locale/setlocale.c
+++ b/locale/setlocale.c
@@ -489,7 +489,7 @@ free_category (int category,
       struct __locale_data *data = (struct __locale_data *) runp->data;
 
       if (data != NULL && data != c_data)
-	_nl_unload_locale (data);
+	_nl_unload_locale (category, data);
       runp = runp->next;
       free ((char *) curr->filename);
       free (curr);
diff --git a/time/alt_digit.c b/time/alt_digit.c
index ae22b3f927..7ed9b6b0a5 100644
--- a/time/alt_digit.c
+++ b/time/alt_digit.c
@@ -41,7 +41,6 @@ _nl_init_alt_digit (struct __locale_data *current)
       if (current->private.time == NULL)
 	return;
       memset (current->private.time, 0, sizeof *current->private.time);
-      current->private.cleanup = &_nl_cleanup_time;
     }
   data = current->private.time;
 
@@ -110,7 +109,6 @@ _nl_get_walt_digit (unsigned int number, struct __locale_data *current)
       if (current->private.time == NULL)
 	goto out;
       memset (current->private.time, 0, sizeof *current->private.time);
-      current->private.cleanup = &_nl_cleanup_time;
     }
   data = current->private.time;
 
diff --git a/time/era.c b/time/era.c
index 43528ad0d2..d4b538c7b0 100644
--- a/time/era.c
+++ b/time/era.c
@@ -53,7 +53,6 @@ _nl_init_era_entries (struct __locale_data *current)
       if (current->private.time == NULL)
 	goto out;
       memset (current->private.time, 0, sizeof *current->private.time);
-      current->private.cleanup = &_nl_cleanup_time;
     }
   data = current->private.time;
 
diff --git a/time/lc-time-cleanup.c b/time/lc-time-cleanup.c
index 2734e25815..f844e04905 100644
--- a/time/lc-time-cleanup.c
+++ b/time/lc-time-cleanup.c
@@ -26,7 +26,6 @@ _nl_cleanup_time (struct __locale_data *locale)
   if (data != NULL)
     {
       locale->private.time = NULL;
-      locale->private.cleanup = NULL;
 
       free (data->eras);
       free (data->alt_digits);
diff --git a/wcsmbs/wcsmbsload.c b/wcsmbs/wcsmbsload.c
index ffb5cb7b87..af539a099a 100644
--- a/wcsmbs/wcsmbsload.c
+++ b/wcsmbs/wcsmbsload.c
@@ -202,10 +202,7 @@ __wcsmbs_load_conv (struct __locale_data *new_category)
 	  new_category->private.ctype = &__wcsmbs_gconv_fcts_c;
 	}
       else
-	{
-	  new_category->private.ctype = new_fcts;
-	  new_category->private.cleanup = &_nl_cleanup_ctype;
-	}
+	new_category->private.ctype = new_fcts;
     }
 
   __libc_rwlock_unlock (__libc_setlocale_lock);
@@ -267,10 +264,9 @@ void
 _nl_cleanup_ctype (struct __locale_data *locale)
 {
   const struct gconv_fcts *const data = locale->private.ctype;
-  if (data != NULL)
+  if (data != NULL && data != &__wcsmbs_gconv_fcts_c)
     {
       locale->private.ctype = NULL;
-      locale->private.cleanup = NULL;
 
       /* Free the old conversions.  */
       __gconv_close_transform (data->tomb, data->tomb_nsteps);
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 14/26] locale: Remove private union from struct __locale_data
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (12 preceding siblings ...)
  2022-03-17 19:30 ` [PATCH 13/26] locale: Remove cleanup function pointer from struct __localedata Florian Weimer
@ 2022-03-17 19:30 ` Florian Weimer
  2022-05-20 18:22   ` Adhemerval Zanella
  2022-03-17 19:30 ` [PATCH 15/26] locale: Add more cached data to LC_CTYPE Florian Weimer
                   ` (11 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:30 UTC (permalink / raw)
  To: libc-alpha

This avoids an alias violation later.  This commit also fixes
an incorrect double-checked locking idiom in _nl_init_era_entries.
---
 locale/C-address.c        |  2 +-
 locale/C-collate.c        |  2 +-
 locale/C-ctype.c          |  2 +-
 locale/C-identification.c |  2 +-
 locale/C-measurement.c    |  2 +-
 locale/C-messages.c       |  2 +-
 locale/C-monetary.c       |  2 +-
 locale/C-name.c           |  2 +-
 locale/C-numeric.c        |  2 +-
 locale/C-paper.c          |  2 +-
 locale/C-telephone.c      |  2 +-
 locale/C-time.c           |  2 +-
 locale/localeinfo.h       | 14 ++++++------
 time/alt_digit.c          | 47 ++++++++++++++++----------------------
 time/era.c                | 48 +++++++++++++++++++--------------------
 time/lc-time-cleanup.c    |  4 ++--
 wcsmbs/wcsmbsload.c       | 10 ++++----
 wcsmbs/wcsmbsload.h       |  8 ++++---
 18 files changed, 74 insertions(+), 81 deletions(-)

diff --git a/locale/C-address.c b/locale/C-address.c
index 1f509e4785..40ce9fbcf3 100644
--- a/locale/C-address.c
+++ b/locale/C-address.c
@@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_ADDRESS attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   13,
diff --git a/locale/C-collate.c b/locale/C-collate.c
index 510e90cf14..f9c2b7741b 100644
--- a/locale/C-collate.c
+++ b/locale/C-collate.c
@@ -25,7 +25,7 @@ const struct __locale_data _nl_C_LC_COLLATE attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   19,
diff --git a/locale/C-ctype.c b/locale/C-ctype.c
index cc99bff930..ef4b67029c 100644
--- a/locale/C-ctype.c
+++ b/locale/C-ctype.c
@@ -542,7 +542,7 @@ const struct __locale_data _nl_C_LC_CTYPE attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   1,		/* Enable transliteration by default.  */
   NR_FIXED + NR_CLASSES + NR_MAPS,
diff --git a/locale/C-identification.c b/locale/C-identification.c
index edaf417c70..9648de05bc 100644
--- a/locale/C-identification.c
+++ b/locale/C-identification.c
@@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_IDENTIFICATION attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   16,
diff --git a/locale/C-measurement.c b/locale/C-measurement.c
index b98d624b16..99e16caa3e 100644
--- a/locale/C-measurement.c
+++ b/locale/C-measurement.c
@@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_MEASUREMENT attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   2,
diff --git a/locale/C-messages.c b/locale/C-messages.c
index 88331c7ab6..8fc5c397e3 100644
--- a/locale/C-messages.c
+++ b/locale/C-messages.c
@@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_MESSAGES attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   5,
diff --git a/locale/C-monetary.c b/locale/C-monetary.c
index 295f7a93f0..9c752bc1b6 100644
--- a/locale/C-monetary.c
+++ b/locale/C-monetary.c
@@ -27,7 +27,7 @@ const struct __locale_data _nl_C_LC_MONETARY attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   46,
diff --git a/locale/C-name.c b/locale/C-name.c
index c9b03ef176..0c04b447cd 100644
--- a/locale/C-name.c
+++ b/locale/C-name.c
@@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_NAME attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   7,
diff --git a/locale/C-numeric.c b/locale/C-numeric.c
index 178f61cd89..6f67deca1f 100644
--- a/locale/C-numeric.c
+++ b/locale/C-numeric.c
@@ -23,7 +23,7 @@ const struct __locale_data _nl_C_LC_NUMERIC attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL, 			/* No cached data.  */
   UNDELETABLE,
   0,
   6,
diff --git a/locale/C-paper.c b/locale/C-paper.c
index 06822385f6..56b9519801 100644
--- a/locale/C-paper.c
+++ b/locale/C-paper.c
@@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_PAPER attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   3,
diff --git a/locale/C-telephone.c b/locale/C-telephone.c
index 7f71f605c5..1e8e8b7a66 100644
--- a/locale/C-telephone.c
+++ b/locale/C-telephone.c
@@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_TELEPHONE attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL,				/* No cached data.  */
   UNDELETABLE,
   0,
   5,
diff --git a/locale/C-time.c b/locale/C-time.c
index ebd79591e2..78d87046fb 100644
--- a/locale/C-time.c
+++ b/locale/C-time.c
@@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_TIME attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  { NULL, },			/* no cached data */
+  NULL, 			/* No cached data.  */
   UNDELETABLE,
   0,
   159,
diff --git a/locale/localeinfo.h b/locale/localeinfo.h
index 8ce072b7b4..01ec5535bb 100644
--- a/locale/localeinfo.h
+++ b/locale/localeinfo.h
@@ -59,13 +59,13 @@ struct __locale_data
   } alloc;
 
   /* This provides a slot for category-specific code to cache data
-     computed about this locale.  This is deallocated at the start of
-     _nl_unload_locale.  */
-  union
-  {
-    struct lc_time_data *time;
-    const struct gconv_fcts *ctype;
-  } private;
+     computed about this locale.  Type of the data pointed to:
+
+     LC_CTYPE   struct gconv_fcts (get_gconv_fcts, __wcsmbs_load_conv)
+     LC_TIME    struct lc_time_data (_nl_init_alt_digit, _nl_init_era_entries)
+
+     This data deallocated at the start of _nl_unload_locale.  */
+  void *private;
 
   unsigned int usage_count;	/* Counter for users.  */
 
diff --git a/time/alt_digit.c b/time/alt_digit.c
index 7ed9b6b0a5..331cb395ce 100644
--- a/time/alt_digit.c
+++ b/time/alt_digit.c
@@ -30,19 +30,18 @@ __libc_rwlock_define (extern, __libc_setlocale_lock attribute_hidden)
 #define CURRENT_WSTR(item) \
   ((wchar_t *) current->values[_NL_ITEM_INDEX (item)].wstr)
 
-static void
+static struct lc_time_data *
 _nl_init_alt_digit (struct __locale_data *current)
 {
-  struct lc_time_data *data;
+  struct lc_time_data *data = current->private;
 
-  if (current->private.time == NULL)
+  if (data == NULL)
     {
-      current->private.time = malloc (sizeof *current->private.time);
-      if (current->private.time == NULL)
-	return;
-      memset (current->private.time, 0, sizeof *current->private.time);
+      data = calloc (sizeof *data, 1);
+      if (data == NULL)
+	return NULL;
+      current->private = data;
     }
-  data = current->private.time;
 
   if (! data->alt_digits_initialized)
     {
@@ -65,6 +64,7 @@ _nl_init_alt_digit (struct __locale_data *current)
 	}
     }
 
+  return data;
 }
 
 const char *
@@ -77,13 +77,11 @@ _nl_get_alt_digit (unsigned int number, struct __locale_data *current)
 
   __libc_rwlock_wrlock (__libc_setlocale_lock);
 
-  if (current->private.time == NULL
-      || ! current->private.time->alt_digits_initialized)
-    _nl_init_alt_digit (current);
+  struct lc_time_data *data = _nl_init_alt_digit (current);
 
-  result = ((current->private.time != NULL
-	     && current->private.time->alt_digits != NULL)
-	    ? current->private.time->alt_digits[number]
+  result = ((data != NULL
+	     && data->alt_digits != NULL)
+	    ? data->alt_digits[number]
 	    : NULL);
 
   __libc_rwlock_unlock (__libc_setlocale_lock);
@@ -96,21 +94,20 @@ const wchar_t *
 _nl_get_walt_digit (unsigned int number, struct __locale_data *current)
 {
   const wchar_t *result = NULL;
-  struct lc_time_data *data;
 
   if (number >= 100 || CURRENT_WSTR (_NL_WALT_DIGITS)[0] == L'\0')
     return NULL;
 
   __libc_rwlock_wrlock (__libc_setlocale_lock);
 
-  if (current->private.time == NULL)
+  struct lc_time_data *data = current->private;
+  if (data == NULL)
     {
-      current->private.time = malloc (sizeof *current->private.time);
-      if (current->private.time == NULL)
+      data = calloc (sizeof *data, 1);
+      if (data == NULL)
 	goto out;
-      memset (current->private.time, 0, sizeof *current->private.time);
+      current->private = data;
     }
-  data = current->private.time;
 
   if (! data->walt_digits_initialized)
     {
@@ -156,12 +153,8 @@ _nl_parse_alt_digit (const char **strp, struct __locale_data *current)
 
   __libc_rwlock_wrlock (__libc_setlocale_lock);
 
-  if (current->private.time == NULL
-      || ! current->private.time->alt_digits_initialized)
-    _nl_init_alt_digit (current);
-
-  if (current->private.time != NULL
-      && current->private.time->alt_digits != NULL)
+  struct lc_time_data *data = _nl_init_alt_digit (current);
+  if (data != NULL && data->alt_digits != NULL)
     /* Matching is not unambiguous.  The alternative digits could be like
        I, II, III, ... and the first one is a substring of the second
        and third.  Therefore we must keep on searching until we found
@@ -169,7 +162,7 @@ _nl_parse_alt_digit (const char **strp, struct __locale_data *current)
        the standard.  */
     for (cnt = 0; cnt < 100; ++cnt)
       {
-	const char *const dig = current->private.time->alt_digits[cnt];
+	const char *const dig = data->alt_digits[cnt];
 	size_t len = strlen (dig);
 
 	if (len > maxlen && strncmp (dig, str, len) == 0)
diff --git a/time/era.c b/time/era.c
index d4b538c7b0..7f18071888 100644
--- a/time/era.c
+++ b/time/era.c
@@ -35,7 +35,7 @@ __libc_rwlock_define (extern, __libc_setlocale_lock attribute_hidden)
 
 /* Look up the era information in CURRENT's locale strings and
    cache it in CURRENT->private.  */
-static void
+static struct lc_time_data *
 _nl_init_era_entries (struct __locale_data *current)
 {
   size_t cnt;
@@ -43,18 +43,22 @@ _nl_init_era_entries (struct __locale_data *current)
 
   /* Avoid touching CURRENT if there is no data at all, for _nl_C_LC_TIME.  */
   if (CURRENT_WORD (_NL_TIME_ERA_NUM_ENTRIES) == 0)
-    return;
+    return NULL;
+
+  data = current->private;
+  if (data != NULL && atomic_load_acquire (&data->era_initialized))
+    return data;
 
   __libc_rwlock_wrlock (__libc_setlocale_lock);
 
-  if (current->private.time == NULL)
+  data = current->private;
+  if (data == NULL)
     {
-      current->private.time = malloc (sizeof *current->private.time);
-      if (current->private.time == NULL)
+      data = calloc (sizeof *data, 1);
+      if (data == NULL)
 	goto out;
-      memset (current->private.time, 0, sizeof *current->private.time);
+      current->private = data;
     }
-  data = current->private.time;
 
   if (! data->era_initialized)
     {
@@ -130,33 +134,30 @@ _nl_init_era_entries (struct __locale_data *current)
 	    }
 	}
 
-      data->era_initialized = 1;
+      atomic_store_release (&data->era_initialized, 1);
     }
 
  out:
   __libc_rwlock_unlock (__libc_setlocale_lock);
+  return data;
 }
 
 struct era_entry *
 _nl_get_era_entry (const struct tm *tp, struct __locale_data *current)
 {
-  if (current->private.time == NULL || !current->private.time->era_initialized)
-    _nl_init_era_entries (current);
+  struct lc_time_data *data = _nl_init_era_entries (current);
 
-  if (current->private.time != NULL)
+  if (data != NULL)
     {
       /* Now compare date with the available eras.  */
       const int32_t tdate[3] = { tp->tm_year, tp->tm_mon, tp->tm_mday };
       size_t cnt;
-      for (cnt = 0; cnt < current->private.time->num_eras; ++cnt)
-	if ((ERA_DATE_CMP (current->private.time->eras[cnt].start_date, tdate)
-	     && ERA_DATE_CMP (tdate,
-			      current->private.time->eras[cnt].stop_date))
-	    || (ERA_DATE_CMP (current->private.time->eras[cnt].stop_date,
-			      tdate)
-		&& ERA_DATE_CMP (tdate,
-				 current->private.time->eras[cnt].start_date)))
-	  return &current->private.time->eras[cnt];
+      for (cnt = 0; cnt < data->num_eras; ++cnt)
+	if ((ERA_DATE_CMP (data->eras[cnt].start_date, tdate)
+	     && ERA_DATE_CMP (tdate, data->eras[cnt].stop_date))
+	    || (ERA_DATE_CMP (data->eras[cnt].stop_date, tdate)
+		&& ERA_DATE_CMP (tdate, data->eras[cnt].start_date)))
+	  return &data->eras[cnt];
     }
 
   return NULL;
@@ -166,9 +167,6 @@ _nl_get_era_entry (const struct tm *tp, struct __locale_data *current)
 struct era_entry *
 _nl_select_era_entry (int cnt, struct __locale_data *current)
 {
-  if (current->private.time == NULL || !current->private.time->era_initialized)
-    _nl_init_era_entries (current);
-
-  return (current->private.time == NULL
-	  ? NULL : &current->private.time->eras[cnt]);
+  struct lc_time_data *data = _nl_init_era_entries (current);
+  return data == NULL ? NULL : &data->eras[cnt];
 }
diff --git a/time/lc-time-cleanup.c b/time/lc-time-cleanup.c
index f844e04905..bcf6d2fbc9 100644
--- a/time/lc-time-cleanup.c
+++ b/time/lc-time-cleanup.c
@@ -22,10 +22,10 @@
 void
 _nl_cleanup_time (struct __locale_data *locale)
 {
-  struct lc_time_data *const data = locale->private.time;
+  struct lc_time_data *const data = locale->private;
   if (data != NULL)
     {
-      locale->private.time = NULL;
+      locale->private = NULL;
 
       free (data->eras);
       free (data->alt_digits);
diff --git a/wcsmbs/wcsmbsload.c b/wcsmbs/wcsmbsload.c
index af539a099a..2650834e29 100644
--- a/wcsmbs/wcsmbsload.c
+++ b/wcsmbs/wcsmbsload.c
@@ -155,7 +155,7 @@ __wcsmbs_load_conv (struct __locale_data *new_category)
 
   /* We should repeat the test since while we waited some other thread
      might have run this function.  */
-  if (__glibc_likely (new_category->private.ctype == NULL))
+  if (__glibc_likely (new_category->private == NULL))
     {
       /* We must find the real functions.  */
       const char *charset_name;
@@ -199,10 +199,10 @@ __wcsmbs_load_conv (struct __locale_data *new_category)
 	  free (new_fcts);
 
 	failed:
-	  new_category->private.ctype = &__wcsmbs_gconv_fcts_c;
+	  new_category->private = (void *) &__wcsmbs_gconv_fcts_c;
 	}
       else
-	new_category->private.ctype = new_fcts;
+	new_category->private = new_fcts;
     }
 
   __libc_rwlock_unlock (__libc_setlocale_lock);
@@ -263,10 +263,10 @@ __wcsmbs_named_conv (struct gconv_fcts *copy, const char *name)
 void
 _nl_cleanup_ctype (struct __locale_data *locale)
 {
-  const struct gconv_fcts *const data = locale->private.ctype;
+  const struct gconv_fcts *const data = locale->private;
   if (data != NULL && data != &__wcsmbs_gconv_fcts_c)
     {
-      locale->private.ctype = NULL;
+      locale->private = NULL;
 
       /* Free the old conversions.  */
       __gconv_close_transform (data->tomb, data->tomb_nsteps);
diff --git a/wcsmbs/wcsmbsload.h b/wcsmbs/wcsmbsload.h
index 1ff51e0f8a..8bbd34ba02 100644
--- a/wcsmbs/wcsmbsload.h
+++ b/wcsmbs/wcsmbsload.h
@@ -66,13 +66,15 @@ extern const struct __locale_data _nl_C_LC_CTYPE attribute_hidden;
 static inline const struct gconv_fcts *
 get_gconv_fcts (struct __locale_data *data)
 {
-  if (__glibc_unlikely (data->private.ctype == NULL))
+  struct gconv_fcts *private = data->private;
+  if (private == NULL)
     {
-      if (__glibc_unlikely (data == &_nl_C_LC_CTYPE))
+      if (data == &_nl_C_LC_CTYPE)
 	return &__wcsmbs_gconv_fcts_c;
       __wcsmbs_load_conv (data);
+      private = data->private;
     }
-  return data->private.ctype;
+  return private;
 }
 
 #endif	/* wcsmbsload.h */
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 15/26] locale: Add more cached data to LC_CTYPE
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (13 preceding siblings ...)
  2022-03-17 19:30 ` [PATCH 14/26] locale: Remove private union from struct __locale_data Florian Weimer
@ 2022-03-17 19:30 ` Florian Weimer
  2022-05-20 18:29   ` Adhemerval Zanella
  2022-03-17 19:31 ` [PATCH 16/26] locale: Implement struct grouping_iterator Florian Weimer
                   ` (10 subsequent siblings)
  25 siblings, 1 reply; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:30 UTC (permalink / raw)
  To: libc-alpha

This data will be used in number formatting.
---
 locale/C-ctype.c    |  9 +++++-
 locale/loadlocale.c | 74 ++++++++++++++++++++++++++++++++++++++++++---
 locale/localeinfo.h | 23 +++++++++++++-
 wcsmbs/wcsmbsload.c | 23 ++++++++------
 wcsmbs/wcsmbsload.h | 13 +++-----
 5 files changed, 117 insertions(+), 25 deletions(-)

diff --git a/locale/C-ctype.c b/locale/C-ctype.c
index ef4b67029c..6253f7f413 100644
--- a/locale/C-ctype.c
+++ b/locale/C-ctype.c
@@ -19,6 +19,7 @@
 #include <endian.h>
 #include <stdalign.h>
 #include <stdint.h>
+#include <wcsmbs/wcsmbsload.h>
 
 #include "C-translit.h"
 
@@ -538,11 +539,17 @@ _nl_C_LC_CTYPE_width attribute_hidden =
    NR_FIXED == _NL_ITEM_INDEX (_NL_CTYPE_EXTRA_MAP_1). */
 typedef int assertion1[1 - 2 * (NR_FIXED != _NL_ITEM_INDEX (_NL_CTYPE_EXTRA_MAP_1))];
 
+static const struct lc_ctype_data lc_ctype_data =
+  {
+    .fcts = &__wcsmbs_gconv_fcts_c,
+    .outdigit_bytes_all_equal = 1,
+  };
+
 const struct __locale_data _nl_C_LC_CTYPE attribute_hidden =
 {
   _nl_C_name,
   NULL, 0, 0,			/* no file mapped */
-  NULL,				/* No cached data.  */
+  (void *) &lc_ctype_data,
   UNDELETABLE,
   1,		/* Enable transliteration by default.  */
   NR_FIXED + NR_CLASSES + NR_MAPS,
diff --git a/locale/loadlocale.c b/locale/loadlocale.c
index 9069bafcd8..ce78dfd071 100644
--- a/locale/loadlocale.c
+++ b/locale/loadlocale.c
@@ -31,7 +31,6 @@
 #include <not-cancel.h>
 #include "localeinfo.h"
 
-
 static const size_t _nl_category_num_items[] =
 {
 #define DEFINE_CATEGORY(category, category_name, items, a) \
@@ -62,6 +61,61 @@ static const enum value_type *const _nl_value_types[] =
 #undef DEFINE_CATEGORY
 };
 
+/* Fill in LOCDATA->private for the LC_CTYPE category.  */
+static void
+_nl_intern_locale_data_fill_cache_ctype (struct __locale_data *locdata)
+{
+  struct lc_ctype_data *data = locdata->private;
+
+  /* Default to no translation.  Assumes zero initialization of *data.  */
+  memset (data->outdigit_bytes, 1, 10);
+
+  for (int i = 0; i <= 9; ++i)
+    {
+      const char *digit
+	= locdata->values[_NL_ITEM_INDEX (_NL_CTYPE_OUTDIGIT0_MB + i)].string;
+      unsigned char len;
+      if (digit[0] != '0' + i || digit[1] != '\0')
+	 {
+	   data->outdigit_translation_needed = true;
+	   len = strlen (locdata->values[_NL_ITEM_INDEX
+					 (_NL_CTYPE_OUTDIGIT0_MB + i)].string);
+	 }
+      else
+	len = 1;
+      data->outdigit_bytes[i] = len;
+      if (i == 0)
+	data->outdigit_bytes_all_equal = len;
+      else if (data->outdigit_bytes_all_equal != len)
+	data->outdigit_bytes_all_equal = 0;
+    }
+}
+
+/* Updates data in LOCDATA->private for CATEGORY.  */
+static void
+_nl_intern_locale_data_fill_cache (int category, struct __locale_data *locdata)
+{
+  switch (category)
+    {
+    case LC_CTYPE:
+      _nl_intern_locale_data_fill_cache_ctype (locdata);
+      break;
+    }
+}
+
+/* Returns the number of bytes allocated of struct __locale_data for
+   CATEGORY.  */
+static size_t
+_nl_intern_locale_data_extra_size (int category)
+{
+  switch (category)
+    {
+    case LC_CTYPE:
+      return sizeof (struct lc_ctype_data);
+    default:
+      return 0;
+    }
+}
 
 struct __locale_data *
 _nl_intern_locale_data (int category, const void *data, size_t datasize)
@@ -94,14 +148,23 @@ _nl_intern_locale_data (int category, const void *data, size_t datasize)
       return NULL;
     }
 
-  newdata = malloc (sizeof *newdata
-		    + filedata->nstrings * sizeof (union locale_data_value));
+  size_t base_size = (sizeof *newdata
+		      + filedata->nstrings * sizeof (union locale_data_value));
+  size_t extra_size = _nl_intern_locale_data_extra_size (category);
+
+  newdata = malloc (base_size + extra_size);
   if (newdata == NULL)
     return NULL;
 
   newdata->filedata = (void *) filedata;
   newdata->filesize = datasize;
-  memset (&newdata->private, 0, sizeof (newdata->private));
+  if (extra_size == 0)
+    newdata->private = NULL;
+  else
+    {
+      newdata->private = (char *) newdata + base_size;
+      memset (newdata->private, 0, extra_size);
+    }
   newdata->usage_count = 0;
   newdata->use_translit = 0;
   newdata->nstrings = filedata->nstrings;
@@ -157,6 +220,9 @@ _nl_intern_locale_data (int category, const void *data, size_t datasize)
 	}
     }
 
+  if (extra_size > 0)
+    _nl_intern_locale_data_fill_cache (category, newdata);
+
   return newdata;
 }
 
diff --git a/locale/localeinfo.h b/locale/localeinfo.h
index 01ec5535bb..fd43033a19 100644
--- a/locale/localeinfo.h
+++ b/locale/localeinfo.h
@@ -61,7 +61,7 @@ struct __locale_data
   /* This provides a slot for category-specific code to cache data
      computed about this locale.  Type of the data pointed to:
 
-     LC_CTYPE   struct gconv_fcts (get_gconv_fcts, __wcsmbs_load_conv)
+     LC_CTYPE   struct lc_ctype_data (_nl_intern_locale_data)
      LC_TIME    struct lc_time_data (_nl_init_alt_digit, _nl_init_era_entries)
 
      This data deallocated at the start of _nl_unload_locale.  */
@@ -161,6 +161,27 @@ struct lc_time_data
   int walt_digits_initialized;
 };
 
+/* Ancillary data for LC_CTYPE.  Co-allocated after struct
+   __locale_data by _nl_intern_locale_data.  */
+struct lc_ctype_data
+{
+  /* See get_gconv_fcts and __wcsmbs_load_conv.  */
+  const struct gconv_fcts *fcts;
+
+  /* If false, outdigit just maps to the ASCII digits.  */
+  bool outdigit_translation_needed;
+
+  /* Cached multi-byte string lengths.  This could be added to the
+     locale data itself if the format is changed (which impacts
+     existing statically linked binaries).  */
+
+  /* For the outdigit decimal digits (copied from LC_CTYPE).  */
+  unsigned char outdigit_bytes[10];
+
+  /* If all outdigit_bytes elements are equal, this is that value,
+     otherwise it is 0.  */
+  unsigned char outdigit_bytes_all_equal;
+};
 
 /* LC_CTYPE specific:
    Hardwired indices for standard wide character translation mappings.  */
diff --git a/wcsmbs/wcsmbsload.c b/wcsmbs/wcsmbsload.c
index 2650834e29..0f0f55f9ed 100644
--- a/wcsmbs/wcsmbsload.c
+++ b/wcsmbs/wcsmbsload.c
@@ -150,12 +150,14 @@ __libc_rwlock_define (extern, __libc_setlocale_lock attribute_hidden)
 void
 __wcsmbs_load_conv (struct __locale_data *new_category)
 {
+  struct lc_ctype_data *data = new_category->private;
+
   /* Acquire the lock.  */
   __libc_rwlock_wrlock (__libc_setlocale_lock);
 
   /* We should repeat the test since while we waited some other thread
      might have run this function.  */
-  if (__glibc_likely (new_category->private == NULL))
+  if (__glibc_likely (data->fcts == NULL))
     {
       /* We must find the real functions.  */
       const char *charset_name;
@@ -199,10 +201,10 @@ __wcsmbs_load_conv (struct __locale_data *new_category)
 	  free (new_fcts);
 
 	failed:
-	  new_category->private = (void *) &__wcsmbs_gconv_fcts_c;
+	  data->fcts = (void *) &__wcsmbs_gconv_fcts_c;
 	}
       else
-	new_category->private = new_fcts;
+	data->fcts = new_fcts;
     }
 
   __libc_rwlock_unlock (__libc_setlocale_lock);
@@ -263,14 +265,15 @@ __wcsmbs_named_conv (struct gconv_fcts *copy, const char *name)
 void
 _nl_cleanup_ctype (struct __locale_data *locale)
 {
-  const struct gconv_fcts *const data = locale->private;
-  if (data != NULL && data != &__wcsmbs_gconv_fcts_c)
+  struct lc_ctype_data *data = locale->private;
+  if (data->fcts != NULL && data->fcts != &__wcsmbs_gconv_fcts_c)
     {
-      locale->private = NULL;
-
       /* Free the old conversions.  */
-      __gconv_close_transform (data->tomb, data->tomb_nsteps);
-      __gconv_close_transform (data->towc, data->towc_nsteps);
-      free ((char *) data);
+      __gconv_close_transform (data->fcts->tomb, data->fcts->tomb_nsteps);
+      __gconv_close_transform (data->fcts->towc, data->fcts->towc_nsteps);
+
+      free ((void *) data->fcts);
+      data->fcts = NULL;
+      /* data itself is allocated within locale.  */
     }
 }
diff --git a/wcsmbs/wcsmbsload.h b/wcsmbs/wcsmbsload.h
index 8bbd34ba02..876f8368b1 100644
--- a/wcsmbs/wcsmbsload.h
+++ b/wcsmbs/wcsmbsload.h
@@ -66,15 +66,10 @@ extern const struct __locale_data _nl_C_LC_CTYPE attribute_hidden;
 static inline const struct gconv_fcts *
 get_gconv_fcts (struct __locale_data *data)
 {
-  struct gconv_fcts *private = data->private;
-  if (private == NULL)
-    {
-      if (data == &_nl_C_LC_CTYPE)
-	return &__wcsmbs_gconv_fcts_c;
-      __wcsmbs_load_conv (data);
-      private = data->private;
-    }
-  return private;
+  struct lc_ctype_data *private = data->private;
+  if (private->fcts == NULL)
+    __wcsmbs_load_conv (data);
+  return private->fcts;
 }
 
 #endif	/* wcsmbsload.h */
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 16/26] locale: Implement struct grouping_iterator
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (14 preceding siblings ...)
  2022-03-17 19:30 ` [PATCH 15/26] locale: Add more cached data to LC_CTYPE Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:31 ` [PATCH 17/26] stdio-common: Introduce buffers for implementing printf Florian Weimer
                   ` (9 subsequent siblings)
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

The iterator allows grouping while scanning forward through
the digits.  This enables emitting digits as they are processed.
---
 stdio-common/Makefile                |   8 +-
 stdio-common/grouping_iterator.c     | 125 +++++++++++++
 stdio-common/grouping_iterator.h     |  65 +++++++
 stdio-common/tst-grouping_iterator.c | 262 +++++++++++++++++++++++++++
 4 files changed, 459 insertions(+), 1 deletion(-)
 create mode 100644 stdio-common/grouping_iterator.c
 create mode 100644 stdio-common/grouping_iterator.h
 create mode 100644 stdio-common/tst-grouping_iterator.c

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 222c9ea63d..c9c3a4b976 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -39,6 +39,7 @@ routines := \
   gentempfd \
   getline \
   getw \
+  grouping_iterator \
   iovfscanf \
   isoc99_fscanf \
   isoc99_scanf \
@@ -211,6 +212,9 @@ tests := \
   xbug \
   # tests
 
+tests-internal = \
+  tst-grouping_iterator \
+  # tests-internal
 
 test-srcs = tst-unbputc tst-printf tst-printfsz-islongdouble
 
@@ -254,13 +258,15 @@ LOCALES := \
   hi_IN.UTF-8 \
   ja_JP.EUC-JP \
   ps_AF.UTF-8 \
-  # LOCALES
+  tg_TJ.UTF-8 \
+ # LOCALES
 include ../gen-locales.mk
 
 $(objpfx)bug14.out: $(gen-locales)
 $(objpfx)scanf13.out: $(gen-locales)
 $(objpfx)test-vfprintf.out: $(gen-locales)
 $(objpfx)tst-grouping.out: $(gen-locales)
+$(objpfx)tst-grouping_iterator.out: $(gen-locales)
 $(objpfx)tst-sprintf.out: $(gen-locales)
 $(objpfx)tst-sscanf.out: $(gen-locales)
 $(objpfx)tst-swprintf.out: $(gen-locales)
diff --git a/stdio-common/grouping_iterator.c b/stdio-common/grouping_iterator.c
new file mode 100644
index 0000000000..cc169e2b09
--- /dev/null
+++ b/stdio-common/grouping_iterator.c
@@ -0,0 +1,125 @@
+/* Iterator for inserting thousands separators into numbers.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <grouping_iterator.h>
+
+#include <assert.h>
+#include <limits.h>
+#include <locale/localeinfo.h>
+#include <stdint.h>
+#include <string.h>
+
+/* Initializes *IT with no grouping information for a string of length
+   DIGITS, and return false to indicate no grouping.  */
+bool
+__grouping_iterator_init_none (struct grouping_iterator *it, size_t digits)
+{
+  memset (it, 0, sizeof (*it));
+  it->remaining_in_current_group = digits;
+  it->remaining = digits;
+  return false;
+}
+
+static bool
+grouping_iterator_setup (struct grouping_iterator *it, size_t digits,
+                         const char *grouping)
+{
+  /* We treat all negative values like CHAR_MAX.  */
+
+  if (*grouping == CHAR_MAX || *grouping <= 0)
+    /* No grouping should be done.  */
+    return __grouping_iterator_init_none (it, digits);
+
+  size_t remaining_to_group = digits;
+  size_t non_repeating_groups = 0;
+  size_t groups = 0;
+  while (true)
+    {
+      non_repeating_groups += *grouping;
+      if (remaining_to_group <= (unsigned int) *grouping)
+        break;
+
+      ++groups;
+      remaining_to_group -= *grouping++;
+
+      if (*grouping == CHAR_MAX
+#if CHAR_MIN < 0
+          || *grouping < 0
+#endif
+          )
+          /* No more grouping should be done.  */
+        break;
+      else if (*grouping == 0)
+        {
+          /* Same grouping repeats.  */
+          --grouping;
+          non_repeating_groups -= *grouping; /* Over-counted.  */
+          size_t repeats = (remaining_to_group - 1) / *grouping;
+          groups += repeats;
+          remaining_to_group -= repeats * *grouping;
+          break;
+        }
+    }
+
+  it->remaining_in_current_group = remaining_to_group;
+  it->remaining = digits;
+  it->groupings = grouping;
+  it->non_repeating_groups = non_repeating_groups;
+  it->separators = groups;
+  return it->separators > 0;
+}
+
+/* Returns the appropriate grouping item in LOC depending on CATEGORY
+   (which must be LC_MONETARY or LC_NUMERIC).  */
+static const char *
+get_grouping (int category, locale_t loc)
+{
+  return _nl_lookup (loc, category,
+                     category == LC_MONETARY ? MON_GROUPING : GROUPING);
+}
+
+
+bool
+__grouping_iterator_init (struct grouping_iterator *it,
+                          int category, locale_t loc, size_t digits)
+{
+  if (digits <= 1)
+    return __grouping_iterator_init_none (it, digits);
+  else
+    return grouping_iterator_setup (it, digits, get_grouping (category, loc));
+}
+
+bool
+__grouping_iterator_next (struct grouping_iterator *it)
+{
+  assert (it->remaining > 0);
+  --it->remaining;
+
+  if (it->remaining_in_current_group > 0)
+    {
+      --it->remaining_in_current_group;
+      return false;
+    }
+
+  /* If we are in the non-repeating part, switch group.  */
+  if (it->remaining < it->non_repeating_groups)
+    --it->groupings;
+
+  it->remaining_in_current_group = *it->groupings - 1;
+  return true;
+}
diff --git a/stdio-common/grouping_iterator.h b/stdio-common/grouping_iterator.h
new file mode 100644
index 0000000000..ca41a7fdc1
--- /dev/null
+++ b/stdio-common/grouping_iterator.h
@@ -0,0 +1,65 @@
+/* Iterator for grouping a number while scanning it forward.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef GROUPING_ITERATOR_H
+#define GROUPING_ITERATOR_H
+
+#include <locale.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+struct grouping_iterator
+{
+  /* Number of characters in the current group.  If this reaches zero,
+     a thousands separator needs to be emittted.  */
+  size_t remaining_in_current_group;
+
+  /* Number of characters remaining in the number.  This is used to
+     detect the start of the non-repeating groups.  */
+  size_t remaining;
+
+  /* Points to the current grouping descriptor.  */
+  const char *groupings;
+
+  /* Total number of characters in the non-repeating groups.  */
+  size_t non_repeating_groups;
+
+  /* Number of separators that will be inserted if the whole number is
+     processed.  (Does not change during iteration.)  */
+  size_t separators;
+};
+
+struct __locale_data;
+
+/* Initializes *IT with the data from LOCDATA (which must be for
+   LC_MONETARY or LC_NUMERIC).  DIGITS is the length of the number.
+   Returns true if grouping is active, false if not.  */
+bool __grouping_iterator_init (struct grouping_iterator *it,
+                               int category, locale_t loc,
+                               size_t digits) attribute_hidden;
+
+/* Initializes *IT with no grouping information for a string of length
+   DIGITS, and return false to indicate no grouping.  */
+bool __grouping_iterator_init_none (struct grouping_iterator *it, size_t digits)
+  attribute_hidden;
+
+/* Advances to the next character and returns true if a thousands
+   separator should be inserted before emitting it.  */
+bool __grouping_iterator_next (struct grouping_iterator *it);
+
+#endif /* GROUPING_ITERATOR_H */
diff --git a/stdio-common/tst-grouping_iterator.c b/stdio-common/tst-grouping_iterator.c
new file mode 100644
index 0000000000..97d8f40628
--- /dev/null
+++ b/stdio-common/tst-grouping_iterator.c
@@ -0,0 +1,262 @@
+/* Test for struct grouping_iterator.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* Rebuild the fail to access internal-only functions.  */
+#include <grouping_iterator.c>
+
+#include <stdio.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+static void
+check (int lineno, const char *groupings,
+       const char *input, const char *expected)
+{
+  if (test_verbose)
+    {
+      printf ("info: %s:%d: \"%s\" via \"", __FILE__, lineno, input);
+      for (const char *p = groupings; *p != 0; ++p)
+        printf ("\\%o", *p & 0xff);
+      printf ("\" to \"%s\"\n", expected);
+    }
+
+  size_t initial_group = strchrnul (expected, '\'') - expected;
+  size_t separators = 0;
+  for (const char *p = expected; *p != '\0'; ++p)
+    separators += *p == '\'';
+
+  size_t digits = strlen (input);
+  char *out = xmalloc (2 * digits + 1);
+
+  struct grouping_iterator it;
+  TEST_COMPARE (grouping_iterator_setup (&it, digits, groupings),
+                strchr (expected, '\'') != NULL);
+  TEST_COMPARE (it.remaining, digits);
+  TEST_COMPARE (it.remaining_in_current_group, initial_group);
+  TEST_COMPARE (it.separators, separators);
+
+  char *p = out;
+  while (*input != '\0')
+    {
+      if (__grouping_iterator_next (&it))
+        *p++ = '\'';
+      TEST_COMPARE (it.separators, separators);
+      *p++ = *input++;
+    }
+  *p++ = '\0';
+
+  TEST_COMPARE (it.remaining, 0);
+  TEST_COMPARE (it.remaining_in_current_group, 0);
+
+  TEST_COMPARE_STRING (out, expected);
+
+  free (out);
+}
+
+static int
+do_test (void)
+{
+  check (__LINE__, "", "1", "1");
+  check (__LINE__, "", "12", "12");
+  check (__LINE__, "", "123", "123");
+  check (__LINE__, "", "1234", "1234");
+
+  check (__LINE__, "\3", "1", "1");
+  check (__LINE__, "\3", "12", "12");
+  check (__LINE__, "\3", "123", "123");
+  check (__LINE__, "\3", "1234", "1'234");
+  check (__LINE__, "\3", "12345", "12'345");
+  check (__LINE__, "\3", "123456", "123'456");
+  check (__LINE__, "\3", "1234567", "1'234'567");
+  check (__LINE__, "\3", "12345678", "12'345'678");
+  check (__LINE__, "\3", "123456789", "123'456'789");
+  check (__LINE__, "\3", "1234567890", "1'234'567'890");
+
+  check (__LINE__, "\2\3", "1", "1");
+  check (__LINE__, "\2\3", "12", "12");
+  check (__LINE__, "\2\3", "123", "1'23");
+  check (__LINE__, "\2\3", "1234", "12'34");
+  check (__LINE__, "\2\3", "12345", "123'45");
+  check (__LINE__, "\2\3", "123456", "1'234'56");
+  check (__LINE__, "\2\3", "1234567", "12'345'67");
+  check (__LINE__, "\2\3", "12345678", "123'456'78");
+  check (__LINE__, "\2\3", "123456789", "1'234'567'89");
+  check (__LINE__, "\2\3", "1234567890", "12'345'678'90");
+
+  check (__LINE__, "\3\2", "1", "1");
+  check (__LINE__, "\3\2", "12", "12");
+  check (__LINE__, "\3\2", "123", "123");
+  check (__LINE__, "\3\2", "1234", "1'234");
+  check (__LINE__, "\3\2", "12345", "12'345");
+  check (__LINE__, "\3\2", "123456", "1'23'456");
+  check (__LINE__, "\3\2", "1234567", "12'34'567");
+  check (__LINE__, "\3\2", "12345678", "1'23'45'678");
+  check (__LINE__, "\3\2", "123456789", "12'34'56'789");
+  check (__LINE__, "\3\2", "1234567890", "1'23'45'67'890");
+
+  check (__LINE__, "\3\2\1", "1", "1");
+  check (__LINE__, "\3\2\1", "12", "12");
+  check (__LINE__, "\3\2\1", "123", "123");
+  check (__LINE__, "\3\2\1", "1234", "1'234");
+  check (__LINE__, "\3\2\1", "12345", "12'345");
+  check (__LINE__, "\3\2\1", "123456", "1'23'456");
+  check (__LINE__, "\3\2\1", "1234567", "1'2'34'567");
+  check (__LINE__, "\3\2\1", "12345678", "1'2'3'45'678");
+  check (__LINE__, "\3\2\1", "123456789", "1'2'3'4'56'789");
+  check (__LINE__, "\3\2\1", "1234567890", "1'2'3'4'5'67'890");
+
+  check (__LINE__, "\2\3\1", "1", "1");
+  check (__LINE__, "\2\3\1", "12", "12");
+  check (__LINE__, "\2\3\1", "123", "1'23");
+  check (__LINE__, "\2\3\1", "1234", "12'34");
+  check (__LINE__, "\2\3\1", "12345", "123'45");
+  check (__LINE__, "\2\3\1", "123456", "1'234'56");
+  check (__LINE__, "\2\3\1", "1234567", "1'2'345'67");
+  check (__LINE__, "\2\3\1", "12345678", "1'2'3'456'78");
+  check (__LINE__, "\2\3\1", "123456789", "1'2'3'4'567'89");
+  check (__LINE__, "\2\3\1", "1234567890", "1'2'3'4'5'678'90");
+
+  /* No repeats.  */
+  check (__LINE__, "\3\377", "1", "1");
+  check (__LINE__, "\3\377", "12", "12");
+  check (__LINE__, "\3\377", "123", "123");
+  check (__LINE__, "\3\377", "1234", "1'234");
+  check (__LINE__, "\3\377", "12345", "12'345");
+  check (__LINE__, "\3\377", "123456", "123'456");
+  check (__LINE__, "\3\377", "1234567", "1234'567");
+  check (__LINE__, "\3\377", "12345678", "12345'678");
+
+  check (__LINE__, "\2\3\377", "1", "1");
+  check (__LINE__, "\2\3\377", "12", "12");
+  check (__LINE__, "\2\3\377", "123", "1'23");
+  check (__LINE__, "\2\3\377", "1234", "12'34");
+  check (__LINE__, "\2\3\377", "12345", "123'45");
+  check (__LINE__, "\2\3\377", "123456", "1'234'56");
+  check (__LINE__, "\2\3\377", "1234567", "12'345'67");
+  check (__LINE__, "\2\3\377", "12345678", "123'456'78");
+  check (__LINE__, "\2\3\377", "123456789", "1234'567'89");
+  check (__LINE__, "\2\3\377", "1234567890", "12345'678'90");
+
+  check (__LINE__, "\3\2\377", "1", "1");
+  check (__LINE__, "\3\2\377", "12", "12");
+  check (__LINE__, "\3\2\377", "123", "123");
+  check (__LINE__, "\3\2\377", "1234", "1'234");
+  check (__LINE__, "\3\2\377", "12345", "12'345");
+  check (__LINE__, "\3\2\377", "123456", "1'23'456");
+  check (__LINE__, "\3\2\377", "1234567", "12'34'567");
+  check (__LINE__, "\3\2\377", "12345678", "123'45'678");
+  check (__LINE__, "\3\2\377", "123456789", "1234'56'789");
+  check (__LINE__, "\3\2\377", "1234567890", "12345'67'890");
+
+  /* Locale-based tests.  */
+
+  locale_t loc;
+  struct lc_ctype_data *ctype;
+  struct grouping_iterator it;
+
+  loc = newlocale (LC_ALL_MASK, "de_DE.UTF-8", 0);
+  TEST_VERIFY_EXIT (loc != 0);
+  ctype = loc->__locales[LC_CTYPE]->private;
+  TEST_VERIFY (!ctype->outdigit_translation_needed);
+  for (int i = 0; i <= 9; ++i)
+    TEST_COMPARE (ctype->outdigit_bytes[i], 1);
+  TEST_COMPARE (ctype->outdigit_bytes_all_equal, 1);
+  TEST_COMPARE (__grouping_iterator_init (&it, LC_NUMERIC, loc, 8), true);
+  TEST_COMPARE (it.remaining_in_current_group, 2);
+  TEST_COMPARE (it.remaining, 8);
+  TEST_COMPARE (*it.groupings, 3);
+  TEST_COMPARE (it.non_repeating_groups, 3); /* Locale duplicates 3.  */
+  TEST_COMPARE (it.separators, 2);
+  TEST_COMPARE (__grouping_iterator_init (&it, LC_MONETARY, loc, 8), true);
+  TEST_COMPARE (it.remaining_in_current_group, 2);
+  TEST_COMPARE (it.remaining, 8);
+  TEST_COMPARE (*it.groupings, 3);
+  TEST_COMPARE (it.non_repeating_groups, 3); /* Locale duplicates 3.  */
+  TEST_COMPARE (it.separators, 2);
+  freelocale (loc);
+
+  loc = newlocale (LC_ALL_MASK, "tg_TJ.UTF-8", 0);
+  TEST_VERIFY_EXIT (loc != 0);
+  ctype = loc->__locales[LC_CTYPE]->private;
+  TEST_VERIFY (!ctype->outdigit_translation_needed);
+  for (int i = 0; i <= 9; ++i)
+    TEST_COMPARE (ctype->outdigit_bytes[i], 1);
+  TEST_COMPARE (ctype->outdigit_bytes_all_equal, 1);
+  TEST_COMPARE (__grouping_iterator_init (&it, LC_NUMERIC, loc, 8), true);
+  TEST_COMPARE (it.remaining_in_current_group, 2);
+  TEST_COMPARE (it.remaining, 8);
+  TEST_COMPARE (*it.groupings, 3);
+  TEST_COMPARE (it.non_repeating_groups, 3); /* Locale duplicates 3.  */
+  TEST_COMPARE (it.separators, 2);
+  TEST_COMPARE (__grouping_iterator_init (&it, LC_MONETARY, loc, 8), true);
+  TEST_COMPARE (it.remaining_in_current_group, 2);
+  TEST_COMPARE (it.remaining, 8);
+  TEST_COMPARE (*it.groupings, 3);
+  TEST_COMPARE (it.non_repeating_groups, 3); /* Locale duplicates 3.  */
+  TEST_COMPARE (it.separators, 2);
+  freelocale (loc);
+
+  loc = newlocale (LC_ALL_MASK, "hi_IN.UTF-8", 0);
+  TEST_VERIFY_EXIT (loc != 0);
+  ctype = loc->__locales[LC_CTYPE]->private;
+  TEST_VERIFY (ctype->outdigit_translation_needed);
+  for (int i = 0; i <= 9; ++i)
+    /* Locale uses Devanagari digits.  */
+    TEST_COMPARE (ctype->outdigit_bytes[i], 3);
+  TEST_COMPARE (ctype->outdigit_bytes_all_equal, 3);
+  TEST_COMPARE (__grouping_iterator_init (&it, LC_NUMERIC, loc, 8), true);
+  TEST_COMPARE (it.remaining_in_current_group, 2);
+  TEST_COMPARE (it.remaining, 8);
+  TEST_COMPARE (*it.groupings, 3);
+  TEST_COMPARE (it.non_repeating_groups, 0);
+  TEST_COMPARE (it.separators, 2);
+  TEST_COMPARE (__grouping_iterator_init (&it, LC_MONETARY, loc, 8), true);
+  TEST_COMPARE (it.remaining_in_current_group, 1);
+  TEST_COMPARE (it.remaining, 8);
+  TEST_COMPARE (*it.groupings, 2);
+  TEST_COMPARE (it.non_repeating_groups, 3);
+  TEST_COMPARE (it.separators, 3);
+  freelocale (loc);
+
+  loc = newlocale (LC_ALL_MASK, "ps_AF.UTF-8", 0);
+  TEST_VERIFY_EXIT (loc != 0);
+  ctype = loc->__locales[LC_CTYPE]->private;
+  TEST_VERIFY (ctype->outdigit_translation_needed);
+  for (int i = 0; i <= 9; ++i)
+    /* Locale uses non-ASCII digits.  */
+    TEST_COMPARE (ctype->outdigit_bytes[i], 2);
+  TEST_COMPARE (ctype->outdigit_bytes_all_equal, 2);
+  TEST_COMPARE (__grouping_iterator_init (&it, LC_NUMERIC, loc, 8), true);
+  TEST_COMPARE (it.remaining_in_current_group, 2);
+  TEST_COMPARE (it.remaining, 8);
+  TEST_COMPARE (*it.groupings, 3);
+  TEST_COMPARE (it.non_repeating_groups, 0);
+  TEST_COMPARE (it.separators, 2);
+  TEST_COMPARE (__grouping_iterator_init (&it, LC_MONETARY, loc, 8), true);
+  TEST_COMPARE (it.remaining_in_current_group, 2);
+  TEST_COMPARE (it.remaining, 8);
+  TEST_COMPARE (*it.groupings, 3);
+  TEST_COMPARE (it.non_repeating_groups, 0);
+  TEST_COMPARE (it.separators, 2);
+  freelocale (loc);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 17/26] stdio-common: Introduce buffers for implementing printf
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (15 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 16/26] locale: Implement struct grouping_iterator Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:31 ` [PATCH 18/26] stdio-common: Add __printf_function_invoke Florian Weimer
                   ` (8 subsequent siblings)
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

These buffers will eventually be used instead of FILE * objects
to implement printf functions.  The multibyte buffer is struct
__printf_buffer, the wide buffer is struct __wprintf_buffer.

To enable writing type-generic code, the header files
printf_buffer-char.h and printf_buffer-wchar_t.h define the
Xprintf macro differently, enabling Xprintf (buffer) to stand
for __printf_buffer and __wprintf_buffer as appropriate.  For
common cases, macros like Xprintf_buffer are provided as a more
syntactically convenient shortcut.

Buffer-specific flush callbacks are implemented with a switch
statement instead of a function pointer, to avoid hardening issues
similar to those of libio vtables.  struct __printf_buffer_as_file
is needed to support custom printf specifiers because the public
interface for that requires passing a FILE *, which is why there
is a trapdoor back from these buffers to FILE * streams.

Since the immediate user of these interfaces knows when processing
has finished, there is no flush callback for the end of processing,
only a flush callback for the intermediate buffer flush.
---
 include/printf_buffer.h               | 281 ++++++++++++++++++++++++++
 stdio-common/Makefile                 |  16 ++
 stdio-common/Xprintf_buffer_done.c    |  40 ++++
 stdio-common/Xprintf_buffer_flush.c   |  72 +++++++
 stdio-common/Xprintf_buffer_pad_1.c   |  44 ++++
 stdio-common/Xprintf_buffer_putc_1.c  |  29 +++
 stdio-common/Xprintf_buffer_puts_1.c  |  37 ++++
 stdio-common/Xprintf_buffer_write.c   |  43 ++++
 stdio-common/printf_buffer-char.h     |  24 +++
 stdio-common/printf_buffer-wchar_t.h  |  24 +++
 stdio-common/printf_buffer_as_file.c  | 148 ++++++++++++++
 stdio-common/printf_buffer_as_file.h  |  87 ++++++++
 stdio-common/printf_buffer_done.c     |  21 ++
 stdio-common/printf_buffer_flush.c    |  42 ++++
 stdio-common/printf_buffer_pad_1.c    |  21 ++
 stdio-common/printf_buffer_putc_1.c   |  21 ++
 stdio-common/printf_buffer_puts_1.c   |  21 ++
 stdio-common/printf_buffer_to_file.c  | 122 +++++++++++
 stdio-common/printf_buffer_to_file.h  |  57 ++++++
 stdio-common/printf_buffer_write.c    |  21 ++
 stdio-common/wprintf_buffer_as_file.c | 153 ++++++++++++++
 stdio-common/wprintf_buffer_done.c    |  21 ++
 stdio-common/wprintf_buffer_flush.c   |  36 ++++
 stdio-common/wprintf_buffer_pad_1.c   |  21 ++
 stdio-common/wprintf_buffer_putc_1.c  |  21 ++
 stdio-common/wprintf_buffer_puts_1.c  |  21 ++
 stdio-common/wprintf_buffer_to_file.c |  55 +++++
 stdio-common/wprintf_buffer_write.c   |  21 ++
 28 files changed, 1520 insertions(+)
 create mode 100644 include/printf_buffer.h
 create mode 100644 stdio-common/Xprintf_buffer_done.c
 create mode 100644 stdio-common/Xprintf_buffer_flush.c
 create mode 100644 stdio-common/Xprintf_buffer_pad_1.c
 create mode 100644 stdio-common/Xprintf_buffer_putc_1.c
 create mode 100644 stdio-common/Xprintf_buffer_puts_1.c
 create mode 100644 stdio-common/Xprintf_buffer_write.c
 create mode 100644 stdio-common/printf_buffer-char.h
 create mode 100644 stdio-common/printf_buffer-wchar_t.h
 create mode 100644 stdio-common/printf_buffer_as_file.c
 create mode 100644 stdio-common/printf_buffer_as_file.h
 create mode 100644 stdio-common/printf_buffer_done.c
 create mode 100644 stdio-common/printf_buffer_flush.c
 create mode 100644 stdio-common/printf_buffer_pad_1.c
 create mode 100644 stdio-common/printf_buffer_putc_1.c
 create mode 100644 stdio-common/printf_buffer_puts_1.c
 create mode 100644 stdio-common/printf_buffer_to_file.c
 create mode 100644 stdio-common/printf_buffer_to_file.h
 create mode 100644 stdio-common/printf_buffer_write.c
 create mode 100644 stdio-common/wprintf_buffer_as_file.c
 create mode 100644 stdio-common/wprintf_buffer_done.c
 create mode 100644 stdio-common/wprintf_buffer_flush.c
 create mode 100644 stdio-common/wprintf_buffer_pad_1.c
 create mode 100644 stdio-common/wprintf_buffer_putc_1.c
 create mode 100644 stdio-common/wprintf_buffer_puts_1.c
 create mode 100644 stdio-common/wprintf_buffer_to_file.c
 create mode 100644 stdio-common/wprintf_buffer_write.c

diff --git a/include/printf_buffer.h b/include/printf_buffer.h
new file mode 100644
index 0000000000..6be7723869
--- /dev/null
+++ b/include/printf_buffer.h
@@ -0,0 +1,281 @@
+/* Multibyte and wide buffers for implementing printf-related functions.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* The naming of the multibyte and wide variants is intentionally
+   consistent, so that it is possible to use the Xprintf macro in
+   stdio-common/printf_buffer-char.h and
+   stdio-common/printf_buffer-wchar_t.h to select between them in
+   type-generic code.  */
+
+#ifndef PRINTF_BUFFER_H
+#define PRINTF_BUFFER_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <wchar.h>
+
+/* <printf_buffer_as_file.h> introduces a way to use struct
+   __printf_buffer objects from FILE * streams.  To avoid storing a
+   function pointer (or vtable pointer) in struct __printf_buffer
+   (which would defeat libio vtable hardening), a switch statement
+   over the different flush implementations is used to implement
+   __printf_buffer_flush.
+
+   __printf_buffer_mode_failed is special: it is the sticky failure
+   indicator.  Unlike struct alloc_buffer, this is not folded into
+   write_ptr, so that snprintf and other string-writing functions can
+   discover the end of the string even in the error case, to be able
+   to add the null terminator.  */
+enum __printf_buffer_mode
+  {
+    __printf_buffer_mode_failed,
+    __printf_buffer_mode_to_file,
+  };
+
+/* Buffer for fast character writing with overflow handling.
+   Typically embedded in another struct with further data that is used
+   by the flush function.  */
+struct __printf_buffer
+{
+  /* These pointer members follow FILE streams.  write_ptr and
+     write_end must be initialized to cover the target buffer.  See
+     __printf_buffer_init.
+
+     Data can be written directly to *write_ptr while write_ptr !=
+     write_end, and write_ptr can be advanced accordingly.  Note that
+     is not possible to use the apparently-unused part of the buffer
+     as scratch space because sprintf (and snprintf, but that is a bit
+     iffy) must only write the minimum number of characters produced
+     by the format string and its arguments.
+
+     write_base must be initialized to be equal to write_ptr.  The
+     framework uses this pointer to compute the total number of
+     written bytes, together with the written field.  See
+     __printf_buffer_done.
+
+     write_base and write_end are only read by the generic functions
+     after initialization, only the flush implementation called from
+     __printf_buffer_flush might change these pointers.  See the
+     comment on Xprintf (buffer_do_flush) in Xprintf_buffer_flush.c
+     for details regarding the flush operation.  */
+  char *write_base;
+  char *write_ptr;
+  char *write_end;
+
+  /* Number of characters written so far (excluding the current
+     buffer).  Potentially updated on flush.  The actual number of
+     written bytes also inclused the unflushed-but-written buffer
+     part, write_ptr - write_base.  A 64-bit value is used to avoid
+     the need for overflow checks.  */
+  uint64_t written;
+
+  /* Identifies the flush callback.  */
+  enum __printf_buffer_mode mode;
+};
+
+/* Marks the buffer as failed, so that __printf_buffer_has_failed
+   returns true and future flush operations are no-ops.  */
+static inline void
+__printf_buffer_mark_failed (struct __printf_buffer *buf)
+{
+  buf->mode = __printf_buffer_mode_failed;
+}
+
+/* Returns true if the sticky error indicator of the buffer has been
+   set to failed.  */
+static inline bool __attribute_warn_unused_result__
+__printf_buffer_has_failed (struct __printf_buffer *buf)
+{
+  return buf->mode == __printf_buffer_mode_failed;
+}
+
+/* Initialization of a buffer, using the memory region from [BASE, BASE +LEN)
+   as the initial buffer contents.  LEN can be zero.  */
+static inline void
+__printf_buffer_init (struct __printf_buffer *buf, char *base, size_t len,
+                      enum __printf_buffer_mode mode)
+{
+  buf->write_base = base;
+  buf->write_ptr = base;
+  buf->write_end = base + len;
+  buf->written = 0;
+  buf->mode = mode;
+}
+
+/* Called by printf_buffer_putc for a full buffer.  */
+void __printf_buffer_putc_1 (struct __printf_buffer *buf, char ch)
+  attribute_hidden;
+
+/* Writes CH to BUF.  */
+static inline void
+__printf_buffer_putc (struct __printf_buffer *buf, char ch)
+{
+  if (buf->write_ptr != buf->write_end)
+      *buf->write_ptr++ = ch;
+  else
+    __printf_buffer_putc_1 (buf, ch);
+}
+
+/* Writes COUNT repeats of CH to BUF.  */
+void __printf_buffer_pad_1 (struct __printf_buffer *buf,
+                            char ch, size_t count) attribute_hidden;
+
+/* __printf_buffer_pad with fast path for no padding.  COUNT is a
+   long int, to accomodate signed uses in printf and elsewhere.  */
+static inline void
+__printf_buffer_pad (struct __printf_buffer *buf, char ch, long int count)
+{
+  if (count > 0)
+    __printf_buffer_pad_1 (buf, ch, count);
+}
+
+/* Write COUNT bytes starting at S to BUF.  S must not overlap with
+   the internal buffer.  */
+void __printf_buffer_write (struct __printf_buffer *buf, const char *s,
+                            size_t count) attribute_hidden;
+
+/* Write S to BUF.  S must not overlap with the internal buffer.  */
+void __printf_buffer_puts_1 (struct __printf_buffer *buf, const char *s)
+  attribute_hidden;
+static inline void
+__printf_buffer_puts (struct __printf_buffer *buf, const char *s)
+{
+  if (__builtin_constant_p (__builtin_strlen (s)))
+    __printf_buffer_write (buf, s, __builtin_strlen (s));
+  else
+    __printf_buffer_puts_1 (buf, s);
+}
+
+/* Returns the number of bytes written through the buffer, or -1 if
+   there was an error (that is, __printf_buffer_has_failed (BUF) is true).
+
+   The number of written bytes includes pending bytes in the buffer
+   (between BUF->write_base and BUF->write_ptr).
+
+   If the number is larger than INT_MAX, returns -1 and sets errno to
+   EOVERFLOW.  This function does not flush the buffer.  If the caller
+   needs the side effect of flushing, it has to do this
+   separately.  */
+int __printf_buffer_done (struct __printf_buffer *buf) attribute_hidden;
+
+/* Internally used to call the flush function.  This can be called
+   explicitly for certain modes to flush the buffer prematuraly.  In
+   such cases, it is often the case that the buffer mode is statically
+   known, and the flush implementation can be called directly.  */
+bool __printf_buffer_flush (struct __printf_buffer *buf) attribute_hidden;
+
+/* Wide version of struct __printf_buffer follows.  */
+
+enum __wprintf_buffer_mode
+  {
+    __wprintf_buffer_mode_failed,
+    __wprintf_buffer_mode_to_file,
+  };
+
+struct __wprintf_buffer
+{
+  wchar_t *write_base;
+  wchar_t *write_ptr;
+  wchar_t *write_end;
+  uint64_t written;
+  enum __wprintf_buffer_mode mode;
+};
+
+static inline void
+__wprintf_buffer_mark_failed (struct __wprintf_buffer *buf)
+{
+  buf->mode = __wprintf_buffer_mode_failed;
+}
+
+static inline bool __attribute_warn_unused_result__
+__wprintf_buffer_has_failed (struct __wprintf_buffer *buf)
+{
+  return buf->mode == __wprintf_buffer_mode_failed;
+}
+
+static inline void
+__wprintf_buffer_init (struct __wprintf_buffer *buf,
+                       wchar_t *base, size_t len,
+                       enum __wprintf_buffer_mode mode)
+{
+  buf->write_base = base;
+  buf->write_ptr = base;
+  buf->write_end = base + len;
+  buf->written = 0;
+  buf->mode = mode;
+}
+
+void __wprintf_buffer_putc_1 (struct __wprintf_buffer *buf, wchar_t ch)
+  attribute_hidden;
+
+static inline void
+__wprintf_buffer_putc (struct __wprintf_buffer *buf, wchar_t ch)
+{
+  if (buf->write_ptr != buf->write_end)
+      *buf->write_ptr++ = ch;
+  else
+    __wprintf_buffer_putc_1 (buf, ch);
+}
+
+void __wprintf_buffer_pad_1 (struct __wprintf_buffer *buf,
+                             wchar_t ch, size_t count) attribute_hidden;
+
+static inline void
+__wprintf_buffer_pad (struct __wprintf_buffer *buf, char ch, long int count)
+{
+  if (count > 0)
+    __wprintf_buffer_pad_1 (buf, ch, count);
+}
+
+void __wprintf_buffer_write (struct __wprintf_buffer *buf, const wchar_t *s,
+                             size_t count) attribute_hidden;
+
+void __wprintf_buffer_puts (struct __wprintf_buffer *buf, const wchar_t *s)
+  attribute_hidden;
+
+int __wprintf_buffer_done (struct __wprintf_buffer *buf) attribute_hidden;
+
+bool __wprintf_buffer_flush (struct __wprintf_buffer *buf) attribute_hidden;
+
+/* Type-generic convenience macros.  They are useful if
+   printf_buffer-char.h or printf_buffer-wchar_t.h is included as
+   well.  */
+
+#define Xprintf_buffer Xprintf (buffer)
+#define Xprintf_buffer_done Xprintf (buffer_done)
+#define Xprintf_buffer_flush Xprintf (buffer_flush)
+#define Xprintf_buffer_has_failed Xprintf (buffer_has_failed)
+#define Xprintf_buffer_mark_failed Xprintf (buffer_mark_failed)
+#define Xprintf_buffer_pad Xprintf (buffer_pad)
+#define Xprintf_buffer_putc Xprintf (buffer_putc)
+#define Xprintf_buffer_puts Xprintf (buffer_puts)
+#define Xprintf_buffer_write Xprintf (buffer_write)
+
+/* Flush function implementations follow.  They are called from
+   __printf_buffer_flush.  Generic code should not call these flush
+   functions directly.  Some modes have inline implementations.  */
+
+struct __printf_buffer_to_file;
+void __printf_buffer_flush_to_file (struct __printf_buffer_to_file *)
+  attribute_hidden;
+
+struct __wprintf_buffer_to_file;
+void __wprintf_buffer_flush_to_file (struct __wprintf_buffer_to_file *)
+  attribute_hidden;
+
+#endif /* PRINTF_BUFFER_H */
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index c9c3a4b976..fa493a24af 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -53,6 +53,14 @@ routines := \
   perror \
   printf \
   printf-prs \
+  printf_buffer_as_file \
+  printf_buffer_done \
+  printf_buffer_flush \
+  printf_buffer_pad_1 \
+  printf_buffer_putc_1 \
+  printf_buffer_puts_1 \
+  printf_buffer_to_file \
+  printf_buffer_write \
   printf_fp \
   printf_fphex \
   printf_size \
@@ -85,6 +93,14 @@ routines := \
   vfwscanf \
   vfwscanf-internal \
   vprintf \
+  wprintf_buffer_as_file \
+  wprintf_buffer_done \
+  wprintf_buffer_flush \
+  wprintf_buffer_pad_1 \
+  wprintf_buffer_putc_1 \
+  wprintf_buffer_puts_1 \
+  wprintf_buffer_to_file \
+  wprintf_buffer_write \
   # routines
 
 aux := \
diff --git a/stdio-common/Xprintf_buffer_done.c b/stdio-common/Xprintf_buffer_done.c
new file mode 100644
index 0000000000..c7551979af
--- /dev/null
+++ b/stdio-common/Xprintf_buffer_done.c
@@ -0,0 +1,40 @@
+/* Final status reporting for struct __*printf_buffer.  Generic version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+
+int
+Xprintf_buffer_done (struct Xprintf_buffer *buf)
+{
+  if (Xprintf_buffer_has_failed (buf))
+    return -1;
+
+  /* Use uintptr_t to deal with ptrdiff_t overflows (to some degree).  */
+  uintptr_t written_current = buf->write_ptr - buf->write_base;
+  unsigned int written_total;
+  if (__builtin_add_overflow (buf->written, written_current, &written_total)
+      || buf->written > INT_MAX)
+    {
+      __set_errno (EOVERFLOW);
+      return -1;
+    }
+  else
+    return written_total;
+}
diff --git a/stdio-common/Xprintf_buffer_flush.c b/stdio-common/Xprintf_buffer_flush.c
new file mode 100644
index 0000000000..1368cfe684
--- /dev/null
+++ b/stdio-common/Xprintf_buffer_flush.c
@@ -0,0 +1,72 @@
+/* Flush wrapper for struct __*printf_buffer.  Generic version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+
+#include <assert.h>
+#include <stdint.h>
+
+/* Xprintf (buffer_do_flush) (BUF) performs the flush operation.  The
+   actual implementation is specific to the multibyte and wide
+   variants.
+
+   If the flush fails, Xprintf_buffer_mark_failed (BUF) must be
+   called, and BUF->write_ptr and BUF->write_end can be left
+   unchanged.
+
+   The function must not do anything if failure has already occurred,
+   that is, if BUF->mode == Xprintf (buffer_mode_failed).
+
+   The framework implicitly invokes flush with BUF->write_ptr ==
+   BUF->write_end only.  (This is particularly relevant to the
+   __sprintf_chk flush, which just calls __chk_fail.)  But in some
+   cases, Xprintf_buffer_flush may be called explicitly (when
+   BUF->mode/the backing function is known).  In that case, it is
+   possible that BUF->write_ptr < BUF->write_end is true.
+
+   If the flush succeeds, the pointers are changed so that
+   BUF->write_ptr < BUF->write_end.  It is possible to switch to a
+   completely different buffer here.  If the buffer is moved, it may
+   be necessary to updated BUF->write_base and BUF->written from the
+   flush function as well.
+
+   Note that when chaining buffers, in the flush function for the
+   outer buffer (to which data is written first), it is necessary to
+   check for BUF->next->failed (for the inner buffer) and set
+   BUF->base.failed to true (for the outer buffer).  This should come
+   towards the end of the outer flush function.  Usually, there is
+   also some unwrapping step afterwards; it has to check the outer
+   buffer (BUF->base.failed) and propagate any error to the inner
+   buffer (BUF->next->failed), so essentially in the other
+   direction.  */
+static void Xprintf (buffer_do_flush) (struct Xprintf_buffer *buf);
+
+bool
+Xprintf_buffer_flush (struct Xprintf_buffer *buf)
+{
+  if (__glibc_unlikely (Xprintf_buffer_has_failed (buf)))
+    return false;
+
+  Xprintf (buffer_do_flush) (buf);
+  if (Xprintf_buffer_has_failed (buf))
+    return false;
+
+  /* Ensure that the flush has made available some bytes.  */
+  assert (buf->write_ptr != buf->write_end);
+  return true;
+}
diff --git a/stdio-common/Xprintf_buffer_pad_1.c b/stdio-common/Xprintf_buffer_pad_1.c
new file mode 100644
index 0000000000..2be8270ad2
--- /dev/null
+++ b/stdio-common/Xprintf_buffer_pad_1.c
@@ -0,0 +1,44 @@
+/* Write repeated characters to struct __*printf_buffer.  Generic version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <string.h>
+
+void
+Xprintf (buffer_pad_1) (struct Xprintf_buffer *buf, CHAR_T ch, size_t count)
+{
+  if (__glibc_unlikely (Xprintf_buffer_has_failed (buf)))
+    return;
+
+  do
+    {
+      /* Proactively make room.  __*printf_buffer_pad has already
+         checked for a zero-length write, so this function is only
+         called when there is actually data to write.  */
+      if (buf->write_ptr == buf->write_end && !Xprintf_buffer_flush (buf))
+        return;
+      assert (buf->write_ptr != buf->write_end);
+      size_t to_fill = buf->write_end - buf->write_ptr;
+      if (to_fill > count)
+        to_fill = count;
+      MEMSET (buf->write_ptr, ch, to_fill);
+      buf->write_ptr += to_fill;
+      count -= to_fill;
+    }
+  while (count > 0);
+}
diff --git a/stdio-common/Xprintf_buffer_putc_1.c b/stdio-common/Xprintf_buffer_putc_1.c
new file mode 100644
index 0000000000..3458775a1c
--- /dev/null
+++ b/stdio-common/Xprintf_buffer_putc_1.c
@@ -0,0 +1,29 @@
+/* Overflow write function for struct __*printf_buffer.  Generic version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+
+void
+Xprintf (buffer_putc_1) (struct Xprintf_buffer *buf, CHAR_T ch)
+{
+  if (__glibc_unlikely (Xprintf_buffer_has_failed (buf))
+      || !Xprintf_buffer_flush (buf))
+    return;
+  assert (buf->write_ptr < buf->write_end);
+  *buf->write_ptr++ = ch;
+}
diff --git a/stdio-common/Xprintf_buffer_puts_1.c b/stdio-common/Xprintf_buffer_puts_1.c
new file mode 100644
index 0000000000..d8ee7ef47e
--- /dev/null
+++ b/stdio-common/Xprintf_buffer_puts_1.c
@@ -0,0 +1,37 @@
+/* String write function for struct __*printf_buffer.  Generic version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <string.h>
+
+void
+Xprintf (buffer_puts_1) (struct Xprintf_buffer *buf, const CHAR_T *s)
+{
+  if (__glibc_unlikely (Xprintf_buffer_has_failed (buf)))
+    return;
+
+  while (*s != 0)
+    {
+      if (buf->write_ptr == buf->write_end && !Xprintf_buffer_flush (buf))
+        return;
+      assert (buf->write_ptr != buf->write_end);
+      size_t to_copy = STRNLEN (s, buf->write_end - buf->write_ptr);
+      buf->write_ptr = MEMPCPY (buf->write_ptr, s, to_copy);
+      s += to_copy;
+    }
+}
diff --git a/stdio-common/Xprintf_buffer_write.c b/stdio-common/Xprintf_buffer_write.c
new file mode 100644
index 0000000000..fc6ad5fdd1
--- /dev/null
+++ b/stdio-common/Xprintf_buffer_write.c
@@ -0,0 +1,43 @@
+/* Blob write function for struct __*printf_buffer.  Generic version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+
+#include <assert.h>
+#include <string.h>
+
+void
+Xprintf_buffer_write (struct Xprintf_buffer *buf,
+                        const CHAR_T *s, size_t count)
+{
+  if (__glibc_unlikely (Xprintf_buffer_has_failed (buf)))
+    return;
+
+  while (count > 0)
+    {
+      if (buf->write_ptr == buf->write_end && !Xprintf_buffer_flush (buf))
+        return;
+      assert (buf->write_ptr != buf->write_end);
+      size_t to_copy = buf->write_end - buf->write_ptr;
+      if (to_copy > count)
+        to_copy = count;
+      buf->write_ptr = MEMPCPY (buf->write_ptr, s, to_copy);
+      s += to_copy;
+      count -= to_copy;
+    }
+}
diff --git a/stdio-common/printf_buffer-char.h b/stdio-common/printf_buffer-char.h
new file mode 100644
index 0000000000..88eb326a45
--- /dev/null
+++ b/stdio-common/printf_buffer-char.h
@@ -0,0 +1,24 @@
+/* Macros for the multibyte (char) implementation of struct __printf_buffer.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#define Xprintf(n) __printf_##n
+
+#define CHAR_T char
+#define MEMPCPY __mempcpy
+#define MEMSET memset
+#define STRNLEN __strnlen
diff --git a/stdio-common/printf_buffer-wchar_t.h b/stdio-common/printf_buffer-wchar_t.h
new file mode 100644
index 0000000000..7597dc2eab
--- /dev/null
+++ b/stdio-common/printf_buffer-wchar_t.h
@@ -0,0 +1,24 @@
+/* Macros for wide (wchar_t) implementation of struct __wprintf_buffer.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#define Xprintf(n) __wprintf_##n
+
+#define CHAR_T wchar_t
+#define MEMPCPY __wmempcpy
+#define MEMSET __wmemset
+#define STRNLEN __wcsnlen
diff --git a/stdio-common/printf_buffer_as_file.c b/stdio-common/printf_buffer_as_file.c
new file mode 100644
index 0000000000..f27b000d78
--- /dev/null
+++ b/stdio-common/printf_buffer_as_file.c
@@ -0,0 +1,148 @@
+/* FILE * interface to a struct __printf_buffer.  Multibyte version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer_as_file.h>
+
+#include <assert.h>
+#include <printf_buffer.h>
+
+/* Commit the data directly written through the stdio stream.  */
+static void
+__printf_buffer_as_file_commit (struct __printf_buffer_as_file *file)
+{
+  /* Check that the write pointers in the file stream are consistent
+     with the next buffer.  */
+  assert (file->stream._IO_write_ptr >= file->next->write_ptr);
+  assert (file->stream._IO_write_ptr <= file->next->write_end);
+  assert (file->stream._IO_write_base == file->next->write_base);
+  assert (file->stream._IO_write_end == file->next->write_end);
+
+  file->next->write_ptr = file->stream._IO_write_ptr;
+}
+
+/* Pointer the FILE * write buffer into the active printf_buffer
+   area.  */
+static void
+__printf_buffer_as_file_switch_to_buffer (struct __printf_buffer_as_file *file)
+{
+  file->stream._IO_write_base = file->next->write_base;
+  file->stream._IO_write_ptr = file->next->write_ptr;
+  file->stream._IO_write_end = file->next->write_end;
+}
+
+/* Only a small subset of the vtable functions is implemented here,
+   following _IO_obstack_jumps.  */
+
+static int
+__printf_buffer_as_file_overflow (FILE *fp, int ch)
+{
+  struct __printf_buffer_as_file *file = (struct __printf_buffer_as_file *) fp;
+
+  __printf_buffer_as_file_commit (file);
+
+  /* EOF means only a flush is requested.   */
+  if (ch != EOF)
+    __printf_buffer_putc (file->next, ch);
+
+  /* Ensure that flushing actually produces room.  */
+  if (!__printf_buffer_has_failed (file->next)
+      && file->next->write_ptr == file->next->write_end)
+    __printf_buffer_flush (file->next);
+
+  __printf_buffer_as_file_switch_to_buffer (file);
+
+  if (!__printf_buffer_has_failed (file->next))
+    return (unsigned char) ch;
+  else
+    return EOF;
+}
+
+static size_t
+__printf_buffer_as_file_xsputn (FILE *fp, const void *buf, size_t len)
+{
+  struct __printf_buffer_as_file *file = (struct __printf_buffer_as_file *) fp;
+
+  __printf_buffer_as_file_commit (file);
+
+  /* Copy the data.  */
+  __printf_buffer_write (file->next, buf, len);
+
+  __printf_buffer_as_file_switch_to_buffer (file);
+
+  if (!__printf_buffer_has_failed (file->next))
+    return len;
+  else
+    /* We may actually have written something.  But the stream is
+       corrupted in this case anyway, so try not to divine the write
+       count here.  */
+    return 0;
+}
+
+static const struct _IO_jump_t _IO_printf_buffer_as_file_jumps libio_vtable =
+{
+  JUMP_INIT_DUMMY,
+  JUMP_INIT(finish, NULL),
+  JUMP_INIT(overflow, __printf_buffer_as_file_overflow),
+  JUMP_INIT(underflow, NULL),
+  JUMP_INIT(uflow, NULL),
+  JUMP_INIT(pbackfail, NULL),
+  JUMP_INIT(xsputn, __printf_buffer_as_file_xsputn),
+  JUMP_INIT(xsgetn, NULL),
+  JUMP_INIT(seekoff, NULL),
+  JUMP_INIT(seekpos, NULL),
+  JUMP_INIT(setbuf, NULL),
+  JUMP_INIT(sync, NULL),
+  JUMP_INIT(doallocate, NULL),
+  JUMP_INIT(read, NULL),
+  JUMP_INIT(write, NULL),
+  JUMP_INIT(seek, NULL),
+  JUMP_INIT(close, NULL),
+  JUMP_INIT(stat, NULL),
+  JUMP_INIT(showmanyc, NULL),
+  JUMP_INIT(imbue, NULL)
+};
+
+void
+__printf_buffer_as_file_init (struct __printf_buffer_as_file *file,
+                              struct __printf_buffer *next)
+{
+  file->stream._lock = NULL;
+  _IO_no_init (&file->stream, _IO_USER_LOCK, -1, NULL, NULL);
+  file->vtable = &_IO_printf_buffer_as_file_jumps;
+
+  /* Set up the write buffer from the next buffer.  */
+  file->next = next;
+  __printf_buffer_as_file_switch_to_buffer (file);
+
+  /* Mark the read area as inactive, by making all pointers equal.  */
+  file->stream._IO_read_base = file->stream._IO_write_base;
+  file->stream._IO_read_ptr = file->stream._IO_write_base;
+  file->stream._IO_read_end = file->stream._IO_write_base;
+}
+
+bool
+__printf_buffer_as_file_terminate (struct __printf_buffer_as_file *file)
+{
+  if (file->stream._flags & _IO_ERR_SEEN)
+    return false;
+  else
+    {
+      __printf_buffer_as_file_commit (file);
+      return true;
+    }
+}
diff --git a/stdio-common/printf_buffer_as_file.h b/stdio-common/printf_buffer_as_file.h
new file mode 100644
index 0000000000..ab7fa44144
--- /dev/null
+++ b/stdio-common/printf_buffer_as_file.h
@@ -0,0 +1,87 @@
+/* FILE * interface to a struct __*printf_buffer.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* Registered printf format specifier callbacks produce data via a
+   FILE * stream.  struct __printf_buffer_as_file enables vfprintf to
+   create a suitable stdio stream.  Likewise struct
+   __wprintf_buffer_as_file for vfwprintf.  */
+
+#ifndef PRINTF_BUFFER_AS_FILE_H
+#define PRINTF_BUFFER_AS_FILE_H
+
+#include <libio/libioP.h>
+
+struct __printf_buffer;
+
+struct __printf_buffer_as_file
+{
+  /* Interface to libio.  */
+  FILE stream;
+  const struct _IO_jump_t *vtable;
+
+  /* Pointer to the underlying buffer.  */
+  struct __printf_buffer *next;
+};
+
+/* Initialization *FP so that data written to its FILE * stream ends
+   up in NEXT.  */
+void __printf_buffer_as_file_init (struct __printf_buffer_as_file *fp,
+                                   struct __printf_buffer *next)
+  attribute_hidden;
+
+/* Returns the FILE * that can be used to write data to the
+   buffer.  */
+static inline FILE *
+__printf_buffer_as_file_get (struct __printf_buffer_as_file *file)
+{
+  return &file->stream;
+}
+
+/* Transfers all pending data from the FILE * to the underlying
+   buffer.  Returns true if there have been no errors. */
+bool __printf_buffer_as_file_terminate (struct __printf_buffer_as_file *)
+  attribute_hidden;
+
+/* Wide variant follows.  */
+
+struct __wprintf_buffer;
+struct __wprintf_buffer_as_file
+{
+  /* Interface to libio.  */
+  FILE stream;
+  const struct _IO_jump_t *vtable;
+  struct _IO_wide_data wide_stream;
+
+  /* Pointer to the underlying buffer.  */
+  struct __wprintf_buffer *next;
+};
+
+void __wprintf_buffer_as_file_init (struct __wprintf_buffer_as_file *fp,
+                                    struct __wprintf_buffer *next)
+  attribute_hidden;
+
+static inline FILE *
+__wprintf_buffer_as_file_get (struct __wprintf_buffer_as_file *file)
+{
+  return &file->stream;
+}
+
+bool __wprintf_buffer_as_file_terminate (struct __wprintf_buffer_as_file *)
+  attribute_hidden;
+
+#endif /* PRINTF_BUFFER_AS_FILE_H */
diff --git a/stdio-common/printf_buffer_done.c b/stdio-common/printf_buffer_done.c
new file mode 100644
index 0000000000..5f3df1daad
--- /dev/null
+++ b/stdio-common/printf_buffer_done.c
@@ -0,0 +1,21 @@
+/* Final status reporting for struct __printf_buffer.  Multibyte version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-char.h"
+#include "Xprintf_buffer_done.c"
diff --git a/stdio-common/printf_buffer_flush.c b/stdio-common/printf_buffer_flush.c
new file mode 100644
index 0000000000..9b25c0fde5
--- /dev/null
+++ b/stdio-common/printf_buffer_flush.c
@@ -0,0 +1,42 @@
+/* Flush a struct __printf_buffer.  Multibyte version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+
+#include "printf_buffer-char.h"
+#include "Xprintf_buffer_flush.c"
+
+/* The __printf_buffer_flush_* functions are defined together with
+   functions that are pulled in by strong references.  */
+#ifndef SHARED
+# pragma weak __printf_buffer_flush_to_file
+#endif /* !SHARED */
+
+static void
+__printf_buffer_do_flush (struct __printf_buffer *buf)
+{
+  switch (buf->mode)
+    {
+    case __printf_buffer_mode_failed:
+      return;
+    case __printf_buffer_mode_to_file:
+      __printf_buffer_flush_to_file ((struct __printf_buffer_to_file *) buf);
+      return;
+    }
+  __builtin_trap ();
+}
diff --git a/stdio-common/printf_buffer_pad_1.c b/stdio-common/printf_buffer_pad_1.c
new file mode 100644
index 0000000000..c4b9a7af56
--- /dev/null
+++ b/stdio-common/printf_buffer_pad_1.c
@@ -0,0 +1,21 @@
+/* Write repeated characters to struct __printf_buffer.  Multibyte version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-char.h"
+#include "Xprintf_buffer_pad_1.c"
diff --git a/stdio-common/printf_buffer_putc_1.c b/stdio-common/printf_buffer_putc_1.c
new file mode 100644
index 0000000000..a15e8d16d1
--- /dev/null
+++ b/stdio-common/printf_buffer_putc_1.c
@@ -0,0 +1,21 @@
+/* Overflow character write function for struct __printf_buffer.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-char.h"
+#include "Xprintf_buffer_putc_1.c"
diff --git a/stdio-common/printf_buffer_puts_1.c b/stdio-common/printf_buffer_puts_1.c
new file mode 100644
index 0000000000..ed97fd6b3f
--- /dev/null
+++ b/stdio-common/printf_buffer_puts_1.c
@@ -0,0 +1,21 @@
+/* String write function for struct __printf_buffer.  Multibyte version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-char.h"
+#include "Xprintf_buffer_puts_1.c"
diff --git a/stdio-common/printf_buffer_to_file.c b/stdio-common/printf_buffer_to_file.c
new file mode 100644
index 0000000000..6aeb954509
--- /dev/null
+++ b/stdio-common/printf_buffer_to_file.c
@@ -0,0 +1,122 @@
+/* Multibyte printf buffers writing data to a FILE * stream.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer_to_file.h>
+
+#include <assert.h>
+#include <array_length.h>
+#include <libio/libioP.h>
+
+/* Switch to the file buffer if possible.  If the file has write_ptr
+   == write_end, use the stage buffer instead.  */
+void
+__printf_buffer_to_file_switch (struct __printf_buffer_to_file *buf)
+{
+  if (buf->fp->_IO_write_ptr < buf->fp->_IO_write_end)
+    {
+      /* buf->fp has a buffer associated with it, so write directly to
+         it from now on.  */
+      buf->base.write_ptr = buf->fp->_IO_write_ptr;
+      buf->base.write_end = buf->fp->_IO_write_end;
+    }
+  else
+    {
+      /* Use the staging area if no buffer is available in buf->fp.  */
+      buf->base.write_ptr = buf->stage;
+      buf->base.write_end = array_end (buf->stage);
+    }
+
+  buf->base.write_base = buf->base.write_ptr;
+}
+
+void
+__printf_buffer_flush_to_file (struct __printf_buffer_to_file *buf)
+{
+  /* The bytes in the buffer are always consumed.  */
+  buf->base.written += buf->base.write_ptr - buf->base.write_base;
+
+  if (buf->base.write_end == array_end (buf->stage))
+    {
+      /* If the stage buffer is used, make a copy into the file.  The
+         stage buffer is always consumed fully, even if just partially
+         written, to ensure that the file stream has all the data.  */
+      size_t count = buf->base.write_ptr - buf->stage;
+      if ((size_t) _IO_sputn (buf->fp, buf->stage, count) != count)
+            {
+              __printf_buffer_mark_failed (&buf->base);
+              return;
+            }
+      /* buf->fp may have a buffer now.  */
+      __printf_buffer_to_file_switch (buf);
+      return;
+    }
+  else if (buf->base.write_end == buf->stage + 1)
+    {
+      /* Special one-character buffer case.  This is used to avoid
+         flush-only overflow below.  */
+      if (buf->base.write_ptr == buf->base.write_end)
+        {
+          if (__overflow (buf->fp, (unsigned char) *buf->stage) == EOF)
+            {
+              __printf_buffer_mark_failed (&buf->base);
+              return;
+            }
+          __printf_buffer_to_file_switch (buf);
+        }
+      /* Else there is nothing to write.  */
+      return;
+    }
+
+  /* We have written directly into the buf->fp buffer.  */
+  assert (buf->base.write_end == buf->fp->_IO_write_end);
+
+  /* Mark the bytes as written.  */
+  buf->fp->_IO_write_ptr = buf->base.write_ptr;
+
+  if (buf->base.write_ptr == buf->base.write_end)
+    {
+      /* The buffer in buf->fp has been filled.  This should just call
+         __overflow (buf->fp, EOF), but flush-only overflow is obscure
+         and not always correctly implemented.  See bug 28949.  Be
+         conservative and switch to a one-character buffer instead, to
+         obtain one more character for a regular __overflow call.  */
+      buf->base.write_ptr = buf->stage;
+      buf->base.write_end = buf->stage + 1;
+    }
+  /* The bytes in the file stream were already marked as written above.  */
+
+  buf->base.write_base = buf->base.write_ptr;
+}
+
+void
+__printf_buffer_to_file_init (struct __printf_buffer_to_file *buf, FILE *fp)
+{
+  __printf_buffer_init (&buf->base, buf->stage, array_length (buf->stage),
+                        __printf_buffer_mode_to_file);
+  buf->fp = fp;
+  __printf_buffer_to_file_switch (buf);
+}
+
+int
+__printf_buffer_to_file_done (struct __printf_buffer_to_file *buf)
+{
+  if (__printf_buffer_has_failed (&buf->base))
+    return -1;
+  __printf_buffer_flush_to_file (buf);
+  return __printf_buffer_done (&buf->base);
+}
diff --git a/stdio-common/printf_buffer_to_file.h b/stdio-common/printf_buffer_to_file.h
new file mode 100644
index 0000000000..97ec1d6d95
--- /dev/null
+++ b/stdio-common/printf_buffer_to_file.h
@@ -0,0 +1,57 @@
+/* Multibyte and wide printf buffers writing data to a FILE * stream.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef PRINTF_BUFFER_TO_FILE_H
+#define PRINTF_BUFFER_TO_FILE_H
+
+#include <bits/types/FILE.h>
+#include <printf_buffer.h>
+
+struct __printf_buffer_to_file
+{
+  struct __printf_buffer base;
+  FILE *fp;
+
+  /* Staging buffer.  All data goes through this buffer before
+     reaching the fp stream.  (Any buffer in fp is not yet used.)  */
+  char stage[128];
+};
+
+/* Initializes *BUF to write data to FP.  */
+void __printf_buffer_to_file_init (struct __printf_buffer_to_file *buf,
+                                   FILE *fp) attribute_hidden;
+
+/* Transfers any pending data in BUF to BUF->FP.  The return value
+   follows the printf convention (number bytes written; or -1 for error).  */
+int __printf_buffer_to_file_done (struct __printf_buffer_to_file *buf)
+  attribute_hidden;
+
+/* Wide version below.  */
+
+struct __wprintf_buffer_to_file
+{
+  struct __wprintf_buffer base;
+  FILE *fp;
+  wchar_t stage[128];
+};
+void __wprintf_buffer_to_file_init (struct __wprintf_buffer_to_file *buf,
+                                    FILE *fp) attribute_hidden;
+int __wprintf_buffer_to_file_done (struct __wprintf_buffer_to_file *buf)
+  attribute_hidden;
+
+#endif /* PRINTF_BUFFER_TO_FILE_H */
diff --git a/stdio-common/printf_buffer_write.c b/stdio-common/printf_buffer_write.c
new file mode 100644
index 0000000000..1038f69063
--- /dev/null
+++ b/stdio-common/printf_buffer_write.c
@@ -0,0 +1,21 @@
+/* Blob write function for struct __printf_buffer.  Multibyte version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-char.h"
+#include "Xprintf_buffer_write.c"
diff --git a/stdio-common/wprintf_buffer_as_file.c b/stdio-common/wprintf_buffer_as_file.c
new file mode 100644
index 0000000000..cd48a7d42a
--- /dev/null
+++ b/stdio-common/wprintf_buffer_as_file.c
@@ -0,0 +1,153 @@
+/* FILE * interface to a struct __wprintf_buffer.  Wide version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer_as_file.h>
+
+#include <assert.h>
+#include <printf_buffer.h>
+
+/* Commit the data directly written through the stdio stream.  */
+static void
+__wprintf_buffer_as_file_commit (struct __wprintf_buffer_as_file *file)
+{
+  /* Check that the write pointers in the file stream are consistent
+     with the next buffer.  */
+  assert (file->wide_stream._IO_write_ptr >= file->next->write_ptr);
+  assert (file->wide_stream._IO_write_ptr <= file->next->write_end);
+  assert (file->wide_stream._IO_write_base == file->next->write_base);
+  assert (file->wide_stream._IO_write_end == file->next->write_end);
+
+  file->next->write_ptr = file->wide_stream._IO_write_ptr;
+}
+
+/* Pointer the FILE * write buffer into the active struct __wprintf_buffer
+   area.  */
+static void
+__wprintf_buffer_as_file_switch_to_buffer (struct __wprintf_buffer_as_file *file)
+{
+  file->wide_stream._IO_write_base = file->next->write_base;
+  file->wide_stream._IO_write_ptr = file->next->write_ptr;
+  file->wide_stream._IO_write_end = file->next->write_end;
+}
+
+/* Only a small subset of the vtable functions is implemented here,
+   following _IO_obstack_jumps.  */
+
+static wint_t
+__wprintf_buffer_as_file_overflow (FILE *fp, int ch)
+{
+  struct __wprintf_buffer_as_file *file
+    = (struct __wprintf_buffer_as_file *) fp;
+
+  __wprintf_buffer_as_file_commit (file);
+
+  /* EOF means only a flush is requested.   */
+  if (ch != WEOF)
+    __wprintf_buffer_putc (file->next, ch);
+  else
+    ch = 0;
+
+  /* Ensure that flushing actually produces room.  */
+  if (!__wprintf_buffer_has_failed (file->next)
+      && file->next->write_ptr == file->next->write_end)
+    __wprintf_buffer_flush (file->next);
+
+  __wprintf_buffer_as_file_switch_to_buffer (file);
+
+  if (!__wprintf_buffer_has_failed (file->next))
+    return (unsigned char) ch;
+  else
+    return WEOF;
+}
+
+static size_t
+__wprintf_buffer_as_file_xsputn (FILE *fp, const void *buf, size_t len)
+{
+  struct __wprintf_buffer_as_file *file
+    = (struct __wprintf_buffer_as_file *) fp;
+
+  __wprintf_buffer_as_file_commit (file);
+
+  /* Copy the data.  */
+  __wprintf_buffer_write (file->next, buf, len);
+
+  __wprintf_buffer_as_file_switch_to_buffer (file);
+
+  if (!__wprintf_buffer_has_failed (file->next))
+    return len;
+  else
+    /* We may actually have written something.  But the stream is
+       corrupted in this case anyway, so try not to divine the write
+       count here.  */
+    return 0;
+}
+
+static const struct _IO_jump_t _IO_wprintf_buffer_as_file_jumps libio_vtable =
+{
+  JUMP_INIT_DUMMY,
+  JUMP_INIT(finish, NULL),
+  JUMP_INIT(overflow, (_IO_overflow_t) __wprintf_buffer_as_file_overflow),
+  JUMP_INIT(underflow, NULL),
+  JUMP_INIT(uflow, NULL),
+  JUMP_INIT(pbackfail, NULL),
+  JUMP_INIT(xsputn, __wprintf_buffer_as_file_xsputn),
+  JUMP_INIT(xsgetn, NULL),
+  JUMP_INIT(seekoff, NULL),
+  JUMP_INIT(seekpos, NULL),
+  JUMP_INIT(setbuf, NULL),
+  JUMP_INIT(sync, NULL),
+  JUMP_INIT(doallocate, NULL),
+  JUMP_INIT(read, NULL),
+  JUMP_INIT(write, NULL),
+  JUMP_INIT(seek, NULL),
+  JUMP_INIT(close, NULL),
+  JUMP_INIT(stat, NULL),
+  JUMP_INIT(showmanyc, NULL),
+  JUMP_INIT(imbue, NULL)
+};
+
+void
+__wprintf_buffer_as_file_init (struct __wprintf_buffer_as_file *file,
+                               struct __wprintf_buffer *next)
+{
+  file->stream._lock = NULL;
+  _IO_no_init (&file->stream, _IO_USER_LOCK, 0, &file->wide_stream,
+               &_IO_wprintf_buffer_as_file_jumps);
+  _IO_fwide (&file->stream, 1);
+
+  /* Set up the write buffer from the next buffer.  */
+  file->next = next;
+  __wprintf_buffer_as_file_switch_to_buffer (file);
+
+  /* Mark the read area as inactive, by making all pointers equal.  */
+  file->stream._IO_read_base = file->stream._IO_write_base;
+  file->stream._IO_read_ptr = file->stream._IO_write_base;
+  file->stream._IO_read_end = file->stream._IO_write_base;
+}
+
+bool
+__wprintf_buffer_as_file_terminate (struct __wprintf_buffer_as_file *file)
+{
+  if (file->stream._flags & _IO_ERR_SEEN)
+    return false;
+  else
+    {
+      __wprintf_buffer_as_file_commit (file);
+      return true;
+    }
+}
diff --git a/stdio-common/wprintf_buffer_done.c b/stdio-common/wprintf_buffer_done.c
new file mode 100644
index 0000000000..60eac03955
--- /dev/null
+++ b/stdio-common/wprintf_buffer_done.c
@@ -0,0 +1,21 @@
+/* Final status reporting for struct __wprintf_buffer.  Wide version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-wchar_t.h"
+#include "Xprintf_buffer_done.c"
diff --git a/stdio-common/wprintf_buffer_flush.c b/stdio-common/wprintf_buffer_flush.c
new file mode 100644
index 0000000000..2d91095cca
--- /dev/null
+++ b/stdio-common/wprintf_buffer_flush.c
@@ -0,0 +1,36 @@
+/* Flush a struct __wprintf_buffer.  Wide version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+
+#include "printf_buffer-wchar_t.h"
+#include "Xprintf_buffer_flush.c"
+
+static void
+__wprintf_buffer_do_flush (struct __wprintf_buffer *buf)
+{
+  switch (buf->mode)
+    {
+    case __wprintf_buffer_mode_failed:
+      return;
+    case __wprintf_buffer_mode_to_file:
+      __wprintf_buffer_flush_to_file ((struct __wprintf_buffer_to_file *) buf);
+      return;
+    }
+  __builtin_trap ();
+}
diff --git a/stdio-common/wprintf_buffer_pad_1.c b/stdio-common/wprintf_buffer_pad_1.c
new file mode 100644
index 0000000000..9c91126f42
--- /dev/null
+++ b/stdio-common/wprintf_buffer_pad_1.c
@@ -0,0 +1,21 @@
+/* Write repeated characters to struct __wprintf_buffer.  Wide version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-wchar_t.h"
+#include "Xprintf_buffer_pad_1.c"
diff --git a/stdio-common/wprintf_buffer_putc_1.c b/stdio-common/wprintf_buffer_putc_1.c
new file mode 100644
index 0000000000..b1af68e721
--- /dev/null
+++ b/stdio-common/wprintf_buffer_putc_1.c
@@ -0,0 +1,21 @@
+/* Overflow character write function for struct __wprintf_buffer.  Wide version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-wchar_t.h"
+#include "Xprintf_buffer_putc_1.c"
diff --git a/stdio-common/wprintf_buffer_puts_1.c b/stdio-common/wprintf_buffer_puts_1.c
new file mode 100644
index 0000000000..039aebb56e
--- /dev/null
+++ b/stdio-common/wprintf_buffer_puts_1.c
@@ -0,0 +1,21 @@
+/* String write function struct __wprintf_buffer.  Wide version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-wchar_t.h"
+#include "Xprintf_buffer_puts_1.c"
diff --git a/stdio-common/wprintf_buffer_to_file.c b/stdio-common/wprintf_buffer_to_file.c
new file mode 100644
index 0000000000..ac936fab6c
--- /dev/null
+++ b/stdio-common/wprintf_buffer_to_file.c
@@ -0,0 +1,55 @@
+/* Wide printf buffers writing data to a FILE *.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This implementation is not optimized (unlikely the multibyte
+   implementation) and always writes to the temporary buffer first.  */
+
+#include <printf_buffer_to_file.h>
+
+#include <array_length.h>
+#include <libio/libioP.h>
+
+void
+__wprintf_buffer_flush_to_file (struct __wprintf_buffer_to_file *buf)
+{
+  size_t count = buf->base.write_ptr - buf->stage;
+  if ((size_t) _IO_sputn (buf->fp, buf->stage, count) != count)
+    {
+      __wprintf_buffer_mark_failed (&buf->base);
+      return;
+    }
+  buf->base.written += count;
+  buf->base.write_ptr = buf->stage;
+}
+
+void
+__wprintf_buffer_to_file_init (struct __wprintf_buffer_to_file *buf, FILE *fp)
+{
+  __wprintf_buffer_init (&buf->base, buf->stage, array_length (buf->stage),
+                         __wprintf_buffer_mode_to_file);
+  buf->fp = fp;
+}
+
+int
+__wprintf_buffer_to_file_done (struct __wprintf_buffer_to_file *buf)
+{
+  if (__wprintf_buffer_has_failed (&buf->base))
+    return -1;
+  __wprintf_buffer_flush_to_file (buf);
+  return __wprintf_buffer_done (&buf->base);
+}
diff --git a/stdio-common/wprintf_buffer_write.c b/stdio-common/wprintf_buffer_write.c
new file mode 100644
index 0000000000..06b53c74c3
--- /dev/null
+++ b/stdio-common/wprintf_buffer_write.c
@@ -0,0 +1,21 @@
+/* Blob write function for struct __wprintf_buffer.  Wide version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf_buffer.h>
+#include "printf_buffer-wchar_t.h"
+#include "Xprintf_buffer_write.c"
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 18/26] stdio-common: Add __printf_function_invoke
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (16 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 17/26] stdio-common: Introduce buffers for implementing printf Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:31 ` [PATCH 19/26] stdio-common: Add __translated_number_width Florian Weimer
                   ` (7 subsequent siblings)
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

And __wprintf_function_invoke.  These functions will be used to
to call registered printf specifier callbacks on printf buffers
after vfprintf and vfwprintf have been converted to buffers.
---
 include/printf.h                       | 10 ++++++
 stdio-common/Makefile                  |  2 ++
 stdio-common/Xprintf_function_invoke.c | 42 ++++++++++++++++++++++++++
 stdio-common/printf_function_invoke.c  | 22 ++++++++++++++
 stdio-common/wprintf_function_invoke.c | 22 ++++++++++++++
 5 files changed, 98 insertions(+)
 create mode 100644 stdio-common/Xprintf_function_invoke.c
 create mode 100644 stdio-common/printf_function_invoke.c
 create mode 100644 stdio-common/wprintf_function_invoke.c

diff --git a/include/printf.h b/include/printf.h
index 770c32d8f8..9ca6064c83 100644
--- a/include/printf.h
+++ b/include/printf.h
@@ -39,6 +39,16 @@ union printf_arg
     void *pa_user;
 };
 
+/* Invoke a registered printf callback.  Called from vfprintf and vfwprintf.  */
+int __printf_function_invoke (void *, printf_function callback,
+			      union printf_arg *args_value,
+			      size_t ndata_args,
+			      struct printf_info *info) attribute_hidden;
+int __wprintf_function_invoke (void *, printf_function callback,
+			       union printf_arg *args_value,
+			       size_t ndata_args,
+			       struct printf_info *info) attribute_hidden;
+
 #include <bits/types/locale_t.h>
 
 /* Now define the internal interfaces.  */
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index fa493a24af..6ad8b4b94a 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -63,6 +63,7 @@ routines := \
   printf_buffer_write \
   printf_fp \
   printf_fphex \
+  printf_function_invoke \
   printf_size \
   psiginfo \
   psignal \
@@ -101,6 +102,7 @@ routines := \
   wprintf_buffer_puts_1 \
   wprintf_buffer_to_file \
   wprintf_buffer_write \
+  wprintf_function_invoke \
   # routines
 
 aux := \
diff --git a/stdio-common/Xprintf_function_invoke.c b/stdio-common/Xprintf_function_invoke.c
new file mode 100644
index 0000000000..f631469ba9
--- /dev/null
+++ b/stdio-common/Xprintf_function_invoke.c
@@ -0,0 +1,42 @@
+/* Invoke a printf specifier handler.  Generic version.
+   Copyright (C) 1991-2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+int
+Xprintf (function_invoke) (void *buf,
+                           printf_function callback,
+                           union printf_arg *args_value,
+                           size_t ndata_args,
+                           struct printf_info *info)
+{
+  const void *args[ndata_args];
+  for (unsigned int i = 0; i < ndata_args; ++i)
+    args[i] = &args_value[i];
+
+  struct Xprintf (buffer_as_file) s;
+  Xprintf (buffer_as_file_init) (&s, buf);
+
+  /* Call the function.  */
+  int done = callback (Xprintf (buffer_as_file_get) (&s), info, args);
+
+  if (!Xprintf (buffer_as_file_terminate) (&s))
+    /* Low-level error.  */
+    return -1;
+
+  /* Potential error from the callback function.  */
+  return done;
+}
diff --git a/stdio-common/printf_function_invoke.c b/stdio-common/printf_function_invoke.c
new file mode 100644
index 0000000000..8919b892ca
--- /dev/null
+++ b/stdio-common/printf_function_invoke.c
@@ -0,0 +1,22 @@
+/* Invoke a printf specifier handler.  Multibyte version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf.h>
+#include <printf_buffer_as_file.h>
+#include "printf_buffer-char.h"
+#include "Xprintf_function_invoke.c"
diff --git a/stdio-common/wprintf_function_invoke.c b/stdio-common/wprintf_function_invoke.c
new file mode 100644
index 0000000000..29a6b4e073
--- /dev/null
+++ b/stdio-common/wprintf_function_invoke.c
@@ -0,0 +1,22 @@
+/* Invoke a printf specifier handler.  Wide version.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <printf.h>
+#include <printf_buffer_as_file.h>
+#include "printf_buffer-wchar_t.h"
+#include "Xprintf_function_invoke.c"
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 19/26] stdio-common: Add __translated_number_width
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (17 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 18/26] stdio-common: Add __printf_function_invoke Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:31 ` [PATCH 20/26] stdio-common: Convert vfprintf and related functions to buffers Florian Weimer
                   ` (6 subsequent siblings)
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

This function will be used to compute the width of a number
after i18n digit translation.
---
 include/printf.h                       | 13 +++++++-
 stdio-common/Makefile                  |  1 +
 stdio-common/translated_number_width.c | 42 ++++++++++++++++++++++++++
 3 files changed, 55 insertions(+), 1 deletion(-)
 create mode 100644 stdio-common/translated_number_width.c

diff --git a/include/printf.h b/include/printf.h
index 9ca6064c83..ab8e0b2c73 100644
--- a/include/printf.h
+++ b/include/printf.h
@@ -51,7 +51,18 @@ int __wprintf_function_invoke (void *, printf_function callback,
 
 #include <bits/types/locale_t.h>
 
-/* Now define the internal interfaces.  */
+/* Returns the width (as for printf, in bytes) of the converted ASCII
+   number in the characters in the range [FIRST, LAST).  The range
+   must only contain ASCII digits.  The caller is responsible for
+   avoiding overflow.
+
+   This function is used during non-wide digit translation.  Wide
+   digit translate produces one wide character per ASCII digit,
+   so the width is simply LAST - FIRST.  */
+int __translated_number_width (locale_t loc,
+			       const char *first, const char *last)
+  attribute_hidden;
+
 extern int __printf_fphex (FILE *, const struct printf_info *,
 			   const void *const *) attribute_hidden;
 extern int __printf_fp (FILE *, const struct printf_info *,
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 6ad8b4b94a..c8beb7b567 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -85,6 +85,7 @@ routines := \
   tmpfile64 \
   tmpnam \
   tmpnam_r \
+  translated_number_width \
   vfprintf \
   vfprintf-internal \
   vfscanf \
diff --git a/stdio-common/translated_number_width.c b/stdio-common/translated_number_width.c
new file mode 100644
index 0000000000..f42d5968a1
--- /dev/null
+++ b/stdio-common/translated_number_width.c
@@ -0,0 +1,42 @@
+/* Compute the printf width of a sequence of ASCII digits.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <limits.h>
+#include <locale/localeinfo.h>
+#include <printf.h>
+
+int
+__translated_number_width (locale_t loc, const char *first, const char *last)
+{
+  struct lc_ctype_data *ctype = loc->__locales[LC_CTYPE]->private;
+
+  if (ctype->outdigit_bytes_all_equal > 0)
+    return (last - first) * ctype->outdigit_bytes_all_equal;
+  else
+    {
+      /* Digits have varying length, so the fast path cannot be used.  */
+      int digits = 0;
+      for (const char *p = first; p < last; ++p)
+        {
+          assert ('0' <= *p && *p <= '9');
+          digits += ctype->outdigit_bytes[*p - '0'];
+        }
+      return digits;
+    }
+}
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 20/26] stdio-common: Convert vfprintf and related functions to buffers
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (18 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 19/26] stdio-common: Add __translated_number_width Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:31 ` [PATCH 21/26] stdio-common: Add lock optimization to vfprintf and vfwprintf Florian Weimer
                   ` (5 subsequent siblings)
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

vfprintf is entangled with vfwprintf (of course), __printf_fp,
__printf_fphex, __vstrfmon_l_internal, and the strfrom family of
functions.  The latter use the internal snprintf functionality,
so vsnprintf is converted as well.

The simples conversion is __printf_fphex, followed by
__vstrfmon_l_internal and __printf_fp, and finally
__vfprintf_internal and __vfwprintf_internal.  __vsnprintf_internal
and strfrom* are mostly consuming the new interfaces, so they
are comparatively simple.

__printf_fp is a public symbol, so the FILE *-based interface
had to preserved.

The __printf_fp rewrite does not change the actual binary-to-decimal
conversion algorithm, and digits are still not emitted directly to
the target buffer.  However, the staging buffer now uses bytes
instead of wide characters, and one buffer copy is eliminated.

The changes are at least performance-neutral in my testing.
Floating point printing and snprintf improved measurably, so that
this Lua script

  for i=1,5000000 do
      print(i, i * math.pi)
  end

runs about 5% faster for me.  To preserve fprintf performance for
a simple "%d" format, this commit has some logic changes under
LABEL (unsigned_number) to avoid additional function calls.  There
are certainly some very easy performance improvements here: binary,
octal and hexadecimal formatting can easily avoid the temporary work
buffer (the number of digits can be computed ahead-of-time using one
of the __builtin_clz* built-ins). Decimal formatting can use a
specialized version of _itoa_word for base 10.

The existing (inconsistent) width handling between strfmon and printf
is preserved here.  __print_fp_buffer_1 would have to use
__translated_number_width to achieve ISO conformance for printf.

Test expectations in libio/tst-vtables-common.c are adjusted because
the internal staging buffer merges all virtual function calls into
one.

In general, stack buffer usage is greatly reduced, particularly for
unbuffered input streams.  __printf_fp can still use a large buffer
in binary128 mode for %g, though.
---
 include/printf.h                              |  29 +-
 include/printf_buffer.h                       |  36 +
 libio/tst-vtables-common.c                    |   9 +-
 libio/vsnprintf.c                             | 131 ++--
 stdio-common/printf_buffer_flush.c            |  23 +
 stdio-common/printf_fp.c                      | 736 ++++++++----------
 stdio-common/printf_fphex.c                   | 259 +++---
 stdio-common/vfprintf-internal.c              | 664 +++++-----------
 stdio-common/vfprintf-process-arg.c           | 172 ++--
 stdlib/strfmon_l.c                            | 196 ++---
 stdlib/strfrom-skeleton.c                     |  38 +-
 sysdeps/ia64/fpu/printf_fphex.c               |   8 +-
 .../ieee754/ldbl-128/printf_fphex_macros.h    |  36 +-
 sysdeps/ieee754/ldbl-128ibm/printf_fphex.c    |  36 +-
 sysdeps/ieee754/ldbl-96/printf_fphex.c        |  22 +-
 sysdeps/x86_64/fpu/printf_fphex.c             |  21 +-
 16 files changed, 943 insertions(+), 1473 deletions(-)

diff --git a/include/printf.h b/include/printf.h
index ab8e0b2c73..cd5ad73d96 100644
--- a/include/printf.h
+++ b/include/printf.h
@@ -63,18 +63,31 @@ int __translated_number_width (locale_t loc,
 			       const char *first, const char *last)
   attribute_hidden;
 
-extern int __printf_fphex (FILE *, const struct printf_info *,
-			   const void *const *) attribute_hidden;
+
+struct __printf_buffer;
+void __printf_buffer (struct __printf_buffer *buf, const char *format,
+		      va_list ap, unsigned int mode_flags);
+struct __wprintf_buffer;
+void __wprintf_buffer (struct __wprintf_buffer *buf, const wchar_t *format,
+		       va_list ap, unsigned int mode_flags);
+
 extern int __printf_fp (FILE *, const struct printf_info *,
 			const void *const *);
 libc_hidden_proto (__printf_fp)
-extern int __printf_fp_l (FILE *, locale_t, const struct printf_info *,
-			  const void *const *);
-libc_hidden_proto (__printf_fp_l)
 
-extern unsigned int __guess_grouping (unsigned int intdig_max,
-				      const char *grouping)
-     attribute_hidden;
+void __printf_fphex_l_buffer (struct __printf_buffer *, locale_t,
+			      const struct printf_info *,
+			      const void *const *) attribute_hidden;
+void __printf_fp_l_buffer (struct __printf_buffer *, locale_t,
+			   const struct printf_info *,
+			   const void *const *) attribute_hidden;
+struct __wprintf_buffer;
+void __wprintf_fphex_l_buffer (struct __wprintf_buffer *, locale_t,
+			       const struct printf_info *,
+			       const void *const *) attribute_hidden;
+void __wprintf_fp_l_buffer (struct __wprintf_buffer *, locale_t,
+			    const struct printf_info *,
+			    const void *const *) attribute_hidden;
 
 # endif /* !_ISOMAC */
 #endif
diff --git a/include/printf_buffer.h b/include/printf_buffer.h
index 6be7723869..95d1b06489 100644
--- a/include/printf_buffer.h
+++ b/include/printf_buffer.h
@@ -44,7 +44,12 @@
 enum __printf_buffer_mode
   {
     __printf_buffer_mode_failed,
+    __printf_buffer_mode_snprintf,
     __printf_buffer_mode_to_file,
+    __printf_buffer_mode_strfmon,
+    __printf_buffer_mode_fp,         /* For __printf_fp_l_buffer.  */
+    __printf_buffer_mode_fp_to_wide, /* For __wprintf_fp_l_buffer.  */
+    __printf_buffer_mode_fphex_to_wide, /* For __wprintf_fphex_l_buffer.  */
   };
 
 /* Buffer for fast character writing with overflow handling.
@@ -266,13 +271,44 @@ bool __wprintf_buffer_flush (struct __wprintf_buffer *buf) attribute_hidden;
 #define Xprintf_buffer_puts Xprintf (buffer_puts)
 #define Xprintf_buffer_write Xprintf (buffer_write)
 
+/* Commonly used buffers.  */
+
+struct __printf_buffer_snprintf
+{
+  struct __printf_buffer base;
+  char discard[128];            /* Used in counting mode.  */
+};
+
+/* Sets up [BUFFER, BUFFER + LENGTH) as the write target.  If LENGTH
+   is positive, also writes a NUL byte to *BUFFER.  */
+void __printf_buffer_snprintf_init (struct __printf_buffer_snprintf *,
+                                    char *buffer, size_t length)
+  attribute_hidden;
+
+/* Add the null terminator after everything has been written.  The
+   return value is the one expected by printf (see __printf_buffer_done).  */
+int __printf_buffer_snprintf_done (struct __printf_buffer_snprintf *)
+  attribute_hidden;
+
 /* Flush function implementations follow.  They are called from
    __printf_buffer_flush.  Generic code should not call these flush
    functions directly.  Some modes have inline implementations.  */
 
+void __printf_buffer_flush_snprintf (struct __printf_buffer_snprintf *)
+  attribute_hidden;
 struct __printf_buffer_to_file;
 void __printf_buffer_flush_to_file (struct __printf_buffer_to_file *)
   attribute_hidden;
+struct __printf_buffer_fp;
+void __printf_buffer_flush_fp (struct __printf_buffer_fp *)
+  attribute_hidden;
+struct __printf_buffer_fp_to_wide;
+void __printf_buffer_flush_fp_to_wide (struct __printf_buffer_fp_to_wide *)
+  attribute_hidden;
+struct __printf_buffer_fphex_to_wide;
+void __printf_buffer_flush_fphex_to_wide (struct
+                                          __printf_buffer_fphex_to_wide *)
+  attribute_hidden;
 
 struct __wprintf_buffer_to_file;
 void __wprintf_buffer_flush_to_file (struct __wprintf_buffer_to_file *)
diff --git a/libio/tst-vtables-common.c b/libio/tst-vtables-common.c
index d18df23e55..a310e516f2 100644
--- a/libio/tst-vtables-common.c
+++ b/libio/tst-vtables-common.c
@@ -409,11 +409,14 @@ void _IO_init (FILE *fp, int flags);
 static void
 with_compatibility_fprintf (void *closure)
 {
+  /* A temporary staging buffer is used in the current fprintf
+     implementation, which is why there is just one call to
+     xsputn.  */
   TEST_COMPARE (fprintf_ptr (shared->fp, "A%sCD", "B"), 4);
-  TEST_COMPARE (shared->calls, 3);
-  TEST_COMPARE (shared->calls_xsputn, 3);
+  TEST_COMPARE (shared->calls, 1);
+  TEST_COMPARE (shared->calls_xsputn, 1);
   TEST_COMPARE_BLOB (shared->buffer, shared->buffer_length,
-                     "CD", 2);
+                     "ABCD", 4);
 }
 
 static void
diff --git a/libio/vsnprintf.c b/libio/vsnprintf.c
index 8dae66761d..7a9667f966 100644
--- a/libio/vsnprintf.c
+++ b/libio/vsnprintf.c
@@ -25,97 +25,76 @@
    in files containing the exception.  */
 
 #include "libioP.h"
-#include "strfile.h"
 
-static int _IO_strn_overflow (FILE *fp, int c) __THROW;
+#include <array_length.h>
+#include <printf.h>
+#include <printf_buffer.h>
 
-static int
-_IO_strn_overflow (FILE *fp, int c)
+void
+__printf_buffer_flush_snprintf (struct __printf_buffer_snprintf *buf)
 {
-  /* When we come to here this means the user supplied buffer is
-     filled.  But since we must return the number of characters which
-     would have been written in total we must provide a buffer for
-     further use.  We can do this by writing on and on in the overflow
-     buffer in the _IO_strnfile structure.  */
-  _IO_strnfile *snf = (_IO_strnfile *) fp;
-
-  if (fp->_IO_buf_base != snf->overflow_buf)
+  /* Record the bytes written so far, before switching buffers.  */
+  buf->base.written += buf->base.write_ptr - buf->base.write_base;
+
+  if (buf->base.write_base != buf->discard)
     {
-      /* Terminate the string.  We know that there is room for at
-	 least one more character since we initialized the stream with
-	 a size to make this possible.  */
-      *fp->_IO_write_ptr = '\0';
-
-      _IO_setb (fp, snf->overflow_buf,
-		snf->overflow_buf + sizeof (snf->overflow_buf), 0);
-
-      fp->_IO_write_base = snf->overflow_buf;
-      fp->_IO_read_base = snf->overflow_buf;
-      fp->_IO_read_ptr = snf->overflow_buf;
-      fp->_IO_read_end = snf->overflow_buf + sizeof (snf->overflow_buf);
-    }
+      /* We just finished writing the caller-supplied buffer.  Force
+	 NUL termination if the string length is not zero.  */
+      if (buf->base.write_base != buf->base.write_end)
+	buf->base.write_end[-1] = '\0';
 
-  fp->_IO_write_ptr = snf->overflow_buf;
-  fp->_IO_write_end = snf->overflow_buf;
 
-  /* Since we are not really interested in storing the characters
-     which do not fit in the buffer we simply ignore it.  */
-  return c;
-}
+      /* Switch to the discard buffer.  */
+      buf->base.write_base = buf->discard;
+      buf->base.write_ptr = buf->discard;
+      buf->base.write_end = array_end (buf->discard);
+    }
 
+  buf->base.write_base = buf->discard;
+  buf->base.write_ptr = buf->discard;
+}
 
-const struct _IO_jump_t _IO_strn_jumps libio_vtable attribute_hidden =
+void
+__printf_buffer_snprintf_init (struct __printf_buffer_snprintf *buf,
+			       char *buffer, size_t length)
 {
-  JUMP_INIT_DUMMY,
-  JUMP_INIT(finish, _IO_str_finish),
-  JUMP_INIT(overflow, _IO_strn_overflow),
-  JUMP_INIT(underflow, _IO_str_underflow),
-  JUMP_INIT(uflow, _IO_default_uflow),
-  JUMP_INIT(pbackfail, _IO_str_pbackfail),
-  JUMP_INIT(xsputn, _IO_default_xsputn),
-  JUMP_INIT(xsgetn, _IO_default_xsgetn),
-  JUMP_INIT(seekoff, _IO_str_seekoff),
-  JUMP_INIT(seekpos, _IO_default_seekpos),
-  JUMP_INIT(setbuf, _IO_default_setbuf),
-  JUMP_INIT(sync, _IO_default_sync),
-  JUMP_INIT(doallocate, _IO_default_doallocate),
-  JUMP_INIT(read, _IO_default_read),
-  JUMP_INIT(write, _IO_default_write),
-  JUMP_INIT(seek, _IO_default_seek),
-  JUMP_INIT(close, _IO_default_close),
-  JUMP_INIT(stat, _IO_default_stat),
-  JUMP_INIT(showmanyc, _IO_default_showmanyc),
-  JUMP_INIT(imbue, _IO_default_imbue)
-};
+  __printf_buffer_init (&buf->base, buffer, length,
+			__printf_buffer_mode_snprintf);
+  if (length > 0)
+    /* Historic behavior for trivially overlapping buffers (checked by
+       the test suite).  */
+    *buffer = '\0';
+}
 
+int
+__printf_buffer_snprintf_done (struct __printf_buffer_snprintf *buf)
+{
+  /* NB: Do not check for buf->base.fail here.  Write the null
+     terminator even in case of errors. */
+
+  if (buf->base.write_ptr < buf->base.write_end)
+    *buf->base.write_ptr = '\0';
+  else if (buf->base.write_ptr > buf->base.write_base)
+    /* If write_ptr == write_base, nothing has been written.  No null
+       termination is needed because of the early truncation in
+       __printf_buffer_snprintf_init (the historic behavior).
+
+       We might also be at the start of the discard buffer, but in
+       this case __printf_buffer_flush_snprintf has already written
+       the NUL terminator.  */
+    buf->base.write_ptr[-1] = '\0';
+
+  return __printf_buffer_done (&buf->base);
+}
 
 int
 __vsnprintf_internal (char *string, size_t maxlen, const char *format,
 		      va_list args, unsigned int mode_flags)
 {
-  _IO_strnfile sf;
-  int ret;
-#ifdef _IO_MTSAFE_IO
-  sf.f._sbf._f._lock = NULL;
-#endif
-
-  /* We need to handle the special case where MAXLEN is 0.  Use the
-     overflow buffer right from the start.  */
-  if (maxlen == 0)
-    {
-      string = sf.overflow_buf;
-      maxlen = sizeof (sf.overflow_buf);
-    }
-
-  _IO_no_init (&sf.f._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
-  _IO_JUMPS (&sf.f._sbf) = &_IO_strn_jumps;
-  string[0] = '\0';
-  _IO_str_init_static_internal (&sf.f, string, maxlen - 1, string);
-  ret = __vfprintf_internal (&sf.f._sbf._f, format, args, mode_flags);
-
-  if (sf.f._sbf._f._IO_buf_base != sf.overflow_buf)
-    *sf.f._sbf._f._IO_write_ptr = '\0';
-  return ret;
+  struct __printf_buffer_snprintf buf;
+  __printf_buffer_snprintf_init (&buf, string, maxlen);
+  __printf_buffer (&buf.base, format, args, mode_flags);
+  return __printf_buffer_snprintf_done (&buf);
 }
 
 int
diff --git a/stdio-common/printf_buffer_flush.c b/stdio-common/printf_buffer_flush.c
index 9b25c0fde5..bfd1f9d733 100644
--- a/stdio-common/printf_buffer_flush.c
+++ b/stdio-common/printf_buffer_flush.c
@@ -16,6 +16,7 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <errno.h>
 #include <printf_buffer.h>
 
 #include "printf_buffer-char.h"
@@ -24,7 +25,11 @@
 /* The __printf_buffer_flush_* functions are defined together with
    functions that are pulled in by strong references.  */
 #ifndef SHARED
+# pragma weak __printf_buffer_flush_snprintf
 # pragma weak __printf_buffer_flush_to_file
+# pragma weak __printf_buffer_flush_fp
+# pragma weak __printf_buffer_flush_fp_to_wide
+# pragma weak __printf_buffer_flush_fphex_to_wide
 #endif /* !SHARED */
 
 static void
@@ -34,9 +39,27 @@ __printf_buffer_do_flush (struct __printf_buffer *buf)
     {
     case __printf_buffer_mode_failed:
       return;
+    case __printf_buffer_mode_snprintf:
+      __printf_buffer_flush_snprintf ((struct __printf_buffer_snprintf *) buf);
+      return;
     case __printf_buffer_mode_to_file:
       __printf_buffer_flush_to_file ((struct __printf_buffer_to_file *) buf);
       return;
+    case __printf_buffer_mode_strfmon:
+      __set_errno (E2BIG);
+      __printf_buffer_mark_failed (buf);
+      return;
+    case __printf_buffer_mode_fp:
+      __printf_buffer_flush_fp ((struct __printf_buffer_fp *) buf);
+      return;
+    case __printf_buffer_mode_fp_to_wide:
+      __printf_buffer_flush_fp_to_wide
+        ((struct __printf_buffer_fp_to_wide *) buf);
+      return;
+    case __printf_buffer_mode_fphex_to_wide:
+      __printf_buffer_flush_fphex_to_wide
+        ((struct __printf_buffer_fphex_to_wide *) buf);
+      return;
     }
   __builtin_trap ();
 }
diff --git a/stdio-common/printf_fp.c b/stdio-common/printf_fp.c
index 3a5560fc16..5c9c30aaee 100644
--- a/stdio-common/printf_fp.c
+++ b/stdio-common/printf_fp.c
@@ -41,90 +41,12 @@
 #include <wchar.h>
 #include <stdbool.h>
 #include <rounding-mode.h>
+#include <printf_buffer.h>
+#include <printf_buffer_to_file.h>
+#include <grouping_iterator.h>
 
-#ifdef COMPILE_WPRINTF
-# define CHAR_T        wchar_t
-#else
-# define CHAR_T        char
-#endif
-
-#include "_i18n_number.h"
-
-#ifndef NDEBUG
-# define NDEBUG			/* Undefine this for debugging assertions.  */
-#endif
 #include <assert.h>
 
-#define PUT(f, s, n) _IO_sputn (f, s, n)
-#define PAD(f, c, n) (wide ? _IO_wpadn (f, c, n) : _IO_padn (f, c, n))
-#undef putc
-#define putc(c, f) (wide \
-		    ? (int)_IO_putwc_unlocked (c, f) : _IO_putc_unlocked (c, f))
-
-\f
-/* Macros for doing the actual output.  */
-
-#define outchar(ch)							      \
-  do									      \
-    {									      \
-      const int outc = (ch);						      \
-      if (putc (outc, fp) == EOF)					      \
-	{								      \
-	  if (buffer_malloced)						      \
-	    {								      \
-	      free (buffer);						      \
-	      free (wbuffer);						      \
-	    }								      \
-	  return -1;							      \
-	}								      \
-      ++done;								      \
-    } while (0)
-
-#define PRINT(ptr, wptr, len)						      \
-  do									      \
-    {									      \
-      size_t outlen = (len);						      \
-      if (len > 20)							      \
-	{								      \
-	  if (PUT (fp, wide ? (const char *) wptr : ptr, outlen) != outlen)   \
-	    {								      \
-	      if (buffer_malloced)					      \
-		{							      \
-		  free (buffer);					      \
-		  free (wbuffer);					      \
-		}							      \
-	      return -1;						      \
-	    }								      \
-	  ptr += outlen;						      \
-	  done += outlen;						      \
-	}								      \
-      else								      \
-	{								      \
-	  if (wide)							      \
-	    while (outlen-- > 0)					      \
-	      outchar (*wptr++);					      \
-	  else								      \
-	    while (outlen-- > 0)					      \
-	      outchar (*ptr++);						      \
-	}								      \
-    } while (0)
-
-#define PADN(ch, len)							      \
-  do									      \
-    {									      \
-      if (PAD (fp, ch, len) != len)					      \
-	{								      \
-	  if (buffer_malloced)						      \
-	    {								      \
-	      free (buffer);						      \
-	      free (wbuffer);						      \
-	    }								      \
-	  return -1;							      \
-	}								      \
-      done += len;							      \
-    }									      \
-  while (0)
-\f
 /* We use the GNU MP library to handle large numbers.
 
    An MP variable occupies a varying number of entries in its array.  We keep
@@ -145,10 +67,6 @@ extern mp_size_t __mpn_extract_long_double (mp_ptr res_ptr, mp_size_t size,
 					    long double value);
 
 
-static wchar_t *group_number (wchar_t *buf, wchar_t *bufend,
-			      unsigned int intdig_no, const char *grouping,
-			      wchar_t thousands_sep, int ngroups);
-
 struct hack_digit_param
 {
   /* Sign of the exponent.  */
@@ -165,7 +83,7 @@ struct hack_digit_param
   MPN_VAR(tmp);
 };
 
-static wchar_t
+static char
 hack_digit (struct hack_digit_param *p)
 {
   mp_limb_t hi;
@@ -197,7 +115,7 @@ hack_digit (struct hack_digit_param *p)
 	      /* We're not prepared for an mpn variable with zero
 		 limbs.  */
 	      p->fracsize = 1;
-	      return L'0' + hi;
+	      return '0' + hi;
 	    }
 	}
 
@@ -206,13 +124,22 @@ hack_digit (struct hack_digit_param *p)
 	p->frac[p->fracsize++] = _cy;
     }
 
-  return L'0' + hi;
+  return '0' + hi;
 }
 
-int
-__printf_fp_l (FILE *fp, locale_t loc,
-	       const struct printf_info *info,
-	       const void *const *args)
+/* Version that performs grouping (if INFO->group && THOUSANDS_SEP != 0),
+   but not i18n digit translation.
+
+   The output buffer is always multibyte (not wide) at this stage.
+   Wide conversion and i18n digit translation happen later, with a
+   temporary buffer.  To prepare for that, THOUSANDS_SEP_LENGTH is the
+   final length of the thousands separator.  */
+static void
+__printf_fp_buffer_1 (struct __printf_buffer *buf, locale_t loc,
+		      char thousands_sep, char decimal,
+		      unsigned int thousands_sep_length,
+		      const struct printf_info *info,
+		      const void *const *args)
 {
   /* The floating-point value to output.  */
   union
@@ -225,18 +152,11 @@ __printf_fp_l (FILE *fp, locale_t loc,
     }
   fpnum;
 
-  /* Locale-dependent representation of decimal point.	*/
-  const char *decimal;
-  wchar_t decimalwc;
-
-  /* Locale-dependent thousands separator and grouping specification.  */
-  const char *thousands_sep = NULL;
-  wchar_t thousands_sepwc = 0;
-  const char *grouping;
-
   /* "NaN" or "Inf" for the special cases.  */
   const char *special = NULL;
-  const wchar_t *wspecial = NULL;
+
+  /* Used to determine grouping rules.  */
+  int lc_category = info->extra ? LC_MONETARY : LC_NUMERIC;
 
   /* When _Float128 is enabled in the library and ABI-distinct from long
      double, we need mp_limbs enough for any of them.  */
@@ -256,90 +176,16 @@ __printf_fp_l (FILE *fp, locale_t loc,
   /* Sign of float number.  */
   int is_neg = 0;
 
-  /* Counter for number of written characters.	*/
-  int done = 0;
-
   /* General helper (carry limb).  */
   mp_limb_t cy;
 
-  /* Nonzero if this is output on a wide character stream.  */
-  int wide = info->wide;
-
   /* Buffer in which we produce the output.  */
-  wchar_t *wbuffer = NULL;
-  char *buffer = NULL;
+  char *wbuffer = NULL;
   /* Flag whether wbuffer and buffer are malloc'ed or not.  */
   int buffer_malloced = 0;
 
   p.expsign = 0;
 
-  /* Figure out the decimal point character.  */
-  if (info->extra == 0)
-    {
-      decimal = _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT);
-      decimalwc = _nl_lookup_word
-	(loc, LC_NUMERIC, _NL_NUMERIC_DECIMAL_POINT_WC);
-    }
-  else
-    {
-      decimal = _nl_lookup (loc, LC_MONETARY, MON_DECIMAL_POINT);
-      if (*decimal == '\0')
-	decimal = _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT);
-      decimalwc = _nl_lookup_word (loc, LC_MONETARY,
-				    _NL_MONETARY_DECIMAL_POINT_WC);
-      if (decimalwc == L'\0')
-	decimalwc = _nl_lookup_word (loc, LC_NUMERIC,
-				      _NL_NUMERIC_DECIMAL_POINT_WC);
-    }
-  /* The decimal point character must not be zero.  */
-  assert (*decimal != '\0');
-  assert (decimalwc != L'\0');
-
-  if (info->group)
-    {
-      if (info->extra == 0)
-	grouping = _nl_lookup (loc, LC_NUMERIC, GROUPING);
-      else
-	grouping = _nl_lookup (loc, LC_MONETARY, MON_GROUPING);
-
-      if (*grouping <= 0 || *grouping == CHAR_MAX)
-	grouping = NULL;
-      else
-	{
-	  /* Figure out the thousands separator character.  */
-	  if (wide)
-	    {
-	      if (info->extra == 0)
-		thousands_sepwc = _nl_lookup_word
-		  (loc, LC_NUMERIC, _NL_NUMERIC_THOUSANDS_SEP_WC);
-	      else
-		thousands_sepwc =
-		  _nl_lookup_word (loc, LC_MONETARY,
-				    _NL_MONETARY_THOUSANDS_SEP_WC);
-	    }
-	  else
-	    {
-	      if (info->extra == 0)
-		thousands_sep = _nl_lookup (loc, LC_NUMERIC, THOUSANDS_SEP);
-	      else
-		thousands_sep = _nl_lookup
-		  (loc, LC_MONETARY, MON_THOUSANDS_SEP);
-	    }
-
-	  if ((wide && thousands_sepwc == L'\0')
-	      || (! wide && *thousands_sep == '\0'))
-	    grouping = NULL;
-	  else if (thousands_sepwc == L'\0')
-	    /* If we are printing multibyte characters and there is a
-	       multibyte representation for the thousands separator,
-	       we must ensure the wide character thousands separator
-	       is available, even if it is fake.  */
-	    thousands_sepwc = 0xfffffffe;
-	}
-    }
-  else
-    grouping = NULL;
-
 #define PRINTF_FP_FETCH(FLOAT, VAR, SUFFIX, MANT_DIG)			\
   {									\
     (VAR) = *(const FLOAT *) args[0];					\
@@ -349,29 +195,17 @@ __printf_fp_l (FILE *fp, locale_t loc,
       {									\
 	is_neg = signbit (VAR);						\
 	if (isupper (info->spec))					\
-	  {								\
-	    special = "NAN";						\
-	    wspecial = L"NAN";						\
-	  }								\
+	  special = "NAN";						\
 	else								\
-	  {								\
-	    special = "nan";						\
-	    wspecial = L"nan";						\
-	  }								\
+	  special = "nan";						\
       }									\
     else if (isinf (VAR))						\
       {									\
 	is_neg = signbit (VAR);						\
 	if (isupper (info->spec))					\
-	  {								\
-	    special = "INF";						\
-	    wspecial = L"INF";						\
-	  }								\
+	  special = "INF";						\
 	else								\
-	  {								\
-	    special = "inf";						\
-	    wspecial = L"inf";						\
-	  }								\
+	  special = "inf";						\
       }									\
     else								\
       {									\
@@ -405,22 +239,22 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	--width;
       width -= 3;
 
-      if (!info->left && width > 0)
-	PADN (' ', width);
+      if (!info->left)
+	__printf_buffer_pad (buf, ' ', width);
 
       if (is_neg)
-	outchar ('-');
+	__printf_buffer_putc (buf, '-');
       else if (info->showsign)
-	outchar ('+');
+	__printf_buffer_putc (buf, '+');
       else if (info->space)
-	outchar (' ');
+	__printf_buffer_putc (buf, ' ');
 
-      PRINT (special, wspecial, 3);
+      __printf_buffer_puts (buf, special);
 
-      if (info->left && width > 0)
-	PADN (' ', width);
+      if (info->left)
+	__printf_buffer_pad (buf, ' ', width);
 
-      return done;
+      return;
     }
 
 
@@ -829,7 +663,7 @@ __printf_fp_l (FILE *fp, locale_t loc,
 
   {
     int width = info->width;
-    wchar_t *wstartp, *wcp;
+    char *wstartp, *wcp;
     size_t chars_needed;
     int expscale;
     int intdig_max, intdig_no = 0;
@@ -837,7 +671,6 @@ __printf_fp_l (FILE *fp, locale_t loc,
     int fracdig_max;
     int dig_max;
     int significant;
-    int ngroups = 0;
     char spec = _tolower (info->spec);
 
     if (spec == 'e')
@@ -898,38 +731,32 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	significant = 0;		/* We count significant digits.	 */
       }
 
-    if (grouping)
-      {
-	/* Guess the number of groups we will make, and thus how
-	   many spaces we need for separator characters.  */
-	ngroups = __guess_grouping (intdig_max, grouping);
-	/* Allocate one more character in case rounding increases the
-	   number of groups.  */
-	chars_needed += ngroups + 1;
-      }
-
     /* Allocate buffer for output.  We need two more because while rounding
        it is possible that we need two more characters in front of all the
        other output.  If the amount of memory we have to allocate is too
        large use `malloc' instead of `alloca'.  */
-    if (__builtin_expect (chars_needed >= (size_t) -1 / sizeof (wchar_t) - 2
-			  || chars_needed < fracdig_max, 0))
+    if (__glibc_unlikely (chars_needed >= (size_t) -1 - 2
+			  || chars_needed < fracdig_max))
       {
 	/* Some overflow occurred.  */
 	__set_errno (ERANGE);
-	return -1;
+	__printf_buffer_mark_failed (buf);
+	return;
       }
-    size_t wbuffer_to_alloc = (2 + chars_needed) * sizeof (wchar_t);
+    size_t wbuffer_to_alloc = 2 + chars_needed;
     buffer_malloced = ! __libc_use_alloca (wbuffer_to_alloc);
     if (__builtin_expect (buffer_malloced, 0))
       {
-	wbuffer = (wchar_t *) malloc (wbuffer_to_alloc);
+	wbuffer = malloc (wbuffer_to_alloc);
 	if (wbuffer == NULL)
-	  /* Signal an error to the caller.  */
-	  return -1;
+	  {
+	    /* Signal an error to the caller.  */
+	    __printf_buffer_mark_failed (buf);
+	    return;
+	  }
       }
     else
-      wbuffer = (wchar_t *) alloca (wbuffer_to_alloc);
+      wbuffer = alloca (wbuffer_to_alloc);
     wcp = wstartp = wbuffer + 2;	/* Let room for rounding.  */
 
     /* Do the real work: put digits in allocated buffer.  */
@@ -945,15 +772,15 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	if (info->alt
 	    || fracdig_min > 0
 	    || (fracdig_max > 0 && (p.fracsize > 1 || p.frac[0] != 0)))
-	  *wcp++ = decimalwc;
+	  *wcp++ = decimal;
       }
     else
       {
 	/* |fp| < 1.0 and the selected p.type is 'f', so put "0."
 	   in the buffer.  */
-	*wcp++ = L'0';
+	*wcp++ = '0';
 	--p.exponent;
-	*wcp++ = decimalwc;
+	*wcp++ = decimal;
       }
 
     /* Generate the needed number of fractional digits.	 */
@@ -964,7 +791,7 @@ __printf_fp_l (FILE *fp, locale_t loc,
       {
 	++fracdig_no;
 	*wcp = hack_digit (&p);
-	if (*wcp++ != L'0')
+	if (*wcp++ != '0')
 	  significant = 1;
 	else if (significant == 0)
 	  {
@@ -975,10 +802,10 @@ __printf_fp_l (FILE *fp, locale_t loc,
       }
 
     /* Do rounding.  */
-    wchar_t last_digit = wcp[-1] != decimalwc ? wcp[-1] : wcp[-2];
-    wchar_t next_digit = hack_digit (&p);
+    char last_digit = wcp[-1] != decimal ? wcp[-1] : wcp[-2];
+    char next_digit = hack_digit (&p);
     bool more_bits;
-    if (next_digit != L'0' && next_digit != L'5')
+    if (next_digit != '0' && next_digit != '5')
       more_bits = true;
     else if (p.fracsize == 1 && p.frac[0] == 0)
       /* Rest of the number is zero.  */
@@ -995,29 +822,29 @@ __printf_fp_l (FILE *fp, locale_t loc,
     else
       more_bits = true;
     int rounding_mode = get_rounding_mode ();
-    if (round_away (is_neg, (last_digit - L'0') & 1, next_digit >= L'5',
+    if (round_away (is_neg, (last_digit - '0') & 1, next_digit >= '5',
 		    more_bits, rounding_mode))
       {
-	wchar_t *wtp = wcp;
+	char *wtp = wcp;
 
 	if (fracdig_no > 0)
 	  {
 	    /* Process fractional digits.  Terminate if not rounded or
 	       radix character is reached.  */
 	    int removed = 0;
-	    while (*--wtp != decimalwc && *wtp == L'9')
+	    while (*--wtp != decimal && *wtp == '9')
 	      {
-		*wtp = L'0';
+		*wtp = '0';
 		++removed;
 	      }
 	    if (removed == fracdig_min && added_zeros > 0)
 	      --added_zeros;
-	    if (*wtp != decimalwc)
+	    if (*wtp != decimal)
 	      /* Round up.  */
 	      (*wtp)++;
 	    else if (__builtin_expect (spec == 'g' && p.type == 'f' && info->alt
 				       && wtp == wstartp + 1
-				       && wstartp[0] == L'0',
+				       && wstartp[0] == '0',
 				       0))
 	      /* This is a special case: the rounded number is 1.0,
 		 the format is 'g' or 'G', and the alternative format
@@ -1025,14 +852,14 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	      --added_zeros;
 	  }
 
-	if (fracdig_no == 0 || *wtp == decimalwc)
+	if (fracdig_no == 0 || *wtp == decimal)
 	  {
 	    /* Round the integer digits.  */
-	    if (*(wtp - 1) == decimalwc)
+	    if (*(wtp - 1) == decimal)
 	      --wtp;
 
-	    while (--wtp >= wstartp && *wtp == L'9')
-	      *wtp = L'0';
+	    while (--wtp >= wstartp && *wtp == '9')
+	      *wtp = '0';
 
 	    if (wtp >= wstartp)
 	      /* Round up.  */
@@ -1056,13 +883,13 @@ __printf_fp_l (FILE *fp, locale_t loc,
 		    /* This is the case where for p.type %g the number fits
 		       really in the range for %f output but after rounding
 		       the number of digits is too big.	 */
-		    *--wstartp = decimalwc;
-		    *--wstartp = L'1';
+		    *--wstartp = decimal;
+		    *--wstartp = '1';
 
 		    if (info->alt || fracdig_no > 0)
 		      {
 			/* Overwrite the old radix character.  */
-			wstartp[intdig_no + 2] = L'0';
+			wstartp[intdig_no + 2] = '0';
 			++fracdig_no;
 		      }
 
@@ -1077,7 +904,7 @@ __printf_fp_l (FILE *fp, locale_t loc,
 		  {
 		    /* We can simply add another another digit before the
 		       radix.  */
-		    *--wstartp = L'1';
+		    *--wstartp = '1';
 		    ++intdig_no;
 		  }
 
@@ -1094,28 +921,16 @@ __printf_fp_l (FILE *fp, locale_t loc,
       }
 
     /* Now remove unnecessary '0' at the end of the string.  */
-    while (fracdig_no > fracdig_min + added_zeros && *(wcp - 1) == L'0')
+    while (fracdig_no > fracdig_min + added_zeros && *(wcp - 1) == '0')
       {
 	--wcp;
 	--fracdig_no;
       }
     /* If we eliminate all fractional digits we perhaps also can remove
        the radix character.  */
-    if (fracdig_no == 0 && !info->alt && *(wcp - 1) == decimalwc)
+    if (fracdig_no == 0 && !info->alt && *(wcp - 1) == decimal)
       --wcp;
 
-    if (grouping)
-      {
-	/* Rounding might have changed the number of groups.  We allocated
-	   enough memory but we need here the correct number of groups.  */
-	if (intdig_no != intdig_max)
-	  ngroups = __guess_grouping (intdig_no, grouping);
-
-	/* Add in separator characters, overwriting the same buffer.  */
-	wcp = group_number (wstartp, wcp, intdig_no, grouping, thousands_sepwc,
-			    ngroups);
-      }
-
     /* Write the p.exponent if it is needed.  */
     if (p.type != 'f')
       {
@@ -1125,12 +940,12 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	       really smaller than -4, which requires the 'e'/'E' format.
 	       But after rounding the number has an p.exponent of -4.  */
 	    assert (wcp >= wstartp + 1);
-	    assert (wstartp[0] == L'1');
-	    __wmemcpy (wstartp, L"0.0001", 6);
-	    wstartp[1] = decimalwc;
+	    assert (wstartp[0] == '1');
+	    memcpy (wstartp, "0.0001", 6);
+	    wstartp[1] = decimal;
 	    if (wcp >= wstartp + 2)
 	      {
-		__wmemset (wstartp + 6, L'0', wcp - (wstartp + 2));
+		memset (wstartp + 6, '0', wcp - (wstartp + 2));
 		wcp += 4;
 	      }
 	    else
@@ -1138,8 +953,8 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	  }
 	else
 	  {
-	    *wcp++ = (wchar_t) p.type;
-	    *wcp++ = p.expsign ? L'-' : L'+';
+	    *wcp++ = p.type;
+	    *wcp++ = p.expsign ? '-' : '+';
 
 	    /* Find the magnitude of the p.exponent.	*/
 	    expscale = 10;
@@ -1148,220 +963,293 @@ __printf_fp_l (FILE *fp, locale_t loc,
 
 	    if (p.exponent < 10)
 	      /* Exponent always has at least two digits.  */
-	      *wcp++ = L'0';
+	      *wcp++ = '0';
 	    else
 	      do
 		{
 		  expscale /= 10;
-		  *wcp++ = L'0' + (p.exponent / expscale);
+		  *wcp++ = '0' + (p.exponent / expscale);
 		  p.exponent %= expscale;
 		}
 	      while (expscale > 10);
-	    *wcp++ = L'0' + p.exponent;
+	    *wcp++ = '0' + p.exponent;
 	  }
       }
 
+    struct grouping_iterator iter;
+    if (thousands_sep != '\0' && info->group)
+      __grouping_iterator_init (&iter, lc_category, loc, intdig_no);
+    else
+      iter.separators = 0;
+
     /* Compute number of characters which must be filled with the padding
        character.  */
     if (is_neg || info->showsign || info->space)
       --width;
+    /* To count bytes, we would have to use __translated_number_width
+       for info->i18n && !info->wide.  See bug 28943.  */
     width -= wcp - wstartp;
+    /* For counting bytes, we would have to multiply by
+       thousands_sep_length.  */
+    width -= iter.separators;
 
-    if (!info->left && info->pad != '0' && width > 0)
-      PADN (info->pad, width);
+    if (!info->left && info->pad != '0')
+      __printf_buffer_pad (buf, info->pad, width);
 
     if (is_neg)
-      outchar ('-');
+      __printf_buffer_putc (buf, '-');
     else if (info->showsign)
-      outchar ('+');
+      __printf_buffer_putc (buf, '+');
     else if (info->space)
-      outchar (' ');
+      __printf_buffer_putc (buf, ' ');
 
-    if (!info->left && info->pad == '0' && width > 0)
-      PADN ('0', width);
+    if (!info->left && info->pad == '0')
+      __printf_buffer_pad (buf, '0', width);
 
-    {
-      char *buffer_end = NULL;
-      char *cp = NULL;
-      char *tmpptr;
+    if (iter.separators > 0)
+      {
+	char *cp = wstartp;
+	for (int i = 0; i < intdig_no; ++i)
+	  {
+	    if (__grouping_iterator_next (&iter))
+	      __printf_buffer_putc (buf, thousands_sep);
+	    __printf_buffer_putc (buf, *cp);
+	    ++cp;
+	  }
+	__printf_buffer_write (buf, cp, wcp - cp);
+      }
+    else
+      __printf_buffer_write (buf, wstartp, wcp - wstartp);
 
-      if (! wide)
-	{
-	  /* Create the single byte string.  */
-	  size_t decimal_len;
-	  size_t thousands_sep_len;
-	  wchar_t *copywc;
-	  size_t factor;
-	  if (info->i18n)
-	    factor = _nl_lookup_word (loc, LC_CTYPE, _NL_CTYPE_MB_CUR_MAX);
-	  else
-	    factor = 1;
+    if (info->left)
+      __printf_buffer_pad (buf, info->pad, width);
+  }
 
-	  decimal_len = strlen (decimal);
+  if (buffer_malloced)
+    free (wbuffer);
+}
 
-	  if (thousands_sep == NULL)
-	    thousands_sep_len = 0;
-	  else
-	    thousands_sep_len = strlen (thousands_sep);
+/* ASCII to localization translation.  Multibyte version.  */
+struct __printf_buffer_fp
+{
+  struct __printf_buffer base;
 
-	  size_t nbuffer = (2 + chars_needed * factor + decimal_len
-			    + ngroups * thousands_sep_len);
-	  if (__glibc_unlikely (buffer_malloced))
-	    {
-	      buffer = (char *) malloc (nbuffer);
-	      if (buffer == NULL)
-		{
-		  /* Signal an error to the caller.  */
-		  free (wbuffer);
-		  return -1;
-		}
-	    }
-	  else
-	    buffer = (char *) alloca (nbuffer);
-	  buffer_end = buffer + nbuffer;
-
-	  /* Now copy the wide character string.  Since the character
-	     (except for the decimal point and thousands separator) must
-	     be coming from the ASCII range we can esily convert the
-	     string without mapping tables.  */
-	  for (cp = buffer, copywc = wstartp; copywc < wcp; ++copywc)
-	    if (*copywc == decimalwc)
-	      cp = (char *) __mempcpy (cp, decimal, decimal_len);
-	    else if (*copywc == thousands_sepwc)
-	      cp = (char *) __mempcpy (cp, thousands_sep, thousands_sep_len);
-	    else
-	      *cp++ = (char) *copywc;
-	}
+  /* Replacement for ',' and '.'.  */
+  const char *thousands_sep;
+  const char *decimal;
+  unsigned char decimal_point_bytes;
+  unsigned char thousands_sep_length;
 
-      tmpptr = buffer;
-      if (__glibc_unlikely (info->i18n))
-	{
-#ifdef COMPILE_WPRINTF
-	  wstartp = _i18n_number_rewrite (wstartp, wcp,
-					  wbuffer + wbuffer_to_alloc);
-	  wcp = wbuffer + wbuffer_to_alloc;
-	  assert ((uintptr_t) wbuffer <= (uintptr_t) wstartp);
-	  assert ((uintptr_t) wstartp
-		  < (uintptr_t) wbuffer + wbuffer_to_alloc);
-#else
-	  tmpptr = _i18n_number_rewrite (tmpptr, cp, buffer_end);
-	  cp = buffer_end;
-	  assert ((uintptr_t) buffer <= (uintptr_t) tmpptr);
-	  assert ((uintptr_t) tmpptr < (uintptr_t) buffer_end);
-#endif
-	}
+  /* Buffer to write to.   */
+  struct __printf_buffer *next;
+
+  /* Activates outdigit translation if not NULL.  */
+  struct __locale_data *ctype;
 
-      PRINT (tmpptr, wstartp, wide ? wcp - wstartp : cp - tmpptr);
+  /* Buffer to which the untranslated ASCII digits are written.  */
+  char untranslated[64];
+};
 
-      /* Free the memory if necessary.  */
-      if (__glibc_unlikely (buffer_malloced))
+/*  Multibyte buffer-to-buffer flush function with full translation.  */
+void
+__printf_buffer_flush_fp (struct __printf_buffer_fp *buf)
+{
+  /* No need to update buf->base.written; the actual count is
+     maintained in buf->next->written.  */
+  for (char *p = buf->untranslated; p < buf->base.write_ptr; ++p)
+    {
+      char ch = *p;
+      const char *replacement = NULL;
+      unsigned int replacement_bytes;
+      if (ch == ',')
+	{
+	  replacement = buf->thousands_sep;
+	  replacement_bytes = buf->thousands_sep_length;
+	}
+      else if (ch == '.')
+	{
+	  replacement = buf->decimal;
+	  replacement_bytes = buf->decimal_point_bytes;
+	}
+      else if (buf->ctype != NULL && '0' <= ch && ch <= '9')
 	{
-	  free (buffer);
-	  free (wbuffer);
-	  /* Avoid a double free if the subsequent PADN encounters an
-	     I/O error.  */
-	  buffer = NULL;
-	  wbuffer = NULL;
+	  int digit = ch - '0';
+	  replacement
+	    = buf->ctype->values[_NL_ITEM_INDEX (_NL_CTYPE_OUTDIGIT0_MB)
+				 + digit].string;
+	  struct lc_ctype_data *ctype = buf->ctype->private;
+	  replacement_bytes = ctype->outdigit_bytes[digit];
 	}
+      if (replacement == NULL)
+	__printf_buffer_putc (buf->next, ch);
+      else
+	__printf_buffer_write (buf->next, replacement, replacement_bytes);
     }
 
-    if (info->left && width > 0)
-      PADN (info->pad, width);
-  }
-  return done;
+  if (!__printf_buffer_has_failed (buf->next))
+    buf->base.write_ptr = buf->untranslated;
+  else
+    __printf_buffer_mark_failed (&buf->base);
 }
-libc_hidden_def (__printf_fp_l)
 
-int
-___printf_fp (FILE *fp, const struct printf_info *info,
-	      const void *const *args)
+void
+__printf_fp_l_buffer (struct __printf_buffer *buf, locale_t loc,
+		      const struct printf_info *info,
+		      const void *const *args)
 {
-  return __printf_fp_l (fp, _NL_CURRENT_LOCALE, info, args);
+  struct __printf_buffer_fp tmp;
+
+  if (info->extra)
+    {
+      tmp.thousands_sep = _nl_lookup (loc, LC_MONETARY, MON_THOUSANDS_SEP);
+      tmp.decimal = _nl_lookup (loc, LC_MONETARY, MON_DECIMAL_POINT);
+      if (tmp.decimal[0] == '\0')
+	tmp.decimal = _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT);
+    }
+  else
+    {
+      tmp.thousands_sep = _nl_lookup (loc, LC_NUMERIC, THOUSANDS_SEP);
+      tmp.decimal = _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT);
+    }
+
+  tmp.thousands_sep_length = strlen (tmp.thousands_sep);
+  if (tmp.decimal[1] == '\0' && tmp.thousands_sep_length <= 1
+      && !info->i18n)
+    {
+      /* Emit the the characters directly.  This is only possible if the
+	 separators have length 1 (or 0 in case of thousands_sep).  i18n
+	 digit translation still needs the full conversion.  */
+      __printf_fp_buffer_1 (buf, loc,
+			    tmp.thousands_sep[0], tmp.decimal[0],
+			    tmp.thousands_sep_length,
+			    info, args);
+      return;
+    }
+
+  tmp.decimal_point_bytes = strlen (tmp.decimal);
+
+  if (info->i18n)
+    tmp.ctype = loc->__locales[LC_CTYPE];
+  else
+    tmp.ctype = NULL;
+  tmp.next = buf;
+
+  __printf_buffer_init (&tmp.base, tmp.untranslated, sizeof (tmp.untranslated),
+			__printf_buffer_mode_fp);
+  __printf_fp_buffer_1 (&tmp.base, loc, ',', '.',
+			tmp.thousands_sep_length, info, args);
+  if (__printf_buffer_has_failed (&tmp.base))
+    {
+      __printf_buffer_mark_failed (tmp.next);
+      return;
+    }
+  __printf_buffer_flush_fp (&tmp);
 }
-ldbl_hidden_def (___printf_fp, __printf_fp)
-ldbl_strong_alias (___printf_fp, __printf_fp)
 
-\f
-/* Return the number of extra grouping characters that will be inserted
-   into a number with INTDIG_MAX integer digits.  */
+/* The wide version is implemented on top of the multibyte version using
+   translation.  */
 
-unsigned int
-__guess_grouping (unsigned int intdig_max, const char *grouping)
+struct __printf_buffer_fp_to_wide
 {
-  unsigned int groups;
+  struct __printf_buffer base;
+  wchar_t thousands_sep_wc;
+  wchar_t decimalwc;
+  struct __wprintf_buffer *next;
 
-  /* We treat all negative values like CHAR_MAX.  */
+  /* Activates outdigit translation if not NULL.  */
+  struct __locale_data *ctype;
 
-  if (*grouping == CHAR_MAX || *grouping <= 0)
-    /* No grouping should be done.  */
-    return 0;
+  char untranslated[64];
+};
 
-  groups = 0;
-  while (intdig_max > (unsigned int) *grouping)
+void
+__printf_buffer_flush_fp_to_wide (struct __printf_buffer_fp_to_wide *buf)
+{
+  /* No need to update buf->base.written; the actual count is
+     maintained in buf->next->written.  */
+  for (char *p = buf->untranslated; p < buf->base.write_ptr; ++p)
     {
-      ++groups;
-      intdig_max -= *grouping++;
-
-      if (*grouping == CHAR_MAX
-#if CHAR_MIN < 0
-	  || *grouping < 0
-#endif
-	  )
-	/* No more grouping should be done.  */
-	break;
-      else if (*grouping == 0)
+      /* wchar_t overlaps with char in the ASCII range.  */
+      wchar_t ch = *p;
+      if (ch == L',')
 	{
-	  /* Same grouping repeats.  */
-	  groups += (intdig_max - 1) / grouping[-1];
-	  break;
+	  ch = buf->thousands_sep_wc;
+	  if (ch == 0)
+	    continue;
 	}
+      else if (ch == L'.')
+	ch = buf->decimalwc;
+      else if (buf->ctype != NULL && L'0' <= ch && ch <= L'9')
+	ch = buf->ctype->values[_NL_ITEM_INDEX (_NL_CTYPE_OUTDIGIT0_WC)
+				+ ch - L'0'].word;
+      __wprintf_buffer_putc (buf->next, ch);
     }
 
-  return groups;
+  if (!__wprintf_buffer_has_failed (buf->next))
+    buf->base.write_ptr = buf->untranslated;
+  else
+    __printf_buffer_mark_failed (&buf->base);
 }
 
-/* Group the INTDIG_NO integer digits of the number in [BUF,BUFEND).
-   There is guaranteed enough space past BUFEND to extend it.
-   Return the new end of buffer.  */
-
-static wchar_t *
-group_number (wchar_t *buf, wchar_t *bufend, unsigned int intdig_no,
-	      const char *grouping, wchar_t thousands_sep, int ngroups)
+void
+__wprintf_fp_l_buffer (struct __wprintf_buffer *buf, locale_t loc,
+		       const struct printf_info *info,
+		       const void *const *args)
 {
-  wchar_t *p;
-
-  if (ngroups == 0)
-    return bufend;
+  struct __printf_buffer_fp_to_wide tmp;
+  if (info->extra)
+    {
+      tmp.decimalwc = _nl_lookup_word (loc, LC_MONETARY,
+				       _NL_MONETARY_DECIMAL_POINT_WC);
+      tmp.thousands_sep_wc = _nl_lookup_word (loc, LC_MONETARY,
+					      _NL_MONETARY_THOUSANDS_SEP_WC);
+      if (tmp.decimalwc == 0)
+	tmp.decimalwc = _nl_lookup_word (loc, LC_NUMERIC,
+					 _NL_NUMERIC_DECIMAL_POINT_WC);
+    }
+  else
+    {
+      tmp.decimalwc = _nl_lookup_word (loc, LC_NUMERIC,
+				       _NL_NUMERIC_DECIMAL_POINT_WC);
+      tmp.thousands_sep_wc = _nl_lookup_word (loc, LC_NUMERIC,
+					      _NL_NUMERIC_THOUSANDS_SEP_WC);
+    }
 
-  /* Move the fractional part down.  */
-  __wmemmove (buf + intdig_no + ngroups, buf + intdig_no,
-	      bufend - (buf + intdig_no));
+  if (info->i18n)
+    tmp.ctype = loc->__locales[LC_CTYPE];
+  else
+    tmp.ctype = NULL;
+  tmp.next = buf;
 
-  p = buf + intdig_no + ngroups - 1;
-  do
+  __printf_buffer_init (&tmp.base, tmp.untranslated, sizeof (tmp.untranslated),
+			__printf_buffer_mode_fp_to_wide);
+  __printf_fp_buffer_1 (&tmp.base, loc, ',', '.', 1, info, args);
+  if (__printf_buffer_has_failed (&tmp.base))
     {
-      unsigned int len = *grouping++;
-      do
-	*p-- = buf[--intdig_no];
-      while (--len > 0);
-      *p-- = thousands_sep;
+      __wprintf_buffer_mark_failed (tmp.next);
+      return;
+    }
+  __printf_buffer_flush (&tmp.base);
+}
 
-      if (*grouping == CHAR_MAX
-#if CHAR_MIN < 0
-	  || *grouping < 0
-#endif
-	  )
-	/* No more grouping should be done.  */
-	break;
-      else if (*grouping == 0)
-	/* Same grouping repeats.  */
-	--grouping;
-    } while (intdig_no > (unsigned int) *grouping);
-
-  /* Copy the remaining ungrouped digits.  */
-  do
-    *p-- = buf[--intdig_no];
-  while (p > buf);
-
-  return bufend + ngroups;
+int
+___printf_fp (FILE *fp, const struct printf_info *info,
+	      const void *const *args)
+{
+  if (info->wide)
+    {
+      struct __wprintf_buffer_to_file buf;
+      __wprintf_buffer_to_file_init (&buf, fp);
+      __wprintf_fp_l_buffer (&buf.base, _NL_CURRENT_LOCALE, info, args);
+      return __wprintf_buffer_to_file_done (&buf);
+    }
+  else
+    {
+      struct __printf_buffer_to_file buf;
+      __printf_buffer_to_file_init (&buf, fp);
+      __printf_fp_l_buffer (&buf.base, _NL_CURRENT_LOCALE, info, args);
+      return __printf_buffer_to_file_done (&buf);
+    }
 }
+ldbl_hidden_def (___printf_fp, __printf_fp)
+ldbl_strong_alias (___printf_fp, __printf_fp)
diff --git a/stdio-common/printf_fphex.c b/stdio-common/printf_fphex.c
index df11b4a166..83bac372f3 100644
--- a/stdio-common/printf_fphex.c
+++ b/stdio-common/printf_fphex.c
@@ -17,10 +17,12 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <array_length.h>
+#include <assert.h>
 #include <ctype.h>
 #include <ieee754.h>
 #include <math.h>
 #include <printf.h>
+#include <libioP.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -30,6 +32,9 @@
 #include <locale/localeinfo.h>
 #include <stdbool.h>
 #include <rounding-mode.h>
+#include <sys/param.h>
+#include <printf_buffer.h>
+#include <errno.h>
 
 #if __HAVE_DISTINCT_FLOAT128
 # include "ieee754_float128.h"
@@ -39,58 +44,11 @@
 		IEEE854_FLOAT128_BIAS)
 #endif
 
-/* #define NDEBUG 1*/		/* Undefine this for debugging assertions.  */
-#include <assert.h>
-
-#include <libioP.h>
-#define PUT(f, s, n) _IO_sputn (f, s, n)
-#define PAD(f, c, n) (wide ? _IO_wpadn (f, c, n) : _IO_padn (f, c, n))
-#undef putc
-#define putc(c, f) (wide \
-		     ? (int)_IO_putwc_unlocked (c, f) : _IO_putc_unlocked (c, f))
-
-\f
-/* Macros for doing the actual output.  */
-
-#define outchar(ch)							      \
-  do									      \
-    {									      \
-      const int outc = (ch);						      \
-      if (putc (outc, fp) == EOF)					      \
-	return -1;							      \
-      ++done;								      \
-    } while (0)
-
-#define PRINT(ptr, wptr, len)						      \
-  do									      \
-    {									      \
-      size_t outlen = (len);						      \
-      if (wide)								      \
-	while (outlen-- > 0)						      \
-	  outchar (*wptr++);						      \
-      else								      \
-	while (outlen-- > 0)						      \
-	  outchar (*ptr++);						      \
-    } while (0)
-
-#define PADN(ch, len)							      \
-  do									      \
-    {									      \
-      if (PAD (fp, ch, len) != len)					      \
-	return -1;							      \
-      done += len;							      \
-    }									      \
-  while (0)
-
-#ifndef MIN
-# define MIN(a,b) ((a)<(b)?(a):(b))
-#endif
-\f
-
-int
-__printf_fphex (FILE *fp,
-		const struct printf_info *info,
-		const void *const *args)
+static void
+__printf_fphex_buffer (struct __printf_buffer *buf,
+		       const char *decimal,
+		       const struct printf_info *info,
+		       const void *const *args)
 {
   /* The floating-point value to output.  */
   union
@@ -103,33 +61,19 @@ __printf_fphex (FILE *fp,
     }
   fpnum;
 
-  /* Locale-dependent representation of decimal point. Hexadecimal
-     formatting always using LC_NUMERIC (disregarding info->extra).  */
-  const char *decimal = _NL_CURRENT (LC_NUMERIC, DECIMAL_POINT);
-  wchar_t decimalwc = _NL_CURRENT_WORD (LC_NUMERIC, _NL_NUMERIC_DECIMAL_POINT_WC);
-
-  /* The decimal point character must never be zero.  */
-  assert (*decimal != '\0' && decimalwc != L'\0');
-
   /* "NaN" or "Inf" for the special cases.  */
   const char *special = NULL;
-  const wchar_t *wspecial = NULL;
 
   /* Buffer for the generated number string for the mantissa.  The
      maximal size for the mantissa is 128 bits.  */
   char numbuf[32];
   char *numstr;
   char *numend;
-  wchar_t wnumbuf[32];
-  wchar_t *wnumstr;
-  wchar_t *wnumend;
   int negative;
 
   /* The maximal exponent of two in decimal notation has 5 digits.  */
   char expbuf[5];
   char *expstr;
-  wchar_t wexpbuf[5];
-  wchar_t *wexpstr;
   int expnegative;
   int exponent;
 
@@ -145,12 +89,6 @@ __printf_fphex (FILE *fp,
   /* Width.  */
   int width = info->width;
 
-  /* Number of characters written.  */
-  int done = 0;
-
-  /* Nonzero if this is output on a wide character stream.  */
-  int wide = info->wide;
-
 #define PRINTF_FPHEX_FETCH(FLOAT, VAR)					\
   {									\
     (VAR) = *(const FLOAT *) args[0];					\
@@ -159,30 +97,18 @@ __printf_fphex (FILE *fp,
     if (isnan (VAR))							\
       {									\
 	if (isupper (info->spec))					\
-	  {								\
-	    special = "NAN";						\
-	    wspecial = L"NAN";						\
-	  }								\
+	  special = "NAN";						\
 	else								\
-	  {								\
-	    special = "nan";						\
-	    wspecial = L"nan";						\
-	  }								\
+	  special = "nan";						\
       }									\
     else								\
       {									\
 	if (isinf (VAR))						\
 	  {								\
 	    if (isupper (info->spec))					\
-	      {								\
-		special = "INF";					\
-		wspecial = L"INF";					\
-	      }								\
+	      special = "INF";						\
 	    else							\
-	      {								\
-		special = "inf";					\
-		wspecial = L"inf";					\
-	      }								\
+	      special = "inf";						\
 	  }								\
       }									\
     negative = signbit (VAR);						\
@@ -211,22 +137,22 @@ __printf_fphex (FILE *fp,
 	--width;
       width -= 3;
 
-      if (!info->left && width > 0)
-	PADN (' ', width);
+      if (!info->left)
+	__printf_buffer_pad (buf, ' ', width);
 
       if (negative)
-	outchar ('-');
+	__printf_buffer_putc (buf, '-');
       else if (info->showsign)
-	outchar ('+');
+	__printf_buffer_putc (buf, '+');
       else if (info->space)
-	outchar (' ');
+	__printf_buffer_putc (buf, ' ');
 
-      PRINT (special, wspecial, 3);
+      __printf_buffer_puts (buf, special);
 
-      if (info->left && width > 0)
-	PADN (' ', width);
+      if (info->left)
+	__printf_buffer_pad (buf, ' ', width);
 
-      return done;
+      return;
     }
 
 #if __HAVE_DISTINCT_FLOAT128
@@ -248,26 +174,15 @@ __printf_fphex (FILE *fp,
       zero_mantissa = num == 0;
 
       if (sizeof (unsigned long int) > 6)
-	{
-	  wnumstr = _itowa_word (num, wnumbuf + (sizeof wnumbuf) / sizeof (wchar_t), 16,
-				 info->spec == 'A');
 	  numstr = _itoa_word (num, numbuf + sizeof numbuf, 16,
 			       info->spec == 'A');
-	}
       else
-	{
-	  wnumstr = _itowa (num, wnumbuf + sizeof wnumbuf / sizeof (wchar_t), 16,
-			    info->spec == 'A');
 	  numstr = _itoa (num, numbuf + sizeof numbuf, 16,
 			  info->spec == 'A');
-	}
 
       /* Fill with zeroes.  */
-      while (wnumstr > wnumbuf + (sizeof wnumbuf - 52) / sizeof (wchar_t))
-	{
-	  *--wnumstr = L'0';
-	  *--numstr = '0';
-	}
+      while (numstr > numbuf + (sizeof numbuf - 13))
+	*--numstr = '0';
 
       leading = fpnum.dbl.ieee.exponent == 0 ? '0' : '1';
 
@@ -303,13 +218,9 @@ __printf_fphex (FILE *fp,
   /* Look for trailing zeroes.  */
   if (! zero_mantissa)
     {
-      wnumend = array_end (wnumbuf);
       numend = array_end (numbuf);
-      while (wnumend[-1] == L'0')
-	{
-	  --wnumend;
+      while (numend[-1] == '0')
 	  --numend;
-	}
 
       bool do_round_away = false;
 
@@ -348,7 +259,6 @@ __printf_fphex (FILE *fp,
 		 like in ASCII.  This is true for the rest of GNU, too.  */
 	      if (ch == '9')
 		{
-		  wnumstr[cnt] = (wchar_t) info->spec;
 		  numstr[cnt] = info->spec;	/* This is tricky,
 						   think about it!  */
 		  break;
@@ -356,14 +266,10 @@ __printf_fphex (FILE *fp,
 	      else if (tolower (ch) < 'f')
 		{
 		  ++numstr[cnt];
-		  ++wnumstr[cnt];
 		  break;
 		}
 	      else
-		{
-		  numstr[cnt] = '0';
-		  wnumstr[cnt] = L'0';
-		}
+		numstr[cnt] = '0';
 	    }
 	  if (cnt < 0)
 	    {
@@ -397,13 +303,10 @@ __printf_fphex (FILE *fp,
       if (precision == -1)
 	precision = 0;
       numend = numstr;
-      wnumend = wnumstr;
     }
 
   /* Now we can compute the exponent string.  */
   expstr = _itoa_word (exponent, expbuf + sizeof expbuf, 10, 0);
-  wexpstr = _itowa_word (exponent,
-			 wexpbuf + sizeof wexpbuf / sizeof (wchar_t), 10, 0);
 
   /* Now we have all information to compute the size.  */
   width -= ((negative || info->showsign || info->space)
@@ -417,54 +320,110 @@ __printf_fphex (FILE *fp,
      A special case when the mantissa or the precision is zero and the `#'
      is not given.  In this case we must not print the decimal point.  */
   if (precision > 0 || info->alt)
-    width -= wide ? 1 : strlen (decimal);
+    --width;
 
-  if (!info->left && info->pad != '0' && width > 0)
-    PADN (' ', width);
+  if (!info->left && info->pad != '0')
+    __printf_buffer_pad (buf, ' ', width);
 
   if (negative)
-    outchar ('-');
+    __printf_buffer_putc (buf, '-');
   else if (info->showsign)
-    outchar ('+');
+    __printf_buffer_putc (buf, '+');
   else if (info->space)
-    outchar (' ');
+    __printf_buffer_putc (buf, ' ');
 
-  outchar ('0');
+  __printf_buffer_putc (buf, '0');
   if ('X' - 'A' == 'x' - 'a')
-    outchar (info->spec + ('x' - 'a'));
+    __printf_buffer_putc (buf, info->spec + ('x' - 'a'));
   else
-    outchar (info->spec == 'A' ? 'X' : 'x');
+    __printf_buffer_putc (buf, info->spec == 'A' ? 'X' : 'x');
 
-  if (!info->left && info->pad == '0' && width > 0)
-    PADN ('0', width);
+  if (!info->left && info->pad == '0')
+    __printf_buffer_pad (buf, '0', width);
 
-  outchar (leading);
+  __printf_buffer_putc (buf, leading);
 
   if (precision > 0 || info->alt)
-    {
-      const wchar_t *wtmp = &decimalwc;
-      PRINT (decimal, wtmp, wide ? 1 : strlen (decimal));
-    }
+    __printf_buffer_puts (buf, decimal);
 
   if (precision > 0)
     {
       ssize_t tofill = precision - (numend - numstr);
-      PRINT (numstr, wnumstr, MIN (numend - numstr, precision));
-      if (tofill > 0)
-	PADN ('0', tofill);
+      __printf_buffer_write (buf, numstr, MIN (numend - numstr, precision));
+      __printf_buffer_pad (buf, '0', tofill);
     }
 
   if ('P' - 'A' == 'p' - 'a')
-    outchar (info->spec + ('p' - 'a'));
+    __printf_buffer_putc (buf, info->spec + ('p' - 'a'));
   else
-    outchar (info->spec == 'A' ? 'P' : 'p');
+    __printf_buffer_putc (buf, info->spec == 'A' ? 'P' : 'p');
+
+  __printf_buffer_putc (buf, expnegative ? '-' : '+');
+
+  __printf_buffer_write (buf, expstr, (expbuf + sizeof expbuf) - expstr);
+
+  if (info->left && info->pad != '0')
+    __printf_buffer_pad (buf, info->pad, width);
+}
+
+void
+__printf_fphex_l_buffer (struct __printf_buffer *buf, locale_t loc,
+			 const struct printf_info *info,
+			 const void *const *args)
+{
+  __printf_fphex_buffer (buf, _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT),
+			 info, args);
+}
 
-  outchar (expnegative ? '-' : '+');
 
-  PRINT (expstr, wexpstr, (expbuf + sizeof expbuf) - expstr);
+/* The wide buffer version is implemented by translating the output of
+   the multibyte verison.  */
 
-  if (info->left && info->pad != '0' && width > 0)
-    PADN (info->pad, width);
+struct __printf_buffer_fphex_to_wide
+{
+  struct __printf_buffer base;
+  wchar_t decimalwc;
+  struct __wprintf_buffer *next;
+  char untranslated[64];
+};
+
+/* Translate to wide characters, rewriting "." to the actual decimal
+   point.  */
+void
+__printf_buffer_flush_fphex_to_wide (struct __printf_buffer_fphex_to_wide *buf)
+{
+  /* No need to adjust buf->base.written, only buf->next->written matters.  */
+  for (char *p = buf->untranslated; p < buf->base.write_ptr; ++p)
+    {
+      /* wchar_t overlaps with char in the ASCII range.  */
+      wchar_t ch = *p;
+      if (ch == L'.')
+	ch = buf->decimalwc;
+      __wprintf_buffer_putc (buf->next, ch);
+    }
 
-  return done;
+  if (!__wprintf_buffer_has_failed (buf->next))
+    buf->base.write_ptr = buf->untranslated;
+  else
+    __printf_buffer_mark_failed (&buf->base);
+}
+
+void
+__wprintf_fphex_l_buffer (struct __wprintf_buffer *next, locale_t loc,
+			  const struct printf_info *info,
+			  const void *const *args)
+{
+  struct __printf_buffer_fphex_to_wide buf;
+  __printf_buffer_init (&buf.base, buf.untranslated, sizeof (buf.untranslated),
+			__printf_buffer_mode_fphex_to_wide);
+  buf.decimalwc = _nl_lookup_word (loc, LC_NUMERIC,
+				   _NL_NUMERIC_DECIMAL_POINT_WC);
+  buf.next = next;
+  __printf_fphex_buffer (&buf.base, ".", info, args);
+  if (__printf_buffer_has_failed (&buf.base))
+    {
+      __wprintf_buffer_mark_failed (buf.next);
+      return;
+    }
+  __printf_buffer_flush_fphex_to_wide (&buf);
 }
diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
index fb94961f37..83a6aea510 100644
--- a/stdio-common/vfprintf-internal.c
+++ b/stdio-common/vfprintf-internal.c
@@ -16,6 +16,7 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <array_length.h>
+#include <assert.h>
 #include <ctype.h>
 #include <limits.h>
 #include <printf.h>
@@ -29,9 +30,12 @@
 #include <sys/param.h>
 #include <_itoa.h>
 #include <locale/localeinfo.h>
+#include <grouping_iterator.h>
 #include <stdio.h>
 #include <scratch_buffer.h>
 #include <intprops.h>
+#include <printf_buffer.h>
+#include <printf_buffer_to_file.h>
 
 /* This code is shared between the standard stdio implementation found
    in GNU C library and the libio implementation originally found in
@@ -47,21 +51,21 @@
 #endif
 
 #define ARGCHECK(S, Format) \
-  do									      \
-    {									      \
-      /* Check file argument for consistence.  */			      \
-      CHECK_FILE (S, -1);						      \
-      if (S->_flags & _IO_NO_WRITES)					      \
-	{								      \
-	  S->_flags |= _IO_ERR_SEEN;					      \
-	  __set_errno (EBADF);						      \
-	  return -1;							      \
-	}								      \
-      if (Format == NULL)						      \
-	{								      \
-	  __set_errno (EINVAL);						      \
-	  return -1;							      \
-	}								      \
+  do									     \
+    {									     \
+      /* Check file argument for consistence.  */			     \
+      CHECK_FILE (S, -1);						     \
+      if (S->_flags & _IO_NO_WRITES)					     \
+       {								     \
+	 S->_flags |= _IO_ERR_SEEN;					     \
+	 __set_errno (EBADF);						     \
+	 return -1;							     \
+       }								     \
+      if (Format == NULL)						     \
+       {								     \
+	 __set_errno (EINVAL);						     \
+	 return -1;							     \
+       }								     \
     } while (0)
 #define UNBUFFERED_P(S) ((S)->_flags & _IO_UNBUFFERED)
 
@@ -116,37 +120,9 @@
   while (0)
 #endif
 
-/* Add LENGTH to DONE.  Return the new value of DONE, or -1 on
-   overflow (and set errno accordingly).  */
-static inline int
-done_add_func (size_t length, int done)
-{
-  if (done < 0)
-    return done;
-  int ret;
-  if (INT_ADD_WRAPV (done, length, &ret))
-    {
-      __set_errno (EOVERFLOW);
-      return -1;
-    }
-  return ret;
-}
-
-#define done_add(val)							\
-  do									\
-    {									\
-      /* Ensure that VAL has a type similar to int.  */			\
-      _Static_assert (sizeof (val) == sizeof (int), "value int size");	\
-      _Static_assert ((__typeof__ (val)) -1 < 0, "value signed");	\
-      done = done_add_func ((val), done);				\
-      if (done < 0)							\
-	goto all_done;							\
-    }									\
-  while (0)
-
 #ifndef COMPILE_WPRINTF
+# include "printf_buffer-char.h"
 # define vfprintf	__vfprintf_internal
-# define CHAR_T		char
 # define OTHER_CHAR_T   wchar_t
 # define UCHAR_T	unsigned char
 # define INT_T		int
@@ -155,14 +131,12 @@ typedef const char *THOUSANDS_SEP_T;
 # define ISDIGIT(Ch)	((unsigned int) ((Ch) - '0') < 10)
 # define STR_LEN(Str)	strlen (Str)
 
-# define PUT(F, S, N)	_IO_sputn ((F), (S), (N))
-# define PUTC(C, F)	_IO_putc_unlocked (C, F)
 # define ORIENT		if (_IO_vtable_offset (s) == 0 && _IO_fwide (s, -1) != -1)\
 			  return -1
 # define CONVERT_FROM_OTHER_STRING __wcsrtombs
 #else
+# include "printf_buffer-wchar_t.h"
 # define vfprintf	__vfwprintf_internal
-# define CHAR_T		wchar_t
 # define OTHER_CHAR_T   char
 /* This is a hack!!!  There should be a type uwchar_t.  */
 # define UCHAR_T	unsigned int /* uwchar_t */
@@ -174,8 +148,6 @@ typedef wchar_t THOUSANDS_SEP_T;
 
 # include <_itowa.h>
 
-# define PUT(F, S, N)	_IO_sputn ((F), (S), (N))
-# define PUTC(C, F)	_IO_putwc_unlocked (C, F)
 # define ORIENT		if (_IO_fwide (s, 1) != 1) return -1
 # define CONVERT_FROM_OTHER_STRING __mbsrtowcs
 
@@ -186,76 +158,16 @@ typedef wchar_t THOUSANDS_SEP_T;
 # define EOF WEOF
 #endif
 
-static inline int
-pad_func (FILE *s, CHAR_T padchar, int width, int done)
-{
-  if (width > 0)
-    {
-      ssize_t written;
-#ifndef COMPILE_WPRINTF
-      written = _IO_padn (s, padchar, width);
-#else
-      written = _IO_wpadn (s, padchar, width);
-#endif
-      if (__glibc_unlikely (written != width))
-	return -1;
-      return done_add_func (width, done);
-    }
-  return done;
-}
-
-#define PAD(Padchar)							\
-  do									\
-    {									\
-      done = pad_func (s, (Padchar), width, done);			\
-      if (done < 0)							\
-	goto all_done;							\
-    }									\
-  while (0)
-
-#include "_i18n_number.h"
-
 /* Include the shared code for parsing the format string.  */
 #include "printf-parse.h"
 
 
-#define	outchar(Ch)							      \
-  do									      \
-    {									      \
-      const INT_T outc = (Ch);						      \
-      if (PUTC (outc, s) == EOF || done == INT_MAX)			      \
-	{								      \
-	  done = -1;							      \
-	  goto all_done;						      \
-	}								      \
-      ++done;								      \
-    }									      \
-  while (0)
-
-static inline int
-outstring_func (FILE *s, const UCHAR_T *string, size_t length, int done)
-{
-  assert ((size_t) done <= (size_t) INT_MAX);
-  if ((size_t) PUT (s, string, length) != (size_t) (length))
-    return -1;
-  return done_add_func (length, done);
-}
-
-#define outstring(String, Len)						\
-  do									\
-    {									\
-      const void *string_ = (String);					\
-      done = outstring_func (s, string_, (Len), done);			\
-      if (done < 0)							\
-	goto all_done;							\
-    }									\
-   while (0)
-
 /* Write the string SRC to S.  If PREC is non-negative, write at most
    PREC bytes.  If LEFT is true, perform left justification.  */
-static int
-outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
-				 int width, bool left, int done)
+static void
+outstring_converted_wide_string (struct Xprintf_buffer *target,
+				 const OTHER_CHAR_T *src, int prec,
+				 int width, bool left)
 {
   /* Use a small buffer to combine processing of multiple characters.
      CONVERT_FROM_OTHER_STRING expects the buffer size in (wide)
@@ -290,7 +202,10 @@ outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
 	      size_t written = CONVERT_FROM_OTHER_STRING
 		(buf, &src_copy, write_limit, &mbstate);
 	      if (written == (size_t) -1)
-		return -1;
+		{
+		  Xprintf_buffer_mark_failed (target);
+		  return;
+		}
 	      if (written == 0)
 		break;
 	      total_written += written;
@@ -299,12 +214,9 @@ outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
 	}
 
       /* Output initial padding.  */
-      if (total_written < width)
-	{
-	  done = pad_func (s, L_(' '), width - total_written, done);
-	  if (done < 0)
-	    return done;
-	}
+      Xprintf_buffer_pad (target, L_(' '), width - total_written);
+      if (Xprintf_buffer_has_failed (target))
+	return;
     }
 
   /* Convert the input string, piece by piece.  */
@@ -324,12 +236,13 @@ outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
 	size_t written = CONVERT_FROM_OTHER_STRING
 	  (buf, &src, write_limit, &mbstate);
 	if (written == (size_t) -1)
-	  return -1;
+	  {
+	    Xprintf_buffer_mark_failed (target);
+	    return;
+	  }
 	if (written == 0)
 	  break;
-	done = outstring_func (s, (const UCHAR_T *) buf, written, done);
-	if (done < 0)
-	  return done;
+	Xprintf_buffer_write (target, buf, written);
 	total_written += written;
 	if (prec >= 0)
 	  remaining -= written;
@@ -337,21 +250,20 @@ outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
   }
 
   /* Add final padding.  */
-  if (width > 0 && left && total_written < width)
-    return pad_func (s, L_(' '), width - total_written, done);
-  return done;
+  if (width > 0 && left)
+    Xprintf_buffer_pad (target, L_(' '), width - total_written);
 }
 
 /* Calls __printf_fp or __printf_fphex based on the value of the
    format specifier INFO->spec.  */
-static inline int
-__printf_fp_spec (FILE *fp, const struct printf_info *info,
-		  const void *const *args)
+static inline void
+__printf_fp_spec (struct Xprintf_buffer *target,
+		  const struct printf_info *info, const void *const *args)
 {
   if (info->spec == 'a' || info->spec == 'A')
-    return __printf_fphex (fp, info, args);
+    Xprintf (fphex_l_buffer) (target, _NL_CURRENT_LOCALE, info, args);
   else
-    return __printf_fp (fp, info, args);
+    Xprintf (fp_l_buffer) (target, _NL_CURRENT_LOCALE, info, args);
 }
 
 /* For handling long_double and longlong we use the same flag.  If
@@ -656,31 +568,29 @@ static const uint8_t jump_table[] =
       REF (form_binary),	/* for 'B', 'b' */			      \
     }
 
-/* Helper function to provide temporary buffering for unbuffered streams.  */
-static int buffered_vfprintf (FILE *stream, const CHAR_T *fmt, va_list,
-			      unsigned int)
-     __THROW __attribute__ ((noinline));
-
 /* Handle positional format specifiers.  */
-static int printf_positional (FILE *s,
-			      const CHAR_T *format, int readonly_format,
-			      va_list ap, va_list *ap_savep, int done,
-			      int nspecs_done, const UCHAR_T *lead_str_end,
-			      CHAR_T *work_buffer, int save_errno,
-			      const char *grouping,
-			      THOUSANDS_SEP_T thousands_sep,
-			      unsigned int mode_flags);
+static void printf_positional (struct Xprintf_buffer *buf,
+			       const CHAR_T *format, int readonly_format,
+			       va_list ap, va_list *ap_savep,
+			       int nspecs_done, const UCHAR_T *lead_str_end,
+			       CHAR_T *work_buffer, int save_errno,
+			       const char *grouping,
+			       THOUSANDS_SEP_T thousands_sep,
+			       unsigned int mode_flags);
 
 /* Handle unknown format specifier.  */
-static int printf_unknown (FILE *, const struct printf_info *) __THROW;
-
-/* Group digits of number string.  */
-static CHAR_T *group_number (CHAR_T *, CHAR_T *, CHAR_T *, const char *,
-			     THOUSANDS_SEP_T);
-
-/* The function itself.  */
-int
-vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
+static void printf_unknown (struct Xprintf_buffer *,
+			    const struct printf_info *) __THROW;
+
+static void group_number (struct Xprintf_buffer *buf,
+			  struct grouping_iterator *iter,
+			  CHAR_T *from, CHAR_T *to,
+			  THOUSANDS_SEP_T thousands_sep, bool i18n);
+
+/* The buffer-based function itself.  */
+void
+Xprintf_buffer (struct Xprintf_buffer *buf, const CHAR_T *format,
+		  va_list ap, unsigned int mode_flags)
 {
   /* The character used as thousands separator.  */
   THOUSANDS_SEP_T thousands_sep = 0;
@@ -688,9 +598,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
   /* The string describing the size of groups of digits.  */
   const char *grouping;
 
-  /* Place to accumulate the result.  */
-  int done;
-
   /* Current character in format string.  */
   const UCHAR_T *f;
 
@@ -717,30 +624,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
      0 if unknown.  */
   int readonly_format = 0;
 
-  /* Orient the stream.  */
-#ifdef ORIENT
-  ORIENT;
-#endif
-
-  /* Sanity check of arguments.  */
-  ARGCHECK (s, format);
-
-#ifdef ORIENT
-  /* Check for correct orientation.  */
-  if (_IO_vtable_offset (s) == 0
-      && _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)
-      != (sizeof (CHAR_T) == 1 ? -1 : 1))
-    /* The stream is already oriented otherwise.  */
-    return EOF;
-#endif
-
-  if (UNBUFFERED_P (s))
-    /* Use a helper function which will allocate a local temporary buffer
-       for the stream and then call us again.  */
-    return buffered_vfprintf (s, format, ap, mode_flags);
-
   /* Initialize local variables.  */
-  done = 0;
   grouping = (const char *) -1;
 #ifdef __va_copy
   /* This macro will be available soon in gcc's <stdarg.h>.  We need it
@@ -759,17 +643,15 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
   f = lead_str_end = __find_specmb ((const UCHAR_T *) format);
 #endif
 
-  /* Lock stream.  */
-  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
-  _IO_flockfile (s);
-
   /* Write the literal text before the first format.  */
-  outstring ((const UCHAR_T *) format,
-	     lead_str_end - (const UCHAR_T *) format);
+  Xprintf_buffer_write (buf, format,
+			  lead_str_end - (const UCHAR_T *) format);
+  if (Xprintf_buffer_has_failed (buf))
+    return;
 
   /* If we only have to print a simple string, return now.  */
   if (*f == L_('\0'))
-    goto all_done;
+    return;
 
   /* Use the slow path in case any printf handler is registered.  */
   if (__glibc_unlikely (__printf_function_table != NULL
@@ -885,7 +767,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	    if (pos == -1)
 	      {
 		__set_errno (EOVERFLOW);
-		done = -1;
+		Xprintf_buffer_mark_failed (buf);
 		goto all_done;
 	      }
 
@@ -912,7 +794,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
       if (__glibc_unlikely (width == -1))
 	{
 	  __set_errno (EOVERFLOW);
-	  done = -1;
+	  Xprintf_buffer_mark_failed (buf);
 	  goto all_done;
 	}
 
@@ -935,7 +817,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	      if (pos == -1)
 		{
 		  __set_errno (EOVERFLOW);
-		  done = -1;
+		  Xprintf_buffer_mark_failed (buf);
 		  goto all_done;
 		}
 
@@ -958,7 +840,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	  if (prec == -1)
 	    {
 	      __set_errno (EOVERFLOW);
-	      done = -1;
+	      Xprintf_buffer_mark_failed (buf);
 	      goto all_done;
 	    }
 	}
@@ -1058,13 +940,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	    PARSE_FLOAT_VA_ARG_EXTENDED (info);
 	    const void *ptr = &the_arg;
 
-	    int function_done = __printf_fp_spec (s, &info, &ptr);
-	    if (function_done < 0)
-	      {
-		done = -1;
-		goto all_done;
-	      }
-	    done_add (function_done);
+	    __printf_fp_spec (buf, &info, &ptr);
 	  }
 	  break;
 
@@ -1073,7 +949,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	    {
 	      /* The format string ended before the specifier is complete.  */
 	      __set_errno (EINVAL);
-	      done = -1;
+	      Xprintf_buffer_mark_failed (buf);
 	      goto all_done;
 	    }
 
@@ -1093,30 +969,28 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 #endif
 
       /* Write the following constant string.  */
-      outstring (end_of_spec, f - end_of_spec);
+      Xprintf_buffer_write (buf, (const CHAR_T *) end_of_spec,
+			      f - end_of_spec);
     }
-  while (*f != L_('\0'));
+  while (*f != L_('\0') && !Xprintf_buffer_has_failed (buf));
 
-  /* Unlock stream and return.  */
-  goto all_done;
+ all_done:
+  /* printf_positional performs cleanup under its all_done label, so
+     vfprintf-process-arg.c uses it for this function and
+     printf_positional below.  */
+  return;
 
   /* Hand off processing for positional parameters.  */
 do_positional:
-  done = printf_positional (s, format, readonly_format, ap, &ap_save,
-			    done, nspecs_done, lead_str_end, work_buffer,
-			    save_errno, grouping, thousands_sep, mode_flags);
-
- all_done:
-  /* Unlock the stream.  */
-  _IO_funlockfile (s);
-  _IO_cleanup_region_end (0);
-
-  return done;
+  printf_positional (buf, format, readonly_format, ap, &ap_save,
+		     nspecs_done, lead_str_end, work_buffer,
+		     save_errno, grouping, thousands_sep, mode_flags);
 }
 \f
-static int
-printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
-		   va_list ap, va_list *ap_savep, int done, int nspecs_done,
+static void
+printf_positional (struct Xprintf_buffer * buf, const CHAR_T *format,
+		   int readonly_format,
+		   va_list ap, va_list *ap_savep, int nspecs_done,
 		   const UCHAR_T *lead_str_end,
 		   CHAR_T *work_buffer, int save_errno,
 		   const char *grouping, THOUSANDS_SEP_T thousands_sep,
@@ -1171,7 +1045,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 	{
 	  if (!scratch_buffer_grow_preserve (&specsbuf))
 	    {
-	      done = -1;
+	      Xprintf_buffer_mark_failed (buf);
 	      goto all_done;
 	    }
 	  specs = specsbuf.data;
@@ -1199,7 +1073,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
       = sizeof (*args_value) + sizeof (*args_size) + sizeof (*args_type);
     if (!scratch_buffer_set_array_size (&argsbuf, nargs, bytes_per_arg))
       {
-	done = -1;
+	Xprintf_buffer_mark_failed (buf);
 	goto all_done;
       }
     args_value = argsbuf.data;
@@ -1312,7 +1186,8 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
       }
 
   /* Now walk through all format specifiers and process them.  */
-  for (; (size_t) nspecs_done < nspecs; ++nspecs_done)
+  for (; (size_t) nspecs_done < nspecs && !Xprintf_buffer_has_failed (buf);
+       ++nspecs_done)
     {
       STEP4_TABLE;
 
@@ -1376,26 +1251,19 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 	}
 
       /* Process format specifiers.  */
-      while (1)
+      do
 	{
-	  int function_done;
-
 	  if (spec <= UCHAR_MAX
 	      && __printf_function_table != NULL
 	      && __printf_function_table[(size_t) spec] != NULL)
 	    {
-	      const void **ptr = alloca (specs[nspecs_done].ndata_args
-					 * sizeof (const void *));
-
-	      /* Fill in an array of pointers to the argument values.  */
-	      for (unsigned int i = 0; i < specs[nspecs_done].ndata_args;
-		   ++i)
-		ptr[i] = &args_value[specs[nspecs_done].data_arg + i];
-
-	      /* Call the function.  */
-	      function_done = __printf_function_table[(size_t) spec]
-		(s, &specs[nspecs_done].info, ptr);
-
+	      int function_done
+		= Xprintf (function_invoke) (buf,
+					     __printf_function_table[(size_t) spec],
+					     &args_value[specs[nspecs_done]
+							 .data_arg],
+					     specs[nspecs_done].ndata_args,
+					     &specs[nspecs_done].info);
 	      if (function_done != -2)
 		{
 		  /* If an error occurred we don't have information
@@ -1403,11 +1271,9 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 		  if (function_done < 0)
 		    {
 		      /* Function has set errno.  */
-		      done = -1;
+		      Xprintf_buffer_mark_failed (buf);
 		      goto all_done;
 		    }
-
-		  done_add (function_done);
 		  break;
 		}
 	    }
@@ -1450,327 +1316,159 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 	      }
 	    SETUP_FLOAT128_INFO (specs[nspecs_done].info);
 
-	    int function_done
-	      = __printf_fp_spec (s, &specs[nspecs_done].info, &ptr);
-	    if (function_done < 0)
-	      {
-		/* Error in print handler; up to handler to set errno.  */
-		done = -1;
-		goto all_done;
-	      }
-	    done_add (function_done);
+	    __printf_fp_spec (buf, &specs[nspecs_done].info, &ptr);
 	  }
 	  break;
 
 	  LABEL (form_unknown):
 	  {
-	    int function_done = printf_unknown (s, &specs[nspecs_done].info);
-
-	    /* If an error occurred we don't have information about #
-	       of chars.  */
-	    if (function_done < 0)
-	      {
-		/* Function has set errno.  */
-		done = -1;
-		goto all_done;
-	      }
-
-	    done_add (function_done);
+	    printf_unknown (buf, &specs[nspecs_done].info);
 	  }
 	  break;
 	}
+      while (Xprintf_buffer_has_failed (buf));
 
       /* Write the following constant string.  */
-      outstring (specs[nspecs_done].end_of_fmt,
-		 specs[nspecs_done].next_fmt
-		 - specs[nspecs_done].end_of_fmt);
+      Xprintf_buffer_write (buf,
+			      (const CHAR_T *) specs[nspecs_done].end_of_fmt,
+			      (specs[nspecs_done].next_fmt
+			       - specs[nspecs_done].end_of_fmt));
     }
  all_done:
   scratch_buffer_free (&argsbuf);
   scratch_buffer_free (&specsbuf);
-  return done;
 }
 \f
 /* Handle an unknown format specifier.  This prints out a canonicalized
    representation of the format spec itself.  */
-static int
-printf_unknown (FILE *s, const struct printf_info *info)
+static void
+printf_unknown (struct Xprintf_buffer *buf, const struct printf_info *info)
 {
-  int done = 0;
   CHAR_T work_buffer[MAX (sizeof (info->width), sizeof (info->prec)) * 3];
   CHAR_T *const workend
     = &work_buffer[sizeof (work_buffer) / sizeof (CHAR_T)];
   CHAR_T *w;
 
-  outchar (L_('%'));
+  Xprintf_buffer_putc (buf, L_('%'));
 
   if (info->alt)
-    outchar (L_('#'));
+    Xprintf_buffer_putc (buf, L_('#'));
   if (info->group)
-    outchar (L_('\''));
+    Xprintf_buffer_putc (buf, L_('\''));
   if (info->showsign)
-    outchar (L_('+'));
+    Xprintf_buffer_putc (buf, L_('+'));
   else if (info->space)
-    outchar (L_(' '));
+    Xprintf_buffer_putc (buf, L_(' '));
   if (info->left)
-    outchar (L_('-'));
+    Xprintf_buffer_putc (buf, L_('-'));
   if (info->pad == L_('0'))
-    outchar (L_('0'));
+    Xprintf_buffer_putc (buf, L_('0'));
   if (info->i18n)
-    outchar (L_('I'));
+    Xprintf_buffer_putc (buf, L_('I'));
 
   if (info->width != 0)
     {
       w = _itoa_word (info->width, workend, 10, 0);
       while (w < workend)
-	outchar (*w++);
+	Xprintf_buffer_putc (buf, *w++);
     }
 
   if (info->prec != -1)
     {
-      outchar (L_('.'));
+      Xprintf_buffer_putc (buf, L_('.'));
       w = _itoa_word (info->prec, workend, 10, 0);
       while (w < workend)
-	outchar (*w++);
+	Xprintf_buffer_putc (buf, *w++);
     }
 
   if (info->spec != L_('\0'))
-    outchar (info->spec);
-
- all_done:
-  return done;
+    Xprintf_buffer_putc (buf, info->spec);
 }
-\f
-/* Group the digits from W to REAR_PTR according to the grouping rules
-   of the current locale.  The interpretation of GROUPING is as in
-   `struct lconv' from <locale.h>.  The grouped number extends from
-   the returned pointer until REAR_PTR.  FRONT_PTR to W is used as a
-   scratch area.  */
-static CHAR_T *
-group_number (CHAR_T *front_ptr, CHAR_T *w, CHAR_T *rear_ptr,
-	      const char *grouping, THOUSANDS_SEP_T thousands_sep)
+
+static void
+group_number (struct Xprintf_buffer *buf,
+	      struct grouping_iterator *iter,
+	      CHAR_T *from, CHAR_T *to, THOUSANDS_SEP_T thousands_sep,
+	      bool i18n)
 {
-  /* Length of the current group.  */
-  int len;
-#ifndef COMPILE_WPRINTF
-  /* Length of the separator (in wide mode, the separator is always a
-     single wide character).  */
-  int tlen = strlen (thousands_sep);
+  if (!i18n)
+    for (CHAR_T *cp = from; cp != to; ++cp)
+      {
+	if (__grouping_iterator_next (iter))
+	  {
+#ifdef COMPILE_WPRINTF
+	    __wprintf_buffer_putc (buf, thousands_sep);
+#else
+	    __printf_buffer_puts (buf, thousands_sep);
 #endif
-
-  /* We treat all negative values like CHAR_MAX.  */
-
-  if (*grouping == CHAR_MAX || *grouping <= 0)
-    /* No grouping should be done.  */
-    return w;
-
-  len = *grouping++;
-
-  /* Copy existing string so that nothing gets overwritten.  */
-  memmove (front_ptr, w, (rear_ptr - w) * sizeof (CHAR_T));
-  CHAR_T *s = front_ptr + (rear_ptr - w);
-
-  w = rear_ptr;
-
-  /* Process all characters in the string.  */
-  while (s > front_ptr)
+	  }
+	Xprintf_buffer_putc (buf, *cp);
+      }
+  else
     {
-      *--w = *--s;
-
-      if (--len == 0 && s > front_ptr)
+      /* Apply digit translation and grouping.  */
+      for (CHAR_T *cp = from; cp != to; ++cp)
 	{
-	  /* A new group begins.  */
+	  if (__grouping_iterator_next (iter))
+	    {
 #ifdef COMPILE_WPRINTF
-	  if (w != s)
-	    *--w = thousands_sep;
-	  else
-	    /* Not enough room for the separator.  */
-	    goto copy_rest;
+	      __wprintf_buffer_putc (buf, thousands_sep);
 #else
-	  int cnt = tlen;
-	  if (tlen < w - s)
-	    do
-	      *--w = thousands_sep[--cnt];
-	    while (cnt > 0);
-	  else
-	    /* Not enough room for the separator.  */
-	    goto copy_rest;
-#endif
-
-	  if (*grouping == CHAR_MAX
-#if CHAR_MIN < 0
-		   || *grouping < 0
+	      __printf_buffer_puts (buf, thousands_sep);
 #endif
-		   )
-	    {
-	    copy_rest:
-	      /* No further grouping to be done.  Copy the rest of the
-		 number.  */
-	      w -= s - front_ptr;
-	      memmove (w, front_ptr, (s - front_ptr) * sizeof (CHAR_T));
-	      break;
 	    }
-	  else if (*grouping != '\0')
-	    len = *grouping++;
-	  else
-	    /* The previous grouping repeats ad infinitum.  */
-	    len = grouping[-1];
-	}
-    }
-  return w;
-}
-\f
-/* Helper "class" for `fprintf to unbuffered': creates a temporary buffer.  */
-struct helper_file
-  {
-    struct _IO_FILE_plus _f;
+	  int digit = *cp - '0';
 #ifdef COMPILE_WPRINTF
-    struct _IO_wide_data _wide_data;
-#endif
-    FILE *_put_stream;
-#ifdef _IO_MTSAFE_IO
-    _IO_lock_t lock;
-#endif
-  };
-
-static int
-_IO_helper_overflow (FILE *s, int c)
-{
-  FILE *target = ((struct helper_file*) s)->_put_stream;
-#ifdef COMPILE_WPRINTF
-  int used = s->_wide_data->_IO_write_ptr - s->_wide_data->_IO_write_base;
-  if (used)
-    {
-      size_t written = _IO_sputn (target, s->_wide_data->_IO_write_base, used);
-      if (written == 0 || written == WEOF)
-	return WEOF;
-      __wmemmove (s->_wide_data->_IO_write_base,
-		  s->_wide_data->_IO_write_base + written,
-		  used - written);
-      s->_wide_data->_IO_write_ptr -= written;
-    }
+	  __wprintf_buffer_putc
+	    (buf, _NL_CURRENT_WORD (LC_CTYPE,
+				    _NL_CTYPE_OUTDIGIT0_WC + digit));
 #else
-  int used = s->_IO_write_ptr - s->_IO_write_base;
-  if (used)
-    {
-      size_t written = _IO_sputn (target, s->_IO_write_base, used);
-      if (written == 0 || written == EOF)
-	return EOF;
-      memmove (s->_IO_write_base, s->_IO_write_base + written,
-	       used - written);
-      s->_IO_write_ptr -= written;
-    }
+	  __printf_buffer_puts
+	    (buf, _NL_CURRENT (LC_CTYPE, _NL_CTYPE_OUTDIGIT0_MB + digit));
 #endif
-  return PUTC (c, s);
+	}
+    }
 }
 
-#ifdef COMPILE_WPRINTF
-static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
-{
-  JUMP_INIT_DUMMY,
-  JUMP_INIT (finish, _IO_wdefault_finish),
-  JUMP_INIT (overflow, _IO_helper_overflow),
-  JUMP_INIT (underflow, _IO_default_underflow),
-  JUMP_INIT (uflow, _IO_default_uflow),
-  JUMP_INIT (pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
-  JUMP_INIT (xsputn, _IO_wdefault_xsputn),
-  JUMP_INIT (xsgetn, _IO_wdefault_xsgetn),
-  JUMP_INIT (seekoff, _IO_default_seekoff),
-  JUMP_INIT (seekpos, _IO_default_seekpos),
-  JUMP_INIT (setbuf, _IO_default_setbuf),
-  JUMP_INIT (sync, _IO_default_sync),
-  JUMP_INIT (doallocate, _IO_wdefault_doallocate),
-  JUMP_INIT (read, _IO_default_read),
-  JUMP_INIT (write, _IO_default_write),
-  JUMP_INIT (seek, _IO_default_seek),
-  JUMP_INIT (close, _IO_default_close),
-  JUMP_INIT (stat, _IO_default_stat)
-};
-#else
-static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
-{
-  JUMP_INIT_DUMMY,
-  JUMP_INIT (finish, _IO_default_finish),
-  JUMP_INIT (overflow, _IO_helper_overflow),
-  JUMP_INIT (underflow, _IO_default_underflow),
-  JUMP_INIT (uflow, _IO_default_uflow),
-  JUMP_INIT (pbackfail, _IO_default_pbackfail),
-  JUMP_INIT (xsputn, _IO_default_xsputn),
-  JUMP_INIT (xsgetn, _IO_default_xsgetn),
-  JUMP_INIT (seekoff, _IO_default_seekoff),
-  JUMP_INIT (seekpos, _IO_default_seekpos),
-  JUMP_INIT (setbuf, _IO_default_setbuf),
-  JUMP_INIT (sync, _IO_default_sync),
-  JUMP_INIT (doallocate, _IO_default_doallocate),
-  JUMP_INIT (read, _IO_default_read),
-  JUMP_INIT (write, _IO_default_write),
-  JUMP_INIT (seek, _IO_default_seek),
-  JUMP_INIT (close, _IO_default_close),
-  JUMP_INIT (stat, _IO_default_stat)
-};
-#endif
 
-static int
-buffered_vfprintf (FILE *s, const CHAR_T *format, va_list args,
-		   unsigned int mode_flags)
+/* The FILE-based function.  */
+int
+vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 {
-  CHAR_T buf[BUFSIZ];
-  struct helper_file helper;
-  FILE *hp = (FILE *) &helper._f;
-  int result, to_flush;
-
   /* Orient the stream.  */
 #ifdef ORIENT
   ORIENT;
 #endif
 
-  /* Initialize helper.  */
-  helper._put_stream = s;
-#ifdef COMPILE_WPRINTF
-  hp->_wide_data = &helper._wide_data;
-  _IO_wsetp (hp, buf, buf + sizeof buf / sizeof (CHAR_T));
-  hp->_mode = 1;
-#else
-  _IO_setp (hp, buf, buf + sizeof buf);
-  hp->_mode = -1;
-#endif
-  hp->_flags = _IO_MAGIC|_IO_NO_READS|_IO_USER_LOCK;
-#if _IO_JUMPS_OFFSET
-  hp->_vtable_offset = 0;
-#endif
-#ifdef _IO_MTSAFE_IO
-  hp->_lock = NULL;
+  /* Sanity check of arguments.  */
+  ARGCHECK (s, format);
+
+#ifdef ORIENT
+  /* Check for correct orientation.  */
+  if (_IO_vtable_offset (s) == 0
+      && _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)
+      != (sizeof (CHAR_T) == 1 ? -1 : 1))
+    /* The stream is already oriented otherwise.  */
+    return EOF;
 #endif
-  hp->_flags2 = s->_flags2;
-  _IO_JUMPS (&helper._f) = (struct _IO_jump_t *) &_IO_helper_jumps;
 
-  /* Now print to helper instead.  */
-  result = vfprintf (hp, format, args, mode_flags);
+  int done;
 
   /* Lock stream.  */
-  __libc_cleanup_region_start (1, (void (*) (void *)) &_IO_funlockfile, s);
+  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
   _IO_flockfile (s);
 
-  /* Now flush anything from the helper to the S. */
-#ifdef COMPILE_WPRINTF
-  if ((to_flush = (hp->_wide_data->_IO_write_ptr
-		   - hp->_wide_data->_IO_write_base)) > 0)
-    {
-      if ((int) _IO_sputn (s, hp->_wide_data->_IO_write_base, to_flush)
-	  != to_flush)
-	result = -1;
-    }
-#else
-  if ((to_flush = hp->_IO_write_ptr - hp->_IO_write_base) > 0)
-    {
-      if ((int) _IO_sputn (s, hp->_IO_write_base, to_flush) != to_flush)
-	result = -1;
-    }
-#endif
+  /* Set up the wrapping buffer.  */
+  struct Xprintf (buffer_to_file) wrap;
+  Xprintf (buffer_to_file_init) (&wrap, s);
+
+  /* Perform the printing operation on the buffer.  */
+  Xprintf_buffer (&wrap.base, format, ap, mode_flags);
+  done = Xprintf (buffer_to_file_done) (&wrap);
 
   /* Unlock the stream.  */
   _IO_funlockfile (s);
-  __libc_cleanup_region_end (0);
+  _IO_cleanup_region_end (0);
 
-  return result;
+  return done;
 }
diff --git a/stdio-common/vfprintf-process-arg.c b/stdio-common/vfprintf-process-arg.c
index 4fe369e111..ca6402449d 100644
--- a/stdio-common/vfprintf-process-arg.c
+++ b/stdio-common/vfprintf-process-arg.c
@@ -27,7 +27,7 @@
      now process the wanted format specifier.  */
 LABEL (form_percent):
   /* Write a literal "%".  */
-  outchar (L_('%'));
+  Xprintf_buffer_putc (buf, L_('%'));
   break;
 
 LABEL (form_integer):
@@ -116,16 +116,8 @@ LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
             *--string = L_('0');
         }
       else
-        {
-          /* Put the number in WORK.  */
-          string = _itoa (number.longlong, workend, base,
-                          spec == L_('X'));
-          if (group && grouping)
-            string = group_number (work_buffer, string, workend,
-                                   grouping, thousands_sep);
-          if (use_outdigits && base == 10)
-            string = _i18n_number_rewrite (string, workend, workend);
-        }
+        /* Put the number in WORK.  */
+        string = _itoa (number.longlong, workend, base, spec == L_('X'));
       /* Simplify further test for num != 0.  */
       number.word = number.longlong != 0;
     }
@@ -159,27 +151,46 @@ LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
             *--string = L_('0');
         }
       else
-        {
-          /* Put the number in WORK.  */
-          string = _itoa_word (number.word, workend, base,
-                               spec == L_('X'));
-          if (group && grouping)
-            string = group_number (work_buffer, string, workend,
-                                   grouping, thousands_sep);
-          if (use_outdigits && base == 10)
-            string = _i18n_number_rewrite (string, workend, workend);
-        }
+        /* Put the number in WORK.  */
+        string = _itoa_word (number.word, workend, base,
+                             spec == L_('X'));
     }
 
-  if (prec <= workend - string && number.word != 0 && alt && base == 8)
-    /* Add octal marker.  */
-    *--string = L_('0');
+  /* Grouping is also used for outdigits translation.  */
+  struct grouping_iterator iter;
+  bool number_slow_path = group || (use_outdigits && base == 10);
+  if (group)
+    __grouping_iterator_init (&iter, LC_NUMERIC, _NL_CURRENT_LOCALE,
+                              workend - string);
+  else if (use_outdigits && base == 10)
+    __grouping_iterator_init_none (&iter, workend - string);
+
+  int number_length;
+#ifndef COMPILE_WPRINTF
+  if (use_outdigits && base == 10)
+    number_length = __translated_number_width (_NL_CURRENT_LOCALE,
+                                               string, workend);
+  else
+    number_length = workend - string;
+  if (group)
+    number_length += iter.separators * strlen (thousands_sep);
+#else
+  number_length = workend - string;
+  /* All wide separators have length 1.  */
+  if (group && thousands_sep != L'\0')
+    number_length += iter.separators;
+#endif
+
+  /* The marker comes right before the number, but is not subject
+     to grouping.  */
+  bool octal_marker = (prec <= number_length && number.word != 0
+                       && alt && base == 8);
 
   prec = MAX (0, prec - (workend - string));
 
   if (!left)
     {
-      width -= workend - string + prec;
+      width -= number_length + prec;
 
       if (number.word != 0 && alt && (base == 16 || base == 2))
         /* Account for 0X, 0x, 0B or 0b hex or binary marker.  */
@@ -190,27 +201,34 @@ LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
 
       if (pad == L_(' '))
         {
-          PAD (L_(' '));
+          Xprintf_buffer_pad (buf, L_(' '), width);
           width = 0;
         }
 
       if (is_negative)
-        outchar (L_('-'));
+        Xprintf_buffer_putc (buf, L_('-'));
       else if (showsign)
-        outchar (L_('+'));
+        Xprintf_buffer_putc (buf, L_('+'));
       else if (space)
-        outchar (L_(' '));
+        Xprintf_buffer_putc (buf, L_(' '));
 
       if (number.word != 0 && alt && (base == 16 || base == 2))
         {
-          outchar (L_('0'));
-          outchar (spec);
+          Xprintf_buffer_putc (buf, L_('0'));
+          Xprintf_buffer_putc (buf, spec);
         }
 
       width += prec;
-      PAD (L_('0'));
+      Xprintf_buffer_pad (buf, L_('0'), width);
+
+      if (octal_marker)
+        Xprintf_buffer_putc (buf, L_('0'));
 
-      outstring (string, workend - string);
+      if (number_slow_path)
+        group_number (buf, &iter, string, workend, thousands_sep,
+                      use_outdigits && base == 10);
+      else
+        Xprintf_buffer_write (buf, string, workend - string);
 
       break;
     }
@@ -218,40 +236,41 @@ LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
     {
       if (is_negative)
         {
-          outchar (L_('-'));
+          Xprintf_buffer_putc (buf, L_('-'));
           --width;
         }
       else if (showsign)
         {
-          outchar (L_('+'));
+          Xprintf_buffer_putc (buf, L_('+'));
           --width;
         }
       else if (space)
         {
-          outchar (L_(' '));
+          Xprintf_buffer_putc (buf, L_(' '));
           --width;
         }
 
       if (number.word != 0 && alt && (base == 16 || base == 2))
         {
-          outchar (L_('0'));
-          outchar (spec);
+          Xprintf_buffer_putc (buf, L_('0'));
+          Xprintf_buffer_putc (buf, spec);
           width -= 2;
         }
 
       width -= workend - string + prec;
 
-      if (prec > 0)
-        {
-          int temp = width;
-          width = prec;
-          PAD (L_('0'));
-          width = temp;
-        }
+      Xprintf_buffer_pad (buf, L_('0'), prec);
+
+      if (octal_marker)
+        Xprintf_buffer_putc (buf, L_('0'));
 
-      outstring (string, workend - string);
+      if (number_slow_path)
+        group_number (buf, &iter, string, workend, thousands_sep,
+                      use_outdigits && base == 10);
+      else
+        Xprintf_buffer_write (buf, string, workend - string);
 
-      PAD (L_(' '));
+      Xprintf_buffer_pad (buf, L_(' '), width);
       break;
     }
 
@@ -300,16 +319,17 @@ LABEL (form_number):
     }
   /* Answer the count of characters written.  */
   void *ptrptr = process_arg_pointer ();
+  unsigned int written = Xprintf_buffer_done (buf);
   if (is_longlong)
-    *(long long int *) ptrptr = done;
+    *(long long int *) ptrptr = written;
   else if (is_long_num)
-    *(long int *) ptrptr = done;
+    *(long int *) ptrptr = written;
   else if (is_char)
-    *(char *) ptrptr = done;
+    *(char *) ptrptr = written;
   else if (!is_short)
-    *(int *) ptrptr = done;
+    *(int *) ptrptr = written;
   else
-    *(short int *) ptrptr = done;
+    *(short int *) ptrptr = written;
   break;
 
 LABEL (form_strerror):
@@ -341,14 +361,16 @@ LABEL (form_character):
     goto LABEL (form_wcharacter);
   --width;  /* Account for the character itself.  */
   if (!left)
-    PAD (L_(' '));
+    Xprintf_buffer_pad (buf, L_(' '), width);
 #ifdef COMPILE_WPRINTF
-  outchar (__btowc ((unsigned char) process_arg_int ())); /* Promoted. */
+  __wprintf_buffer_putc (buf, __btowc ((unsigned char) /* Promoted. */
+                                       process_arg_int ()));
 #else
-  outchar ((unsigned char) process_arg_int ()); /* Promoted.  */
+  __printf_buffer_putc (buf, (unsigned char) /* Promoted.  */
+                        process_arg_int ());
 #endif
   if (left)
-    PAD (L_(' '));
+    Xprintf_buffer_pad (buf, L_(' '), width);
   break;
 
 LABEL (form_string):
@@ -382,10 +404,8 @@ LABEL (form_string):
     else if (!is_long && spec != L_('S'))
       {
 #ifdef COMPILE_WPRINTF
-        done = outstring_converted_wide_string
-          (s, (const char *) string, prec, width, left, done);
-        if (done < 0)
-          goto all_done;
+        outstring_converted_wide_string (buf, (const char *) string,
+                                         prec, width, left);
         /* The padding has already been written.  */
         break;
 #else
@@ -407,10 +427,8 @@ LABEL (form_string):
         else
           len = __wcslen (string);
 #else
-        done = outstring_converted_wide_string
-          (s, (const wchar_t *) string, prec, width, left, done);
-        if (done < 0)
-          goto all_done;
+        outstring_converted_wide_string (buf, (const wchar_t *) string,
+                                         prec, width, left);
         /* The padding has already been written.  */
         break;
 #endif
@@ -418,15 +436,15 @@ LABEL (form_string):
 
     if ((width -= len) < 0)
       {
-        outstring (string, len);
+        Xprintf_buffer_write (buf, string, len);
         break;
       }
 
     if (!left)
-      PAD (L_(' '));
-    outstring (string, len);
+      Xprintf_buffer_pad (buf, L_(' '), width);
+    Xprintf_buffer_write (buf, string, len);
     if (left)
-      PAD (L_(' '));
+      Xprintf_buffer_pad (buf, L_(' '), width);
   }
   break;
 
@@ -436,10 +454,10 @@ LABEL (form_wcharacter):
     /* Wide character.  */
     --width;
     if (!left)
-      PAD (L' ');
-    outchar (process_arg_wchar_t ());
+      Xprintf_buffer_pad (buf, L_(' '), width);
+    Xprintf_buffer_putc (buf, process_arg_wchar_t ());
     if (left)
-      PAD (L' ');
+      Xprintf_buffer_pad (buf, L_(' '), width);
   }
   break;
 
@@ -447,24 +465,24 @@ LABEL (form_wcharacter):
 LABEL (form_wcharacter):
   {
     /* Wide character.  */
-    char buf[MB_LEN_MAX];
+    char wcbuf[MB_LEN_MAX];
     mbstate_t mbstate;
     size_t len;
 
     memset (&mbstate, '\0', sizeof (mbstate_t));
-    len = __wcrtomb (buf, process_arg_wchar_t (), &mbstate);
+    len = __wcrtomb (wcbuf, process_arg_wchar_t (), &mbstate);
     if (len == (size_t) -1)
       {
         /* Something went wrong during the conversion.  Bail out.  */
-        done = -1;
+        __printf_buffer_mark_failed (buf);
         goto all_done;
       }
     width -= len;
     if (!left)
-      PAD (' ');
-    outstring (buf, len);
+      Xprintf_buffer_pad (buf, L_(' '), width);
+    Xprintf_buffer_write (buf, wcbuf, len);
     if (left)
-      PAD (' ');
+      Xprintf_buffer_pad (buf, L_(' '), width);
   }
   break;
 #endif /* !COMPILE_WPRINTF */
diff --git a/stdlib/strfmon_l.c b/stdlib/strfmon_l.c
index d9b22088c7..6dc36e07cc 100644
--- a/stdlib/strfmon_l.c
+++ b/stdlib/strfmon_l.c
@@ -29,33 +29,8 @@
 #include <string.h>
 #include "../locale/localeinfo.h"
 #include <bits/floatn.h>
-
-
-#define out_char(Ch)							      \
-  do {									      \
-    if (dest >= s + maxsize - 1)					      \
-      {									      \
-	__set_errno (E2BIG);						      \
-	va_end (ap);							      \
-	return -1;							      \
-      }									      \
-    *dest++ = (Ch);							      \
-  } while (0)
-
-#define out_string(String)						      \
-  do {									      \
-    const char *_s = (String);						      \
-    while (*_s)								      \
-      out_char (*_s++);							      \
-  } while (0)
-
-#define out_nstring(String, N)						      \
-  do {									      \
-    int _n = (N);							      \
-    const char *_s = (String);						      \
-    while (_n-- > 0)							      \
-      out_char (*_s++);							      \
-  } while (0)
+#include <stdio-common/grouping_iterator.h>
+#include <printf_buffer.h>
 
 #define to_digit(Ch) ((Ch) - '0')
 
@@ -75,21 +50,15 @@
    some information in the LC_MONETARY category which should be used,
    too.  Some of the information contradicts the information which can
    be specified in format string.  */
-ssize_t
-__vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
-		       const char *format, va_list ap, unsigned int flags)
+static void
+__vstrfmon_l_buffer (struct __printf_buffer *buf, locale_t loc,
+		     const char *fmt, va_list ap, unsigned int flags)
 {
   struct __locale_data *current = loc->__locales[LC_MONETARY];
-  _IO_strfile f;
   struct printf_info info;
-  char *dest;			/* Pointer so copy the output.  */
-  const char *fmt;		/* Pointer that walks through format.  */
-
-  dest = s;
-  fmt = format;
 
   /* Loop through the format-string.  */
-  while (*fmt != '\0')
+  while (*fmt != '\0' && !__printf_buffer_has_failed (buf))
     {
       /* The floating-point value to output.  */
       union
@@ -122,11 +91,9 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
       int other_cs_precedes;
       const char *sign_string;
       const char *other_sign_string;
-      int done;
       const char *currency_symbol;
       size_t currency_symbol_len;
       long int width;
-      char *startp;
       const void *ptr;
       char space_char;
 
@@ -134,14 +101,14 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	 specification.  */
       if (*fmt != '%')
 	{
-	  out_char (*fmt++);
+	  __printf_buffer_putc (buf, *fmt++);
 	  continue;
 	}
 
       /* "%%" means a single '%' character.  */
       if (fmt[1] == '%')
 	{
-	  out_char (*++fmt);
+	  __printf_buffer_putc (buf, *++fmt);
 	  ++fmt;
 	  continue;
 	}
@@ -171,7 +138,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 		{
 		  /* Premature EOS.  */
 		  __set_errno (EINVAL);
-		  return -1;
+		  __printf_buffer_mark_failed (buf);
+		  return;
 		}
 	      continue;
 	    case '^':			/* Don't group digits.  */
@@ -181,7 +149,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	      if (n_sign_posn != -2)
 		{
 		  __set_errno (EINVAL);
-		  return -1;
+		  __printf_buffer_mark_failed (buf);
+		  return;
 		}
 	      p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
 	      n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
@@ -190,7 +159,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	      if (n_sign_posn != -2)
 		{
 		  __set_errno (EINVAL);
-		  return -1;
+		  __printf_buffer_mark_failed (buf);
+		  return;
 		}
 	      p_sign_posn = 0;
 	      n_sign_posn = 0;
@@ -220,19 +190,12 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 		  || (width == LONG_MAX && val > LONG_MAX % 10))
 		{
 		  __set_errno (E2BIG);
-		  return -1;
+		  __printf_buffer_mark_failed (buf);
+		  return;
 		}
 
 	      width = width * 10 + val;
 	    }
-
-	  /* If we don't have enough room for the demanded width we
-	     can stop now and return an error.  */
-	  if (width >= maxsize - (dest - s))
-	    {
-	      __set_errno (E2BIG);
-	      return -1;
-	    }
 	}
 
       /* Recognize left precision.  */
@@ -241,7 +204,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	  if (!isdigit (*++fmt))
 	    {
 	      __set_errno (EINVAL);
-	      return -1;
+	      __printf_buffer_mark_failed (buf);
+	      return;
 	    }
 	  left_prec = to_digit (*fmt);
 
@@ -258,7 +222,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	  if (!isdigit (*++fmt))
 	    {
 	      __set_errno (EINVAL);
-	      return -1;
+	      __printf_buffer_mark_failed (buf);
+	      return;
 	    }
 	  right_prec = to_digit (*fmt);
 
@@ -306,7 +271,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	  break;
 	default:		/* Any unrecognized format is an error.  */
 	  __set_errno (EINVAL);
-	  return -1;
+	  __printf_buffer_mark_failed (buf);
+	  return;
 	}
 
       /* If not specified by the format string now find the values for
@@ -327,8 +293,11 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
       /* If we have to print the digits grouped determine how many
 	 extra characters this means.  */
       if (group && left_prec != -1)
-	left_prec += __guess_grouping (left_prec,
-				       _NL_CURRENT (LC_MONETARY, MON_GROUPING));
+	{
+	  struct grouping_iterator it;
+	  __grouping_iterator_init (&it, LC_MONETARY, loc, left_prec);
+	  left_prec += it.separators;
+	}
 
       /* Now it's time to get the value.  */
       if (is_long_double == 1)
@@ -482,57 +451,46 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 #define left_paren '('
 #define right_paren ')'
 
-      startp = dest;		/* Remember start so we can compute length.  */
+      char *startp = buf->write_ptr;
 
-      while (left_pad-- > 0)
-	out_char (' ');
+      __printf_buffer_pad (buf, ' ', left_pad);
 
       if (sign_posn == 0 && is_negative)
-	out_char (left_paren);
+	__printf_buffer_putc (buf, left_paren);
 
       if (cs_precedes)
 	{
 	  if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
 	      && sign_posn != 5)
 	    {
-	      out_string (sign_string);
+	      __printf_buffer_puts (buf, sign_string);
 	      if (sep_by_space == 2)
-		out_char (' ');
+		__printf_buffer_putc (buf, ' ');
 	    }
 
 	  if (print_curr_symbol)
-	    out_string (currency_symbol);
+	    __printf_buffer_puts (buf, currency_symbol);
 
 	  if (sign_posn == 4)
 	    {
 	      if (print_curr_symbol && sep_by_space == 2)
-		out_char (space_char);
-	      out_string (sign_string);
+		__printf_buffer_putc (buf, space_char);
+	      __printf_buffer_puts (buf, sign_string);
 	      if (sep_by_space == 1)
 		/* POSIX.2 and SUS are not clear on this case, but C99
 		   says a space follows the adjacent-symbol-and-sign */
-		out_char (' ');
+		__printf_buffer_putc (buf, ' ');
 	    }
 	  else
 	    if (print_curr_symbol && sep_by_space == 1)
-	      out_char (space_char);
+	      __printf_buffer_putc (buf, space_char);
 	}
       else
 	if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
 	    && sign_posn != 4 && sign_posn != 5)
-	  out_string (sign_string);
+	  __printf_buffer_puts (buf, sign_string);
 
       /* Print the number.  */
-#ifdef _IO_MTSAFE_IO
-      f._sbf._f._lock = NULL;
-#endif
-      _IO_init_internal (&f._sbf._f, 0);
-      _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
-      _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
-      /* We clear the last available byte so we can find out whether
-	 the numeric representation is too long.  */
-      s[maxsize - 1] = '\0';
-
       memset (&info, '\0', sizeof (info));
       info.prec = right_prec;
       info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
@@ -544,25 +502,17 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
       info.extra = 1;		/* This means use values from LC_MONETARY.  */
 
       ptr = &fpnum;
-      done = __printf_fp_l (&f._sbf._f, loc, &info, &ptr);
-      if (done < 0)
-	return -1;
-
-      if (s[maxsize - 1] != '\0')
-	{
-	  __set_errno (E2BIG);
-	  return -1;
-	}
-
-      dest += done;
+      __printf_fp_l_buffer (buf, loc, &info, &ptr);
+      if (__printf_buffer_has_failed (buf))
+	return;
 
       if (!cs_precedes)
 	{
 	  if (sign_posn == 3)
 	    {
 	      if (sep_by_space == 1)
-		out_char (' ');
-	      out_string (sign_string);
+		__printf_buffer_putc (buf, ' ');
+	      __printf_buffer_puts (buf, sign_string);
 	    }
 
 	  if (print_curr_symbol)
@@ -572,55 +522,61 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 		  || (sign_posn == 2 && sep_by_space == 1)
 		  || (sign_posn == 1 && sep_by_space == 1)
 		  || (sign_posn == 0 && sep_by_space == 1))
-		out_char (space_char);
-	      out_nstring (currency_symbol, currency_symbol_len);
+		__printf_buffer_putc (buf, space_char);
+	      __printf_buffer_write (buf, currency_symbol,
+				       __strnlen (currency_symbol,
+						  currency_symbol_len));
 	    }
 
 	  if (sign_posn == 4)
 	    {
 	      if (sep_by_space == 2)
-		out_char (' ');
-	      out_string (sign_string);
+		__printf_buffer_putc (buf, ' ');
+	      __printf_buffer_puts (buf, sign_string);
 	    }
 	}
 
       if (sign_posn == 2)
 	{
 	  if (sep_by_space == 2)
-	    out_char (' ');
-	  out_string (sign_string);
+	    __printf_buffer_putc (buf, ' ');
+	  __printf_buffer_puts (buf, sign_string);
 	}
 
       if (sign_posn == 0 && is_negative)
-	out_char (right_paren);
+	__printf_buffer_putc (buf, right_paren);
 
       /* Now test whether the output width is filled.  */
-      if (dest - startp < width)
+      if (buf->write_ptr - startp < width)
 	{
-	  if (left)
-	    /* We simply have to fill using spaces.  */
-	    do
-	      out_char (' ');
-	    while (dest - startp < width);
-	  else
+	  size_t pad_width = width - (buf->write_ptr - startp);
+	  __printf_buffer_pad (buf, ' ', pad_width);
+	  if (__printf_buffer_has_failed (buf))
+	    /* Implies length check.  */
+	    return;
+	  /* Left padding is already in the correct position.
+	     Otherwise move the field contents in place.  */
+	  if (!left)
 	    {
-	      long int dist = width - (dest - startp);
-	      for (char *cp = dest - 1; cp >= startp; --cp)
-		cp[dist] = cp[0];
-
-	      dest += dist;
-
-	      do
-		startp[--dist] = ' ';
-	      while (dist > 0);
+	      memmove (startp + pad_width, startp, buf->write_ptr - startp);
+	      memset (startp, ' ', pad_width);
 	    }
 	}
     }
+}
 
-  /* Terminate the string.  */
-  *dest = '\0';
-
-  return dest - s;
+ssize_t
+__vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
+		       const char *format, va_list ap, unsigned int flags)
+{
+  struct __printf_buffer buf;
+  __printf_buffer_init (&buf, s, maxsize, __printf_buffer_mode_strfmon);
+  __vstrfmon_l_buffer (&buf, loc, format, ap, flags);
+  __printf_buffer_putc (&buf, '\0'); /* Terminate the string.  */
+  if (__printf_buffer_has_failed (&buf))
+    return -1;
+  else
+    return buf.write_ptr - buf.write_base - 1; /* Exclude NUL byte.  */
 }
 
 ssize_t
diff --git a/stdlib/strfrom-skeleton.c b/stdlib/strfrom-skeleton.c
index 1fba04bf6a..2ef3025c57 100644
--- a/stdlib/strfrom-skeleton.c
+++ b/stdlib/strfrom-skeleton.c
@@ -27,6 +27,7 @@
 #include <printf.h>
 #include <string.h>
 #include <locale/localeinfo.h>
+#include <printf_buffer.h>
 
 #define UCHAR_T char
 #define L_(Str) Str
@@ -36,12 +37,7 @@
 int
 STRFROM (char *dest, size_t size, const char *format, FLOAT f)
 {
-  _IO_strnfile sfile;
-#ifdef _IO_MTSAFE_IO
-  sfile.f._sbf._f._lock = NULL;
-#endif
-
-  int done;
+  struct __printf_buffer_snprintf buf;
 
   /* Single-precision values need to be stored in a double type, because
      __printf_fp_l and __printf_fphex do not accept the float type.  */
@@ -105,23 +101,8 @@ STRFROM (char *dest, size_t size, const char *format, FLOAT f)
       abort ();
     }
 
-  /* The following code to prepare the virtual file has been adapted from the
-     function __vsnprintf_internal from libio.  */
-
-  if (size == 0)
-    {
-    /* When size is zero, nothing is written and dest may be a null pointer.
-       This is specified for snprintf in ISO/IEC 9899:2011, Section 7.21.6.5,
-       in the second paragraph.  Thus, if size is zero, prepare to use the
-       overflow buffer right from the start.  */
-      dest = sfile.overflow_buf;
-      size = sizeof (sfile.overflow_buf);
-    }
-
-  /* Prepare the virtual string file.  */
-  _IO_no_init (&sfile.f._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
-  _IO_JUMPS (&sfile.f._sbf) = &_IO_strn_jumps;
-  _IO_str_init_static_internal (&sfile.f, dest, size - 1, dest);
+  /* Prepare the string buffer.  */
+  __printf_buffer_snprintf_init (&buf, dest, size);
 
   /* Prepare the format specification for printf_fp.  */
   memset (&info, '\0', sizeof (info));
@@ -143,13 +124,8 @@ STRFROM (char *dest, size_t size, const char *format, FLOAT f)
   info.spec = specifier;
 
   if (info.spec != 'a' && info.spec != 'A')
-    done = __printf_fp_l (&sfile.f._sbf._f, _NL_CURRENT_LOCALE, &info, &fpptr);
+    __printf_fp_l_buffer (&buf.base, _NL_CURRENT_LOCALE, &info, &fpptr);
   else
-    done = __printf_fphex (&sfile.f._sbf._f, &info, &fpptr);
-
-  /* Terminate the string.  */
-  if (sfile.f._sbf._f._IO_buf_base != sfile.overflow_buf)
-    *sfile.f._sbf._f._IO_write_ptr = '\0';
-
-  return done;
+    __printf_fphex_l_buffer (&buf.base, _NL_CURRENT_LOCALE, &info, &fpptr);
+  return __printf_buffer_snprintf_done (&buf);
 }
diff --git a/sysdeps/ia64/fpu/printf_fphex.c b/sysdeps/ia64/fpu/printf_fphex.c
index e7c3109510..91347b7e3d 100644
--- a/sysdeps/ia64/fpu/printf_fphex.c
+++ b/sysdeps/ia64/fpu/printf_fphex.c
@@ -35,16 +35,10 @@ do {									      \
 									      \
       numstr = _itoa_word (num, numbuf + sizeof numbuf, 16,		      \
 			   info->spec == 'A');				      \
-      wnumstr = _itowa_word (num,					      \
-			     wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),   \
-			     16, info->spec == 'A');			      \
 									      \
       /* Fill with zeroes.  */						      \
       while (numstr > numbuf + (sizeof numbuf - 64 / 4))		      \
-	{								      \
-	  *--numstr = '0';						      \
-	  *--wnumstr = L'0';						      \
-	}								      \
+	*--numstr = '0';						      \
 									      \
       /* We use a full nibble for the leading digit.  */		      \
       leading = *numstr++;						      \
diff --git a/sysdeps/ieee754/ldbl-128/printf_fphex_macros.h b/sysdeps/ieee754/ldbl-128/printf_fphex_macros.h
index 28dccb1170..bc712a2527 100644
--- a/sysdeps/ieee754/ldbl-128/printf_fphex_macros.h
+++ b/sysdeps/ieee754/ldbl-128/printf_fphex_macros.h
@@ -36,45 +36,23 @@ do {									      \
       zero_mantissa = (num0|num1) == 0;					      \
 									      \
       if (sizeof (unsigned long int) > 6)				      \
-	{								      \
-	  numstr = _itoa_word (num1, numbuf + sizeof numbuf, 16,	      \
-			       info->spec == 'A');			      \
-	  wnumstr = _itowa_word (num1,					      \
-				 wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),\
-				 16, info->spec == 'A');		      \
-	}								      \
+	numstr = _itoa_word (num1, numbuf + sizeof numbuf, 16,		      \
+			     info->spec == 'A');			      \
       else								      \
-	{								      \
-	  numstr = _itoa (num1, numbuf + sizeof numbuf, 16,		      \
-			  info->spec == 'A');				      \
-	  wnumstr = _itowa (num1,					      \
-			    wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),    \
-			    16, info->spec == 'A');			      \
-	}								      \
+	numstr = _itoa (num1, numbuf + sizeof numbuf, 16,		      \
+			info->spec == 'A');				      \
 									      \
       while (numstr > numbuf + (sizeof numbuf - 64 / 4))		      \
-	{								      \
-	  *--numstr = '0';						      \
-	  *--wnumstr = L'0';						      \
-	}								      \
+	*--numstr = '0';						      \
 									      \
       if (sizeof (unsigned long int) > 6)				      \
-	{								      \
-	  numstr = _itoa_word (num0, numstr, 16, info->spec == 'A');	      \
-	  wnumstr = _itowa_word (num0, wnumstr, 16, info->spec == 'A');	      \
-	}								      \
+	numstr = _itoa_word (num0, numstr, 16, info->spec == 'A');	      \
       else								      \
-	{								      \
-	  numstr = _itoa (num0, numstr, 16, info->spec == 'A');		      \
-	  wnumstr = _itowa (num0, wnumstr, 16, info->spec == 'A');	      \
-	}								      \
+	numstr = _itoa (num0, numstr, 16, info->spec == 'A');		      \
 									      \
       /* Fill with zeroes.  */						      \
       while (numstr > numbuf + (sizeof numbuf - 112 / 4))		      \
-	{								      \
 	  *--numstr = '0';						      \
-	  *--wnumstr = L'0';						      \
-	}								      \
 									      \
       leading = u.ieee.exponent == 0 ? '0' : '1';			      \
 									      \
diff --git a/sysdeps/ieee754/ldbl-128ibm/printf_fphex.c b/sysdeps/ieee754/ldbl-128ibm/printf_fphex.c
index 58733f85e5..a06cbe7cb6 100644
--- a/sysdeps/ieee754/ldbl-128ibm/printf_fphex.c
+++ b/sysdeps/ieee754/ldbl-128ibm/printf_fphex.c
@@ -68,45 +68,23 @@ do {									      \
       zero_mantissa = (num0|num1) == 0;					      \
 									      \
       if (sizeof (unsigned long int) > 6)				      \
-	{								      \
-	  numstr = _itoa_word (num1, numbuf + sizeof numbuf, 16,	      \
-			       info->spec == 'A');			      \
-	  wnumstr = _itowa_word (num1,					      \
-				 wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),\
-				 16, info->spec == 'A');		      \
-	}								      \
+	numstr = _itoa_word (num1, numbuf + sizeof numbuf, 16,		      \
+			     info->spec == 'A');			      \
       else								      \
-	{								      \
-	  numstr = _itoa (num1, numbuf + sizeof numbuf, 16,		      \
+	numstr = _itoa (num1, numbuf + sizeof numbuf, 16,		      \
 			  info->spec == 'A');				      \
-	  wnumstr = _itowa (num1,					      \
-			    wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),    \
-			    16, info->spec == 'A');			      \
-	}								      \
 									      \
       while (numstr > numbuf + (sizeof numbuf - 64 / 4))		      \
-	{								      \
-	  *--numstr = '0';						      \
-	  *--wnumstr = L'0';						      \
-	}								      \
+	*--numstr = '0';						      \
 									      \
       if (sizeof (unsigned long int) > 6)				      \
-	{								      \
-	  numstr = _itoa_word (num0, numstr, 16, info->spec == 'A');	      \
-	  wnumstr = _itowa_word (num0, wnumstr, 16, info->spec == 'A');	      \
-	}								      \
+	numstr = _itoa_word (num0, numstr, 16, info->spec == 'A');	      \
       else								      \
-	{								      \
-	  numstr = _itoa (num0, numstr, 16, info->spec == 'A');		      \
-	  wnumstr = _itowa (num0, wnumstr, 16, info->spec == 'A');	      \
-	}								      \
+	numstr = _itoa (num0, numstr, 16, info->spec == 'A');		      \
 									      \
       /* Fill with zeroes.  */						      \
       while (numstr > numbuf + (sizeof numbuf - 112 / 4))		      \
-	{								      \
-	  *--numstr = '0';						      \
-	  *--wnumstr = L'0';						      \
-	}								      \
+	*--numstr = '0';						      \
 									      \
       leading = u.d[0].ieee.exponent == 0 ? '0' : '1';			      \
 									      \
diff --git a/sysdeps/ieee754/ldbl-96/printf_fphex.c b/sysdeps/ieee754/ldbl-96/printf_fphex.c
index 26bc60682e..1f09b5f08b 100644
--- a/sysdeps/ieee754/ldbl-96/printf_fphex.c
+++ b/sysdeps/ieee754/ldbl-96/printf_fphex.c
@@ -36,31 +36,17 @@ do {									      \
       zero_mantissa = num == 0;						      \
 									      \
       if (sizeof (unsigned long int) > 6)				      \
-	{								      \
-	  numstr = _itoa_word (num, numbuf + sizeof numbuf, 16,		      \
-			       info->spec == 'A');			      \
-	  wnumstr = _itowa_word (num,					      \
-				 wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),\
-				 16, info->spec == 'A');		      \
-	}								      \
+	numstr = _itoa_word (num, numbuf + sizeof numbuf, 16,		      \
+			     info->spec == 'A');			      \
       else								      \
-	{								      \
-	  numstr = _itoa (num, numbuf + sizeof numbuf, 16, info->spec == 'A');\
-	  wnumstr = _itowa (num,					      \
-			    wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),    \
-			    16, info->spec == 'A');			      \
-	}								      \
+	numstr = _itoa (num, numbuf + sizeof numbuf, 16, info->spec == 'A');  \
 									      \
       /* Fill with zeroes.  */						      \
       while (numstr > numbuf + (sizeof numbuf - 64 / 4))		      \
-	{								      \
-	  *--numstr = '0';						      \
-	  *--wnumstr = L'0';						      \
-	}								      \
+	*--numstr = '0';						      \
 									      \
       /* We use a full nibble for the leading digit.  */		      \
       leading = *numstr++;						      \
-      wnumstr++;							      \
 									      \
       /* We have 3 bits from the mantissa in the leading nibble.	      \
 	 Therefore we are here using `IEEE854_LONG_DOUBLE_BIAS + 3'.  */      \
diff --git a/sysdeps/x86_64/fpu/printf_fphex.c b/sysdeps/x86_64/fpu/printf_fphex.c
index d2de75d4ac..bb702ec7c5 100644
--- a/sysdeps/x86_64/fpu/printf_fphex.c
+++ b/sysdeps/x86_64/fpu/printf_fphex.c
@@ -34,31 +34,16 @@ do {									      \
       zero_mantissa = num == 0;						      \
 									      \
       if (sizeof (unsigned long int) > 6)				      \
-	{								      \
-	  numstr = _itoa_word (num, numbuf + sizeof numbuf, 16,		      \
-			       info->spec == 'A');			      \
-	  wnumstr = _itowa_word (num,					      \
-				 wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),\
-				 16, info->spec == 'A');		      \
-	}								      \
+	numstr = _itoa_word (num, numbuf + sizeof numbuf, 16, info->spec == 'A');\
       else								      \
-	{								      \
-	  numstr = _itoa (num, numbuf + sizeof numbuf, 16, info->spec == 'A');\
-	  wnumstr = _itowa (num,					      \
-			    wnumbuf + sizeof (wnumbuf) / sizeof (wchar_t),    \
-			    16, info->spec == 'A');			      \
-	}								      \
+	numstr = _itoa (num, numbuf + sizeof numbuf, 16, info->spec == 'A');  \
 									      \
       /* Fill with zeroes.  */						      \
       while (numstr > numbuf + (sizeof numbuf - 64 / 4))		      \
-	{								      \
-	  *--numstr = '0';						      \
-	  *--wnumstr = L'0';						      \
-	}								      \
+	*--numstr = '0';						      \
 									      \
       /* We use a full nibble for the leading digit.  */		      \
       leading = *numstr++;						      \
-      wnumstr++;							      \
 									      \
       /* We have 3 bits from the mantissa in the leading nibble.	      \
 	 Therefore we are here using `IEEE854_LONG_DOUBLE_BIAS + 3'.  */      \
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 21/26] stdio-common: Add lock optimization to vfprintf and vfwprintf
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (19 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 20/26] stdio-common: Convert vfprintf and related functions to buffers Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:31 ` [PATCH 22/26] libio: Convert __vsprintf_internal to buffers Florian Weimer
                   ` (4 subsequent siblings)
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

After the rewrite and the implicit unbuffered streams handling, this
is very straightforward to add.
---
 stdio-common/vfprintf-internal.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
index 83a6aea510..23ada8d0ff 100644
--- a/stdio-common/vfprintf-internal.c
+++ b/stdio-common/vfprintf-internal.c
@@ -1452,6 +1452,14 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
     return EOF;
 #endif
 
+  if (!_IO_need_lock (s))
+    {
+      struct Xprintf (buffer_to_file) wrap;
+      Xprintf (buffer_to_file_init) (&wrap, s);
+      Xprintf_buffer (&wrap.base, format, ap, mode_flags);
+      return Xprintf (buffer_to_file_done) (&wrap);
+    }
+
   int done;
 
   /* Lock stream.  */
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 22/26] libio: Convert __vsprintf_internal to buffers
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (20 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 21/26] stdio-common: Add lock optimization to vfprintf and vfwprintf Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:31 ` [PATCH 23/26] libio: Convert __vasprintf_internal " Florian Weimer
                   ` (3 subsequent siblings)
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

---
 include/printf_buffer.h            |  2 +
 libio/iovsprintf.c                 | 70 ++++++++----------------------
 stdio-common/printf_buffer_flush.c |  4 ++
 3 files changed, 24 insertions(+), 52 deletions(-)

diff --git a/include/printf_buffer.h b/include/printf_buffer.h
index 95d1b06489..c938cb6085 100644
--- a/include/printf_buffer.h
+++ b/include/printf_buffer.h
@@ -44,7 +44,9 @@
 enum __printf_buffer_mode
   {
     __printf_buffer_mode_failed,
+    __printf_buffer_mode_sprintf,
     __printf_buffer_mode_snprintf,
+    __printf_buffer_mode_sprintf_chk,
     __printf_buffer_mode_to_file,
     __printf_buffer_mode_strfmon,
     __printf_buffer_mode_fp,         /* For __printf_fp_l_buffer.  */
diff --git a/libio/iovsprintf.c b/libio/iovsprintf.c
index 72c67bf27b..ba4e180a35 100644
--- a/libio/iovsprintf.c
+++ b/libio/iovsprintf.c
@@ -25,58 +25,18 @@
    in files containing the exception.  */
 
 #include "libioP.h"
-#include "strfile.h"
 
-static int __THROW
-_IO_str_chk_overflow (FILE *fp, int c)
-{
-  /* If we get here, the user-supplied buffer would be overrun by
-     further output.  */
-  __chk_fail ();
-}
-
-static const struct _IO_jump_t _IO_str_chk_jumps libio_vtable =
-{
-  JUMP_INIT_DUMMY,
-  JUMP_INIT(finish, _IO_str_finish),
-  JUMP_INIT(overflow, _IO_str_chk_overflow),
-  JUMP_INIT(underflow, _IO_str_underflow),
-  JUMP_INIT(uflow, _IO_default_uflow),
-  JUMP_INIT(pbackfail, _IO_str_pbackfail),
-  JUMP_INIT(xsputn, _IO_default_xsputn),
-  JUMP_INIT(xsgetn, _IO_default_xsgetn),
-  JUMP_INIT(seekoff, _IO_str_seekoff),
-  JUMP_INIT(seekpos, _IO_default_seekpos),
-  JUMP_INIT(setbuf, _IO_default_setbuf),
-  JUMP_INIT(sync, _IO_default_sync),
-  JUMP_INIT(doallocate, _IO_default_doallocate),
-  JUMP_INIT(read, _IO_default_read),
-  JUMP_INIT(write, _IO_default_write),
-  JUMP_INIT(seek, _IO_default_seek),
-  JUMP_INIT(close, _IO_default_close),
-  JUMP_INIT(stat, _IO_default_stat),
-  JUMP_INIT(showmanyc, _IO_default_showmanyc),
-  JUMP_INIT(imbue, _IO_default_imbue)
-};
-
-/* This function is called by regular vsprintf with maxlen set to -1,
-   and by vsprintf_chk with maxlen set to the size of the output
-   string.  In the former case, _IO_str_chk_overflow will never be
-   called; in the latter case it will crash the program if the buffer
-   overflows.  */
+#include <printf.h>
+#include <stdint.h>
+#include <printf_buffer.h>
 
 int
 __vsprintf_internal (char *string, size_t maxlen,
 		     const char *format, va_list args,
 		     unsigned int mode_flags)
 {
-  _IO_strfile sf;
-  int ret;
+  struct __printf_buffer buf;
 
-#ifdef _IO_MTSAFE_IO
-  sf._sbf._f._lock = NULL;
-#endif
-  _IO_no_init (&sf._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
   /* When called from fortified sprintf/vsprintf, erase the destination
      buffer and try to detect overflows.  When called from regular
      sprintf/vsprintf, do not erase the destination buffer, because
@@ -84,19 +44,25 @@ __vsprintf_internal (char *string, size_t maxlen,
      by ISO C), nor try to detect overflows.  */
   if ((mode_flags & PRINTF_CHK) != 0)
     {
-      _IO_JUMPS (&sf._sbf) = &_IO_str_chk_jumps;
       string[0] = '\0';
+      __printf_buffer_init (&buf, string, maxlen,
+			    __printf_buffer_mode_sprintf);
     }
   else
-    _IO_JUMPS (&sf._sbf) = &_IO_str_jumps;
-  _IO_str_init_static_internal (&sf, string,
-				(maxlen == -1) ? -1 : maxlen - 1,
-				string);
+    {
+      __printf_buffer_init (&buf, string, 0, __printf_buffer_mode_sprintf_chk);
+      buf.write_end = (char *) ~(uintptr_t) 0; /* End of address space.  */
+    }
+
+  __printf_buffer (&buf, format, args, mode_flags);
 
-  ret = __vfprintf_internal (&sf._sbf._f, format, args, mode_flags);
+  /* Write the NUL terminator if there is room.  Do not use the putc
+     operation to avoid overflowing the character write count.  */
+  if ((mode_flags & PRINTF_CHK) != 0 && buf.write_ptr == buf.write_end)
+    __chk_fail ();
+  *buf.write_ptr = '\0';
 
-  *sf._sbf._f._IO_write_ptr = '\0';
-  return ret;
+  return __printf_buffer_done (&buf);
 }
 
 int
diff --git a/stdio-common/printf_buffer_flush.c b/stdio-common/printf_buffer_flush.c
index bfd1f9d733..f44c8b887f 100644
--- a/stdio-common/printf_buffer_flush.c
+++ b/stdio-common/printf_buffer_flush.c
@@ -38,10 +38,14 @@ __printf_buffer_do_flush (struct __printf_buffer *buf)
   switch (buf->mode)
     {
     case __printf_buffer_mode_failed:
+    case __printf_buffer_mode_sprintf:
       return;
     case __printf_buffer_mode_snprintf:
       __printf_buffer_flush_snprintf ((struct __printf_buffer_snprintf *) buf);
       return;
+    case __printf_buffer_mode_sprintf_chk:
+      __chk_fail ();
+      break;
     case __printf_buffer_mode_to_file:
       __printf_buffer_flush_to_file ((struct __printf_buffer_to_file *) buf);
       return;
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 23/26] libio: Convert __vasprintf_internal to buffers
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (21 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 22/26] libio: Convert __vsprintf_internal to buffers Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:31 ` [PATCH 24/26] libio: Convert __vdprintf_internal " Florian Weimer
                   ` (2 subsequent siblings)
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

The buffer resizing algorithm is slightly different.  The initial
buffer is on the stack, and small buffers are directly allocated
on the heap using the exact required size.  The overhead of the
additional copy is compensated by the lowered setup cost for buffers
compared to libio streams.
---
 include/printf_buffer.h            |   4 +
 libio/vasprintf.c                  | 141 ++++++++++++++++++++---------
 stdio-common/printf_buffer_flush.c |   4 +
 3 files changed, 104 insertions(+), 45 deletions(-)

diff --git a/include/printf_buffer.h b/include/printf_buffer.h
index c938cb6085..0f3543d9b9 100644
--- a/include/printf_buffer.h
+++ b/include/printf_buffer.h
@@ -48,6 +48,7 @@ enum __printf_buffer_mode
     __printf_buffer_mode_snprintf,
     __printf_buffer_mode_sprintf_chk,
     __printf_buffer_mode_to_file,
+    __printf_buffer_mode_asprintf,
     __printf_buffer_mode_strfmon,
     __printf_buffer_mode_fp,         /* For __printf_fp_l_buffer.  */
     __printf_buffer_mode_fp_to_wide, /* For __wprintf_fp_l_buffer.  */
@@ -301,6 +302,9 @@ void __printf_buffer_flush_snprintf (struct __printf_buffer_snprintf *)
 struct __printf_buffer_to_file;
 void __printf_buffer_flush_to_file (struct __printf_buffer_to_file *)
   attribute_hidden;
+struct __printf_buffer_asprintf;
+void __printf_buffer_flush_asprintf (struct __printf_buffer_asprintf *)
+  attribute_hidden;
 struct __printf_buffer_fp;
 void __printf_buffer_flush_fp (struct __printf_buffer_fp *)
   attribute_hidden;
diff --git a/libio/vasprintf.c b/libio/vasprintf.c
index 4430a266c6..f4ff7ec98b 100644
--- a/libio/vasprintf.c
+++ b/libio/vasprintf.c
@@ -24,64 +24,115 @@
    This exception applies to code released by its copyright holders
    in files containing the exception.  */
 
-#include <string.h>
+#include <array_length.h>
+#include <errno.h>
+#include <limits.h>
+#include <math_ldbl_opt.h>
+#include <printf.h>
+#include <stdio.h>
 #include <stdlib.h>
-#include <strfile.h>
+#include <string.h>
+#include <printf_buffer.h>
+
+struct __printf_buffer_asprintf
+{
+  /* base.write_base points either to a heap-allocated buffer, or to
+     the direct array below.  */
+  struct __printf_buffer base;
+
+  /* Initial allocation.  200 should be large enough to copy almost
+     all asprintf usages with just a single (final, correctly sized)
+     heap allocation.  */
+  char direct[200];
+};
+
+void
+__printf_buffer_flush_asprintf (struct __printf_buffer_asprintf *buf)
+{
+  size_t current_pos = buf->base.write_ptr - buf->base.write_base;
+  if (current_pos >= INT_MAX)
+    {
+      /* The result is not representable.  No need to continue.  */
+      __set_errno (EOVERFLOW);
+      __printf_buffer_mark_failed (&buf->base);
+      return;
+    }
+
+  size_t current_size = buf->base.write_end - buf->base.write_base;
+  /* Implement an exponentiatial sizing policy.  Keep the size
+     congruent 8 (mod 16), to account for the footer in glibc
+     malloc.  */
+  size_t new_size = ((current_size + current_size / 2) & -15) | 8;
+  char *new_buffer;
+  if (buf->base.write_base == buf->direct)
+    {
+      new_buffer = malloc (new_size);
+      if (new_buffer == NULL)
+	{
+	  __printf_buffer_mark_failed (&buf->base);
+	  return;
+	}
+      memcpy (new_buffer, buf->direct, current_pos);
+    }
+  else
+    {
+      new_buffer = realloc (buf->base.write_base, new_size);
+      if (new_buffer == NULL)
+	{
+	  __printf_buffer_mark_failed (&buf->base);
+	  return;
+	}
+    }
+
+  /* Set up the new write area.  */
+  buf->base.write_base = new_buffer;
+  buf->base.write_ptr = new_buffer + current_pos;
+  buf->base.write_end = new_buffer + new_size;
+}
+
 
 int
 __vasprintf_internal (char **result_ptr, const char *format, va_list args,
 		      unsigned int mode_flags)
 {
-  /* Initial size of the buffer to be used.  Will be doubled each time an
-     overflow occurs.  */
-  const size_t init_string_size = 100;
-  char *string;
-  _IO_strfile sf;
-  int ret;
-  size_t needed;
-  size_t allocated;
-  /* No need to clear the memory here (unlike for open_memstream) since
-     we know we will never seek on the stream.  */
-  string = (char *) malloc (init_string_size);
-  if (string == NULL)
-    return -1;
-#ifdef _IO_MTSAFE_IO
-  sf._sbf._f._lock = NULL;
-#endif
-  _IO_no_init (&sf._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
-  _IO_JUMPS (&sf._sbf) = &_IO_str_jumps;
-  _IO_str_init_static_internal (&sf, string, init_string_size, string);
-  sf._sbf._f._flags &= ~_IO_USER_BUF;
-  sf._s._allocate_buffer_unused = (_IO_alloc_type) malloc;
-  sf._s._free_buffer_unused = (_IO_free_type) free;
-  ret = __vfprintf_internal (&sf._sbf._f, format, args, mode_flags);
-  if (ret < 0)
+  struct __printf_buffer_asprintf buf;
+  __printf_buffer_init (&buf.base, buf.direct, array_length (buf.direct),
+			__printf_buffer_mode_asprintf);
+
+  __printf_buffer (&buf.base, format, args, mode_flags);
+  int done = __printf_buffer_done (&buf.base);
+  if (done < 0)
     {
-      free (sf._sbf._f._IO_buf_base);
-      return ret;
+      if (buf.base.write_base != buf.direct)
+	free (buf.base.write_base);
+      return done;
+    }
+
+  /* Transfer to the final buffer.  */
+  char *result;
+  size_t size = buf.base.write_ptr - buf.base.write_base;
+  if (buf.base.write_base == buf.direct)
+    {
+      result = malloc (size + 1);
+      if (result == NULL)
+	return -1;
+      memcpy (result, buf.direct, size);
     }
-  /* Only use realloc if the size we need is of the same (binary)
-     order of magnitude then the memory we allocated.  */
-  needed = sf._sbf._f._IO_write_ptr - sf._sbf._f._IO_write_base + 1;
-  allocated = sf._sbf._f._IO_write_end - sf._sbf._f._IO_write_base;
-  if ((allocated >> 1) <= needed)
-    *result_ptr = (char *) realloc (sf._sbf._f._IO_buf_base, needed);
   else
     {
-      *result_ptr = (char *) malloc (needed);
-      if (*result_ptr != NULL)
+      result = realloc (buf.base.write_base, size + 1);
+      if (result == NULL)
 	{
-	  memcpy (*result_ptr, sf._sbf._f._IO_buf_base, needed - 1);
-	  free (sf._sbf._f._IO_buf_base);
+	  free (buf.base.write_base);
+	  return -1;
 	}
-      else
-	/* We have no choice, use the buffer we already have.  */
-	*result_ptr = (char *) realloc (sf._sbf._f._IO_buf_base, needed);
     }
-  if (*result_ptr == NULL)
-    *result_ptr = sf._sbf._f._IO_buf_base;
-  (*result_ptr)[needed - 1] = '\0';
-  return ret;
+
+  /* Add NUL termination.  */
+  result[size] = '\0';
+  *result_ptr = result;
+
+  return done;
 }
 
 int
diff --git a/stdio-common/printf_buffer_flush.c b/stdio-common/printf_buffer_flush.c
index f44c8b887f..14fe1b2df4 100644
--- a/stdio-common/printf_buffer_flush.c
+++ b/stdio-common/printf_buffer_flush.c
@@ -27,6 +27,7 @@
 #ifndef SHARED
 # pragma weak __printf_buffer_flush_snprintf
 # pragma weak __printf_buffer_flush_to_file
+# pragma weak __printf_buffer_flush_asprintf
 # pragma weak __printf_buffer_flush_fp
 # pragma weak __printf_buffer_flush_fp_to_wide
 # pragma weak __printf_buffer_flush_fphex_to_wide
@@ -49,6 +50,9 @@ __printf_buffer_do_flush (struct __printf_buffer *buf)
     case __printf_buffer_mode_to_file:
       __printf_buffer_flush_to_file ((struct __printf_buffer_to_file *) buf);
       return;
+    case __printf_buffer_mode_asprintf:
+      __printf_buffer_flush_asprintf ((struct __printf_buffer_asprintf *) buf);
+      return;
     case __printf_buffer_mode_strfmon:
       __set_errno (E2BIG);
       __printf_buffer_mark_failed (buf);
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 24/26] libio: Convert __vdprintf_internal to buffers
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (22 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 23/26] libio: Convert __vasprintf_internal " Florian Weimer
@ 2022-03-17 19:31 ` Florian Weimer
  2022-03-17 19:32 ` [PATCH 25/26] libio: Convert __obstack_vprintf_internal to buffers (bug 27124) Florian Weimer
  2022-03-17 19:32 ` [PATCH 26/26] libio: Convert __vswprintf_internal to buffers (bug 27857) Florian Weimer
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:31 UTC (permalink / raw)
  To: libc-alpha

The internal buffer size is set to 2048 bytes.  This is less than
the original BUFSIZ value used by buffered_vfprintf before
the conversion, but it hopefully covers all cases where write
boundaries matter.
---
 include/printf_buffer.h            |  4 ++
 libio/iovdprintf.c                 | 69 ++++++++++++++++++------------
 stdio-common/printf_buffer_flush.c |  4 ++
 3 files changed, 49 insertions(+), 28 deletions(-)

diff --git a/include/printf_buffer.h b/include/printf_buffer.h
index 0f3543d9b9..1943cd76f1 100644
--- a/include/printf_buffer.h
+++ b/include/printf_buffer.h
@@ -49,6 +49,7 @@ enum __printf_buffer_mode
     __printf_buffer_mode_sprintf_chk,
     __printf_buffer_mode_to_file,
     __printf_buffer_mode_asprintf,
+    __printf_buffer_mode_dprintf,
     __printf_buffer_mode_strfmon,
     __printf_buffer_mode_fp,         /* For __printf_fp_l_buffer.  */
     __printf_buffer_mode_fp_to_wide, /* For __wprintf_fp_l_buffer.  */
@@ -305,6 +306,9 @@ void __printf_buffer_flush_to_file (struct __printf_buffer_to_file *)
 struct __printf_buffer_asprintf;
 void __printf_buffer_flush_asprintf (struct __printf_buffer_asprintf *)
   attribute_hidden;
+struct __printf_buffer_dprintf;
+void __printf_buffer_flush_dprintf (struct __printf_buffer_dprintf *)
+  attribute_hidden;
 struct __printf_buffer_fp;
 void __printf_buffer_flush_fp (struct __printf_buffer_fp *)
   attribute_hidden;
diff --git a/libio/iovdprintf.c b/libio/iovdprintf.c
index 1e483868c9..f4cdd6b537 100644
--- a/libio/iovdprintf.c
+++ b/libio/iovdprintf.c
@@ -24,41 +24,54 @@
    This exception applies to code released by its copyright holders
    in files containing the exception.  */
 
-#include <libioP.h>
+#include <array_length.h>
+#include <math_ldbl_opt.h>
+#include <printf.h>
 #include <stdio_ext.h>
+#include <unistd.h>
+#include <printf_buffer.h>
 
-int
-__vdprintf_internal (int d, const char *format, va_list arg,
-		     unsigned int mode_flags)
+struct __printf_buffer_dprintf
 {
-  struct _IO_FILE_plus tmpfil;
-  struct _IO_wide_data wd;
-  int done;
+  struct __printf_buffer base;
+  int fd;
+
+  /* This should cover most of the packet-oriented file descriptors,
+     where boundaries between writes could be visible to readers.  */
+  char buf[2048];
+};
 
-#ifdef _IO_MTSAFE_IO
-  tmpfil.file._lock = NULL;
-#endif
-  _IO_no_init (&tmpfil.file, _IO_USER_LOCK, 0, &wd, &_IO_wfile_jumps);
-  _IO_JUMPS (&tmpfil) = &_IO_file_jumps;
-  _IO_new_file_init_internal (&tmpfil);
-  if (_IO_file_attach (&tmpfil.file, d) == NULL)
+void
+__printf_buffer_flush_dprintf (struct __printf_buffer_dprintf *buf)
+{
+  char *p = buf->buf;
+  char *end = buf->base.write_ptr;
+  while (p < end)
     {
-      _IO_un_link (&tmpfil);
-      return EOF;
+      ssize_t ret = TEMP_FAILURE_RETRY (write (buf->fd, p, end - p));
+      if (ret < 0)
+	{
+	  __printf_buffer_mark_failed (&buf->base);
+	  return;
+	}
+      p += ret;
     }
-  tmpfil.file._flags |= _IO_DELETE_DONT_CLOSE;
-
-  _IO_mask_flags (&tmpfil.file, _IO_NO_READS,
-		  _IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
-
-  done = __vfprintf_internal (&tmpfil.file, format, arg, mode_flags);
-
-  if (done != EOF && _IO_do_flush (&tmpfil.file) == EOF)
-    done = EOF;
-
-  _IO_FINISH (&tmpfil.file);
+  buf->base.write_ptr = buf->buf;
+}
 
-  return done;
+int
+__vdprintf_internal (int d, const char *format, va_list arg,
+		     unsigned int mode_flags)
+{
+  struct __printf_buffer_dprintf buf;
+  __printf_buffer_init (&buf.base, buf.buf, array_length (buf.buf),
+			__printf_buffer_mode_dprintf);
+  buf.fd = d;
+  __printf_buffer (&buf.base, format, arg, mode_flags);
+  if (__printf_buffer_has_failed (&buf.base))
+    return -1;
+  __printf_buffer_flush_dprintf (&buf);
+  return __printf_buffer_done (&buf.base);
 }
 
 int
diff --git a/stdio-common/printf_buffer_flush.c b/stdio-common/printf_buffer_flush.c
index 14fe1b2df4..922340cc54 100644
--- a/stdio-common/printf_buffer_flush.c
+++ b/stdio-common/printf_buffer_flush.c
@@ -28,6 +28,7 @@
 # pragma weak __printf_buffer_flush_snprintf
 # pragma weak __printf_buffer_flush_to_file
 # pragma weak __printf_buffer_flush_asprintf
+# pragma weak __printf_buffer_flush_dprintf
 # pragma weak __printf_buffer_flush_fp
 # pragma weak __printf_buffer_flush_fp_to_wide
 # pragma weak __printf_buffer_flush_fphex_to_wide
@@ -53,6 +54,9 @@ __printf_buffer_do_flush (struct __printf_buffer *buf)
     case __printf_buffer_mode_asprintf:
       __printf_buffer_flush_asprintf ((struct __printf_buffer_asprintf *) buf);
       return;
+    case __printf_buffer_mode_dprintf:
+      __printf_buffer_flush_dprintf ((struct __printf_buffer_dprintf *) buf);
+      return;
     case __printf_buffer_mode_strfmon:
       __set_errno (E2BIG);
       __printf_buffer_mark_failed (buf);
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 25/26] libio: Convert __obstack_vprintf_internal to buffers (bug 27124)
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (23 preceding siblings ...)
  2022-03-17 19:31 ` [PATCH 24/26] libio: Convert __vdprintf_internal " Florian Weimer
@ 2022-03-17 19:32 ` Florian Weimer
  2022-03-17 19:32 ` [PATCH 26/26] libio: Convert __vswprintf_internal to buffers (bug 27857) Florian Weimer
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:32 UTC (permalink / raw)
  To: libc-alpha

This fixes bug 27124 because the problematic built-in vtable is gone.
---
 include/printf_buffer.h            |   4 +
 libio/obprintf.c                   | 170 +++++++++--------------------
 stdio-common/printf_buffer_flush.c |   4 +
 3 files changed, 58 insertions(+), 120 deletions(-)

diff --git a/include/printf_buffer.h b/include/printf_buffer.h
index 1943cd76f1..8b9a09647d 100644
--- a/include/printf_buffer.h
+++ b/include/printf_buffer.h
@@ -54,6 +54,7 @@ enum __printf_buffer_mode
     __printf_buffer_mode_fp,         /* For __printf_fp_l_buffer.  */
     __printf_buffer_mode_fp_to_wide, /* For __wprintf_fp_l_buffer.  */
     __printf_buffer_mode_fphex_to_wide, /* For __wprintf_fphex_l_buffer.  */
+    __printf_buffer_mode_obstack,
   };
 
 /* Buffer for fast character writing with overflow handling.
@@ -319,6 +320,9 @@ struct __printf_buffer_fphex_to_wide;
 void __printf_buffer_flush_fphex_to_wide (struct
                                           __printf_buffer_fphex_to_wide *)
   attribute_hidden;
+struct __printf_buffer_obstack;
+void __printf_buffer_flush_obstack (struct __printf_buffer_obstack *)
+  attribute_hidden;
 
 struct __wprintf_buffer_to_file;
 void __wprintf_buffer_flush_to_file (struct __wprintf_buffer_to_file *)
diff --git a/libio/obprintf.c b/libio/obprintf.c
index c220be9adc..459d73ebf3 100644
--- a/libio/obprintf.c
+++ b/libio/obprintf.c
@@ -16,131 +16,59 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-
-#include <stdlib.h>
-#include "libioP.h"
-#include "strfile.h"
 #include <assert.h>
-#include <string.h>
-#include <errno.h>
+#include <math_ldbl_opt.h>
 #include <obstack.h>
+#include <printf.h>
 #include <stdarg.h>
-#include <stdio_ext.h>
-
+#include <printf_buffer.h>
 
-struct _IO_obstack_file
+struct __printf_buffer_obstack
 {
-  struct _IO_FILE_plus file;
+  struct __printf_buffer base;
   struct obstack *obstack;
-};
-
-
-static int
-_IO_obstack_overflow (FILE *fp, int c)
-{
-  struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;
-  int size;
-
-  /* Make room for another character.  This might as well allocate a
-     new chunk a memory and moves the old contents over.  */
-  assert (c != EOF);
-  obstack_1grow (obstack, c);
-
-  /* Setup the buffer pointers again.  */
-  fp->_IO_write_base = obstack_base (obstack);
-  fp->_IO_write_ptr = obstack_next_free (obstack);
-  size = obstack_room (obstack);
-  fp->_IO_write_end = fp->_IO_write_ptr + size;
-  /* Now allocate the rest of the current chunk.  */
-  obstack_blank_fast (obstack, size);
-
-  return c;
-}
 
+  /* obstack_1grow is called for compatibility reasons.  This needs
+     one extra character, and this is the backing store for it.  */
+  char ch;
+};
 
-static size_t
-_IO_obstack_xsputn (FILE *fp, const void *data, size_t n)
+void
+__printf_buffer_flush_obstack (struct __printf_buffer_obstack *buf)
 {
-  struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;
+  /* About to switch buffers, so record the bytes written so far.  */
+  buf->base.written += buf->base.write_ptr - buf->base.write_base;
 
-  if (fp->_IO_write_ptr + n > fp->_IO_write_end)
+  if (buf->base.write_ptr == &buf->ch + 1)
     {
-      int size;
-
-      /* We need some more memory.  First shrink the buffer to the
-	 space we really currently need.  */
-      obstack_blank_fast (obstack, fp->_IO_write_ptr - fp->_IO_write_end);
-
-      /* Now grow for N bytes, and put the data there.  */
-      obstack_grow (obstack, data, n);
-
-      /* Setup the buffer pointers again.  */
-      fp->_IO_write_base = obstack_base (obstack);
-      fp->_IO_write_ptr = obstack_next_free (obstack);
-      size = obstack_room (obstack);
-      fp->_IO_write_end = fp->_IO_write_ptr + size;
-      /* Now allocate the rest of the current chunk.  */
-      obstack_blank_fast (obstack, size);
+      /* Errors are reported via a callback mechanism (presumably for
+	 process termination).  */
+      obstack_1grow (buf->obstack, buf->ch);
+      buf->base.write_base = obstack_next_free (buf->obstack);
+      buf->base.write_ptr = buf->base.write_base;
+      size_t size = obstack_room (buf->obstack);
+      buf->base.write_end = buf->base.write_ptr + size;
+      /* Reserve the space on the obstack size.  */
+      obstack_blank_fast (buf->obstack, size);
     }
   else
-    fp->_IO_write_ptr = __mempcpy (fp->_IO_write_ptr, data, n);
-
-  return n;
+    {
+      /* Obtain the extra character.  */
+      buf->base.write_base = &buf->ch;
+      buf->base.write_ptr = &buf->ch;
+      buf->base.write_end = &buf->ch + 1;
+    }
 }
 
-
-/* the jump table.  */
-const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden =
-{
-  JUMP_INIT_DUMMY,
-  JUMP_INIT(finish, NULL),
-  JUMP_INIT(overflow, _IO_obstack_overflow),
-  JUMP_INIT(underflow, NULL),
-  JUMP_INIT(uflow, NULL),
-  JUMP_INIT(pbackfail, NULL),
-  JUMP_INIT(xsputn, _IO_obstack_xsputn),
-  JUMP_INIT(xsgetn, NULL),
-  JUMP_INIT(seekoff, NULL),
-  JUMP_INIT(seekpos, NULL),
-  JUMP_INIT(setbuf, NULL),
-  JUMP_INIT(sync, NULL),
-  JUMP_INIT(doallocate, NULL),
-  JUMP_INIT(read, NULL),
-  JUMP_INIT(write, NULL),
-  JUMP_INIT(seek, NULL),
-  JUMP_INIT(close, NULL),
-  JUMP_INIT(stat, NULL),
-  JUMP_INIT(showmanyc, NULL),
-  JUMP_INIT(imbue, NULL)
-};
-
-
 int
 __obstack_vprintf_internal (struct obstack *obstack, const char *format,
 			    va_list args, unsigned int mode_flags)
 {
-  struct obstack_FILE
-    {
-      struct _IO_obstack_file ofile;
-  } new_f;
-  int result;
-  int size;
-  int room;
-
-#ifdef _IO_MTSAFE_IO
-  new_f.ofile.file.file._lock = NULL;
-#endif
-
-  _IO_no_init (&new_f.ofile.file.file, _IO_USER_LOCK, -1, NULL, NULL);
-  _IO_JUMPS (&new_f.ofile.file) = &_IO_obstack_jumps;
-  room = obstack_room (obstack);
-  size = obstack_object_size (obstack) + room;
+  /* Legacy setup code for compatibility.  */
+  size_t room = obstack_room (obstack);
+  size_t size = obstack_object_size (obstack) + room;
   if (size == 0)
     {
-      /* We have to handle the allocation a bit different since the
-	 `_IO_str_init_static' function would handle a size of zero
-	 different from what we expect.  */
-
       /* Get more memory.  */
       obstack_make_room (obstack, 64);
 
@@ -151,27 +79,29 @@ __obstack_vprintf_internal (struct obstack *obstack, const char *format,
       assert (size != 0);
     }
 
-  _IO_str_init_static_internal ((struct _IO_strfile_ *) &new_f.ofile,
-				obstack_base (obstack),
-				size, obstack_next_free (obstack));
+  struct __printf_buffer_obstack buf;
+  {
+    /* The obstack write location might be in the middle of an object.  */
+    char *ptr = obstack_next_free (obstack);
+    char *end = obstack_base (obstack) + size;
+    __printf_buffer_init (&buf.base, ptr, end - ptr,
+			  __printf_buffer_mode_obstack);
+  }
+  buf.obstack = obstack;
+
   /* Now allocate the rest of the current chunk.  */
-  assert (size == (new_f.ofile.file.file._IO_write_end
-		   - new_f.ofile.file.file._IO_write_base));
-  assert (new_f.ofile.file.file._IO_write_ptr
-	  == (new_f.ofile.file.file._IO_write_base
-	      + obstack_object_size (obstack)));
   obstack_blank_fast (obstack, room);
 
-  new_f.ofile.obstack = obstack;
-
-  result = __vfprintf_internal (&new_f.ofile.file.file, format, args,
-				mode_flags);
+  __printf_buffer (&buf.base, format, args, mode_flags);
 
-  /* Shrink the buffer to the space we really currently need.  */
-  obstack_blank_fast (obstack, (new_f.ofile.file.file._IO_write_ptr
-				- new_f.ofile.file.file._IO_write_end));
+  if (buf.base.write_ptr == &buf.ch + 1)
+    /* buf.ch is in use.  Put it into the obstack.  */
+    obstack_1grow (buf.obstack, buf.ch);
+  else if (buf.base.write_ptr != &buf.ch)
+    /* Shrink the buffer to the space we really currently need.  */
+    obstack_blank_fast (buf.obstack, buf.base.write_ptr - buf.base.write_end);
 
-  return result;
+  return __printf_buffer_done (&buf.base);
 }
 
 int
diff --git a/stdio-common/printf_buffer_flush.c b/stdio-common/printf_buffer_flush.c
index 922340cc54..42afe49413 100644
--- a/stdio-common/printf_buffer_flush.c
+++ b/stdio-common/printf_buffer_flush.c
@@ -32,6 +32,7 @@
 # pragma weak __printf_buffer_flush_fp
 # pragma weak __printf_buffer_flush_fp_to_wide
 # pragma weak __printf_buffer_flush_fphex_to_wide
+# pragma weak __printf_buffer_flush_obstack
 #endif /* !SHARED */
 
 static void
@@ -72,6 +73,9 @@ __printf_buffer_do_flush (struct __printf_buffer *buf)
       __printf_buffer_flush_fphex_to_wide
         ((struct __printf_buffer_fphex_to_wide *) buf);
       return;
+    case __printf_buffer_mode_obstack:
+      __printf_buffer_flush_obstack ((struct __printf_buffer_obstack *) buf);
+      return;
     }
   __builtin_trap ();
 }
-- 
2.35.1



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 26/26] libio: Convert __vswprintf_internal to buffers (bug 27857)
  2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
                   ` (24 preceding siblings ...)
  2022-03-17 19:32 ` [PATCH 25/26] libio: Convert __obstack_vprintf_internal to buffers (bug 27124) Florian Weimer
@ 2022-03-17 19:32 ` Florian Weimer
  25 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-17 19:32 UTC (permalink / raw)
  To: libc-alpha

Always null-terminate the buffer and set E2BIG if the buffer is too
small.  This fixes bug 27857.
---
 include/printf_buffer.h             |   1 +
 libio/tst_swprintf.c                |  31 ++++++++-
 libio/vswprintf.c                   | 100 +++++-----------------------
 manual/stdio.texi                   |   7 +-
 stdio-common/wprintf_buffer_flush.c |   6 ++
 5 files changed, 57 insertions(+), 88 deletions(-)

diff --git a/include/printf_buffer.h b/include/printf_buffer.h
index 8b9a09647d..51d5dfc84e 100644
--- a/include/printf_buffer.h
+++ b/include/printf_buffer.h
@@ -194,6 +194,7 @@ bool __printf_buffer_flush (struct __printf_buffer *buf) attribute_hidden;
 enum __wprintf_buffer_mode
   {
     __wprintf_buffer_mode_failed,
+    __wprintf_buffer_mode_swprintf,
     __wprintf_buffer_mode_to_file,
   };
 
diff --git a/libio/tst_swprintf.c b/libio/tst_swprintf.c
index 5ede402fff..6684749807 100644
--- a/libio/tst_swprintf.c
+++ b/libio/tst_swprintf.c
@@ -1,4 +1,5 @@
 #include <array_length.h>
+#include <errno.h>
 #include <stdio.h>
 #include <support/check.h>
 #include <sys/types.h>
@@ -37,6 +38,8 @@ do_test (void)
 
   for (n = 0; n < array_length(tests); ++n)
     {
+      wmemset (buf, 0xabcd, array_length (buf));
+      errno = 0;
       ssize_t res = swprintf (buf, tests[n].n, L"%s", tests[n].str);
 
       if (tests[n].exp < 0 && res >= 0)
@@ -52,9 +55,31 @@ do_test (void)
 swprintf (buf, %Zu, L\"%%s\", \"%s\") expected to return %Zd, but got %Zd\n",
 		  tests[n].n, tests[n].str, tests[n].exp, res);
 	}
-      else
-	printf ("swprintf (buf, %Zu, L\"%%s\", \"%s\") OK\n",
-		tests[n].n, tests[n].str);
+      else if (res < 0
+	       && tests[n].n > 0
+	       && wcsnlen (buf, array_length (buf)) == array_length (buf))
+	{
+	  support_record_failure ();
+	  printf ("\
+error: swprintf (buf, %Zu, L\"%%s\", \"%s\") missing null terminator\n",
+		  tests[n].n, tests[n].str);
+	}
+      else if (res < 0
+	       && tests[n].n > 0
+	       && wcsnlen (buf, array_length (buf)) < array_length (buf)
+	       && buf[wcsnlen (buf, array_length (buf)) + 1] != 0xabcd)
+	{
+	  support_record_failure ();
+	  printf ("\
+error: swprintf (buf, %Zu, L\"%%s\", \"%s\") out of bounds write\n",
+		  tests[n].n, tests[n].str);
+	}
+
+      if (res < 0 && tests[n].n < 0)
+	TEST_COMPARE (errno, E2BIG);
+
+      printf ("swprintf (buf, %Zu, L\"%%s\", \"%s\") OK\n",
+	      tests[n].n, tests[n].str);
     }
 
   TEST_COMPARE (swprintf (buf, array_length (buf), L"%.0s", "foo"), 0);
diff --git a/libio/vswprintf.c b/libio/vswprintf.c
index 5a9ccdff3e..f53545c806 100644
--- a/libio/vswprintf.c
+++ b/libio/vswprintf.c
@@ -24,101 +24,37 @@
    This exception applies to code released by its copyright holders
    in files containing the exception.  */
 
-#include "libioP.h"
-#include "strfile.h"
-
-
-static wint_t _IO_wstrn_overflow (FILE *fp, wint_t c) __THROW;
-
-static wint_t
-_IO_wstrn_overflow (FILE *fp, wint_t c)
-{
-  /* When we come to here this means the user supplied buffer is
-     filled.  But since we must return the number of characters which
-     would have been written in total we must provide a buffer for
-     further use.  We can do this by writing on and on in the overflow
-     buffer in the _IO_wstrnfile structure.  */
-  _IO_wstrnfile *snf = (_IO_wstrnfile *) fp;
-
-  if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
-    {
-      _IO_wsetb (fp, snf->overflow_buf,
-		 snf->overflow_buf + (sizeof (snf->overflow_buf)
-				      / sizeof (wchar_t)), 0);
-
-      fp->_wide_data->_IO_write_base = snf->overflow_buf;
-      fp->_wide_data->_IO_read_base = snf->overflow_buf;
-      fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
-      fp->_wide_data->_IO_read_end = (snf->overflow_buf
-				      + (sizeof (snf->overflow_buf)
-					 / sizeof (wchar_t)));
-    }
-
-  fp->_wide_data->_IO_write_ptr = snf->overflow_buf;
-  fp->_wide_data->_IO_write_end = snf->overflow_buf;
-
-  /* Since we are not really interested in storing the characters
-     which do not fit in the buffer we simply ignore it.  */
-  return c;
-}
-
-
-const struct _IO_jump_t _IO_wstrn_jumps libio_vtable attribute_hidden =
-{
-  JUMP_INIT_DUMMY,
-  JUMP_INIT(finish, _IO_wstr_finish),
-  JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstrn_overflow),
-  JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
-  JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
-  JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
-  JUMP_INIT(xsputn, _IO_wdefault_xsputn),
-  JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
-  JUMP_INIT(seekoff, _IO_wstr_seekoff),
-  JUMP_INIT(seekpos, _IO_default_seekpos),
-  JUMP_INIT(setbuf, _IO_default_setbuf),
-  JUMP_INIT(sync, _IO_default_sync),
-  JUMP_INIT(doallocate, _IO_wdefault_doallocate),
-  JUMP_INIT(read, _IO_default_read),
-  JUMP_INIT(write, _IO_default_write),
-  JUMP_INIT(seek, _IO_default_seek),
-  JUMP_INIT(close, _IO_default_close),
-  JUMP_INIT(stat, _IO_default_stat),
-  JUMP_INIT(showmanyc, _IO_default_showmanyc),
-  JUMP_INIT(imbue, _IO_default_imbue)
-};
-
+#include <errno.h>
+#include <math_ldbl_opt.h>
+#include <printf.h>
+#include <printf_buffer.h>
 
 int
 __vswprintf_internal (wchar_t *string, size_t maxlen, const wchar_t *format,
 		      va_list args, unsigned int mode_flags)
 {
-  _IO_wstrnfile sf;
-  int ret;
-  struct _IO_wide_data wd;
-#ifdef _IO_MTSAFE_IO
-  sf.f._sbf._f._lock = NULL;
-#endif
-
   if (maxlen == 0)
     /* Since we have to write at least the terminating L'\0' a buffer
        length of zero always makes the function fail.  */
     return -1;
 
-  _IO_no_init (&sf.f._sbf._f, _IO_USER_LOCK, 0, &wd, &_IO_wstrn_jumps);
-  _IO_fwide (&sf.f._sbf._f, 1);
-  string[0] = L'\0';
-  _IO_wstr_init_static (&sf.f._sbf._f, string, maxlen - 1, string);
-  ret = __vfwprintf_internal ((FILE *) &sf.f._sbf, format, args, mode_flags);
+  struct __wprintf_buffer buf;
+  __wprintf_buffer_init (&buf, string, maxlen, __wprintf_buffer_mode_swprintf);
 
-  if (sf.f._sbf._f._wide_data->_IO_buf_base == sf.overflow_buf)
-    /* ISO C99 requires swprintf/vswprintf to return an error if the
-       output does not fit in the provided buffer.  */
-    return -1;
+  __wprintf_buffer (&buf, format, args, mode_flags);
+
+  if (buf.write_ptr == buf.write_end)
+    {
+      /* Buffer has been filled exactly, excluding the null wide
+	 character.  This is an error because the null wide character
+	 is required.  */
+      buf.write_end[-1] = L'\0';
+      return -1;
+    }
 
-  /* Terminate the string.  */
-  *sf.f._sbf._f._wide_data->_IO_write_ptr = '\0';
+  buf.write_ptr[0] = L'\0';
 
-  return ret;
+  return __wprintf_buffer_done (&buf);
 }
 
 int
diff --git a/manual/stdio.texi b/manual/stdio.texi
index 753c50920d..278ab462ae 100644
--- a/manual/stdio.texi
+++ b/manual/stdio.texi
@@ -2412,9 +2412,10 @@ allocate at least @var{size} wide characters for the string @var{ws}.
 
 The return value is the number of characters generated for the given
 input, excluding the trailing null.  If not all output fits into the
-provided buffer a negative value is returned.  You should try again with
-a bigger output string.  @emph{Note:} this is different from how
-@code{snprintf} handles this situation.
+provided buffer a negative value is returned, and @code{errno} is set to
+@code{E2BIG}.  (The setting of @code{errno} is a GNU extension.)  You
+should try again with a bigger output string.  @emph{Note:} this is
+different from how @code{snprintf} handles this situation.
 
 Note that the corresponding narrow stream function takes fewer
 parameters.  @code{swprintf} in fact corresponds to the @code{snprintf}
diff --git a/stdio-common/wprintf_buffer_flush.c b/stdio-common/wprintf_buffer_flush.c
index 2d91095cca..46af0f348f 100644
--- a/stdio-common/wprintf_buffer_flush.c
+++ b/stdio-common/wprintf_buffer_flush.c
@@ -16,6 +16,7 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <errno.h>
 #include <printf_buffer.h>
 
 #include "printf_buffer-wchar_t.h"
@@ -31,6 +32,11 @@ __wprintf_buffer_do_flush (struct __wprintf_buffer *buf)
     case __wprintf_buffer_mode_to_file:
       __wprintf_buffer_flush_to_file ((struct __wprintf_buffer_to_file *) buf);
       return;
+    case __wprintf_buffer_mode_swprintf:
+      buf->write_end[-1] = L'\0';
+      __set_errno (E2BIG);
+      __wprintf_buffer_mark_failed (buf);
+      return;
     }
   __builtin_trap ();
 }
-- 
2.35.1


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 01/26] libio: Convert tst_swprintf to the test framework
  2022-03-17 19:28 ` [PATCH 01/26] libio: Convert tst_swprintf to the test framework Florian Weimer
@ 2022-03-18 17:40   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-03-18 17:40 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:28, Florian Weimer via Libc-alpha wrote:
> And increase test coverage slightly.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  libio/tst_swprintf.c | 79 ++++++++++++++++----------------------------
>  1 file changed, 28 insertions(+), 51 deletions(-)
> 
> diff --git a/libio/tst_swprintf.c b/libio/tst_swprintf.c
> index e4bd7f022a..5ede402fff 100644
> --- a/libio/tst_swprintf.c
> +++ b/libio/tst_swprintf.c
> @@ -1,10 +1,11 @@
> +#include <array_length.h>
>  #include <stdio.h>
> -#include <wchar.h>
> +#include <support/check.h>
>  #include <sys/types.h>
> +#include <wchar.h>
>  
>  
>  static wchar_t buf[100];
> -#define nbuf (sizeof (buf) / sizeof (buf[0]))
>  static const struct
>  {
>    size_t n;
> @@ -12,81 +13,57 @@ static const struct
>    ssize_t exp;
>  } tests[] =
>    {
> -    { nbuf, "hello world", 11 },
> +    { array_length (buf), "hello world", 11 },
>      { 0, "hello world", -1 },
> +    { 1, "hello world", -1 },
> +    { 2, "hello world", -1 },
> +    { 11, "hello world", -1 },
> +    { 12, "hello world", 11 },
>      { 0, "", -1 },
> -    { nbuf, "", 0 }
> +    { array_length (buf), "", 0 }
>    };
>  
> -int
> -main (int argc, char *argv[])
> +static int
> +do_test (void)
>  {
>    size_t n;
> -  int result = 0;
>  
> -  puts ("test 1");
> -  n = swprintf (buf, nbuf, L"Hello %s", "world");
> -  if (n != 11)
> -    {
> -      printf ("incorrect return value: %zd instead of 11\n", n);
> -      result = 1;
> -    }
> -  else if (wcscmp (buf, L"Hello world") != 0)
> -    {
> -      printf ("incorrect string: L\"%ls\" instead of L\"Hello world\"\n", buf);
> -      result = 1;
> -    }
> +  TEST_COMPARE (swprintf (buf, array_length (buf), L"Hello %s", "world"), 11);
> +  TEST_COMPARE_STRING_WIDE (buf, L"Hello world");
>  
> -  puts ("test 2");
> -  n = swprintf (buf, nbuf, L"Is this >%g< 3.1?", 3.1);
> -  if (n != 18)
> -    {
> -      printf ("incorrect return value: %zd instead of 18\n", n);
> -      result = 1;
> -    }
> -  else if (wcscmp (buf, L"Is this >3.1< 3.1?") != 0)
> -    {
> -      printf ("incorrect string: L\"%ls\" instead of L\"Is this >3.1< 3.1?\"\n",
> -	      buf);
> -      result = 1;
> -    }
> +  TEST_COMPARE (swprintf (buf, array_length (buf), L"Is this >%g< 3.1?", 3.1),
> +		18);
> +  TEST_COMPARE_STRING_WIDE (buf, L"Is this >3.1< 3.1?");
>  
> -  for (n = 0; n < sizeof (tests) / sizeof (tests[0]); ++n)
> +  for (n = 0; n < array_length(tests); ++n)

Missing space before '('?

>      {
>        ssize_t res = swprintf (buf, tests[n].n, L"%s", tests[n].str);
>  
>        if (tests[n].exp < 0 && res >= 0)
>  	{
> +	  support_record_failure ();
>  	  printf ("swprintf (buf, %Zu, L\"%%s\", \"%s\") expected to fail\n",
>  		  tests[n].n, tests[n].str);
> -	  result = 1;
>  	}
>        else if (tests[n].exp >= 0 && tests[n].exp != res)
>  	{
> -	  printf ("swprintf (buf, %Zu, L\"%%s\", \"%s\") expected to return %Zd, but got %Zd\n",
> +	  support_record_failure ();
> +	  printf ("\
> +swprintf (buf, %Zu, L\"%%s\", \"%s\") expected to return %Zd, but got %Zd\n",
>  		  tests[n].n, tests[n].str, tests[n].exp, res);
> -	  result = 1;
>  	}
>        else
>  	printf ("swprintf (buf, %Zu, L\"%%s\", \"%s\") OK\n",
>  		tests[n].n, tests[n].str);
>      }
>  
> -  if (swprintf (buf, nbuf, L"%.0s", "foo") != 0
> -      || wcslen (buf) != 0)
> -    {
> -      printf ("swprintf (buf, %Zu, L\"%%.0s\", \"foo\") create some output\n",
> -	      nbuf);
> -      result = 1;
> -    }
> +  TEST_COMPARE (swprintf (buf, array_length (buf), L"%.0s", "foo"), 0);
> +  TEST_COMPARE_STRING_WIDE (buf, L"");
>  
> -  if (swprintf (buf, nbuf, L"%.0ls", L"foo") != 0
> -      || wcslen (buf) != 0)
> -    {
> -      printf ("swprintf (buf, %Zu, L\"%%.0ls\", L\"foo\") create some output\n",
> -	      nbuf);
> -      result = 1;
> -    }
> +  TEST_COMPARE (swprintf (buf, array_length (buf), L"%.0ls", L"foo"), 0);
> +  TEST_COMPARE_STRING_WIDE (buf, L"");
>  
> -  return result;
> +  return 0;
>  }
> +
> +#include <support/test-driver.c>

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/26] libio: Flush-only _IO_str_overflow must not return EOF (bug 28949)
  2022-03-17 19:28 ` [PATCH 02/26] libio: Flush-only _IO_str_overflow must not return EOF (bug 28949) Florian Weimer
@ 2022-03-18 18:11   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-03-18 18:11 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:28, Florian Weimer via Libc-alpha wrote:
> In general, _IO_str_overflow returns the character passed as an argument
> on success.  However, if flush-only operation is requested by passing
> EOF, returning EOF looks like an error, and the caller cannot tell
> whether the operation was successful or not.
> 
> _IO_wstr_overflow had the same bug regarding WEOF.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  libio/strops.c  | 5 ++++-
>  libio/wstrops.c | 5 ++++-
>  2 files changed, 8 insertions(+), 2 deletions(-)
> 
> diff --git a/libio/strops.c b/libio/strops.c
> index 6a9a8846c4..1cd0bf6c3d 100644
> --- a/libio/strops.c
> +++ b/libio/strops.c
> @@ -133,7 +133,10 @@ _IO_str_overflow (FILE *fp, int c)
>      *fp->_IO_write_ptr++ = (unsigned char) c;
>    if (fp->_IO_write_ptr > fp->_IO_read_end)
>      fp->_IO_read_end = fp->_IO_write_ptr;
> -  return c;
> +  if (flush_only)
> +    return 0;
> +  else
> +    return c;
>  }
>  libc_hidden_def (_IO_str_overflow)
>  
> diff --git a/libio/wstrops.c b/libio/wstrops.c
> index 8e44f86c35..2aec314937 100644
> --- a/libio/wstrops.c
> +++ b/libio/wstrops.c
> @@ -130,7 +130,10 @@ _IO_wstr_overflow (FILE *fp, wint_t c)
>      *fp->_wide_data->_IO_write_ptr++ = c;
>    if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_read_end)
>      fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_write_ptr;
> -  return c;
> +  if (flush_only)
> +    return 0;
> +  else
> +    return c;
>  }
>  
>  

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 03/26] stdio-common: Add wide stream coverage to tst-vfprintf-user-type
  2022-03-17 19:28 ` [PATCH 03/26] stdio-common: Add wide stream coverage to tst-vfprintf-user-type Florian Weimer
@ 2022-03-18 18:30   ` Adhemerval Zanella
  2022-03-18 19:19     ` Florian Weimer
  0 siblings, 1 reply; 49+ messages in thread
From: Adhemerval Zanella @ 2022-03-18 18:30 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:28, Florian Weimer via Libc-alpha wrote:
> And use TEST_COMPARE_STRING for the narrow tests.

LGTM, just a comment below.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  stdio-common/tst-vfprintf-user-type.c | 128 ++++++++++++--------------
>  1 file changed, 61 insertions(+), 67 deletions(-)
> 
> diff --git a/stdio-common/tst-vfprintf-user-type.c b/stdio-common/tst-vfprintf-user-type.c
> index f2650ac018..4aa78a6b60 100644
> --- a/stdio-common/tst-vfprintf-user-type.c
> +++ b/stdio-common/tst-vfprintf-user-type.c
> @@ -21,6 +21,7 @@
>     this indicates the number of such pairs which constitute the
>     argument.  */
>  
> +#include <array_length.h>
>  #include <locale.h>
>  #include <printf.h>
>  #include <stdio.h>
> @@ -60,13 +61,23 @@ my_printf_function (FILE *fp, const struct printf_info *info,
>              __func__, fp, info, args[0], args, (wint_t) info->spec,
>              info->prec);
>  
> +  TEST_COMPARE (info->wide, fwide (fp, 0) > 0);
> +
>    TEST_VERIFY (info->spec == 'P');
>    size_t nargs;
>    int printed;
>    if (info->prec >= 0)
>      {
> -      if (fputc ('{', fp) < 0)
> -        return -1;
> +      if (info->wide)
> +        {
> +          if (fputwc (L'{', fp) < 0)
> +            return -1;
> +          }
> +      else
> +        {
> +          if (fputc ('{', fp) < 0)
> +            return -1;
> +        }
>        nargs = info->prec;
>        printed = 1;
>      }

Ok.

> @@ -80,8 +91,16 @@ my_printf_function (FILE *fp, const struct printf_info *info,
>      {
>        if (i != 0)
>          {
> -          if (fputc (',', fp) < 0)
> -            return -1;
> +          if (info->wide)
> +            {
> +              if (fputwc (L',', fp) < 0)
> +                return -1;
> +            }
> +          else
> +            {
> +              if (fputc (',', fp) < 0)
> +                return -1;
> +            }
>            ++printed;
>          }
>  

Ok.

> @@ -89,15 +108,27 @@ my_printf_function (FILE *fp, const struct printf_info *info,
>           and those pointers point to a pointer to the memory area
>           supplied to my_va_arg_function.  */
>        struct two_argument *pair = *(void **) args[i];
> -      int ret = fprintf (fp, "(%ld, %f)", pair->i, pair->d);
> +      int ret;
> +      if (info->wide)
> +        ret = fwprintf (fp, L"(%ld, %f)", pair->i, pair->d);
> +      else
> +        ret = fprintf (fp, "(%ld, %f)", pair->i, pair->d);
>        if (ret < 0)
>          return -1;
>        printed += ret;
>      }
>    if (info->prec >= 0)
>      {
> -      if (fputc ('}', fp) < 0)
> -        return -1;
> +      if (info->wide)
> +        {
> +          if (fputwc (L'}', fp) < 0)
> +            return -1;
> +        }
> +      else
> +        {
> +          if (fputc ('}', fp) < 0)
> +            return -1;
> +        }
>        ++printed;
>      }
>    return printed;

Ok.

> @@ -145,77 +176,40 @@ do_test (void)
>    TEST_VERIFY_EXIT (register_printf_specifier
>                      ('P', my_printf_function, my_arginfo_function) >= 0);
>  
> -  /* Alias declaration for asprintf, to avoid the format string
> -     attribute and the associated warning.  */
> -#if __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI == 1
> -  extern int asprintf_alias (char **, const char *, ...) __asm__ ("__asprintfieee128");
> -#else
> -  extern int asprintf_alias (char **, const char *, ...) __asm__ ("asprintf");
> -#endif
> -  TEST_VERIFY (asprintf_alias == asprintf);
> -  char *str = NULL;
> -  TEST_VERIFY (asprintf_alias (&str, "[[%P]]", 123L, 456.0) >= 0);
> -  if (test_verbose > 0)
> -    printf ("info: %s\n", str);
> -  TEST_VERIFY (strcmp (str, "[[(123, 456.000000)]]") == 0);
> -  free (str);
> -
> -  str = NULL;
> -  TEST_VERIFY (asprintf_alias (&str, "[[%1$P %1$P]]", 123L, 457.0) >= 0);
> -  if (test_verbose > 0)
> -    printf ("info: %s\n", str);
> -  TEST_VERIFY (strcmp (str, "[[(123, 457.000000) (123, 457.000000)]]") == 0);
> -  free (str);
> -
> -  str = NULL;
> -  TEST_VERIFY (asprintf_alias (&str, "[[%.1P]]", 1L, 2.0) >= 0);
> -  if (test_verbose > 0)
> -    printf ("info: %s\n", str);
> -  TEST_VERIFY (strcmp (str, "[[{(1, 2.000000)}]]") == 0);
> -  free (str);
> +  /* Wide variants of the tests above.  */
>  
> -  str = NULL;
> -  TEST_VERIFY (asprintf_alias (&str, "[[%.2P]]", 1L, 2.0, 3L, 4.0) >= 0);
> -  if (test_verbose > 0)
> -    printf ("info: %s\n", str);
> -  TEST_VERIFY (strcmp (str, "[[{(1, 2.000000),(3, 4.000000)}]]") == 0);
> -  free (str);
> +  wchar_t buf[200];
> +  TEST_VERIFY (swprintf (buf, array_length (buf), L"[[%.2P]]",
> +                         1L, 2.0, 3L, 4.0) >= 0);
> +  TEST_COMPARE_STRING_WIDE (buf, L"[[{(1, 2.000000),(3, 4.000000)}]]");
>  
> -  str = NULL;
> -  TEST_VERIFY (asprintf_alias
> -               (&str, "[[%.2P | %.3P]]",
> +  TEST_VERIFY (swprintf
> +               (buf, array_length (buf), L"[[%.2P | %.3P]]",
>                  /* argument 1: */ 1L, 2.0, 3L, 4.0,
>                  /* argument 2: */ 5L, 6.0, 7L, 8.0, 9L, 10.0)
>                 >= 0);
> -  if (test_verbose > 0)
> -    printf ("info: %s\n", str);
> -  TEST_VERIFY (strcmp (str,
> -                       "[["
> -                       "{(1, 2.000000),(3, 4.000000)}"
> -                       " | "
> -                       "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
> -                       "]]") == 0);
> -  free (str);
> +  TEST_COMPARE_STRING_WIDE (buf,
> +                            L"[["
> +                            "{(1, 2.000000),(3, 4.000000)}"
> +                            " | "
> +                            "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
> +                            "]]");
>  
>    /* The following subtest fails due to bug 21534.  */
>  #if 0
> -  str = NULL;
> -  TEST_VERIFY (asprintf_alias
> -               (&str, "[[%1$.2P | %2$.3P | %1$.2P]]",
> +  TEST_VERIFY (swprintf
> +               (&buf, array_length (buf), L"[[%1$.2P | %2$.3P | %1$.2P]]",
>                  /* argument 1: */ 1L, 2.0, 3L, 4.0,
>                  /* argument 2: */ 5L, 6.0, 7L, 8.0, 9L, 10.0)
>                 >= 0);
> -  if (test_verbose > 0)
> -    printf ("info: %s\n", str);
> -  TEST_VERIFY (strcmp (str,
> -                       "[["
> -                       "{(1, 2.000000),(3, 4.000000)}"
> -                       " | "
> -                       "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
> -                       " | "
> -                       "{(1, 2.000000),(3, 4.000000)}"
> -                       "]]") == 0);
> -  free (str);
> +  TEST_COMPARE_STRING_WIDE (buf,
> +                            L"[["
> +                            "{(1, 2.000000),(3, 4.000000)}"
> +                            " | "
> +                            "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
> +                            " | "
> +                            "{(1, 2.000000),(3, 4.000000)}"
> +                            "]]");
>  #endif
>  
>    return 0;

Why adjust a dead code here (I haven't checked the rest of the series to check
if you actually enabled it)?

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 03/26] stdio-common: Add wide stream coverage to tst-vfprintf-user-type
  2022-03-18 18:30   ` Adhemerval Zanella
@ 2022-03-18 19:19     ` Florian Weimer
  0 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-03-18 19:19 UTC (permalink / raw)
  To: Adhemerval Zanella; +Cc: libc-alpha

* Adhemerval Zanella:

> Why adjust a dead code here (I haven't checked the rest of the series to check
> if you actually enabled it)?

Hmm, I posted a debugging version of the test by mistake.  It removes
most of the subtests. 8-(

I'll fix this and push.

Thanks,
Florian


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 04/26] stdio-common: Add tst-printf-width-i18n to cover numeric field width
  2022-03-17 19:28 ` [PATCH 04/26] stdio-common: Add tst-printf-width-i18n to cover numeric field width Florian Weimer
@ 2022-05-20 13:22   ` Adhemerval Zanella
  2022-05-20 13:33     ` Adhemerval Zanella
  0 siblings, 1 reply; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 13:22 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:28, Florian Weimer via Libc-alpha wrote:
> Related to bug 28943 and bug 28944.

LGTM with the fix to enabled the test.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  stdio-common/Makefile                |  1 +
>  stdio-common/tst-printf-width-i18n.c | 95 ++++++++++++++++++++++++++++
>  2 files changed, 96 insertions(+)
>  create mode 100644 stdio-common/tst-printf-width-i18n.c
> 
> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
> index 435cd8904f..f0e65f0dcd 100644
> --- a/stdio-common/Makefile
> +++ b/stdio-common/Makefile
> @@ -250,6 +250,7 @@ LOCALES := \
>    de_DE.ISO-8859-1 \
>    de_DE.UTF-8 \
>    en_US.ISO-8859-1 \
> +  hi_IN.UTF-8 \
>    ja_JP.EUC-JP \
>    ps_AF.UTF-8 \
>    # LOCALES

The tests is not enabled since there is no entry in tests.

> diff --git a/stdio-common/tst-printf-width-i18n.c b/stdio-common/tst-printf-width-i18n.c
> new file mode 100644
> index 0000000000..2355f30d67
> --- /dev/null
> +++ b/stdio-common/tst-printf-width-i18n.c
> @@ -0,0 +1,95 @@
> +/* Test for width of non-ASCII digit sequences.
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +/* Behavior is currently inconsistent between %d and %f (bug 28943,
> +   bug 28944).  This test intends to capture the status quo.  */
> +
> +#include <monetary.h>
> +#include <stdio.h>
> +#include <support/support.h>
> +#include <support/check.h>
> +
> +static int
> +do_test (void)
> +{
> +  char buf[40];
> +
> +  xsetlocale (LC_ALL, "hi_IN.UTF-8");
> +
> +  /* Ungrouped, not translated.  */
> +  TEST_COMPARE (sprintf (buf, "%7d", 12345), 7);
> +  TEST_COMPARE_STRING (buf, "  12345");
> +  TEST_COMPARE (sprintf (buf, "%10.2f", 12345.67), 10);
> +  TEST_COMPARE_STRING (buf, "  12345.67");
> +  TEST_COMPARE (strfmon (buf, sizeof (buf), "%^13i", 12345.67), 13);
> +  TEST_COMPARE_STRING (buf, "  INR12345.67");
> +
> +  /* Grouped.  */
> +  TEST_COMPARE (sprintf (buf, "%'8d", 12345), 8);
> +  TEST_COMPARE_STRING (buf, "  12,345");
> +  TEST_COMPARE (sprintf (buf, "%'11.2f", 12345.67), 11);
> +  TEST_COMPARE_STRING (buf, "  12,345.67");
> +  TEST_COMPARE (strfmon (buf, sizeof (buf), "%13i", 12345.67), 13);
> +  TEST_COMPARE_STRING (buf, " INR12,345.67");
> +
> +  /* Translated.  */
> +  TEST_COMPARE (sprintf (buf, "%I16d", 12345), 16);
> +  TEST_COMPARE_STRING (buf, " १२३४५");
> +  TEST_COMPARE (sprintf (buf, "%I12.2f", 12345.67), 26);
> +  TEST_COMPARE_STRING (buf, "    १२३४५.६७");
> +
> +  /* Translated and grouped.  */
> +  TEST_COMPARE (sprintf (buf, "%'I17d", 12345), 17);
> +  TEST_COMPARE_STRING (buf, " १२,३४५");
> +  TEST_COMPARE (sprintf (buf, "%'I12.2f", 12345.67), 26);
> +  TEST_COMPARE_STRING (buf, "   १२,३४५.६७");
> +
> +  xsetlocale (LC_ALL, "ps_AF.UTF-8");
> +
> +  /* Ungrouped, not translated.  */
> +  TEST_COMPARE (sprintf (buf, "%7d", 12345), 7);
> +  TEST_COMPARE_STRING (buf, "  12345");
> +  TEST_COMPARE (sprintf (buf, "%10.2f", 12345.67), 11);
> +  TEST_COMPARE_STRING (buf, "  12345٫67");
> +  TEST_COMPARE (strfmon (buf, sizeof (buf), "%^13i", 12345.67), 13);
> +  TEST_COMPARE_STRING (buf, "    12346 AFN");
> +
> +  /* Grouped.  */
> +  TEST_COMPARE (sprintf (buf, "%'8d", 12345), 8);
> +  TEST_COMPARE_STRING (buf, " 12٬345");
> +  TEST_COMPARE (sprintf (buf, "%'11.2f", 12345.67), 13);
> +  TEST_COMPARE_STRING (buf, "  12٬345٫67"); /* Counts characters.  */
> +  TEST_COMPARE (strfmon (buf, sizeof (buf), "%13i", 12345.67), 13);
> +  TEST_COMPARE_STRING (buf, "  12٬346 AFN"); /* Counts bytes.   */
> +
> +  /* Translated.  */
> +  TEST_COMPARE (sprintf (buf, "%I11d", 12345), 11);
> +  TEST_COMPARE_STRING (buf, " ١٢٣۴٥");
> +  TEST_COMPARE (sprintf (buf, "%I12.2f", 12345.67), 20);
> +  TEST_COMPARE_STRING (buf, "    ١٢٣۴٥٫٦٧");
> +
> +  /* Translated and grouped.  */
> +  TEST_COMPARE (sprintf (buf, "%'I13d", 12345), 13);
> +  TEST_COMPARE_STRING (buf, " ١٢٬٣۴٥");
> +  TEST_COMPARE (sprintf (buf, "%'I12.2f", 12345.67), 21);
> +  TEST_COMPARE_STRING (buf, "   ١٢٬٣۴٥٫٦٧");
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>

Ok.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 05/26] vfprintf: Move argument processing into vfprintf-process-arg.c
  2022-03-17 19:28 ` [PATCH 05/26] vfprintf: Move argument processing into vfprintf-process-arg.c Florian Weimer
@ 2022-05-20 13:28   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 13:28 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:28, Florian Weimer via Libc-alpha wrote:
> This simplies formatting and helps with debugging.  It also allows
> the use of localized COMPILE_WPRINTF preprocessor conditionals.

LGTM, this no an usual way to decompose the function, but I think trying to
make it as an functions will also likely result in way more refactoring.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  stdio-common/vfprintf-internal.c    | 501 +--------------------------
>  stdio-common/vfprintf-process-arg.c | 515 ++++++++++++++++++++++++++++
>  2 files changed, 517 insertions(+), 499 deletions(-)
>  create mode 100644 stdio-common/vfprintf-process-arg.c
> 
> diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
> index 59bd76c890..1986c4bdb5 100644
> --- a/stdio-common/vfprintf-internal.c
> +++ b/stdio-common/vfprintf-internal.c
> @@ -656,501 +656,6 @@ static const uint8_t jump_table[] =
>        REF (form_binary),	/* for 'B', 'b' */			      \
>      }
>  
> -/* Before invoking this macro, process_arg_int etc. macros have to be
> -   defined to extract one argument of the appropriate type.  */
> -#define process_arg()						              \
> -      /* Start real work.  We know about all flags and modifiers and	      \
> -	 now process the wanted format specifier.  */			      \
> -    LABEL (form_percent):						      \
> -      /* Write a literal "%".  */					      \
> -      outchar (L_('%'));						      \
> -      break;								      \
> -									      \
> -    LABEL (form_integer):						      \
> -      /* Signed decimal integer.  */					      \
> -      base = 10;							      \
> -									      \
> -      if (is_longlong)							      \
> -	{								      \
> -	  long long int signed_number = process_arg_long_long_int ();	      \
> -	  is_negative = signed_number < 0;				      \
> -	  number.longlong = is_negative ? (- signed_number) : signed_number;  \
> -									      \
> -	  goto LABEL (longlong_number);					      \
> -	}								      \
> -      else								      \
> -	{								      \
> -	  long int signed_number;					      \
> -	  if (is_long_num)						      \
> -	    signed_number = process_arg_long_int ();			      \
> -	  else if (is_char)						      \
> -	    signed_number = (signed char) process_arg_unsigned_int ();	      \
> -	  else if (!is_short)						      \
> -	    signed_number = process_arg_int ();				      \
> -	  else								      \
> -	    signed_number = (short int) process_arg_unsigned_int ();	      \
> -									      \
> -	  is_negative = signed_number < 0;				      \
> -	  number.word = is_negative ? (- signed_number) : signed_number;      \
> -									      \
> -	  goto LABEL (number);						      \
> -	}								      \
> -      /* NOTREACHED */							      \
> -									      \
> -    LABEL (form_unsigned):						      \
> -      /* Unsigned decimal integer.  */					      \
> -      base = 10;							      \
> -      goto LABEL (unsigned_number);					      \
> -      /* NOTREACHED */							      \
> -									      \
> -    LABEL (form_octal):							      \
> -      /* Unsigned octal integer.  */					      \
> -      base = 8;								      \
> -      goto LABEL (unsigned_number);					      \
> -      /* NOTREACHED */							      \
> -									      \
> -    LABEL (form_hexa):							      \
> -      /* Unsigned hexadecimal integer.  */				      \
> -      base = 16;							      \
> -      goto LABEL (unsigned_number);					      \
> -      /* NOTREACHED */							      \
> -									      \
> -    LABEL (form_binary):						      \
> -      /* Unsigned binary integer.  */					      \
> -      base = 2;								      \
> -      goto LABEL (unsigned_number);					      \
> -      /* NOTREACHED */							      \
> -									      \
> -    LABEL (unsigned_number):	  /* Unsigned number of base BASE.  */	      \
> -									      \
> -      /* ISO specifies the `+' and ` ' flags only for signed		      \
> -	 conversions.  */						      \
> -      is_negative = 0;							      \
> -      showsign = 0;							      \
> -      space = 0;							      \
> -									      \
> -      if (is_longlong)							      \
> -	{								      \
> -	  number.longlong = process_arg_unsigned_long_long_int ();	      \
> -									      \
> -	LABEL (longlong_number):					      \
> -	  if (prec < 0)							      \
> -	    /* Supply a default precision if none was given.  */	      \
> -	    prec = 1;							      \
> -	  else								      \
> -	    /* We have to take care for the '0' flag.  If a precision	      \
> -	       is given it must be ignored.  */				      \
> -	    pad = L_(' ');						      \
> -									      \
> -	  /* If the precision is 0 and the number is 0 nothing has to	      \
> -	     be written for the number, except for the 'o' format in	      \
> -	     alternate form.  */					      \
> -	  if (prec == 0 && number.longlong == 0)			      \
> -	    {								      \
> -	      string = workend;						      \
> -	      if (base == 8 && alt)					      \
> -		*--string = L_('0');					      \
> -	    }								      \
> -	  else								      \
> -	    {								      \
> -	      /* Put the number in WORK.  */				      \
> -	      string = _itoa (number.longlong, workend, base,		      \
> -			      spec == L_('X'));				      \
> -	      if (group && grouping)					      \
> -		string = group_number (work_buffer, string, workend,	      \
> -				       grouping, thousands_sep);	      \
> -	      if (use_outdigits && base == 10)				      \
> -		string = _i18n_number_rewrite (string, workend, workend);     \
> -	    }								      \
> -	  /* Simplify further test for num != 0.  */			      \
> -	  number.word = number.longlong != 0;				      \
> -	}								      \
> -      else								      \
> -	{								      \
> -	  if (is_long_num)						      \
> -	    number.word = process_arg_unsigned_long_int ();		      \
> -	  else if (is_char)						      \
> -	    number.word = (unsigned char) process_arg_unsigned_int ();	      \
> -	  else if (!is_short)						      \
> -	    number.word = process_arg_unsigned_int ();			      \
> -	  else								      \
> -	    number.word = (unsigned short int) process_arg_unsigned_int ();   \
> -									      \
> -	LABEL (number):							      \
> -	  if (prec < 0)							      \
> -	    /* Supply a default precision if none was given.  */	      \
> -	    prec = 1;							      \
> -	  else								      \
> -	    /* We have to take care for the '0' flag.  If a precision	      \
> -	       is given it must be ignored.  */				      \
> -	    pad = L_(' ');						      \
> -									      \
> -	  /* If the precision is 0 and the number is 0 nothing has to	      \
> -	     be written for the number, except for the 'o' format in	      \
> -	     alternate form.  */					      \
> -	  if (prec == 0 && number.word == 0)				      \
> -	    {								      \
> -	      string = workend;						      \
> -	      if (base == 8 && alt)					      \
> -		*--string = L_('0');					      \
> -	    }								      \
> -	  else								      \
> -	    {								      \
> -	      /* Put the number in WORK.  */				      \
> -	      string = _itoa_word (number.word, workend, base,		      \
> -				   spec == L_('X'));			      \
> -	      if (group && grouping)					      \
> -		string = group_number (work_buffer, string, workend,	      \
> -				       grouping, thousands_sep);	      \
> -	      if (use_outdigits && base == 10)				      \
> -		string = _i18n_number_rewrite (string, workend, workend);     \
> -	    }								      \
> -	}								      \
> -									      \
> -      if (prec <= workend - string && number.word != 0 && alt && base == 8)   \
> -	/* Add octal marker.  */					      \
> -	*--string = L_('0');						      \
> -									      \
> -      prec = MAX (0, prec - (workend - string));			      \
> -									      \
> -      if (!left)							      \
> -	{								      \
> -	  width -= workend - string + prec;				      \
> -									      \
> -	  if (number.word != 0 && alt && (base == 16 || base == 2))	      \
> -	    /* Account for 0X, 0x, 0B or 0b hex or binary marker.  */	      \
> -	    width -= 2;							      \
> -									      \
> -	  if (is_negative || showsign || space)				      \
> -	    --width;							      \
> -									      \
> -	  if (pad == L_(' '))						      \
> -	    {								      \
> -	      PAD (L_(' '));						      \
> -	      width = 0;						      \
> -	    }								      \
> -									      \
> -	  if (is_negative)						      \
> -	    outchar (L_('-'));						      \
> -	  else if (showsign)						      \
> -	    outchar (L_('+'));						      \
> -	  else if (space)						      \
> -	    outchar (L_(' '));						      \
> -									      \
> -	  if (number.word != 0 && alt && (base == 16 || base == 2))	      \
> -	    {								      \
> -	      outchar (L_('0'));					      \
> -	      outchar (spec);						      \
> -	    }								      \
> -									      \
> -	  width += prec;						      \
> -	  PAD (L_('0'));						      \
> -									      \
> -	  outstring (string, workend - string);				      \
> -									      \
> -	  break;							      \
> -	}								      \
> -      else								      \
> -	{								      \
> -	  if (is_negative)						      \
> -	    {								      \
> -	      outchar (L_('-'));					      \
> -	      --width;							      \
> -	    }								      \
> -	  else if (showsign)						      \
> -	    {								      \
> -	      outchar (L_('+'));					      \
> -	      --width;							      \
> -	    }								      \
> -	  else if (space)						      \
> -	    {								      \
> -	      outchar (L_(' '));					      \
> -	      --width;							      \
> -	    }								      \
> -									      \
> -	  if (number.word != 0 && alt && (base == 16 || base == 2))	      \
> -	    {								      \
> -	      outchar (L_('0'));					      \
> -	      outchar (spec);						      \
> -	      width -= 2;						      \
> -	    }								      \
> -									      \
> -	  width -= workend - string + prec;				      \
> -									      \
> -	  if (prec > 0)							      \
> -	    {								      \
> -	      int temp = width;						      \
> -	      width = prec;						      \
> -	      PAD (L_('0'));						      \
> -	      width = temp;						      \
> -	    }								      \
> -									      \
> -	  outstring (string, workend - string);				      \
> -									      \
> -	  PAD (L_(' '));						      \
> -	  break;							      \
> -	}								      \
> -									      \
> -    LABEL (form_pointer):						      \
> -      /* Generic pointer.  */						      \
> -      {									      \
> -	const void *ptr = process_arg_pointer ();			      \
> -	if (ptr != NULL)						      \
> -	  {								      \
> -	    /* If the pointer is not NULL, write it as a %#x spec.  */	      \
> -	    base = 16;							      \
> -	    number.word = (unsigned long int) ptr;			      \
> -	    is_negative = 0;						      \
> -	    alt = 1;							      \
> -	    group = 0;							      \
> -	    spec = L_('x');						      \
> -	    goto LABEL (number);					      \
> -	  }								      \
> -	else								      \
> -	  {								      \
> -	    /* Write "(nil)" for a nil pointer.  */			      \
> -	    string = (CHAR_T *) L_("(nil)");				      \
> -	    /* Make sure the full string "(nil)" is printed.  */	      \
> -	    if (prec < 5)						      \
> -	      prec = 5;							      \
> -	    /* This is a wide string iff compiling wprintf.  */		      \
> -	    is_long = sizeof (CHAR_T) > 1;				      \
> -	    goto LABEL (print_string);					      \
> -	  }								      \
> -      }									      \
> -      /* NOTREACHED */							      \
> -									      \
> -    LABEL (form_number):						      \
> -      if ((mode_flags & PRINTF_FORTIFY) != 0)				      \
> -	{								      \
> -	  if (! readonly_format)					      \
> -	    {								      \
> -	      extern int __readonly_area (const void *, size_t)		      \
> -		attribute_hidden;					      \
> -	      readonly_format						      \
> -		= __readonly_area (format, ((STR_LEN (format) + 1)	      \
> -					    * sizeof (CHAR_T)));	      \
> -	    }								      \
> -	  if (readonly_format < 0)					      \
> -	    __libc_fatal ("*** %n in writable segment detected ***\n");	      \
> -	}								      \
> -      /* Answer the count of characters written.  */			      \
> -      void *ptrptr = process_arg_pointer ();				      \
> -      if (is_longlong)							      \
> -	*(long long int *) ptrptr = done;				      \
> -      else if (is_long_num)						      \
> -	*(long int *) ptrptr = done;					      \
> -      else if (is_char)							      \
> -	*(char *) ptrptr = done;					      \
> -      else if (!is_short)						      \
> -	*(int *) ptrptr = done;						      \
> -      else								      \
> -	*(short int *) ptrptr = done;					      \
> -      break;								      \
> -									      \
> -    LABEL (form_strerror):						      \
> -      /* Print description of error ERRNO.  */				      \
> -      if (alt)								      \
> -	string = (CHAR_T *) __get_errname (save_errno);			      \
> -      else								      \
> -	string = (CHAR_T *) __strerror_r (save_errno, (char *) work_buffer,   \
> -					  WORK_BUFFER_SIZE * sizeof (CHAR_T));\
> -      if (string == NULL)						\
> -	{								      \
> -          /* Print as a decimal number. */				      \
> -          base = 10;							      \
> -	  is_negative = save_errno < 0;					      \
> -	  number.word = save_errno;					      \
> -	  if (is_negative)						      \
> -	    number.word = -number.word;					      \
> -	  goto LABEL (number);						      \
> -	}								      \
> -      else								      \
> -	{								      \
> -	  is_long = 0;	/* This is no wide-char string.  */		      \
> -	  goto LABEL (print_string);					      \
> -	}
> -
> -#ifdef COMPILE_WPRINTF
> -# define process_string_arg()						      \
> -    LABEL (form_character):						      \
> -      /* Character.  */							      \
> -      if (is_long)							      \
> -	goto LABEL (form_wcharacter);					      \
> -      --width;	/* Account for the character itself.  */		      \
> -      if (!left)							      \
> -	PAD (L' ');							      \
> -      outchar (__btowc ((unsigned char) process_arg_int ())); /* Promoted. */ \
> -      if (left)								      \
> -	PAD (L' ');							      \
> -      break;								      \
> -									      \
> -    LABEL (form_wcharacter):						      \
> -      {									      \
> -	/* Wide character.  */						      \
> -	--width;							      \
> -	if (!left)							      \
> -	  PAD (L' ');							      \
> -	outchar (process_arg_wchar_t ());				      \
> -	if (left)							      \
> -	  PAD (L' ');							      \
> -      }									      \
> -      break;								      \
> -									      \
> -    LABEL (form_string):						      \
> -      {									      \
> -	size_t len;							      \
> -									      \
> -	/* The string argument could in fact be `char *' or `wchar_t *'.      \
> -	   But this should not make a difference here.  */		      \
> -	string = (CHAR_T *) process_arg_wstring ();			      \
> -									      \
> -	/* Entry point for printing other strings.  */			      \
> -      LABEL (print_string):						      \
> -									      \
> -	if (string == NULL)						      \
> -	  {								      \
> -	    /* Write "(null)" if there's space.  */			      \
> -	    if (prec == -1 || prec >= (int) array_length (null) - 1)          \
> -	      {								      \
> -		string = (CHAR_T *) null;				      \
> -		len = array_length (null) - 1;				      \
> -	      }								      \
> -	    else							      \
> -	      {								      \
> -		string = (CHAR_T *) L"";				      \
> -		len = 0;						      \
> -	      }								      \
> -	  }								      \
> -	else if (!is_long && spec != L_('S'))				      \
> -	  {								      \
> -	    done = outstring_converted_wide_string			      \
> -	      (s, (const char *) string, prec, width, left, done);	      \
> -	    if (done < 0)						      \
> -	      goto all_done;						      \
> -	    /* The padding has already been written.  */		      \
> -	    break;							      \
> -	  }								      \
> -	else								      \
> -	  {								      \
> -	    if (prec != -1)						      \
> -	      /* Search for the end of the string, but don't search past      \
> -		 the length specified by the precision.  */		      \
> -	      len = __wcsnlen (string, prec);				      \
> -	    else							      \
> -	      len = __wcslen (string);					      \
> -	  }								      \
> -									      \
> -	if ((width -= len) < 0)						      \
> -	  {								      \
> -	    outstring (string, len);					      \
> -	    break;							      \
> -	  }								      \
> -									      \
> -	if (!left)							      \
> -	  PAD (L' ');							      \
> -	outstring (string, len);					      \
> -	if (left)							      \
> -	  PAD (L' ');							      \
> -      }									      \
> -      break;
> -#else
> -# define process_string_arg()						      \
> -    LABEL (form_character):						      \
> -      /* Character.  */							      \
> -      if (is_long)							      \
> -	goto LABEL (form_wcharacter);					      \
> -      --width;	/* Account for the character itself.  */		      \
> -      if (!left)							      \
> -	PAD (' ');							      \
> -      outchar ((unsigned char) process_arg_int ()); /* Promoted.  */	      \
> -      if (left)								      \
> -	PAD (' ');							      \
> -      break;								      \
> -									      \
> -    LABEL (form_wcharacter):						      \
> -      {									      \
> -	/* Wide character.  */						      \
> -	char buf[MB_LEN_MAX];						      \
> -	mbstate_t mbstate;						      \
> -	size_t len;							      \
> -									      \
> -	memset (&mbstate, '\0', sizeof (mbstate_t));			      \
> -	len = __wcrtomb (buf, process_arg_wchar_t (), &mbstate);	      \
> -	if (len == (size_t) -1)						      \
> -	  {								      \
> -	    /* Something went wrong during the conversion.  Bail out.  */     \
> -	    done = -1;							      \
> -	    goto all_done;						      \
> -	  }								      \
> -	width -= len;							      \
> -	if (!left)							      \
> -	  PAD (' ');							      \
> -	outstring (buf, len);						      \
> -	if (left)							      \
> -	  PAD (' ');							      \
> -      }									      \
> -      break;								      \
> -									      \
> -    LABEL (form_string):						      \
> -      {									      \
> -	size_t len;							      \
> -									      \
> -	/* The string argument could in fact be `char *' or `wchar_t *'.      \
> -	   But this should not make a difference here.  */		      \
> -	string = (char *) process_arg_string ();			      \
> -									      \
> -	/* Entry point for printing other strings.  */			      \
> -      LABEL (print_string):						      \
> -									      \
> -	if (string == NULL)						      \
> -	  {								      \
> -	    /* Write "(null)" if there's space.  */			      \
> -	    if (prec == -1 || prec >= (int) sizeof (null) - 1)		      \
> -	      {								      \
> -		string = (char *) null;					      \
> -		len = sizeof (null) - 1;				      \
> -	      }								      \
> -	    else							      \
> -	      {								      \
> -		string = (char *) "";					      \
> -		len = 0;						      \
> -	      }								      \
> -	  }								      \
> -	else if (!is_long && spec != L_('S'))				      \
> -	  {								      \
> -	    if (prec != -1)						      \
> -	      /* Search for the end of the string, but don't search past      \
> -		 the length (in bytes) specified by the precision.  */	      \
> -	      len = __strnlen (string, prec);				      \
> -	    else							      \
> -	      len = strlen (string);					      \
> -	  }								      \
> -	else								      \
> -	  {								      \
> -	    done = outstring_converted_wide_string			      \
> -	      (s, (const wchar_t *) string, prec, width, left, done);	      \
> -	    if (done < 0)						      \
> -	      goto all_done;						      \
> -	    /* The padding has already been written.  */		      \
> -	    break;							      \
> -	  }								      \
> -									      \
> -	if ((width -= len) < 0)						      \
> -	  {								      \
> -	    outstring (string, len);					      \
> -	    break;							      \
> -	  }								      \
> -									      \
> -	if (!left)							      \
> -	  PAD (' ');							      \
> -	outstring (string, len);					      \
> -	if (left)							      \
> -	  PAD (' ');							      \
> -      }									      \
> -      break;
> -#endif
> -
>  /* Helper function to provide temporary buffering for unbuffered streams.  */
>  static int buffered_vfprintf (FILE *stream, const CHAR_T *fmt, va_list,
>  			      unsigned int)
> @@ -1513,8 +1018,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
>  #define process_arg_unsigned_long_long_int() va_arg (ap, unsigned long long int)
>  #define process_arg_wchar_t() va_arg (ap, wchar_t)
>  #define process_arg_wstring() va_arg (ap, const wchar_t *)
> -	  process_arg ();
> -	  process_string_arg ();
> +#include "vfprintf-process-arg.c"
>  #undef process_arg_int
>  #undef process_arg_long_int
>  #undef process_arg_long_long_int
> @@ -1923,8 +1427,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
>  #define process_arg_unsigned_long_long_int() process_arg_data.pa_u_long_long_int
>  #define process_arg_wchar_t() process_arg_data.pa_wchar
>  #define process_arg_wstring() process_arg_data.pa_wstring
> -	  process_arg ();
> -	  process_string_arg ();
> +#include "vfprintf-process-arg.c"
>  #undef process_arg_data
>  #undef process_arg_int
>  #undef process_arg_long_int
> diff --git a/stdio-common/vfprintf-process-arg.c b/stdio-common/vfprintf-process-arg.c
> new file mode 100644
> index 0000000000..a28afce7de
> --- /dev/null
> +++ b/stdio-common/vfprintf-process-arg.c
> @@ -0,0 +1,515 @@
> +/* Argument-processing fragment for vfprintf.
> +   Copyright (C) 1991-2022 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +/* This file is included twice from vfprintf-internal.c, for standard
> +   and GNU-style positional (%N$) arguments.  Before that,
> +   process_arg_int etc. macros have to be defined to extract one
> +   argument of the appropriate type, in addition to the file-specific
> +   macros in vfprintf-internal.c.  */
> +
> +{
> +  /* Start real work.  We know about all flags and modifiers and
> +     now process the wanted format specifier.  */
> +LABEL (form_percent):
> +  /* Write a literal "%".  */
> +  outchar (L_('%'));
> +  break;
> +
> +LABEL (form_integer):
> +  /* Signed decimal integer.  */
> +  base = 10;
> +
> +  if (is_longlong)
> +    {
> +      long long int signed_number = process_arg_long_long_int ();
> +      is_negative = signed_number < 0;
> +      number.longlong = is_negative ? (- signed_number) : signed_number;
> +
> +      goto LABEL (longlong_number);
> +    }
> +  else
> +    {
> +      long int signed_number;
> +      if (is_long_num)
> +        signed_number = process_arg_long_int ();
> +      else if (is_char)
> +        signed_number = (signed char) process_arg_unsigned_int ();
> +      else if (!is_short)
> +        signed_number = process_arg_int ();
> +      else
> +        signed_number = (short int) process_arg_unsigned_int ();
> +
> +      is_negative = signed_number < 0;
> +      number.word = is_negative ? (- signed_number) : signed_number;
> +
> +      goto LABEL (number);
> +    }
> +  /* NOTREACHED */
> +
> +LABEL (form_unsigned):
> +  /* Unsigned decimal integer.  */
> +  base = 10;
> +  goto LABEL (unsigned_number);
> +  /* NOTREACHED */
> +
> +LABEL (form_octal):
> +  /* Unsigned octal integer.  */
> +  base = 8;
> +  goto LABEL (unsigned_number);
> +  /* NOTREACHED */
> +
> +LABEL (form_hexa):
> +  /* Unsigned hexadecimal integer.  */
> +  base = 16;
> +  goto LABEL (unsigned_number);
> +  /* NOTREACHED */
> +
> +LABEL (form_binary):
> +  /* Unsigned binary integer.  */
> +  base = 2;
> +  goto LABEL (unsigned_number);
> +  /* NOTREACHED */
> +
> +LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
> +
> +  /* ISO specifies the `+' and ` ' flags only for signed
> +     conversions.  */
> +  is_negative = 0;
> +  showsign = 0;
> +  space = 0;
> +
> +  if (is_longlong)
> +    {
> +      number.longlong = process_arg_unsigned_long_long_int ();
> +
> +      LABEL (longlong_number):
> +      if (prec < 0)
> +        /* Supply a default precision if none was given.  */
> +        prec = 1;
> +      else
> +        /* We have to take care for the '0' flag.  If a precision
> +           is given it must be ignored.  */
> +        pad = L_(' ');
> +
> +      /* If the precision is 0 and the number is 0 nothing has to
> +         be written for the number, except for the 'o' format in
> +         alternate form.  */
> +      if (prec == 0 && number.longlong == 0)
> +        {
> +          string = workend;
> +          if (base == 8 && alt)
> +            *--string = L_('0');
> +        }
> +      else
> +        {
> +          /* Put the number in WORK.  */
> +          string = _itoa (number.longlong, workend, base,
> +                          spec == L_('X'));
> +          if (group && grouping)
> +            string = group_number (work_buffer, string, workend,
> +                                   grouping, thousands_sep);
> +          if (use_outdigits && base == 10)
> +            string = _i18n_number_rewrite (string, workend, workend);
> +        }
> +      /* Simplify further test for num != 0.  */
> +      number.word = number.longlong != 0;
> +    }
> +  else
> +    {
> +      if (is_long_num)
> +        number.word = process_arg_unsigned_long_int ();
> +      else if (is_char)
> +        number.word = (unsigned char) process_arg_unsigned_int ();
> +      else if (!is_short)
> +        number.word = process_arg_unsigned_int ();
> +      else
> +        number.word = (unsigned short int) process_arg_unsigned_int ();
> +
> +      LABEL (number):
> +      if (prec < 0)
> +        /* Supply a default precision if none was given.  */
> +        prec = 1;
> +      else
> +        /* We have to take care for the '0' flag.  If a precision
> +           is given it must be ignored.  */
> +        pad = L_(' ');
> +
> +      /* If the precision is 0 and the number is 0 nothing has to
> +         be written for the number, except for the 'o' format in
> +         alternate form.  */
> +      if (prec == 0 && number.word == 0)
> +        {
> +          string = workend;
> +          if (base == 8 && alt)
> +            *--string = L_('0');
> +        }
> +      else
> +        {
> +          /* Put the number in WORK.  */
> +          string = _itoa_word (number.word, workend, base,
> +                               spec == L_('X'));
> +          if (group && grouping)
> +            string = group_number (work_buffer, string, workend,
> +                                   grouping, thousands_sep);
> +          if (use_outdigits && base == 10)
> +            string = _i18n_number_rewrite (string, workend, workend);
> +        }
> +    }
> +
> +  if (prec <= workend - string && number.word != 0 && alt && base == 8)
> +    /* Add octal marker.  */
> +    *--string = L_('0');
> +
> +  prec = MAX (0, prec - (workend - string));
> +
> +  if (!left)
> +    {
> +      width -= workend - string + prec;
> +
> +      if (number.word != 0 && alt && (base == 16 || base == 2))
> +        /* Account for 0X, 0x, 0B or 0b hex or binary marker.  */
> +        width -= 2;
> +
> +      if (is_negative || showsign || space)
> +        --width;
> +
> +      if (pad == L_(' '))
> +        {
> +          PAD (L_(' '));
> +          width = 0;
> +        }
> +
> +      if (is_negative)
> +        outchar (L_('-'));
> +      else if (showsign)
> +        outchar (L_('+'));
> +      else if (space)
> +        outchar (L_(' '));
> +
> +      if (number.word != 0 && alt && (base == 16 || base == 2))
> +        {
> +          outchar (L_('0'));
> +          outchar (spec);
> +        }
> +
> +      width += prec;
> +      PAD (L_('0'));
> +
> +      outstring (string, workend - string);
> +
> +      break;
> +    }
> +  else
> +    {
> +      if (is_negative)
> +        {
> +          outchar (L_('-'));
> +          --width;
> +        }
> +      else if (showsign)
> +        {
> +          outchar (L_('+'));
> +          --width;
> +        }
> +      else if (space)
> +        {
> +          outchar (L_(' '));
> +          --width;
> +        }
> +
> +      if (number.word != 0 && alt && (base == 16 || base == 2))
> +        {
> +          outchar (L_('0'));
> +          outchar (spec);
> +          width -= 2;
> +        }
> +
> +      width -= workend - string + prec;
> +
> +      if (prec > 0)
> +        {
> +          int temp = width;
> +          width = prec;
> +          PAD (L_('0'));
> +          width = temp;
> +        }
> +
> +      outstring (string, workend - string);
> +
> +      PAD (L_(' '));
> +      break;
> +    }
> +
> +LABEL (form_pointer):
> +  /* Generic pointer.  */
> +  {
> +    const void *ptr = process_arg_pointer ();
> +    if (ptr != NULL)
> +      {
> +        /* If the pointer is not NULL, write it as a %#x spec.  */
> +        base = 16;
> +        number.word = (unsigned long int) ptr;
> +        is_negative = 0;
> +        alt = 1;
> +        group = 0;
> +        spec = L_('x');
> +        goto LABEL (number);
> +      }
> +    else
> +      {
> +        /* Write "(nil)" for a nil pointer.  */
> +        string = (CHAR_T *) L_("(nil)");
> +        /* Make sure the full string "(nil)" is printed.  */
> +        if (prec < 5)
> +          prec = 5;
> +        /* This is a wide string iff compiling wprintf.  */
> +        is_long = sizeof (CHAR_T) > 1;
> +        goto LABEL (print_string);
> +      }
> +  }
> +  /* NOTREACHED */
> +
> +LABEL (form_number):
> +  if ((mode_flags & PRINTF_FORTIFY) != 0)
> +    {
> +      if (! readonly_format)
> +        {
> +          extern int __readonly_area (const void *, size_t)
> +            attribute_hidden;
> +          readonly_format
> +            = __readonly_area (format, ((STR_LEN (format) + 1)
> +                                        * sizeof (CHAR_T)));
> +        }
> +      if (readonly_format < 0)
> +        __libc_fatal ("*** %n in writable segment detected ***\n");
> +    }
> +  /* Answer the count of characters written.  */
> +  void *ptrptr = process_arg_pointer ();
> +  if (is_longlong)
> +    *(long long int *) ptrptr = done;
> +  else if (is_long_num)
> +    *(long int *) ptrptr = done;
> +  else if (is_char)
> +    *(char *) ptrptr = done;
> +  else if (!is_short)
> +    *(int *) ptrptr = done;
> +  else
> +    *(short int *) ptrptr = done;
> +  break;
> +
> +LABEL (form_strerror):
> +  /* Print description of error ERRNO.  */
> +  if (alt)
> +    string = (CHAR_T *) __get_errname (save_errno);
> +  else
> +    string = (CHAR_T *) __strerror_r (save_errno, (char *) work_buffer,
> +                                      WORK_BUFFER_SIZE * sizeof (CHAR_T));
> +  if (string == NULL)
> +    {
> +      /* Print as a decimal number. */
> +      base = 10;
> +      is_negative = save_errno < 0;
> +      number.word = save_errno;
> +      if (is_negative)
> +        number.word = -number.word;
> +      goto LABEL (number);
> +    }
> +  else
> +    {
> +      is_long = 0;  /* This is no wide-char string.  */
> +      goto LABEL (print_string);
> +    }
> +
> +#ifdef COMPILE_WPRINTF
> +LABEL (form_character):
> +  /* Character.  */
> +  if (is_long)
> +    goto LABEL (form_wcharacter);
> +  --width;  /* Account for the character itself.  */
> +  if (!left)
> +    PAD (L' ');
> +  outchar (__btowc ((unsigned char) process_arg_int ())); /* Promoted. */
> +  if (left)
> +    PAD (L' ');
> +  break;
> +
> +LABEL (form_wcharacter):
> +  {
> +    /* Wide character.  */
> +    --width;
> +    if (!left)
> +      PAD (L' ');
> +    outchar (process_arg_wchar_t ());
> +    if (left)
> +      PAD (L' ');
> +  }
> +  break;
> +
> +LABEL (form_string):
> +  {
> +    size_t len;
> +
> +    /* The string argument could in fact be `char *' or `wchar_t *'.
> +       But this should not make a difference here.  */
> +    string = (CHAR_T *) process_arg_wstring ();
> +
> +    /* Entry point for printing other strings.  */
> +    LABEL (print_string):
> +
> +    if (string == NULL)
> +      {
> +        /* Write "(null)" if there's space.  */
> +        if (prec == -1 || prec >= (int) array_length (null) - 1)
> +          {
> +            string = (CHAR_T *) null;
> +            len = array_length (null) - 1;
> +          }
> +        else
> +          {
> +            string = (CHAR_T *) L"";
> +            len = 0;
> +          }
> +      }
> +    else if (!is_long && spec != L_('S'))
> +      {
> +        done = outstring_converted_wide_string
> +          (s, (const char *) string, prec, width, left, done);
> +        if (done < 0)
> +          goto all_done;
> +        /* The padding has already been written.  */
> +        break;
> +      }
> +    else
> +      {
> +        if (prec != -1)
> +          /* Search for the end of the string, but don't search past
> +             the length specified by the precision.  */
> +          len = __wcsnlen (string, prec);
> +        else
> +          len = __wcslen (string);
> +      }
> +
> +    if ((width -= len) < 0)
> +      {
> +        outstring (string, len);
> +        break;
> +      }
> +
> +    if (!left)
> +      PAD (L' ');
> +    outstring (string, len);
> +    if (left)
> +      PAD (L' ');
> +  }
> +  break;
> +#else /* !COMPILE_WPRINTF */
> +LABEL (form_character):
> +  /* Character.  */
> +  if (is_long)
> +    goto LABEL (form_wcharacter);
> +  --width;  /* Account for the character itself.  */
> +  if (!left)
> +    PAD (' ');
> +  outchar ((unsigned char) process_arg_int ()); /* Promoted.  */
> +  if (left)
> +    PAD (' ');
> +  break;
> +
> +LABEL (form_wcharacter):
> +  {
> +    /* Wide character.  */
> +    char buf[MB_LEN_MAX];
> +    mbstate_t mbstate;
> +    size_t len;
> +
> +    memset (&mbstate, '\0', sizeof (mbstate_t));
> +    len = __wcrtomb (buf, process_arg_wchar_t (), &mbstate);
> +    if (len == (size_t) -1)
> +      {
> +        /* Something went wrong during the conversion.  Bail out.  */
> +        done = -1;
> +        goto all_done;
> +      }
> +    width -= len;
> +    if (!left)
> +      PAD (' ');
> +    outstring (buf, len);
> +    if (left)
> +      PAD (' ');
> +  }
> +  break;
> +
> +LABEL (form_string):
> +  {
> +    size_t len;
> +
> +    /* The string argument could in fact be `char *' or `wchar_t *'.
> +       But this should not make a difference here.  */
> +    string = (char *) process_arg_string ();
> +
> +    /* Entry point for printing other strings.  */
> +    LABEL (print_string):
> +
> +    if (string == NULL)
> +      {
> +        /* Write "(null)" if there's space.  */
> +        if (prec == -1 || prec >= (int) sizeof (null) - 1)
> +          {
> +            string = (char *) null;
> +            len = sizeof (null) - 1;
> +          }
> +        else
> +          {
> +            string = (char *) "";
> +            len = 0;
> +          }
> +      }
> +    else if (!is_long && spec != L_('S'))
> +      {
> +        if (prec != -1)
> +          /* Search for the end of the string, but don't search past
> +             the length (in bytes) specified by the precision.  */
> +          len = __strnlen (string, prec);
> +        else
> +          len = strlen (string);
> +      }
> +    else
> +      {
> +        done = outstring_converted_wide_string
> +          (s, (const wchar_t *) string, prec, width, left, done);
> +        if (done < 0)
> +          goto all_done;
> +        /* The padding has already been written.  */
> +        break;
> +      }
> +
> +    if ((width -= len) < 0)
> +      {
> +        outstring (string, len);
> +        break;
> +      }
> +
> +    if (!left)
> +      PAD (' ');
> +    outstring (string, len);
> +    if (left)
> +      PAD (' ');
> +  }
> +  break;
> +#endif /* !COMPILE_WPRINTF */
> +}

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 04/26] stdio-common: Add tst-printf-width-i18n to cover numeric field width
  2022-05-20 13:22   ` Adhemerval Zanella
@ 2022-05-20 13:33     ` Adhemerval Zanella
  2022-05-23  6:39       ` Florian Weimer
  0 siblings, 1 reply; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 13:33 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 20/05/2022 10:22, Adhemerval Zanella wrote:
> 
> 
> On 17/03/2022 16:28, Florian Weimer via Libc-alpha wrote:
>> Related to bug 28943 and bug 28944.
> 
> LGTM with the fix to enabled the test.
> 
> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
> 
>> ---
>>  stdio-common/Makefile                |  1 +
>>  stdio-common/tst-printf-width-i18n.c | 95 ++++++++++++++++++++++++++++
>>  2 files changed, 96 insertions(+)
>>  create mode 100644 stdio-common/tst-printf-width-i18n.c
>>
>> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
>> index 435cd8904f..f0e65f0dcd 100644
>> --- a/stdio-common/Makefile
>> +++ b/stdio-common/Makefile
>> @@ -250,6 +250,7 @@ LOCALES := \
>>    de_DE.ISO-8859-1 \
>>    de_DE.UTF-8 \
>>    en_US.ISO-8859-1 \
>> +  hi_IN.UTF-8 \
>>    ja_JP.EUC-JP \
>>    ps_AF.UTF-8 \
>>    # LOCALES
> 
> The tests is not enabled since there is no entry in tests.

And you will also need to add a rule to avoid the test fail in parallel builds:

$(objpfx)/tst-printf-width-i18n.out: $(gen-locales)

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 06/26] vfprintf: Consolidate some multibyte/wide character processing
  2022-03-17 19:28 ` [PATCH 06/26] vfprintf: Consolidate some multibyte/wide character processing Florian Weimer
@ 2022-05-20 14:16   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 14:16 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:28, Florian Weimer via Libc-alpha wrote:
> form_character and form_string processing a sufficiently similar
> that the logic can be shared.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  stdio-common/vfprintf-process-arg.c | 130 +++++++++-------------------
>  1 file changed, 43 insertions(+), 87 deletions(-)
> 
> diff --git a/stdio-common/vfprintf-process-arg.c b/stdio-common/vfprintf-process-arg.c
> index a28afce7de..4fe369e111 100644
> --- a/stdio-common/vfprintf-process-arg.c
> +++ b/stdio-common/vfprintf-process-arg.c
> @@ -335,29 +335,20 @@ LABEL (form_strerror):
>        goto LABEL (print_string);
>      }
>  
> -#ifdef COMPILE_WPRINTF
>  LABEL (form_character):
>    /* Character.  */
>    if (is_long)
>      goto LABEL (form_wcharacter);
>    --width;  /* Account for the character itself.  */
>    if (!left)
> -    PAD (L' ');
> +    PAD (L_(' '));
> +#ifdef COMPILE_WPRINTF
>    outchar (__btowc ((unsigned char) process_arg_int ())); /* Promoted. */
> +#else
> +  outchar ((unsigned char) process_arg_int ()); /* Promoted.  */
> +#endif
>    if (left)
> -    PAD (L' ');
> -  break;
> -
> -LABEL (form_wcharacter):
> -  {
> -    /* Wide character.  */
> -    --width;
> -    if (!left)
> -      PAD (L' ');
> -    outchar (process_arg_wchar_t ());
> -    if (left)
> -      PAD (L' ');
> -  }
> +    PAD (L_(' '));
>    break;
>  
>  LABEL (form_string):
> @@ -366,8 +357,11 @@ LABEL (form_string):
>  
>      /* The string argument could in fact be `char *' or `wchar_t *'.
>         But this should not make a difference here.  */
> +#ifdef COMPILE_WPRINTF
>      string = (CHAR_T *) process_arg_wstring ();
> -
> +#else
> +    string = (CHAR_T *) process_arg_string ();
> +#endif
>      /* Entry point for printing other strings.  */
>      LABEL (print_string):
>  
> @@ -387,21 +381,39 @@ LABEL (form_string):
>        }
>      else if (!is_long && spec != L_('S'))
>        {
> +#ifdef COMPILE_WPRINTF
>          done = outstring_converted_wide_string
>            (s, (const char *) string, prec, width, left, done);
>          if (done < 0)
>            goto all_done;
>          /* The padding has already been written.  */
>          break;
> +#else
> +        if (prec != -1)
> +          /* Search for the end of the string, but don't search past
> +             the length (in bytes) specified by the precision.  */
> +          len = __strnlen (string, prec);
> +        else
> +          len = strlen (string);
> +#endif
>        }
>      else
>        {
> +#ifdef COMPILE_WPRINTF
>          if (prec != -1)
>            /* Search for the end of the string, but don't search past
>               the length specified by the precision.  */
>            len = __wcsnlen (string, prec);
>          else
>            len = __wcslen (string);
> +#else
> +        done = outstring_converted_wide_string
> +          (s, (const wchar_t *) string, prec, width, left, done);
> +        if (done < 0)
> +          goto all_done;
> +        /* The padding has already been written.  */
> +        break;
> +#endif
>        }
>  
>      if ((width -= len) < 0)
> @@ -411,25 +423,27 @@ LABEL (form_string):
>        }
>  
>      if (!left)
> -      PAD (L' ');
> +      PAD (L_(' '));
>      outstring (string, len);
>      if (left)
> -      PAD (L' ');
> +      PAD (L_(' '));
>    }
>    break;
> -#else /* !COMPILE_WPRINTF */
> -LABEL (form_character):
> -  /* Character.  */
> -  if (is_long)
> -    goto LABEL (form_wcharacter);
> -  --width;  /* Account for the character itself.  */
> -  if (!left)
> -    PAD (' ');
> -  outchar ((unsigned char) process_arg_int ()); /* Promoted.  */
> -  if (left)
> -    PAD (' ');
> +
> +#ifdef COMPILE_WPRINTF
> +LABEL (form_wcharacter):
> +  {
> +    /* Wide character.  */
> +    --width;
> +    if (!left)
> +      PAD (L' ');
> +    outchar (process_arg_wchar_t ());
> +    if (left)
> +      PAD (L' ');
> +  }
>    break;
>  
> +#else /* !COMPILE_WPRINTF */
>  LABEL (form_wcharacter):
>    {
>      /* Wide character.  */
> @@ -453,63 +467,5 @@ LABEL (form_wcharacter):
>        PAD (' ');
>    }
>    break;
> -
> -LABEL (form_string):
> -  {
> -    size_t len;
> -
> -    /* The string argument could in fact be `char *' or `wchar_t *'.
> -       But this should not make a difference here.  */
> -    string = (char *) process_arg_string ();
> -
> -    /* Entry point for printing other strings.  */
> -    LABEL (print_string):
> -
> -    if (string == NULL)
> -      {
> -        /* Write "(null)" if there's space.  */
> -        if (prec == -1 || prec >= (int) sizeof (null) - 1)
> -          {
> -            string = (char *) null;
> -            len = sizeof (null) - 1;
> -          }
> -        else
> -          {
> -            string = (char *) "";
> -            len = 0;
> -          }
> -      }
> -    else if (!is_long && spec != L_('S'))
> -      {
> -        if (prec != -1)
> -          /* Search for the end of the string, but don't search past
> -             the length (in bytes) specified by the precision.  */
> -          len = __strnlen (string, prec);
> -        else
> -          len = strlen (string);
> -      }
> -    else
> -      {
> -        done = outstring_converted_wide_string
> -          (s, (const wchar_t *) string, prec, width, left, done);
> -        if (done < 0)
> -          goto all_done;
> -        /* The padding has already been written.  */
> -        break;
> -      }
> -
> -    if ((width -= len) < 0)
> -      {
> -        outstring (string, len);
> -        break;
> -      }
> -
> -    if (!left)
> -      PAD (' ');
> -    outstring (string, len);
> -    if (left)
> -      PAD (' ');
> -  }
> -  break;
>  #endif /* !COMPILE_WPRINTF */
>  }

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 07/26] __printf_fphex always uses LC_NUMERIC
  2022-03-17 19:29 ` [PATCH 07/26] __printf_fphex always uses LC_NUMERIC Florian Weimer
@ 2022-05-20 14:21   ` Adhemerval Zanella
  2022-05-23  6:55     ` Florian Weimer
  0 siblings, 1 reply; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 14:21 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:29, Florian Weimer via Libc-alpha wrote:
> There is no hexadecimal currency printing.  strfmon uses
> __printf_fp_l exclusively.

LGTM, some comments below.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  stdio-common/printf_fphex.c | 26 +++++++-------------------
>  1 file changed, 7 insertions(+), 19 deletions(-)
> 
> diff --git a/stdio-common/printf_fphex.c b/stdio-common/printf_fphex.c
> index 3dbbefd972..df11b4a166 100644
> --- a/stdio-common/printf_fphex.c
> +++ b/stdio-common/printf_fphex.c
> @@ -103,9 +103,13 @@ __printf_fphex (FILE *fp,
>      }
>    fpnum;
>  
> -  /* Locale-dependent representation of decimal point.	*/
> -  const char *decimal;
> -  wchar_t decimalwc;
> +  /* Locale-dependent representation of decimal point. Hexadecimal
> +     formatting always using LC_NUMERIC (disregarding info->extra).  */
> +  const char *decimal = _NL_CURRENT (LC_NUMERIC, DECIMAL_POINT);
> +  wchar_t decimalwc = _NL_CURRENT_WORD (LC_NUMERIC, _NL_NUMERIC_DECIMAL_POINT_WC);

Line too long.

> +
> +  /* The decimal point character must never be zero.  */
> +  assert (*decimal != '\0' && decimalwc != L'\0');
>  

Should we assert for info->extra == 0 ?

>    /* "NaN" or "Inf" for the special cases.  */
>    const char *special = NULL;
> @@ -147,22 +151,6 @@ __printf_fphex (FILE *fp,
>    /* Nonzero if this is output on a wide character stream.  */
>    int wide = info->wide;
>  
> -
> -  /* Figure out the decimal point character.  */
> -  if (info->extra == 0)
> -    {
> -      decimal = _NL_CURRENT (LC_NUMERIC, DECIMAL_POINT);
> -      decimalwc = _NL_CURRENT_WORD (LC_NUMERIC, _NL_NUMERIC_DECIMAL_POINT_WC);
> -    }
> -  else
> -    {
> -      decimal = _NL_CURRENT (LC_MONETARY, MON_DECIMAL_POINT);
> -      decimalwc = _NL_CURRENT_WORD (LC_MONETARY,
> -				    _NL_MONETARY_DECIMAL_POINT_WC);
> -    }
> -  /* The decimal point character must never be zero.  */
> -  assert (*decimal != '\0' && decimalwc != L'\0');
> -
>  #define PRINTF_FPHEX_FETCH(FLOAT, VAR)					\
>    {									\
>      (VAR) = *(const FLOAT *) args[0];					\

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 08/26] stdio-common: Add tst-memstream-string for open_memstream overflow
  2022-03-17 19:29 ` [PATCH 08/26] stdio-common: Add tst-memstream-string for open_memstream overflow Florian Weimer
@ 2022-05-20 17:44   ` Adhemerval Zanella
  2022-05-23  7:03     ` Florian Weimer
  0 siblings, 1 reply; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 17:44 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:29, Florian Weimer via Libc-alpha wrote:
> This code path is exercised indirectly by some of the DNS stub
> resolver tests, via their own use of xopen_memstream for constructing
> strings describing result data.  The relative lack of test suite
> coverage became apparent when these tests starting failing after a
> printf changes uncovered bug 28949.

LGTM, with maybe a suggestio below.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  stdio-common/Makefile               |  1 +
>  stdio-common/tst-memstream-string.c | 85 +++++++++++++++++++++++++++++
>  2 files changed, 86 insertions(+)
>  create mode 100644 stdio-common/tst-memstream-string.c
> 
> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
> index f0e65f0dcd..222c9ea63d 100644
> --- a/stdio-common/Makefile
> +++ b/stdio-common/Makefile
> @@ -173,6 +173,7 @@ tests := \
>    tst-gets \
>    tst-grouping \
>    tst-long-dbl-fphex \
> +  tst-memstream-string \
>    tst-obprintf \
>    tst-perror \
>    tst-popen \
> diff --git a/stdio-common/tst-memstream-string.c b/stdio-common/tst-memstream-string.c
> new file mode 100644
> index 0000000000..1c1bf0154a
> --- /dev/null
> +++ b/stdio-common/tst-memstream-string.c
> @@ -0,0 +1,85 @@
> +/* Test writing differently sized strings to a memstream.
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/xmemstream.h>
> +
> +/* Returns a printable ASCII character based on INDEX.   */
> +static inline char
> +char_from_index (unsigned int index)
> +{
> +  return ' ' + (index % 95);
> +}
> +
> +/* Hide fprintf behind a compiler barrier, to avoid the fputs
> +   transformation.  */
> +void __attribute__ ((weak))
> +fprintf_compiler_barrier (FILE *fp, const char *format, const char *arg)
> +{
> +  fprintf (fp, format, arg);
> +}

You can also avoid it by explicit disable the builtin on object build:

CFLAGS-tst-memstream-string.c += -fno-builtin-fprintf

> +
> +enum { result_size = 25000 };
> +
> +static void
> +run_one_size (unsigned int chunk_size)
> +{
> +  char *chunk = xmalloc (chunk_size + 1);
> +
> +  struct xmemstream mem;
> +  xopen_memstream (&mem);
> +  unsigned int written = 0;
> +  for (unsigned int i = 0; i < result_size; )
> +    {
> +      unsigned int to_print = result_size - i;
> +      if (to_print > chunk_size)
> +        to_print = chunk_size;
> +      for (unsigned int j = 0; j < to_print; ++j)
> +        chunk[j] = char_from_index(i + j);
> +      chunk[to_print] = '\0';
> +      fprintf_compiler_barrier (mem.out, "%s", chunk);
> +      i += to_print;
> +      written += strlen(chunk);
> +    }
> +  xfclose_memstream (&mem);
> +
> +  TEST_COMPARE (written, result_size);
> +  TEST_COMPARE (mem.length, result_size);
> +  TEST_COMPARE (strlen (mem.buffer), result_size);
> +
> +  for (unsigned int i = 0; i < result_size; ++i)
> +    TEST_COMPARE (mem.buffer[i], char_from_index (i));
> +
> +  free (mem.buffer);
> +  free (chunk);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  for (unsigned int chunk_size = 1; chunk_size <= 30; ++ chunk_size)
> +    run_one_size (chunk_size);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 09/26] stdio-common: Add printf specifier registry to <printf.h>
  2022-03-17 19:29 ` [PATCH 09/26] stdio-common: Add printf specifier registry to <printf.h> Florian Weimer
@ 2022-05-20 17:49   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 17:49 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:29, Florian Weimer via Libc-alpha wrote:
> Add  __printf_function_table, __register_printf_specifier to
> include/printf.h, where they belong.
> ---
>  include/printf.h                 | 6 ++++++
>  stdio-common/reg-printf.c        | 7 -------
>  stdio-common/vfprintf-internal.c | 1 -
>  3 files changed, 6 insertions(+), 8 deletions(-)
> 
> diff --git a/include/printf.h b/include/printf.h
> index 0ed6e87387..e1ac20807b 100644
> --- a/include/printf.h
> +++ b/include/printf.h
> @@ -12,6 +12,12 @@
>  
>  # ifndef _ISOMAC
>  
> +/* Internal interfaces for registered specifiers.  */
> +extern printf_function **__printf_function_table attribute_hidden;
> +int __register_printf_specifier (int, printf_function,
> +				 printf_arginfo_size_function);
> +libc_hidden_proto (__register_printf_specifier)
> +
>  #include <bits/types/locale_t.h>
>  
>  /* Now define the internal interfaces.  */

The __printf_function_table prototype is still present at 
stdio-common/printf-parse.h.  Maybe either move __printf_arginfo_table and
__printf_va_arg_table, or remove __printf_function_table from printf.h.


> diff --git a/stdio-common/reg-printf.c b/stdio-common/reg-printf.c
> index 400b99d2f6..5f4c6a24c2 100644
> --- a/stdio-common/reg-printf.c
> +++ b/stdio-common/reg-printf.c
> @@ -30,13 +30,6 @@ printf_function **__printf_function_table attribute_hidden;
>  
>  __libc_lock_define_initialized (static, lock)
>  
> -int __register_printf_specifier (int, printf_function,
> -				 printf_arginfo_size_function);
> -libc_hidden_proto (__register_printf_specifier)
> -int __register_printf_function (int, printf_function,
> -				printf_arginfo_function);
> -
> -
>  /* Register FUNC to be called to format SPEC specifiers.  */
>  int
>  __register_printf_specifier (int spec, printf_function converter,

Ok.

> diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
> index 1986c4bdb5..f8aa2fdafb 100644
> --- a/stdio-common/vfprintf-internal.c
> +++ b/stdio-common/vfprintf-internal.c
> @@ -1379,7 +1379,6 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
>        /* Process format specifiers.  */
>        while (1)
>  	{
> -	  extern printf_function **__printf_function_table;
>  	  int function_done;
>  
>  	  if (spec <= UCHAR_MAX

Ok.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 10/26] stdio-common: Move union printf_arg int <printf.h>
  2022-03-17 19:30 ` [PATCH 10/26] stdio-common: Move union printf_arg int <printf.h> Florian Weimer
@ 2022-05-20 17:51   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 17:51 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:30, Florian Weimer via Libc-alpha wrote:
> The type does not depend on wide vs narrow preprocessor macros,
> so it does not need to be customized in stdio-common/printf-parse.h.


LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>


> ---
>  include/printf.h            | 21 +++++++++++++++++++++
>  stdio-common/printf-parse.h | 23 -----------------------
>  2 files changed, 21 insertions(+), 23 deletions(-)
> 
> diff --git a/include/printf.h b/include/printf.h
> index e1ac20807b..770c32d8f8 100644
> --- a/include/printf.h
> +++ b/include/printf.h
> @@ -18,6 +18,27 @@ int __register_printf_specifier (int, printf_function,
>  				 printf_arginfo_size_function);
>  libc_hidden_proto (__register_printf_specifier)
>  
> +/* The various kinds of arguments that can be passed to printf.  */
> +union printf_arg
> +  {
> +    wchar_t pa_wchar;
> +    int pa_int;
> +    long int pa_long_int;
> +    long long int pa_long_long_int;
> +    unsigned int pa_u_int;
> +    unsigned long int pa_u_long_int;
> +    unsigned long long int pa_u_long_long_int;
> +    double pa_double;
> +    long double pa_long_double;
> +#if __HAVE_FLOAT128_UNLIKE_LDBL
> +    _Float128 pa_float128;
> +#endif
> +    const char *pa_string;
> +    const wchar_t *pa_wstring;
> +    void *pa_pointer;
> +    void *pa_user;
> +};
> +
>  #include <bits/types/locale_t.h>
>  
>  /* Now define the internal interfaces.  */
> diff --git a/stdio-common/printf-parse.h b/stdio-common/printf-parse.h
> index de0f289c1f..d00c165131 100644
> --- a/stdio-common/printf-parse.h
> +++ b/stdio-common/printf-parse.h
> @@ -44,29 +44,6 @@ struct printf_spec
>      int size;
>    };
>  
> -
> -/* The various kinds off arguments that can be passed to printf.  */
> -union printf_arg
> -  {
> -    wchar_t pa_wchar;
> -    int pa_int;
> -    long int pa_long_int;
> -    long long int pa_long_long_int;
> -    unsigned int pa_u_int;
> -    unsigned long int pa_u_long_int;
> -    unsigned long long int pa_u_long_long_int;
> -    double pa_double;
> -    long double pa_long_double;
> -#if __HAVE_FLOAT128_UNLIKE_LDBL
> -    _Float128 pa_float128;
> -#endif
> -    const char *pa_string;
> -    const wchar_t *pa_wstring;
> -    void *pa_pointer;
> -    void *pa_user;
> -  };
> -
> -
>  #ifndef DONT_NEED_READ_INT
>  /* Read a simple integer from a string and update the string pointer.
>     It is assumed that the first character is a digit.  */

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 11/26] stdio-common: Simplify printf_unknown interface in vfprintf-internal.c
  2022-03-17 19:30 ` [PATCH 11/26] stdio-common: Simplify printf_unknown interface in vfprintf-internal.c Florian Weimer
@ 2022-05-20 18:07   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 18:07 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:30, Florian Weimer via Libc-alpha wrote:
> The called function does not use the args array, so there is no need
> to produce it.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  stdio-common/vfprintf-internal.c | 21 +++------------------
>  1 file changed, 3 insertions(+), 18 deletions(-)
> 
> diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
> index f8aa2fdafb..fb94961f37 100644
> --- a/stdio-common/vfprintf-internal.c
> +++ b/stdio-common/vfprintf-internal.c
> @@ -672,8 +672,7 @@ static int printf_positional (FILE *s,
>  			      unsigned int mode_flags);
>  
>  /* Handle unknown format specifier.  */
> -static int printf_unknown (FILE *, const struct printf_info *,
> -			   const void *const *) __THROW;
> +static int printf_unknown (FILE *, const struct printf_info *) __THROW;
>  
>  /* Group digits of number string.  */
>  static CHAR_T *group_number (CHAR_T *, CHAR_T *, CHAR_T *, const char *,
> @@ -1465,19 +1464,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
>  
>  	  LABEL (form_unknown):
>  	  {
> -	    unsigned int i;
> -	    const void **ptr;
> -
> -	    ptr = alloca (specs[nspecs_done].ndata_args
> -			  * sizeof (const void *));
> -
> -	    /* Fill in an array of pointers to the argument values.  */
> -	    for (i = 0; i < specs[nspecs_done].ndata_args; ++i)
> -	      ptr[i] = &args_value[specs[nspecs_done].data_arg + i];
> -
> -	    /* Call the function.  */
> -	    function_done = printf_unknown (s, &specs[nspecs_done].info,
> -					    ptr);
> +	    int function_done = printf_unknown (s, &specs[nspecs_done].info);
>  
>  	    /* If an error occurred we don't have information about #
>  	       of chars.  */
> @@ -1507,9 +1494,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
>  /* Handle an unknown format specifier.  This prints out a canonicalized
>     representation of the format spec itself.  */
>  static int
> -printf_unknown (FILE *s, const struct printf_info *info,
> -		const void *const *args)
> -
> +printf_unknown (FILE *s, const struct printf_info *info)
>  {
>    int done = 0;
>    CHAR_T work_buffer[MAX (sizeof (info->width), sizeof (info->prec)) * 3];

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 12/26] locale: Call _nl_unload_locale from _nl_archive_subfreeres
  2022-03-17 19:30 ` [PATCH 12/26] locale: Call _nl_unload_locale from _nl_archive_subfreeres Florian Weimer
@ 2022-05-20 18:09   ` Adhemerval Zanella
  2022-05-23  7:14     ` Florian Weimer
  0 siblings, 1 reply; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 18:09 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:30, Florian Weimer via Libc-alpha wrote:
> The function performs the same teps for ld_archive locales

s/teps/steps

> (mapped from an archive), and this code is not performance-critical,
> so the specialization does not add vale.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  locale/loadarchive.c | 8 +-------
>  1 file changed, 1 insertion(+), 7 deletions(-)
> 
> diff --git a/locale/loadarchive.c b/locale/loadarchive.c
> index e7c797bc70..5a2356707f 100644
> --- a/locale/loadarchive.c
> +++ b/locale/loadarchive.c
> @@ -515,13 +515,7 @@ _nl_archive_subfreeres (void)
>        free (dead->name);
>        for (category = 0; category < __LC_LAST; ++category)
>  	if (category != LC_ALL && dead->data[category] != NULL)
> -	  {
> -	    /* _nl_unload_locale just does this free for the archive case.  */
> -	    if (dead->data[category]->private.cleanup)
> -	      (*dead->data[category]->private.cleanup) (dead->data[category]);
> -
> -	    free (dead->data[category]);
> -	  }
> +	  _nl_unload_locale (dead->data[category]);
>        free (dead);
>      }
>    archloaded = NULL;

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 13/26] locale: Remove cleanup function pointer from struct __localedata
  2022-03-17 19:30 ` [PATCH 13/26] locale: Remove cleanup function pointer from struct __localedata Florian Weimer
@ 2022-05-20 18:16   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 18:16 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:30, Florian Weimer via Libc-alpha wrote:
> We can call the cleanup functions directly from _nl_unload_locale
> if we pass the category to it.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  locale/findlocale.c    |  2 +-
>  locale/loadarchive.c   |  2 +-
>  locale/loadlocale.c    | 17 ++++++++++++-----
>  locale/localeinfo.h    | 23 ++++++++++-------------
>  locale/setlocale.c     |  2 +-
>  time/alt_digit.c       |  2 --
>  time/era.c             |  1 -
>  time/lc-time-cleanup.c |  1 -
>  wcsmbs/wcsmbsload.c    |  8 ++------
>  9 files changed, 27 insertions(+), 31 deletions(-)
> 
> diff --git a/locale/findlocale.c b/locale/findlocale.c
> index 64f687ea9d..fc433b61d8 100644
> --- a/locale/findlocale.c
> +++ b/locale/findlocale.c
> @@ -348,6 +348,6 @@ _nl_remove_locale (int locale, struct __locale_data *data)
>  	}
>  
>        /* This does the real work.  */
> -      _nl_unload_locale (data);
> +      _nl_unload_locale (locale, data);
>      }
>  }
> diff --git a/locale/loadarchive.c b/locale/loadarchive.c
> index 5a2356707f..fcc4913319 100644
> --- a/locale/loadarchive.c
> +++ b/locale/loadarchive.c
> @@ -515,7 +515,7 @@ _nl_archive_subfreeres (void)
>        free (dead->name);
>        for (category = 0; category < __LC_LAST; ++category)
>  	if (category != LC_ALL && dead->data[category] != NULL)
> -	  _nl_unload_locale (dead->data[category]);
> +	  _nl_unload_locale (category, dead->data[category]);
>        free (dead);
>      }
>    archloaded = NULL;
> diff --git a/locale/loadlocale.c b/locale/loadlocale.c
> index b8cd1aa441..9069bafcd8 100644
> --- a/locale/loadlocale.c
> +++ b/locale/loadlocale.c
> @@ -101,8 +101,7 @@ _nl_intern_locale_data (int category, const void *data, size_t datasize)
>  
>    newdata->filedata = (void *) filedata;
>    newdata->filesize = datasize;
> -  newdata->private.data = NULL;
> -  newdata->private.cleanup = NULL;
> +  memset (&newdata->private, 0, sizeof (newdata->private));
>    newdata->usage_count = 0;
>    newdata->use_translit = 0;
>    newdata->nstrings = filedata->nstrings;
> @@ -282,10 +281,18 @@ _nl_load_locale (struct loaded_l10nfile *file, int category)
>  }
>  
>  void
> -_nl_unload_locale (struct __locale_data *locale)
> +_nl_unload_locale (int category, struct __locale_data *locale)
>  {
> -  if (locale->private.cleanup)
> -    (*locale->private.cleanup) (locale);
> +  /* Deallocate locale->private.  */
> +  switch (category)
> +    {
> +    case LC_CTYPE:
> +      _nl_cleanup_ctype (locale);
> +      break;
> +    case LC_TIME:
> +      _nl_cleanup_time (locale);
> +      break;
> +    }
>  
>    switch (__builtin_expect (locale->alloc, ld_mapped))
>      {
> diff --git a/locale/localeinfo.h b/locale/localeinfo.h
> index 87d3b48c16..8ce072b7b4 100644
> --- a/locale/localeinfo.h
> +++ b/locale/localeinfo.h
> @@ -58,18 +58,13 @@ struct __locale_data
>      ld_archive			/* Both point into mmap'd archive regions.  */
>    } alloc;
>  
> -  /* This provides a slot for category-specific code to cache data computed
> -     about this locale.  That code can set a cleanup function to deallocate
> -     the data.  */
> -  struct
> +  /* This provides a slot for category-specific code to cache data
> +     computed about this locale.  This is deallocated at the start of
> +     _nl_unload_locale.  */
> +  union
>    {
> -    void (*cleanup) (struct __locale_data *);
> -    union
> -    {
> -      void *data;
> -      struct lc_time_data *time;
> -      const struct gconv_fcts *ctype;
> -    };
> +    struct lc_time_data *time;
> +    const struct gconv_fcts *ctype;
>    } private;
>  
>    unsigned int usage_count;	/* Counter for users.  */
> @@ -349,7 +344,8 @@ extern void _nl_load_locale (struct loaded_l10nfile *file, int category)
>       attribute_hidden;
>  
>  /* Free all resource.  */
> -extern void _nl_unload_locale (struct __locale_data *locale) attribute_hidden;
> +extern void _nl_unload_locale (int category, struct __locale_data *locale)
> +  attribute_hidden;
>  
>  /* Free the locale and give back all memory if the usage count is one.  */
>  extern void _nl_remove_locale (int locale, struct __locale_data *data)
> @@ -409,7 +405,8 @@ extern int _nl_parse_alt_digit (const char **strp,
>  /* Postload processing.  */
>  extern void _nl_postload_ctype (void);
>  
> -/* Functions used for the `private.cleanup' hook.  */
> +/* Deallocate category-specific data.  Used in _nl_unload_locale.  */
> +extern void _nl_cleanup_ctype (struct __locale_data *) attribute_hidden;
>  extern void _nl_cleanup_time (struct __locale_data *) attribute_hidden;
>  
>  
> diff --git a/locale/setlocale.c b/locale/setlocale.c
> index 38e9bec6bb..56c14d8533 100644
> --- a/locale/setlocale.c
> +++ b/locale/setlocale.c
> @@ -489,7 +489,7 @@ free_category (int category,
>        struct __locale_data *data = (struct __locale_data *) runp->data;
>  
>        if (data != NULL && data != c_data)
> -	_nl_unload_locale (data);
> +	_nl_unload_locale (category, data);
>        runp = runp->next;
>        free ((char *) curr->filename);
>        free (curr);
> diff --git a/time/alt_digit.c b/time/alt_digit.c
> index ae22b3f927..7ed9b6b0a5 100644
> --- a/time/alt_digit.c
> +++ b/time/alt_digit.c
> @@ -41,7 +41,6 @@ _nl_init_alt_digit (struct __locale_data *current)
>        if (current->private.time == NULL)
>  	return;
>        memset (current->private.time, 0, sizeof *current->private.time);
> -      current->private.cleanup = &_nl_cleanup_time;
>      }
>    data = current->private.time;
>  
> @@ -110,7 +109,6 @@ _nl_get_walt_digit (unsigned int number, struct __locale_data *current)
>        if (current->private.time == NULL)
>  	goto out;
>        memset (current->private.time, 0, sizeof *current->private.time);
> -      current->private.cleanup = &_nl_cleanup_time;
>      }
>    data = current->private.time;
>  
> diff --git a/time/era.c b/time/era.c
> index 43528ad0d2..d4b538c7b0 100644
> --- a/time/era.c
> +++ b/time/era.c
> @@ -53,7 +53,6 @@ _nl_init_era_entries (struct __locale_data *current)
>        if (current->private.time == NULL)
>  	goto out;
>        memset (current->private.time, 0, sizeof *current->private.time);
> -      current->private.cleanup = &_nl_cleanup_time;
>      }
>    data = current->private.time;
>  
> diff --git a/time/lc-time-cleanup.c b/time/lc-time-cleanup.c
> index 2734e25815..f844e04905 100644
> --- a/time/lc-time-cleanup.c
> +++ b/time/lc-time-cleanup.c
> @@ -26,7 +26,6 @@ _nl_cleanup_time (struct __locale_data *locale)
>    if (data != NULL)
>      {
>        locale->private.time = NULL;
> -      locale->private.cleanup = NULL;
>  
>        free (data->eras);
>        free (data->alt_digits);
> diff --git a/wcsmbs/wcsmbsload.c b/wcsmbs/wcsmbsload.c
> index ffb5cb7b87..af539a099a 100644
> --- a/wcsmbs/wcsmbsload.c
> +++ b/wcsmbs/wcsmbsload.c
> @@ -202,10 +202,7 @@ __wcsmbs_load_conv (struct __locale_data *new_category)
>  	  new_category->private.ctype = &__wcsmbs_gconv_fcts_c;
>  	}
>        else
> -	{
> -	  new_category->private.ctype = new_fcts;
> -	  new_category->private.cleanup = &_nl_cleanup_ctype;
> -	}
> +	new_category->private.ctype = new_fcts;
>      }
>  
>    __libc_rwlock_unlock (__libc_setlocale_lock);
> @@ -267,10 +264,9 @@ void
>  _nl_cleanup_ctype (struct __locale_data *locale)
>  {
>    const struct gconv_fcts *const data = locale->private.ctype;
> -  if (data != NULL)
> +  if (data != NULL && data != &__wcsmbs_gconv_fcts_c)
>      {
>        locale->private.ctype = NULL;
> -      locale->private.cleanup = NULL;
>  
>        /* Free the old conversions.  */
>        __gconv_close_transform (data->tomb, data->tomb_nsteps);

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 14/26] locale: Remove private union from struct __locale_data
  2022-03-17 19:30 ` [PATCH 14/26] locale: Remove private union from struct __locale_data Florian Weimer
@ 2022-05-20 18:22   ` Adhemerval Zanella
  0 siblings, 0 replies; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 18:22 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:30, Florian Weimer via Libc-alpha wrote:
> This avoids an alias violation later.  This commit also fixes
> an incorrect double-checked locking idiom in _nl_init_era_entries.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  locale/C-address.c        |  2 +-
>  locale/C-collate.c        |  2 +-
>  locale/C-ctype.c          |  2 +-
>  locale/C-identification.c |  2 +-
>  locale/C-measurement.c    |  2 +-
>  locale/C-messages.c       |  2 +-
>  locale/C-monetary.c       |  2 +-
>  locale/C-name.c           |  2 +-
>  locale/C-numeric.c        |  2 +-
>  locale/C-paper.c          |  2 +-
>  locale/C-telephone.c      |  2 +-
>  locale/C-time.c           |  2 +-
>  locale/localeinfo.h       | 14 ++++++------
>  time/alt_digit.c          | 47 ++++++++++++++++----------------------
>  time/era.c                | 48 +++++++++++++++++++--------------------
>  time/lc-time-cleanup.c    |  4 ++--
>  wcsmbs/wcsmbsload.c       | 10 ++++----
>  wcsmbs/wcsmbsload.h       |  8 ++++---
>  18 files changed, 74 insertions(+), 81 deletions(-)
> 
> diff --git a/locale/C-address.c b/locale/C-address.c
> index 1f509e4785..40ce9fbcf3 100644
> --- a/locale/C-address.c
> +++ b/locale/C-address.c
> @@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_ADDRESS attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    13,
> diff --git a/locale/C-collate.c b/locale/C-collate.c
> index 510e90cf14..f9c2b7741b 100644
> --- a/locale/C-collate.c
> +++ b/locale/C-collate.c
> @@ -25,7 +25,7 @@ const struct __locale_data _nl_C_LC_COLLATE attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    19,
> diff --git a/locale/C-ctype.c b/locale/C-ctype.c
> index cc99bff930..ef4b67029c 100644
> --- a/locale/C-ctype.c
> +++ b/locale/C-ctype.c
> @@ -542,7 +542,7 @@ const struct __locale_data _nl_C_LC_CTYPE attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    1,		/* Enable transliteration by default.  */
>    NR_FIXED + NR_CLASSES + NR_MAPS,
> diff --git a/locale/C-identification.c b/locale/C-identification.c
> index edaf417c70..9648de05bc 100644
> --- a/locale/C-identification.c
> +++ b/locale/C-identification.c
> @@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_IDENTIFICATION attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    16,
> diff --git a/locale/C-measurement.c b/locale/C-measurement.c
> index b98d624b16..99e16caa3e 100644
> --- a/locale/C-measurement.c
> +++ b/locale/C-measurement.c
> @@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_MEASUREMENT attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    2,
> diff --git a/locale/C-messages.c b/locale/C-messages.c
> index 88331c7ab6..8fc5c397e3 100644
> --- a/locale/C-messages.c
> +++ b/locale/C-messages.c
> @@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_MESSAGES attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    5,
> diff --git a/locale/C-monetary.c b/locale/C-monetary.c
> index 295f7a93f0..9c752bc1b6 100644
> --- a/locale/C-monetary.c
> +++ b/locale/C-monetary.c
> @@ -27,7 +27,7 @@ const struct __locale_data _nl_C_LC_MONETARY attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    46,
> diff --git a/locale/C-name.c b/locale/C-name.c
> index c9b03ef176..0c04b447cd 100644
> --- a/locale/C-name.c
> +++ b/locale/C-name.c
> @@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_NAME attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    7,
> diff --git a/locale/C-numeric.c b/locale/C-numeric.c
> index 178f61cd89..6f67deca1f 100644
> --- a/locale/C-numeric.c
> +++ b/locale/C-numeric.c
> @@ -23,7 +23,7 @@ const struct __locale_data _nl_C_LC_NUMERIC attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL, 			/* No cached data.  */
>    UNDELETABLE,
>    0,
>    6,
> diff --git a/locale/C-paper.c b/locale/C-paper.c
> index 06822385f6..56b9519801 100644
> --- a/locale/C-paper.c
> +++ b/locale/C-paper.c
> @@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_PAPER attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    3,
> diff --git a/locale/C-telephone.c b/locale/C-telephone.c
> index 7f71f605c5..1e8e8b7a66 100644
> --- a/locale/C-telephone.c
> +++ b/locale/C-telephone.c
> @@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_TELEPHONE attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL,				/* No cached data.  */
>    UNDELETABLE,
>    0,
>    5,
> diff --git a/locale/C-time.c b/locale/C-time.c
> index ebd79591e2..78d87046fb 100644
> --- a/locale/C-time.c
> +++ b/locale/C-time.c
> @@ -26,7 +26,7 @@ const struct __locale_data _nl_C_LC_TIME attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  { NULL, },			/* no cached data */
> +  NULL, 			/* No cached data.  */
>    UNDELETABLE,
>    0,
>    159,
> diff --git a/locale/localeinfo.h b/locale/localeinfo.h
> index 8ce072b7b4..01ec5535bb 100644
> --- a/locale/localeinfo.h
> +++ b/locale/localeinfo.h
> @@ -59,13 +59,13 @@ struct __locale_data
>    } alloc;
>  
>    /* This provides a slot for category-specific code to cache data
> -     computed about this locale.  This is deallocated at the start of
> -     _nl_unload_locale.  */
> -  union
> -  {
> -    struct lc_time_data *time;
> -    const struct gconv_fcts *ctype;
> -  } private;
> +     computed about this locale.  Type of the data pointed to:
> +
> +     LC_CTYPE   struct gconv_fcts (get_gconv_fcts, __wcsmbs_load_conv)
> +     LC_TIME    struct lc_time_data (_nl_init_alt_digit, _nl_init_era_entries)
> +
> +     This data deallocated at the start of _nl_unload_locale.  */
> +  void *private;
>  
>    unsigned int usage_count;	/* Counter for users.  */
>  
> diff --git a/time/alt_digit.c b/time/alt_digit.c
> index 7ed9b6b0a5..331cb395ce 100644
> --- a/time/alt_digit.c
> +++ b/time/alt_digit.c
> @@ -30,19 +30,18 @@ __libc_rwlock_define (extern, __libc_setlocale_lock attribute_hidden)
>  #define CURRENT_WSTR(item) \
>    ((wchar_t *) current->values[_NL_ITEM_INDEX (item)].wstr)
>  
> -static void
> +static struct lc_time_data *
>  _nl_init_alt_digit (struct __locale_data *current)
>  {
> -  struct lc_time_data *data;
> +  struct lc_time_data *data = current->private;
>  
> -  if (current->private.time == NULL)
> +  if (data == NULL)
>      {
> -      current->private.time = malloc (sizeof *current->private.time);
> -      if (current->private.time == NULL)
> -	return;
> -      memset (current->private.time, 0, sizeof *current->private.time);
> +      data = calloc (sizeof *data, 1);
> +      if (data == NULL)
> +	return NULL;
> +      current->private = data;
>      }
> -  data = current->private.time;
>  
>    if (! data->alt_digits_initialized)
>      {
> @@ -65,6 +64,7 @@ _nl_init_alt_digit (struct __locale_data *current)
>  	}
>      }
>  
> +  return data;
>  }
>  
>  const char *
> @@ -77,13 +77,11 @@ _nl_get_alt_digit (unsigned int number, struct __locale_data *current)
>  
>    __libc_rwlock_wrlock (__libc_setlocale_lock);
>  
> -  if (current->private.time == NULL
> -      || ! current->private.time->alt_digits_initialized)
> -    _nl_init_alt_digit (current);
> +  struct lc_time_data *data = _nl_init_alt_digit (current);
>  
> -  result = ((current->private.time != NULL
> -	     && current->private.time->alt_digits != NULL)
> -	    ? current->private.time->alt_digits[number]
> +  result = ((data != NULL
> +	     && data->alt_digits != NULL)
> +	    ? data->alt_digits[number]
>  	    : NULL);
>  
>    __libc_rwlock_unlock (__libc_setlocale_lock);
> @@ -96,21 +94,20 @@ const wchar_t *
>  _nl_get_walt_digit (unsigned int number, struct __locale_data *current)
>  {
>    const wchar_t *result = NULL;
> -  struct lc_time_data *data;
>  
>    if (number >= 100 || CURRENT_WSTR (_NL_WALT_DIGITS)[0] == L'\0')
>      return NULL;
>  
>    __libc_rwlock_wrlock (__libc_setlocale_lock);
>  
> -  if (current->private.time == NULL)
> +  struct lc_time_data *data = current->private;
> +  if (data == NULL)
>      {
> -      current->private.time = malloc (sizeof *current->private.time);
> -      if (current->private.time == NULL)
> +      data = calloc (sizeof *data, 1);
> +      if (data == NULL)
>  	goto out;
> -      memset (current->private.time, 0, sizeof *current->private.time);
> +      current->private = data;
>      }
> -  data = current->private.time;
>  
>    if (! data->walt_digits_initialized)
>      {
> @@ -156,12 +153,8 @@ _nl_parse_alt_digit (const char **strp, struct __locale_data *current)
>  
>    __libc_rwlock_wrlock (__libc_setlocale_lock);
>  
> -  if (current->private.time == NULL
> -      || ! current->private.time->alt_digits_initialized)
> -    _nl_init_alt_digit (current);
> -
> -  if (current->private.time != NULL
> -      && current->private.time->alt_digits != NULL)
> +  struct lc_time_data *data = _nl_init_alt_digit (current);
> +  if (data != NULL && data->alt_digits != NULL)
>      /* Matching is not unambiguous.  The alternative digits could be like
>         I, II, III, ... and the first one is a substring of the second
>         and third.  Therefore we must keep on searching until we found
> @@ -169,7 +162,7 @@ _nl_parse_alt_digit (const char **strp, struct __locale_data *current)
>         the standard.  */
>      for (cnt = 0; cnt < 100; ++cnt)
>        {
> -	const char *const dig = current->private.time->alt_digits[cnt];
> +	const char *const dig = data->alt_digits[cnt];
>  	size_t len = strlen (dig);
>  
>  	if (len > maxlen && strncmp (dig, str, len) == 0)
> diff --git a/time/era.c b/time/era.c
> index d4b538c7b0..7f18071888 100644
> --- a/time/era.c
> +++ b/time/era.c
> @@ -35,7 +35,7 @@ __libc_rwlock_define (extern, __libc_setlocale_lock attribute_hidden)
>  
>  /* Look up the era information in CURRENT's locale strings and
>     cache it in CURRENT->private.  */
> -static void
> +static struct lc_time_data *
>  _nl_init_era_entries (struct __locale_data *current)
>  {
>    size_t cnt;
> @@ -43,18 +43,22 @@ _nl_init_era_entries (struct __locale_data *current)
>  
>    /* Avoid touching CURRENT if there is no data at all, for _nl_C_LC_TIME.  */
>    if (CURRENT_WORD (_NL_TIME_ERA_NUM_ENTRIES) == 0)
> -    return;
> +    return NULL;
> +
> +  data = current->private;
> +  if (data != NULL && atomic_load_acquire (&data->era_initialized))
> +    return data;
>  
>    __libc_rwlock_wrlock (__libc_setlocale_lock);
>  
> -  if (current->private.time == NULL)
> +  data = current->private;
> +  if (data == NULL)
>      {
> -      current->private.time = malloc (sizeof *current->private.time);
> -      if (current->private.time == NULL)
> +      data = calloc (sizeof *data, 1);
> +      if (data == NULL)
>  	goto out;
> -      memset (current->private.time, 0, sizeof *current->private.time);
> +      current->private = data;
>      }
> -  data = current->private.time;
>  
>    if (! data->era_initialized)
>      {
> @@ -130,33 +134,30 @@ _nl_init_era_entries (struct __locale_data *current)
>  	    }
>  	}
>  
> -      data->era_initialized = 1;
> +      atomic_store_release (&data->era_initialized, 1);
>      }
>  
>   out:
>    __libc_rwlock_unlock (__libc_setlocale_lock);
> +  return data;
>  }
>  
>  struct era_entry *
>  _nl_get_era_entry (const struct tm *tp, struct __locale_data *current)
>  {
> -  if (current->private.time == NULL || !current->private.time->era_initialized)
> -    _nl_init_era_entries (current);
> +  struct lc_time_data *data = _nl_init_era_entries (current);
>  
> -  if (current->private.time != NULL)
> +  if (data != NULL)
>      {
>        /* Now compare date with the available eras.  */
>        const int32_t tdate[3] = { tp->tm_year, tp->tm_mon, tp->tm_mday };
>        size_t cnt;
> -      for (cnt = 0; cnt < current->private.time->num_eras; ++cnt)
> -	if ((ERA_DATE_CMP (current->private.time->eras[cnt].start_date, tdate)
> -	     && ERA_DATE_CMP (tdate,
> -			      current->private.time->eras[cnt].stop_date))
> -	    || (ERA_DATE_CMP (current->private.time->eras[cnt].stop_date,
> -			      tdate)
> -		&& ERA_DATE_CMP (tdate,
> -				 current->private.time->eras[cnt].start_date)))
> -	  return &current->private.time->eras[cnt];
> +      for (cnt = 0; cnt < data->num_eras; ++cnt)
> +	if ((ERA_DATE_CMP (data->eras[cnt].start_date, tdate)
> +	     && ERA_DATE_CMP (tdate, data->eras[cnt].stop_date))
> +	    || (ERA_DATE_CMP (data->eras[cnt].stop_date, tdate)
> +		&& ERA_DATE_CMP (tdate, data->eras[cnt].start_date)))
> +	  return &data->eras[cnt];
>      }
>  
>    return NULL;
> @@ -166,9 +167,6 @@ _nl_get_era_entry (const struct tm *tp, struct __locale_data *current)
>  struct era_entry *
>  _nl_select_era_entry (int cnt, struct __locale_data *current)
>  {
> -  if (current->private.time == NULL || !current->private.time->era_initialized)
> -    _nl_init_era_entries (current);
> -
> -  return (current->private.time == NULL
> -	  ? NULL : &current->private.time->eras[cnt]);
> +  struct lc_time_data *data = _nl_init_era_entries (current);
> +  return data == NULL ? NULL : &data->eras[cnt];
>  }
> diff --git a/time/lc-time-cleanup.c b/time/lc-time-cleanup.c
> index f844e04905..bcf6d2fbc9 100644
> --- a/time/lc-time-cleanup.c
> +++ b/time/lc-time-cleanup.c
> @@ -22,10 +22,10 @@
>  void
>  _nl_cleanup_time (struct __locale_data *locale)
>  {
> -  struct lc_time_data *const data = locale->private.time;
> +  struct lc_time_data *const data = locale->private;
>    if (data != NULL)
>      {
> -      locale->private.time = NULL;
> +      locale->private = NULL;
>  
>        free (data->eras);
>        free (data->alt_digits);
> diff --git a/wcsmbs/wcsmbsload.c b/wcsmbs/wcsmbsload.c
> index af539a099a..2650834e29 100644
> --- a/wcsmbs/wcsmbsload.c
> +++ b/wcsmbs/wcsmbsload.c
> @@ -155,7 +155,7 @@ __wcsmbs_load_conv (struct __locale_data *new_category)
>  
>    /* We should repeat the test since while we waited some other thread
>       might have run this function.  */
> -  if (__glibc_likely (new_category->private.ctype == NULL))
> +  if (__glibc_likely (new_category->private == NULL))
>      {
>        /* We must find the real functions.  */
>        const char *charset_name;
> @@ -199,10 +199,10 @@ __wcsmbs_load_conv (struct __locale_data *new_category)
>  	  free (new_fcts);
>  
>  	failed:
> -	  new_category->private.ctype = &__wcsmbs_gconv_fcts_c;
> +	  new_category->private = (void *) &__wcsmbs_gconv_fcts_c;
>  	}
>        else
> -	new_category->private.ctype = new_fcts;
> +	new_category->private = new_fcts;
>      }
>  
>    __libc_rwlock_unlock (__libc_setlocale_lock);
> @@ -263,10 +263,10 @@ __wcsmbs_named_conv (struct gconv_fcts *copy, const char *name)
>  void
>  _nl_cleanup_ctype (struct __locale_data *locale)
>  {
> -  const struct gconv_fcts *const data = locale->private.ctype;
> +  const struct gconv_fcts *const data = locale->private;
>    if (data != NULL && data != &__wcsmbs_gconv_fcts_c)
>      {
> -      locale->private.ctype = NULL;
> +      locale->private = NULL;
>  
>        /* Free the old conversions.  */
>        __gconv_close_transform (data->tomb, data->tomb_nsteps);
> diff --git a/wcsmbs/wcsmbsload.h b/wcsmbs/wcsmbsload.h
> index 1ff51e0f8a..8bbd34ba02 100644
> --- a/wcsmbs/wcsmbsload.h
> +++ b/wcsmbs/wcsmbsload.h
> @@ -66,13 +66,15 @@ extern const struct __locale_data _nl_C_LC_CTYPE attribute_hidden;
>  static inline const struct gconv_fcts *
>  get_gconv_fcts (struct __locale_data *data)
>  {
> -  if (__glibc_unlikely (data->private.ctype == NULL))
> +  struct gconv_fcts *private = data->private;
> +  if (private == NULL)
>      {
> -      if (__glibc_unlikely (data == &_nl_C_LC_CTYPE))
> +      if (data == &_nl_C_LC_CTYPE)
>  	return &__wcsmbs_gconv_fcts_c;
>        __wcsmbs_load_conv (data);
> +      private = data->private;
>      }
> -  return data->private.ctype;
> +  return private;
>  }
>  
>  #endif	/* wcsmbsload.h */

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 15/26] locale: Add more cached data to LC_CTYPE
  2022-03-17 19:30 ` [PATCH 15/26] locale: Add more cached data to LC_CTYPE Florian Weimer
@ 2022-05-20 18:29   ` Adhemerval Zanella
  2022-05-23  7:20     ` Florian Weimer
  0 siblings, 1 reply; 49+ messages in thread
From: Adhemerval Zanella @ 2022-05-20 18:29 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



On 17/03/2022 16:30, Florian Weimer via Libc-alpha wrote:
> This data will be used in number formatting.

Looks ok, some minor comments below.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  locale/C-ctype.c    |  9 +++++-
>  locale/loadlocale.c | 74 ++++++++++++++++++++++++++++++++++++++++++---
>  locale/localeinfo.h | 23 +++++++++++++-
>  wcsmbs/wcsmbsload.c | 23 ++++++++------
>  wcsmbs/wcsmbsload.h | 13 +++-----
>  5 files changed, 117 insertions(+), 25 deletions(-)
> 
> diff --git a/locale/C-ctype.c b/locale/C-ctype.c
> index ef4b67029c..6253f7f413 100644
> --- a/locale/C-ctype.c
> +++ b/locale/C-ctype.c
> @@ -19,6 +19,7 @@
>  #include <endian.h>
>  #include <stdalign.h>
>  #include <stdint.h>
> +#include <wcsmbs/wcsmbsload.h>
>  
>  #include "C-translit.h"
>  
> @@ -538,11 +539,17 @@ _nl_C_LC_CTYPE_width attribute_hidden =
>     NR_FIXED == _NL_ITEM_INDEX (_NL_CTYPE_EXTRA_MAP_1). */
>  typedef int assertion1[1 - 2 * (NR_FIXED != _NL_ITEM_INDEX (_NL_CTYPE_EXTRA_MAP_1))];
>  
> +static const struct lc_ctype_data lc_ctype_data =
> +  {
> +    .fcts = &__wcsmbs_gconv_fcts_c,
> +    .outdigit_bytes_all_equal = 1,
> +  };
> +
>  const struct __locale_data _nl_C_LC_CTYPE attribute_hidden =
>  {
>    _nl_C_name,
>    NULL, 0, 0,			/* no file mapped */
> -  NULL,				/* No cached data.  */
> +  (void *) &lc_ctype_data,
>    UNDELETABLE,
>    1,		/* Enable transliteration by default.  */
>    NR_FIXED + NR_CLASSES + NR_MAPS,
> diff --git a/locale/loadlocale.c b/locale/loadlocale.c
> index 9069bafcd8..ce78dfd071 100644
> --- a/locale/loadlocale.c
> +++ b/locale/loadlocale.c
> @@ -31,7 +31,6 @@
>  #include <not-cancel.h>
>  #include "localeinfo.h"
>  
> -

Spurious line removal.

>  static const size_t _nl_category_num_items[] =
>  {
>  #define DEFINE_CATEGORY(category, category_name, items, a) \
> @@ -62,6 +61,61 @@ static const enum value_type *const _nl_value_types[] =
>  #undef DEFINE_CATEGORY
>  };
>  
> +/* Fill in LOCDATA->private for the LC_CTYPE category.  */
> +static void
> +_nl_intern_locale_data_fill_cache_ctype (struct __locale_data *locdata)
> +{
> +  struct lc_ctype_data *data = locdata->private;
> +
> +  /* Default to no translation.  Assumes zero initialization of *data.  */
> +  memset (data->outdigit_bytes, 1, 10);

Use sizeof data->outdigit_bytes here.

> +
> +  for (int i = 0; i <= 9; ++i)
> +    {
> +      const char *digit
> +	= locdata->values[_NL_ITEM_INDEX (_NL_CTYPE_OUTDIGIT0_MB + i)].string;
> +      unsigned char len;
> +      if (digit[0] != '0' + i || digit[1] != '\0')
> +	 {
> +	   data->outdigit_translation_needed = true;
> +	   len = strlen (locdata->values[_NL_ITEM_INDEX
> +					 (_NL_CTYPE_OUTDIGIT0_MB + i)].string);
> +	 }
> +      else
> +	len = 1;
> +      data->outdigit_bytes[i] = len;
> +      if (i == 0)
> +	data->outdigit_bytes_all_equal = len;
> +      else if (data->outdigit_bytes_all_equal != len)
> +	data->outdigit_bytes_all_equal = 0;
> +    }
> +}
> +
> +/* Updates data in LOCDATA->private for CATEGORY.  */
> +static void
> +_nl_intern_locale_data_fill_cache (int category, struct __locale_data *locdata)
> +{
> +  switch (category)
> +    {
> +    case LC_CTYPE:
> +      _nl_intern_locale_data_fill_cache_ctype (locdata);
> +      break;
> +    }
> +}
> +
> +/* Returns the number of bytes allocated of struct __locale_data for
> +   CATEGORY.  */
> +static size_t
> +_nl_intern_locale_data_extra_size (int category)
> +{
> +  switch (category)
> +    {
> +    case LC_CTYPE:
> +      return sizeof (struct lc_ctype_data);
> +    default:
> +      return 0;
> +    }
> +}
>  
>  struct __locale_data *
>  _nl_intern_locale_data (int category, const void *data, size_t datasize)
> @@ -94,14 +148,23 @@ _nl_intern_locale_data (int category, const void *data, size_t datasize)
>        return NULL;
>      }
>  
> -  newdata = malloc (sizeof *newdata
> -		    + filedata->nstrings * sizeof (union locale_data_value));
> +  size_t base_size = (sizeof *newdata
> +		      + filedata->nstrings * sizeof (union locale_data_value));
> +  size_t extra_size = _nl_intern_locale_data_extra_size (category);
> +
> +  newdata = malloc (base_size + extra_size);
>    if (newdata == NULL)
>      return NULL;
>  
>    newdata->filedata = (void *) filedata;
>    newdata->filesize = datasize;
> -  memset (&newdata->private, 0, sizeof (newdata->private));
> +  if (extra_size == 0)
> +    newdata->private = NULL;
> +  else
> +    {
> +      newdata->private = (char *) newdata + base_size;
> +      memset (newdata->private, 0, extra_size);
> +    }
>    newdata->usage_count = 0;
>    newdata->use_translit = 0;
>    newdata->nstrings = filedata->nstrings;
> @@ -157,6 +220,9 @@ _nl_intern_locale_data (int category, const void *data, size_t datasize)
>  	}
>      }
>  
> +  if (extra_size > 0)
> +    _nl_intern_locale_data_fill_cache (category, newdata);
> +
>    return newdata;
>  }
>  
> diff --git a/locale/localeinfo.h b/locale/localeinfo.h
> index 01ec5535bb..fd43033a19 100644
> --- a/locale/localeinfo.h
> +++ b/locale/localeinfo.h
> @@ -61,7 +61,7 @@ struct __locale_data
>    /* This provides a slot for category-specific code to cache data
>       computed about this locale.  Type of the data pointed to:
>  
> -     LC_CTYPE   struct gconv_fcts (get_gconv_fcts, __wcsmbs_load_conv)
> +     LC_CTYPE   struct lc_ctype_data (_nl_intern_locale_data)
>       LC_TIME    struct lc_time_data (_nl_init_alt_digit, _nl_init_era_entries)
>  
>       This data deallocated at the start of _nl_unload_locale.  */
> @@ -161,6 +161,27 @@ struct lc_time_data
>    int walt_digits_initialized;
>  };
>  
> +/* Ancillary data for LC_CTYPE.  Co-allocated after struct
> +   __locale_data by _nl_intern_locale_data.  */
> +struct lc_ctype_data
> +{
> +  /* See get_gconv_fcts and __wcsmbs_load_conv.  */
> +  const struct gconv_fcts *fcts;
> +
> +  /* If false, outdigit just maps to the ASCII digits.  */
> +  bool outdigit_translation_needed;
> +
> +  /* Cached multi-byte string lengths.  This could be added to the
> +     locale data itself if the format is changed (which impacts
> +     existing statically linked binaries).  */
> +
> +  /* For the outdigit decimal digits (copied from LC_CTYPE).  */
> +  unsigned char outdigit_bytes[10];
> +
> +  /* If all outdigit_bytes elements are equal, this is that value,
> +     otherwise it is 0.  */
> +  unsigned char outdigit_bytes_all_equal;

Why not _Bool?

> +};
>  
>  /* LC_CTYPE specific:
>     Hardwired indices for standard wide character translation mappings.  */
> diff --git a/wcsmbs/wcsmbsload.c b/wcsmbs/wcsmbsload.c
> index 2650834e29..0f0f55f9ed 100644
> --- a/wcsmbs/wcsmbsload.c
> +++ b/wcsmbs/wcsmbsload.c
> @@ -150,12 +150,14 @@ __libc_rwlock_define (extern, __libc_setlocale_lock attribute_hidden)
>  void
>  __wcsmbs_load_conv (struct __locale_data *new_category)
>  {
> +  struct lc_ctype_data *data = new_category->private;
> +
>    /* Acquire the lock.  */
>    __libc_rwlock_wrlock (__libc_setlocale_lock);
>  
>    /* We should repeat the test since while we waited some other thread
>       might have run this function.  */
> -  if (__glibc_likely (new_category->private == NULL))
> +  if (__glibc_likely (data->fcts == NULL))
>      {
>        /* We must find the real functions.  */
>        const char *charset_name;
> @@ -199,10 +201,10 @@ __wcsmbs_load_conv (struct __locale_data *new_category)
>  	  free (new_fcts);
>  
>  	failed:
> -	  new_category->private = (void *) &__wcsmbs_gconv_fcts_c;
> +	  data->fcts = (void *) &__wcsmbs_gconv_fcts_c;
>  	}
>        else
> -	new_category->private = new_fcts;
> +	data->fcts = new_fcts;
>      }
>  
>    __libc_rwlock_unlock (__libc_setlocale_lock);
> @@ -263,14 +265,15 @@ __wcsmbs_named_conv (struct gconv_fcts *copy, const char *name)
>  void
>  _nl_cleanup_ctype (struct __locale_data *locale)
>  {
> -  const struct gconv_fcts *const data = locale->private;
> -  if (data != NULL && data != &__wcsmbs_gconv_fcts_c)
> +  struct lc_ctype_data *data = locale->private;
> +  if (data->fcts != NULL && data->fcts != &__wcsmbs_gconv_fcts_c)
>      {
> -      locale->private = NULL;
> -
>        /* Free the old conversions.  */
> -      __gconv_close_transform (data->tomb, data->tomb_nsteps);
> -      __gconv_close_transform (data->towc, data->towc_nsteps);
> -      free ((char *) data);
> +      __gconv_close_transform (data->fcts->tomb, data->fcts->tomb_nsteps);
> +      __gconv_close_transform (data->fcts->towc, data->fcts->towc_nsteps);
> +
> +      free ((void *) data->fcts);
> +      data->fcts = NULL;
> +      /* data itself is allocated within locale.  */
>      }
>  }
> diff --git a/wcsmbs/wcsmbsload.h b/wcsmbs/wcsmbsload.h
> index 8bbd34ba02..876f8368b1 100644
> --- a/wcsmbs/wcsmbsload.h
> +++ b/wcsmbs/wcsmbsload.h
> @@ -66,15 +66,10 @@ extern const struct __locale_data _nl_C_LC_CTYPE attribute_hidden;
>  static inline const struct gconv_fcts *
>  get_gconv_fcts (struct __locale_data *data)
>  {
> -  struct gconv_fcts *private = data->private;
> -  if (private == NULL)
> -    {
> -      if (data == &_nl_C_LC_CTYPE)
> -	return &__wcsmbs_gconv_fcts_c;
> -      __wcsmbs_load_conv (data);
> -      private = data->private;
> -    }
> -  return private;
> +  struct lc_ctype_data *private = data->private;
> +  if (private->fcts == NULL)
> +    __wcsmbs_load_conv (data);
> +  return private->fcts;
>  }
>  
>  #endif	/* wcsmbsload.h */

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 04/26] stdio-common: Add tst-printf-width-i18n to cover numeric field width
  2022-05-20 13:33     ` Adhemerval Zanella
@ 2022-05-23  6:39       ` Florian Weimer
  0 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-05-23  6:39 UTC (permalink / raw)
  To: Adhemerval Zanella; +Cc: libc-alpha

* Adhemerval Zanella:

> On 20/05/2022 10:22, Adhemerval Zanella wrote:
>> 
>> 
>> On 17/03/2022 16:28, Florian Weimer via Libc-alpha wrote:
>>> Related to bug 28943 and bug 28944.
>> 
>> LGTM with the fix to enabled the test.
>> 
>> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
>> 
>>> ---
>>>  stdio-common/Makefile                |  1 +
>>>  stdio-common/tst-printf-width-i18n.c | 95 ++++++++++++++++++++++++++++
>>>  2 files changed, 96 insertions(+)
>>>  create mode 100644 stdio-common/tst-printf-width-i18n.c
>>>
>>> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
>>> index 435cd8904f..f0e65f0dcd 100644
>>> --- a/stdio-common/Makefile
>>> +++ b/stdio-common/Makefile
>>> @@ -250,6 +250,7 @@ LOCALES := \
>>>    de_DE.ISO-8859-1 \
>>>    de_DE.UTF-8 \
>>>    en_US.ISO-8859-1 \
>>> +  hi_IN.UTF-8 \
>>>    ja_JP.EUC-JP \
>>>    ps_AF.UTF-8 \
>>>    # LOCALES
>> 
>> The tests is not enabled since there is no entry in tests.
>
> And you will also need to add a rule to avoid the test fail in parallel builds:
>
> $(objpfx)/tst-printf-width-i18n.out: $(gen-locales)

Right, fixed both and renamed the test to tst-vfprintf-width-i18n, to be
consistent with the other tests.

Thanks,
Florian


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 07/26] __printf_fphex always uses LC_NUMERIC
  2022-05-20 14:21   ` Adhemerval Zanella
@ 2022-05-23  6:55     ` Florian Weimer
  0 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-05-23  6:55 UTC (permalink / raw)
  To: Adhemerval Zanella; +Cc: libc-alpha

* Adhemerval Zanella:

> On 17/03/2022 16:29, Florian Weimer via Libc-alpha wrote:
>> There is no hexadecimal currency printing.  strfmon uses
>> __printf_fp_l exclusively.
>
> LGTM, some comments below.
>
> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
>
>> ---
>>  stdio-common/printf_fphex.c | 26 +++++++-------------------
>>  1 file changed, 7 insertions(+), 19 deletions(-)
>> 
>> diff --git a/stdio-common/printf_fphex.c b/stdio-common/printf_fphex.c
>> index 3dbbefd972..df11b4a166 100644
>> --- a/stdio-common/printf_fphex.c
>> +++ b/stdio-common/printf_fphex.c
>> @@ -103,9 +103,13 @@ __printf_fphex (FILE *fp,
>>      }
>>    fpnum;
>>  
>> -  /* Locale-dependent representation of decimal point.	*/
>> -  const char *decimal;
>> -  wchar_t decimalwc;
>> +  /* Locale-dependent representation of decimal point. Hexadecimal
>> +     formatting always using LC_NUMERIC (disregarding info->extra).  */
>> +  const char *decimal = _NL_CURRENT (LC_NUMERIC, DECIMAL_POINT);
>> +  wchar_t decimalwc = _NL_CURRENT_WORD (LC_NUMERIC, _NL_NUMERIC_DECIMAL_POINT_WC);
>
> Line too long.

Fixed.

>> +  /* The decimal point character must never be zero.  */
>> +  assert (*decimal != '\0' && decimalwc != L'\0');
>>  
>
> Should we assert for info->extra == 0 ?

I added:

  /* This function always uses LC_NUMERIC.  */
  assert (info->extra == 0);

Thanks,
Florian


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 08/26] stdio-common: Add tst-memstream-string for open_memstream overflow
  2022-05-20 17:44   ` Adhemerval Zanella
@ 2022-05-23  7:03     ` Florian Weimer
  0 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-05-23  7:03 UTC (permalink / raw)
  To: Adhemerval Zanella; +Cc: libc-alpha

* Adhemerval Zanella:

> On 17/03/2022 16:29, Florian Weimer via Libc-alpha wrote:
>> This code path is exercised indirectly by some of the DNS stub
>> resolver tests, via their own use of xopen_memstream for constructing
>> strings describing result data.  The relative lack of test suite
>> coverage became apparent when these tests starting failing after a
>> printf changes uncovered bug 28949.
>
> LGTM, with maybe a suggestio below.
>
> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

>> +/* Hide fprintf behind a compiler barrier, to avoid the fputs
>> +   transformation.  */
>> +void __attribute__ ((weak))
>> +fprintf_compiler_barrier (FILE *fp, const char *format, const char *arg)
>> +{
>> +  fprintf (fp, format, arg);
>> +}
>
> You can also avoid it by explicit disable the builtin on object build:
>
> CFLAGS-tst-memstream-string.c += -fno-builtin-fprintf

Thanks, I made this change.

Florian


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 12/26] locale: Call _nl_unload_locale from _nl_archive_subfreeres
  2022-05-20 18:09   ` Adhemerval Zanella
@ 2022-05-23  7:14     ` Florian Weimer
  0 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-05-23  7:14 UTC (permalink / raw)
  To: Adhemerval Zanella; +Cc: libc-alpha

* Adhemerval Zanella:

> On 17/03/2022 16:30, Florian Weimer via Libc-alpha wrote:
>> The function performs the same teps for ld_archive locales
>
> s/teps/steps
>
>> (mapped from an archive), and this code is not performance-critical,
>> so the specialization does not add vale.
>
> LGTM, thanks.
>
> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Fixed (also “val[u]e”).

Thanks,
Florian


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 15/26] locale: Add more cached data to LC_CTYPE
  2022-05-20 18:29   ` Adhemerval Zanella
@ 2022-05-23  7:20     ` Florian Weimer
  0 siblings, 0 replies; 49+ messages in thread
From: Florian Weimer @ 2022-05-23  7:20 UTC (permalink / raw)
  To: Adhemerval Zanella; +Cc: libc-alpha

* Adhemerval Zanella:

> On 17/03/2022 16:30, Florian Weimer via Libc-alpha wrote:
>> This data will be used in number formatting.
>
> Looks ok, some minor comments below.
>
> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

>> diff --git a/locale/loadlocale.c b/locale/loadlocale.c
>> index 9069bafcd8..ce78dfd071 100644
>> --- a/locale/loadlocale.c
>> +++ b/locale/loadlocale.c
>> @@ -31,7 +31,6 @@
>>  #include <not-cancel.h>
>>  #include "localeinfo.h"
>>  
>> -
>
> Spurious line removal.

Fixed.

>>  static const size_t _nl_category_num_items[] =
>>  {
>>  #define DEFINE_CATEGORY(category, category_name, items, a) \
>> @@ -62,6 +61,61 @@ static const enum value_type *const _nl_value_types[] =
>>  #undef DEFINE_CATEGORY
>>  };
>>  
>> +/* Fill in LOCDATA->private for the LC_CTYPE category.  */
>> +static void
>> +_nl_intern_locale_data_fill_cache_ctype (struct __locale_data *locdata)
>> +{
>> +  struct lc_ctype_data *data = locdata->private;
>> +
>> +  /* Default to no translation.  Assumes zero initialization of *data.  */
>> +  memset (data->outdigit_bytes, 1, 10);
>
> Use sizeof data->outdigit_bytes here.

Also fixed.

>> +/* Ancillary data for LC_CTYPE.  Co-allocated after struct
>> +   __locale_data by _nl_intern_locale_data.  */
>> +struct lc_ctype_data
>> +{
>> +  /* See get_gconv_fcts and __wcsmbs_load_conv.  */
>> +  const struct gconv_fcts *fcts;
>> +
>> +  /* If false, outdigit just maps to the ASCII digits.  */
>> +  bool outdigit_translation_needed;
>> +
>> +  /* Cached multi-byte string lengths.  This could be added to the
>> +     locale data itself if the format is changed (which impacts
>> +     existing statically linked binaries).  */
>> +
>> +  /* For the outdigit decimal digits (copied from LC_CTYPE).  */
>> +  unsigned char outdigit_bytes[10];
>> +
>> +  /* If all outdigit_bytes elements are equal, this is that value,
>> +     otherwise it is 0.  */
>> +  unsigned char outdigit_bytes_all_equal;
>
> Why not _Bool?

And use outdigit_bytes[0] instead of this field?  The use looks like
this:

  if (ctype->outdigit_bytes_all_equal > 0)
    return (last - first) * ctype->outdigit_bytes_all_equal;

I thought this was simple enough.

Thanks,
Florian


^ permalink raw reply	[flat|nested] 49+ messages in thread

end of thread, other threads:[~2022-05-23  7:20 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-17 19:28 [PATCH 00/26] vfprintf rework to remove vtables Florian Weimer
2022-03-17 19:28 ` [PATCH 01/26] libio: Convert tst_swprintf to the test framework Florian Weimer
2022-03-18 17:40   ` Adhemerval Zanella
2022-03-17 19:28 ` [PATCH 02/26] libio: Flush-only _IO_str_overflow must not return EOF (bug 28949) Florian Weimer
2022-03-18 18:11   ` Adhemerval Zanella
2022-03-17 19:28 ` [PATCH 03/26] stdio-common: Add wide stream coverage to tst-vfprintf-user-type Florian Weimer
2022-03-18 18:30   ` Adhemerval Zanella
2022-03-18 19:19     ` Florian Weimer
2022-03-17 19:28 ` [PATCH 04/26] stdio-common: Add tst-printf-width-i18n to cover numeric field width Florian Weimer
2022-05-20 13:22   ` Adhemerval Zanella
2022-05-20 13:33     ` Adhemerval Zanella
2022-05-23  6:39       ` Florian Weimer
2022-03-17 19:28 ` [PATCH 05/26] vfprintf: Move argument processing into vfprintf-process-arg.c Florian Weimer
2022-05-20 13:28   ` Adhemerval Zanella
2022-03-17 19:28 ` [PATCH 06/26] vfprintf: Consolidate some multibyte/wide character processing Florian Weimer
2022-05-20 14:16   ` Adhemerval Zanella
2022-03-17 19:29 ` [PATCH 07/26] __printf_fphex always uses LC_NUMERIC Florian Weimer
2022-05-20 14:21   ` Adhemerval Zanella
2022-05-23  6:55     ` Florian Weimer
2022-03-17 19:29 ` [PATCH 08/26] stdio-common: Add tst-memstream-string for open_memstream overflow Florian Weimer
2022-05-20 17:44   ` Adhemerval Zanella
2022-05-23  7:03     ` Florian Weimer
2022-03-17 19:29 ` [PATCH 09/26] stdio-common: Add printf specifier registry to <printf.h> Florian Weimer
2022-05-20 17:49   ` Adhemerval Zanella
2022-03-17 19:30 ` [PATCH 10/26] stdio-common: Move union printf_arg int <printf.h> Florian Weimer
2022-05-20 17:51   ` Adhemerval Zanella
2022-03-17 19:30 ` [PATCH 11/26] stdio-common: Simplify printf_unknown interface in vfprintf-internal.c Florian Weimer
2022-05-20 18:07   ` Adhemerval Zanella
2022-03-17 19:30 ` [PATCH 12/26] locale: Call _nl_unload_locale from _nl_archive_subfreeres Florian Weimer
2022-05-20 18:09   ` Adhemerval Zanella
2022-05-23  7:14     ` Florian Weimer
2022-03-17 19:30 ` [PATCH 13/26] locale: Remove cleanup function pointer from struct __localedata Florian Weimer
2022-05-20 18:16   ` Adhemerval Zanella
2022-03-17 19:30 ` [PATCH 14/26] locale: Remove private union from struct __locale_data Florian Weimer
2022-05-20 18:22   ` Adhemerval Zanella
2022-03-17 19:30 ` [PATCH 15/26] locale: Add more cached data to LC_CTYPE Florian Weimer
2022-05-20 18:29   ` Adhemerval Zanella
2022-05-23  7:20     ` Florian Weimer
2022-03-17 19:31 ` [PATCH 16/26] locale: Implement struct grouping_iterator Florian Weimer
2022-03-17 19:31 ` [PATCH 17/26] stdio-common: Introduce buffers for implementing printf Florian Weimer
2022-03-17 19:31 ` [PATCH 18/26] stdio-common: Add __printf_function_invoke Florian Weimer
2022-03-17 19:31 ` [PATCH 19/26] stdio-common: Add __translated_number_width Florian Weimer
2022-03-17 19:31 ` [PATCH 20/26] stdio-common: Convert vfprintf and related functions to buffers Florian Weimer
2022-03-17 19:31 ` [PATCH 21/26] stdio-common: Add lock optimization to vfprintf and vfwprintf Florian Weimer
2022-03-17 19:31 ` [PATCH 22/26] libio: Convert __vsprintf_internal to buffers Florian Weimer
2022-03-17 19:31 ` [PATCH 23/26] libio: Convert __vasprintf_internal " Florian Weimer
2022-03-17 19:31 ` [PATCH 24/26] libio: Convert __vdprintf_internal " Florian Weimer
2022-03-17 19:32 ` [PATCH 25/26] libio: Convert __obstack_vprintf_internal to buffers (bug 27124) Florian Weimer
2022-03-17 19:32 ` [PATCH 26/26] libio: Convert __vswprintf_internal to buffers (bug 27857) Florian Weimer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).