From ce4026523a7bf1994a980c269ca99f385bb0c8f5 Mon Sep 17 00:00:00 2001 From: "Peter D. Barnes, Jr" Date: Fri, 20 Jan 2023 14:06:57 -0800 Subject: [PATCH] core: enable environment variable for Windows --- src/core/model/environment-variable.cc | 81 ++++++++++++++++++- src/core/model/environment-variable.h | 22 +++++ .../test/environment-variable-test-suite.cc | 23 ++++-- 3 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/core/model/environment-variable.cc b/src/core/model/environment-variable.cc index 997ced050..84e74589f 100644 --- a/src/core/model/environment-variable.cc +++ b/src/core/model/environment-variable.cc @@ -21,15 +21,78 @@ #include "ns3/string.h" -#include // getenv +#include // std::getenv #include // strlen #include // clog +#include // Global functions setenv, unsetenv /** * \file * \ingroup core-environ * Class EnvironmentVariable implementation. */ + +#ifdef __WIN32__ +#include + +/** + * Windows implementation of the POSIX function `setenv()` + * + * \param [in] var_name The environment variable to set. + * Must not be a null-pointer, and must not contain `=`. + * \param [in] new_value The new value to set \p var_name to. + * Must not by a null pointer or empty. + * \param [in] change_flag Must be non-zero to actually change the environment. + * \returns 0 if successful, -1 if failed. + */ +int +setenv(const char* var_name, const char* new_value, int change_flag) +{ + std::string variable{var_name}; + std::string value{new_value}; + + // In case arguments are null pointers, return invalid error + // Windows does not accept empty environment variables + if (variable.empty() || value.empty()) + { + errno = EINVAL; + return -1; + } + + // Posix does not accept '=', so impose that here + if (variable.find('=') != std::string::npos) + { + errno = EINVAL; + return -1; + } + + // Change flag equals to zero preserves a pre-existing value + if (change_flag == 0) + { + char* old_value = std::getenv(var_name); + if (old_value != nullptr) + { + return 0; + } + } + + // Write new value for the environment variable + return _putenv_s(var_name, new_value); +} + +/** + * Windows implementation of the POSIX function `unsetenv()` + * \param [in] var_name The environment variable to unset and remove from the environment. + * \returns 0 if successful, -1 if failed. + */ +int +unsetenv(const char* var_name) +{ + return _putenv_s(var_name, ""); +} + +#endif // __WIN32__ + namespace ns3 { @@ -114,6 +177,22 @@ EnvironmentVariable::Get(const std::string& envvar, return dict->Get(key); } +/* static */ +bool +EnvironmentVariable::Set(const std::string& variable, const std::string& value) +{ + int fail = setenv(variable.c_str(), value.c_str(), 1); + return !fail; +} + +/* static */ +bool +EnvironmentVariable::Unset(const std::string& variable) +{ + int fail = unsetenv(variable.c_str()); + return !fail; +} + EnvironmentVariable::KeyFoundType EnvironmentVariable::Dictionary::Get(const std::string& key) const { diff --git a/src/core/model/environment-variable.h b/src/core/model/environment-variable.h index 9efff46ae..607ee15c6 100644 --- a/src/core/model/environment-variable.h +++ b/src/core/model/environment-variable.h @@ -167,6 +167,28 @@ class EnvironmentVariable }; // class Dictionary + /** + * Set an environment variable. + * + * To set a variable to the empty string use `Set(variable, "")`. + * Note: empty environment variables are not portable (unsupported on Windows). + * + * \param [in] variable The environment variable to set. Note this may not contain the `=` + * character. \param [in] value The value to set. Note this must not be an empty string on + * Windows. \returns \c true if the variable was set successfully + */ + static bool Set(const std::string& variable, const std::string& value); + + /** + * Unset an environment variable. + * This removes the variable from the environment. + * To set a variable to the empty string use `Set(variable, "")`. + * + * \param [in] variable The environment variable to unset. Note this may not contain the `=` + * character. \returns \c true if the variable was unset successfully. + */ + static bool Unset(const std::string& variable); + /** * \name Singleton * diff --git a/src/core/test/environment-variable-test-suite.cc b/src/core/test/environment-variable-test-suite.cc index da92566e5..2efcce15c 100644 --- a/src/core/test/environment-variable-test-suite.cc +++ b/src/core/test/environment-variable-test-suite.cc @@ -20,8 +20,7 @@ #include "ns3/environment-variable.h" #include "ns3/test.h" -#include // setenv, unsetenv -#include // getenv +#include // getenv namespace ns3 { @@ -52,7 +51,7 @@ class EnvVarTestCase : public TestCase EnvVarTestCase(); /** Destructor */ - ~EnvVarTestCase() override = default; + ~EnvVarTestCase() override; private: /** Run the tests */ @@ -129,12 +128,17 @@ EnvVarTestCase::EnvVarTestCase() { } +EnvVarTestCase::~EnvVarTestCase() +{ + UnsetVariable("destructor"); +} + void EnvVarTestCase::SetVariable(const std::string& where, const std::string& value) { EnvironmentVariable::Clear(); - int ok = setenv(m_variable.c_str(), value.c_str(), 1); - NS_TEST_EXPECT_MSG_EQ(ok, 0, where << ": failed to set variable"); + bool ok = EnvironmentVariable::Set(m_variable, value); + NS_TEST_EXPECT_MSG_EQ(ok, true, where << ": failed to set variable"); // Double check const char* envCstr = std::getenv(m_variable.c_str()); @@ -146,8 +150,8 @@ void EnvVarTestCase::UnsetVariable(const std::string& where) { EnvironmentVariable::Clear(); - int ok = unsetenv(m_variable.c_str()); - NS_TEST_EXPECT_MSG_EQ(ok, 0, where << ": failed to unset variable"); + bool ok = EnvironmentVariable::Unset(where); + NS_TEST_EXPECT_MSG_EQ(ok, true, where << ": failed to unset variable"); } void @@ -250,7 +254,10 @@ EnvVarTestCase::DoRun() NS_TEST_EXPECT_MSG_EQ(value.empty(), true, "unset: non-empty value from unset variable"); // Variable set but empty +#ifndef __WIN32__ + // Windows doesn't support environment variables with empty values SetCheckAndGet("empty", "", {}, "", {true, ""}); +#endif // Key not in variable SetCheckAndGet("no-key", @@ -307,7 +314,7 @@ EnvVarTestCase::DoRun() /** * \ingroup environ-var-tests * - * TypeId test suites. + * Environment variable handling test suite. */ class EnvironmentVariableTestSuite : public TestSuite {