diff --git a/meson.build b/meson.build index 96adaab..6ef1710 100644 --- a/meson.build +++ b/meson.build @@ -12,5 +12,14 @@ conf.set('PROJECT_NAME', meson.project_name()) conf.set('PROJECT_VERSION', meson.project_version()) conf.set('COPYRIGHT_YEAR', '2020') conf.set('PROJECT_REPO_URL', 'https://alarmpi.no-ip.org/gitan/King_DuckZ/user-gcc') +if get_option('bash_method') == 'naive' + conf.set('USE_NAIVE_BASH_METHOD', true) +else + conf.set('USE_NATIVE_BASH_METHOD', true) + bash_found = find_program('bash', required: false) + if not bash_found.found() + warning('bash was not found on your system, you will still be able to build ' + meson.project_name() + ' but you won\'t be able to run it correctly') + endif +endif subdir('src') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..27ad11d --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('bash_method', type: 'combo', description: 'Select the implementation of the Bash script parsing method', choices: ['naive', 'native'], value: 'native') diff --git a/src/config.h.in b/src/config.h.in index 2614718..dd69007 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -21,3 +21,5 @@ #define PROJECT_VERSION "@PROJECT_VERSION@" #define COPYRIGHT_YEAR "@COPYRIGHT_YEAR@" #define PROJECT_REPO_URL "@PROJECT_REPO_URL@" +#mesondefine USE_NATIVE_BASH_METHOD +#mesondefine USE_NAIVE_BASH_METHOD diff --git a/src/main.cpp b/src/main.cpp index e940c29..562092b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,6 +59,7 @@ int main(int argc, char* argv[]) { duck::RunContext syspaths; syspaths.add_to_env(g_simple_colours); + syspaths.env().set("argv0", PROJECT_NAME); #if !defined(NDEBUG) std::cout << syspaths << '\n'; #endif diff --git a/src/replace_bash_vars.cpp b/src/replace_bash_vars.cpp index 242f67d..6238947 100644 --- a/src/replace_bash_vars.cpp +++ b/src/replace_bash_vars.cpp @@ -19,25 +19,30 @@ #include "config.h" #include "run_context.hpp" #include -#include +#if defined(USE_NAIVE_BASH_METHOD) +# include +#else +# include "run_cmd.hpp" +# include "env_real.hpp" +# include "env_fake.hpp" +# include +# include +# include +# include +# include +# include +#endif namespace duck { +#if defined(USE_NAIVE_BASH_METHOD) + namespace { struct VarInfo { std::string_view name; std::size_t orig_len{}; }; - //std::string replaced_all (std::string text, std::string_view search, std::string_view repl) { - // std::size_t pos = 0; - // while ((pos = text.find(search, pos)) != std::string::npos) { - // text.replace(pos, search.size(), repl); - // pos += repl.size(); - // } - // return text; - //} - - VarInfo extract_var_name (const RunContext& sp, std::string_view text) { + VarInfo extract_var_name (const RunContext& rc, std::string_view text) { assert(not text.empty()); assert(text.front() == '$'); if (text.size() == 1) { @@ -59,7 +64,7 @@ namespace duck { std::string_view token(text.substr(2, end - 2)); std::string_view retval(token.empty() || token.front() != '!' ? token : token.substr(1)); if (not token.empty() and token.front() == '!') { - return {sp.env()[retval], end + 1}; + return {rc.env()[retval], end + 1}; } else { return {retval, end + 1}; @@ -76,7 +81,7 @@ namespace duck { } } //unnamed namespace - std::string replace_bash_vars (const RunContext& sp, std::string_view text) { + std::string replace_bash_vars (const RunContext& rc, std::string_view text) { std::size_t pos = 0; std::size_t prev_start = 0; std::string rtext; @@ -84,7 +89,7 @@ namespace duck { while ((pos = text.find('$', pos)) != std::string::npos) { if (0 == pos or rtext[pos-1] != '\\') { - auto var = extract_var_name(sp, text.substr(pos)); + auto var = extract_var_name(rc, text.substr(pos)); if (not var.orig_len) { ++pos; continue; @@ -95,7 +100,7 @@ namespace duck { if (var.name == "argv0") rtext += PROJECT_NAME; else - rtext += sp.env()[var.name]; + rtext += rc.env()[var.name]; pos += var.orig_len; prev_start = pos; } @@ -107,4 +112,103 @@ namespace duck { rtext.append(text.begin() + prev_start, text.end()); return rtext; } + +#elif defined(USE_NATIVE_BASH_METHOD) + + namespace { + std::string replaced_all (std::string text, std::string_view search, std::string_view repl) { + std::size_t pos = 0; + while ((pos = text.find(search, pos)) != std::string::npos) { + text.replace(pos, search.size(), repl); + pos += repl.size(); + } + return text; + } + + struct Pipes { + Pipes() { + if(pipe(cout_pipe) or pipe(cerr_pipe)) + throw std::runtime_error("Error in pipe() in replace_bash_vars()"); + } + + ~Pipes() noexcept { + close(cout_pipe[0]), close(cerr_pipe[0]); + close(cout_pipe[1]), close(cerr_pipe[1]); + } + + void close_child_side() noexcept { + close(cout_pipe[1]), close(cerr_pipe[1]); + cout_pipe[1] = cerr_pipe[1] = 0; + } + + int cout_pipe[2]; + int cerr_pipe[2]; + }; + + struct FileAction { + FileAction() { posix_spawn_file_actions_init(&action); } + ~FileAction() noexcept { posix_spawn_file_actions_destroy(&action); } + + posix_spawn_file_actions_t action; + }; + } //unnamed namespace + + std::string replace_bash_vars (const RunContext& rc, std::string_view text) { + Pipes p; + FileAction fa; + + //see: https://stackoverflow.com/questions/13893085/posix-spawnp-and-piping-child-output-to-a-string + posix_spawn_file_actions_addclose(&fa.action, p.cout_pipe[0]); + posix_spawn_file_actions_addclose(&fa.action, p.cerr_pipe[0]); + posix_spawn_file_actions_adddup2(&fa.action, p.cout_pipe[1], 1); + posix_spawn_file_actions_adddup2(&fa.action, p.cerr_pipe[1], 2); + posix_spawn_file_actions_addclose(&fa.action, p.cout_pipe[1]); + posix_spawn_file_actions_addclose(&fa.action, p.cerr_pipe[1]); + + char shell_cpy[] = {'b', 'a', 's', 'h', '\0'}; + char c_arg_cpy[] = {'-', 'c', '\0'}; + std::vector command; + { + std::string san_text = "\"" + replaced_all(to_string(text), "\"", "\\\"") + "\""; + command.reserve(5 + san_text.size() + 1); + command.insert(command.begin(), "echo ", "echo " + 5); + std::copy(san_text.begin(), san_text.end(), std::back_inserter(command)); + command.push_back('\0'); + } + char* const argv[] = {shell_cpy, c_arg_cpy, command.data(), nullptr}; + + const auto new_env = rc.env().to_c_array(); + pid_t pid; + const int spawn_ret = posix_spawnp(&pid, argv[0], &fa.action, nullptr, argv, new_env.get()); + p.close_child_side(); + + if (spawn_ret) + throw std::runtime_error("posix_spawnp() failed with code " + std::to_string(spawn_ret)); + + // Read from pipes + std::string retval; + std::string buffer(1024,' '); + std::vector plist = { {p.cout_pipe[0], POLLIN}, {p.cerr_pipe[0], POLLIN} }; + constexpr const int timeout = -1; + for (int rval; (rval = poll(&plist[0], plist.size(), timeout)) > 0;) { + if (plist[0].revents & POLLIN) { + const int bytes_read = read(p.cout_pipe[0], &buffer[0], buffer.length()); + retval.append(buffer.begin(), buffer.begin() + bytes_read); + } + else if (plist[1].revents & POLLIN) { + /*const int bytes_read =*/ read(p.cerr_pipe[0], &buffer[0], buffer.length()); + //cout << buffer.substr(0, static_cast(bytes_read)) << "\n"; + } + else break; // nothing left to read + } + + int exit_code; + waitpid(pid, &exit_code, 0); + + return retval; + } + +#else +# error "None of the known bash parsing method has been selected by the user" +#endif } //namespace duck