diff --git a/localedata/Makefile b/localedata/Makefile
index 5d2ebd0256..01b32348d4 100644
--- a/localedata/Makefile
+++ b/localedata/Makefile
@@ -236,6 +236,7 @@ tests = \
bug-iconv-trans \
bug-setlocale1 \
bug-usesetlocale \
+ tst-bz12701-lc \
tst-bz13988 \
tst-c-utf8-consistency \
tst-digits \
diff --git a/localedata/tst-bz12701-lc.c b/localedata/tst-bz12701-lc.c
new file mode 100644
index 0000000000..fcc9f29a14
--- /dev/null
+++ b/localedata/tst-bz12701-lc.c
@@ -0,0 +1,224 @@
+/* Verify scanf field width handling with the 'lc' conversion (BZ #12701).
+ Copyright (C) 2025 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
+ . */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+/* Compare character-wise the initial part of the wide character object
+ pointed to by WS corresponding to wide characters obtained by the
+ conversion of first N bytes of the multibyte character object pointed
+ to by S. */
+
+static int
+tst_bz12701_lc_memcmp (const wchar_t *ds, const char *s, size_t n)
+{
+ size_t nc = mbsnrtowcs (NULL, &s, n, 0, NULL);
+
+ struct support_next_to_fault ntf;
+ ntf = support_next_to_fault_allocate (nc * sizeof (wchar_t));
+ wchar_t *ss = (wchar_t *) ntf.buffer;
+
+ mbsnrtowcs (ss, &s, n, nc, NULL);
+ int r = wmemcmp (ds, ss, nc);
+
+ support_next_to_fault_free (&ntf);
+
+ return r;
+}
+
+/* Verify various aspects of field width handling, including the data
+ obtained, the number of bytes consumed, and the stream position. */
+
+static int
+do_test (void)
+{
+ if (setlocale (LC_ALL, "pl_PL.UTF-8") == NULL)
+ FAIL_EXIT1 ("setlocale (LC_ALL, \"pl_PL.UTF-8\")");
+
+ /* Part of a tongue-twister in Polish, which says:
+ "On a rainy morning cuckoos and warblers, rather than starting
+ on earthworms, stuffed themselves fasted with the flesh of cress." */
+ static const char s[126] = "Dżdżystym rankiem gżegżółki i piegże, "
+ "zamiast wziąć się za dżdżownice, "
+ "nażarły się na czczo miąższu rzeżuchy";
+
+ const char *sp = s;
+ size_t nc;
+ TEST_VERIFY_EXIT ((nc = mbsnrtowcs (NULL, &sp, sizeof (s), 0, NULL)) == 108);
+
+ struct support_next_to_fault ntfo, ntfi;
+ ntfo = support_next_to_fault_allocate (nc * sizeof (wchar_t));
+ ntfi = support_next_to_fault_allocate (sizeof (s));
+ wchar_t *e = (wchar_t *) ntfo.buffer + nc;
+ char *b = ntfi.buffer;
+
+ wchar_t *c;
+ FILE *f;
+ int ic;
+ int n;
+ int i;
+
+ memcpy (ntfi.buffer, s, sizeof (s));
+
+ ic = i = 0;
+ f = fmemopen (b, sizeof (s), "r");
+ if (f == NULL)
+ FAIL_EXIT1 ("fmemopen: %m");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ /* Avoid: "warning: zero width in gnu_scanf format [-Werror=format=]". */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
+ TEST_VERIFY_EXIT (fscanf (f, "%0lc%n", c, &n) == 1);
+ DIAG_POP_NEEDS_COMMENT;
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%1lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 3);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 2;
+ i += n;
+
+ c = e - 4;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%4lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 4);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 4;
+ i += n;
+
+ c = e - 8;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%8lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 8);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 8;
+ i += n;
+
+ c = e - 16;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%16lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 20);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 16;
+ i += n;
+
+ c = e - 32;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%32lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 38);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 32;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%64lc%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 38);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, sizeof (s) - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == sizeof (s));
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ ic = i = 0;
+ f = fmemopen (b, 3, "r");
+ if (f == NULL)
+ FAIL_EXIT1 ("fmemopen: %m");
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 3);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 2;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (feof (f) == 0);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 3);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ ic = i = 0;
+ f = fmemopen (b, 3, "r");
+ if (f == NULL)
+ FAIL_EXIT1 ("fmemopen: %m");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, 3 - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ support_next_to_fault_free (&ntfi);
+ support_next_to_fault_free (&ntfo);
+
+ return 0;
+}
+
+#include
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index ae2b90c2ad..8da164695f 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -260,6 +260,7 @@ tests := \
tllformat \
tst-bz11319 \
tst-bz11319-fortify2 \
+ tst-bz12701-c \
tst-cookie \
tst-dprintf-length \
tst-fclose-devzero \
diff --git a/stdio-common/tst-bz12701-c.c b/stdio-common/tst-bz12701-c.c
new file mode 100644
index 0000000000..ffb4330bfb
--- /dev/null
+++ b/stdio-common/tst-bz12701-c.c
@@ -0,0 +1,175 @@
+/* Verify scanf field width handling with the 'c' conversion (BZ #12701).
+ Copyright (C) 2025 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
+ . */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+/* Verify various aspects of field width handling, including the data
+ obtained, the number of bytes consumed, and the stream position. */
+
+static int
+do_test (void)
+{
+ static const char s[43] = "The quick brown fox jumps over the lazy dog";
+ struct support_next_to_fault ntfo, ntfi;
+ ntfo = support_next_to_fault_allocate (sizeof (s));
+ ntfi = support_next_to_fault_allocate (sizeof (s));
+ char *e = ntfo.buffer + sizeof (s);
+ char *b = ntfi.buffer;
+
+ char *c;
+ FILE *f;
+ int n;
+ int i;
+
+ memcpy (ntfi.buffer, s, sizeof (s));
+
+ i = 0;
+ f = fmemopen (b, sizeof (s), "r");
+ if (f == NULL)
+ FAIL_EXIT1 ("fmemopen: %m");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ /* Avoid: "warning: zero width in gnu_scanf format [-Werror=format=]". */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
+ TEST_VERIFY_EXIT (fscanf (f, "%0c%n", c, &n) == 1);
+ DIAG_POP_NEEDS_COMMENT;
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%1c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 4;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%4c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 4);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 8;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%8c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 8);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 16;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%16c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 16);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (sizeof (s) - i);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%32c%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 16);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, sizeof (s) - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == sizeof (s));
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ i = 0;
+ f = fmemopen (b, 3, "r");
+ if (f == NULL)
+ FAIL_EXIT1 ("fmemopen: %m");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (3 - i);
+ TEST_VERIFY_EXIT (feof (f) == 0);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 2);
+
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ i = 0;
+ f = fmemopen (b, 3, "r");
+ if (f == NULL)
+ FAIL_EXIT1 ("fmemopen: %m");
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (3 - i);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, 3 - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ support_next_to_fault_free (&ntfi);
+ support_next_to_fault_free (&ntfo);
+
+ return 0;
+}
+
+#include
diff --git a/stdio-common/tst-scanf-format-c-c.input b/stdio-common/tst-scanf-format-c-c.input
index a3a6ee26af..67c78651ba 100644
--- a/stdio-common/tst-scanf-format-c-c.input
+++ b/stdio-common/tst-scanf-format-c-c.input
@@ -22,30 +22,14 @@
%*2c:brown fox:0:2:
%2c:jumps over the lazy dog:1:2:ju:
%*2c:jumps over the lazy dog:0:2:
-# BZ12701 %5c:The:0:-1:
-# BZ12701 %*5c:The:0:-1:
%5c:quick:1:5:quick:
%*5c:quick:0:5:
%5c:brown fox:1:5:brown:
%*5c:brown fox:0:5:
%5c:jumps over the lazy dog:1:5:jumps:
%*5c:jumps over the lazy dog:0:5:
-# BZ12701 %10c:The:0:-1:
-# BZ12701 %*10c:The:0:-1:
-# BZ12701 %10c:quick:0:-1:
-# BZ12701 %*10c:quick:0:-1:
-# BZ12701 %10c:brown fox:0:-1:
-# BZ12701 %*10c:brown fox:0:-1:
%10c:jumps over the lazy dog:1:10:jumps over:
%*10c:jumps over the lazy dog:0:10:
-# BZ12701 %25c:The:0:-1:
-# BZ12701 %*25c:The:0:-1:
-# BZ12701 %25c:quick:0:-1:
-# BZ12701 %*25c:quick:0:-1:
-# BZ12701 %25c:brown fox:0:-1:
-# BZ12701 %*25c:brown fox:0:-1:
-# BZ12701 %25c:jumps over the lazy dog:0:-1:
-# BZ12701 %*25c:jumps over the lazy dog:0:-1:
%5c: The :1:5: The :
%*5c: The :0:5:
%5c: quick :1:5: quic:
@@ -54,11 +38,5 @@
%*5c: brown fox :0:5:
%5c: jumps over the lazy dog :1:5: jump:
%*5c: jumps over the lazy dog :0:5:
-# BZ12701 %25c: The :0:-1:
-# BZ12701 %*25c: The :0:-1:
-# BZ12701 %25c: quick :0:-1:
-# BZ12701 %*25c: quick :0:-1:
-# BZ12701 %25c: brown fox :0:-1:
-# BZ12701 %*25c: brown fox :0:-1:
%25c: jumps over the lazy dog :1:25: jumps over the lazy dog :
%*25c: jumps over the lazy dog :0:25:
diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
index daeb068dda..d8facb6e89 100644
--- a/stdio-common/vfscanf-internal.c
+++ b/stdio-common/vfscanf-internal.c
@@ -898,6 +898,8 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
else
while (--width > 0 && inchar () != EOF);
#endif
+ if (width > 0)
+ input_error ();
if (!(flags & SUPPRESS))
{
@@ -1051,6 +1053,8 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
while (--width > 0 && inchar () != EOF);
}
#endif
+ if (width > 0)
+ input_error ();
if (!(flags & SUPPRESS))
{