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

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

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 warning Skipped because it is an RFC
linaro-tcwg-bot/tcwg_glibc_build--master-arm warning Skipped because it is an RFC

Commit Message

Alejandro Colomar March 18, 2026, 6:15 p.m. UTC
  Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
 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/stdio.texi        | 26 +++++++++++++++++++
 stdio-common/Makefile    |  2 ++
 stdio-common/Versions    |  2 ++
 stdio-common/aprintf.c   | 40 ++++++++++++++++++++++++++++
 14 files changed, 239 insertions(+), 1 deletion(-)
 create mode 100644 libio/tst-aprintf.c
 create mode 100644 libio/vaprintf.c
 create mode 100644 stdio-common/aprintf.c
  

Patch

diff --git a/debug/Versions b/debug/Versions
index fc818d29..d87148f5 100644
--- a/debug/Versions
+++ b/debug/Versions
@@ -71,6 +71,9 @@  libc {
   GLIBC_2.43 {
     __memset_explicit_chk;
   }
+  GLIBC_2.44 {
+    __aprintf_chk; __vaprintf_chk;
+  }
   GLIBC_PRIVATE {
     __fortify_fail;
   }
diff --git a/include/stdio.h b/include/stdio.h
index 88166993..cb9b9210 100644
--- a/include/stdio.h
+++ b/include/stdio.h
@@ -241,6 +241,9 @@  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)
diff --git a/libio/Makefile b/libio/Makefile
index 08e1e0ec..cbf311e1 100644
--- a/libio/Makefile
+++ b/libio/Makefile
@@ -43,7 +43,7 @@  routines	:=							      \
 									      \
 	clearerr feof ferror fileno fputc freopen fseek getc getchar	      \
 	memstream pclose putc putchar rewind setbuf setlinebuf vasprintf      \
-	iovdprintf vscanf vsnprintf obprintf fcloseall fseeko ftello	      \
+	vaprintf iovdprintf vscanf vsnprintf obprintf fcloseall fseeko ftello \
 	freopen64 fseeko64 ftello64					      \
 									      \
 	__fbufsize __freading __fwriting __freadable __fwritable __flbf	      \
@@ -63,6 +63,7 @@  routines_no_fortify += \
   iovdprintf \
   swprintf \
   vasprintf \
+  vaprintf \
   vsnprintf \
   vswprintf \
   vwprintf \
@@ -90,6 +91,7 @@  tests = \
   test-fputs-unbuffered-full \
   test-fputws-unbuffered-full \
   tst-asprintf-null \
+  tst-aprintf \
   tst-atime \
   tst-bz22415 \
   tst-bz24051 \
diff --git a/libio/Versions b/libio/Versions
index b91a7bc9..48ffe3d3 100644
--- a/libio/Versions
+++ b/libio/Versions
@@ -155,6 +155,9 @@  libc {
     # f*
     fmemopen;
   }
+  GLIBC_2.44 {
+    vaprintf;
+  }
   GLIBC_PRIVATE {
     # Used by NPTL and librt
     __libc_fatal;
diff --git a/libio/bits/stdio-ldbl.h b/libio/bits/stdio-ldbl.h
index 3cbff14e..d76720f0 100644
--- a/libio/bits/stdio-ldbl.h
+++ b/libio/bits/stdio-ldbl.h
@@ -125,6 +125,8 @@  __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
diff --git a/libio/bits/stdio2-decl.h b/libio/bits/stdio2-decl.h
index ada092ea..d59ac647 100644
--- a/libio/bits/stdio2-decl.h
+++ b/libio/bits/stdio2-decl.h
@@ -72,6 +72,14 @@  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,
 				 ...)
diff --git a/libio/bits/stdio2.h b/libio/bits/stdio2.h
index 193494db..92fe35b5 100644
--- a/libio/bits/stdio2.h
+++ b/libio/bits/stdio2.h
@@ -215,6 +215,18 @@  __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, ...))
@@ -247,6 +259,26 @@  __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),
@@ -264,6 +296,10 @@  __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
@@ -275,6 +311,12 @@  __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))
diff --git a/libio/stdio.h b/libio/stdio.h
index 3bf6a1f6..179441ed 100644
--- a/libio/stdio.h
+++ b/libio/stdio.h
@@ -412,6 +412,16 @@  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)))
+     __attribute_malloc__;
+extern char *aprintf (const char *__restrict __fmt, ...)
+     __THROWNL __attribute__ ((__format__ (__printf__, 1, 2)))
+     __attribute_malloc__;
+#endif
+
 #ifdef __USE_XOPEN2K8
 /* Write formatted output to a file descriptor.  */
 extern int vdprintf (int __fd, const char *__restrict __fmt,
diff --git a/libio/tst-aprintf.c b/libio/tst-aprintf.c
new file mode 100644
index 00000000..e2c3c911
--- /dev/null
+++ b/libio/tst-aprintf.c
@@ -0,0 +1,56 @@ 
+/* 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>
diff --git a/libio/vaprintf.c b/libio/vaprintf.c
new file mode 100644
index 00000000..6789052f
--- /dev/null
+++ b/libio/vaprintf.c
@@ -0,0 +1,39 @@ 
+/* 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 <libioP.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+
+char *
+__vaprintf (const char *fmt, va_list args)
+{
+  char *p;
+
+  return __vasprintf_internal (&p, fmt, args, 0) < 0 ? NULL : p;
+}
+ldbl_weak_alias (__vaprintf, vaprintf)
diff --git a/manual/stdio.texi b/manual/stdio.texi
index c01feaed..e7deca78 100644
--- a/manual/stdio.texi
+++ b/manual/stdio.texi
@@ -2578,6 +2578,32 @@  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{}}}
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 21094483..ec52c1ea 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -90,6 +90,7 @@  routines := \
   _itoa \
   _itowa \
   asprintf \
+  aprintf \
   ctermid \
   cuserid \
   dprintf \
@@ -178,6 +179,7 @@  routines := \
 # Exclude fortified routines from being built with _FORTIFY_SOURCE
 routines_no_fortify += \
   asprintf \
+  aprintf \
   dprintf \
   fprintf \
   printf \
diff --git a/stdio-common/Versions b/stdio-common/Versions
index 8e1cbf85..aee611b2 100644
--- a/stdio-common/Versions
+++ b/stdio-common/Versions
@@ -70,6 +70,8 @@  libc {
     __isoc23_vfscanf;
     __isoc23_sscanf;
     __isoc23_vsscanf;
+  GLIBC_2.44 {
+    aprintf;
   }
   GLIBC_PRIVATE {
     # global variables
diff --git a/stdio-common/aprintf.c b/stdio-common/aprintf.c
new file mode 100644
index 00000000..bee49667
--- /dev/null
+++ b/stdio-common/aprintf.c
@@ -0,0 +1,40 @@ 
+/* 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 <libioP.h>
+
+/* Write formatted output from FORMAT to a string which is
+   allocated with malloc.  */
+/* VARARGS1 */
+char *
+___aprintf (const char *fmt, ...)
+{
+  char *p;
+  va_list ap;
+
+  va_start (ap, fmt);
+  if (__vasprintf_internal (&p, fmt, ap, 0) < 0)
+    p = NULL;
+  va_end (ap);
+
+  return p;
+}
+ldbl_hidden_def (___aprintf, __aprintf)
+
+ldbl_strong_alias (___aprintf, __aprintf)
+ldbl_weak_alias (___aprintf, aprintf)