]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
basic/stdio-util: introduce asprintf_safe
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 3 Mar 2026 09:00:29 +0000 (10:00 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Fri, 6 Mar 2026 16:46:59 +0000 (17:46 +0100)
asprintf is nice to use, but the _documented_ error return convention is
unclear:
> If  memory  allocation  wasn't possible, or some other error occurs,
> these functions will return -1, and the contents of strp are undefined.

What exactly "undefined" means is up for debate: if it was really
undefined, the caller wouldn't be able to meaningfully clean up, because
they wouldn't know if strp is a valid pointer. So far we interpreted
"undefined" — in some parts of the code base — as "either NULL or a
valid pointer that needs to be freed", and — in other parts of the
codebase — as "always NULL". I checked glibc and musl, and they both
uncoditionally set the output pointer to NULL on failure.

There is also no information _why_ asprintf failed. It could be an
allocation error or format string error. But we just don't have this
information.

Let's add a wrapper that either returns a good string or a NULL pointer.
Since there's just one failure result, we don't need a separate return
value and an output argument and can simplify callers.

docs/CODING_STYLE.md
src/basic/meson.build
src/basic/stdio-util.c [new file with mode: 0644]
src/basic/stdio-util.h

index 767ab6734bb83f404ba4ad8b9c7c9af32a5bdbd9..085c97df5ce44e694ee0e4817ebc47349557199c 100644 (file)
@@ -754,9 +754,9 @@ SPDX-License-Identifier: LGPL-2.1-or-later
   section of the `alloca(3)` man page.
 
 - If you want to concatenate two or more strings, consider using `strjoina()`
-  or `strjoin()` rather than `asprintf()`, as the latter is a lot slower. This
-  matters particularly in inner loops (but note that `strjoina()` cannot be
-  used there).
+  or `strjoin()` rather than `asprintf()` or `asprintf_safe`, as the latter is
+  a lot slower. This matters particularly in inner loops (but note that
+  `strjoina()` cannot be used there).
 
 ## Runtime Behaviour
 
index b8ffa80244d07b3c4c4b27d99d41018577720e31..775dc1fa3d59509d8e599c2bd830dd2fbe9a3ed5 100644 (file)
@@ -99,6 +99,7 @@ basic_sources = files(
         'sort-util.c',
         'stat-util.c',
         'static-destruct.c',
+        'stdio-util.c',
         'strbuf.c',
         'string-table.c',
         'string-util.c',
diff --git a/src/basic/stdio-util.c b/src/basic/stdio-util.c
new file mode 100644 (file)
index 0000000..53267f0
--- /dev/null
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "stdio-util.h"
+
+char* asprintf_safe(const char *restrict fmt, ...) {
+        _cleanup_free_ char *buf = NULL;
+        va_list ap;
+        int r;
+
+        va_start(ap, fmt);
+        r = vasprintf(&buf, fmt, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return NULL;
+        return TAKE_PTR(buf);
+}
index f8ab0f0012fb7dbf89d904b5158cbc75cb04b848..f8055b3853bccca9fad39b921b018cc34785d634 100644 (file)
@@ -21,6 +21,8 @@ static inline char* snprintf_ok(char *buf, size_t len, const char *format, ...)
 #define xsprintf(buf, fmt, ...) \
         assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, ##__VA_ARGS__), "xsprintf: buffer too small")
 
+char* asprintf_safe(const char *restrict fmt, ...) _printf_(1, 2);
+
 #define VA_FORMAT_ADVANCE(format, ap)                                   \
 do {                                                                    \
         int _argtypes[128];                                             \