[RFC,v3,0/2] Add [v]aprintf(3)

Message ID cover.1773857304.git.alx@kernel.org (mailing list archive)
Headers
Series Add [v]aprintf(3) |

Message

Alejandro Colomar March 18, 2026, 6:14 p.m. UTC
  Hi!

I've added docs, some tests, and fortified versions.  Doing regression
testing shows that the newly added tests are okay, but I somehow broke
the Makefiles.

	$ diff -u ../.tmp.master/tests.sum tests.sum 
	--- ../.tmp.master/tests.sum	2026-03-18 18:52:49.914978699 +0100
	+++ tests.sum	2026-03-18 18:27:58.265965856 +0100
	@@ -7,7 +7,7 @@
	 PASS: check-local-headers
	 PASS: check-wrapper-headers
	 PASS: link-static-libc
	-PASS: lint-makefiles
	+FAIL: lint-makefiles
	 
	 Running argp ...
	 PASS: argp/argp-test
	@@ -1936,6 +1936,7 @@
	 PASS: libio/test-fputs-unbuffered-full
	 PASS: libio/test-fputws-unbuffered-full
	 PASS: libio/test-freopen
	+PASS: libio/tst-aprintf
	 PASS: libio/tst-asprintf-null
	 PASS: libio/tst-atime
	 PASS: libio/tst-bz22415

However, I don't know how I broke 'lint-makefiles'.  Where's the log for
consulting the details of this?

I still need to do the long double variants, as Joseph said.  Joseph
would you mind pointing me to what I should do about it and how?

Also, is there anything else I should do?

Of course, I plan to fill the commit messages more.  I still haven't
started with that part.

See the range-diff at the bottom, for the changes in this revision.


Have a lovely day!
Alex


Alejandro Colomar (2):
  Add [v]aprintf(3)
  manual/: Prefer aprintf(3) over asprintf(3)

 debug/Versions            |  3 +++
 include/stdio.h           |  3 +++
 libio/Makefile            |  4 ++-
 libio/Versions            |  3 +++
 libio/bits/stdio-ldbl.h   |  2 ++
 libio/bits/stdio2-decl.h  |  8 ++++++
 libio/bits/stdio2.h       | 42 +++++++++++++++++++++++++++++
 libio/stdio.h             | 10 +++++++
 libio/tst-aprintf.c       | 56 +++++++++++++++++++++++++++++++++++++++
 libio/vaprintf.c          | 39 +++++++++++++++++++++++++++
 manual/examples/rprintf.c |  4 +--
 manual/stdio.texi         | 30 +++++++++++++++++++--
 manual/string.texi        |  2 +-
 stdio-common/Makefile     |  2 ++
 stdio-common/Versions     |  2 ++
 stdio-common/aprintf.c    | 40 ++++++++++++++++++++++++++++
 16 files changed, 244 insertions(+), 6 deletions(-)
 create mode 100644 libio/tst-aprintf.c
 create mode 100644 libio/vaprintf.c
 create mode 100644 stdio-common/aprintf.c

Range-diff against v2:
1:  916855e5 ! 1:  f616528f Add [v]aprintf(3)
    @@ Commit message
     
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
    + ## debug/Versions ##
    +@@ debug/Versions: libc {
    +   GLIBC_2.43 {
    +     __memset_explicit_chk;
    +   }
    ++  GLIBC_2.44 {
    ++    __aprintf_chk; __vaprintf_chk;
    ++  }
    +   GLIBC_PRIVATE {
    +     __fortify_fail;
    +   }
    +
    + ## include/stdio.h ##
    +@@ include/stdio.h: extern const char *__get_errname (int) attribute_hidden;
    + 
    + libc_hidden_ldbl_proto (__asprintf)
    + 
    ++extern __typeof (aprintf) __aprintf;
    ++libc_hidden_ldbl_proto (__aprintf)
    ++
    + #  if IS_IN (libc)
    + extern FILE *_IO_new_fopen (const char*, const char*);
    + #   define fopen(fname, mode) _IO_new_fopen (fname, mode)
    +
      ## libio/Makefile ##
     @@ libio/Makefile: routines	:=							      \
      									      \
    @@ libio/Makefile: routines_no_fortify += \
        vsnprintf \
        vswprintf \
        vwprintf \
    +@@ libio/Makefile: tests = \
    +   test-fputs-unbuffered-full \
    +   test-fputws-unbuffered-full \
    +   tst-asprintf-null \
    ++  tst-aprintf \
    +   tst-atime \
    +   tst-bz22415 \
    +   tst-bz24051 \
    +
    + ## libio/Versions ##
    +@@ libio/Versions: libc {
    +     # f*
    +     fmemopen;
    +   }
    ++  GLIBC_2.44 {
    ++    vaprintf;
    ++  }
    +   GLIBC_PRIVATE {
    +     # Used by NPTL and librt
    +     __libc_fatal;
    +
    + ## libio/bits/stdio-ldbl.h ##
    +@@ libio/bits/stdio-ldbl.h: __LDBL_REDIR2_DECL (vdprintf_chk)
    + #  ifdef __USE_GNU
    + __LDBL_REDIR2_DECL (asprintf_chk)
    + __LDBL_REDIR2_DECL (vasprintf_chk)
    ++__LDBL_REDIR2_DECL (aprintf_chk)
    ++__LDBL_REDIR2_DECL (vaprintf_chk)
    + __LDBL_REDIR2_DECL (obstack_printf_chk)
    + __LDBL_REDIR2_DECL (obstack_vprintf_chk)
    + #  endif
    +
    + ## libio/bits/stdio2-decl.h ##
    +@@ libio/bits/stdio2-decl.h: extern int __asprintf_chk (char **__restrict __ptr, int __flag,
    + extern int __vasprintf_chk (char **__restrict __ptr, int __flag,
    + 			    const char *__restrict __fmt, __gnuc_va_list __arg)
    +      __THROW __attribute__ ((__format__ (__printf__, 3, 0))) __wur;
    ++extern char *__aprintf_chk (int __flag,
    ++			   const char *__restrict __fmt, ...)
    ++     __THROW __attribute__ ((__format__ (__printf__, 2, 3)))
    ++     __attribute_malloc__;
    ++extern char *__vaprintf_chk (int __flag,
    ++			    const char *__restrict __fmt, __gnuc_va_list __arg)
    ++     __THROW __attribute__ ((__format__ (__printf__, 2, 0)))
    ++     __attribute_malloc__;
    + extern int __obstack_printf_chk (struct obstack *__restrict __obstack,
    + 				 int __flag, const char *__restrict __format,
    + 				 ...)
    +
    + ## libio/bits/stdio2.h ##
    +@@ libio/bits/stdio2.h: __NTH (__asprintf (char **__restrict __ptr, const char *__restrict __fmt,
    + 			 __va_arg_pack ());
    + }
    + 
    ++__fortify_function char *
    ++__NTH (aprintf (const char *__restrict __fmt, ...))
    ++{
    ++  return __aprintf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
    ++}
    ++
    ++__fortify_function char *
    ++__NTH (__aprintf (const char *__restrict __fmt, ...))
    ++{
    ++  return __aprintf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
    ++}
    ++
    + __fortify_function int
    + __NTH (obstack_printf (struct obstack *__restrict __obstack,
    + 		       const char *__restrict __fmt, ...))
    +@@ libio/bits/stdio2.h: __NTH (__asprintf (__fortify_clang_overload_arg (char **, __restrict, __ptr),
    +   return __r;
    + }
    + 
    ++__fortify_function char *
    ++__NTH (aprintf (const char *__restrict __fmt, ...))
    ++{
    ++  __gnuc_va_list __fortify_ap;
    ++  __builtin_va_start (__fortify_ap, __fmt);
    ++  char * __p = __vaprintf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __fortify_ap);
    ++  __builtin_va_end (__fortify_ap);
    ++  return __p;
    ++}
    ++
    ++__fortify_function char *
    ++__NTH (__aprintf (const char *__restrict __fmt, ...))
    ++{
    ++  __gnuc_va_list __fortify_ap;
    ++  __builtin_va_start (__fortify_ap, __fmt);
    ++  char *__p = __vaprintf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __fortify_ap);
    ++  __builtin_va_end (__fortify_ap);
    ++  return __p;
    ++}
    ++
    + __fortify_function_error_function __attribute_overloadable__ int
    + __NTH (obstack_printf (__fortify_clang_overload_arg (struct obstack *,
    + 						     __restrict, __obstack),
    +@@ libio/bits/stdio2.h: __NTH (obstack_printf (__fortify_clang_overload_arg (struct obstack *,
    +   __asprintf_chk (ptr, __USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
    + #   define __asprintf(ptr, ...) \
    +   __asprintf_chk (ptr, __USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
    ++#   define aprintf(...) \
    ++  __aprintf_chk (__USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
    ++#   define __aprintf(...) \
    ++  __aprintf_chk (__USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
    + #   define obstack_printf(obstack, ...) \
    +   __obstack_printf_chk (obstack, __USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
    + #  endif
    +@@ libio/bits/stdio2.h: __NTH (vasprintf (char **__restrict __ptr, const char *__restrict __fmt,
    +   return __vasprintf_chk (__ptr, __USE_FORTIFY_LEVEL - 1, __fmt, __ap);
    + }
    + 
    ++__fortify_function char *
    ++__NTH (vaprintf (const char *__restrict __fmt, __gnuc_va_list __ap))
    ++{
    ++  return __vaprintf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __ap);
    ++}
    ++
    + __fortify_function int
    + __NTH (obstack_vprintf (struct obstack *__restrict __obstack,
    + 			const char *__restrict __fmt, __gnuc_va_list __ap))
     
      ## libio/stdio.h ##
    -@@ libio/stdio.h: extern int __asprintf (char **__restrict __ptr,
    - extern int asprintf (char **__restrict __ptr,
    - 		     const char *__restrict __fmt, ...)
    +@@ libio/stdio.h: extern int asprintf (char **__restrict __ptr,
           __THROWNL __attribute__ ((__format__ (__printf__, 2, 3))) __wur;
    -+
    + #endif
    + 
    ++#ifdef __USE_GNU
     +/* Write formatted output to a string dynamically allocated with `malloc'.  */
     +extern char *vaprintf (const char *__restrict __f, __gnuc_va_list __arg)
     +     __THROWNL __attribute__ ((__format__ (__printf__, 1, 0)))
    @@ libio/stdio.h: extern int __asprintf (char **__restrict __ptr,
     +extern char *aprintf (const char *__restrict __fmt, ...)
     +     __THROWNL __attribute__ ((__format__ (__printf__, 1, 2)))
     +     __attribute_malloc__;
    - #endif
    - 
    ++#endif
    ++
      #ifdef __USE_XOPEN2K8
    + /* Write formatted output to a file descriptor.  */
    + extern int vdprintf (int __fd, const char *__restrict __fmt,
    +
    + ## libio/tst-aprintf.c (new) ##
    +@@
    ++/* Test aprintf.
    ++   Copyright (C) 2026 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 <stdlib.h>
    ++#include <stdio.h>
    ++#include <support/check.h>
    ++#include <sys/resource.h>
    ++
    ++static int
    ++do_test (void)
    ++{
    ++  char *buf;
    ++  {
    ++    /* Avoid -Wformat-overflow warning.  */
    ++    const char *volatile format = "%2000000000d %2000000000d";
    ++    buf = aprintf (format, 1, 2);
    ++    TEST_VERIFY (buf == NULL);
    ++  }
    ++  if (errno != ENOMEM)
    ++    TEST_COMPARE (errno, EOVERFLOW);
    ++
    ++  /* Force ENOMEM in the test below.  */
    ++  struct rlimit rl;
    ++  TEST_COMPARE (getrlimit (RLIMIT_AS, &rl), 0);
    ++  rl.rlim_cur = 10 * 1024 * 1024;
    ++  TEST_COMPARE (setrlimit (RLIMIT_AS, &rl), 0);
    ++
    ++  buf = aprintf ("%20000000d", 1);
    ++  TEST_VERIFY (buf == NULL);
    ++  TEST_COMPARE (errno, ENOMEM);
    ++
    ++  /* Success */
    ++  buf = aprintf ("foo %d", 42);
    ++  TEST_COMPARE_STRING (buf, "foo 42");
    ++  free(buf);
    ++
    ++  return 0;
    ++}
    ++
    ++#include <support/test-driver.c>
     
      ## libio/vaprintf.c (new) ##
     @@
    @@ libio/vaprintf.c (new)
     +}
     +ldbl_weak_alias (__vaprintf, vaprintf)
     
    + ## manual/stdio.texi ##
    +@@ manual/stdio.texi: other systems offer this function as an async-signal-safe alternative to
    + The functions in this section do formatted output and place the results
    + in dynamically allocated memory.
    + 
    ++@deftypefun {char *} aprintf (const char *@var{template}, @dots{})
    ++@standards{GNU, stdio.h}
    ++@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
    ++This function is similar to @code{sprintf},
    ++except that it dynamically allocates a string
    ++(as with @code{malloc}; @pxref{Unconstrained Allocation})
    ++to hold the output,
    ++instead of putting the output in a buffer you allocate in advance.
    ++A successful call to @code{aprintf} returns
    ++a pointer to the newly allocated string.
    ++
    ++Here is how to use @code{aprintf}
    ++to get the same result as the @code{snprintf} example,
    ++but more easily:
    ++
    ++@smallexample
    ++/* @r{Construct a message describing the value of a variable}
    ++   @r{whose name is @var{name} and whose value is @var{value}.} */
    ++char *
    ++make_message (char *name, char *value)
    ++@{
    ++  return aprintf ("value of %s is %s", name, value);
    ++@}
    ++@end smallexample
    ++@end deftypefun
    ++
    + @deftypefun int asprintf (char **@var{ptr}, const char *@var{template}, @dots{})
    + @standards{GNU, stdio.h}
    + @safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
    +
      ## stdio-common/Makefile ##
     @@ stdio-common/Makefile: routines := \
        _itoa \
    @@ stdio-common/Makefile: routines := \
        fprintf \
        printf \
     
    + ## stdio-common/Versions ##
    +@@ stdio-common/Versions: libc {
    +     __isoc23_vfscanf;
    +     __isoc23_sscanf;
    +     __isoc23_vsscanf;
    ++  GLIBC_2.44 {
    ++    aprintf;
    +   }
    +   GLIBC_PRIVATE {
    +     # global variables
    +
      ## stdio-common/aprintf.c (new) ##
     @@
     +/* Copyright (C) 2026 Free Software Foundation, Inc.
    @@ stdio-common/aprintf.c (new)
     +
     +  return p;
     +}
    -+ldbl_hidden_def (__aprintf, aprintf)
    ++ldbl_hidden_def (___aprintf, __aprintf)
     +
     +ldbl_strong_alias (___aprintf, __aprintf)
     +ldbl_weak_alias (___aprintf, aprintf)
-:  -------- > 2:  131b1f0c manual/: Prefer aprintf(3) over asprintf(3)

base-commit: 9da7ad6d74700811c9b4c82b5f5eb555e39241a7