[libc][stdlib] Add unsetenv (#202422)
Added the POSIX unsetenv() function and its internal support. Implemented EnvironmentManager::unset() to remove a variable by name, free the string if allocated, and compact the array. Updated EnvironmentManager to synchronize the public global environ pointer when transitioning to managed storage. Registered for x86_64, aarch64, and riscv. Integration tests cover basic operations and edge cases. Assisted-by: Automated tooling, human reviewed.
This commit is contained in:
1 parent
ec574cf044
commit
bef95e5467
13 files changed
+363
-10
No files matched your search
@@ -1177,6 +1177,7 @@ if(LLVM_LIBC_FULL_BUILD)
|
||||
libc.src.stdlib.exit
|
||||
libc.src.stdlib.getenv
|
||||
libc.src.stdlib.setenv
|
||||
libc.src.stdlib.unsetenv
|
||||
libc.src.stdlib.quick_exit
|
||||
|
||||
# signal.h entrypoints
|
||||
|
||||
@@ -1306,6 +1306,7 @@ if(LLVM_LIBC_FULL_BUILD)
|
||||
libc.src.stdlib.exit
|
||||
libc.src.stdlib.getenv
|
||||
libc.src.stdlib.setenv
|
||||
libc.src.stdlib.unsetenv
|
||||
libc.src.stdlib.quick_exit
|
||||
|
||||
# signal.h entrypoints
|
||||
|
||||
@@ -1372,6 +1372,7 @@ if(LLVM_LIBC_FULL_BUILD)
|
||||
libc.src.stdlib.exit
|
||||
libc.src.stdlib.getenv
|
||||
libc.src.stdlib.setenv
|
||||
libc.src.stdlib.unsetenv
|
||||
libc.src.stdlib.mbstowcs
|
||||
libc.src.stdlib.mbtowc
|
||||
libc.src.stdlib.mblen
|
||||
|
||||
@@ -376,6 +376,12 @@ functions:
|
||||
return_type: int
|
||||
arguments:
|
||||
- type: const char *
|
||||
- name: unsetenv
|
||||
standards:
|
||||
- posix
|
||||
return_type: int
|
||||
arguments:
|
||||
- type: const char *
|
||||
- name: wctomb
|
||||
standards:
|
||||
- stdc
|
||||
|
||||
@@ -14,11 +14,13 @@ add_subdirectory(netinet)
|
||||
add_subdirectory(stdbit)
|
||||
add_subdirectory(stdfix)
|
||||
add_subdirectory(stdio)
|
||||
# unistd must be added before stdlib because stdlib's environ_internal
|
||||
# target conditionally depends on unistd's environ target.
|
||||
add_subdirectory(unistd)
|
||||
add_subdirectory(stdlib)
|
||||
add_subdirectory(string)
|
||||
add_subdirectory(strings)
|
||||
add_subdirectory(time)
|
||||
add_subdirectory(unistd)
|
||||
add_subdirectory(wchar)
|
||||
add_subdirectory(wctype)
|
||||
|
||||
|
||||
@@ -77,6 +77,24 @@ if((${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") AND
|
||||
"-fdisable-tree-waccess3")
|
||||
endif()
|
||||
|
||||
set(environ_internal_deps
|
||||
libc.config.app_h
|
||||
libc.hdr.types.size_t
|
||||
libc.src.__support.alloc_checker
|
||||
libc.src.__support.CPP.new
|
||||
libc.src.__support.CPP.optional
|
||||
libc.src.__support.CPP.string_view
|
||||
libc.src.__support.macros.attributes
|
||||
libc.src.__support.macros.config
|
||||
libc.src.string.memory_utils.inline_memcpy
|
||||
)
|
||||
set(environ_internal_compile_options ${environ_internal_gcc12_workaround})
|
||||
|
||||
if("libc.src.unistd.environ" IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
|
||||
list(APPEND environ_internal_deps libc.src.unistd.environ)
|
||||
list(APPEND environ_internal_compile_options "-DLIBC_COPT_SUPPORT_ENVIRON")
|
||||
endif()
|
||||
|
||||
add_object_library(
|
||||
environ_internal
|
||||
SRCS
|
||||
@@ -84,16 +102,9 @@ add_object_library(
|
||||
HDRS
|
||||
environ_internal.h
|
||||
COMPILE_OPTIONS
|
||||
${environ_internal_gcc12_workaround}
|
||||
${environ_internal_compile_options}
|
||||
DEPENDS
|
||||
libc.config.app_h
|
||||
libc.hdr.types.size_t
|
||||
libc.src.__support.CPP.new
|
||||
libc.src.__support.CPP.optional
|
||||
libc.src.__support.CPP.string_view
|
||||
libc.src.__support.macros.attributes
|
||||
libc.src.__support.macros.config
|
||||
libc.src.string.memory_utils.inline_memcpy
|
||||
${environ_internal_deps}
|
||||
)
|
||||
|
||||
add_entrypoint_object(
|
||||
@@ -677,6 +688,13 @@ add_entrypoint_object(
|
||||
.${LIBC_TARGET_OS}.setenv
|
||||
)
|
||||
|
||||
add_entrypoint_object(
|
||||
unsetenv
|
||||
ALIAS
|
||||
DEPENDS
|
||||
.${LIBC_TARGET_OS}.unsetenv
|
||||
)
|
||||
|
||||
if(LIBC_TARGET_OS_IS_BAREMETAL OR LIBC_TARGET_OS_IS_GPU)
|
||||
add_entrypoint_object(
|
||||
malloc
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#include "src/__support/alloc-checker.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
#include "src/string/memory_utils/inline_memcpy.h"
|
||||
#ifdef LIBC_COPT_SUPPORT_ENVIRON
|
||||
#include "src/unistd/environ.h"
|
||||
#endif
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
namespace internal {
|
||||
@@ -142,6 +145,9 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
|
||||
|
||||
// Update the global environ pointer.
|
||||
app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
|
||||
#ifdef LIBC_COPT_SUPPORT_ENVIRON
|
||||
environ = storage;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -168,6 +174,9 @@ bool EnvironmentManager::ensure_capacity(size_t needed) {
|
||||
|
||||
// Update the global environ pointer.
|
||||
app.env_ptr = reinterpret_cast<uintptr_t *>(storage);
|
||||
#ifdef LIBC_COPT_SUPPORT_ENVIRON
|
||||
environ = storage;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -221,5 +230,40 @@ int EnvironmentManager::set(cpp::string_view name, cpp::string_view value,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int EnvironmentManager::unset(cpp::string_view name) {
|
||||
cpp::optional<size_t> idx = find_var(name);
|
||||
if (!idx)
|
||||
return 0; // Variable not found; POSIX defines this as success.
|
||||
|
||||
// Transition to managed storage so we can modify the array and track
|
||||
// ownership correctly.
|
||||
if (!ensure_capacity(count))
|
||||
return -1;
|
||||
|
||||
char **env_array = get_array();
|
||||
|
||||
// Loop to remove all instances of the variable (e.g. duplicates from execve).
|
||||
while (idx) {
|
||||
size_t i = *idx;
|
||||
if (ownership[i].can_free())
|
||||
delete[] env_array[i];
|
||||
|
||||
// Compact: shift remaining entries left to fill the gap.
|
||||
// Shifting elements preserves the order of environment variables, which is
|
||||
// desirable to maintain consistency with getenv resolution order and
|
||||
// typical environ iteration behavior.
|
||||
for (size_t j = i; j < count - 1; j++) {
|
||||
env_array[j] = env_array[j + 1];
|
||||
ownership[j] = ownership[j + 1];
|
||||
}
|
||||
count--;
|
||||
env_array[count] = nullptr;
|
||||
|
||||
idx = find_var(name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
@@ -129,6 +129,11 @@ public:
|
||||
// Returns 0 on success, -1 on allocation failure (caller should set
|
||||
// errno to ENOMEM).
|
||||
int set(cpp::string_view name, cpp::string_view value, bool overwrite);
|
||||
|
||||
// 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.
|
||||
int unset(cpp::string_view name);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
@@ -23,3 +23,18 @@ add_entrypoint_object(
|
||||
libc.src.__support.macros.null_check
|
||||
libc.src.stdlib.environ_internal
|
||||
)
|
||||
|
||||
add_entrypoint_object(
|
||||
unsetenv
|
||||
SRCS
|
||||
unsetenv.cpp
|
||||
HDRS
|
||||
../unsetenv.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
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 unsetenv function.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "src/stdlib/unsetenv.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, unsetenv, (const char *name)) {
|
||||
LIBC_CRASH_ON_NULLPTR(name);
|
||||
|
||||
cpp::string_view name_view(name);
|
||||
|
||||
// POSIX: name must not be empty or contain '='.
|
||||
if (name_view.empty() ||
|
||||
name_view.find_first_of('=') != cpp::string_view::npos) {
|
||||
libc_errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int result = internal::EnvironmentManager::get_instance().unset(name_view);
|
||||
if (result != 0)
|
||||
libc_errno = ENOMEM;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
@@ -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 unsetenv function.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIBC_SRC_STDLIB_UNSETENV_H
|
||||
#define LLVM_LIBC_SRC_STDLIB_UNSETENV_H
|
||||
|
||||
#include "src/__support/macros/config.h"
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
|
||||
int unsetenv(const char *name);
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
|
||||
#endif // LLVM_LIBC_SRC_STDLIB_UNSETENV_H
|
||||
@@ -30,6 +30,22 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
|
||||
libc.src.string.strcmp
|
||||
)
|
||||
|
||||
add_integration_test(
|
||||
unsetenv_test
|
||||
SUITE
|
||||
stdlib-integration-tests
|
||||
SRCS
|
||||
unsetenv_test.cpp
|
||||
DEPENDS
|
||||
libc.src.__support.alloc_checker
|
||||
libc.src.stdlib.getenv
|
||||
libc.src.stdlib.setenv
|
||||
libc.src.stdlib.unsetenv
|
||||
libc.src.string.memory_utils.inline_memcpy
|
||||
libc.src.string.strcmp
|
||||
libc.src.unistd.environ
|
||||
)
|
||||
|
||||
add_integration_test(
|
||||
abort_test
|
||||
SUITE
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 unsetenv.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "src/__support/alloc-checker.h"
|
||||
#include "src/stdlib/environ_internal.h"
|
||||
#include "src/stdlib/getenv.h"
|
||||
#include "src/stdlib/setenv.h"
|
||||
#include "src/stdlib/unsetenv.h"
|
||||
#include "src/string/memory_utils/inline_memcpy.h"
|
||||
#include "src/string/strcmp.h"
|
||||
#include "src/unistd/environ.h"
|
||||
|
||||
#include "test/IntegrationTest/test.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
namespace LIBC_NAMESPACE {
|
||||
|
||||
TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
|
||||
[[maybe_unused]] char **envp) {
|
||||
// Test: Remove a variable set by setenv
|
||||
{
|
||||
ASSERT_EQ(setenv("UNSET_VAR", "value", 1), 0);
|
||||
ASSERT_TRUE(getenv("UNSET_VAR") != nullptr);
|
||||
|
||||
ASSERT_EQ(unsetenv("UNSET_VAR"), 0);
|
||||
ASSERT_TRUE(getenv("UNSET_VAR") == nullptr);
|
||||
}
|
||||
|
||||
// Test: Unset non-existent variable succeeds
|
||||
{
|
||||
ASSERT_EQ(unsetenv("DOES_NOT_EXIST"), 0);
|
||||
}
|
||||
|
||||
// Test: Empty name sets errno to EINVAL
|
||||
{
|
||||
errno = 0;
|
||||
ASSERT_EQ(unsetenv(""), -1);
|
||||
ASSERT_ERRNO_EQ(EINVAL);
|
||||
}
|
||||
|
||||
// Test: Name with '=' sets errno to EINVAL
|
||||
{
|
||||
errno = 0;
|
||||
ASSERT_EQ(unsetenv("BAD=NAME"), -1);
|
||||
ASSERT_ERRNO_EQ(EINVAL);
|
||||
}
|
||||
|
||||
// Test: Unset then re-set
|
||||
{
|
||||
ASSERT_EQ(setenv("REUSE_VAR", "first", 1), 0);
|
||||
ASSERT_EQ(strcmp(getenv("REUSE_VAR"), "first"), 0);
|
||||
|
||||
ASSERT_EQ(unsetenv("REUSE_VAR"), 0);
|
||||
ASSERT_TRUE(getenv("REUSE_VAR") == nullptr);
|
||||
|
||||
ASSERT_EQ(setenv("REUSE_VAR", "second", 1), 0);
|
||||
ASSERT_EQ(strcmp(getenv("REUSE_VAR"), "second"), 0);
|
||||
}
|
||||
|
||||
// Test: Unset multiple variables
|
||||
{
|
||||
ASSERT_EQ(setenv("MULTI_A", "a", 1), 0);
|
||||
ASSERT_EQ(setenv("MULTI_B", "b", 1), 0);
|
||||
ASSERT_EQ(setenv("MULTI_C", "c", 1), 0);
|
||||
|
||||
ASSERT_EQ(unsetenv("MULTI_B"), 0);
|
||||
|
||||
ASSERT_TRUE(getenv("MULTI_A") != nullptr);
|
||||
ASSERT_TRUE(getenv("MULTI_B") == nullptr);
|
||||
ASSERT_TRUE(getenv("MULTI_C") != nullptr);
|
||||
|
||||
ASSERT_EQ(strcmp(getenv("MULTI_A"), "a"), 0);
|
||||
ASSERT_EQ(strcmp(getenv("MULTI_C"), "c"), 0);
|
||||
}
|
||||
|
||||
// Test: Unset same variable twice is harmless
|
||||
{
|
||||
ASSERT_EQ(setenv("DOUBLE_UNSET", "val", 1), 0);
|
||||
ASSERT_EQ(unsetenv("DOUBLE_UNSET"), 0);
|
||||
ASSERT_EQ(unsetenv("DOUBLE_UNSET"), 0);
|
||||
ASSERT_TRUE(getenv("DOUBLE_UNSET") == nullptr);
|
||||
}
|
||||
|
||||
// Test: environ is updated and does NOT contain the variable
|
||||
{
|
||||
ASSERT_EQ(setenv("ENV_CHECK", "val", 1), 0);
|
||||
// Verify it is in environ
|
||||
bool found = false;
|
||||
for (char **env = environ; *env != nullptr; ++env) {
|
||||
if (strcmp(*env, "ENV_CHECK=val") == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found);
|
||||
|
||||
// Now unset it
|
||||
ASSERT_EQ(unsetenv("ENV_CHECK"), 0);
|
||||
|
||||
// Verify it is NOT in environ
|
||||
found = false;
|
||||
for (char **env = environ; *env != nullptr; ++env) {
|
||||
bool match = true;
|
||||
const char *prefix = "ENV_CHECK=";
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
if ((*env)[i] == '\0' || (*env)[i] != prefix[i]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_FALSE(found);
|
||||
}
|
||||
|
||||
// Test: Unset removes all duplicate instances of a variable
|
||||
{
|
||||
ASSERT_EQ(setenv("DUP_VAL1", "1", 1), 0);
|
||||
ASSERT_EQ(setenv("DUP_VAL2", "2", 1), 0);
|
||||
|
||||
AllocChecker ac;
|
||||
char *d1 = new (ac) char[14];
|
||||
ASSERT_TRUE(ac);
|
||||
const char *s1 = "DUP_VAR=first";
|
||||
inline_memcpy(d1, s1, 14);
|
||||
|
||||
char *d2 = new (ac) char[15];
|
||||
ASSERT_TRUE(ac);
|
||||
const char *s2 = "DUP_VAR=second";
|
||||
inline_memcpy(d2, s2, 15);
|
||||
|
||||
internal::EnvironmentManager &mgr =
|
||||
internal::EnvironmentManager::get_instance();
|
||||
char **env_array = mgr.begin();
|
||||
size_t count = mgr.size();
|
||||
|
||||
size_t idx1 = count;
|
||||
size_t idx2 = count;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
cpp::string_view curr(env_array[i]);
|
||||
if (curr.starts_with("DUP_VAL1="))
|
||||
idx1 = i;
|
||||
if (curr.starts_with("DUP_VAL2="))
|
||||
idx2 = i;
|
||||
}
|
||||
ASSERT_TRUE(idx1 < count);
|
||||
ASSERT_TRUE(idx2 < count);
|
||||
|
||||
delete[] env_array[idx1];
|
||||
delete[] env_array[idx2];
|
||||
|
||||
env_array[idx1] = d1;
|
||||
env_array[idx2] = d2;
|
||||
|
||||
ASSERT_EQ(unsetenv("DUP_VAR"), 0);
|
||||
ASSERT_TRUE(getenv("DUP_VAR") == nullptr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE
|
||||
Reference in new issue
Block a user