From 481f9ba1aee62fea092184f2243d210a8686781f Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 14 Jun 2018 18:13:04 +0200 Subject: Add portable environment variable manipulation functions --- libbutl/path.cxx | 33 ++++++++++++++++++++------------- libbutl/process.cxx | 41 +++++++++++++++++++++++------------------ libbutl/utility.cxx | 32 ++++++++++++++++++++++++++++++++ libbutl/utility.ixx | 13 +++++++++++++ libbutl/utility.mxx | 22 ++++++++++++++++++++++ tests/process/driver.cxx | 35 ++++++++++++++--------------------- 6 files changed, 124 insertions(+), 52 deletions(-) diff --git a/libbutl/path.cxx b/libbutl/path.cxx index 11a34c9..47d691b 100644 --- a/libbutl/path.cxx +++ b/libbutl/path.cxx @@ -17,7 +17,7 @@ #else # include // struct passwd, getpwuid_r() # include // EINVAL -# include // realpath(), getenv() +# include // realpath() # include // PATH_MAX # include // getcwd(), chdir() # include // strlen(), strcpy() @@ -29,6 +29,7 @@ #ifndef __cpp_lib_modules #include +#include #include #include @@ -123,33 +124,39 @@ namespace butl } #ifndef _WIN32 - static const char* + static const vector tmp_vars ({"TMPDIR", "TMP", "TEMP", "TEMPDIR"}); + + static string temp_directory () { - const char* dir (nullptr); - const char* env[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr}; + optional dir; - for (auto e (env); dir == nullptr && *e != nullptr; ++e) - dir = getenv (*e); + for (const string& v: tmp_vars) + { + dir = getenv (v); + + if (dir) + break; + } - if (dir == nullptr) + if (!dir) dir = "/tmp"; struct stat s; - if (stat (dir, &s) != 0) + if (stat (dir->c_str (), &s) != 0) throw_generic_error (errno); if (!S_ISDIR (s.st_mode)) throw_generic_error (ENOTDIR); - return dir; + return *dir; } static string home () { - if (const char* h = getenv ("HOME")) - return h; + if (optional h = getenv ("HOME")) + return *h; // Struct passwd has 5 members that will use this buffer. Two are the // home directory and shell paths. The other three are the user login @@ -214,8 +221,8 @@ namespace butl #else // Could be set by, e.g., MSYS and Cygwin shells. // - if (const char* h = getenv ("HOME")) - return h; + if (optional h = getenv ("HOME")) + return *h; char h[_MAX_PATH]; HRESULT r (SHGetFolderPathA (NULL, CSIDL_PROFILE, NULL, 0, h)); diff --git a/libbutl/process.cxx b/libbutl/process.cxx index 41e2c0d..384af29 100644 --- a/libbutl/process.cxx +++ b/libbutl/process.cxx @@ -9,7 +9,6 @@ #include #ifndef _WIN32 -# include // setenv(), unsetenv() # include // SIG* # include // execvp, fork, dup2, pipe, chdir, *_FILENO, getpid # include // waitpid @@ -60,7 +59,7 @@ #include #include // milli #include -#include // getenv(), __argv[] +#include // __argv[] #include // find() #endif #endif @@ -83,7 +82,6 @@ import std.io; import std.threading; // Clang wants it in purview (see process-details.hxx). #endif import butl.path; -import butl.optional; import butl.fdstream; import butl.vector_view; import butl.small_vector; @@ -273,7 +271,8 @@ namespace butl // 2. We do not continue searching on EACCES from execve(). // 3. We do not execute via default shell on ENOEXEC from execve(). // - for (const char* b (getenv ("PATH")), *e; + optional p (getenv ("PATH")); + for (const char* b (p ? p->c_str () : nullptr), *e; b != nullptr; b = (e != nullptr ? e + 1 : e)) { @@ -460,12 +459,17 @@ namespace butl { const char* v (strchr (ev, '=')); - int r (v != nullptr - ? setenv (string (ev, v - ev).c_str (), v + 1, 1) - : unsetenv (ev)); - - if (r == -1) - fail (true); + try + { + if (v != nullptr) + setenv (string (ev, v - ev), v + 1); + else + unsetenv (ev); + } + catch (const system_error& e) + { + throw process_child_error (e.code ().value ()); + } } } @@ -845,7 +849,8 @@ namespace butl // Now search in PATH. Recall is unchanged. // - for (const char* b (getenv ("PATH")), *e; + optional p (getenv ("PATH")); + for (const char* b (p ? p->c_str () : nullptr), *e; b != nullptr; b = (e != nullptr ? e + 1 : e)) { @@ -1126,7 +1131,7 @@ namespace butl // cmd.exe and passing the batch file as an argument (see CreateProcess() // for deails). // - const char* batch (nullptr); + optional batch; { const char* p (pp.effect_string ()); const char* e (path::traits::find_extension (p, strlen (p))); @@ -1135,7 +1140,7 @@ namespace butl { batch = getenv ("COMSPEC"); - if (batch == nullptr) + if (!batch) batch = "C:\\Windows\\System32\\cmd.exe"; } } @@ -1240,14 +1245,14 @@ namespace butl cmd_line += '"'; }; - if (batch != nullptr) + if (batch) { - append (batch); + append (*batch); append ("/c"); append (pp.effect_string ()); } - for (const char* const* p (args + (batch != nullptr ? 1 : 0)); + for (const char* const* p (args + (batch ? 1 : 0)); *p != 0; ++p) append (*p); @@ -1463,7 +1468,7 @@ namespace butl }; bool msys (false); - if (batch == nullptr) + if (!batch) { string p (pp.effect_string ()); auto i (detect_msys_cache_.find (p)); @@ -1482,7 +1487,7 @@ namespace butl for (system_clock::duration timeout (1h);;) // Try for about 1 hour. { if (!CreateProcess ( - batch != nullptr ? batch : pp.effect_string (), + batch ? batch->c_str () : pp.effect_string (), const_cast (cmd_line.c_str ()), 0, // Process security attributes. 0, // Primary thread security attributes. diff --git a/libbutl/utility.cxx b/libbutl/utility.cxx index 8e2422aa..502586c 100644 --- a/libbutl/utility.cxx +++ b/libbutl/utility.cxx @@ -10,6 +10,8 @@ #include #endif +#include // setenv(), unsetenv(), _putenv() + #ifndef __cpp_lib_modules #include #include @@ -124,6 +126,36 @@ namespace butl return l; } + + void + setenv (const string& name, const string& value) + { +#ifndef _WIN32 + if (::setenv (name.c_str (), value.c_str (), 1 /* overwrite */) == -1) + throw_generic_error (errno); +#else + // The documentation doesn't say how to obtain the failure reason, so we + // will assume it to always be EINVAL (as the most probable one). + // + if (_putenv (string (name + '=' + value).c_str ()) == -1) + throw_generic_error (EINVAL); +#endif + } + + void + unsetenv (const string& name) + { +#ifndef _WIN32 + if (::unsetenv (name.c_str ()) == -1) + throw_generic_error (errno); +#else + // The documentation doesn't say how to obtain the failure reason, so we + // will assume it to always be EINVAL (as the most probable one). + // + if (_putenv (string (name + '=').c_str ()) == -1) + throw_generic_error (EINVAL); +#endif + } } namespace std diff --git a/libbutl/utility.ixx b/libbutl/utility.ixx index d4aaa65..565a694 100644 --- a/libbutl/utility.ixx +++ b/libbutl/utility.ixx @@ -2,6 +2,10 @@ // copyright : Copyright (c) 2014-2018 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#ifndef __cpp_lib_modules +#include // getenv() +#endif + namespace butl { inline char @@ -199,4 +203,13 @@ namespace butl throw std::istream::failure (""); } + + inline optional + getenv (const std::string& name) + { + if (const char* r = std::getenv (name.c_str ())) + return std::string (r); + + return nullopt; + } } diff --git a/libbutl/utility.mxx b/libbutl/utility.mxx index 740b411..252a4b4 100644 --- a/libbutl/utility.mxx +++ b/libbutl/utility.mxx @@ -35,6 +35,9 @@ export module butl.utility; import std.core; import std.io; #endif +import butl.optional; +#else +#include #endif #include @@ -183,6 +186,25 @@ LIBBUTL_MODEXPORT namespace butl bool eof (std::istream&); + // Environment variables. + // + optional + getenv (const std::string&); + + // Throw system_error on failure. + // + // Note that on Windows setting an empty value usets the variable. + // + LIBBUTL_SYMEXPORT + void + setenv (const std::string& name, const std::string& value); + + // Throw system_error on failure. + // + LIBBUTL_SYMEXPORT + void + unsetenv (const std::string&); + // Key comparators (i.e., to be used in sets, maps, etc). // struct compare_c_string diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx index 9bee6c5..6ca28a5 100644 --- a/tests/process/driver.cxx +++ b/tests/process/driver.cxx @@ -2,8 +2,6 @@ // copyright : Copyright (c) 2014-2018 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // getenv(), setenv(), _putenv() - #include #ifndef __cpp_lib_modules @@ -23,11 +21,13 @@ import std.core; import std.io; #endif import butl.path; +import butl.utility; // setenv(), getenv() import butl.process; import butl.optional; import butl.fdstream; #else #include +#include #include #include #include @@ -68,13 +68,8 @@ exec (const path& p, // sure that the child process will not see the variable that is requested // to be unset, and will see the other one unaffected. // -#ifndef _WIN32 - assert (setenv ("DEF", "2", 1) == 0); - assert (setenv ("XYZ", "3", 1) == 0); -#else - assert (_putenv ("DEF=2") == 0); - assert (_putenv ("XYZ=3") == 0); -#endif + setenv ("DEF", "2"); + setenv ("XYZ", "3"); } if (cwd != nullptr) @@ -205,6 +200,9 @@ exec (const path& p, int main (int argc, const char* argv[]) { + using butl::getenv; + using butl::optional; + bool child (false); bool bin (false); dir_path wd; // Working directory. @@ -276,10 +274,9 @@ main (int argc, const char* argv[]) // Check that the ABC variable is set, the DEF is unset and the XYZ is // left unchanged. // - const char* v; - if ((v = getenv ("ABC")) == nullptr || string ("1") != v || - getenv ("DEF") != nullptr || - (v = getenv ("XYZ")) == nullptr || string ("3") != v) + if (getenv ("ABC") != optional ("1") || + getenv ("DEF") || + getenv ("XYZ") != optional ("3")) return 1; } @@ -383,14 +380,10 @@ main (int argc, const char* argv[]) // string paths (fp.directory ().string ()); - if (char const* s = getenv ("PATH")) - paths += string (1, path::traits::path_separator) + s; + if (optional p = getenv ("PATH")) + paths += string (1, path::traits::path_separator) + *p; -#ifndef _WIN32 - assert (setenv ("PATH", paths.c_str (), 1) == 0); -#else - assert (_putenv (("PATH=" + paths).c_str ()) == 0); -#endif + setenv ("PATH", paths); dir_path::current_directory (fp.directory () / dir_path ("..")); @@ -419,7 +412,7 @@ main (int argc, const char* argv[]) assert (exec (owd / "test")); paths = owd.string () + path::traits::path_separator + paths; - assert (_putenv (("PATH=" + paths).c_str ()) == 0); + setenv ("PATH", paths); assert (exec (path ("test.bat"))); assert (exec (path ("test"))); -- cgit v1.1