From b9dc2d7ebf0f6cac79dd9fa5b69ec718e899d2d2 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 26 Feb 2024 11:23:14 +0000 Subject: [PATCH] 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. --- Userland/Libraries/LibCore/CMakeLists.txt | 1 + Userland/Libraries/LibCore/Environment.cpp | 169 +++++++++++++++++++++ Userland/Libraries/LibCore/Environment.h | 81 ++++++++++ 3 files changed, 251 insertions(+) create mode 100644 Userland/Libraries/LibCore/Environment.cpp create mode 100644 Userland/Libraries/LibCore/Environment.h diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt index d5b63f2805..095f4da62e 100644 --- a/Userland/Libraries/LibCore/CMakeLists.txt +++ b/Userland/Libraries/LibCore/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES DirectoryEntry.cpp DirIterator.cpp ElapsedTimer.cpp + Environment.cpp Event.cpp EventLoop.cpp EventLoopImplementation.cpp diff --git a/Userland/Libraries/LibCore/Environment.cpp b/Userland/Libraries/LibCore/Environment.cpp new file mode 100644 index 0000000000..b23b054ee9 --- /dev/null +++ b/Userland/Libraries/LibCore/Environment.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2021-2022, Andreas Kling + * Copyright (c) 2021-2022, Kenneth Myhra + * Copyright (c) 2021-2024, Sam Atkins + * Copyright (c) 2022, Matthias Zimmerman + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Environment.h" +#include + +#if defined(AK_OS_MACOS) +# include +#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 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 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 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 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 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 {}; +} + +} diff --git a/Userland/Libraries/LibCore/Environment.h b/Userland/Libraries/LibCore/Environment.h new file mode 100644 index 0000000000..92d5227f65 --- /dev/null +++ b/Userland/Libraries/LibCore/Environment.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +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 get(StringView name, SecureOnly = SecureOnly::No); + +enum class Overwrite { + No, + Yes, +}; +ErrorOr set(StringView name, StringView value, Overwrite); +ErrorOr unset(StringView name); +ErrorOr put(StringView env); + +ErrorOr clear(); + +}