[libc][stdlib] Add putenv (#208339)
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). Registered for x86_64, aarch64, and riscv. Integration tests cover basic operations, ownership semantics, validation, and edge cases. Assisted-by: Automated tooling, human reviewed.
This commit is contained in:
parent
5a650306e4
commit
e362f3c846
12 changed files with 279 additions and 0 deletions
|
|
@ -1207,6 +1207,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
|
||||
|
|
|
|||
|
|
@ -1400,6 +1400,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
|
||||
|
|
|
|||
|
|
@ -1409,6 +1409,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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (POSIX behavior).
|
||||
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
|
||||
|
|
|
|||
|
|
@ -130,6 +130,12 @@ public:
|
|||
// 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
|
||||
// (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
|
||||
// compacts the array. Returns 0 on success (including if the variable
|
||||
// was not found), -1 on allocation failure during array transition.
|
||||
|
|
|
|||
|
|
@ -51,6 +51,20 @@ add_entrypoint_object(
|
|||
libc.src.stdlib.environ_internal
|
||||
)
|
||||
|
||||
add_entrypoint_object(
|
||||
putenv
|
||||
SRCS
|
||||
putenv.cpp
|
||||
HDRS
|
||||
../putenv.h
|
||||
DEPENDS
|
||||
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
|
||||
|
|
|
|||
33
libc/src/stdlib/linux/putenv.cpp
Normal file
33
libc/src/stdlib/linux/putenv.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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/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);
|
||||
|
||||
int result = internal::EnvironmentManager::get_instance().put(string);
|
||||
if (result != 0)
|
||||
libc_errno = ENOMEM;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
25
libc/src/stdlib/putenv.h
Normal file
25
libc/src/stdlib/putenv.h
Normal file
|
|
@ -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
|
||||
|
|
@ -31,6 +31,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
|
||||
|
|
|
|||
135
libc/test/integration/src/stdlib/putenv_test.cpp
Normal file
135
libc/test/integration/src/stdlib/putenv_test.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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() {
|
||||
// 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 (POSIX behavior)
|
||||
{
|
||||
// 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: Pathological inputs (empty names, empty string) are allowed to match
|
||||
// glibc.
|
||||
{
|
||||
// Empty name with value (=value) should succeed and be found in environ.
|
||||
static char empty_name[] = "=value";
|
||||
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 "=" should succeed and be found in environ.
|
||||
static char just_equals[] = "=";
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in a new issue