[libc-commits] [libc] [libc][stdlib] Add putenv (PR #208339)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Tue Jul 28 06:45:35 PDT 2026


https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/208339

>From d2f88315923429c5d14327f5b77251f7085731ee Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Mon, 8 Jun 2026 20:39:26 +0100
Subject: [PATCH 1/3] [libc][stdlib] Add putenv

Added the POSIX putenv() function and its internal support.

Implemented EnvironmentManager::put() to insert caller-provided
"name=value" strings directly into the environment array, managing
ownership correctly (caller retains ownership).

If no '=' is present, it removes the variable (glibc/musl extension).

Added validation to reject empty variable names (e.g. "=value" or ""),
returning -1 and setting errno to EINVAL (defense-in-depth).

Registered for x86_64, aarch64, and riscv. Integration tests cover
basic operations, ownership semantics, validation, and edge cases.

Assisted-by: Automated tooling, human reviewed.
---
 libc/config/linux/aarch64/entrypoints.txt     |   1 +
 libc/config/linux/riscv/entrypoints.txt       |   1 +
 libc/config/linux/x86_64/entrypoints.txt      |   1 +
 libc/include/stdlib.yaml                      |   6 +
 libc/src/stdlib/CMakeLists.txt                |   7 ++
 libc/src/stdlib/environ_internal.cpp          |  36 ++++++
 libc/src/stdlib/environ_internal.h            |   6 +
 libc/src/stdlib/linux/CMakeLists.txt          |  15 +++
 libc/src/stdlib/linux/putenv.cpp              |  48 ++++++++
 libc/src/stdlib/putenv.h                      |  25 ++++
 .../integration/src/stdlib/CMakeLists.txt     |  14 +++
 .../integration/src/stdlib/putenv_test.cpp    | 116 ++++++++++++++++++
 12 files changed, 276 insertions(+)
 create mode 100644 libc/src/stdlib/linux/putenv.cpp
 create mode 100644 libc/src/stdlib/putenv.h
 create mode 100644 libc/test/integration/src/stdlib/putenv_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 81ff70d0086c6..89322ea29b6cb 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1202,6 +1202,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.stdlib.atexit
     libc.src.stdlib.exit
     libc.src.stdlib.getenv
+    libc.src.stdlib.putenv
     libc.src.stdlib.setenv
     libc.src.stdlib.unsetenv
     libc.src.stdlib.quick_exit
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index eb3f9a77b889b..3eb1317377d2c 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1388,6 +1388,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.stdlib.atexit
     libc.src.stdlib.exit
     libc.src.stdlib.getenv
+    libc.src.stdlib.putenv
     libc.src.stdlib.setenv
     libc.src.stdlib.unsetenv
     libc.src.stdlib.mbstowcs
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index d7e4408357658..97f3caadee390 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1397,6 +1397,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.stdlib.atexit
     libc.src.stdlib.exit
     libc.src.stdlib.getenv
+    libc.src.stdlib.putenv
     libc.src.stdlib.setenv
     libc.src.stdlib.unsetenv
     libc.src.stdlib.mbstowcs
diff --git a/libc/include/stdlib.yaml b/libc/include/stdlib.yaml
index 0533fe04aecbb..efbd47029199e 100644
--- a/libc/include/stdlib.yaml
+++ b/libc/include/stdlib.yaml
@@ -185,6 +185,12 @@ functions:
       - type: void **
       - type: size_t
       - type: size_t
+  - name: putenv
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: char *
   - name: qsort
     standards:
       - stdc
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 8758694d1b272..038a949d143b2 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -695,6 +695,13 @@ add_entrypoint_object(
     .${LIBC_TARGET_OS}.setenv
 )
 
+add_entrypoint_object(
+  putenv
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.putenv
+)
+
 add_entrypoint_object(
   unsetenv
   ALIAS
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index d7ef081e7e2b1..64ff87c3097df 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -265,5 +265,41 @@ int EnvironmentManager::unset(cpp::string_view name) {
   return 0;
 }
 
+int EnvironmentManager::put(char *string) {
+  cpp::string_view sv(string);
+  size_t eq_pos = sv.find_first_of('=');
+
+  // No '=' found: treat as unset (glibc/musl convention).
+  if (eq_pos == cpp::string_view::npos)
+    return unset(sv);
+
+  cpp::string_view name = sv.substr(0, eq_pos);
+
+  cpp::optional<size_t> idx = find_var(name);
+
+  size_t needed = idx ? count : count + 1;
+  if (!ensure_capacity(needed))
+    return -1;
+
+  char **env_array = get_array();
+
+  if (idx) {
+    // Replace existing variable. Free old string if we own it.
+    if (ownership[*idx].can_free())
+      delete[] env_array[*idx];
+
+    env_array[*idx] = string;
+    ownership[*idx].allocated_by_us = false; // Caller owns this string.
+  } else {
+    // Add new variable at the end.
+    env_array[count] = string;
+    ownership[count].allocated_by_us = false;
+    count++;
+    env_array[count] = nullptr;
+  }
+
+  return 0;
+}
+
 } // namespace internal
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/environ_internal.h b/libc/src/stdlib/environ_internal.h
index f16d841779734..c315f2d8626e0 100644
--- a/libc/src/stdlib/environ_internal.h
+++ b/libc/src/stdlib/environ_internal.h
@@ -130,6 +130,12 @@ class EnvironmentManager {
   // errno to ENOMEM).
   int set(cpp::string_view name, cpp::string_view value, bool overwrite);
 
+  // Insert a caller-provided "name=value" string into the environment.
+  // The caller retains ownership of the string; the manager will not
+  // free it. If string contains no '=', the named variable is removed
+  // (glibc/musl convention). Returns 0 on success, -1 on failure.
+  int put(char *string);
+
   // Remove a variable by name. Frees the string if we own it, then
   // compacts the array. Returns 0 on success (including if the variable
   // was not found), -1 on allocation failure during array transition.
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 9189c985d9a27..9f2ef97d516cf 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -45,6 +45,21 @@ add_entrypoint_object(
     libc.src.stdlib.environ_internal
 )
 
+add_entrypoint_object(
+  putenv
+  SRCS
+    putenv.cpp
+  HDRS
+    ../putenv.h
+  DEPENDS
+    libc.src.__support.CPP.string_view
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    libc.src.stdlib.environ_internal
+)
+
 add_entrypoint_object(
   unsetenv
   SRCS
diff --git a/libc/src/stdlib/linux/putenv.cpp b/libc/src/stdlib/linux/putenv.cpp
new file mode 100644
index 0000000000000..e97d9c8b33e7a
--- /dev/null
+++ b/libc/src/stdlib/linux/putenv.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the POSIX putenv function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/putenv.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/stdlib/environ_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, putenv, (char *string)) {
+  LIBC_CRASH_ON_NULLPTR(string);
+
+  cpp::string_view sv(string);
+  size_t eq_pos = sv.find_first_of('=');
+
+  // Defense in depth: Reject empty variable names.
+  // - eq_pos == 0: e.g. "=value" (empty name)
+  // - eq_pos == cpp::string_view::npos && sv.empty(): e.g. "" (attempting to
+  // unset empty name)
+  if (eq_pos == 0 || (eq_pos == cpp::string_view::npos && sv.empty())) {
+    libc_errno = EINVAL;
+    return -1;
+  }
+
+  int result = internal::EnvironmentManager::get_instance().put(string);
+  if (result != 0) {
+    libc_errno = ENOMEM;
+    return -1;
+  }
+
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/putenv.h b/libc/src/stdlib/putenv.h
new file mode 100644
index 0000000000000..1c07c5f80a61f
--- /dev/null
+++ b/libc/src/stdlib/putenv.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of the POSIX putenv function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDLIB_PUTENV_H
+#define LLVM_LIBC_SRC_STDLIB_PUTENV_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int putenv(char *string);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDLIB_PUTENV_H
diff --git a/libc/test/integration/src/stdlib/CMakeLists.txt b/libc/test/integration/src/stdlib/CMakeLists.txt
index 789f9aded9570..169779e9278b4 100644
--- a/libc/test/integration/src/stdlib/CMakeLists.txt
+++ b/libc/test/integration/src/stdlib/CMakeLists.txt
@@ -30,6 +30,20 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
       libc.src.string.strcmp
   )
 
+  add_integration_test(
+    putenv_test
+    SUITE
+      stdlib-integration-tests
+    SRCS
+      putenv_test.cpp
+    DEPENDS
+      libc.src.errno.errno
+      libc.src.stdlib.getenv
+      libc.src.stdlib.putenv
+      libc.src.string.strcmp
+      libc.src.unistd.environ
+  )
+
   add_integration_test(
     unsetenv_test
     SUITE
diff --git a/libc/test/integration/src/stdlib/putenv_test.cpp b/libc/test/integration/src/stdlib/putenv_test.cpp
new file mode 100644
index 0000000000000..ed82ae5e9ccd6
--- /dev/null
+++ b/libc/test/integration/src/stdlib/putenv_test.cpp
@@ -0,0 +1,116 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Integration tests for putenv.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/getenv.h"
+#include "src/stdlib/putenv.h"
+#include "src/string/strcmp.h"
+#include "src/unistd/environ.h"
+
+#include "test/IntegrationTest/test.h"
+
+#include <errno.h>
+
+static char set_var[] = "PUTENV_TEST=hello";
+static char replace_var[] = "PUTENV_TEST=world";
+static char empty_value[] = "PUTENV_EMPTY=";
+static char special_chars[] = "PUTENV_SPECIAL=!@#$%^&*()";
+
+TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
+          [[maybe_unused]] char **envp) {
+  // Test: Basic set
+  {
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(set_var), 0);
+    char *value = LIBC_NAMESPACE::getenv("PUTENV_TEST");
+    ASSERT_TRUE(value != nullptr);
+    ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "hello"), 0);
+  }
+
+  // Test: Overwrite existing variable
+  {
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(replace_var), 0);
+    char *value = LIBC_NAMESPACE::getenv("PUTENV_TEST");
+    ASSERT_TRUE(value != nullptr);
+    ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "world"), 0);
+  }
+
+  // Test: The pointer itself is used (not a copy)
+  {
+    // After putenv, getenv should return a pointer into the original string.
+    char *value = LIBC_NAMESPACE::getenv("PUTENV_TEST");
+    ASSERT_TRUE(value == replace_var + 12); // "PUTENV_TEST=" is 12 chars
+  }
+
+  // Test: Empty value
+  {
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(empty_value), 0);
+    char *value = LIBC_NAMESPACE::getenv("PUTENV_EMPTY");
+    ASSERT_TRUE(value != nullptr);
+    ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, ""), 0);
+  }
+
+  // Test: Special characters in value
+  {
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(special_chars), 0);
+    char *value = LIBC_NAMESPACE::getenv("PUTENV_SPECIAL");
+    ASSERT_TRUE(value != nullptr);
+    ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "!@#$%^&*()"), 0);
+  }
+
+  // Test: No '=' removes the variable (glibc/musl convention)
+  {
+    // First set a variable via putenv
+    static char var_to_remove[] = "REMOVE_ME=present";
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(var_to_remove), 0);
+    ASSERT_TRUE(LIBC_NAMESPACE::getenv("REMOVE_ME") != nullptr);
+
+    // Now call putenv without '=' to remove it
+    static char remove_cmd[] = "REMOVE_ME";
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(remove_cmd), 0);
+    ASSERT_TRUE(LIBC_NAMESPACE::getenv("REMOVE_ME") == nullptr);
+  }
+
+  // Test: environ is updated and contains the exact pointer
+  {
+    bool found = false;
+    for (char **env = LIBC_NAMESPACE::environ; *env != nullptr; ++env) {
+      if (*env == replace_var) {
+        found = true;
+        break;
+      }
+    }
+    ASSERT_TRUE(found);
+  }
+
+  // Test: Invalid inputs (defense-in-depth)
+  {
+    // Empty string (attempt to unset empty name)
+    static char empty_string[] = "";
+    errno = 0;
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(empty_string), -1);
+    ASSERT_ERRNO_EQ(EINVAL);
+
+    // Empty name with value
+    static char empty_name[] = "=value";
+    errno = 0;
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(empty_name), -1);
+    ASSERT_ERRNO_EQ(EINVAL);
+
+    // Just "=" (empty name, empty value)
+    static char just_equals[] = "=";
+    errno = 0;
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(just_equals), -1);
+    ASSERT_ERRNO_EQ(EINVAL);
+  }
+
+  return 0;
+}

>From 7f361ab3d76ca1f3ad0aa8f3c15f0de9e8d38fbd Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Tue, 14 Jul 2026 17:21:34 +0100
Subject: [PATCH 2/3] Address review comments: remove empty name validation
 from putenv

---
 libc/src/stdlib/linux/CMakeLists.txt          |  1 -
 libc/src/stdlib/linux/putenv.cpp              | 19 +------
 .../integration/src/stdlib/putenv_test.cpp    | 50 +++++++++++++------
 3 files changed, 37 insertions(+), 33 deletions(-)

diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 9f2ef97d516cf..40b5fd0a5b551 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -52,7 +52,6 @@ add_entrypoint_object(
   HDRS
     ../putenv.h
   DEPENDS
-    libc.src.__support.CPP.string_view
     libc.src.__support.common
     libc.src.__support.libc_errno
     libc.src.__support.macros.config
diff --git a/libc/src/stdlib/linux/putenv.cpp b/libc/src/stdlib/linux/putenv.cpp
index e97d9c8b33e7a..3a499d6c0d1ba 100644
--- a/libc/src/stdlib/linux/putenv.cpp
+++ b/libc/src/stdlib/linux/putenv.cpp
@@ -12,7 +12,6 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/putenv.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"
@@ -24,25 +23,11 @@ namespace LIBC_NAMESPACE_DECL {
 LLVM_LIBC_FUNCTION(int, putenv, (char *string)) {
   LIBC_CRASH_ON_NULLPTR(string);
 
-  cpp::string_view sv(string);
-  size_t eq_pos = sv.find_first_of('=');
-
-  // Defense in depth: Reject empty variable names.
-  // - eq_pos == 0: e.g. "=value" (empty name)
-  // - eq_pos == cpp::string_view::npos && sv.empty(): e.g. "" (attempting to
-  // unset empty name)
-  if (eq_pos == 0 || (eq_pos == cpp::string_view::npos && sv.empty())) {
-    libc_errno = EINVAL;
-    return -1;
-  }
-
   int result = internal::EnvironmentManager::get_instance().put(string);
-  if (result != 0) {
+  if (result != 0)
     libc_errno = ENOMEM;
-    return -1;
-  }
 
-  return 0;
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/integration/src/stdlib/putenv_test.cpp b/libc/test/integration/src/stdlib/putenv_test.cpp
index ed82ae5e9ccd6..03ce6d89b8642 100644
--- a/libc/test/integration/src/stdlib/putenv_test.cpp
+++ b/libc/test/integration/src/stdlib/putenv_test.cpp
@@ -91,25 +91,45 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
     ASSERT_TRUE(found);
   }
 
-  // Test: Invalid inputs (defense-in-depth)
+  // Test: Pathological inputs (empty names, empty string) are allowed to match
+  // glibc.
   {
-    // Empty string (attempt to unset empty name)
-    static char empty_string[] = "";
-    errno = 0;
-    ASSERT_EQ(LIBC_NAMESPACE::putenv(empty_string), -1);
-    ASSERT_ERRNO_EQ(EINVAL);
-
-    // Empty name with value
+    // Empty name with value (=value) should succeed and be found in environ.
     static char empty_name[] = "=value";
-    errno = 0;
-    ASSERT_EQ(LIBC_NAMESPACE::putenv(empty_name), -1);
-    ASSERT_ERRNO_EQ(EINVAL);
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(empty_name), 0);
+    bool found = false;
+    for (char **env = LIBC_NAMESPACE::environ; *env != nullptr; ++env) {
+      if (*env == empty_name) {
+        found = true;
+        break;
+      }
+    }
+    ASSERT_TRUE(found);
 
-    // Just "=" (empty name, empty value)
+    // Just "=" should succeed and be found in environ.
     static char just_equals[] = "=";
-    errno = 0;
-    ASSERT_EQ(LIBC_NAMESPACE::putenv(just_equals), -1);
-    ASSERT_ERRNO_EQ(EINVAL);
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(just_equals), 0);
+    found = false;
+    for (char **env = LIBC_NAMESPACE::environ; *env != nullptr; ++env) {
+      if (*env == just_equals) {
+        found = true;
+        break;
+      }
+    }
+    ASSERT_TRUE(found);
+
+    // Empty string unsets the empty name.
+    static char empty_string[] = "";
+    ASSERT_EQ(LIBC_NAMESPACE::putenv(empty_string), 0);
+    // Verify "=value" is no longer in environ (it should have been unset).
+    found = false;
+    for (char **env = LIBC_NAMESPACE::environ; *env != nullptr; ++env) {
+      if (*env == empty_name) {
+        found = true;
+        break;
+      }
+    }
+    ASSERT_FALSE(found);
   }
 
   return 0;

>From b6f5a0202b82cbf97aa68c9aed534dd6061d0c39 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Tue, 28 Jul 2026 14:45:19 +0100
Subject: [PATCH 3/3] Address review comments: update comments to POSIX and
 simplify TEST_MAIN

---
 libc/src/stdlib/environ_internal.cpp             | 2 +-
 libc/src/stdlib/environ_internal.h               | 2 +-
 libc/test/integration/src/stdlib/putenv_test.cpp | 5 ++---
 3 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index 64ff87c3097df..da2e5c8185ca0 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -269,7 +269,7 @@ int EnvironmentManager::put(char *string) {
   cpp::string_view sv(string);
   size_t eq_pos = sv.find_first_of('=');
 
-  // No '=' found: treat as unset (glibc/musl convention).
+  // No '=' found: treat as unset (POSIX behavior).
   if (eq_pos == cpp::string_view::npos)
     return unset(sv);
 
diff --git a/libc/src/stdlib/environ_internal.h b/libc/src/stdlib/environ_internal.h
index c315f2d8626e0..aa111b8496aec 100644
--- a/libc/src/stdlib/environ_internal.h
+++ b/libc/src/stdlib/environ_internal.h
@@ -133,7 +133,7 @@ class EnvironmentManager {
   // Insert a caller-provided "name=value" string into the environment.
   // The caller retains ownership of the string; the manager will not
   // free it. If string contains no '=', the named variable is removed
-  // (glibc/musl convention). Returns 0 on success, -1 on failure.
+  // (POSIX behavior). Returns 0 on success, -1 on failure.
   int put(char *string);
 
   // Remove a variable by name. Frees the string if we own it, then
diff --git a/libc/test/integration/src/stdlib/putenv_test.cpp b/libc/test/integration/src/stdlib/putenv_test.cpp
index 03ce6d89b8642..0781299936726 100644
--- a/libc/test/integration/src/stdlib/putenv_test.cpp
+++ b/libc/test/integration/src/stdlib/putenv_test.cpp
@@ -25,8 +25,7 @@ static char replace_var[] = "PUTENV_TEST=world";
 static char empty_value[] = "PUTENV_EMPTY=";
 static char special_chars[] = "PUTENV_SPECIAL=!@#$%^&*()";
 
-TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
-          [[maybe_unused]] char **envp) {
+TEST_MAIN() {
   // Test: Basic set
   {
     ASSERT_EQ(LIBC_NAMESPACE::putenv(set_var), 0);
@@ -66,7 +65,7 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
     ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "!@#$%^&*()"), 0);
   }
 
-  // Test: No '=' removes the variable (glibc/musl convention)
+  // Test: No '=' removes the variable (POSIX behavior)
   {
     // First set a variable via putenv
     static char var_to_remove[] = "REMOVE_ME=present";



More information about the libc-commits mailing list