[libc-commits] [libc] [libc][stdlib] Implement setenv() with environment management infrastructure (PR #163018)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Fri Dec 19 03:36:42 PST 2025
https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/163018
>From d83be34f27759484c7e7a421ef4ae07375a17f3a Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Sat, 11 Oct 2025 19:48:00 +0000
Subject: [PATCH 1/7] [libc][stdlib] Implement setenv() with environment
management infrastructure
Implement setenv() for LLVM libc, enabling modification of process
environment variables in compliance with POSIX specifications.
Implementation Overview:
- Shared infrastructure in environ_internal.{h,cpp} providing thread-safe
environment array management with copy-on-write semantics
- Memory ownership tracking to distinguish library-allocated strings
from external strings (startup environ)
- Thread-safe operations using environ_mutex for all modifications
- Dynamic array growth with configurable initial capacity
Key Design Decisions:
- Linear array approach (O(n) lookups) suitable for typical environment
sizes (30-80 variables) with minimal memory overhead
- Copy-on-write: starts with startup environment, allocates on first
modification to avoid unnecessary copying
- Memory ownership model prevents double-free bugs: tracks which strings
can be safely freed (setenv allocations)
Function Implementation:
- setenv(name, value, overwrite): Allocates "name=value" string, respects
overwrite flag, validates inputs per POSIX (rejects NULL, empty names,
names with '='). Returns 0 on success, -1 with errno on error.
Testing:
- Comprehensive integration test suite covering basic functionality,
POSIX compliance, error conditions, and edge cases
- 12 test cases passing
---
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/include/stdlib.yaml | 8 +
libc/src/stdlib/CMakeLists.txt | 32 +++
libc/src/stdlib/environ_internal.cpp | 165 +++++++++++++++
libc/src/stdlib/environ_internal.h | 82 ++++++++
libc/src/stdlib/setenv.cpp | 141 +++++++++++++
libc/src/stdlib/setenv.h | 20 ++
.../integration/src/stdlib/CMakeLists.txt | 12 ++
.../integration/src/stdlib/setenv_test.cpp | 190 ++++++++++++++++++
9 files changed, 651 insertions(+)
create mode 100644 libc/src/stdlib/environ_internal.cpp
create mode 100644 libc/src/stdlib/environ_internal.h
create mode 100644 libc/src/stdlib/setenv.cpp
create mode 100644 libc/src/stdlib/setenv.h
create mode 100644 libc/test/integration/src/stdlib/setenv_test.cpp
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 9399b284fa2da..8ce787a6ea4f7 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1264,6 +1264,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.mbstowcs
libc.src.stdlib.mbtowc
libc.src.stdlib.quick_exit
+ libc.src.stdlib.setenv
libc.src.stdlib.wcstombs
libc.src.stdlib.wctomb
diff --git a/libc/include/stdlib.yaml b/libc/include/stdlib.yaml
index 4752244279243..8a12d41714f56 100644
--- a/libc/include/stdlib.yaml
+++ b/libc/include/stdlib.yaml
@@ -197,6 +197,14 @@ functions:
return_type: int
arguments:
- type: void
+ - name: setenv
+ standards:
+ - posix
+ return_type: int
+ arguments:
+ - type: const char *
+ - type: const char *
+ - type: int
- name: srand
standards:
- stdc
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 62da469f0eb9e..409a4ccd0acdf 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -65,6 +65,38 @@ add_entrypoint_object(
libc.config.app_h
)
+add_object_library(
+ environ_internal
+ SRCS
+ environ_internal.cpp
+ HDRS
+ environ_internal.h
+ DEPENDS
+ libc.config.app_h
+ libc.hdr.types.size_t
+ libc.src.__support.CPP.string_view
+ libc.src.__support.threads.mutex
+ libc.src.stdlib.free
+ libc.src.stdlib.malloc
+ libc.src.string.memcpy
+)
+
+add_entrypoint_object(
+ setenv
+ SRCS
+ setenv.cpp
+ HDRS
+ setenv.h
+ DEPENDS
+ .environ_internal
+ libc.src.__support.CPP.string_view
+ libc.src.__support.libc_errno
+ libc.src.__support.threads.mutex
+ libc.src.stdlib.malloc
+ libc.src.string.memcpy
+ libc.src.string.strlen
+)
+
add_entrypoint_object(
strfromf
SRCS
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
new file mode 100644
index 0000000000000..fbb0b968074a6
--- /dev/null
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -0,0 +1,165 @@
+//===-- Implementation of internal environment utilities ------------------===//
+//
+// 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 "environ_internal.h"
+#include "config/app.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/macros/config.h"
+#include "src/string/memcpy.h"
+
+// We use extern "C" declarations for malloc/free/realloc instead of including
+// src/stdlib/malloc.h, src/stdlib/free.h, and src/stdlib/realloc.h. This allows
+// the implementation to work with different allocator implementations,
+// particularly in integration tests which provide a simple bump allocator. The
+// extern "C" linkage ensures we use whatever allocator is linked with the test
+// or application.
+extern "C" void *malloc(size_t);
+extern "C" void free(void *);
+extern "C" void *realloc(void *, size_t);
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+// Minimum initial capacity for the environment array when first allocated.
+// This avoids frequent reallocations for small environments.
+constexpr size_t MIN_ENVIRON_CAPACITY = 32;
+
+// Growth factor for environment array capacity when expanding.
+// When capacity is exceeded, new_capacity = old_capacity *
+// ENVIRON_GROWTH_FACTOR.
+constexpr size_t ENVIRON_GROWTH_FACTOR = 2;
+
+// Global state for environment management
+Mutex environ_mutex(false, false, false, false);
+char **environ_storage = nullptr;
+EnvStringOwnership *environ_ownership = nullptr;
+size_t environ_capacity = 0;
+size_t environ_size = 0;
+bool environ_is_ours = false;
+
+char **get_environ_array() {
+ if (environ_is_ours)
+ return environ_storage;
+ return reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
+}
+
+void init_environ() {
+ // Count entries in the startup environ
+ char **env_ptr = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
+ if (!env_ptr)
+ return;
+
+ size_t count = 0;
+ for (char **env = env_ptr; *env != nullptr; env++)
+ count++;
+
+ environ_size = count;
+}
+
+int find_env_var(cpp::string_view name) {
+ char **env_array = get_environ_array();
+ if (!env_array)
+ return -1;
+
+ for (size_t i = 0; i < environ_size; i++) {
+ cpp::string_view current(env_array[i]);
+ if (!current.starts_with(name))
+ continue;
+
+ // Check that name is followed by '='
+ if (current.size() > name.size() && current[name.size()] == '=')
+ return static_cast<int>(i);
+ }
+
+ return -1;
+}
+
+bool ensure_capacity(size_t needed) {
+ // IMPORTANT: This function assumes environ_mutex is already held by the
+ // caller. Do not add locking here as it would cause deadlock.
+
+ // If we're still using the startup environ, we need to copy it
+ if (!environ_is_ours) {
+ char **old_env = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
+
+ // Allocate new array with room to grow
+ size_t new_capacity = needed < MIN_ENVIRON_CAPACITY
+ ? MIN_ENVIRON_CAPACITY
+ : needed * ENVIRON_GROWTH_FACTOR;
+ char **new_storage =
+ reinterpret_cast<char **>(malloc(sizeof(char *) * (new_capacity + 1)));
+ if (!new_storage)
+ return false;
+
+ // Allocate ownership tracking array
+ EnvStringOwnership *new_ownership = reinterpret_cast<EnvStringOwnership *>(
+ malloc(sizeof(EnvStringOwnership) * (new_capacity + 1)));
+ if (!new_ownership) {
+ free(new_storage);
+ return false;
+ }
+
+ // Copy existing pointers (we don't own the strings yet, so just copy
+ // pointers)
+ if (old_env) {
+ for (size_t i = 0; i < environ_size; i++) {
+ new_storage[i] = old_env[i];
+ // Initialize ownership: startup strings are not owned by us
+ new_ownership[i] = EnvStringOwnership();
+ }
+ }
+ new_storage[environ_size] = nullptr;
+
+ environ_storage = new_storage;
+ environ_ownership = new_ownership;
+ environ_capacity = new_capacity;
+ environ_is_ours = true;
+
+ // Update app.env_ptr to point to our storage
+ LIBC_NAMESPACE::app.env_ptr =
+ reinterpret_cast<uintptr_t *>(environ_storage);
+
+ return true;
+ }
+
+ // We already own environ, check if we need to grow it
+ if (needed <= environ_capacity)
+ return true;
+
+ // Grow capacity by the growth factor
+ size_t new_capacity = needed * ENVIRON_GROWTH_FACTOR;
+
+ // Use realloc to grow the arrays
+ char **new_storage = reinterpret_cast<char **>(
+ realloc(environ_storage, sizeof(char *) * (new_capacity + 1)));
+ if (!new_storage)
+ return false;
+
+ EnvStringOwnership *new_ownership =
+ reinterpret_cast<EnvStringOwnership *>(realloc(
+ environ_ownership, sizeof(EnvStringOwnership) * (new_capacity + 1)));
+ if (!new_ownership) {
+ // If ownership realloc fails, we still have the old storage in new_storage
+ // which was successfully reallocated. We need to restore or handle this.
+ // For safety, we'll keep the successfully reallocated storage.
+ environ_storage = new_storage;
+ return false;
+ }
+
+ environ_storage = new_storage;
+ environ_ownership = new_ownership;
+ environ_capacity = new_capacity;
+
+ // Update app.env_ptr to point to our new storage
+ LIBC_NAMESPACE::app.env_ptr = reinterpret_cast<uintptr_t *>(environ_storage);
+
+ return true;
+}
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/environ_internal.h b/libc/src/stdlib/environ_internal.h
new file mode 100644
index 0000000000000..4d4d2b85bc779
--- /dev/null
+++ b/libc/src/stdlib/environ_internal.h
@@ -0,0 +1,82 @@
+//===-- Internal utilities for environment management ----------*- 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_STDLIB_ENVIRON_INTERNAL_H
+#define LLVM_LIBC_SRC_STDLIB_ENVIRON_INTERNAL_H
+
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/mutex.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+// Ownership information for environment strings.
+// We need to track ownership because environment strings come from three
+// sources:
+// 1. Startup environment (from program loader) - we don't own these
+// 2. putenv() calls where caller provides the string - we don't own these
+// 3. setenv() calls where we allocate the string - we DO own these
+// Only strings we allocated can be freed when replaced or removed.
+struct EnvStringOwnership {
+ bool allocated_by_us; // True if we malloc'd this string (must free).
+ // False for startup environ or putenv strings (don't
+ // free).
+
+ // Default: not owned by us (startup or putenv - don't free).
+ LIBC_INLINE EnvStringOwnership() : allocated_by_us(false) {}
+
+ // Returns true if this string can be safely freed.
+ LIBC_INLINE bool can_free() const { return allocated_by_us; }
+};
+
+// Global mutex protecting all environ modifications
+extern Mutex environ_mutex;
+
+// Our allocated environ array (nullptr if using startup environ)
+extern char **environ_storage;
+
+// Parallel array tracking ownership of each environ string
+// Same size/capacity as environ_storage
+extern EnvStringOwnership *environ_ownership;
+
+// Allocated capacity of environ_storage
+extern size_t environ_capacity;
+
+// Current number of variables in environ
+extern size_t environ_size;
+
+// True if we allocated environ_storage (and are responsible for freeing it)
+extern bool environ_is_ours;
+
+// Search for a variable by name in the current environ array.
+// Returns the index if found, or -1 if not found.
+// This function assumes the mutex is already held.
+int find_env_var(cpp::string_view name);
+
+// Ensure environ has capacity for at least `needed` entries (plus null
+// terminator). May allocate or reallocate environ_storage. Returns true on
+// success, false on allocation failure. This function assumes the mutex is
+// already held.
+bool ensure_capacity(size_t needed);
+
+// Get a pointer to the current environ array.
+// This may be app.env_ptr (startup environ) or environ_storage (our copy).
+char **get_environ_array();
+
+// Initialize environ management from the startup environment.
+// This must be called before any setenv/unsetenv operations.
+// This function is thread-safe and idempotent.
+void init_environ();
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDLIB_ENVIRON_INTERNAL_H
diff --git a/libc/src/stdlib/setenv.cpp b/libc/src/stdlib/setenv.cpp
new file mode 100644
index 0000000000000..156c94cc66cb7
--- /dev/null
+++ b/libc/src/stdlib/setenv.cpp
@@ -0,0 +1,141 @@
+//===-- Implementation of setenv ------------------------------------------===//
+//
+// 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/stdlib/setenv.h"
+#include "environ_internal.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/string/memcpy.h"
+#include "src/string/strlen.h"
+
+// We use extern "C" declarations for malloc/free instead of including
+// src/stdlib/malloc.h and src/stdlib/free.h. This allows the implementation
+// to work with different allocator implementations, particularly in integration
+// tests which provide a simple bump allocator. The extern "C" linkage ensures
+// we use whatever allocator is linked with the test or application.
+extern "C" void *malloc(size_t);
+extern "C" void free(void *);
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, setenv,
+ (const char *name, const char *value, int overwrite)) {
+ // Validate inputs
+ if (name == nullptr || value == nullptr) {
+ libc_errno = EINVAL;
+ return -1;
+ }
+
+ cpp::string_view name_view(name);
+ if (name_view.empty()) {
+ libc_errno = EINVAL;
+ return -1;
+ }
+
+ // POSIX: name cannot contain '='
+ if (name_view.find_first_of('=') != cpp::string_view::npos) {
+ libc_errno = EINVAL;
+ return -1;
+ }
+
+ // Lock mutex for thread safety
+ internal::environ_mutex.lock();
+
+ // Initialize environ if not already done
+ internal::init_environ();
+
+ // Search for existing variable
+ int index = internal::find_env_var(name_view);
+
+ if (index >= 0) {
+ // Variable exists
+ if (overwrite == 0) {
+ // Don't overwrite, just return success
+ internal::environ_mutex.unlock();
+ return 0;
+ }
+
+ // Need to replace the value
+ // Calculate size for "name=value" string
+ size_t name_len = LIBC_NAMESPACE::strlen(name);
+ size_t value_len = LIBC_NAMESPACE::strlen(value);
+ size_t total_len =
+ name_len + 1 + value_len + 1; // name + '=' + value + '\0'
+
+ char *new_string = reinterpret_cast<char *>(malloc(total_len));
+ if (!new_string) {
+ internal::environ_mutex.unlock();
+ libc_errno = ENOMEM;
+ return -1;
+ }
+
+ // Build "name=value" string
+ LIBC_NAMESPACE::memcpy(new_string, name, name_len);
+ new_string[name_len] = '=';
+ LIBC_NAMESPACE::memcpy(new_string + name_len + 1, value, value_len);
+ new_string[name_len + 1 + value_len] = '\0';
+
+ // Replace in environ array
+ char **env_array = internal::get_environ_array();
+
+ // Free old string if we allocated it
+ if (internal::environ_ownership[index].can_free()) {
+ free(env_array[index]);
+ }
+
+ env_array[index] = new_string;
+ // Mark this string as allocated by us
+ internal::environ_ownership[index].allocated_by_us = true;
+
+ internal::environ_mutex.unlock();
+ return 0;
+ }
+
+ // Variable doesn't exist, need to add it
+ // Ensure we have capacity for one more entry
+ if (!internal::ensure_capacity(internal::environ_size + 1)) {
+ internal::environ_mutex.unlock();
+ libc_errno = ENOMEM;
+ return -1;
+ }
+
+ // Calculate size for "name=value" string
+ size_t name_len = LIBC_NAMESPACE::strlen(name);
+ size_t value_len = LIBC_NAMESPACE::strlen(value);
+ size_t total_len = name_len + 1 + value_len + 1; // name + '=' + value + '\0'
+
+ char *new_string = reinterpret_cast<char *>(malloc(total_len));
+ if (!new_string) {
+ internal::environ_mutex.unlock();
+ libc_errno = ENOMEM;
+ return -1;
+ }
+
+ // Build "name=value" string
+ LIBC_NAMESPACE::memcpy(new_string, name, name_len);
+ new_string[name_len] = '=';
+ LIBC_NAMESPACE::memcpy(new_string + name_len + 1, value, value_len);
+ new_string[name_len + 1 + value_len] = '\0';
+
+ // Add to environ array
+ char **env_array = internal::get_environ_array();
+ env_array[internal::environ_size] = new_string;
+
+ // Mark this string as allocated by us
+ internal::environ_ownership[internal::environ_size].allocated_by_us = true;
+
+ internal::environ_size++;
+ env_array[internal::environ_size] = nullptr; // Maintain null terminator
+
+ internal::environ_mutex.unlock();
+ return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/setenv.h b/libc/src/stdlib/setenv.h
new file mode 100644
index 0000000000000..0a6ad78be92cd
--- /dev/null
+++ b/libc/src/stdlib/setenv.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for setenv ------------------------*- 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_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 1773d9fc9f0f5..c7d005b2e81db 100644
--- a/libc/test/integration/src/stdlib/CMakeLists.txt
+++ b/libc/test/integration/src/stdlib/CMakeLists.txt
@@ -16,3 +16,15 @@ add_integration_test(
FRANCE=Paris
GERMANY=Berlin
)
+
+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
+)
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..fbedadbf3a2e6
--- /dev/null
+++ b/libc/test/integration/src/stdlib/setenv_test.cpp
@@ -0,0 +1,190 @@
+//===-- Unittests for setenv ----------------------------------------------===//
+//
+// 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/stdlib/getenv.h"
+#include "src/stdlib/setenv.h"
+#include "src/stdlib/unsetenv.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);
+
+ // Verify it was set
+ char *value = LIBC_NAMESPACE::getenv("SETENV_TEST_VAR");
+ ASSERT_TRUE(value != nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "test_value"), 0);
+
+ // Clean up
+ LIBC_NAMESPACE::unsetenv("SETENV_TEST_VAR");
+ }
+
+ // Test: OverwriteExisting
+ {
+ // Set initial value
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("OVERWRITE_VAR", "original", 1), 0);
+ 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_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("OVERWRITE_VAR"),
+ "replaced"),
+ 0);
+
+ // Clean up
+ LIBC_NAMESPACE::unsetenv("OVERWRITE_VAR");
+ }
+
+ // Test: NoOverwriteFlag
+ {
+ // Set initial value
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("NO_OVERWRITE_VAR", "original", 1), 0);
+ 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_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_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"),
+ "changed"),
+ 0);
+
+ // Clean up
+ LIBC_NAMESPACE::unsetenv("NO_OVERWRITE_VAR");
+ }
+
+ // Test: NullName
+ {
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::setenv(nullptr, "value", 1), -1);
+ ASSERT_ERRNO_EQ(EINVAL);
+ }
+
+ // Test: NullValue
+ {
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("NULL_VALUE_VAR", nullptr, 1), -1);
+ ASSERT_ERRNO_EQ(EINVAL);
+ }
+
+ // 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
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("EMPTY_VALUE_VAR", "", 1), 0);
+
+ char *value = LIBC_NAMESPACE::getenv("EMPTY_VALUE_VAR");
+ ASSERT_TRUE(value != nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, ""), 0);
+
+ // Clean up
+ LIBC_NAMESPACE::unsetenv("EMPTY_VALUE_VAR");
+ }
+
+ // Test: MultipleVariables
+ {
+ // Set multiple different variables
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("VAR1", "value1", 1), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("VAR2", "value2", 1), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("VAR3", "value3", 1), 0);
+
+ // 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);
+
+ // Clean up
+ LIBC_NAMESPACE::unsetenv("VAR1");
+ LIBC_NAMESPACE::unsetenv("VAR2");
+ LIBC_NAMESPACE::unsetenv("VAR3");
+ }
+
+ // 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_EQ(
+ LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv(long_name), long_value),
+ 0);
+
+ // Clean up
+ LIBC_NAMESPACE::unsetenv(long_name);
+ }
+
+ // Test: SpecialCharacters
+ {
+ // Test with special characters in value (but not in name)
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("SPECIAL_CHARS", "!@#$%^&*()", 1), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("SPECIAL_CHARS"),
+ "!@#$%^&*()"),
+ 0);
+
+ // Clean up
+ LIBC_NAMESPACE::unsetenv("SPECIAL_CHARS");
+ }
+
+ // Test: ReplaceMultipleTimes
+ {
+ // Replace the same variable multiple times
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value1", 1), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
+ "value1"),
+ 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value2", 1), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
+ "value2"),
+ 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value3", 1), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
+ "value3"),
+ 0);
+
+ // Clean up
+ LIBC_NAMESPACE::unsetenv("MULTI_REPLACE");
+ }
+
+ return 0;
+}
>From 58f1f202e47006432a04d88acf66d4a75f56d359 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Sun, 12 Oct 2025 09:46:57 +0000
Subject: [PATCH 2/7] Remove unsetenv cleanup calls from setenv tests, to be
uncommented after unsetenv implementation (coming in subsequent commit)
---
.../integration/src/stdlib/setenv_test.cpp | 37 +++++++++----------
1 file changed, 18 insertions(+), 19 deletions(-)
diff --git a/libc/test/integration/src/stdlib/setenv_test.cpp b/libc/test/integration/src/stdlib/setenv_test.cpp
index fbedadbf3a2e6..afa8825fe9cff 100644
--- a/libc/test/integration/src/stdlib/setenv_test.cpp
+++ b/libc/test/integration/src/stdlib/setenv_test.cpp
@@ -8,7 +8,6 @@
#include "src/stdlib/getenv.h"
#include "src/stdlib/setenv.h"
-#include "src/stdlib/unsetenv.h"
#include "src/string/strcmp.h"
#include "test/IntegrationTest/test.h"
@@ -27,8 +26,8 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
ASSERT_TRUE(value != nullptr);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "test_value"), 0);
- // Clean up
- LIBC_NAMESPACE::unsetenv("SETENV_TEST_VAR");
+ // Uncomment after unsetenv is committed
+ // LIBC_NAMESPACE::unsetenv("SETENV_TEST_VAR");
}
// Test: OverwriteExisting
@@ -45,8 +44,8 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
"replaced"),
0);
- // Clean up
- LIBC_NAMESPACE::unsetenv("OVERWRITE_VAR");
+ // Uncomment after unsetenv is committed
+ // LIBC_NAMESPACE::unsetenv("OVERWRITE_VAR");
}
// Test: NoOverwriteFlag
@@ -69,8 +68,8 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
"changed"),
0);
- // Clean up
- LIBC_NAMESPACE::unsetenv("NO_OVERWRITE_VAR");
+ // Uncomment after unsetenv is committed
+ // LIBC_NAMESPACE::unsetenv("NO_OVERWRITE_VAR");
}
// Test: NullName
@@ -110,8 +109,8 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
ASSERT_TRUE(value != nullptr);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, ""), 0);
- // Clean up
- LIBC_NAMESPACE::unsetenv("EMPTY_VALUE_VAR");
+ // Uncomment after unsetenv is committed
+ // LIBC_NAMESPACE::unsetenv("EMPTY_VALUE_VAR");
}
// Test: MultipleVariables
@@ -129,10 +128,10 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("VAR3"), "value3"),
0);
- // Clean up
- LIBC_NAMESPACE::unsetenv("VAR1");
- LIBC_NAMESPACE::unsetenv("VAR2");
- LIBC_NAMESPACE::unsetenv("VAR3");
+ // Uncomment after unsetenv is committed
+ // LIBC_NAMESPACE::unsetenv("VAR1");
+ // LIBC_NAMESPACE::unsetenv("VAR2");
+ // LIBC_NAMESPACE::unsetenv("VAR3");
}
// Test: LongValues
@@ -148,8 +147,8 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv(long_name), long_value),
0);
- // Clean up
- LIBC_NAMESPACE::unsetenv(long_name);
+ // Uncomment after unsetenv is committed
+ // LIBC_NAMESPACE::unsetenv(long_name);
}
// Test: SpecialCharacters
@@ -160,8 +159,8 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
"!@#$%^&*()"),
0);
- // Clean up
- LIBC_NAMESPACE::unsetenv("SPECIAL_CHARS");
+ // Uncomment after unsetenv is committed
+ // LIBC_NAMESPACE::unsetenv("SPECIAL_CHARS");
}
// Test: ReplaceMultipleTimes
@@ -182,8 +181,8 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
"value3"),
0);
- // Clean up
- LIBC_NAMESPACE::unsetenv("MULTI_REPLACE");
+ // Uncomment after unsetenv is committed
+ // LIBC_NAMESPACE::unsetenv("MULTI_REPLACE");
}
return 0;
>From 8c3d0bc9196ba5c1e5d9300b58c66d6e1e29ed70 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Sun, 12 Oct 2025 10:12:30 +0000
Subject: [PATCH 3/7] Wrap the files in if(LLVM_LIBC_FULL_BUILD) so that they
won't show up on Windows and Mac overlay builds.
---
libc/src/stdlib/CMakeLists.txt | 63 ++++++++++---------
.../integration/src/stdlib/CMakeLists.txt | 25 ++++----
2 files changed, 47 insertions(+), 41 deletions(-)
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 409a4ccd0acdf..d7f8f9a02bb92 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -65,37 +65,40 @@ add_entrypoint_object(
libc.config.app_h
)
-add_object_library(
- environ_internal
- SRCS
- environ_internal.cpp
- HDRS
- environ_internal.h
- DEPENDS
- libc.config.app_h
- libc.hdr.types.size_t
- libc.src.__support.CPP.string_view
- libc.src.__support.threads.mutex
- libc.src.stdlib.free
- libc.src.stdlib.malloc
- libc.src.string.memcpy
-)
+# Environment variable functions only make sense in full build mode.
+if(LLVM_LIBC_FULL_BUILD)
+ add_object_library(
+ environ_internal
+ SRCS
+ environ_internal.cpp
+ HDRS
+ environ_internal.h
+ DEPENDS
+ libc.config.app_h
+ libc.hdr.types.size_t
+ libc.src.__support.CPP.string_view
+ libc.src.__support.threads.mutex
+ libc.src.stdlib.free
+ libc.src.stdlib.malloc
+ libc.src.string.memcpy
+ )
-add_entrypoint_object(
- setenv
- SRCS
- setenv.cpp
- HDRS
- setenv.h
- DEPENDS
- .environ_internal
- libc.src.__support.CPP.string_view
- libc.src.__support.libc_errno
- libc.src.__support.threads.mutex
- libc.src.stdlib.malloc
- libc.src.string.memcpy
- libc.src.string.strlen
-)
+ add_entrypoint_object(
+ setenv
+ SRCS
+ setenv.cpp
+ HDRS
+ setenv.h
+ DEPENDS
+ .environ_internal
+ libc.src.__support.CPP.string_view
+ libc.src.__support.libc_errno
+ libc.src.__support.threads.mutex
+ libc.src.stdlib.malloc
+ libc.src.string.memcpy
+ libc.src.string.strlen
+ )
+endif()
add_entrypoint_object(
strfromf
diff --git a/libc/test/integration/src/stdlib/CMakeLists.txt b/libc/test/integration/src/stdlib/CMakeLists.txt
index c7d005b2e81db..d2abf848c573b 100644
--- a/libc/test/integration/src/stdlib/CMakeLists.txt
+++ b/libc/test/integration/src/stdlib/CMakeLists.txt
@@ -17,14 +17,17 @@ add_integration_test(
GERMANY=Berlin
)
-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
-)
+# Environment variable functions only make sense in full build mode.
+if(LLVM_LIBC_FULL_BUILD)
+ 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
+ )
+endif()
\ No newline at end of file
>From 8cb2be66c30013a74b2780f35ed831f9dfb84812 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Thu, 13 Nov 2025 14:54:37 +0000
Subject: [PATCH 4/7] Update with internal functions rather than external
interfaces.
---
libc/src/stdlib/CMakeLists.txt | 64 ++++++++++++++--------------
libc/src/stdlib/environ_internal.cpp | 14 ++----
libc/src/stdlib/setenv.cpp | 30 ++++++-------
3 files changed, 46 insertions(+), 62 deletions(-)
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index d7f8f9a02bb92..1c2642e5d2a83 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -65,40 +65,38 @@ add_entrypoint_object(
libc.config.app_h
)
-# Environment variable functions only make sense in full build mode.
-if(LLVM_LIBC_FULL_BUILD)
- add_object_library(
- environ_internal
- SRCS
- environ_internal.cpp
- HDRS
- environ_internal.h
- DEPENDS
- libc.config.app_h
- libc.hdr.types.size_t
- libc.src.__support.CPP.string_view
- libc.src.__support.threads.mutex
- libc.src.stdlib.free
- libc.src.stdlib.malloc
- libc.src.string.memcpy
- )
+add_object_library(
+ environ_internal
+ SRCS
+ environ_internal.cpp
+ HDRS
+ environ_internal.h
+ DEPENDS
+ libc.config.app_h
+ libc.hdr.types.size_t
+ libc.src.__support.CPP.string_view
+ libc.src.__support.threads.mutex
+ libc.hdr.func.free
+ libc.hdr.func.malloc
+ libc.hdr.func.realloc
+)
- add_entrypoint_object(
- setenv
- SRCS
- setenv.cpp
- HDRS
- setenv.h
- DEPENDS
- .environ_internal
- libc.src.__support.CPP.string_view
- libc.src.__support.libc_errno
- libc.src.__support.threads.mutex
- libc.src.stdlib.malloc
- libc.src.string.memcpy
- libc.src.string.strlen
- )
-endif()
+add_entrypoint_object(
+ setenv
+ SRCS
+ setenv.cpp
+ HDRS
+ setenv.h
+ DEPENDS
+ .environ_internal
+ libc.src.__support.CPP.string_view
+ libc.src.__support.libc_errno
+ libc.src.__support.threads.mutex
+ libc.hdr.func.malloc
+ libc.hdr.func.free
+ libc.src.string.memory_utils.inline_memcpy
+ libc.src.string.string_utils
+)
add_entrypoint_object(
strfromf
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index fbb0b968074a6..bafd0c1169a48 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -8,19 +8,11 @@
#include "environ_internal.h"
#include "config/app.h"
+#include "hdr/func/free.h"
+#include "hdr/func/malloc.h"
+#include "hdr/func/realloc.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/macros/config.h"
-#include "src/string/memcpy.h"
-
-// We use extern "C" declarations for malloc/free/realloc instead of including
-// src/stdlib/malloc.h, src/stdlib/free.h, and src/stdlib/realloc.h. This allows
-// the implementation to work with different allocator implementations,
-// particularly in integration tests which provide a simple bump allocator. The
-// extern "C" linkage ensures we use whatever allocator is linked with the test
-// or application.
-extern "C" void *malloc(size_t);
-extern "C" void free(void *);
-extern "C" void *realloc(void *, size_t);
namespace LIBC_NAMESPACE_DECL {
namespace internal {
diff --git a/libc/src/stdlib/setenv.cpp b/libc/src/stdlib/setenv.cpp
index 156c94cc66cb7..2247349330e1a 100644
--- a/libc/src/stdlib/setenv.cpp
+++ b/libc/src/stdlib/setenv.cpp
@@ -8,20 +8,14 @@
#include "src/stdlib/setenv.h"
#include "environ_internal.h"
+#include "hdr/func/free.h"
+#include "hdr/func/malloc.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/string/memcpy.h"
-#include "src/string/strlen.h"
-
-// We use extern "C" declarations for malloc/free instead of including
-// src/stdlib/malloc.h and src/stdlib/free.h. This allows the implementation
-// to work with different allocator implementations, particularly in integration
-// tests which provide a simple bump allocator. The extern "C" linkage ensures
-// we use whatever allocator is linked with the test or application.
-extern "C" void *malloc(size_t);
-extern "C" void free(void *);
+#include "src/string/memory_utils/inline_memcpy.h"
+#include "src/string/string_utils.h"
namespace LIBC_NAMESPACE_DECL {
@@ -64,8 +58,8 @@ LLVM_LIBC_FUNCTION(int, setenv,
// Need to replace the value
// Calculate size for "name=value" string
- size_t name_len = LIBC_NAMESPACE::strlen(name);
- size_t value_len = LIBC_NAMESPACE::strlen(value);
+ size_t name_len = LIBC_NAMESPACE::internal::string_length(name);
+ size_t value_len = LIBC_NAMESPACE::internal::string_length(value);
size_t total_len =
name_len + 1 + value_len + 1; // name + '=' + value + '\0'
@@ -77,9 +71,9 @@ LLVM_LIBC_FUNCTION(int, setenv,
}
// Build "name=value" string
- LIBC_NAMESPACE::memcpy(new_string, name, name_len);
+ LIBC_NAMESPACE::inline_memcpy(new_string, name, name_len);
new_string[name_len] = '=';
- LIBC_NAMESPACE::memcpy(new_string + name_len + 1, value, value_len);
+ LIBC_NAMESPACE::inline_memcpy(new_string + name_len + 1, value, value_len);
new_string[name_len + 1 + value_len] = '\0';
// Replace in environ array
@@ -107,8 +101,8 @@ LLVM_LIBC_FUNCTION(int, setenv,
}
// Calculate size for "name=value" string
- size_t name_len = LIBC_NAMESPACE::strlen(name);
- size_t value_len = LIBC_NAMESPACE::strlen(value);
+ size_t name_len = LIBC_NAMESPACE::internal::string_length(name);
+ size_t value_len = LIBC_NAMESPACE::internal::string_length(value);
size_t total_len = name_len + 1 + value_len + 1; // name + '=' + value + '\0'
char *new_string = reinterpret_cast<char *>(malloc(total_len));
@@ -119,9 +113,9 @@ LLVM_LIBC_FUNCTION(int, setenv,
}
// Build "name=value" string
- LIBC_NAMESPACE::memcpy(new_string, name, name_len);
+ LIBC_NAMESPACE::inline_memcpy(new_string, name, name_len);
new_string[name_len] = '=';
- LIBC_NAMESPACE::memcpy(new_string + name_len + 1, value, value_len);
+ LIBC_NAMESPACE::inline_memcpy(new_string + name_len + 1, value, value_len);
new_string[name_len + 1 + value_len] = '\0';
// Add to environ array
>From d46224a64505799d46c11e23152160eb15636c2a Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 14 Nov 2025 10:42:27 +0000
Subject: [PATCH 5/7] [libc][stdlib] Refactor environment management to
singleton pattern without locking
This commit refactors the environment management infrastructure for
setenv() from a global variable-based approach with mutex locking to
a singleton EnvironmentManager class without thread synchronization.
Key changes:
- Replace global variables (environ_storage, environ_ownership, etc.)
with encapsulated state in EnvironmentManager singleton class
- Remove all mutex-based locking (environ_mutex.lock/unlock calls)
- Convert free functions (find_env_var, ensure_capacity, etc.) to
EnvironmentManager member methods
- Add LLVM_LIBC_FULL_BUILD guard around setenv-related CMake targets
to prevent build issues in non-full build configurations
The locking removal is intentional - thread safety will be added back
in a future change with a more comprehensive approach.
---
libc/src/stdlib/CMakeLists.txt | 64 +++++++-------
libc/src/stdlib/environ_internal.cpp | 62 ++++++--------
libc/src/stdlib/environ_internal.h | 84 ++++++++++++-------
libc/src/stdlib/setenv.cpp | 34 ++++----
.../integration/src/stdlib/CMakeLists.txt | 2 +-
5 files changed, 128 insertions(+), 118 deletions(-)
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 1c2642e5d2a83..41f709fd0f51d 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -65,38 +65,40 @@ add_entrypoint_object(
libc.config.app_h
)
-add_object_library(
- environ_internal
- SRCS
- environ_internal.cpp
- HDRS
- environ_internal.h
- DEPENDS
- libc.config.app_h
- libc.hdr.types.size_t
- libc.src.__support.CPP.string_view
- libc.src.__support.threads.mutex
- libc.hdr.func.free
- libc.hdr.func.malloc
- libc.hdr.func.realloc
-)
+if(LLVM_LIBC_FULL_BUILD)
+ add_object_library(
+ environ_internal
+ SRCS
+ environ_internal.cpp
+ HDRS
+ environ_internal.h
+ DEPENDS
+ libc.config.app_h
+ libc.hdr.types.size_t
+ libc.src.__support.CPP.string_view
+ libc.src.__support.threads.mutex
+ libc.hdr.func.free
+ libc.hdr.func.malloc
+ libc.hdr.func.realloc
+ )
-add_entrypoint_object(
- setenv
- SRCS
- setenv.cpp
- HDRS
- setenv.h
- DEPENDS
- .environ_internal
- libc.src.__support.CPP.string_view
- libc.src.__support.libc_errno
- libc.src.__support.threads.mutex
- libc.hdr.func.malloc
- libc.hdr.func.free
- libc.src.string.memory_utils.inline_memcpy
- libc.src.string.string_utils
-)
+ add_entrypoint_object(
+ setenv
+ SRCS
+ setenv.cpp
+ HDRS
+ setenv.h
+ DEPENDS
+ .environ_internal
+ libc.src.__support.CPP.string_view
+ libc.src.__support.libc_errno
+ libc.src.__support.threads.mutex
+ libc.hdr.func.malloc
+ libc.hdr.func.free
+ libc.src.string.memory_utils.inline_memcpy
+ libc.src.string.string_utils
+ )
+endif()
add_entrypoint_object(
strfromf
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index bafd0c1169a48..08319e2a73b0f 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -26,21 +26,13 @@ constexpr size_t MIN_ENVIRON_CAPACITY = 32;
// ENVIRON_GROWTH_FACTOR.
constexpr size_t ENVIRON_GROWTH_FACTOR = 2;
-// Global state for environment management
-Mutex environ_mutex(false, false, false, false);
-char **environ_storage = nullptr;
-EnvStringOwnership *environ_ownership = nullptr;
-size_t environ_capacity = 0;
-size_t environ_size = 0;
-bool environ_is_ours = false;
-
-char **get_environ_array() {
- if (environ_is_ours)
- return environ_storage;
+char **EnvironmentManager::get_array() {
+ if (is_ours)
+ return storage;
return reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
}
-void init_environ() {
+void EnvironmentManager::init() {
// Count entries in the startup environ
char **env_ptr = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
if (!env_ptr)
@@ -50,15 +42,15 @@ void init_environ() {
for (char **env = env_ptr; *env != nullptr; env++)
count++;
- environ_size = count;
+ size = count;
}
-int find_env_var(cpp::string_view name) {
- char **env_array = get_environ_array();
+int EnvironmentManager::find_var(cpp::string_view name) {
+ char **env_array = get_array();
if (!env_array)
return -1;
- for (size_t i = 0; i < environ_size; i++) {
+ for (size_t i = 0; i < size; i++) {
cpp::string_view current(env_array[i]);
if (!current.starts_with(name))
continue;
@@ -71,12 +63,9 @@ int find_env_var(cpp::string_view name) {
return -1;
}
-bool ensure_capacity(size_t needed) {
- // IMPORTANT: This function assumes environ_mutex is already held by the
- // caller. Do not add locking here as it would cause deadlock.
-
+bool EnvironmentManager::ensure_capacity(size_t needed) {
// If we're still using the startup environ, we need to copy it
- if (!environ_is_ours) {
+ if (!is_ours) {
char **old_env = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
// Allocate new array with room to grow
@@ -99,28 +88,27 @@ bool ensure_capacity(size_t needed) {
// Copy existing pointers (we don't own the strings yet, so just copy
// pointers)
if (old_env) {
- for (size_t i = 0; i < environ_size; i++) {
+ for (size_t i = 0; i < size; i++) {
new_storage[i] = old_env[i];
// Initialize ownership: startup strings are not owned by us
new_ownership[i] = EnvStringOwnership();
}
}
- new_storage[environ_size] = nullptr;
+ new_storage[size] = nullptr;
- environ_storage = new_storage;
- environ_ownership = new_ownership;
- environ_capacity = new_capacity;
- environ_is_ours = true;
+ storage = new_storage;
+ ownership = new_ownership;
+ capacity = new_capacity;
+ is_ours = true;
// Update app.env_ptr to point to our storage
- LIBC_NAMESPACE::app.env_ptr =
- reinterpret_cast<uintptr_t *>(environ_storage);
+ LIBC_NAMESPACE::app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
return true;
}
// We already own environ, check if we need to grow it
- if (needed <= environ_capacity)
+ if (needed <= capacity)
return true;
// Grow capacity by the growth factor
@@ -128,27 +116,27 @@ bool ensure_capacity(size_t needed) {
// Use realloc to grow the arrays
char **new_storage = reinterpret_cast<char **>(
- realloc(environ_storage, sizeof(char *) * (new_capacity + 1)));
+ realloc(storage, sizeof(char *) * (new_capacity + 1)));
if (!new_storage)
return false;
EnvStringOwnership *new_ownership =
reinterpret_cast<EnvStringOwnership *>(realloc(
- environ_ownership, sizeof(EnvStringOwnership) * (new_capacity + 1)));
+ ownership, sizeof(EnvStringOwnership) * (new_capacity + 1)));
if (!new_ownership) {
// If ownership realloc fails, we still have the old storage in new_storage
// which was successfully reallocated. We need to restore or handle this.
// For safety, we'll keep the successfully reallocated storage.
- environ_storage = new_storage;
+ storage = new_storage;
return false;
}
- environ_storage = new_storage;
- environ_ownership = new_ownership;
- environ_capacity = new_capacity;
+ storage = new_storage;
+ ownership = new_ownership;
+ capacity = new_capacity;
// Update app.env_ptr to point to our new storage
- LIBC_NAMESPACE::app.env_ptr = reinterpret_cast<uintptr_t *>(environ_storage);
+ LIBC_NAMESPACE::app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
return true;
}
diff --git a/libc/src/stdlib/environ_internal.h b/libc/src/stdlib/environ_internal.h
index 4d4d2b85bc779..b638860b9e9b3 100644
--- a/libc/src/stdlib/environ_internal.h
+++ b/libc/src/stdlib/environ_internal.h
@@ -13,7 +13,6 @@
#include "src/__support/CPP/string_view.h"
#include "src/__support/macros/attributes.h"
#include "src/__support/macros/config.h"
-#include "src/__support/threads/mutex.h"
namespace LIBC_NAMESPACE_DECL {
namespace internal {
@@ -37,44 +36,69 @@ struct EnvStringOwnership {
LIBC_INLINE bool can_free() const { return allocated_by_us; }
};
-// Global mutex protecting all environ modifications
-extern Mutex environ_mutex;
+// Centralized manager for environment variable operations.
+// This class encapsulates all state and operations related to environment
+// management, including memory management and tracking of string ownership.
+class EnvironmentManager {
-// Our allocated environ array (nullptr if using startup environ)
-extern char **environ_storage;
+ // Our allocated environ array (nullptr if using startup environ)
+ char **storage = nullptr;
-// Parallel array tracking ownership of each environ string
-// Same size/capacity as environ_storage
-extern EnvStringOwnership *environ_ownership;
+ // Parallel array tracking ownership of each environ string
+ // Same size/capacity as storage
+ EnvStringOwnership *ownership = nullptr;
-// Allocated capacity of environ_storage
-extern size_t environ_capacity;
+ // Allocated capacity of storage
+ size_t capacity = 0;
-// Current number of variables in environ
-extern size_t environ_size;
+ // Current number of variables in environ
+ size_t size = 0;
-// True if we allocated environ_storage (and are responsible for freeing it)
-extern bool environ_is_ours;
+ // True if we allocated storage (and are responsible for freeing it)
+ bool is_ours = false;
-// Search for a variable by name in the current environ array.
-// Returns the index if found, or -1 if not found.
-// This function assumes the mutex is already held.
-int find_env_var(cpp::string_view name);
+ LIBC_INLINE EnvironmentManager() = default;
+ LIBC_INLINE ~EnvironmentManager() = default;
-// Ensure environ has capacity for at least `needed` entries (plus null
-// terminator). May allocate or reallocate environ_storage. Returns true on
-// success, false on allocation failure. This function assumes the mutex is
-// already held.
-bool ensure_capacity(size_t needed);
+public:
+ // Get the singleton instance of the environment manager
+ LIBC_INLINE static EnvironmentManager &instance() {
+ static EnvironmentManager mgr;
+ return mgr;
+ }
-// Get a pointer to the current environ array.
-// This may be app.env_ptr (startup environ) or environ_storage (our copy).
-char **get_environ_array();
+ // Delete copy and move operations to enforce singleton pattern
+ EnvironmentManager(const EnvironmentManager &) = delete;
+ EnvironmentManager &operator=(const EnvironmentManager &) = delete;
+ EnvironmentManager(EnvironmentManager &&) = delete;
+ EnvironmentManager &operator=(EnvironmentManager &&) = delete;
-// Initialize environ management from the startup environment.
-// This must be called before any setenv/unsetenv operations.
-// This function is thread-safe and idempotent.
-void init_environ();
+ // Get the current size of the environment
+ LIBC_INLINE size_t get_size() const { return size; }
+
+ // Get the ownership array for direct access
+ LIBC_INLINE EnvStringOwnership *get_ownership() { return ownership; }
+
+ // Increment the size counter (used after adding a new variable)
+ LIBC_INLINE void increment_size() { size++; }
+
+ // Search for a variable by name in the current environ array.
+ // Returns the index if found, or -1 if not found.
+ int find_var(cpp::string_view name);
+
+ // Ensure environ has capacity for at least `needed` entries (plus null
+ // terminator). May allocate or reallocate storage. Returns true on
+ // success, false on allocation failure.
+ bool ensure_capacity(size_t needed);
+
+ // Get a pointer to the current environ array.
+ // This may be app.env_ptr (startup environ) or storage (our copy).
+ char **get_array();
+
+ // Initialize environ management from the startup environment.
+ // This must be called before any setenv/unsetenv operations.
+ void init();
+};
} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/setenv.cpp b/libc/src/stdlib/setenv.cpp
index 2247349330e1a..7da72191e6ed7 100644
--- a/libc/src/stdlib/setenv.cpp
+++ b/libc/src/stdlib/setenv.cpp
@@ -39,20 +39,19 @@ LLVM_LIBC_FUNCTION(int, setenv,
return -1;
}
- // Lock mutex for thread safety
- internal::environ_mutex.lock();
+ // Get the singleton environment manager
+ auto &env_mgr = internal::EnvironmentManager::instance();
// Initialize environ if not already done
- internal::init_environ();
+ env_mgr.init();
// Search for existing variable
- int index = internal::find_env_var(name_view);
+ int index = env_mgr.find_var(name_view);
if (index >= 0) {
// Variable exists
if (overwrite == 0) {
// Don't overwrite, just return success
- internal::environ_mutex.unlock();
return 0;
}
@@ -65,7 +64,6 @@ LLVM_LIBC_FUNCTION(int, setenv,
char *new_string = reinterpret_cast<char *>(malloc(total_len));
if (!new_string) {
- internal::environ_mutex.unlock();
libc_errno = ENOMEM;
return -1;
}
@@ -77,25 +75,23 @@ LLVM_LIBC_FUNCTION(int, setenv,
new_string[name_len + 1 + value_len] = '\0';
// Replace in environ array
- char **env_array = internal::get_environ_array();
+ char **env_array = env_mgr.get_array();
// Free old string if we allocated it
- if (internal::environ_ownership[index].can_free()) {
+ if (env_mgr.get_ownership()[index].can_free()) {
free(env_array[index]);
}
env_array[index] = new_string;
// Mark this string as allocated by us
- internal::environ_ownership[index].allocated_by_us = true;
+ env_mgr.get_ownership()[index].allocated_by_us = true;
- internal::environ_mutex.unlock();
return 0;
}
// Variable doesn't exist, need to add it
// Ensure we have capacity for one more entry
- if (!internal::ensure_capacity(internal::environ_size + 1)) {
- internal::environ_mutex.unlock();
+ if (!env_mgr.ensure_capacity(env_mgr.get_size() + 1)) {
libc_errno = ENOMEM;
return -1;
}
@@ -107,7 +103,6 @@ LLVM_LIBC_FUNCTION(int, setenv,
char *new_string = reinterpret_cast<char *>(malloc(total_len));
if (!new_string) {
- internal::environ_mutex.unlock();
libc_errno = ENOMEM;
return -1;
}
@@ -119,16 +114,17 @@ LLVM_LIBC_FUNCTION(int, setenv,
new_string[name_len + 1 + value_len] = '\0';
// Add to environ array
- char **env_array = internal::get_environ_array();
- env_array[internal::environ_size] = new_string;
+ char **env_array = env_mgr.get_array();
+ size_t current_size = env_mgr.get_size();
+ env_array[current_size] = new_string;
// Mark this string as allocated by us
- internal::environ_ownership[internal::environ_size].allocated_by_us = true;
+ env_mgr.get_ownership()[current_size].allocated_by_us = true;
- internal::environ_size++;
- env_array[internal::environ_size] = nullptr; // Maintain null terminator
+ // Increment size and maintain null terminator
+ env_mgr.increment_size();
+ env_array[current_size + 1] = nullptr; // Maintain null terminator
- internal::environ_mutex.unlock();
return 0;
}
diff --git a/libc/test/integration/src/stdlib/CMakeLists.txt b/libc/test/integration/src/stdlib/CMakeLists.txt
index d2abf848c573b..3733d54d4991a 100644
--- a/libc/test/integration/src/stdlib/CMakeLists.txt
+++ b/libc/test/integration/src/stdlib/CMakeLists.txt
@@ -30,4 +30,4 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.setenv
libc.src.string.strcmp
)
-endif()
\ No newline at end of file
+endif()
>From 0a61e3645efdc1c74ad88fc7cba70d4d69de8371 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 19 Dec 2025 11:15:32 +0000
Subject: [PATCH 6/7] [libc][stdlib] Move setenv() to linux folder as POSIX
function
Move setenv() implementation from libc/src/stdlib/ to
libc/src/stdlib/linux/ since it's a POSIX-specific function
rather than a generic stdlib function.
Changes:
- Moved setenv.cpp to linux subfolder
- Updated linux/CMakeLists.txt to include setenv entrypoint
- Removed setenv from main stdlib CMakeLists.txt
- Updated baremetal config to reference linux implementation
---
libc/src/stdlib/CMakeLists.txt | 17 -----------------
libc/src/stdlib/linux/CMakeLists.txt | 16 ++++++++++++++++
libc/src/stdlib/{ => linux}/setenv.cpp | 2 +-
libc/test/integration/src/stdlib/CMakeLists.txt | 2 +-
4 files changed, 18 insertions(+), 19 deletions(-)
rename libc/src/stdlib/{ => linux}/setenv.cpp (99%)
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 41f709fd0f51d..b07e29a8dc383 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -81,23 +81,6 @@ if(LLVM_LIBC_FULL_BUILD)
libc.hdr.func.malloc
libc.hdr.func.realloc
)
-
- add_entrypoint_object(
- setenv
- SRCS
- setenv.cpp
- HDRS
- setenv.h
- DEPENDS
- .environ_internal
- libc.src.__support.CPP.string_view
- libc.src.__support.libc_errno
- libc.src.__support.threads.mutex
- libc.hdr.func.malloc
- libc.hdr.func.free
- libc.src.string.memory_utils.inline_memcpy
- libc.src.string.string_utils
- )
endif()
add_entrypoint_object(
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 1d3c00a5e0ddb..feb0265444e38 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -9,3 +9,19 @@ add_entrypoint_object(
libc.src.signal.raise
libc.src.stdlib._Exit
)
+
+add_entrypoint_object(
+ setenv
+ SRCS
+ setenv.cpp
+ HDRS
+ ../setenv.h
+ DEPENDS
+ libc.src.stdlib.environ_internal
+ libc.src.__support.CPP.string_view
+ libc.src.__support.libc_errno
+ libc.hdr.func.malloc
+ libc.hdr.func.free
+ libc.src.string.memory_utils.inline_memcpy
+ libc.src.string.string_utils
+)
diff --git a/libc/src/stdlib/setenv.cpp b/libc/src/stdlib/linux/setenv.cpp
similarity index 99%
rename from libc/src/stdlib/setenv.cpp
rename to libc/src/stdlib/linux/setenv.cpp
index 7da72191e6ed7..7ce09f6e9135a 100644
--- a/libc/src/stdlib/setenv.cpp
+++ b/libc/src/stdlib/linux/setenv.cpp
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
#include "src/stdlib/setenv.h"
-#include "environ_internal.h"
+#include "../environ_internal.h"
#include "hdr/func/free.h"
#include "hdr/func/malloc.h"
#include "src/__support/CPP/string_view.h"
diff --git a/libc/test/integration/src/stdlib/CMakeLists.txt b/libc/test/integration/src/stdlib/CMakeLists.txt
index 3733d54d4991a..7bbabe4212796 100644
--- a/libc/test/integration/src/stdlib/CMakeLists.txt
+++ b/libc/test/integration/src/stdlib/CMakeLists.txt
@@ -27,7 +27,7 @@ if(LLVM_LIBC_FULL_BUILD)
setenv_test.cpp
DEPENDS
libc.src.stdlib.getenv
- libc.src.stdlib.setenv
+ libc.src.stdlib.linux.setenv
libc.src.string.strcmp
)
endif()
>From 2fea75f0ae9e2e406a42f2114affd611bcdd6edf Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 19 Dec 2025 11:36:24 +0000
Subject: [PATCH 7/7] Run clang-format
---
libc/src/stdlib/environ_internal.cpp | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index 08319e2a73b0f..fe1ca15f78597 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -120,9 +120,8 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
if (!new_storage)
return false;
- EnvStringOwnership *new_ownership =
- reinterpret_cast<EnvStringOwnership *>(realloc(
- ownership, sizeof(EnvStringOwnership) * (new_capacity + 1)));
+ EnvStringOwnership *new_ownership = reinterpret_cast<EnvStringOwnership *>(
+ realloc(ownership, sizeof(EnvStringOwnership) * (new_capacity + 1)));
if (!new_ownership) {
// If ownership realloc fails, we still have the old storage in new_storage
// which was successfully reallocated. We need to restore or handle this.
More information about the libc-commits
mailing list