I haven’t publish code quite a long time. Let’s get back to the roots.
The putenv()
function has a quite unpleasant property. It doesn’t take a
copy of its argument using it directly by pointer. Moreover, the argument
is a non-const pointer. So, it is impossible to pass automatic or
temporary objects, and even passing constant strings (which are persistent
by default) we need to cast them to non-const ones, which is not quite right.
All of this encourages such nonsense like malloc
or strdup
.
So, there is a class below called EnvironmentVariablesManager
. This is a
simple wrapper around putenv()
and getenv()
proving persistent storage
for the values passed to putenv()
(in the form of “name=value”) .
The class is designed to work on Linux, AIX, HP-UX, Solaris and Windows.
EnvironmentVariablesManager.h file:
#ifndef ENVIRONMENT_VARIABLE_MANAGER_H #define ENVIRONMENT_VARIABLE_MANAGER_H #include <string> #include <vector> #include <map> class EnvironmentVariablesManager { public: typedef std::vector<char> VariableContainer; typedef std::map<std::string, VariableContainer> Variables; EnvironmentVariablesManager() {} void put(const std::string& name, const std::string& value); std::string get(const std::string& name) const; void del(const std::string& name); static void PutOSVariable(char* value); static std::string GetOSVariable(const char* name); static bool IsOSVariableSet(const char* name); private: VariableContainer PairToContainer(const std::string& name, const std::string& value) const; Variables vars_; // This class is not copiable. EnvironmentVariablesManager(const EnvironmentVariablesManager&); void operator=(const EnvironmentVariablesManager&); }; #endif
EnvironmentVariablesManager.cpp file:
#include "EnvironmentVariablesManager.h" #ifdef WINDOWS #include <windows.h> #else #include <unistd.h> #endif #include <vector> #include <map> #include <string> #include <iterator> #include <algorithm> #include <cassert> void EnvironmentVariablesManager::put(const std::string& name, const std::string& value) { const VariableContainer pair = PairToContainer(name, value); const std::pair<Variables::iterator, bool> inserted = vars_.insert(std::make_pair(name, pair)); if (!inserted.second) inserted.first->second = pair; char* data = &(inserted.first->second[0]); PutOSVariable(data); } std::string EnvironmentVariablesManager::get(const std::string& name) const { return GetOSVariable(name.c_str()); } void EnvironmentVariablesManager::del(const std::string& name) { put(name, ""); } void EnvironmentVariablesManager::PutOSVariable(char* value) { ::putenv(value); } std::string EnvironmentVariablesManager::GetOSVariable(const char* name) { #ifdef WINDOWS size_t sz = 0; assert(getenv_s(&sz, NULL, 0, name) == 0); if (sz == 0) return std::string(); std::vector<char> value(sz + 1); assert(getenv_s(&sz, &value[0], sz, name) == 0); return std::string(&value[0], sz - 1); #else const char* const value = std::getenv(name); return value ? value : ""; #endif } bool EnvironmentVariablesManager::IsOSVariableSet(const char* name) { #ifdef WINDOWS size_t sz = 0; assert(getenv_s(&sz, NULL, 0, name) == 0); return sz > 0; #else const char* value = std::getenv(name); return value != NULL && *value != '\0'; #endif } EnvironmentVariablesManager::VariableContainer EnvironmentVariablesManager::PairToContainer(const std::string& name, const std::string& value) const { VariableContainer pair; std::copy(name.begin(), name.end(), std::back_inserter(pair)); pair.push_back('='); std::copy(value.begin(), value.end(), std::back_inserter(pair)); pair.push_back('\0'); return pair; }
Unit-tests using std::assert
.
EnvironmentVariablesManager_unittest.cpp file:
#include <iostream> #include <string> #include <vector> #include <cstdlib> #include <cstring> #include <cassert> #ifdef WINDOWS #include <windows.h> #else #include <stdlib.h> #include <stdio.h> #include <unistd.h> #endif #include "EnvironmentVariablesManager.h" void Test_EnvironmentVariablesManager_get_put() { EnvironmentVariablesManager env; assert(std::string("") == env.get("_a_unique_variable_")); env.put("_a_unique_variable_", "b"); assert(std::string("b") == env.get("_a_unique_variable_")); env.put("_a_unique_variable_", "abc"); assert(std::string("abc") == env.get("_a_unique_variable_")); env.put("_a_unique_variable_", ""); assert(std::string("") == env.get("_a_unique_variable_")); } namespace { std::string ReadEnvironmentVariableViaShell(const std::string& name) { #ifdef WINDOWS const std::string shell = EnvironmentVariablesManager::GetOSVariable("ComSpec"); assert(!shell.empty()); const std::string cmd = shell + " /c echo %" + name + "%"; FILE* const f = _popen(cmd.c_str(), "rb"); #else const std::string cmd = "echo $" + name; FILE* const f = popen(cmd.c_str(), "r"); #endif assert(f != NULL); std::vector<char> line(1024, 0); size_t read = 0; while (!::feof(f) && read < line.size()) { const size_t sz = ::fread(&line[read], 1, line.size() - read, f); read += sz; } #ifdef WINDOWS ::_pclose(f); #else ::pclose(f); #endif line.resize(read); std::string trimmed(read, '\0'); std::copy(line.begin(), line.end(), trimmed.begin()); return trimmed.substr(0, trimmed.find_last_not_of("\r\n") + 1); } } void Test_EnvironmentVariablesManager_put_is_propagated_to_child_process() { EnvironmentVariablesManager env; #ifdef WINDOWS const std::string empty = "%__unique_%"; #else const std::string empty = ""; #endif assert(empty == ReadEnvironmentVariableViaShell("__unique_")); env.put("__unique_", "b"); assert(std::string("b") == ReadEnvironmentVariableViaShell("__unique_")); env.put("__unique_", ""); assert(empty == ReadEnvironmentVariableViaShell("__unique_")); } void Test_EnvironmentVariablesManager_must_take_a_copy() { EnvironmentVariablesManager env; char var[] = "12345678"; env.put("var", var); assert(env.get("var") == std::string("12345678")); std::strcpy(var, "abc"); assert(env.get("var") == std::string("12345678")); } void Test_EnvironmentVariablesManager_del() { EnvironmentVariablesManager env; env.put("variable_to_delete", "123"); assert(std::string("123") == env.get("variable_to_delete")); env.del("variable_to_delete"); assert(env.get("variable_to_delete").empty() == true); } void Test_EnvironmentVariablesManager_IsOSVariableSet_set_and_unset() { EnvironmentVariablesManager env; env.put("a", "value"); assert(EnvironmentVariablesManager::IsOSVariableSet("a") == true); env.put("a", ""); assert(EnvironmentVariablesManager::IsOSVariableSet("a") == false); } void Test_EnvironmentVariablesManager_GetOSVariable() { const std::string unique_name = "EnvironmentVariablesManager_GetOSVariable"; assert(EnvironmentVariablesManager::GetOSVariable(unique_name.c_str()) .empty()); const std::string unique_name_pair = unique_name + "=12345678"; char var[1024]; unique_name_pair.copy(var, sizeof(var)); var[unique_name_pair.length()] = '\0'; ::putenv(var); assert(EnvironmentVariablesManager::GetOSVariable(unique_name.c_str()) == "12345678"); } void Test_EnvironmentVariablesManager_PutOSVariable() { const std::string unique_name = "EnvironmentVariablesManager_PutOSVariable"; const char* before = ::getenv(unique_name.c_str()); if (before != NULL) assert(std::string(before).empty()); const std::string unique_name_pair = unique_name + "=12345678"; char var[1024]; unique_name_pair.copy(var, sizeof(var)); var[unique_name_pair.length()] = '\0'; EnvironmentVariablesManager::PutOSVariable(var); const char* after = ::getenv(unique_name.c_str()); assert(after != NULL); assert(std::string(after) == "12345678"); } int run_tests(int argc, const char* const argv[]) { Test_EnvironmentVariablesManager_GetOSVariable(); Test_EnvironmentVariablesManager_PutOSVariable(); Test_EnvironmentVariablesManager_get_put(); Test_EnvironmentVariablesManager_put_is_propagated_to_child_process(); Test_EnvironmentVariablesManager_must_take_a_copy(); Test_EnvironmentVariablesManager_del(); Test_EnvironmentVariablesManager_IsOSVariableSet_set_and_unset(); return 0; } int main(int argc, const char* const argv[]) { run_tests(argc, argv); std::cout << "All tests pass." << std::endl; }
Overall, nothing really intricate but quite handy.