Introduce getenv to LLVM libc

Add support for getenv as defined by the Open Group's "System Interface &
 Header" in https://pubs.opengroup.org/onlinepubs/7908799/xsh/getenv.html

getenv requires a standard way of accessing the environment,
so a pointer to the environment is added to the startup in crt1.
Consquently, this function is not usable on top of other libcs.

Added starts_with method to StringView. getenv function uses it.

Co-authored-by: Jeff Bailey <jeffbailey@google.com>

Reviewed By: sivachandra, rtenneti

Differential Revision: https://reviews.llvm.org/D119403
This commit is contained in:
Raman Tenneti 2022-02-13 17:57:28 -08:00
parent 07b9a44515
commit f2a7f83595
12 changed files with 186 additions and 3 deletions

View File

@ -33,8 +33,13 @@ struct AppProperties {
// 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();

View File

@ -72,6 +72,7 @@ set(TARGET_LIBC_ENTRYPOINTS
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

View File

@ -29,9 +29,6 @@ static constexpr long mmapSyscallNumber = SYS_mmap;
#error "Target platform does not have SYS_mmap or SYS_mmap2 defined"
#endif
// TODO: Declare var an extern var in config/linux/app.h so that other
// libc functions can make use of the application wide information. For
// example, mmap can pick up the page size from here.
AppProperties app;
// TODO: The function is x86_64 specific. Move it to config/linux/app.h
@ -109,6 +106,7 @@ extern "C" void _start() {
// 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;

View File

@ -265,6 +265,20 @@ def POSIX : StandardSpec<"POSIX"> {
]
>;
HeaderSpec StdLib = HeaderSpec<
"stdlib.h",
[], // Macros
[], // Types
[], // Enumerations
[
FunctionSpec<
"getenv",
RetValSpec<CharPtr>,
[ArgSpec<ConstCharPtr>]
>,
]
>;
HeaderSpec String = HeaderSpec<
"string.h",
[
@ -356,6 +370,7 @@ def POSIX : StandardSpec<"POSIX"> {
Errno,
FCntl,
Signal,
StdLib,
SysMMan,
SysStat,
UniStd,

View File

@ -81,6 +81,17 @@ public:
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)

View File

@ -38,6 +38,17 @@ add_entrypoint_object(
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

View File

@ -0,0 +1,42 @@
//===-- 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 <stddef.h> // For size_t.
namespace __llvm_libc {
LLVM_LIBC_FUNCTION(char *, getenv, (const char *name)) {
char **env_ptr = reinterpret_cast<char **>(__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;
if (cur[env_var_name.size()] != '=')
continue;
return const_cast<char *>(
cur.remove_prefix(env_var_name.size() + 1).data());
}
return nullptr;
}
} // namespace __llvm_libc

18
libc/src/stdlib/getenv.h Normal file
View File

@ -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

View File

@ -43,6 +43,20 @@ add_loader_test(
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(

View File

@ -0,0 +1,45 @@
//===-- 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<char *>(nullptr)) &&
(rhs != static_cast<char *>(nullptr))) ||
((lhs != static_cast<char *>(nullptr)) &&
(rhs == static_cast<char *>(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<char *>(nullptr)));
ASSERT_TRUE(my_streq(__llvm_libc::getenv("="), static_cast<char *>(nullptr)));
ASSERT_TRUE(my_streq(__llvm_libc::getenv("MISSING ENV VARIABLE"),
static_cast<char *>(nullptr)));
ASSERT_FALSE(
my_streq(__llvm_libc::getenv("PATH"), static_cast<char *>(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"));
ASSERT_TRUE(
my_streq(__llvm_libc::getenv("FRANC"), static_cast<char *>(nullptr)));
ASSERT_TRUE(
my_streq(__llvm_libc::getenv("FRANCE1"), static_cast<char *>(nullptr)));
return 0;
}

View File

@ -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

View File

@ -45,6 +45,19 @@ TEST(LlvmLibcStringViewTest, Equals) {
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");