.::
+ %.::
+ %*:::
+ %*.:::
+ %.*:::
+ %*.*::::
+
+ Return 0 on success, -1 on failure. */
+
+static int
+do_printf (char *fmt, size_t idx,
+ const struct wp *w, bool point, const struct wp *p,
+ const char *l, char c, type_t val)
+{
+ int wpval[2] = { 0 };
+ size_t nint = 0;
+ int result;
+ size_t i;
+
+ if (w != NULL)
+ {
+ if (w->s == NULL)
+ {
+ fmt[idx++] = '*';
+ wpval[nint++] = w->i;
+ }
+ else
+ for (i = 0; w->s[i] != '\0'; i++)
+ fmt[idx++] = w->s[i];
+ }
+ if (point)
+ fmt[idx++] = '.';
+ if (p != NULL)
+ {
+ if (p->s == NULL)
+ {
+ fmt[idx++] = '*';
+ wpval[nint++] = p->i;
+ }
+ else
+ for (i = 0; p->s[i] != '\0'; i++)
+ fmt[idx++] = p->s[i];
+ }
+ for (i = 0; length[i] != '\0'; i++)
+ fmt[idx++] = length[i];
+ fmt[idx++] = c;
+ fmt[idx] = ':';
+ fmt[idx + 1] = '\0';
+ if (fputs (fmt, stdout) == EOF)
+ {
+ perror ("fputs");
+ return -1;
+ }
+ fmt[idx++] = '\0';
+ if (nint > 0)
+ {
+ result = printf ("%i:", wpval[0]);
+ if (result < 0)
+ {
+ perror ("printf");
+ return -1;
+ }
+ if (nint > 1)
+ {
+ result = printf ("%i:", wpval[1]);
+ if (result < 0)
+ {
+ perror ("printf");
+ return -1;
+ }
+ }
+ }
+ switch (nint)
+ {
+ case 0:
+ result = printf_under_test (fmt, val);
+ break;
+ case 1:
+ result = printf_under_test (fmt, wpval[0], val);
+ break;
+ case 2:
+ result = printf_under_test (fmt, wpval[0], wpval[1], val);
+ break;
+ default:
+ fputs ("Broken test, nint > 2\n", stderr);
+ return -1;
+ }
+ if (result < 0)
+ return -1;
+ if (fputs (":\n", stdout) == EOF)
+ {
+ perror ("fputs");
+ return -1;
+ }
+ return 0;
+}
+
+/* Produce a list of records according to '%' and zero or more output
+ format flags already provided in FMT at indices 0..IDX-1, iterating
+ over widths and precisions defined in global WP array, any length
+ modifiers L, conversion C, and value VAL. Inline '0' is omitted for
+ the width, as it is a flag already handled among the flags supplied.
+ Precision is omitted where the conversion does not allow it.
+
+ Return 0 on success, -1 on failure. */
+
+static int
+do_printf_flags (char *fmt, size_t idx, const char *l, char c, type_t val)
+{
+ bool do_prec = strchr (PREC_FORMATS, c) != NULL;
+ size_t i;
+
+ if (do_printf (fmt, idx, NULL, false, NULL, l, c, val) < 0)
+ return -1;
+ if (do_prec && do_printf (fmt, idx, NULL, true, NULL, l, c, val) < 0)
+ return -1;
+ for (i = 0; i < array_length (wp); i++)
+ {
+ size_t j;
+
+ if (do_prec && do_printf (fmt, idx, NULL, true, wp + i, l, c, val) < 0)
+ return -1;
+ /* Inline '0' is a flag rather than width and is handled elsewhere. */
+ if (wp[i].s != NULL && wp[i].s[0] == '0' && wp[i].s[1] == '\0')
+ continue;
+ if (do_printf (fmt, idx, wp + i, false, NULL, l, c, val) < 0)
+ return -1;
+ if (do_prec)
+ {
+ if (do_printf (fmt, idx, wp + i, true, NULL, l, c, val) < 0)
+ return -1;
+ for (j = 0; j < array_length (wp); j++)
+ if (do_printf (fmt, idx, wp + i, true, wp + j, l, c, val) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* Produce a list of records using the formatted output specifier
+ supplied in ARGV[1] preceded by any length modifier supplied in
+ the global LENGTH variable, iterating over format flags defined
+ in the global FLAGS array, and values supplied in the global VALS
+ array. Note that the output specifier supplied is not verified
+ against TYPE_T, so undefined behavior will result if this is used
+ incorrectly.
+
+ If PREC is nonzero, then this record:
+
+ prec:
+
+ is produced at the beginning. Then for each VAL from VALS a block
+ of records is produced starting with:
+
+ val:
+
+ where VAL is formatted according to REF_FMT output format. The
+ block continues with records as shown with DO_PRINTF above using
+ flags iterated over according to TST_PRINTF_DUPS.
+
+ See the top of this file for the definitions that have to be
+ provided by the source including this skeleton. */
+
+static int
+do_test (int argc, char *argv[])
+{
+ char fmt[100] = {'%'};
+ size_t j;
+ size_t v;
+ char c;
+
+ if (argc < 2 || *argv[1] == '\0')
+ {
+ fprintf (stderr, "Usage: %s \n", basename (argv[0]));
+ return EXIT_FAILURE;
+ }
+
+ mtrace ();
+
+ if (PREC && printf ("prec:%i\n", PREC) < 0)
+ {
+ perror ("printf");
+ return EXIT_FAILURE;
+ }
+
+ c = *argv[1];
+ for (v = 0; v < array_length (vals); v++)
+ {
+ if (printf ("val:%" REF_FMT "\n", REF_VAL (vals[v])) < 0)
+ {
+ perror ("printf");
+ return EXIT_FAILURE;
+ }
+
+ if (do_printf_flags (fmt, 1, length, c, vals[v]) < 0)
+ return EXIT_FAILURE;
+ for (j = 0; j < array_length (flags); j++)
+ {
+ bool done = false;
+ size_t i[j + 1];
+ size_t k;
+
+ memset (i, 0, sizeof (i));
+ while (!done)
+ {
+ bool skip = false;
+ size_t idx = 1;
+ char f;
+
+ for (k = 0; k <= j; k++)
+ {
+ const char *s = flags[i[k]].s;
+
+ if (s && strchr (s, c) == NULL)
+ skip = true;
+ if (!TST_PRINTF_DUPS && j > 1 && k > 0 && i[k] >= i[k - 1])
+ skip = true;
+ if (skip)
+ break;
+
+ f = flags[i[k]].f;
+ fmt[idx++] = f;
+ }
+ if (!skip && do_printf_flags (fmt, idx, length, c, vals[v]) < 0)
+ return EXIT_FAILURE;
+ for (k = 0; k <= j; k++)
+ {
+ i[k]++;
+ if (i[k] < array_length (flags))
+ break;
+ else if (k == j)
+ done = true;
+ else
+ i[k] = 0;
+ }
+ }
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/* Interpose 'dladdr' with a stub to speed up malloc tracing. */
+
+int
+dladdr (const void *, Dl_info *)
+{
+ return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include
diff --git a/stdio-common/tst-printf-format-uchar.sh b/stdio-common/tst-printf-format-uchar.sh
new file mode 100644
index 0000000000..8385d479cc
--- /dev/null
+++ b/stdio-common/tst-printf-format-uchar.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# Testing of unsigned char printf conversions.
+# Copyright (C) 2024 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
+# .
+
+set -e
+
+xprintf=$1; shift
+common_objpfx=$1; shift
+test_program_prefix=$1; shift
+
+AWK=${AWK:-awk}
+
+status=0
+
+for f in o u x X; do
+ echo Verifying $f
+ (set -o pipefail
+ ${test_program_prefix} \
+ ${common_objpfx}stdio-common/tst-printf-format-${xprintf}-uchar $f |
+ $AWK -f tst-printf-format.awk 2>&1 |
+ head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
+ status=1
+done
+
+exit $status
diff --git a/stdio-common/tst-printf-format-uint.sh b/stdio-common/tst-printf-format-uint.sh
new file mode 100644
index 0000000000..6806c99ce0
--- /dev/null
+++ b/stdio-common/tst-printf-format-uint.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+# Testing of unsigned int printf conversions.
+# Copyright (C) 2024 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
+# .
+
+set -e
+
+xprintf=$1; shift
+common_objpfx=$1; shift
+test_program_prefix=$1; shift
+
+AWK=${AWK:-awk}
+
+status=77
+
+# Verify that AWK can handle the range required. It also catches:
+# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
+# message produced where bignum support is not there, which is the
+# only indication as the use of '-M' does not affect the exit status
+# in this case.
+ref="4294967295"
+for AWK in "$AWK -M" "$AWK"; do
+ val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
+ test "$val" = "$ref" && status=0 && break
+done
+
+test $status -eq 0 || { echo "No working AWK found" && exit $status; }
+
+for f in o u x X; do
+ echo Verifying $f
+ (set -o pipefail
+ ${test_program_prefix} \
+ ${common_objpfx}stdio-common/tst-printf-format-${xprintf}-uint $f |
+ $AWK -f tst-printf-format.awk 2>&1 |
+ head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
+ status=1
+done
+
+exit $status
diff --git a/stdio-common/tst-printf-format-ullong.sh b/stdio-common/tst-printf-format-ullong.sh
new file mode 100644
index 0000000000..c4fa69c2d1
--- /dev/null
+++ b/stdio-common/tst-printf-format-ullong.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+# Testing of unsigned long long int printf conversions.
+# Copyright (C) 2024 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
+# .
+
+set -e
+
+xprintf=$1; shift
+common_objpfx=$1; shift
+test_program_prefix=$1; shift
+
+AWK=${AWK:-awk}
+
+status=77
+
+# Verify that AWK can handle the range required. It also catches:
+# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
+# message produced where bignum support is not there, which is the
+# only indication as the use of '-M' does not affect the exit status
+# in this case.
+ref="18446744073709551615"
+for AWK in "$AWK -M" "$AWK"; do
+ val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
+ test "$val" = "$ref" && status=0 && break
+done
+
+test $status -eq 0 || { echo "No working AWK found" && exit $status; }
+
+for f in o u x X; do
+ echo Verifying $f
+ (set -o pipefail
+ ${test_program_prefix} \
+ ${common_objpfx}stdio-common/tst-printf-format-${xprintf}-ullong $f |
+ $AWK -f tst-printf-format.awk 2>&1 |
+ head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
+ status=1
+done
+
+exit $status
diff --git a/stdio-common/tst-printf-format-ulong.sh b/stdio-common/tst-printf-format-ulong.sh
new file mode 100644
index 0000000000..fce881afe3
--- /dev/null
+++ b/stdio-common/tst-printf-format-ulong.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+# Testing of unsigned long int printf conversions.
+# Copyright (C) 2024 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
+# .
+
+set -e
+
+xprintf=$1; shift
+common_objpfx=$1; shift
+test_program_prefix=$1; shift
+
+AWK=${AWK:-awk}
+
+status=77
+
+# Verify that AWK can handle the range required. It also catches:
+# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
+# message produced where bignum support is not there, which is the
+# only indication as the use of '-M' does not affect the exit status
+# in this case.
+ref="18446744073709551615"
+for AWK in "$AWK -M" "$AWK"; do
+ val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
+ test "$val" = "$ref" && status=0 && break
+done
+
+test $status -eq 0 || { echo "No working AWK found" && exit $status; }
+
+for f in o u x X; do
+ echo Verifying $f
+ (set -o pipefail
+ ${test_program_prefix} \
+ ${common_objpfx}stdio-common/tst-printf-format-${xprintf}-ulong $f |
+ $AWK -f tst-printf-format.awk 2>&1 |
+ head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
+ status=1
+done
+
+exit $status
diff --git a/stdio-common/tst-printf-format-ushort.sh b/stdio-common/tst-printf-format-ushort.sh
new file mode 100644
index 0000000000..2f411b777e
--- /dev/null
+++ b/stdio-common/tst-printf-format-ushort.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# Testing of unsigned short int printf conversions.
+# Copyright (C) 2024 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
+# .
+
+set -e
+
+xprintf=$1; shift
+common_objpfx=$1; shift
+test_program_prefix=$1; shift
+
+AWK=${AWK:-awk}
+
+status=0
+
+for f in o u x X; do
+ echo Verifying $f
+ (set -o pipefail
+ ${test_program_prefix} \
+ ${common_objpfx}stdio-common/tst-printf-format-${xprintf}-ushort $f |
+ $AWK -f tst-printf-format.awk 2>&1 |
+ head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
+ status=1
+done
+
+exit $status
diff --git a/stdio-common/tst-printf-format.awk b/stdio-common/tst-printf-format.awk
new file mode 100644
index 0000000000..8b4bc7b1e4
--- /dev/null
+++ b/stdio-common/tst-printf-format.awk
@@ -0,0 +1,127 @@
+# Testing of printf conversions.
+# Copyright (C) 2024 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
+# .
+
+BEGIN {
+ FS = ":"
+}
+
+/^prec:/ {
+ PREC = $2
+ next
+}
+
+/^val:/ {
+ val = $2
+ # Prepend "+" for +Inf or +NaN value lacking a sign, because gawk
+ # interpretes them as strings rather than numeric values in the
+ # non-bignum mode unless a sign has been explicitly given. Keep
+ # original 'val' for reporting.
+ value = gensub(/^(INF|NAN|inf|nan)/, "+\\1", 1, val)
+ next
+}
+
+/^%/ {
+ # Discard the trailing empty field, used to improve legibility of data.
+ input = $--NF
+ format = $1
+ width = $2
+ precision = "." $(NF - 1)
+ # Discard any negative precision, which is to be taken as if omitted.
+ sub(/\.-.*/, "", precision)
+ # Simplify handling and paste the precision and width specified as
+ # arguments to '*' directly into the format.
+ sub(/\.\*/, precision, format)
+ sub(/\*/, width, format)
+ # Discard length modifiers. They are only relevant to C data types.
+ sub(/([DHLjhltz]|wf?[1-9][0-9]*)/, "", format)
+ # Discard the '#' flag with the octal conversion if output starts with
+ # 0 in the absence of this flag. In that case no extra 0 is supposed
+ # to be produced, but gawk prepends it anyway.
+ if (format ~ /#.*o/)
+ {
+ tmpfmt = gensub(/#/, "", "g", format)
+ tmpout = sprintf(tmpfmt, value)
+ if (tmpout ~ /^ *0/)
+ format = tmpfmt
+ }
+ # Likewise with the hexadecimal conversion where zero value with the
+ # precision of zero is supposed to produce no characters, but gawk
+ # outputs 0 instead.
+ else if (format ~ /#.*[Xx]/)
+ {
+ tmpfmt = gensub(/#/, "", "g", format)
+ tmpout = sprintf(tmpfmt, value)
+ if (tmpout ~ /^ *$/)
+ format = tmpfmt
+ }
+ # AWK interpretes input opportunistically as a number, which interferes
+ # with how the 'c' conversion works: "a" input will result in "a" output
+ # however "0" input will result in "^@" output rather than "0". Force
+ # the value to be interpreted as a string then, by appending "".
+ output = sprintf(format, value "")
+ # Make up for various anomalies with the handling of +/-Inf and +/-NaN
+ # values and reprint the output produced using the string conversion,
+ # with the field width carried over and the relevant flags handled by
+ # hand.
+ if (format ~ /[EFGefg]/ && value ~ /(INF|NAN|inf|nan)/)
+ {
+ minus = format ~ /-/ ? "-" : ""
+ sign = value ~ /-/ ? "-" : format ~ /\+/ ? "+" : format ~ / / ? " " : ""
+ if (format ~ /^%[^\.1-9]*[1-9][0-9]*/)
+ width = gensub(/^%[^\.1-9]*([1-9][0-9]*).*$/, "\\1", 1, format)
+ else
+ width = ""
+ output = gensub(/[-+ ]/, "", "g", output)
+ output = sprintf("%" minus width "s", sign output)
+ }
+ # Produce "+" where the '+' flag has been used with a signed integer
+ # conversion for zero value, observing any field width in effect.
+ # In that case "+" is always supposed to be produced, but with the
+ # precision of zero gawk in the non-bignum mode produces any padding
+ # requested only.
+ else if (format ~ /\+.*[di]/ && value == 0)
+ {
+ output = gensub(/^( *) $/, format ~ /-/ ? "+\\1" : "\\1+", 1, output)
+ output = gensub(/^$/, "+", 1, output)
+ }
+ # Produce " " where the space flag has been used with a signed integer
+ # conversion for zero value. In that case at least one " " is
+ # supposed to be produced, but with the precision of zero gawk in the
+ # non-bignum mode produces nothing.
+ else if (format ~ / .*[di]/ && value == 0)
+ {
+ output = gensub(/^$/, " ", 1, output)
+ }
+ if (output != input)
+ {
+ printf "(\"%s\"%s%s, %s) => \"%s\", expected \"%s\"\n", \
+ $1, (NF > 2 ? ", " $2 : ""), (NF > 3 ? ", " $3 : ""), val, \
+ input, output > "/dev/stderr"
+ status = 1
+ }
+ next
+}
+
+{
+ printf "unrecognized input: \"%s\"\n", $0 > "/dev/stderr"
+ status = 1
+}
+
+END {
+ exit status
+}
diff --git a/stdio-common/tst-printf-format.sh b/stdio-common/tst-printf-format.sh
new file mode 100644
index 0000000000..466c4a5f4f
--- /dev/null
+++ b/stdio-common/tst-printf-format.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# Formatted printf output test script dispatcher.
+# Copyright (C) 2024 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
+# .
+
+set -e
+
+output=${1##*/}; shift
+
+tmp=${output#tst-printf-format-}
+tmp=${tmp%.out}
+
+# We are given the name of the make target in $1. With the common prefix
+# and suffix both removed we are left with the inner part, which encodes
+# the function under test, the conversion type, and optionally the format
+# specifier, all separated with hyphens, i.e. F-T-S or F-T. Extract them
+# and call the script corresponding to the conversion type, passing the
+# function under test and any format specifier as arguments.
+
+xprintf=${tmp%%-*}; tmp=${tmp#*-}
+conv=${tmp%%-*}; tmp=${tmp#${conv}}
+fmt=${tmp#*-}
+script=tst-printf-format-$conv.sh
+
+exec ${BASH:-bash} $script $xprintf $fmt "$@"