[libc-commits] [libc] 5c7854c - [libc][stdlib] Add setenv (#163018)
via libc-commits
libc-commits at lists.llvm.org
Sun May 10 22:57:05 PDT 2026
Author: Jeff Bailey
Date: 2026-05-11T06:57:00+01:00
New Revision: 5c7854c8ed1998650af6f01761df385712f5295a
URL: https://github.com/llvm/llvm-project/commit/5c7854c8ed1998650af6f01761df385712f5295a
DIFF: https://github.com/llvm/llvm-project/commit/5c7854c8ed1998650af6f01761df385712f5295a.diff
LOG: [libc][stdlib] Add setenv (#163018)
Add the POSIX setenv() function, with EnvironmentManager::set()
handling environment array management and ownership tracking.
Registered for x86_64, aarch64, and riscv architectures. Integration
tests cover overwrite/no-overwrite semantics, empty/invalid names,
empty values, and repeated replacement.
Assisted-by: Automated tooling, human reviewed.
---------
Co-authored-by: Michael Jones <michaelrj at google.com>
Added:
libc/src/stdlib/linux/setenv.cpp
libc/src/stdlib/setenv.h
libc/test/integration/src/stdlib/setenv_test.cpp
Modified:
libc/config/linux/aarch64/entrypoints.txt
libc/config/linux/riscv/entrypoints.txt
libc/config/linux/x86_64/entrypoints.txt
libc/include/stdlib.yaml
libc/src/stdlib/CMakeLists.txt
libc/src/stdlib/environ_internal.cpp
libc/src/stdlib/environ_internal.h
libc/src/stdlib/linux/CMakeLists.txt
libc/test/integration/src/stdlib/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 643bba2aae694..9994a9294173d 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1148,6 +1148,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.atexit
libc.src.stdlib.exit
libc.src.stdlib.getenv
+ libc.src.stdlib.setenv
libc.src.stdlib.quick_exit
# signal.h entrypoints
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index bbb7aca7f39b6..2748b2b8e6a5d 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1277,6 +1277,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.atexit
libc.src.stdlib.exit
libc.src.stdlib.getenv
+ libc.src.stdlib.setenv
libc.src.stdlib.quick_exit
# signal.h entrypoints
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 2a0e43744ec0d..4b551ced82138 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1341,6 +1341,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.atexit
libc.src.stdlib.exit
libc.src.stdlib.getenv
+ libc.src.stdlib.setenv
libc.src.stdlib.mbstowcs
libc.src.stdlib.mbtowc
libc.src.stdlib.quick_exit
diff --git a/libc/include/stdlib.yaml b/libc/include/stdlib.yaml
index a751f8306be24..4c958cd9d28ad 100644
--- a/libc/include/stdlib.yaml
+++ b/libc/include/stdlib.yaml
@@ -209,6 +209,14 @@ functions:
return_type: void
arguments:
- type: unsigned int
+ - name: setenv
+ standards:
+ - posix
+ return_type: int
+ arguments:
+ - type: const char *
+ - type: const char *
+ - type: int
- name: strfromd
standards:
- stdc
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 7e587235447b0..e79353eb2a581 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -91,6 +91,7 @@ add_object_library(
libc.src.__support.CPP.string_view
libc.src.__support.macros.attributes
libc.src.__support.macros.config
+ libc.src.string.memory_utils.inline_memcpy
)
add_entrypoint_object(
@@ -635,6 +636,13 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
endif()
+add_entrypoint_object(
+ setenv
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.setenv
+)
+
if(LIBC_TARGET_OS_IS_BAREMETAL OR LIBC_TARGET_OS_IS_GPU)
add_entrypoint_object(
malloc
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index a6b630921f9f1..e9da17d028575 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -17,6 +17,7 @@
#include "src/__support/CPP/string_view.h"
#include "src/__support/alloc-checker.h"
#include "src/__support/macros/config.h"
+#include "src/string/memory_utils/inline_memcpy.h"
namespace LIBC_NAMESPACE_DECL {
namespace internal {
@@ -171,5 +172,54 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
return true;
}
+int EnvironmentManager::set(cpp::string_view name, cpp::string_view value,
+ bool overwrite) {
+ cpp::optional<size_t> idx = find_var(name);
+
+ // If the variable exists and we're not overwriting, do nothing.
+ if (idx && !overwrite)
+ return 0;
+
+ // Ensure we have capacity. If the variable doesn't exist, we need one
+ // more slot.
+ size_t needed = idx ? count : count + 1;
+ if (!ensure_capacity(needed))
+ return -1;
+
+ // Build the "name=value" string.
+ size_t name_len = name.size();
+ size_t value_len = value.size();
+ size_t total_len = name_len + 1 + value_len + 1; // name + '=' + value + '\0'
+
+ AllocChecker ac;
+ char *new_string = new (ac) char[total_len];
+ if (!ac)
+ return -1;
+
+ inline_memcpy(new_string, name.data(), name_len);
+ new_string[name_len] = '=';
+ inline_memcpy(new_string + name_len + 1, value.data(), value_len);
+ new_string[name_len + 1 + value_len] = '\0';
+
+ char **env_array = get_array();
+
+ if (idx) {
+ // Replace existing variable. Free old string if we own it.
+ if (ownership[*idx].can_free())
+ delete[] env_array[*idx];
+
+ env_array[*idx] = new_string;
+ ownership[*idx].allocated_by_us = true;
+ } else {
+ // Add new variable at the end.
+ env_array[count] = new_string;
+ ownership[count].allocated_by_us = true;
+ count++;
+ env_array[count] = nullptr; // Maintain null terminator.
+ }
+
+ return 0;
+}
+
} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/environ_internal.h b/libc/src/stdlib/environ_internal.h
index 0ab75e200b968..d902354f421cf 100644
--- a/libc/src/stdlib/environ_internal.h
+++ b/libc/src/stdlib/environ_internal.h
@@ -122,6 +122,13 @@ class EnvironmentManager {
// Look up a variable by name. Returns a pointer to the value string
// (after the '='), or nullptr if not found.
char *get(cpp::string_view name);
+
+ // Set or update an environment variable. Builds a "name=value" string,
+ // manages ownership, and updates the environ array. If `overwrite` is
+ // false and the variable already exists, does nothing and returns 0.
+ // Returns 0 on success, -1 on allocation failure (caller should set
+ // errno to ENOMEM).
+ int set(cpp::string_view name, cpp::string_view value, bool overwrite);
};
} // namespace internal
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 8a4b2bab1c53d..aedcd3f11bf38 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -8,3 +8,18 @@ add_header_library(
libc.src.signal.linux.__restore
libc.src.signal.linux.signal_utils
)
+
+add_entrypoint_object(
+ setenv
+ SRCS
+ setenv.cpp
+ HDRS
+ ../setenv.h
+ DEPENDS
+ libc.src.__support.CPP.string_view
+ libc.src.__support.common
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
+ libc.src.__support.macros.null_check
+ libc.src.stdlib.environ_internal
+)
diff --git a/libc/src/stdlib/linux/setenv.cpp b/libc/src/stdlib/linux/setenv.cpp
new file mode 100644
index 0000000000000..40a3b985bf251
--- /dev/null
+++ b/libc/src/stdlib/linux/setenv.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the POSIX setenv function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/setenv.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/stdlib/environ_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, setenv,
+ (const char *name, const char *value, int overwrite)) {
+ // Passing nullptr for either argument is undefined behavior per POSIX,
+ // so crash rather than returning an error.
+ LIBC_CRASH_ON_NULLPTR(name);
+ LIBC_CRASH_ON_NULLPTR(value);
+
+ cpp::string_view name_view(name);
+
+ // POSIX: name must not be empty or contain '='.
+ if (name_view.empty() ||
+ name_view.find_first_of('=') != cpp::string_view::npos) {
+ libc_errno = EINVAL;
+ return -1;
+ }
+
+ int result = internal::EnvironmentManager::get_instance().set(
+ name_view, value, overwrite != 0);
+ if (result != 0)
+ libc_errno = ENOMEM;
+
+ return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/setenv.h b/libc/src/stdlib/setenv.h
new file mode 100644
index 0000000000000..365d768cdd668
--- /dev/null
+++ b/libc/src/stdlib/setenv.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of the POSIX setenv function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDLIB_SETENV_H
+#define LLVM_LIBC_SRC_STDLIB_SETENV_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int setenv(const char *name, const char *value, int overwrite);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDLIB_SETENV_H
diff --git a/libc/test/integration/src/stdlib/CMakeLists.txt b/libc/test/integration/src/stdlib/CMakeLists.txt
index cf39b7ceb2f87..2e0389f9a60d3 100644
--- a/libc/test/integration/src/stdlib/CMakeLists.txt
+++ b/libc/test/integration/src/stdlib/CMakeLists.txt
@@ -18,6 +18,18 @@ add_integration_test(
)
if(${LIBC_TARGET_OS} STREQUAL "linux")
+ add_integration_test(
+ setenv_test
+ SUITE
+ stdlib-integration-tests
+ SRCS
+ setenv_test.cpp
+ DEPENDS
+ libc.src.stdlib.getenv
+ libc.src.stdlib.setenv
+ libc.src.string.strcmp
+ )
+
add_integration_test(
abort_test
SUITE
diff --git a/libc/test/integration/src/stdlib/setenv_test.cpp b/libc/test/integration/src/stdlib/setenv_test.cpp
new file mode 100644
index 0000000000000..e4ff827f0a807
--- /dev/null
+++ b/libc/test/integration/src/stdlib/setenv_test.cpp
@@ -0,0 +1,175 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Integration tests for the POSIX setenv function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/getenv.h"
+#include "src/stdlib/setenv.h"
+#include "src/string/strcmp.h"
+
+#include "test/IntegrationTest/test.h"
+
+#include <errno.h>
+
+TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
+ [[maybe_unused]] char **envp) {
+ // Test: Basic
+ {
+ // Set a simple environment variable
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("SETENV_TEST_VAR", "test_value", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+
+ // Verify it was set
+ char *value = LIBC_NAMESPACE::getenv("SETENV_TEST_VAR");
+ ASSERT_NE(value, nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "test_value"), 0);
+ }
+
+ // Test: OverwriteExisting
+ {
+ // Set initial value
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("OVERWRITE_VAR", "original", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("OVERWRITE_VAR"),
+ "original"),
+ 0);
+
+ // Overwrite with new value (overwrite = 1)
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("OVERWRITE_VAR", "replaced", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("OVERWRITE_VAR"),
+ "replaced"),
+ 0);
+ }
+
+ // Test: NoOverwriteFlag
+ {
+ // Set initial value
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("NO_OVERWRITE_VAR", "original", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"),
+ "original"),
+ 0);
+
+ // Try to set with overwrite = 0 (should not change)
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("NO_OVERWRITE_VAR", "ignored", 0), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"),
+ "original"),
+ 0);
+
+ // Verify it still works with overwrite = 1
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("NO_OVERWRITE_VAR", "changed", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"),
+ "changed"),
+ 0);
+ }
+
+ // Note: passing nullptr for name or value is undefined behavior per POSIX.
+ // The implementation uses LIBC_CRASH_ON_NULLPTR for both, so there is no
+ // test for that case here.
+
+ // Test: EmptyName
+ {
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("", "value", 1), -1);
+ ASSERT_ERRNO_EQ(EINVAL);
+ }
+
+ // Test: NameWithEquals
+ {
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("BAD=NAME", "value", 1), -1);
+ ASSERT_ERRNO_EQ(EINVAL);
+ }
+
+ // Test: EmptyValue
+ {
+ // Empty value is valid - just means variable is set to empty string
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("EMPTY_VALUE_VAR", "", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+
+ char *value = LIBC_NAMESPACE::getenv("EMPTY_VALUE_VAR");
+ ASSERT_NE(value, nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, ""), 0);
+ }
+
+ // Test: MultipleVariables
+ {
+ // Set multiple
diff erent variables
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("VAR1", "value1", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("VAR2", "value2", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("VAR3", "value3", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+
+ // Verify all are set correctly
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("VAR1"), "value1"),
+ 0);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("VAR2"), "value2"),
+ 0);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("VAR3"), "value3"),
+ 0);
+ }
+
+ // Test: LongValues
+ {
+ // Test with longer strings
+ const char *long_name = "LONG_VAR_NAME_FOR_TESTING";
+ const char *long_value = "This is a fairly long value string to test that "
+ "setenv handles longer strings correctly without "
+ "any memory issues or truncation problems";
+
+ ASSERT_EQ(LIBC_NAMESPACE::setenv(long_name, long_value, 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(
+ LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv(long_name), long_value),
+ 0);
+ }
+
+ // Test: SpecialCharacters
+ {
+ // Test with special characters in value (but not in name)
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("SPECIAL_CHARS", "!@#$%^&*()", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("SPECIAL_CHARS"),
+ "!@#$%^&*()"),
+ 0);
+ }
+
+ // Test: ReplaceMultipleTimes
+ {
+ // Replace the same variable multiple times
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value1", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
+ "value1"),
+ 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value2", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
+ "value2"),
+ 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value3", 1), 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
+ "value3"),
+ 0);
+ }
+
+ return 0;
+}
More information about the libc-commits
mailing list