|
|
e38cb5 |
commit e1c0c00cc2bdd147bfcf362ada1443bee90465ec
|
|
|
e38cb5 |
Author: Joseph Myers <joseph@codesourcery.com>
|
|
|
e38cb5 |
Date: Tue Jul 7 14:54:12 2020 +0000
|
|
|
e38cb5 |
|
|
|
e38cb5 |
Remove most vfprintf width/precision-dependent allocations (bug 14231, bug 26211).
|
|
|
e38cb5 |
|
|
|
e38cb5 |
The vfprintf implementation (used for all printf-family functions)
|
|
|
e38cb5 |
contains complicated logic to allocate internal buffers of a size
|
|
|
e38cb5 |
depending on the width and precision used for a format, using either
|
|
|
e38cb5 |
malloc or alloca depending on that size, and with consequent checks
|
|
|
e38cb5 |
for size overflow and allocation failure.
|
|
|
e38cb5 |
|
|
|
e38cb5 |
As noted in bug 26211, the version of that logic used when '$' plus
|
|
|
e38cb5 |
argument number formats are in use is missing the overflow checks,
|
|
|
e38cb5 |
which can result in segfaults (quite possibly exploitable, I didn't
|
|
|
e38cb5 |
try to work that out) when the width or precision is in the range
|
|
|
e38cb5 |
0x7fffffe0 through 0x7fffffff (maybe smaller values as well in the
|
|
|
e38cb5 |
wprintf case on 32-bit systems, when the multiplication by sizeof
|
|
|
e38cb5 |
(CHAR_T) can overflow).
|
|
|
e38cb5 |
|
|
|
e38cb5 |
All that complicated logic in fact appears to be useless. As far as I
|
|
|
e38cb5 |
can tell, there has been no need (outside the floating-point printf
|
|
|
e38cb5 |
code, which does its own allocations) for allocations depending on
|
|
|
e38cb5 |
width or precision since commit
|
|
|
e38cb5 |
3e95f6602b226e0de06aaff686dc47b282d7cc16 ("Remove limitation on size
|
|
|
e38cb5 |
of precision for integers", Sun Sep 12 21:23:32 1999 +0000). Thus,
|
|
|
e38cb5 |
this patch removes that logic completely, thereby fixing both problems
|
|
|
e38cb5 |
with excessive allocations for large width and precision for
|
|
|
e38cb5 |
non-floating-point formats, and the problem with missing overflow
|
|
|
e38cb5 |
checks with such allocations. Note that this does have the
|
|
|
e38cb5 |
consequence that width and precision up to INT_MAX are now allowed
|
|
|
e38cb5 |
where previously INT_MAX / sizeof (CHAR_T) - EXTSIZ or more would have
|
|
|
e38cb5 |
been rejected, so could potentially expose any other overflows where
|
|
|
e38cb5 |
the value would previously have been rejected by those removed checks.
|
|
|
e38cb5 |
|
|
|
e38cb5 |
I believe this completely fixes bugs 14231 and 26211.
|
|
|
e38cb5 |
|
|
|
e38cb5 |
Excessive allocations are still possible in the floating-point case
|
|
|
e38cb5 |
(bug 21127), as are other integer or buffer overflows (see bug 26201).
|
|
|
e38cb5 |
This does not address the cases where a precision larger than INT_MAX
|
|
|
e38cb5 |
(embedded in the format string) would be meaningful without printf's
|
|
|
e38cb5 |
return value overflowing (when it's used with a string format, or %g
|
|
|
e38cb5 |
without the '#' flag, so the actual output will be much smaller), as
|
|
|
e38cb5 |
mentioned in bug 17829 comment 8; using size_t internally for
|
|
|
e38cb5 |
precision to handle that case would be complicated by struct
|
|
|
e38cb5 |
printf_info being a public ABI. Nor does it address the matter of an
|
|
|
e38cb5 |
INT_MIN width being negated (bug 17829 comment 7; the same logic
|
|
|
e38cb5 |
appears a second time in the file as well, in the form of multiplying
|
|
|
e38cb5 |
by -1). There may be other sources of memory allocations with malloc
|
|
|
e38cb5 |
in printf functions as well (bug 24988, bug 16060). From inspection,
|
|
|
e38cb5 |
I think there are also integer overflows in two copies of "if ((width
|
|
|
e38cb5 |
-= len) < 0)" logic (where width is int, len is size_t and a very long
|
|
|
e38cb5 |
string could result in spurious padding being output on a 32-bit
|
|
|
e38cb5 |
system before printf overflows the count of output characters).
|
|
|
e38cb5 |
|
|
|
e38cb5 |
Tested for x86-64 and x86.
|
|
|
e38cb5 |
|
|
|
e38cb5 |
(cherry picked from commit 6caddd34bd7ffb5ac4f36c8e036eee100c2cc535)
|
|
|
e38cb5 |
|
|
|
e38cb5 |
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
|
|
|
e38cb5 |
index 51062a7dbf698931..d76b47bd5f932f69 100644
|
|
|
e38cb5 |
--- a/stdio-common/Makefile
|
|
|
e38cb5 |
+++ b/stdio-common/Makefile
|
|
|
e38cb5 |
@@ -64,6 +64,7 @@ tests := tstscanf test_rdwr test-popen tstgetln test-fseek \
|
|
|
e38cb5 |
tst-scanf-round \
|
|
|
e38cb5 |
tst-renameat2 \
|
|
|
e38cb5 |
tst-printf-bz25691 \
|
|
|
e38cb5 |
+ tst-vfprintf-width-prec-alloc
|
|
|
e38cb5 |
|
|
|
e38cb5 |
test-srcs = tst-unbputc tst-printf tst-printfsz-islongdouble
|
|
|
e38cb5 |
|
|
|
e38cb5 |
diff --git a/stdio-common/bug22.c b/stdio-common/bug22.c
|
|
|
e38cb5 |
index b26399acb7dfc775..e12b01731e1b4ac8 100644
|
|
|
e38cb5 |
--- a/stdio-common/bug22.c
|
|
|
e38cb5 |
+++ b/stdio-common/bug22.c
|
|
|
e38cb5 |
@@ -42,7 +42,7 @@ do_test (void)
|
|
|
e38cb5 |
|
|
|
e38cb5 |
ret = fprintf (fp, "%." SN3 "d", 1);
|
|
|
e38cb5 |
printf ("ret = %d\n", ret);
|
|
|
e38cb5 |
- if (ret != -1 || errno != EOVERFLOW)
|
|
|
e38cb5 |
+ if (ret != N3)
|
|
|
e38cb5 |
return 1;
|
|
|
e38cb5 |
|
|
|
e38cb5 |
ret = fprintf (fp, "%" SN2 "d%" SN2 "d", 1, 1);
|
|
|
e38cb5 |
diff --git a/stdio-common/tst-vfprintf-width-prec-alloc.c b/stdio-common/tst-vfprintf-width-prec-alloc.c
|
|
|
e38cb5 |
new file mode 100644
|
|
|
e38cb5 |
index 0000000000000000..0a74b53a3389d699
|
|
|
e38cb5 |
--- /dev/null
|
|
|
e38cb5 |
+++ b/stdio-common/tst-vfprintf-width-prec-alloc.c
|
|
|
e38cb5 |
@@ -0,0 +1,41 @@
|
|
|
e38cb5 |
+/* Test large width or precision does not involve large allocation.
|
|
|
e38cb5 |
+ Copyright (C) 2020 Free Software Foundation, Inc.
|
|
|
e38cb5 |
+ This file is part of the GNU C Library.
|
|
|
e38cb5 |
+
|
|
|
e38cb5 |
+ The GNU C Library is free software; you can redistribute it and/or
|
|
|
e38cb5 |
+ modify it under the terms of the GNU Lesser General Public
|
|
|
e38cb5 |
+ License as published by the Free Software Foundation; either
|
|
|
e38cb5 |
+ version 2.1 of the License, or (at your option) any later version.
|
|
|
e38cb5 |
+
|
|
|
e38cb5 |
+ The GNU C Library is distributed in the hope that it will be useful,
|
|
|
e38cb5 |
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
e38cb5 |
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
e38cb5 |
+ Lesser General Public License for more details.
|
|
|
e38cb5 |
+
|
|
|
e38cb5 |
+ You should have received a copy of the GNU Lesser General Public
|
|
|
e38cb5 |
+ License along with the GNU C Library; if not, see
|
|
|
e38cb5 |
+ <https://www.gnu.org/licenses/>. */
|
|
|
e38cb5 |
+
|
|
|
e38cb5 |
+#include <stdio.h>
|
|
|
e38cb5 |
+#include <sys/resource.h>
|
|
|
e38cb5 |
+#include <support/check.h>
|
|
|
e38cb5 |
+
|
|
|
e38cb5 |
+char test_string[] = "test";
|
|
|
e38cb5 |
+
|
|
|
e38cb5 |
+static int
|
|
|
e38cb5 |
+do_test (void)
|
|
|
e38cb5 |
+{
|
|
|
e38cb5 |
+ struct rlimit limit;
|
|
|
e38cb5 |
+ TEST_VERIFY_EXIT (getrlimit (RLIMIT_AS, &limit) == 0);
|
|
|
e38cb5 |
+ limit.rlim_cur = 200 * 1024 * 1024;
|
|
|
e38cb5 |
+ TEST_VERIFY_EXIT (setrlimit (RLIMIT_AS, &limit) == 0);
|
|
|
e38cb5 |
+ FILE *fp = fopen ("/dev/null", "w");
|
|
|
e38cb5 |
+ TEST_VERIFY_EXIT (fp != NULL);
|
|
|
e38cb5 |
+ TEST_COMPARE (fprintf (fp, "%1000000000d", 1), 1000000000);
|
|
|
e38cb5 |
+ TEST_COMPARE (fprintf (fp, "%.1000000000s", test_string), 4);
|
|
|
e38cb5 |
+ TEST_COMPARE (fprintf (fp, "%1000000000d %1000000000d", 1, 2), 2000000001);
|
|
|
e38cb5 |
+ TEST_COMPARE (fprintf (fp, "%2$.*1$s", 0x7fffffff, test_string), 4);
|
|
|
e38cb5 |
+ return 0;
|
|
|
e38cb5 |
+}
|
|
|
e38cb5 |
+
|
|
|
e38cb5 |
+#include <support/test-driver.c>
|
|
|
e38cb5 |
diff --git a/stdio-common/vfprintf.c b/stdio-common/vfprintf.c
|
|
|
e38cb5 |
index dab56b6ba2c7bdbe..6b83ba91a12cdcd5 100644
|
|
|
e38cb5 |
--- a/stdio-common/vfprintf.c
|
|
|
e38cb5 |
+++ b/stdio-common/vfprintf.c
|
|
|
e38cb5 |
@@ -42,10 +42,6 @@
|
|
|
e38cb5 |
|
|
|
e38cb5 |
#include <libioP.h>
|
|
|
e38cb5 |
|
|
|
e38cb5 |
-/* In some cases we need extra space for all the output which is not
|
|
|
e38cb5 |
- counted in the width of the string. We assume 32 characters is
|
|
|
e38cb5 |
- enough. */
|
|
|
e38cb5 |
-#define EXTSIZ 32
|
|
|
e38cb5 |
#define ARGCHECK(S, Format) \
|
|
|
e38cb5 |
do \
|
|
|
e38cb5 |
{ \
|
|
|
e38cb5 |
@@ -1295,7 +1291,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
|
|
|
e38cb5 |
|
|
|
e38cb5 |
/* Buffer intermediate results. */
|
|
|
e38cb5 |
CHAR_T work_buffer[WORK_BUFFER_SIZE];
|
|
|
e38cb5 |
- CHAR_T *workstart = NULL;
|
|
|
e38cb5 |
CHAR_T *workend;
|
|
|
e38cb5 |
|
|
|
e38cb5 |
/* We have to save the original argument pointer. */
|
|
|
e38cb5 |
@@ -1404,7 +1399,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
|
|
|
e38cb5 |
UCHAR_T pad = L_(' ');/* Padding character. */
|
|
|
e38cb5 |
CHAR_T spec;
|
|
|
e38cb5 |
|
|
|
e38cb5 |
- workstart = NULL;
|
|
|
e38cb5 |
workend = work_buffer + WORK_BUFFER_SIZE;
|
|
|
e38cb5 |
|
|
|
e38cb5 |
/* Get current character in format string. */
|
|
|
e38cb5 |
@@ -1496,31 +1490,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
|
|
|
e38cb5 |
pad = L_(' ');
|
|
|
e38cb5 |
left = 1;
|
|
|
e38cb5 |
}
|
|
|
e38cb5 |
-
|
|
|
e38cb5 |
- if (__glibc_unlikely (width >= INT_MAX / sizeof (CHAR_T) - EXTSIZ))
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- __set_errno (EOVERFLOW);
|
|
|
e38cb5 |
- done = -1;
|
|
|
e38cb5 |
- goto all_done;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
-
|
|
|
e38cb5 |
- if (width >= WORK_BUFFER_SIZE - EXTSIZ)
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- /* We have to use a special buffer. */
|
|
|
e38cb5 |
- size_t needed = ((size_t) width + EXTSIZ) * sizeof (CHAR_T);
|
|
|
e38cb5 |
- if (__libc_use_alloca (needed))
|
|
|
e38cb5 |
- workend = (CHAR_T *) alloca (needed) + width + EXTSIZ;
|
|
|
e38cb5 |
- else
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- workstart = (CHAR_T *) malloc (needed);
|
|
|
e38cb5 |
- if (workstart == NULL)
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- done = -1;
|
|
|
e38cb5 |
- goto all_done;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- workend = workstart + width + EXTSIZ;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
}
|
|
|
e38cb5 |
JUMP (*f, step1_jumps);
|
|
|
e38cb5 |
|
|
|
e38cb5 |
@@ -1528,31 +1497,13 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
|
|
|
e38cb5 |
LABEL (width):
|
|
|
e38cb5 |
width = read_int (&f);
|
|
|
e38cb5 |
|
|
|
e38cb5 |
- if (__glibc_unlikely (width == -1
|
|
|
e38cb5 |
- || width >= INT_MAX / sizeof (CHAR_T) - EXTSIZ))
|
|
|
e38cb5 |
+ if (__glibc_unlikely (width == -1))
|
|
|
e38cb5 |
{
|
|
|
e38cb5 |
__set_errno (EOVERFLOW);
|
|
|
e38cb5 |
done = -1;
|
|
|
e38cb5 |
goto all_done;
|
|
|
e38cb5 |
}
|
|
|
e38cb5 |
|
|
|
e38cb5 |
- if (width >= WORK_BUFFER_SIZE - EXTSIZ)
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- /* We have to use a special buffer. */
|
|
|
e38cb5 |
- size_t needed = ((size_t) width + EXTSIZ) * sizeof (CHAR_T);
|
|
|
e38cb5 |
- if (__libc_use_alloca (needed))
|
|
|
e38cb5 |
- workend = (CHAR_T *) alloca (needed) + width + EXTSIZ;
|
|
|
e38cb5 |
- else
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- workstart = (CHAR_T *) malloc (needed);
|
|
|
e38cb5 |
- if (workstart == NULL)
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- done = -1;
|
|
|
e38cb5 |
- goto all_done;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- workend = workstart + width + EXTSIZ;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
if (*f == L_('$'))
|
|
|
e38cb5 |
/* Oh, oh. The argument comes from a positional parameter. */
|
|
|
e38cb5 |
goto do_positional;
|
|
|
e38cb5 |
@@ -1601,34 +1552,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
|
|
|
e38cb5 |
}
|
|
|
e38cb5 |
else
|
|
|
e38cb5 |
prec = 0;
|
|
|
e38cb5 |
- if (prec > width && prec > WORK_BUFFER_SIZE - EXTSIZ)
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- /* Deallocate any previously allocated buffer because it is
|
|
|
e38cb5 |
- too small. */
|
|
|
e38cb5 |
- if (__glibc_unlikely (workstart != NULL))
|
|
|
e38cb5 |
- free (workstart);
|
|
|
e38cb5 |
- workstart = NULL;
|
|
|
e38cb5 |
- if (__glibc_unlikely (prec >= INT_MAX / sizeof (CHAR_T) - EXTSIZ))
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- __set_errno (EOVERFLOW);
|
|
|
e38cb5 |
- done = -1;
|
|
|
e38cb5 |
- goto all_done;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- size_t needed = ((size_t) prec + EXTSIZ) * sizeof (CHAR_T);
|
|
|
e38cb5 |
-
|
|
|
e38cb5 |
- if (__libc_use_alloca (needed))
|
|
|
e38cb5 |
- workend = (CHAR_T *) alloca (needed) + prec + EXTSIZ;
|
|
|
e38cb5 |
- else
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- workstart = (CHAR_T *) malloc (needed);
|
|
|
e38cb5 |
- if (workstart == NULL)
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- done = -1;
|
|
|
e38cb5 |
- goto all_done;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- workend = workstart + prec + EXTSIZ;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
JUMP (*f, step2_jumps);
|
|
|
e38cb5 |
|
|
|
e38cb5 |
/* Process 'h' modifier. There might another 'h' following. */
|
|
|
e38cb5 |
@@ -1692,10 +1615,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
|
|
|
e38cb5 |
/* The format is correctly handled. */
|
|
|
e38cb5 |
++nspecs_done;
|
|
|
e38cb5 |
|
|
|
e38cb5 |
- if (__glibc_unlikely (workstart != NULL))
|
|
|
e38cb5 |
- free (workstart);
|
|
|
e38cb5 |
- workstart = NULL;
|
|
|
e38cb5 |
-
|
|
|
e38cb5 |
/* Look for next format specifier. */
|
|
|
e38cb5 |
#ifdef COMPILE_WPRINTF
|
|
|
e38cb5 |
f = __find_specwc ((end_of_spec = ++f));
|
|
|
e38cb5 |
@@ -1713,18 +1632,11 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
|
|
|
e38cb5 |
|
|
|
e38cb5 |
/* Hand off processing for positional parameters. */
|
|
|
e38cb5 |
do_positional:
|
|
|
e38cb5 |
- if (__glibc_unlikely (workstart != NULL))
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- free (workstart);
|
|
|
e38cb5 |
- workstart = NULL;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
done = printf_positional (s, format, readonly_format, ap, &ap_save,
|
|
|
e38cb5 |
done, nspecs_done, lead_str_end, work_buffer,
|
|
|
e38cb5 |
save_errno, grouping, thousands_sep);
|
|
|
e38cb5 |
|
|
|
e38cb5 |
all_done:
|
|
|
e38cb5 |
- if (__glibc_unlikely (workstart != NULL))
|
|
|
e38cb5 |
- free (workstart);
|
|
|
e38cb5 |
/* Unlock the stream. */
|
|
|
e38cb5 |
_IO_funlockfile (s);
|
|
|
e38cb5 |
_IO_cleanup_region_end (0);
|
|
|
e38cb5 |
@@ -1767,8 +1679,6 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
|
|
|
e38cb5 |
/* Just a counter. */
|
|
|
e38cb5 |
size_t cnt;
|
|
|
e38cb5 |
|
|
|
e38cb5 |
- CHAR_T *workstart = NULL;
|
|
|
e38cb5 |
-
|
|
|
e38cb5 |
if (grouping == (const char *) -1)
|
|
|
e38cb5 |
{
|
|
|
e38cb5 |
#ifdef COMPILE_WPRINTF
|
|
|
e38cb5 |
@@ -1957,7 +1867,6 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
|
|
|
e38cb5 |
char pad = specs[nspecs_done].info.pad;
|
|
|
e38cb5 |
CHAR_T spec = specs[nspecs_done].info.spec;
|
|
|
e38cb5 |
|
|
|
e38cb5 |
- workstart = NULL;
|
|
|
e38cb5 |
CHAR_T *workend = work_buffer + WORK_BUFFER_SIZE;
|
|
|
e38cb5 |
|
|
|
e38cb5 |
/* Fill in last information. */
|
|
|
e38cb5 |
@@ -1991,27 +1900,6 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
|
|
|
e38cb5 |
prec = specs[nspecs_done].info.prec;
|
|
|
e38cb5 |
}
|
|
|
e38cb5 |
|
|
|
e38cb5 |
- /* Maybe the buffer is too small. */
|
|
|
e38cb5 |
- if (MAX (prec, width) + EXTSIZ > WORK_BUFFER_SIZE)
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- if (__libc_use_alloca ((MAX (prec, width) + EXTSIZ)
|
|
|
e38cb5 |
- * sizeof (CHAR_T)))
|
|
|
e38cb5 |
- workend = ((CHAR_T *) alloca ((MAX (prec, width) + EXTSIZ)
|
|
|
e38cb5 |
- * sizeof (CHAR_T))
|
|
|
e38cb5 |
- + (MAX (prec, width) + EXTSIZ));
|
|
|
e38cb5 |
- else
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- workstart = (CHAR_T *) malloc ((MAX (prec, width) + EXTSIZ)
|
|
|
e38cb5 |
- * sizeof (CHAR_T));
|
|
|
e38cb5 |
- if (workstart == NULL)
|
|
|
e38cb5 |
- {
|
|
|
e38cb5 |
- done = -1;
|
|
|
e38cb5 |
- goto all_done;
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- workend = workstart + (MAX (prec, width) + EXTSIZ);
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
- }
|
|
|
e38cb5 |
-
|
|
|
e38cb5 |
/* Process format specifiers. */
|
|
|
e38cb5 |
while (1)
|
|
|
e38cb5 |
{
|
|
|
e38cb5 |
@@ -2085,18 +1973,12 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
|
|
|
e38cb5 |
break;
|
|
|
e38cb5 |
}
|
|
|
e38cb5 |
|
|
|
e38cb5 |
- if (__glibc_unlikely (workstart != NULL))
|
|
|
e38cb5 |
- free (workstart);
|
|
|
e38cb5 |
- workstart = NULL;
|
|
|
e38cb5 |
-
|
|
|
e38cb5 |
/* Write the following constant string. */
|
|
|
e38cb5 |
outstring (specs[nspecs_done].end_of_fmt,
|
|
|
e38cb5 |
specs[nspecs_done].next_fmt
|
|
|
e38cb5 |
- specs[nspecs_done].end_of_fmt);
|
|
|
e38cb5 |
}
|
|
|
e38cb5 |
all_done:
|
|
|
e38cb5 |
- if (__glibc_unlikely (workstart != NULL))
|
|
|
e38cb5 |
- free (workstart);
|
|
|
e38cb5 |
scratch_buffer_free (&argsbuf);
|
|
|
e38cb5 |
scratch_buffer_free (&specsbuf);
|
|
|
e38cb5 |
return done;
|