If multiple scripts try to set the same environment variable, then the last one wins.
Fixes #819 Signed-off-by: Justin Cinkelj <[email protected]> # Conflicts: # loader.cc --- core/commands.cc | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ tests/tst-commands.cc | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/core/commands.cc b/core/commands.cc index b2b3a60..1ba6bf8 100644 --- a/core/commands.cc +++ b/core/commands.cc @@ -12,6 +12,8 @@ #include <boost/config/warning_disable.hpp> #include <boost/spirit/include/qi.hpp> +#include <boost/program_options.hpp> +#include <osv/power.hh> #include <osv/commands.hh> #include <osv/align.hh> #include <sys/types.h> @@ -90,6 +92,66 @@ parse_command_line_min(const std::string line, bool &ok) } /* +In each runscript line, first N args starting with - are options. +Parse options and remove them from result. + +Options are applied immediately, just as in loader.cc parse_options(). +So if two scripts set the same environment variable, then the last one wins. +Applying all options before running any command is also safer than trying to +apply options for each script at script execution (second script would modify +environment setup by the first script, causing a race). +*/ +static void runscript_process_options(std::vector<std::vector<std::string> >& result) { + namespace bpo = boost::program_options; + namespace bpos = boost::program_options::command_line_style; + // don't allow --foo bar (require --foo=bar) so we can find the first non-option + // argument + int style = bpos::unix_style & ~(bpos::long_allow_next | bpos::short_allow_next); + bpo::options_description desc("OSv runscript options"); + desc.add_options() + ("env", bpo::value<std::vector<std::string>>(), "set Unix-like environment variable (putenv())"); + + for (size_t ii=0; ii<result.size(); ii++) { + auto cmd = result[ii]; + bpo::variables_map vars; + + std::vector<const char*> args = { "dummy-string" }; + // due to https://svn.boost.org/trac/boost/ticket/6991, we can't terminate + // command line parsing on the executable name, so we need to look for it + // ourselves + auto ac = cmd.size(); + auto av = std::vector<const char*>(); + av.reserve(ac+1); + size_t jj = 0; + for (auto prm : cmd) { + av[jj++] = prm.c_str(); + } + auto nr_options = std::find_if(av.data(), av.data() + ac, + [](const char* arg) { return arg[0] != '-'; }) - av.data(); + std::copy(av.data(), av.data() + nr_options, std::back_inserter(args)); + + try { + bpo::store(bpo::parse_command_line(args.size(), args.data(), desc, style), vars); + } catch(std::exception &e) { + std::cout << e.what() << '\n'; + std::cout << desc << '\n'; + osv::poweroff(); + } + bpo::notify(vars); + + if (vars.count("env")) { + for (auto t : vars["env"].as<std::vector<std::string>>()) { + debug("Setting in environment: %s\n", t); + putenv(strdup(t.c_str())); + } + } + + cmd.erase(cmd.begin(), cmd.begin() + nr_options); + result[ii] = cmd; + } +} + +/* If cmd starts with "runcript file", read content of file and return vector of all programs to be run. File can contain multiple commands per line. @@ -125,6 +187,8 @@ std::vector<std::vector<std::string>> runscript_expand(const std::vector<std::st ok = false; return result2; } + // process and remove options from command + runscript_process_options(result3); result2.insert(result2.end(), result3.begin(), result3.end()); line_num++; } diff --git a/tests/tst-commands.cc b/tests/tst-commands.cc index de0c143..7d0a679 100644 --- a/tests/tst-commands.cc +++ b/tests/tst-commands.cc @@ -625,6 +625,56 @@ static bool test_runscript_multiline_multiple_commands_per_line_with_args_quotes return true; } +static bool test_runscript_with_env() +{ + std::ofstream of1("/myscript", std::ios::out | std::ios::binary); + of1 << "--env=ASDF=ttrt /prog1 pp1a pp1b\n"; + of1.close(); + + std::vector<std::vector<std::string> > result; + std::vector<std::string> cmd = { "/prog1" }; + size_t expected_size[] = {4}; + bool ok; + + if (NULL != getenv("ASDF")) { + return false; + } + + result = osv::parse_command_line( + std::string("runscript \"/myscript\"; "), + ok); + + if (!ok) { + return false; + } + + if (result.size() != 1) { + return false; + } + + for (size_t i = 0; i < result.size(); i++) { + if (result[i].size() != expected_size[i]) { + return false; + } + if (result[i][0] != cmd[i]) { + return false; + } + } + + if (result[0][1] != std::string("pp1a")) { + return false; + } + if (result[0][2] != std::string("pp1b")) { + return false; + } + + if (std::string("ttrt") != getenv("ASDF")) { + return false; + } + + return true; +} + int main(int argc, char *argv[]) { report(test_parse_simplest(), "simplest command line"); @@ -651,6 +701,8 @@ int main(int argc, char *argv[]) "runscript multiple lines"); report(test_runscript_multiline_multiple_commands_per_line_with_args_quotes(), "runscript multiple lines, multiple commands per line, with args and quotes"); + report(test_runscript_with_env(), + "runscript with --env"); printf("SUMMARY: %d tests, %d failures\n", tests, fails); return 0; } -- 2.9.3 -- You received this message because you are subscribed to the Google Groups "OSv Development" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. For more options, visit https://groups.google.com/d/optout.
