Add a bash-based implementation of replace_bash_vars().

You can choose at configure time if you want the old
c++ variable string replacement implementation or the
new one that execs bash -c 'echo blah blah'. It supports
using both EnvReal and EnvFake.
This commit is contained in:
King_DuckZ 2020-04-15 00:37:49 +02:00
parent a1da8d1053
commit c3cab484b6
5 changed files with 132 additions and 15 deletions

View file

@ -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')

1
meson_options.txt Normal file
View file

@ -0,0 +1 @@
option('bash_method', type: 'combo', description: 'Select the implementation of the Bash script parsing method', choices: ['naive', 'native'], value: 'native')

View file

@ -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

View file

@ -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

View file

@ -19,25 +19,30 @@
#include "config.h"
#include "run_context.hpp"
#include <cassert>
#include <cctype>
#if defined(USE_NAIVE_BASH_METHOD)
# include <cctype>
#else
# include "run_cmd.hpp"
# include "env_real.hpp"
# include "env_fake.hpp"
# include <spawn.h>
# include <unistd.h>
# include <sys/wait.h>
# include <stdexcept>
# include <cstdlib>
# include <poll.h>
#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<char> 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<pollfd> 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<size_t>(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