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.
214 lines
6.1 KiB
C++
214 lines
6.1 KiB
C++
/* Copyright 2020, Michele Santullo
|
|
* This file is part of user-gcc.
|
|
*
|
|
* User-gcc is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* User-gcc is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with user-gcc. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "replace_bash_vars.hpp"
|
|
#include "config.h"
|
|
#include "run_context.hpp"
|
|
#include <cassert>
|
|
#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{};
|
|
};
|
|
|
|
VarInfo extract_var_name (const RunContext& rc, std::string_view text) {
|
|
assert(not text.empty());
|
|
assert(text.front() == '$');
|
|
if (text.size() == 1) {
|
|
return {};
|
|
}
|
|
else if (text.size() == 2) {
|
|
if (std::isalnum(text[1]))
|
|
return {text.substr(1, 1), 2};
|
|
return {};
|
|
}
|
|
else {
|
|
assert(text.size() >= 3);
|
|
if (text[1] == '{') {
|
|
const auto end = text.find('}', 2);
|
|
if (text.npos == end) {
|
|
return {};
|
|
}
|
|
else {
|
|
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 {rc.env()[retval], end + 1};
|
|
}
|
|
else {
|
|
return {retval, end + 1};
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
std::size_t z;
|
|
for (z = 1; z < text.size() && std::isalnum(text[z]); ++z) {
|
|
}
|
|
return {text.substr(1, z - 1), z + 1};
|
|
}
|
|
}
|
|
}
|
|
} //unnamed namespace
|
|
|
|
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;
|
|
rtext.reserve(text.size());
|
|
|
|
while ((pos = text.find('$', pos)) != std::string::npos) {
|
|
if (0 == pos or rtext[pos-1] != '\\') {
|
|
auto var = extract_var_name(rc, text.substr(pos));
|
|
if (not var.orig_len) {
|
|
++pos;
|
|
continue;
|
|
}
|
|
|
|
assert(prev_start <= pos);
|
|
rtext.append(text.begin() + prev_start, text.begin() + pos);
|
|
if (var.name == "argv0")
|
|
rtext += PROJECT_NAME;
|
|
else
|
|
rtext += rc.env()[var.name];
|
|
pos += var.orig_len;
|
|
prev_start = pos;
|
|
}
|
|
else {
|
|
++pos;
|
|
}
|
|
}
|
|
|
|
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
|