Recursive formats such as %F would double count the written bytes,
thus not/partially writing unless the buffer was 2x the required size.
This was exacerbated by an existing issue where recursive failures
were not propagated.
This can be triggered with:
$ ls -ld --time-style='+%504F' /dev/null
The accounting issue was introduced in commit 42ea6679 (Nov 2025),
which moved the output count increment before the recursive call,
reducing its available space.
* lib/strftime.c (subformat_width): Pass the computed length plus one
as the recursive call's buffer size, since add has already accounted
for the output length and padding. Also return FAILURE upon failure
to write the expected length, even though this generally won't happen
now that correct accounting is in place, though fprintftime() might
still fail with e.g. EIO.
* tests/test-nstrftime.h: Add test cases.
---
lib/strftime.c | 19 ++++++++++++-------
tests/test-nstrftime.h | 12 ++++++++++++
2 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/lib/strftime.c b/lib/strftime.c
index 5915acad38..76c7e57b70 100644
--- a/lib/strftime.c
+++ b/lib/strftime.c
@@ -1658,13 +1658,18 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG
(size_t maxsize)
extra_args LOCALE_ARG);
if (FAILURE < 0 && len < 0)
return FAILURE; /* errno is set here */
- add (len, __strftime_internal (p,
- STRFTIME_ARG (maxsize - i)
- subfmt, tp,
- CAL_ARGS (cal, caldate)
- to_uppcase, subpad, subwidth,
- tzset_called
- extra_args LOCALE_ARG));
+ /* ADD accounts for LEN and any padding before evaluating its
+ second argument, so MAXSIZE - I no longer includes LEN. */
+ retval_t sublen = len;
+ add (len, sublen = __strftime_internal (p,
+ STRFTIME_ARG (len + 1)
+ subfmt, tp,
+ CAL_ARGS (cal, caldate)
+ to_uppcase, subpad,
subwidth,
+ tzset_called
+ extra_args LOCALE_ARG));
+ if (sublen != len)
+ return FAILURE;
}
break;
diff --git a/tests/test-nstrftime.h b/tests/test-nstrftime.h
index 73096af17a..7b9e04f387 100644
--- a/tests/test-nstrftime.h
+++ b/tests/test-nstrftime.h
@@ -52,6 +52,9 @@ static struct posixtm_test const T[] =
{ 1300000000, 0, "%F", "2011-03-13" },
{ 1086393600, 0, "%_D", "06/05/04" },
{ 1086393600, 0, "%-D", "06/05/04" },
+ { 0, 0, "%T", "00:00:00" },
+ { 0, 0, "%R", "00:00" },
+ { 0, 0, "x%_12T!", "x 00:00:00!" },
{ 0, 10, "%T.%N", "00:00:00.000000010" },
{ 56, 123456789, "%T.%12N", "00:00:56.123456789000" },
{ 0, 123000000, "%T.%_6N", "00:00:00.123 " },
@@ -93,6 +96,15 @@ posixtm_test (void)
fflush (stdout);
}
+ /* An exact-fit buffer must produce the complete result, including
+ for recursive formats such as %F, %D, and %T. */
+ size_t size = strlen (T[i].exp) + 1;
+ memset (buf, '~', sizeof buf);
+ ASSERT (FUNC (buf, size, T[i].fmt, tm, NULL, T[i].in_ns)
+ == size - 1);
+ ASSERT (memcmp (buf, T[i].exp, size) == 0);
+ ASSERT (buf[size] == '~');
+
/* A one-byte buffer must fail without writing beyond its limit.
In particular, it is too small for the padding in %_10z. */
memset (buf, '~', sizeof buf - 1);
--
2.56.0