diff --git a/include/gz/utils/Environment.hh b/include/gz/utils/Environment.hh index 8ac06a4..9a7a062 100644 --- a/include/gz/utils/Environment.hh +++ b/include/gz/utils/Environment.hh @@ -22,6 +22,7 @@ #include #include +#include namespace gz { @@ -66,7 +67,48 @@ bool GZ_UTILS_VISIBLE setenv( /// \return True if the variable was unset or false otherwise. bool GZ_UTILS_VISIBLE unsetenv(const std::string &_name); -} +/// \brief Unset all environment variables +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `env` or `setenv` +/// +/// \return True if the environment was unset or false otherwise. +bool GZ_UTILS_VISIBLE clearenv(); + +/// \brief Type alias for a collection of environment variables +using EnvironmentMap = std::unordered_map; + +/// \brief Retrieve all current environment variables +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `setenv` or `unsetenv` +/// +/// \return A collection of current environment variables +EnvironmentMap GZ_UTILS_VISIBLE env(); + +/// \brief Set the environment variable '_name'. +/// +/// Note: On Windows setting an empty string (_value=="") +/// is the equivalent of unsetting the variable. +// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `env` or `unsetenv` +/// +/// \param[in] _vars Collection of environment variables to set +/// \return True if all variables were set or false otherwise. +bool GZ_UTILS_VISIBLE setenv(const EnvironmentMap &_vars); + +/// \brief Print the entire current environment to a string +/// +/// This prints each variable in the form KEY=VALUE\n +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `setenv` or `unsetenv` +/// +/// \return A string containing all environment variables +/// NOLINTNEXTLINE - This is incorrectly parsed as a global variable +std::string GZ_UTILS_VISIBLE printenv(); +} // namespace GZ_UTILS_VERSION_NAMESPACE } // namespace utils } // namespace gz diff --git a/src/Environment.cc b/src/Environment.cc index 63e0746..4d86e0b 100644 --- a/src/Environment.cc +++ b/src/Environment.cc @@ -17,8 +17,19 @@ #include +#include #include #include +#include + +#ifdef _WIN32 +#include +#include +#endif + +#ifndef _WIN32 +extern char ** environ; +#endif namespace gz { @@ -98,6 +109,89 @@ bool unsetenv(const std::string &_name) #endif return true; } + +///////////////////////////////////////////////// +bool clearenv() +{ + bool success = true; +#if __linux__ + if (0 != ::clearenv()) + { + success = false; + } +#else + // Windows and macOS don't have clearenv + // so iterate and clear one-by-one + for (const auto &[key, value] : env()) + { + success &= unsetenv(key); + } +#endif + return success; + +} + +///////////////////////////////////////////////// +EnvironmentMap env() +{ + EnvironmentMap ret; + + // Helper function to split KEY=VAL + auto split = [](const std::string &_inp) + { + return std::make_pair( + _inp.substr(0, _inp.find('=')), + _inp.substr(_inp.find('=') + 1)); + }; + + char **currentEnv = nullptr; +#ifdef _WIN32 + currentEnv = *__p__environ(); +#else + currentEnv = environ; +#endif + // In the case that clearenv() was just called + // currentEnv will be nullptr + if (currentEnv == nullptr) + return {}; + + for (; *currentEnv; ++currentEnv) + { + ret.emplace(split(*currentEnv)); + } + return ret; } + +///////////////////////////////////////////////// +bool setenv(const EnvironmentMap &_vars) +{ + bool success = true; + for (const auto &[key, value] : _vars) + { + success &= setenv(key, value); + } + return success; } + +///////////////////////////////////////////////// +std::string printenv() +{ + std::string ret; + // Variables are in an unordered_map as we generally don't + // care, but for printing sort for consistent display + auto currentEnv = env(); + auto sorted = std::vector>( + currentEnv.begin(), currentEnv.end()); + std::sort(sorted.begin(), sorted.end()); + for (const auto &[key, value] : sorted) + { + ret.append(key); + ret.append("="); + ret.append(value); + ret.append("\n"); + } + return ret; } +} // namespace GZ_UTILS_VERSION_NAMESPACE +} // namespace utils +} // namespace gz diff --git a/src/Environment_TEST.cc b/src/Environment_TEST.cc index 8168153..6d09923 100644 --- a/src/Environment_TEST.cc +++ b/src/Environment_TEST.cc @@ -26,6 +26,8 @@ using namespace gz; ///////////////////////////////////////////////// TEST(Environment, emptyENV) { + gz::utils::clearenv(); + std::string var; EXPECT_FALSE(utils::env("!!SHOULD_NOT_EXIST!!", var)); EXPECT_TRUE(var.empty()); @@ -34,6 +36,8 @@ TEST(Environment, emptyENV) ///////////////////////////////////////////////// TEST(Environment, envSet) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_SET"; ASSERT_TRUE(utils::setenv(key, "VALUE")); @@ -67,6 +71,8 @@ TEST(Environment, envSet) ///////////////////////////////////////////////// TEST(Environment, envUnset) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_UNSET"; ASSERT_TRUE(utils::unsetenv(key)); @@ -94,8 +100,10 @@ TEST(Environment, envUnset) } ///////////////////////////////////////////////// -TEST(Util_TEST, envSetEmpty) +TEST(Environment, envSetEmpty) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_SET_EMPTY"; ASSERT_TRUE(utils::setenv(key, "")); @@ -133,3 +141,37 @@ TEST(Util_TEST, envSetEmpty) } ASSERT_TRUE(utils::unsetenv(key)); } + +///////////////////////////////////////////////// +TEST(Environment, envGetCollection) +{ + gz::utils::clearenv(); + auto currentEnv = gz::utils::env(); + EXPECT_EQ(currentEnv.size(), 0); + + ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL")); + + currentEnv = gz::utils::env(); + EXPECT_EQ(currentEnv.size(), 3); + + EXPECT_EQ(currentEnv["GZ_FOO_KEY"], "GZ_FOO_VAL"); + EXPECT_EQ(currentEnv["GZ_BAR_KEY"], "GZ_BAR_VAL"); + EXPECT_EQ(currentEnv["GZ_BAZ_KEY"], "GZ_BAZ_VAL"); +} + +///////////////////////////////////////////////// +TEST(Environment, printenv) +{ + gz::utils::clearenv(); + EXPECT_EQ(gz::utils::printenv(), ""); + + ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL")); + + // Always returned in sorted order + EXPECT_EQ(gz::utils::printenv(), + "GZ_BAR_KEY=GZ_BAR_VAL\nGZ_BAZ_KEY=GZ_BAZ_VAL\nGZ_FOO_KEY=GZ_FOO_VAL\n"); +}