[libc-commits] [libc] [libc][stdlib] Implement setenv() with environment management infrastructure (PR #163018)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Thu Jan 29 07:08:25 PST 2026
https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/163018
>From e32d2db868a6655edf9cf5cddd36af1ccd18f909 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 01/16] [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 8eb28ea453b7c958d44c70dcfb0cbe918a4bb0f8 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 02/16] 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 75dd5bb6cd63842c149277c0622caa8d49161073 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 03/16] 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 45fdd35e2fd288df77f3825246a10635e6dfd7ca 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 04/16] 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 ff86a38175682814786d492504b9027c0b0abfba 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 05/16] [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 c0533b70faf1bdb7050b3727e0e9ad176015c49d 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 06/16] [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 e2a3a1b250a6cec5bca0c7f51452c9571072bd94 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 07/16] 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.
>From 4872fbd41416402f93a0467401d84f55565d9f5a Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 19 Dec 2025 14:25:09 +0000
Subject: [PATCH 08/16] Fix entrypoint for setenv on unsupported arches.
Enable for arm and riscv Linux.
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/src/stdlib/CMakeLists.txt | 11 +++++++-
libc/src/stdlib/linux/CMakeLists.txt | 32 ++++++++++++-----------
4 files changed, 29 insertions(+), 16 deletions(-)
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 970c825bbfc96..63788cf21f7e3 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1084,6 +1084,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.exit
libc.src.stdlib.getenv
libc.src.stdlib.quick_exit
+ libc.src.stdlib.setenv
# signal.h entrypoints
libc.src.signal.kill
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 7baf4de9d8a5b..d2f52dd32d0ea 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1213,6 +1213,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.exit
libc.src.stdlib.getenv
libc.src.stdlib.quick_exit
+ libc.src.stdlib.setenv
# signal.h entrypoints
libc.src.signal.kill
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index b07e29a8dc383..1a13c953e58da 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -65,7 +65,7 @@ add_entrypoint_object(
libc.config.app_h
)
-if(LLVM_LIBC_FULL_BUILD)
+if(TARGET libc.src.__support.threads.mutex)
add_object_library(
environ_internal
SRCS
@@ -730,6 +730,15 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.abort
)
+if(TARGET libc.src.stdlib.environ_internal)
+ add_entrypoint_object(
+ setenv
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.setenv
+ )
+endif()
+
add_entrypoint_object(
system
ALIAS
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index feb0265444e38..b6aeece6500de 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -10,18 +10,20 @@ add_entrypoint_object(
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
-)
+if(TARGET libc.src.stdlib.environ_internal)
+ 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
+ )
+endif()
>From 51622e7939b6dacbf48d9359dc65ae40e1fe007d Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jeffbailey at google.com>
Date: Thu, 29 Jan 2026 11:43:16 +0000
Subject: [PATCH 09/16] Remove extra dependency on Mutex that's from earlier
---
libc/src/stdlib/CMakeLists.txt | 45 +++++++++++++++-------------------
1 file changed, 20 insertions(+), 25 deletions(-)
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 1a13c953e58da..d4bbeebd46aa8 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -65,23 +65,20 @@ add_entrypoint_object(
libc.config.app_h
)
-if(TARGET libc.src.__support.threads.mutex)
- 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
- )
-endif()
+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.hdr.func.free
+ libc.hdr.func.malloc
+ libc.hdr.func.realloc
+)
add_entrypoint_object(
strfromf
@@ -730,14 +727,12 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.abort
)
-if(TARGET libc.src.stdlib.environ_internal)
- add_entrypoint_object(
- setenv
- ALIAS
- DEPENDS
- .${LIBC_TARGET_OS}.setenv
- )
-endif()
+add_entrypoint_object(
+ setenv
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.setenv
+)
add_entrypoint_object(
system
>From ab02c1c3154e83e35f94fb405a0c947113938983 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jeffbailey at google.com>
Date: Thu, 29 Jan 2026 11:47:07 +0000
Subject: [PATCH 10/16] Fix issue where one realloc succeeds but the second one
fails and we have inconsistent items by mallocing both ourselves and copying
them.
---
libc/src/stdlib/environ_internal.cpp | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index fe1ca15f78597..e32e9882d0d5d 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -94,6 +94,8 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
new_ownership[i] = EnvStringOwnership();
}
}
+ for (size_t i = size; i < new_capacity; i++)
+ new_ownership[i] = EnvStringOwnership();
new_storage[size] = nullptr;
storage = new_storage;
@@ -114,22 +116,32 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
// Grow capacity by the growth factor
size_t new_capacity = needed * ENVIRON_GROWTH_FACTOR;
- // Use realloc to grow the arrays
+ // Allocate new arrays and copy to avoid partial realloc failures.
+ // We manage two coupled arrays plus app.env_ptr; reallocating one at a
+ // time can leave the state inconsistent if the second allocation fails.
char **new_storage = reinterpret_cast<char **>(
- realloc(storage, sizeof(char *) * (new_capacity + 1)));
+ malloc(sizeof(char *) * (new_capacity + 1)));
if (!new_storage)
return false;
EnvStringOwnership *new_ownership = reinterpret_cast<EnvStringOwnership *>(
- realloc(ownership, sizeof(EnvStringOwnership) * (new_capacity + 1)));
+ malloc(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.
- storage = new_storage;
+ free(new_storage);
return false;
}
+ for (size_t i = 0; i < size; i++) {
+ new_storage[i] = storage[i];
+ new_ownership[i] = ownership[i];
+ }
+ for (size_t i = size; i < new_capacity; i++)
+ new_ownership[i] = EnvStringOwnership();
+ new_storage[size] = nullptr;
+
+ free(storage);
+ free(ownership);
+
storage = new_storage;
ownership = new_ownership;
capacity = new_capacity;
>From 2b509f96b7712fadf38979435b20dd50ec8aa538 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jeffbailey at google.com>
Date: Thu, 29 Jan 2026 12:02:40 +0000
Subject: [PATCH 11/16] libc: ensure setenv copies environ before overwriting
Call EnvironmentManager::ensure_capacity() when replacing an existing
variable so we transition to owned storage and initialize ownership
tracking before writing. This avoids touching startup environ and
prevents null ownership access.
---
libc/src/stdlib/CMakeLists.txt | 1 -
libc/src/stdlib/environ_internal.cpp | 1 -
libc/src/stdlib/linux/CMakeLists.txt | 1 +
libc/src/stdlib/linux/setenv.cpp | 8 +++++++-
4 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index d4bbeebd46aa8..2e291f14860e5 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -77,7 +77,6 @@ add_object_library(
libc.src.__support.CPP.string_view
libc.hdr.func.free
libc.hdr.func.malloc
- libc.hdr.func.realloc
)
add_entrypoint_object(
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index e32e9882d0d5d..4c3019e578bb2 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -10,7 +10,6 @@
#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"
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index b6aeece6500de..971eb7a5b504a 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -19,6 +19,7 @@ if(TARGET libc.src.stdlib.environ_internal)
../setenv.h
DEPENDS
libc.src.stdlib.environ_internal
+ libc.src.__support.common
libc.src.__support.CPP.string_view
libc.src.__support.libc_errno
libc.hdr.func.malloc
diff --git a/libc/src/stdlib/linux/setenv.cpp b/libc/src/stdlib/linux/setenv.cpp
index 7ce09f6e9135a..e5bdf3c805a1b 100644
--- a/libc/src/stdlib/linux/setenv.cpp
+++ b/libc/src/stdlib/linux/setenv.cpp
@@ -55,7 +55,13 @@ LLVM_LIBC_FUNCTION(int, setenv,
return 0;
}
- // Need to replace the value
+ // Need to replace the value. Ensure we own the environ storage and have
+ // ownership tracking before updating in-place.
+ if (!env_mgr.ensure_capacity(env_mgr.get_size())) {
+ libc_errno = ENOMEM;
+ return -1;
+ }
+
// Calculate size for "name=value" string
size_t name_len = LIBC_NAMESPACE::internal::string_length(name);
size_t value_len = LIBC_NAMESPACE::internal::string_length(value);
>From 9fbe36dcce41986755a41e58bfe5163129b9c454 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jeffbailey at google.com>
Date: Thu, 29 Jan 2026 12:16:45 +0000
Subject: [PATCH 12/16] [libc] Optimize and refactor environment management
infrastructure
- Add an `initialized` flag to `EnvironmentManager` to ensure the startup
environment is only scanned once, reducing `setenv` overhead from O(N)
to O(1) for initialization on subsequent calls.
- Refactor `setenv` to consolidate the logic for replacing existing
variables and adding new ones, significantly reducing code duplication.
- Optimize string handling in `setenv` by utilizing `cpp::string_view`
to avoid redundant length calculations.
- Improve documentation in `environ_internal.cpp` regarding the
transition from startup environment to managed storage and the
memory safety strategy.
---
libc/src/stdlib/environ_internal.cpp | 44 +++++++++-------
libc/src/stdlib/environ_internal.h | 3 ++
libc/src/stdlib/linux/setenv.cpp | 79 ++++++++--------------------
3 files changed, 49 insertions(+), 77 deletions(-)
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index 4c3019e578bb2..3f53a53cb9a06 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -32,16 +32,19 @@ char **EnvironmentManager::get_array() {
}
void EnvironmentManager::init() {
- // Count entries in the startup environ
- char **env_ptr = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
- if (!env_ptr)
+ if (initialized)
return;
- size_t count = 0;
- for (char **env = env_ptr; *env != nullptr; env++)
- count++;
+ // Count entries in the startup environ
+ char **env_ptr = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
+ if (env_ptr) {
+ size_t count = 0;
+ for (char **env = env_ptr; *env != nullptr; env++)
+ count++;
+ size = count;
+ }
- size = count;
+ initialized = true;
}
int EnvironmentManager::find_var(cpp::string_view name) {
@@ -63,11 +66,13 @@ int EnvironmentManager::find_var(cpp::string_view name) {
}
bool EnvironmentManager::ensure_capacity(size_t needed) {
- // If we're still using the startup environ, we need to copy it
+ // If we're still using the startup environ (pointed to by app.env_ptr),
+ // we must transition to our own managed storage. This allows us to
+ // track ownership of strings and safely expand the array.
if (!is_ours) {
char **old_env = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
- // Allocate new array with room to grow
+ // Allocate new array with room to grow.
size_t new_capacity = needed < MIN_ENVIRON_CAPACITY
? MIN_ENVIRON_CAPACITY
: needed * ENVIRON_GROWTH_FACTOR;
@@ -76,7 +81,8 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
if (!new_storage)
return false;
- // Allocate ownership tracking array
+ // Allocate ownership tracking array. We use a parallel array to keep
+ // the environ array compatible with the standard char** format.
EnvStringOwnership *new_ownership = reinterpret_cast<EnvStringOwnership *>(
malloc(sizeof(EnvStringOwnership) * (new_capacity + 1)));
if (!new_ownership) {
@@ -84,12 +90,11 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
return false;
}
- // Copy existing pointers (we don't own the strings yet, so just copy
- // pointers)
+ // Copy existing pointers from the startup environment.
+ // We don't own these strings, so we mark them as not-ours.
if (old_env) {
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();
}
}
@@ -102,22 +107,21 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
capacity = new_capacity;
is_ours = true;
- // Update app.env_ptr to point to our storage
+ // Update the global environ pointer.
LIBC_NAMESPACE::app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
return true;
}
- // We already own environ, check if we need to grow it
+ // We already own the environment array. Check if it's large enough.
if (needed <= capacity)
return true;
- // Grow capacity by the growth factor
+ // Grow capacity.
size_t new_capacity = needed * ENVIRON_GROWTH_FACTOR;
- // Allocate new arrays and copy to avoid partial realloc failures.
- // We manage two coupled arrays plus app.env_ptr; reallocating one at a
- // time can leave the state inconsistent if the second allocation fails.
+ // Allocate new arrays and copy. We avoid realloc to ensure that
+ // failures don't leave the manager in an inconsistent state.
char **new_storage = reinterpret_cast<char **>(
malloc(sizeof(char *) * (new_capacity + 1)));
if (!new_storage)
@@ -145,7 +149,7 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
ownership = new_ownership;
capacity = new_capacity;
- // Update app.env_ptr to point to our new storage
+ // Update the global environ pointer.
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 b638860b9e9b3..981c506abc40b 100644
--- a/libc/src/stdlib/environ_internal.h
+++ b/libc/src/stdlib/environ_internal.h
@@ -54,6 +54,9 @@ class EnvironmentManager {
// Current number of variables in environ
size_t size = 0;
+ // True if we have initialized from the startup environment
+ bool initialized = false;
+
// True if we allocated storage (and are responsible for freeing it)
bool is_ours = false;
diff --git a/libc/src/stdlib/linux/setenv.cpp b/libc/src/stdlib/linux/setenv.cpp
index e5bdf3c805a1b..42d8fc4e71f0f 100644
--- a/libc/src/stdlib/linux/setenv.cpp
+++ b/libc/src/stdlib/linux/setenv.cpp
@@ -48,62 +48,23 @@ LLVM_LIBC_FUNCTION(int, setenv,
// Search for existing variable
int index = env_mgr.find_var(name_view);
- if (index >= 0) {
- // Variable exists
- if (overwrite == 0) {
- // Don't overwrite, just return success
- return 0;
- }
-
- // Need to replace the value. Ensure we own the environ storage and have
- // ownership tracking before updating in-place.
- if (!env_mgr.ensure_capacity(env_mgr.get_size())) {
- libc_errno = ENOMEM;
- return -1;
- }
-
- // Calculate size for "name=value" string
- 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));
- if (!new_string) {
- libc_errno = ENOMEM;
- return -1;
- }
-
- // Build "name=value" string
- LIBC_NAMESPACE::inline_memcpy(new_string, name, name_len);
- new_string[name_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
- char **env_array = env_mgr.get_array();
-
- // Free old string if we allocated it
- if (env_mgr.get_ownership()[index].can_free()) {
- free(env_array[index]);
- }
-
- env_array[index] = new_string;
- // Mark this string as allocated by us
- env_mgr.get_ownership()[index].allocated_by_us = true;
-
+ if (index >= 0 && overwrite == 0) {
return 0;
}
- // Variable doesn't exist, need to add it
- // Ensure we have capacity for one more entry
- if (!env_mgr.ensure_capacity(env_mgr.get_size() + 1)) {
+ // We either need to replace an existing variable or add a new one.
+ // In both cases, we must ensure we have our own environment storage
+ // and enough capacity.
+ size_t current_size = env_mgr.get_size();
+ size_t needed_size = (index >= 0) ? current_size : current_size + 1;
+
+ if (!env_mgr.ensure_capacity(needed_size)) {
libc_errno = ENOMEM;
return -1;
}
// Calculate size for "name=value" string
- size_t name_len = LIBC_NAMESPACE::internal::string_length(name);
+ size_t name_len = name_view.size();
size_t value_len = LIBC_NAMESPACE::internal::string_length(value);
size_t total_len = name_len + 1 + value_len + 1; // name + '=' + value + '\0'
@@ -119,17 +80,21 @@ LLVM_LIBC_FUNCTION(int, setenv,
LIBC_NAMESPACE::inline_memcpy(new_string + name_len + 1, value, value_len);
new_string[name_len + 1 + value_len] = '\0';
- // Add to environ array
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
- env_mgr.get_ownership()[current_size].allocated_by_us = true;
+ if (index >= 0) {
+ // Replace existing variable
+ if (env_mgr.get_ownership()[index].can_free())
+ free(env_array[index]);
- // Increment size and maintain null terminator
- env_mgr.increment_size();
- env_array[current_size + 1] = nullptr; // Maintain null terminator
+ env_array[index] = new_string;
+ env_mgr.get_ownership()[index].allocated_by_us = true;
+ } else {
+ // Add new variable
+ env_array[current_size] = new_string;
+ env_mgr.get_ownership()[current_size].allocated_by_us = true;
+ env_mgr.increment_size();
+ env_array[current_size + 1] = nullptr; // Maintain null terminator
+ }
return 0;
}
>From 0ace7031518f5f52b03519d667cab606f3d80ee8 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jeffbailey at google.com>
Date: Thu, 29 Jan 2026 13:22:01 +0000
Subject: [PATCH 13/16] Use static_cast instead of reinterpret_cast.
---
libc/src/stdlib/environ_internal.cpp | 10 +++++-----
libc/src/stdlib/environ_internal.h | 4 ++--
libc/src/stdlib/linux/setenv.cpp | 4 ++--
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index 3f53a53cb9a06..32c5625906029 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -77,13 +77,13 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
? MIN_ENVIRON_CAPACITY
: needed * ENVIRON_GROWTH_FACTOR;
char **new_storage =
- reinterpret_cast<char **>(malloc(sizeof(char *) * (new_capacity + 1)));
+ static_cast<char **>(malloc(sizeof(char *) * (new_capacity + 1)));
if (!new_storage)
return false;
// Allocate ownership tracking array. We use a parallel array to keep
// the environ array compatible with the standard char** format.
- EnvStringOwnership *new_ownership = reinterpret_cast<EnvStringOwnership *>(
+ EnvStringOwnership *new_ownership = static_cast<EnvStringOwnership *>(
malloc(sizeof(EnvStringOwnership) * (new_capacity + 1)));
if (!new_ownership) {
free(new_storage);
@@ -122,12 +122,12 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
// Allocate new arrays and copy. We avoid realloc to ensure that
// failures don't leave the manager in an inconsistent state.
- char **new_storage = reinterpret_cast<char **>(
- malloc(sizeof(char *) * (new_capacity + 1)));
+ char **new_storage =
+ static_cast<char **>(malloc(sizeof(char *) * (new_capacity + 1)));
if (!new_storage)
return false;
- EnvStringOwnership *new_ownership = reinterpret_cast<EnvStringOwnership *>(
+ EnvStringOwnership *new_ownership = static_cast<EnvStringOwnership *>(
malloc(sizeof(EnvStringOwnership) * (new_capacity + 1)));
if (!new_ownership) {
free(new_storage);
diff --git a/libc/src/stdlib/environ_internal.h b/libc/src/stdlib/environ_internal.h
index 981c506abc40b..8e6354c068358 100644
--- a/libc/src/stdlib/environ_internal.h
+++ b/libc/src/stdlib/environ_internal.h
@@ -1,4 +1,4 @@
-//===-- Internal utilities for environment management ----------*- C++ -*-===//
+//===-- 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.
@@ -65,7 +65,7 @@ class EnvironmentManager {
public:
// Get the singleton instance of the environment manager
- LIBC_INLINE static EnvironmentManager &instance() {
+ LIBC_INLINE static EnvironmentManager &get_instance() {
static EnvironmentManager mgr;
return mgr;
}
diff --git a/libc/src/stdlib/linux/setenv.cpp b/libc/src/stdlib/linux/setenv.cpp
index 42d8fc4e71f0f..ea3df57de6cbb 100644
--- a/libc/src/stdlib/linux/setenv.cpp
+++ b/libc/src/stdlib/linux/setenv.cpp
@@ -40,7 +40,7 @@ LLVM_LIBC_FUNCTION(int, setenv,
}
// Get the singleton environment manager
- auto &env_mgr = internal::EnvironmentManager::instance();
+ auto &env_mgr = internal::EnvironmentManager::get_instance();
// Initialize environ if not already done
env_mgr.init();
@@ -68,7 +68,7 @@ LLVM_LIBC_FUNCTION(int, setenv,
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));
+ char *new_string = static_cast<char *>(malloc(total_len));
if (!new_string) {
libc_errno = ENOMEM;
return -1;
>From c7773a0002e06e7e09ff0dc3be455a78bd676760 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jeffbailey at google.com>
Date: Thu, 29 Jan 2026 13:30:41 +0000
Subject: [PATCH 14/16] Remove namespace quals and an if condition.
---
libc/src/stdlib/environ_internal.cpp | 10 ++++----
libc/src/stdlib/linux/CMakeLists.txt | 34 +++++++++++++---------------
2 files changed, 21 insertions(+), 23 deletions(-)
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index 32c5625906029..9f530d39509ee 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -28,7 +28,7 @@ constexpr size_t ENVIRON_GROWTH_FACTOR = 2;
char **EnvironmentManager::get_array() {
if (is_ours)
return storage;
- return reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
+ return reinterpret_cast<char **>(app.env_ptr);
}
void EnvironmentManager::init() {
@@ -36,7 +36,7 @@ void EnvironmentManager::init() {
return;
// Count entries in the startup environ
- char **env_ptr = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
+ char **env_ptr = reinterpret_cast<char **>(app.env_ptr);
if (env_ptr) {
size_t count = 0;
for (char **env = env_ptr; *env != nullptr; env++)
@@ -70,7 +70,7 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
// we must transition to our own managed storage. This allows us to
// track ownership of strings and safely expand the array.
if (!is_ours) {
- char **old_env = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
+ char **old_env = reinterpret_cast<char **>(app.env_ptr);
// Allocate new array with room to grow.
size_t new_capacity = needed < MIN_ENVIRON_CAPACITY
@@ -108,7 +108,7 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
is_ours = true;
// Update the global environ pointer.
- LIBC_NAMESPACE::app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
+ app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
return true;
}
@@ -150,7 +150,7 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
capacity = new_capacity;
// Update the global environ pointer.
- LIBC_NAMESPACE::app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
+ app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
return true;
}
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 971eb7a5b504a..ef95ad4244196 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -10,21 +10,19 @@ add_entrypoint_object(
libc.src.stdlib._Exit
)
-if(TARGET libc.src.stdlib.environ_internal)
- add_entrypoint_object(
- setenv
- SRCS
- setenv.cpp
- HDRS
- ../setenv.h
- DEPENDS
- libc.src.stdlib.environ_internal
- libc.src.__support.common
- 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
- )
-endif()
+add_entrypoint_object(
+ setenv
+ SRCS
+ setenv.cpp
+ HDRS
+ ../setenv.h
+ DEPENDS
+ libc.src.stdlib.environ_internal
+ libc.src.__support.common
+ 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
+)
>From 9cf906a6993a8addc20cfe6eb037ae2b17b5f36c Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jeffbailey at google.com>
Date: Thu, 29 Jan 2026 13:58:12 +0000
Subject: [PATCH 15/16] Unify lookup handling with getenv and ensure that it
can find everything that setenv creates.
---
libc/src/stdlib/CMakeLists.txt | 3 ++-
libc/src/stdlib/environ_internal.cpp | 7 ++-----
libc/src/stdlib/environ_internal.h | 1 -
libc/src/stdlib/getenv.cpp | 29 +++++++++-------------------
4 files changed, 13 insertions(+), 27 deletions(-)
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 2e291f14860e5..ca29d932d031a 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -62,7 +62,8 @@ add_entrypoint_object(
HDRS
getenv.h
DEPENDS
- libc.config.app_h
+ libc.config.app_h
+ .environ_internal
)
add_object_library(
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index 9f530d39509ee..d5821c56de13d 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -54,11 +54,8 @@ int EnvironmentManager::find_var(cpp::string_view name) {
for (size_t i = 0; i < 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()] == '=')
+ if (current.starts_with(name) && current.size() > name.size() &&
+ current[name.size()] == '=')
return static_cast<int>(i);
}
diff --git a/libc/src/stdlib/environ_internal.h b/libc/src/stdlib/environ_internal.h
index 8e6354c068358..3477854abd39b 100644
--- a/libc/src/stdlib/environ_internal.h
+++ b/libc/src/stdlib/environ_internal.h
@@ -40,7 +40,6 @@ struct EnvStringOwnership {
// 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)
char **storage = nullptr;
diff --git a/libc/src/stdlib/getenv.cpp b/libc/src/stdlib/getenv.cpp
index e6ef03fad5c51..902da020ecd0e 100644
--- a/libc/src/stdlib/getenv.cpp
+++ b/libc/src/stdlib/getenv.cpp
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
#include "src/stdlib/getenv.h"
-#include "config/app.h"
+#include "environ_internal.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
@@ -17,29 +17,18 @@
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(char *, getenv, (const char *name)) {
- char **env_ptr = reinterpret_cast<char **>(LIBC_NAMESPACE::app.env_ptr);
-
- if (name == nullptr || env_ptr == nullptr)
- return nullptr;
-
- LIBC_NAMESPACE::cpp::string_view env_var_name(name);
- if (env_var_name.size() == 0)
+ if (name == nullptr || name[0] == '\0')
return nullptr;
- for (char **env = env_ptr; *env != nullptr; env++) {
- LIBC_NAMESPACE::cpp::string_view cur(*env);
- if (!cur.starts_with(env_var_name))
- continue;
- if (cur[env_var_name.size()] != '=')
- continue;
+ auto &env_mgr = internal::EnvironmentManager::get_instance();
+ env_mgr.init();
- // Remove the name and the equals sign.
- cur.remove_prefix(env_var_name.size() + 1);
- // We know that data is null terminated, so this is safe.
- return const_cast<char *>(cur.data());
- }
+ cpp::string_view name_view(name);
+ int idx = env_mgr.find_var(name_view);
+ if (idx < 0)
+ return nullptr;
- return nullptr;
+ return env_mgr.get_array()[idx] + name_view.size() + 1;
}
} // namespace LIBC_NAMESPACE_DECL
>From 05d59c451bba8988d32dd9554519db3e6ff0fdf3 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jeffbailey at google.com>
Date: Thu, 29 Jan 2026 15:08:03 +0000
Subject: [PATCH 16/16] Remove unneeded stddef include.
---
libc/src/stdlib/getenv.cpp | 2 --
1 file changed, 2 deletions(-)
diff --git a/libc/src/stdlib/getenv.cpp b/libc/src/stdlib/getenv.cpp
index 902da020ecd0e..a6c28152b76a8 100644
--- a/libc/src/stdlib/getenv.cpp
+++ b/libc/src/stdlib/getenv.cpp
@@ -12,8 +12,6 @@
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
-#include <stddef.h> // For size_t.
-
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(char *, getenv, (const char *name)) {
More information about the libc-commits
mailing list