[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