[libc-commits] [libc] [libc] Add program_invocation(_short)_name and tweak err.h functions (PR #212448)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Tue Jul 28 02:58:28 PDT 2026


https://github.com/labath created https://github.com/llvm/llvm-project/pull/212448

These GNU extensions hold the name of the program as invoked (argv[0]) and its short name (the basename after the last slash).

Both variables are initialized in the startup code. As with all of our other variables, they are only available in full build mode.

The trickiest part of this patch are the error reporting functions from <err.h>, which access this variable, and they are currently enabled in overlay mode. To make them work, I add an #ifdef to select the right version. I considered doing something more elaborate, like we have with `errno`, but that seemed too heavy for a single occurrence.

I also drop the linux check in this function. The documentation says the functions should print the "last component of the program name", which "llvmlibc" is not. If someone wants to enable these functions on non-linux, they can figure out what they want to print here and how.

This also means that the tests will not work (crash) in a franken-build configuration (using a full build mode, but linking the unit tests against the system libc. I'm working to remove that mode, but for now, I am disabling the test in this configuration.

Assisted by Gemini.

>From 28a791d0f2cc80a5e44ade4571ea329a69eebc24 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 20 Jul 2026 15:28:29 +0000
Subject: [PATCH] [libc] Add program_invocation(_short)_name and tweak err.h
 functions

These GNU extensions hold the name of the program as invoked (argv[0])
and its short name (the basename after the last slash).

Both variables are initialized in the startup code. As with all of our other
variables, they are only available in full build mode.

The trickiest part of this patch are the error reporting functions from <err.h>,
which access this variable, and they are currently enabled in overlay mode. To
make them work, I add an #ifdef to select the right version. I considered doing
something more elaborate, like we have with `errno`, but that seemed too heavy
for a single occurrence.

I also drop the linux check in this function. The documentation says the
functions should print the "last component of the program name", which
"llvmlibc" is not. If someone wants to enable these functions on non-linux, they
can figure out what they want to print here and how.

This also means that the tests will not work (crash) in a franken-build
configuration (using a full build mode, but linking the unit tests
against the system libc. I'm working to remove that mode, but for now, I
am disabling the test in this configuration.

Assisted by Gemini.
---
 libc/config/linux/aarch64/entrypoints.txt     |   4 +
 libc/config/linux/riscv/entrypoints.txt       |   4 +
 libc/config/linux/x86_64/entrypoints.txt      |   4 +
 libc/include/errno.yaml                       |   6 +-
 libc/src/err/CMakeLists.txt                   |   6 +
 libc/src/err/report.cpp                       |  14 +--
 libc/src/errno/CMakeLists.txt                 |  22 ++++
 libc/src/errno/program_invocation_name.cpp    |  18 +++
 libc/src/errno/program_invocation_name.h      |  20 ++++
 .../errno/program_invocation_short_name.cpp   |  18 +++
 .../src/errno/program_invocation_short_name.h |  20 ++++
 libc/startup/linux/CMakeLists.txt             |   2 +
 libc/startup/linux/do_start.cpp               |  11 ++
 .../integration/startup/linux/CMakeLists.txt  |  11 ++
 .../startup/linux/invocation_name_test.cpp    | 103 ++++++++++++++++++
 libc/test/src/err/CMakeLists.txt              |  22 ++--
 16 files changed, 269 insertions(+), 16 deletions(-)
 create mode 100644 libc/src/errno/program_invocation_name.cpp
 create mode 100644 libc/src/errno/program_invocation_name.h
 create mode 100644 libc/src/errno/program_invocation_short_name.cpp
 create mode 100644 libc/src/errno/program_invocation_short_name.h
 create mode 100644 libc/test/integration/startup/linux/invocation_name_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 046fd301ea01b..3b3438e7d67e0 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1037,6 +1037,10 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.dirent.readdir
     libc.src.dirent.fdopendir
 
+    # errno.h entrypoints
+    libc.src.errno.program_invocation_name
+    libc.src.errno.program_invocation_short_name
+
     # arpa/inet.h entrypoints
     libc.src.arpa.inet.htonl
     libc.src.arpa.inet.htons
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index c6a4b431f33de..7943a9e92a4b9 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1219,6 +1219,10 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.ctype.tolower_l
     libc.src.ctype.toupper_l
 
+    # errno.h entrypoints
+    libc.src.errno.program_invocation_name
+    libc.src.errno.program_invocation_short_name
+
     # stdlib.h entrypoints
     libc.src.stdlib.strtod_l
     libc.src.stdlib.strtof_l
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index a86875fa4b9e8..d9f0f1b228840 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1224,6 +1224,10 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.ctype.tolower_l
     libc.src.ctype.toupper_l
 
+    # errno.h entrypoints
+    libc.src.errno.program_invocation_name
+    libc.src.errno.program_invocation_short_name
+
     # stdlib.h entrypoints
     libc.src.stdlib.strtod_l
     libc.src.stdlib.strtof_l
diff --git a/libc/include/errno.yaml b/libc/include/errno.yaml
index e29d15ba3e3cb..ead1fc4ef2398 100644
--- a/libc/include/errno.yaml
+++ b/libc/include/errno.yaml
@@ -8,5 +8,9 @@ macros: []
 types:
   - type_name: errno_t
 enums: []
-objects: []
+objects:
+  - object_name: program_invocation_name
+    object_type: char *
+  - object_name: program_invocation_short_name
+    object_type: char *
 functions: []
diff --git a/libc/src/err/CMakeLists.txt b/libc/src/err/CMakeLists.txt
index 8f72d2178ea77..a12f006f5f543 100644
--- a/libc/src/err/CMakeLists.txt
+++ b/libc/src/err/CMakeLists.txt
@@ -2,6 +2,11 @@ if(NOT TARGET libc.src.__support.OSUtil.osutil)
   return()
 endif()
 
+set(extra_deps)
+if(LLVM_LIBC_FULL_BUILD)
+  list(APPEND extra_deps libc.src.errno.program_invocation_short_name)
+endif()
+
 add_object_library(
   report
   SRCS
@@ -18,6 +23,7 @@ add_object_library(
     libc.src.__support.printf_core.writer
     libc.src.__support.CPP.string_view
     libc.src.__support.macros.config
+    ${extra_deps}
 )
 
 add_entrypoint_object(
diff --git a/libc/src/err/report.cpp b/libc/src/err/report.cpp
index 1dd69dce3a5f0..49dfc5088630a 100644
--- a/libc/src/err/report.cpp
+++ b/libc/src/err/report.cpp
@@ -23,8 +23,13 @@
 #include "src/__support/printf_core/printf_main.h"
 #include "src/__support/printf_core/writer.h"
 
-#ifdef __linux__
+#ifdef LIBC_FULL_BUILD
+#include "src/errno/program_invocation_short_name.h"
+#define PROGRAM_INVOCATION_SHORT_NAME                                          \
+  LIBC_NAMESPACE::program_invocation_short_name
+#else
 extern "C" char *program_invocation_short_name;
+#define PROGRAM_INVOCATION_SHORT_NAME ::program_invocation_short_name
 #endif
 
 namespace LIBC_NAMESPACE_DECL {
@@ -32,12 +37,7 @@ namespace err_reporting {
 
 void report(bool show_err, int err_num, const char *fmt,
             internal::ArgList &args) {
-  const char *progname = "libllvmlibc";
-  // TODO: Use a proper way to get progname if available.
-#ifdef __linux__
-  progname = program_invocation_short_name;
-#endif
-
+  const char *progname = PROGRAM_INVOCATION_SHORT_NAME;
   char buffer[1024];
   printf_core::FlushingBuffer wb(
       buffer, sizeof(buffer),
diff --git a/libc/src/errno/CMakeLists.txt b/libc/src/errno/CMakeLists.txt
index 2852044e94164..95d91dd02d07a 100644
--- a/libc/src/errno/CMakeLists.txt
+++ b/libc/src/errno/CMakeLists.txt
@@ -14,3 +14,25 @@ add_entrypoint_object(
     libc.src.__support.macros.attributes
     libc.src.__support.macros.config
 )
+
+add_entrypoint_object(
+  program_invocation_name
+  SRCS
+    program_invocation_name.cpp
+  HDRS
+    program_invocation_name.h
+  DEPENDS
+    libc.src.__support.common
+    libc.src.__support.macros.config
+)
+
+add_entrypoint_object(
+  program_invocation_short_name
+  SRCS
+    program_invocation_short_name.cpp
+  HDRS
+    program_invocation_short_name.h
+  DEPENDS
+    libc.src.__support.common
+    libc.src.__support.macros.config
+)
diff --git a/libc/src/errno/program_invocation_name.cpp b/libc/src/errno/program_invocation_name.cpp
new file mode 100644
index 0000000000000..f5b10149930ee
--- /dev/null
+++ b/libc/src/errno/program_invocation_name.cpp
@@ -0,0 +1,18 @@
+//===-- Implementation of program_invocation_name -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/errno/program_invocation_name.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Initialized by startup code.
+LLVM_LIBC_VARIABLE(char *, program_invocation_name) = nullptr;
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/errno/program_invocation_name.h b/libc/src/errno/program_invocation_name.h
new file mode 100644
index 0000000000000..a53fd11f2f309
--- /dev/null
+++ b/libc/src/errno/program_invocation_name.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for program_invocation_name -------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_ERRNO_PROGRAM_INVOCATION_NAME_H
+#define LLVM_LIBC_SRC_ERRNO_PROGRAM_INVOCATION_NAME_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+extern char *program_invocation_name;
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_ERRNO_PROGRAM_INVOCATION_NAME_H
diff --git a/libc/src/errno/program_invocation_short_name.cpp b/libc/src/errno/program_invocation_short_name.cpp
new file mode 100644
index 0000000000000..74f8c887166e2
--- /dev/null
+++ b/libc/src/errno/program_invocation_short_name.cpp
@@ -0,0 +1,18 @@
+//===-- Implementation of program_invocation_short_name -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/errno/program_invocation_short_name.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Initialized by startup code.
+LLVM_LIBC_VARIABLE(char *, program_invocation_short_name) = nullptr;
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/errno/program_invocation_short_name.h b/libc/src/errno/program_invocation_short_name.h
new file mode 100644
index 0000000000000..eeb700e6a374b
--- /dev/null
+++ b/libc/src/errno/program_invocation_short_name.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for program_invocation_short_name -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_ERRNO_PROGRAM_INVOCATION_SHORT_NAME_H
+#define LLVM_LIBC_SRC_ERRNO_PROGRAM_INVOCATION_SHORT_NAME_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+extern char *program_invocation_short_name;
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_ERRNO_PROGRAM_INVOCATION_SHORT_NAME_H
diff --git a/libc/startup/linux/CMakeLists.txt b/libc/startup/linux/CMakeLists.txt
index e2c0b5cf791fe..e08f30a16749e 100644
--- a/libc/startup/linux/CMakeLists.txt
+++ b/libc/startup/linux/CMakeLists.txt
@@ -123,6 +123,8 @@ add_object_library(
     libc.src.stdlib.exit
     libc.src.stdlib.atexit
     libc.src.unistd.environ
+    libc.src.errno.program_invocation_name
+    libc.src.errno.program_invocation_short_name
   COMPILE_OPTIONS
     -ffreestanding       # To avoid compiler warnings about calling the main function.
     -fno-builtin         # avoid emit unexpected calls
diff --git a/libc/startup/linux/do_start.cpp b/libc/startup/linux/do_start.cpp
index a54513654a803..f2e8da5327832 100644
--- a/libc/startup/linux/do_start.cpp
+++ b/libc/startup/linux/do_start.cpp
@@ -15,6 +15,8 @@
 #include "src/__support/OSUtil/syscall.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/threads/thread.h"
+#include "src/errno/program_invocation_name.h"
+#include "src/errno/program_invocation_short_name.h"
 #include "src/stdlib/atexit.h"
 #include "src/stdlib/exit.h"
 #include "src/unistd/environ.h"
@@ -83,6 +85,15 @@ static TLSDescriptor tls;
   // Initialize the POSIX global declared in unistd.h
   environ = reinterpret_cast<char **>(env_ptr);
 
+  if (app.args->argc > 0 && app.args->argv[0] != 0) {
+    program_invocation_name = reinterpret_cast<char *>(app.args->argv[0]);
+    program_invocation_short_name = program_invocation_name;
+    for (char *p = program_invocation_name; *p != '\0'; ++p) {
+      if (*p == '/')
+        program_invocation_short_name = p + 1;
+    }
+  }
+
   // After the env array, is the aux-vector. The end of the aux-vector is
   // denoted by an AT_NULL entry.
   ElfW(Phdr) *program_hdr_table = nullptr;
diff --git a/libc/test/integration/startup/linux/CMakeLists.txt b/libc/test/integration/startup/linux/CMakeLists.txt
index 26ad9aa25623c..2f586422e504a 100644
--- a/libc/test/integration/startup/linux/CMakeLists.txt
+++ b/libc/test/integration/startup/linux/CMakeLists.txt
@@ -18,6 +18,17 @@ add_integration_test(
     GERMANY=Berlin
 )
 
+add_integration_test(
+  invocation_name_test
+  SUITE libc-startup-tests
+  SRCS
+    invocation_name_test.cpp
+  DEPENDS
+    libc.src.errno.program_invocation_name
+    libc.src.errno.program_invocation_short_name
+    libc.src.unistd.execve
+)
+
 add_integration_test(
   startup_no_envp_test
   SUITE libc-startup-tests
diff --git a/libc/test/integration/startup/linux/invocation_name_test.cpp b/libc/test/integration/startup/linux/invocation_name_test.cpp
new file mode 100644
index 0000000000000..06f480d2b3faa
--- /dev/null
+++ b/libc/test/integration/startup/linux/invocation_name_test.cpp
@@ -0,0 +1,103 @@
+//===-- Integration test for program_invocation_name ---------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/errno/program_invocation_name.h"
+#include "src/errno/program_invocation_short_name.h"
+#include "src/unistd/execve.h"
+#include "test/IntegrationTest/test.h"
+
+static bool my_streq(const char *lhs, const char *rhs) {
+  const char *l, *r;
+  for (l = lhs, r = rhs; *l != '\0' && *r != '\0'; ++l, ++r)
+    if (*l != *r)
+      return false;
+
+  return *l == '\0' && *r == '\0';
+}
+
+TEST_MAIN(int argc, char **argv, char **envp) {
+  ASSERT_TRUE(LIBC_NAMESPACE::program_invocation_name != nullptr);
+  ASSERT_TRUE(LIBC_NAMESPACE::program_invocation_short_name != nullptr);
+  ASSERT_TRUE(LIBC_NAMESPACE::program_invocation_name == argv[0]);
+
+  if (argc == 3 && my_streq(argv[1], "reexec1")) {
+    // Step 1: Executed with an absolute path containing slashes.
+    ASSERT_TRUE(my_streq(LIBC_NAMESPACE::program_invocation_name,
+                         "/known/path/to/invocation_name"));
+    ASSERT_TRUE(my_streq(LIBC_NAMESPACE::program_invocation_short_name,
+                         "invocation_name"));
+
+    const char *self_path = argv[2];
+    char *const child_argv[] = {
+        const_cast<char *>("./relative/path/to/invocation_name"),
+        const_cast<char *>("reexec2"),
+        const_cast<char *>(self_path),
+        nullptr,
+    };
+    LIBC_NAMESPACE::execve(self_path, child_argv, envp);
+    ASSERT_TRUE(false);
+  }
+
+  if (argc == 3 && my_streq(argv[1], "reexec2")) {
+    // Step 2: Executed with a relative path containing slashes.
+    ASSERT_TRUE(my_streq(LIBC_NAMESPACE::program_invocation_name,
+                         "./relative/path/to/invocation_name"));
+    ASSERT_TRUE(my_streq(LIBC_NAMESPACE::program_invocation_short_name,
+                         "invocation_name"));
+
+    const char *self_path = argv[2];
+    char *const child_argv[] = {
+        const_cast<char *>(""),
+        const_cast<char *>("reexec3"),
+        const_cast<char *>(self_path),
+        nullptr,
+    };
+    LIBC_NAMESPACE::execve(self_path, child_argv, envp);
+    ASSERT_TRUE(false);
+  }
+
+  if (argc == 3 && my_streq(argv[1], "reexec3")) {
+    // Step 3: Executed with an empty string.
+    ASSERT_TRUE(LIBC_NAMESPACE::program_invocation_name ==
+                LIBC_NAMESPACE::program_invocation_short_name);
+    ASSERT_TRUE(my_streq(LIBC_NAMESPACE::program_invocation_name, ""));
+    ASSERT_TRUE(my_streq(LIBC_NAMESPACE::program_invocation_short_name, ""));
+
+    const char *self_path = argv[2];
+    char *const child_argv[] = {
+        const_cast<char *>("invocation_name_no_slash"),
+        const_cast<char *>("reexec4"),
+        nullptr,
+    };
+    LIBC_NAMESPACE::execve(self_path, child_argv, envp);
+    ASSERT_TRUE(false);
+  }
+
+  if (argc == 2 && my_streq(argv[1], "reexec4")) {
+    // Step 4: Executed with a path without slashes.
+    ASSERT_TRUE(LIBC_NAMESPACE::program_invocation_name ==
+                LIBC_NAMESPACE::program_invocation_short_name);
+    ASSERT_TRUE(my_streq(LIBC_NAMESPACE::program_invocation_name,
+                         "invocation_name_no_slash"));
+    ASSERT_TRUE(my_streq(LIBC_NAMESPACE::program_invocation_short_name,
+                         "invocation_name_no_slash"));
+    return 0;
+  }
+
+  // Step 0: Initial run. Re-exec self with known argv[0] values.
+  const char *self_path = argv[0];
+  char *const child_argv[] = {
+      const_cast<char *>("/known/path/to/invocation_name"),
+      const_cast<char *>("reexec1"),
+      const_cast<char *>(self_path),
+      nullptr,
+  };
+  LIBC_NAMESPACE::execve(self_path, child_argv, envp);
+  ASSERT_TRUE(false);
+  return 1;
+}
diff --git a/libc/test/src/err/CMakeLists.txt b/libc/test/src/err/CMakeLists.txt
index f1e1faddc5249..e25a82c30fae0 100644
--- a/libc/test/src/err/CMakeLists.txt
+++ b/libc/test/src/err/CMakeLists.txt
@@ -4,9 +4,15 @@ if(NOT TARGET libc.src.__support.OSUtil.osutil)
   return()
 endif()
 
+if(LLVM_LIBC_FULL_BUILD)
+  set(MATCHING_CONFIG_ONLY HERMETIC_TEST_ONLY)
+else()
+  set(MATCHING_CONFIG_ONLY UNIT_TEST_ONLY)
+endif()
+
 add_libc_test(
   err_test
-  UNIT_TEST_ONLY
+  ${MATCHING_CONFIG_ONLY}
   SUITE
     libc_err_unittests
   SRCS
@@ -18,7 +24,7 @@ add_libc_test(
 
 add_libc_test(
   errx_test
-  UNIT_TEST_ONLY
+  ${MATCHING_CONFIG_ONLY}
   SUITE
     libc_err_unittests
   SRCS
@@ -29,7 +35,7 @@ add_libc_test(
 
 add_libc_test(
   verr_test
-  UNIT_TEST_ONLY
+  ${MATCHING_CONFIG_ONLY}
   SUITE
     libc_err_unittests
   SRCS
@@ -41,7 +47,7 @@ add_libc_test(
 
 add_libc_test(
   verrx_test
-  UNIT_TEST_ONLY
+  ${MATCHING_CONFIG_ONLY}
   SUITE
     libc_err_unittests
   SRCS
@@ -52,7 +58,7 @@ add_libc_test(
 
 add_libc_test(
   warn_test
-  UNIT_TEST_ONLY
+  ${MATCHING_CONFIG_ONLY}
   SUITE
     libc_err_unittests
   SRCS
@@ -64,7 +70,7 @@ add_libc_test(
 
 add_libc_test(
   warnx_test
-  UNIT_TEST_ONLY
+  ${MATCHING_CONFIG_ONLY}
   SUITE
     libc_err_unittests
   SRCS
@@ -76,7 +82,7 @@ add_libc_test(
 
 add_libc_test(
   vwarn_test
-  UNIT_TEST_ONLY
+  ${MATCHING_CONFIG_ONLY}
   SUITE
     libc_err_unittests
   SRCS
@@ -88,7 +94,7 @@ add_libc_test(
 
 add_libc_test(
   vwarnx_test
-  UNIT_TEST_ONLY
+  ${MATCHING_CONFIG_ONLY}
   SUITE
     libc_err_unittests
   SRCS



More information about the libc-commits mailing list