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

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

Message

Alejandro Colomar March 18, 2026, 9:47 p.m. UTC
  Hi!

In v4:

-  I've written the code to resemble [v]asprintf(3), as Joseph
   suggested.
-  I fixed the 'lint-makefiles' issues, which I think were because
   I wasn't using alphabetic order.
-  I've also sorted [v]aprintf(3) before [v]asprintf(3) in source code.
-  I've written commit messages.

See range-diff at the bottom.

Regression testing now says ok.

	$ 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 22:36:56.877761477 +0100
	@@ -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

Things I haven't done:

-  I still didn't write tests for the sysdeps _chk implementations, nor
   the Versions files under sysdeps/.  I'm aware I need to do this.
-  I didn't update '.abilist' files.  Is that something I should do?

Is there anything else I'm missing?


Have a lovely night!
Alex


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

 debug/Makefile                                |  4 ++
 debug/Versions                                |  3 +
 debug/aprintf_chk.c                           | 51 +++++++++++++++++
 debug/vaprintf_chk.c                          | 39 +++++++++++++
 include/stdio.h                               |  8 +++
 libio/Makefile                                |  4 +-
 libio/Versions                                |  3 +
 libio/bits/stdio-ldbl.h                       |  5 ++
 libio/bits/stdio2-decl.h                      |  8 +++
 libio/bits/stdio2.h                           | 42 ++++++++++++++
 libio/stdio.h                                 | 13 +++++
 libio/tst-aprintf.c                           | 57 +++++++++++++++++++
 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 +++++++++++++
 sysdeps/ieee754/ldbl-128ibm-compat/Makefile   |  8 ++-
 .../ldbl-128ibm-compat/ieee128-aprintf.c      | 36 ++++++++++++
 .../ldbl-128ibm-compat/ieee128-aprintf_chk.c  | 40 +++++++++++++
 .../ldbl-128ibm-compat/ieee128-vaprintf.c     | 29 ++++++++++
 .../ldbl-128ibm-compat/ieee128-vaprintf_chk.c | 31 ++++++++++
 sysdeps/ieee754/ldbl-opt/Makefile             |  6 ++
 sysdeps/ieee754/ldbl-opt/nldbl-aprintf.c      | 17 ++++++
 sysdeps/ieee754/ldbl-opt/nldbl-aprintf_chk.c  | 15 +++++
 sysdeps/ieee754/ldbl-opt/nldbl-vaprintf.c     |  9 +++
 sysdeps/ieee754/ldbl-opt/nldbl-vaprintf_chk.c |  8 +++
 29 files changed, 548 insertions(+), 7 deletions(-)
 create mode 100644 debug/aprintf_chk.c
 create mode 100644 debug/vaprintf_chk.c
 create mode 100644 libio/tst-aprintf.c
 create mode 100644 libio/vaprintf.c
 create mode 100644 stdio-common/aprintf.c
 create mode 100644 sysdeps/ieee754/ldbl-128ibm-compat/ieee128-aprintf.c
 create mode 100644 sysdeps/ieee754/ldbl-128ibm-compat/ieee128-aprintf_chk.c
 create mode 100644 sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vaprintf.c
 create mode 100644 sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vaprintf_chk.c
 create mode 100644 sysdeps/ieee754/ldbl-opt/nldbl-aprintf.c
 create mode 100644 sysdeps/ieee754/ldbl-opt/nldbl-aprintf_chk.c
 create mode 100644 sysdeps/ieee754/ldbl-opt/nldbl-vaprintf.c
 create mode 100644 sysdeps/ieee754/ldbl-opt/nldbl-vaprintf_chk.c

Range-diff against v3:
1:  f616528f ! 1:  f927e1d9 Add [v]aprintf(3)
    @@ Metadata
      ## Commit message ##
         Add [v]aprintf(3)
     
    +    This function is fundamentally the same as asprintf(3) --which was
    +    standardized in POSIX.1-2024--.
    +
    +    However, it has a key difference, which makes it a much better design.
    +    Instead of returning the newly allocated pointer through a char**
    +    parameter, it returns the pointer (like strdup(3)).  The API is as
    +    simple as it can be:
    +
    +            char *aprintf(const char *restrict fmt, ...);
    +            char *vaprintf(const char *restrict fmt, va_list ap);
    +
    +    This difference has several benefits:
    +
    +    -  One less parameter.  This means less source code for using this API,
    +       which implicitly means less chances of making mistakes.  It is also
    +       less noise while reading code, which increases readability.
    +
    +       Especially, we get rid of a pointer-to-pointer parameter, which are
    +       especially weird.
    +
    +    -  The function has an prototype that resembles strdup(3).  This
    +       functionality is commonly paired with strdup(3) calls, because
    +       they're very related.  By having a similar prototype, the code is
    +       more naturally paired with
    +
    +       Compare:
    +
    +            if (cond) {
    +                    p = strdup("foo");
    +                    if (p == NULL)
    +                            goto fail;
    +            } else {
    +                    if (asprintf(&p, "foo %d", 42) < 0)
    +                            goto fail;
    +            }
    +       vs
    +            p = cond ? strdup("foo") : aprintf("foo %d", 42);
    +            if (p == NULL)
    +                    goto fail;
    +
    +    -  We don't need to fail with EOVERFLOW.  This initial implementation
    +       still fails with EOVERFLOW, because it would require a lot of work
    +       implementing it in a way that doesn't have such a failure point, but
    +       the API has no inherent reasons to fail with EOVERFLOW.
    +
    +    -  This API returns a pointer, which makes it possible to use
    +       __attribute__((__malloc__(free))).  This would allow static analysers
    +       to know that these APIs allocate memory, which would prevent memory
    +       leaks, use-after-free errors, and other common issues of handling
    +       memory.
    +
    +    The name is similar to asprintf(3), removing the 's'.  This is the same
    +    name that gnulib has used for their implementation.  It also matches the
    +    name of the n3750 (alx-0007) proposal for ISO C2y.
    +
    +    The C Committee was worried that such a non-reserved name might be
    +    unlikely to be viable, as some existing code already uses it.  However:
    +
    +    -  Most code using this name, uses it with the same exact semantics.
    +       In those cases, it's not a problem.
    +
    +    -  In the remaining cases, the prototype is different, so they'll
    +       eventually get a compiler error for the clash.  This is entirely
    +       safe.  And by hiding the name under _GNU_SOURCE, we make that clash
    +       less likely.  Once/if this is eventually standard, I expect people
    +       will adapt their code, which is entirely reasonable.  Still, then the
    +       APIs will be hidden under _ISOC2Y_SOURCE et al., so this will only
    +       affect users when they opt-in to such a version.
    +
    +    Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3750.txt>
    +    Cc: Joseph Myers <josmyers@redhat.com>
    +    Cc: Paul Eggert <eggert@cs.ucla.edu>
    +    Cc: Bruno Haible <bruno@clisp.org>
    +    Cc: Adhemerval Zanella Netto <adhemerval.zanella@linaro.org>
    +    Cc: Florian Weimer <fweimer@redhat.com>
    +    Cc: "Dmitry V. Levin" <ldv@altlinux.org>
    +    Cc: Archie Cobbs <archie.cobbs@gmail.com>
    +    Cc: Solar Designer <solar@openwall.com>
    +    Cc: Sam James <sam@gentoo.org>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
    + ## debug/Makefile ##
    +@@ debug/Makefile: headers	:= execinfo.h
    + routines = \
    +   $(static-only-routines) \
    +   ____longjmp_chk \
    ++  aprintf_chk \
    +   asprintf_chk \
    +   backtrace \
    +   backtracesyms \
    +@@ debug/Makefile: routines = \
    +   strncpy_chk \
    +   swprintf_chk \
    +   ttyname_r_chk \
    ++  vaprintf_chk \
    +   vasprintf_chk \
    +   vdprintf_chk \
    +   vfprintf_chk \
    +@@ debug/Makefile: CFLAGS-sprintf_chk.c += $(libio-mtsafe)
    + CFLAGS-snprintf_chk.c += $(libio-mtsafe)
    + CFLAGS-vsprintf_chk.c += $(libio-mtsafe)
    + CFLAGS-vsnprintf_chk.c += $(libio-mtsafe)
    ++CFLAGS-aprintf_chk.c += $(libio-mtsafe)
    ++CFLAGS-vaprintf_chk.c += $(libio-mtsafe)
    + CFLAGS-asprintf_chk.c += $(libio-mtsafe)
    + CFLAGS-vasprintf_chk.c += $(libio-mtsafe)
    + CFLAGS-obprintf_chk.c += $(libio-mtsafe)
    +
      ## debug/Versions ##
     @@ debug/Versions: libc {
        GLIBC_2.43 {
    @@ debug/Versions: libc {
          __fortify_fail;
        }
     
    + ## debug/aprintf_chk.c (new) ##
    +@@
    ++/* 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 <stdarg.h>
    ++#include <libio/libioP.h>
    ++
    ++
    ++/* Write formatted output from FORMAT to a string allocated with malloc.  */
    ++char *
    ++___aprintf_chk (int flag, const char *fmt, ...)
    ++{
    ++  /* For flag > 0 (i.e. __USE_FORTIFY_LEVEL > 1) request that %n
    ++     can only come from read-only format strings.  */
    ++  unsigned int mode = (flag > 0) ? PRINTF_FORTIFY : 0;
    ++  va_list ap;
    ++  char *p;
    ++
    ++  va_start (ap, fmt);
    ++  if (__vasprintf_internal (&p, fmt, ap, mode) < 0)
    ++    p = NULL;
    ++  va_end (ap);
    ++
    ++  return p;
    ++}
    ++#if defined __LDBL_COMPAT || __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI == 1
    ++/* This is needed since <bits/stdio-lbdl.h> is included in this case, leading to
    ++ * multiple asm redirection of the same symbol
    ++ */
    ++ldbl_hidden_def (___aprintf_chk, __aprintf_chk)
    ++ldbl_strong_alias (___aprintf_chk, __aprintf_chk)
    ++#else
    ++/* On some systems introduction of ldbl_* macros lead to ABI breakage due to the
    ++ * long_double_symbol aliasing.
    ++ */
    ++strong_alias (___aprintf_chk, __aprintf_chk)
    ++libc_hidden_def (__aprintf_chk)
    ++#endif
    +
    + ## debug/vaprintf_chk.c (new) ##
    +@@
    ++/* 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/>.
    ++
    ++   As a special exception, if you link the code in this file with
    ++   files compiled with a GNU compiler to produce an executable,
    ++   that does not cause the resulting executable to be covered by
    ++   the GNU Lesser General Public License.  This exception does not
    ++   however invalidate any other reasons why the executable file
    ++   might be covered by the GNU Lesser General Public License.
    ++   This exception applies to code released by its copyright holders
    ++   in files containing the exception.  */
    ++
    ++#include <libio/libioP.h>
    ++
    ++char *
    ++__vaprintf_chk (int flag, const char *fmt, va_list ap)
    ++{
    ++  /* For flag > 0 (i.e. __USE_FORTIFY_LEVEL > 1) request that %n
    ++     can only come from read-only format strings.  */
    ++  unsigned int mode = (flag > 0) ? PRINTF_FORTIFY : 0;
    ++  char *p;
    ++
    ++  return __vasprintf_internal (&p, fmt, ap, mode) < 0 ? NULL : p;
    ++}
    ++libc_hidden_def (__vaprintf_chk)
    +
      ## include/stdio.h ##
    -@@ include/stdio.h: extern const char *__get_errname (int) attribute_hidden;
    +@@ include/stdio.h: extern int __vfprintf_chk (FILE *, int, const char *, __gnuc_va_list);
    + stdio_hidden_ldbl_proto (__, vfprintf_chk)
    + extern char *__fgets_unlocked_chk (char *buf, size_t size, int n, FILE *fp);
    + extern char *__fgets_chk (char *buf, size_t size, int n, FILE *fp);
    ++extern char *__aprintf_chk (int, const char *, ...)
    ++     __THROW __attribute_malloc__;
    ++extern char *__vaprintf_chk (int, const char *, __gnuc_va_list)
    ++     __THROW __attribute_malloc__;
    ++stdio_hidden_ldbl_proto (__, vaprintf_chk)
    + extern int __asprintf_chk (char **, int, const char *, ...) __THROW;
    + extern int __vasprintf_chk (char **, int, const char *, __gnuc_va_list) __THROW;
    + stdio_hidden_ldbl_proto (__, vasprintf_chk)
    +@@ include/stdio.h: extern const size_t _sys_errlist_internal_len attribute_hidden;
    + extern const char *__get_errlist (int) attribute_hidden;
    + extern const char *__get_errname (int) attribute_hidden;
      
    ++libc_hidden_ldbl_proto (__aprintf)
      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)
    +@@ include/stdio.h: rtld_hidden_proto (__libc_fatal)
    + libc_hidden_proto (__fgets_unlocked_chk)
    + 
    + #if defined __LDBL_COMPAT || __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI == 1
    ++libc_hidden_ldbl_proto (__aprintf_chk)
    + libc_hidden_ldbl_proto (__asprintf_chk)
    + #else
    ++libc_hidden_proto (__aprintf_chk)
    + libc_hidden_proto (__asprintf_chk)
    + #endif
    + 
     
      ## libio/Makefile ##
     @@ libio/Makefile: routines	:=							      \
    @@ libio/Makefile: routines	:=							      \
      									      \
      	__fbufsize __freading __fwriting __freadable __fwritable __flbf	      \
     @@ libio/Makefile: routines_no_fortify += \
    +   iofgetws_u \
        iovdprintf \
        swprintf \
    -   vasprintf \
     +  vaprintf \
    +   vasprintf \
        vsnprintf \
        vswprintf \
    -   vwprintf \
     @@ libio/Makefile: tests = \
    +   test-fmemopen \
        test-fputs-unbuffered-full \
        test-fputws-unbuffered-full \
    -   tst-asprintf-null \
     +  tst-aprintf \
    +   tst-asprintf-null \
        tst-atime \
        tst-bz22415 \
    -   tst-bz24051 \
     
      ## libio/Versions ##
     @@ libio/Versions: libc {
    @@ libio/Versions: libc {
          __libc_fatal;
     
      ## libio/bits/stdio-ldbl.h ##
    -@@ libio/bits/stdio-ldbl.h: __LDBL_REDIR2_DECL (vdprintf_chk)
    +@@ libio/bits/stdio-ldbl.h: __LDBL_REDIR_DECL (dprintf)
    + #endif
    + 
    + #ifdef __USE_GNU
    ++__LDBL_REDIR_DECL (vaprintf)
    ++__LDBL_REDIR2_DECL (aprintf)
    ++__LDBL_REDIR_DECL (aprintf)
    + __LDBL_REDIR_DECL (vasprintf)
    + __LDBL_REDIR2_DECL (asprintf)
    + __LDBL_REDIR_DECL (asprintf)
    +@@ libio/bits/stdio-ldbl.h: __LDBL_REDIR2_DECL (dprintf_chk)
    + __LDBL_REDIR2_DECL (vdprintf_chk)
    + #  endif
      #  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 (asprintf_chk)
    + __LDBL_REDIR2_DECL (vasprintf_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;
    +@@ libio/bits/stdio2-decl.h: extern int __vdprintf_chk (int __fd, int __flag,
    + 
    + # ifdef __USE_GNU
    + 
     +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)
    ++			    const char *__restrict __fmt, __gnuc_va_list __ap)
     +     __THROW __attribute__ ((__format__ (__printf__, 2, 0)))
     +     __attribute_malloc__;
    - extern int __obstack_printf_chk (struct obstack *__restrict __obstack,
    - 				 int __flag, const char *__restrict __format,
    - 				 ...)
    + extern int __asprintf_chk (char **__restrict __ptr, int __flag,
    + 			   const char *__restrict __fmt, ...)
    +      __THROW __attribute__ ((__format__ (__printf__, 3, 4))) __wur;
     
      ## libio/bits/stdio2.h ##
    -@@ libio/bits/stdio2.h: __NTH (__asprintf (char **__restrict __ptr, const char *__restrict __fmt,
    - 			 __va_arg_pack ());
    - }
    +@@ libio/bits/stdio2.h: vdprintf (int __fd, const char *__restrict __fmt, __gnuc_va_list __ap)
      
    + # ifdef __USE_GNU
    + #  ifdef __va_arg_pack
     +__fortify_function char *
     +__NTH (aprintf (const char *__restrict __fmt, ...))
     +{
    @@ libio/bits/stdio2.h: __NTH (__asprintf (char **__restrict __ptr, const char *__r
     +}
     +
      __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;
    + __NTH (asprintf (char **__restrict __ptr, const char *__restrict __fmt, ...))
    + {
    +@@ libio/bits/stdio2.h: __NTH (obstack_printf (struct obstack *__restrict __obstack,
    + 			       __va_arg_pack ());
      }
    - 
    + #  elif __fortify_use_clang
     +__fortify_function char *
     +__NTH (aprintf (const char *__restrict __fmt, ...))
     +{
    @@ libio/bits/stdio2.h: __NTH (__asprintf (__fortify_clang_overload_arg (char **, _
     +}
     +
      __fortify_function_error_function __attribute_overloadable__ int
    - __NTH (obstack_printf (__fortify_clang_overload_arg (struct obstack *,
    - 						     __restrict, __obstack),
    + __NTH (asprintf (__fortify_clang_overload_arg (char **, __restrict, __ptr),
    + 		 const char *__restrict __fmt, ...))
     @@ 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__)
    +   return __r;
    + }
    + #  elif !defined __cplusplus
     +#   define aprintf(...) \
     +  __aprintf_chk (__USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
     +#   define __aprintf(...) \
     +  __aprintf_chk (__USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
    - #   define obstack_printf(obstack, ...) \
    + #   define asprintf(ptr, ...) \
    +   __asprintf_chk (ptr, __USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
    + #   define __asprintf(ptr, ...) \
    +@@ libio/bits/stdio2.h: __NTH (obstack_printf (__fortify_clang_overload_arg (struct 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))
    @@ libio/bits/stdio2.h: __NTH (vasprintf (char **__restrict __ptr, const char *__re
     +}
     +
      __fortify_function int
    - __NTH (obstack_vprintf (struct obstack *__restrict __obstack,
    - 			const char *__restrict __fmt, __gnuc_va_list __ap))
    + __NTH (vasprintf (char **__restrict __ptr, const char *__restrict __fmt,
    + 		  __gnuc_va_list __ap))
     
      ## libio/stdio.h ##
     @@ libio/stdio.h: extern int asprintf (char **__restrict __ptr,
    @@ libio/stdio.h: extern int asprintf (char **__restrict __ptr,
      
     +#ifdef __USE_GNU
     +/* Write formatted output to a string dynamically allocated with `malloc'.  */
    -+extern char *vaprintf (const char *__restrict __f, __gnuc_va_list __arg)
    ++extern char *vaprintf (const char *__restrict __fmt, __gnuc_va_list __ap)
     +     __THROWNL __attribute__ ((__format__ (__printf__, 1, 0)))
     +     __attribute_malloc__;
    ++extern char *__aprintf (const char *__restrict __fmt, ...)
    ++     __THROWNL __attribute__ ((__format__ (__printf__, 1, 2)))
    ++     __attribute_malloc__;
     +extern char *aprintf (const char *__restrict __fmt, ...)
     +     __THROWNL __attribute__ ((__format__ (__printf__, 1, 2)))
     +     __attribute_malloc__;
    @@ libio/tst-aprintf.c (new)
     +do_test (void)
     +{
     +  char *buf;
    ++
    ++  /* Success */
    ++  buf = aprintf ("foo %d", 42);
    ++  TEST_COMPARE_STRING (buf, "foo 42");
    ++  free(buf);
    ++
     +  {
     +    /* Avoid -Wformat-overflow warning.  */
     +    const char *volatile format = "%2000000000d %2000000000d";
    @@ libio/tst-aprintf.c (new)
     +  TEST_VERIFY (buf == NULL);
     +  TEST_COMPARE (errno, ENOMEM);
     +
    -+  /* Success */
    -+  buf = aprintf ("foo %d", 42);
    -+  TEST_COMPARE_STRING (buf, "foo 42");
    -+  free(buf);
    -+
     +  return 0;
     +}
     +
    @@ libio/vaprintf.c (new)
     +#include <stdio.h>
     +
     +char *
    -+__vaprintf (const char *fmt, va_list args)
    ++__vaprintf (const char *fmt, va_list ap)
     +{
     +  char *p;
     +
    -+  return __vasprintf_internal (&p, fmt, args, 0) < 0 ? NULL : p;
    ++  return __vasprintf_internal (&p, fmt, ap, 0) < 0 ? NULL : p;
     +}
     +ldbl_weak_alias (__vaprintf, vaprintf)
     
    @@ manual/stdio.texi: other systems offer this function as an async-signal-safe alt
     
      ## stdio-common/Makefile ##
     @@ stdio-common/Makefile: routines := \
    +   _fitoa_word \
        _itoa \
        _itowa \
    -   asprintf \
     +  aprintf \
    +   asprintf \
        ctermid \
        cuserid \
    -   dprintf \
     @@ stdio-common/Makefile: routines := \
    + 
      # Exclude fortified routines from being built with _FORTIFY_SOURCE
      routines_no_fortify += \
    -   asprintf \
     +  aprintf \
    +   asprintf \
        dprintf \
        fprintf \
    -   printf \
     
      ## stdio-common/Versions ##
     @@ stdio-common/Versions: libc {
    @@ stdio-common/aprintf.c (new)
     +
     +ldbl_strong_alias (___aprintf, __aprintf)
     +ldbl_weak_alias (___aprintf, aprintf)
    +
    + ## sysdeps/ieee754/ldbl-128ibm-compat/Makefile ##
    +@@ sysdeps/ieee754/ldbl-128ibm-compat/Makefile: ldbl-extra-routines += fwscanf \
    + 		       swscanf \
    + 		       swprintf \
    + 		       wscanf \
    ++		       vaprintf \
    + 		       vasprintf \
    + 		       vdprintf \
    + 		       vscanf \
    +@@ sysdeps/ieee754/ldbl-128ibm-compat/Makefile: endif
    + 
    + ifeq ($(subdir),stdio-common)
    + ldbl-extra-routines += printf_size \
    ++		       aprintf \
    + 		       asprintf \
    + 		       dprintf \
    + 		       fprintf \
    +@@ sysdeps/ieee754/ldbl-128ibm-compat/Makefile: $(objpfx)test-printf-size-ibm128.out: \
    + endif
    + 
    + ifeq ($(subdir),debug)
    +-ldbl-extra-routines += asprintf_chk \
    ++ldbl-extra-routines += aprintf_chk \
    ++		       asprintf_chk \
    + 		       dprintf_chk \
    + 		       fprintf_chk \
    + 		       printf_chk \
    + 		       snprintf_chk \
    + 		       sprintf_chk \
    ++		       vaprintf_chk \
    + 		       vasprintf_chk \
    + 		       vdprintf_chk \
    + 		       vfprintf_chk \
    +@@ sysdeps/ieee754/ldbl-128ibm-compat/Makefile: endif
    + routines += $(foreach r,$(ldbl-extra-routines),ieee128-$(r))
    + 
    + routines_no_fortify += \
    ++  ieee128-aprintf \
    +   ieee128-asprintf \
    +   ieee128-dprintf \
    +   ieee128-fprintf \
    +@@ sysdeps/ieee754/ldbl-128ibm-compat/Makefile: routines_no_fortify += \
    +   ieee128-sprintf \
    +   ieee128-swprintf \
    +   ieee128-syslog \
    ++  ieee128-vaprintf \
    +   ieee128-vasprintf \
    +   ieee128-vdprintf \
    +   ieee128-vfprintf \
    +
    + ## sysdeps/ieee754/ldbl-128ibm-compat/ieee128-aprintf.c (new) ##
    +@@
    ++/* Wrapper for aprintf.  IEEE128 version.
    ++   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 <stdarg.h>
    ++#include <libio/libioP.h>
    ++
    ++extern char *
    ++___ieee128___aprintf (const char *fmt, ...)
    ++{
    ++  va_list ap;
    ++  char *p;
    ++
    ++  va_start (ap, fmt);
    ++  if (__vasprintf_internal (&p, fmt, ap, PRINTF_LDBL_USES_FLOAT128) < 0)
    ++    p = NULL;
    ++  va_end (ap);
    ++
    ++  return p;
    ++}
    ++hidden_def (___ieee128___aprintf)
    ++strong_alias (___ieee128___aprintf, __aprintfieee128)
    +
    + ## sysdeps/ieee754/ldbl-128ibm-compat/ieee128-aprintf_chk.c (new) ##
    +@@
    ++/* Wrapper for __aprintf_chk.  IEEE128 version.
    ++   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 <stdarg.h>
    ++#include <libio/libioP.h>
    ++
    ++extern char *
    ++___ieee128___aprintf_chk (int flag, const char *fmt, ...)
    ++{
    ++  va_list ap;
    ++  char *p;
    ++
    ++  unsigned int mode = PRINTF_LDBL_USES_FLOAT128;
    ++  if (flag > 0)
    ++    mode |= PRINTF_FORTIFY;
    ++
    ++  va_start (ap, fmt);
    ++  if (__vasprintf_internal (&p, fmt, ap, mode) < 0)
    ++    p = NULL;
    ++  va_end (ap);
    ++
    ++  return p;
    ++}
    ++hidden_def (___ieee128___aprintf_chk)
    ++strong_alias (___ieee128___aprintf_chk, __aprintf_chkieee128)
    +
    + ## sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vaprintf.c (new) ##
    +@@
    ++/* Wrapper for vaprintf.  IEEE128 version.
    ++   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 <libio/libioP.h>
    ++
    ++extern char *
    ++___ieee128_vaprintf (const char *fmt, va_list ap)
    ++{
    ++  char *p;
    ++  if (__vasprintf_internal (&p, fmt, ap, PRINTF_LDBL_USES_FLOAT128) < 0)
    ++    p = NULL;
    ++  return p;
    ++}
    ++strong_alias (___ieee128_vaprintf, __vaprintfieee128)
    +
    + ## sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vaprintf_chk.c (new) ##
    +@@
    ++/* Wrapper for __vaprintf_chk.  IEEE128 version.
    ++   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 <libio/libioP.h>
    ++
    ++extern char *
    ++___ieee128___vaprintf_chk (int flag, const char *fmt, va_list ap)
    ++{
    ++  char *p;
    ++  unsigned int mode = PRINTF_LDBL_USES_FLOAT128;
    ++  if (flag > 0)
    ++    mode |= PRINTF_FORTIFY;
    ++
    ++  return __vasprintf_internal (&p, fmt, ap, mode) < 0 ? NULL : p;
    ++}
    ++strong_alias (___ieee128___vaprintf_chk, __vaprintf_chkieee128)
    +
    + ## sysdeps/ieee754/ldbl-opt/Makefile ##
    +@@ sysdeps/ieee754/ldbl-opt/Makefile: libnldbl-calls = \
    +   acos \
    +   acosh \
    +   acospi \
    ++  aprintf \
    ++  aprintf_chk \
    +   asin \
    +   asinh \
    +   asinpi \
    +@@ sysdeps/ieee754/ldbl-opt/Makefile: libnldbl-calls = \
    +   trunc \
    +   ufromfp \
    +   ufromfpx \
    ++  vaprintf \
    ++  vaprintf_chk \
    +   vasprintf \
    +   vasprintf_chk \
    +   vdprintf \
    +@@ sysdeps/ieee754/ldbl-opt/Makefile: CFLAGS-tst-nldbl-wscanf-binary-gnu89.c += -mlong-double-64 -std=gnu89 \
    + endif
    + 
    + routines_no_fortify += \
    ++  nldbl-aprintf \
    +   nldbl-asprintf \
    +   nldbl-dprintf \
    +   nldbl-fprintf \
    +@@ sysdeps/ieee754/ldbl-opt/Makefile: routines_no_fortify += \
    +   nldbl-sprintf \
    +   nldbl-swprintf \
    +   nldbl-syslog \
    ++  nldbl-vaprintf \
    +   nldbl-vasprintf \
    +   nldbl-vdprintf \
    +   nldbl-vfprintf \
    +
    + ## sysdeps/ieee754/ldbl-opt/nldbl-aprintf.c (new) ##
    +@@
    ++#include "nldbl-compat.h"
    ++
    ++attribute_hidden
    ++char *
    ++__aprintf (const char *fmt, ...)
    ++{
    ++  va_list ap;
    ++  char *p;
    ++
    ++  va_start (ap, fmt);
    ++  p = __nldbl_vaprintf (fmt, ap);
    ++  va_end (ap);
    ++
    ++  return p;
    ++}
    ++extern __typeof (__aprintf) aprintf attribute_hidden;
    ++weak_alias (__aprintf, aprintf)
    +
    + ## sysdeps/ieee754/ldbl-opt/nldbl-aprintf_chk.c (new) ##
    +@@
    ++#include "nldbl-compat.h"
    ++
    ++attribute_hidden
    ++char *
    ++__aprintf_chk (int flag, const char *fmt, ...)
    ++{
    ++  va_list ap;
    ++  char *p;
    ++
    ++  va_start (ap, fmt);
    ++  p = __nldbl___vaprintf_chk (flag, fmt, ap);
    ++  va_end (ap);
    ++
    ++  return p;
    ++}
    +
    + ## sysdeps/ieee754/ldbl-opt/nldbl-vaprintf.c (new) ##
    +@@
    ++#include "nldbl-compat.h"
    ++
    ++attribute_hidden
    ++weak_function
    ++char *
    ++vaprintf (const char *fmt, va_list ap)
    ++{
    ++  return __nldbl_vaprintf (fmt, ap);
    ++}
    +
    + ## sysdeps/ieee754/ldbl-opt/nldbl-vaprintf_chk.c (new) ##
    +@@
    ++#include "nldbl-compat.h"
    ++
    ++attribute_hidden
    ++char *
    ++__vaprintf_chk (int flag, const char *fmt, va_list ap)
    ++{
    ++  return __nldbl___vaprintf_chk (flag, fmt, ap);
    ++}
2:  131b1f0c ! 2:  f1dca5c0 manual/: Prefer aprintf(3) over asprintf(3)
    @@ Metadata
      ## Commit message ##
         manual/: Prefer aprintf(3) over asprintf(3)
     
    +    While it's less portable at the moment, it's a better API (simpler,
    +    safer, and less error-prone).
    +
    +    The C Committee has expressed strong support for this API, intending to
    +    standardize it.  Thus, it should be more portable in the long term.
    +
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## manual/examples/rprintf.c ##

base-commit: 9da7ad6d74700811c9b4c82b5f5eb555e39241a7
  

Comments

Joseph Myers March 20, 2026, 4:50 p.m. UTC | #1
On Wed, 18 Mar 2026, Alejandro Colomar wrote:

> Things I haven't done:
> 
> -  I still didn't write tests for the sysdeps _chk implementations, nor
>    the Versions files under sysdeps/.  I'm aware I need to do this.
> -  I didn't update '.abilist' files.  Is that something I should do?

You need to update Versions files for all exported functions, including 
those for long double variants; otherwise they won't be exported and won't 
be usable.  You need to update .abilist files for all exported functions 
(appropriately for each glibc ABI); otherwise the ABI tests will fail.
  
Alejandro Colomar March 20, 2026, 8:22 p.m. UTC | #2
Hi Joseph,

On 2026-03-20T16:50:22+0000, Joseph Myers wrote:
> On Wed, 18 Mar 2026, Alejandro Colomar wrote:
> 
> > Things I haven't done:
> > 
> > -  I still didn't write tests for the sysdeps _chk implementations, nor
> >    the Versions files under sysdeps/.  I'm aware I need to do this.
> > -  I didn't update '.abilist' files.  Is that something I should do?
> 
> You need to update Versions files for all exported functions, including 
> those for long double variants; otherwise they won't be exported and won't 
> be usable.  You need to update .abilist files for all exported functions 
> (appropriately for each glibc ABI); otherwise the ABI tests will fail.

Thanks!  I'll try to imitate asprintf(3) in that regard too.  Since
I don't know much about ABIs, I might not understand some of what I do,
but I hope it's more or less correct.  I'll probably have it in a few
days.


Have a lovely night!
Alex