LibCore: Introduce Core::Environment wrapper

Core::System already had some wrappers for *env() functions, which I've
copied over. But 1) the set of functions there was incomplete, and 2)
the environment feels like an object in its own right, so let's treat it
as one. :^)

Also add `Core::Environment::has(StringView)` for situations where we
just care if a variable is defined.
This commit is contained in:
Sam Atkins 2024-02-26 11:23:14 +00:00 committed by Sam Atkins
parent 55dc69625a
commit b9dc2d7ebf
Notes: sideshowbarker 2024-07-19 01:59:31 +09:00
3 changed files with 251 additions and 0 deletions

View File

@ -8,6 +8,7 @@ set(SOURCES
DirectoryEntry.cpp
DirIterator.cpp
ElapsedTimer.cpp
Environment.cpp
Event.cpp
EventLoop.cpp
EventLoopImplementation.cpp

View File

@ -0,0 +1,169 @@
/*
* Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2022, Kenneth Myhra <kennethmyhra@serenityos.org>
* Copyright (c) 2021-2024, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022, Matthias Zimmerman <matthias291999@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Environment.h"
#include <AK/ByteString.h>
#if defined(AK_OS_MACOS)
# include <crt_externs.h>
#else
extern char** environ;
#endif
namespace Core::Environment {
char** raw_environ()
{
#if defined(AK_OS_MACOS)
return *_NSGetEnviron();
#else
return environ;
#endif
}
Entry Entry::from_chars(char const* input)
{
return Entry::from_string({ input, strlen(input) });
}
Entry Entry::from_string(StringView input)
{
auto split_index = input.find('=');
if (!split_index.has_value()) {
return Entry {
.full_entry = input,
.name = input,
.value = ""sv,
};
}
return Entry {
.full_entry = input,
.name = input.substring_view(0, *split_index),
.value = input.substring_view(*split_index + 1),
};
}
EntryIterator EntryIterator::begin()
{
return EntryIterator(0);
}
EntryIterator EntryIterator::end()
{
auto environment = raw_environ();
size_t env_count = 0;
for (size_t i = 0; environment[i]; ++i)
++env_count;
return EntryIterator(env_count);
}
EntryIterator entries()
{
return EntryIterator::begin();
}
size_t size()
{
auto environment = raw_environ();
size_t environ_size = 0;
while (environment[environ_size])
++environ_size;
return environ_size;
}
bool has(StringView name)
{
return get(name).has_value();
}
Optional<StringView> get(StringView name, [[maybe_unused]] SecureOnly secure)
{
StringBuilder builder;
builder.append(name);
builder.append('\0');
// Note the explicit null terminators above.
#if defined(AK_OS_MACOS)
char* result = ::getenv(builder.string_view().characters_without_null_termination());
#else
char* result;
if (secure == SecureOnly::Yes) {
result = ::secure_getenv(builder.string_view().characters_without_null_termination());
} else {
result = ::getenv(builder.string_view().characters_without_null_termination());
}
#endif
if (result)
return StringView { result, strlen(result) };
return {};
}
ErrorOr<void> set(StringView name, StringView value, Overwrite overwrite)
{
auto builder = TRY(StringBuilder::create());
TRY(builder.try_append(name));
TRY(builder.try_append('\0'));
TRY(builder.try_append(value));
TRY(builder.try_append('\0'));
// Note the explicit null terminators above.
auto c_name = builder.string_view().characters_without_null_termination();
auto c_value = c_name + name.length() + 1;
auto rc = ::setenv(c_name, c_value, overwrite == Overwrite::Yes ? 1 : 0);
if (rc < 0)
return Error::from_errno(errno);
return {};
}
ErrorOr<void> unset(StringView name)
{
auto builder = TRY(StringBuilder::create());
TRY(builder.try_append(name));
TRY(builder.try_append('\0'));
// Note the explicit null terminator above.
auto rc = ::unsetenv(builder.string_view().characters_without_null_termination());
if (rc < 0)
return Error::from_errno(errno);
return {};
}
ErrorOr<void> put(StringView env)
{
#if defined(AK_OS_SERENITY)
auto rc = ::serenity_putenv(env.characters_without_null_termination(), env.length());
#else
// Leak somewhat unavoidable here due to the putenv API.
auto leaked_new_env = strndup(env.characters_without_null_termination(), env.length());
auto rc = ::putenv(leaked_new_env);
#endif
if (rc < 0)
return Error::from_errno(errno);
return {};
}
ErrorOr<void> clear()
{
#if defined(AK_OS_MACOS)
auto environment = raw_environ();
for (size_t environ_size = 0; environment[environ_size]; ++environ_size) {
environment[environ_size] = NULL;
}
#else
auto rc = ::clearenv();
if (rc < 0)
return Error::from_errno(errno);
#endif
return {};
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <AK/Function.h>
#include <AK/IterationDecision.h>
#include <AK/Optional.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
namespace Core::Environment {
char** raw_environ();
struct Entry {
StringView full_entry;
StringView name;
StringView value;
static Entry from_chars(char const* input);
static Entry from_string(StringView input);
};
class EntryIterator {
public:
constexpr bool operator==(EntryIterator other) const { return m_index == other.m_index; }
constexpr bool operator!=(EntryIterator other) const { return m_index != other.m_index; }
constexpr EntryIterator operator++()
{
++m_index;
return *this;
}
constexpr EntryIterator operator++(int)
{
auto result = *this;
++m_index;
return result;
}
Entry operator*() { return Entry::from_chars(raw_environ()[m_index]); }
static EntryIterator begin();
static EntryIterator end();
private:
explicit constexpr EntryIterator(size_t index)
: m_index(index)
{
}
size_t m_index { 0 };
};
EntryIterator entries();
size_t size();
bool has(StringView name);
enum class SecureOnly {
No,
Yes,
};
Optional<StringView> get(StringView name, SecureOnly = SecureOnly::No);
enum class Overwrite {
No,
Yes,
};
ErrorOr<void> set(StringView name, StringView value, Overwrite);
ErrorOr<void> unset(StringView name);
ErrorOr<void> put(StringView env);
ErrorOr<void> clear();
}