diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -68,6 +68,7 @@ libc.src.stdlib.atoll libc.src.stdlib.bsearch libc.src.stdlib.div + libc.src.stdlib.getenv libc.src.stdlib.labs libc.src.stdlib.ldiv libc.src.stdlib.llabs diff --git a/libc/config/linux/app.h b/libc/config/linux/app.h --- a/libc/config/linux/app.h +++ b/libc/config/linux/app.h @@ -33,8 +33,13 @@ // The properties of an application's TLS. TLS tls; + + // Environment data. + uint64_t *envPtr; }; +extern AppProperties app; + // Creates and initializes the TLS area for the current thread. Should not // be called before app.tls has been initialized. void initTLS(); diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -68,6 +68,7 @@ libc.src.stdlib.atoll libc.src.stdlib.bsearch libc.src.stdlib.div + libc.src.stdlib.getenv libc.src.stdlib.labs libc.src.stdlib.ldiv libc.src.stdlib.llabs diff --git a/libc/loader/linux/x86_64/start.cpp b/libc/loader/linux/x86_64/start.cpp --- a/libc/loader/linux/x86_64/start.cpp +++ b/libc/loader/linux/x86_64/start.cpp @@ -109,6 +109,7 @@ // value. We step over it (the "+ 1" below) to get to the env values. uint64_t *env_ptr = args->argv + args->argc + 1; uint64_t *env_end_marker = env_ptr; + app.envPtr = env_ptr; while (*env_end_marker) ++env_end_marker; diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -265,6 +265,20 @@ ] >; + HeaderSpec String = HeaderSpec< + "stdlib.h", + [], // Macros + [], // Types + [], // Enumerations + [ + FunctionSpec< + "getenv", + RetValSpec, + [ArgSpec] + >, + ] + >; + HeaderSpec String = HeaderSpec< "string.h", [ diff --git a/libc/src/__support/CPP/StringView.h b/libc/src/__support/CPP/StringView.h --- a/libc/src/__support/CPP/StringView.h +++ b/libc/src/__support/CPP/StringView.h @@ -81,6 +81,17 @@ return remove_prefix(PrefixLen).remove_suffix(SuffixLen); } + // Check if this string starts with the given \p Prefix. + bool starts_with(StringView Prefix) const { + if (Len < Prefix.Len) + return false; + for (size_t I = 0; I < Prefix.Len; ++I) { + if (Data[I] != Prefix.Data[I]) + return false; + } + return true; + } + // An equivalent method is not available in std::string_view. bool equals(StringView Other) const { if (Len != Other.Len) diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt --- a/libc/src/stdlib/CMakeLists.txt +++ b/libc/src/stdlib/CMakeLists.txt @@ -38,6 +38,17 @@ libc.src.__support.str_to_integer ) +add_entrypoint_object( + getenv + SRCS + getenv.cpp + HDRS + getenv.h + DEPENDS + libc.config.linux.app_h + libc.src.string.strncmp +) + add_entrypoint_object( strtof SRCS diff --git a/libc/src/stdlib/getenv.h b/libc/src/stdlib/getenv.h new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/getenv.h @@ -0,0 +1,18 @@ +//===-- Implementation header for getenv --------------------------------*-===// +// +// 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_GETENV_H +#define LLVM_LIBC_SRC_STDLIB_GETENV_H + +namespace __llvm_libc { + +char *getenv(const char *name); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDLIB_GETENV_H diff --git a/libc/src/stdlib/getenv.cpp b/libc/src/stdlib/getenv.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/getenv.cpp @@ -0,0 +1,43 @@ +//===-- Implementation of getenv ------------------------------------------===// +// +// 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 "config/linux/app.h" +#include "src/__support/CPP/StringView.h" +#include "src/__support/common.h" + +#include // For size_t. + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(char *, getenv, (const char *name)) { + char **env_ptr = reinterpret_cast(__llvm_libc::app.envPtr); + + if (name == nullptr || env_ptr == nullptr) + return nullptr; + + __llvm_libc::cpp::StringView env_var_name(name); + if (env_var_name.size() == 0) + return nullptr; + for (char **env = env_ptr; *env != nullptr; env++) { + __llvm_libc::cpp::StringView cur(*env); + if (!cur.starts_with(env_var_name)) + continue; + + // Check for PATH=, vs PATH1=, PATH2=, etc... + if (cur[env_var_name.size()] != '=') + continue; + + return const_cast( + cur.remove_prefix(env_var_name.size() + 1).data()); + } + + return nullptr; +} + +} // namespace __llvm_libc diff --git a/libc/test/loader/linux/CMakeLists.txt b/libc/test/loader/linux/CMakeLists.txt --- a/libc/test/loader/linux/CMakeLists.txt +++ b/libc/test/loader/linux/CMakeLists.txt @@ -43,6 +43,20 @@ libc.loader.linux.crt1 ) +add_loader_test( + getenv_test + SRC + getenv_test.cpp + DEPENDS + .loader_test + libc.loader.linux.crt1 + libc.src.stdlib.getenv + ENV + FRANCE=Paris + GERMANY=Berlin +) + + # TODO: Disableing this test temporarily to investigate why gold fails to link # and produce an executable for this test. Test works all fine with ld.bfd. #add_loader_test( diff --git a/libc/test/loader/linux/getenv_test.cpp b/libc/test/loader/linux/getenv_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/loader/linux/getenv_test.cpp @@ -0,0 +1,41 @@ +//===-- Unittests for getenv ----------------------------------------------===// +// +// 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 "loader_test.h" +#include "src/stdlib/getenv.h" + +static bool my_streq(const char *lhs, const char *rhs) { + if (lhs == rhs) + return true; + if (((lhs == static_cast(nullptr)) && + (rhs != static_cast(nullptr))) || + ((lhs != static_cast(nullptr)) && + (rhs == static_cast(nullptr)))) { + return false; + } + const char *l, *r; + for (l = lhs, r = rhs; *l != '\0' && *r != '\0'; ++l, ++r) + if (*l != *r) + return false; + + return *l == '\0' && *r == '\0'; +} + +int main(int argc, char **argv, char **envp) { + ASSERT_TRUE(my_streq(__llvm_libc::getenv(""), static_cast(nullptr))); + ASSERT_TRUE(my_streq(__llvm_libc::getenv("="), static_cast(nullptr))); + ASSERT_TRUE(my_streq(__llvm_libc::getenv("MISSING ENV VARIABLE"), + static_cast(nullptr))); + ASSERT_FALSE( + my_streq(__llvm_libc::getenv("PATH"), static_cast(nullptr))); + ASSERT_TRUE(my_streq(__llvm_libc::getenv("FRANCE"), "Paris")); + ASSERT_FALSE(my_streq(__llvm_libc::getenv("FRANCE"), "Berlin")); + ASSERT_TRUE(my_streq(__llvm_libc::getenv("GERMANY"), "Berlin")); + + return 0; +} diff --git a/libc/test/loader/linux/loader_test.h b/libc/test/loader/linux/loader_test.h --- a/libc/test/loader/linux/loader_test.h +++ b/libc/test/loader/linux/loader_test.h @@ -21,7 +21,17 @@ __llvm_libc::quick_exit(127); \ } +#define __CHECK_NE(file, line, val, should_exit) \ + if ((val)) { \ + __llvm_libc::write_to_stderr(file ":" __AS_STRING( \ + line) ": Expected '" #val "' to be false, but is true\n"); \ + if (should_exit) \ + __llvm_libc::quick_exit(127); \ + } + #define EXPECT_TRUE(val) __CHECK(__FILE__, __LINE__, val, false) #define ASSERT_TRUE(val) __CHECK(__FILE__, __LINE__, val, true) +#define EXPECT_FALSE(val) __CHECK_NE(__FILE__, __LINE__, val, false) +#define ASSERT_FALSE(val) __CHECK_NE(__FILE__, __LINE__, val, true) #endif // LLVM_LIBC_TEST_LOADER_LINUX_LOADER_TEST_H diff --git a/libc/test/utils/CPP/stringview_test.cpp b/libc/test/utils/CPP/stringview_test.cpp --- a/libc/test/utils/CPP/stringview_test.cpp +++ b/libc/test/utils/CPP/stringview_test.cpp @@ -45,6 +45,19 @@ ASSERT_FALSE(v.equals(__llvm_libc::cpp::StringView("abcde"))); } +TEST(LlvmLibcStringViewTest, startsWith) { + __llvm_libc::cpp::StringView v("abc"); + ASSERT_TRUE(v.starts_with(__llvm_libc::cpp::StringView("a"))); + ASSERT_TRUE(v.starts_with(__llvm_libc::cpp::StringView("ab"))); + ASSERT_TRUE(v.starts_with(__llvm_libc::cpp::StringView("abc"))); + ASSERT_TRUE(v.starts_with(__llvm_libc::cpp::StringView())); + ASSERT_TRUE(v.starts_with(__llvm_libc::cpp::StringView(""))); + ASSERT_FALSE(v.starts_with(__llvm_libc::cpp::StringView("123"))); + ASSERT_FALSE(v.starts_with(__llvm_libc::cpp::StringView("abd"))); + ASSERT_FALSE(v.starts_with(__llvm_libc::cpp::StringView("aaa"))); + ASSERT_FALSE(v.starts_with(__llvm_libc::cpp::StringView("abcde"))); +} + TEST(LlvmLibcStringViewTest, RemovePrefix) { __llvm_libc::cpp::StringView v("123456789");