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(); + +}