2012-05-03 09:25:13 +02:00
|
|
|
#include "shell_manager.hh"
|
|
|
|
|
2012-12-10 18:41:01 +01:00
|
|
|
#include "context.hh"
|
2015-06-06 12:54:48 +02:00
|
|
|
#include "buffer_utils.hh"
|
2014-11-25 02:00:18 +01:00
|
|
|
#include "event_manager.hh"
|
2014-10-30 01:50:40 +01:00
|
|
|
#include "file.hh"
|
2012-09-06 14:28:07 +02:00
|
|
|
|
2015-11-21 13:11:19 +01:00
|
|
|
#include <chrono>
|
|
|
|
|
2012-05-03 09:25:13 +02:00
|
|
|
#include <cstring>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
2014-10-13 20:28:02 +02:00
|
|
|
#include <unistd.h>
|
2012-05-03 09:25:13 +02:00
|
|
|
|
2015-10-03 12:21:35 +02:00
|
|
|
extern char **environ;
|
|
|
|
|
2012-05-03 09:25:13 +02:00
|
|
|
namespace Kakoune
|
|
|
|
{
|
2013-04-17 19:26:44 +02:00
|
|
|
|
2012-05-03 09:25:13 +02:00
|
|
|
ShellManager::ShellManager()
|
|
|
|
{
|
2014-10-30 01:50:40 +01:00
|
|
|
const char* path = getenv("PATH");
|
2015-06-01 20:06:35 +02:00
|
|
|
auto new_path = format("{}:{}", path, split_path(get_kak_binary_path()).first);
|
2014-10-30 01:50:40 +01:00
|
|
|
setenv("PATH", new_path.c_str(), 1);
|
2012-05-03 09:25:13 +02:00
|
|
|
}
|
|
|
|
|
2015-10-01 20:36:37 +02:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
struct Pipe
|
|
|
|
{
|
|
|
|
Pipe() { pipe(m_fd); }
|
|
|
|
~Pipe() { close_read_fd(); close_write_fd(); }
|
|
|
|
|
|
|
|
int read_fd() const { return m_fd[0]; }
|
|
|
|
int write_fd() const { return m_fd[1]; }
|
|
|
|
|
|
|
|
void close_read_fd() { close_fd(m_fd[0]); }
|
|
|
|
void close_write_fd() { close_fd(m_fd[1]); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
void close_fd(int& fd) { if (fd != -1) { close(fd); fd = -1; } }
|
|
|
|
int m_fd[2];
|
|
|
|
};
|
|
|
|
|
|
|
|
pid_t spawn_process(StringView cmdline, ConstArrayView<String> params, ConstArrayView<String> kak_env,
|
|
|
|
const Pipe& child_stdout, const Pipe& child_stdin, const Pipe& child_stderr)
|
|
|
|
{
|
|
|
|
Vector<const char*> envptrs;
|
|
|
|
for (char** envp = environ; *envp; ++envp)
|
|
|
|
envptrs.push_back(*envp);
|
|
|
|
for (auto& env : kak_env)
|
|
|
|
envptrs.push_back(env.c_str());
|
|
|
|
envptrs.push_back(nullptr);
|
|
|
|
|
|
|
|
const char* shell = "/bin/sh";
|
|
|
|
auto cmdlinezstr = cmdline.zstr();
|
|
|
|
Vector<const char*> execparams = { shell, "-c", cmdlinezstr };
|
|
|
|
if (not params.empty())
|
|
|
|
execparams.push_back(shell);
|
|
|
|
for (auto& param : params)
|
|
|
|
execparams.push_back(param.c_str());
|
|
|
|
execparams.push_back(nullptr);
|
|
|
|
|
|
|
|
if (pid_t pid = fork())
|
|
|
|
return pid;
|
|
|
|
|
|
|
|
auto move = [](int oldfd, int newfd) { dup2(oldfd, newfd); close(oldfd); };
|
|
|
|
|
|
|
|
close(child_stdout.write_fd());
|
|
|
|
move(child_stdout.read_fd(), 0);
|
|
|
|
|
|
|
|
close(child_stdin.read_fd());
|
|
|
|
move(child_stdin.write_fd(), 1);
|
|
|
|
|
|
|
|
close(child_stderr.read_fd());
|
|
|
|
move(child_stderr.write_fd(), 2);
|
|
|
|
|
|
|
|
execve(shell, (char* const*)execparams.data(), (char* const*)envptrs.data());
|
|
|
|
exit(-1);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-03-13 14:39:18 +01:00
|
|
|
std::pair<String, int> ShellManager::eval(
|
|
|
|
StringView cmdline, const Context& context, StringView input,
|
2015-10-22 14:48:57 +02:00
|
|
|
Flags flags, const ShellContext& shell_context)
|
2012-05-03 09:25:13 +02:00
|
|
|
{
|
2015-10-01 20:36:37 +02:00
|
|
|
static const Regex re(R"(\bkak_(\w+)\b)");
|
2012-05-03 09:25:13 +02:00
|
|
|
|
2015-11-21 13:11:19 +01:00
|
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
using TimePoint = Clock::time_point;
|
|
|
|
|
|
|
|
const DebugFlags debug_flags = context.options()["debug"].get<DebugFlags>();
|
|
|
|
const bool profile = debug_flags & DebugFlags::Profile;
|
|
|
|
if (debug_flags & DebugFlags::Shell)
|
2015-11-19 22:58:26 +01:00
|
|
|
write_to_debug_buffer(format("shell:\n{}\n----\n", cmdline));
|
|
|
|
|
2015-11-21 13:11:19 +01:00
|
|
|
auto start_time = profile ? Clock::now() : TimePoint{};
|
|
|
|
|
2015-10-01 20:36:37 +02:00
|
|
|
Vector<String> kak_env;
|
|
|
|
for (RegexIterator<const char*> it{cmdline.begin(), cmdline.end(), re}, end;
|
|
|
|
it != end; ++it)
|
2012-05-03 09:25:13 +02:00
|
|
|
{
|
2015-10-01 20:36:37 +02:00
|
|
|
StringView name{(*it)[1].first, (*it)[1].second};
|
2012-05-29 12:39:03 +02:00
|
|
|
|
2015-10-01 20:36:37 +02:00
|
|
|
auto match_name = [&](const String& s) {
|
|
|
|
return s.length() > name.length() and
|
|
|
|
prefix_match(s, name) and s[name.length()] == '=';
|
|
|
|
};
|
|
|
|
if (find_if(kak_env, match_name) != kak_env.end())
|
|
|
|
continue;
|
2012-05-03 09:25:13 +02:00
|
|
|
|
2015-10-22 14:48:57 +02:00
|
|
|
auto var_it = shell_context.env_vars.find(name);
|
2015-10-01 20:36:37 +02:00
|
|
|
try
|
2012-05-03 09:25:13 +02:00
|
|
|
{
|
2015-10-22 14:48:57 +02:00
|
|
|
const String& value = var_it != shell_context.env_vars.end() ?
|
2015-10-01 20:36:37 +02:00
|
|
|
var_it->value : get_val(name, context);
|
|
|
|
|
|
|
|
kak_env.push_back(format("kak_{}={}", name, value));
|
|
|
|
} catch (runtime_error&) {}
|
2012-05-03 09:25:13 +02:00
|
|
|
}
|
|
|
|
|
2015-11-21 13:11:19 +01:00
|
|
|
auto spawn_time = profile ? Clock::now() : TimePoint{};
|
|
|
|
|
2015-10-01 20:36:37 +02:00
|
|
|
Pipe child_stdin, child_stdout, child_stderr;
|
2015-10-22 14:48:57 +02:00
|
|
|
pid_t pid = spawn_process(cmdline, shell_context.params, kak_env,
|
2015-10-01 20:36:37 +02:00
|
|
|
child_stdin, child_stdout, child_stderr);
|
2012-05-03 09:25:13 +02:00
|
|
|
|
2015-10-01 20:36:37 +02:00
|
|
|
child_stdin.close_read_fd();
|
|
|
|
child_stdout.close_write_fd();
|
|
|
|
child_stderr.close_write_fd();
|
|
|
|
|
2015-11-27 00:40:17 +01:00
|
|
|
write(child_stdin.write_fd(), input);
|
2015-10-01 20:36:37 +02:00
|
|
|
child_stdin.close_write_fd();
|
|
|
|
|
2015-11-21 13:11:19 +01:00
|
|
|
auto wait_time = profile ? Clock::now() : TimePoint{};
|
|
|
|
|
2015-10-01 20:36:37 +02:00
|
|
|
struct PipeReader : FDWatcher
|
|
|
|
{
|
|
|
|
PipeReader(Pipe& pipe, String& contents)
|
|
|
|
: FDWatcher(pipe.read_fd(),
|
|
|
|
[&contents, &pipe](FDWatcher& watcher, EventMode) {
|
|
|
|
char buffer[1024];
|
|
|
|
size_t size = read(pipe.read_fd(), buffer, 1024);
|
|
|
|
if (size <= 0)
|
|
|
|
{
|
|
|
|
pipe.close_read_fd();
|
|
|
|
watcher.disable();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
contents += StringView{buffer, buffer+size};
|
|
|
|
})
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
String stdout_contents, stderr_contents;
|
|
|
|
PipeReader stdout_reader{child_stdout, stdout_contents};
|
|
|
|
PipeReader stderr_reader{child_stderr, stderr_contents};
|
|
|
|
|
|
|
|
// block SIGCHLD to make sure we wont receive it before
|
|
|
|
// our call to pselect, that will end up blocking indefinitly.
|
|
|
|
sigset_t mask, orig_mask;
|
|
|
|
sigemptyset(&mask);
|
|
|
|
sigaddset(&mask, SIGCHLD);
|
|
|
|
sigprocmask(SIG_BLOCK, &mask, &orig_mask);
|
|
|
|
auto restore_mask = on_scope_end([&] { sigprocmask(SIG_SETMASK, &orig_mask, nullptr); });
|
|
|
|
|
|
|
|
int status = 0;
|
|
|
|
// check for termination now that SIGCHLD is blocked
|
|
|
|
bool terminated = waitpid(pid, &status, WNOHANG);
|
|
|
|
|
|
|
|
while (not terminated or
|
|
|
|
((flags & Flags::WaitForStdout) and
|
|
|
|
(child_stdout.read_fd() != -1 or child_stderr.read_fd() != -1)))
|
|
|
|
{
|
|
|
|
EventManager::instance().handle_next_events(EventMode::Urgent, &orig_mask);
|
|
|
|
if (not terminated)
|
|
|
|
terminated = waitpid(pid, &status, WNOHANG);
|
2012-05-03 09:25:13 +02:00
|
|
|
}
|
2015-10-01 20:36:37 +02:00
|
|
|
|
|
|
|
if (not stderr_contents.empty())
|
|
|
|
write_to_debug_buffer(format("shell stderr: <<<\n{}>>>", stderr_contents));
|
|
|
|
|
2015-11-21 13:11:19 +01:00
|
|
|
if (profile)
|
|
|
|
{
|
|
|
|
TimePoint end_time = Clock::now();
|
|
|
|
auto full = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
|
|
|
auto spawn = std::chrono::duration_cast<std::chrono::milliseconds>(wait_time - spawn_time);
|
|
|
|
auto wait = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - wait_time);
|
|
|
|
write_to_debug_buffer(format("shell execution took {} ms (spawn: {}, wait: {})",
|
|
|
|
(size_t)full.count(), (size_t)spawn.count(), (size_t)wait.count()));
|
|
|
|
}
|
|
|
|
|
2015-10-01 20:36:37 +02:00
|
|
|
return { stdout_contents, WIFEXITED(status) ? WEXITSTATUS(status) : -1 };
|
2012-05-03 09:25:13 +02:00
|
|
|
}
|
|
|
|
|
2015-09-03 14:21:35 +02:00
|
|
|
void ShellManager::register_env_var(StringView str, bool prefix,
|
2012-05-03 09:25:13 +02:00
|
|
|
EnvVarRetriever retriever)
|
|
|
|
{
|
2015-09-03 14:21:35 +02:00
|
|
|
m_env_vars.push_back({ str.str(), prefix, std::move(retriever) });
|
2012-05-03 09:25:13 +02:00
|
|
|
}
|
|
|
|
|
2014-06-18 20:28:48 +02:00
|
|
|
String ShellManager::get_val(StringView name, const Context& context) const
|
|
|
|
{
|
|
|
|
auto env_var = std::find_if(
|
|
|
|
m_env_vars.begin(), m_env_vars.end(),
|
2015-09-03 14:21:35 +02:00
|
|
|
[name](const EnvVarDesc& desc) {
|
|
|
|
return desc.prefix ? prefix_match(name, desc.str) : name == desc.str;
|
|
|
|
});
|
2014-06-18 20:28:48 +02:00
|
|
|
|
|
|
|
if (env_var == m_env_vars.end())
|
|
|
|
throw runtime_error("no such env var: " + name);
|
2015-09-03 14:21:35 +02:00
|
|
|
|
|
|
|
return env_var->func(name, context);
|
2014-06-18 20:28:48 +02:00
|
|
|
}
|
|
|
|
|
2012-05-03 09:25:13 +02:00
|
|
|
}
|