construct/ircd/exec.cc

502 lines
8.0 KiB
C++

// The Construct
//
// Copyright (C) The Construct Developers, Authors & Contributors
// Copyright (C) 2016-2020 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#include <RB_INC_SYS_PRCTL_H
#include <boost/process.hpp>
#include <boost/process/extend.hpp>
struct ircd::exec::handler
:boost::process::extend::async_handler
{
exec *e {nullptr};
template<class executor> void on_fork_error(executor &, const std::error_code &) const noexcept;
template<class executor> void on_exec_error(executor &, const std::error_code &) const noexcept;
template<class executor> void on_error(executor&, const std::error_code &) noexcept;
static void handle_exit(exec &, int, const std::error_code &) noexcept;
template<class executor> void on_success(executor &) const noexcept;
template<class executor> void on_exec_setup(executor &) const noexcept;
template<class executor> void on_setup(executor &) noexcept;
template<class executor>
std::function<void (int, const std::error_code &)>
on_exit_handler(executor &) const noexcept;
handler(exec *const &);
~handler() noexcept;
};
decltype(ircd::exec::log)
ircd::exec::log
{
"exec"
};
decltype(ircd::exec::exec::id_ctr)
ircd::exec::exec::id_ctr;
template<>
decltype(ircd::util::instance_list<ircd::exec>::allocator)
ircd::util::instance_list<ircd::exec>::allocator{};
template<>
decltype(ircd::util::instance_list<ircd::exec>::list)
ircd::util::instance_list<ircd::exec>::list
{
allocator
};
ircd::exec::exec(const args &args,
const opts &opt)
:opt
{
std::make_unique<opts>(opt)
}
,path
(
args.at(0)
)
,argv
(
std::next(begin(args), 1), end(args)
)
,pipe
{
std::make_unique<pair<boost::process::async_pipe>>
(
static_cast<asio::io_context &>(ios::main.context()),
static_cast<asio::io_context &>(ios::main.context())
)
}
{
}
ircd::exec::~exec()
noexcept try
{
join(SIGKILL);
dock.wait([this]() noexcept
{
return this->pid <= 0;
});
}
catch(const std::exception &e)
{
log::critical
{
log, "unhandled :%s",
e.what(),
};
}
long
ircd::exec::run()
try
{
assert(pid <= 0);
assert(!child);
eptr = {};
child = std::make_unique<boost::process::child>
(
fs::_path(path).string(),
argv,
(boost::process::std_in) = pipe->first,
(boost::process::std_out & boost::process::std_err) = pipe->second,
handler{this},
static_cast<asio::io_context &>(ios::main.context())
);
return pid;
}
catch(const std::system_error &e)
{
eptr = std::current_exception();
log::error
{
log, "id:%lu pid:%ld `%s' :%s",
id,
pid,
path,
e.what(),
};
throw;
}
long
ircd::exec::join(const int &sig)
try
{
if(!signal(sig))
return code;
log::dwarning
{
log, "id:%lu pid:%ld `%s' signal:%d waiting for exit...",
id,
pid,
path,
sig,
};
//child->wait();
dock.wait([this]() noexcept
{
return this->pid <= 0;
});
assert(!child->running());
return code;
}
catch(const std::exception &e)
{
log::error
{
log, "id:%lu pid:%ld `%s' join :%s",
id,
pid,
path,
e.what(),
};
throw;
}
bool
ircd::exec::signal(const int &sig)
{
if(pid <= 0)
return false;
if(!child)
return false;
if(!child->valid())
return false;
if(!child->running())
return false;
const bool kill
{
#ifdef SIGKILL
sig == SIGKILL
#else
sig == 9
#endif
};
if(kill)
{
child->terminate();
return true;
}
#if defined(HAVE_SIGNAL_H)
if(sig)
syscall(::kill, pid, sig);
#endif
return true;
}
size_t
ircd::exec::write(const const_buffers &bufs)
{
assert(pipe);
auto &pipe
{
this->pipe->first
};
const auto interruption
{
[&pipe](ctx::ctx *const &) noexcept
{
if(pipe.is_open())
pipe.cancel();
}
};
size_t ret{0}; ctx::continuation
{
continuation::asio_predicate, interruption, [&pipe, &bufs, &ret]
(auto &yield)
{
ret = pipe.async_write_some(bufs, yield);
}
};
return ret;
}
size_t
ircd::exec::read(const mutable_buffers &bufs)
{
assert(pipe);
auto &pipe
{
this->pipe->second
};
const auto interruption
{
[&pipe](ctx::ctx *const &) noexcept
{
if(pipe.is_open())
pipe.cancel();
}
};
boost::system::error_code ec;
size_t ret{0}; ctx::continuation
{
continuation::asio_predicate, interruption, [&pipe, &bufs, &ret, &ec]
(auto &yield)
{
ret = pipe.async_read_some(bufs, yield[ec]);
}
};
if(ec)
{
assert(!ret);
if(ec == boost::asio::error::eof)
return 0;
throw_system_error(ec);
__builtin_unreachable();
}
assert(ret);
return ret;
}
//
// exec::handler
//
ircd::exec::handler::handler(exec *const &e)
:e{e}
{
}
ircd::exec::handler::~handler()
noexcept
{
}
template<class executor>
std::function<void (int, const std::error_code &)>
ircd::exec::handler::on_exit_handler(executor &ex)
const noexcept
{
return std::bind(&handler::handle_exit, std::ref(*this->e), ph::_1, ph::_2);
}
template<class executor>
void
ircd::exec::handler::on_setup(executor &ex)
noexcept
{
assert(e);
size_t argc(0), envc(0);
for(auto p(ex.env); p && *p; ++p, ++envc);
for(auto p(ex.cmd_line); p && *p; ++p, ++argc);
log::logf
{
log, log::level::DEBUG,
"id:%lu pid:%ld `%s' start; argc:%zu envc:%zu",
e->id,
ex.pid,
ex.exe,
argc,
envc,
};
}
template<class executor>
void
ircd::exec::handler::on_exec_setup(executor &ex)
const noexcept
{
if constexpr((false)) // outputs from child; don't want
{
assert(e);
log::logf
{
log, log::level::DEBUG,
"id:%lu pid:%ld `%s' executing...",
e->id,
e->pid,
e->path,
};
}
// Set the parent death signal in case of a crash so we won't go zombie.
#if defined(HAVE_SYS_PRCTL_H)
sys::call(prctl, PR_SET_PDEATHSIG, SIGTERM);
#endif
// Ignore SIGINT/SIGQUIT that way the administrator striking ctrl-c or
// ctrl-\ at the console doesn't terminate the child.
#if defined(HAVE_SIGNAL_H)
sigset_t ours;
sys::call(sigemptyset, &ours);
sys::call(sigaddset, &ours, SIGINT);
sys::call(sigaddset, &ours, SIGQUIT);
sys::call(sigprocmask, SIG_BLOCK, &ours, nullptr);
#endif
}
template<class executor>
void
ircd::exec::handler::on_success(executor &ex)
const noexcept
{
assert(e);
e->pid = ex.pid;
e->dock.notify_all();
if(ex.pid < 0)
return;
const auto &level
{
e->opt->exec_log_level
};
log::logf
{
log, level,
"id:%lu pid:%ld `%s' spawned...",
e->id,
e->pid,
e->path,
};
}
void
ircd::exec::handler::handle_exit(exec &e,
int code,
const std::error_code &ec)
noexcept
{
const unwind _{[&]
{
e.pid = 0;
e.code = code;
e.dock.notify_all();
}};
if(unlikely(ec))
{
char ecbuf[64];
log::error
{
log, "id:%lu pid:%ld `%s' exit #%ld :%s",
e.id,
e.pid,
e.path,
ec.value(),
string(ecbuf, ec),
};
return;
}
const auto &level
{
code == 0?
e.opt->exit_log_level:
log::level::ERROR
};
log::logf
{
log, level,
"id:%lu pid:%ld `%s' exit (%ld)",
e.id,
e.pid,
e.path,
code,
};
}
template<class executor>
void
ircd::exec::handler::on_error(executor &ex,
const std::error_code &ec)
noexcept
{
assert(e);
char ecbuf[64];
log::critical
{
log, "id:%lu pid:%ld `%s' fail #%ld :%s",
e->id,
e->pid,
e->path,
e->code,
ec.value(),
string(ecbuf, ec),
};
}
template<class executor>
void
ircd::exec::handler::on_exec_error(executor &ex,
const std::error_code &ec)
const noexcept
{
if constexpr((false)) // outputs from child; don't want
{
assert(e);
char ecbuf[64];
log::error
{
log, "id:%lu pid:%ld `%s' exec() #%ld :%s",
e->id,
e->pid,
e->path,
ec.value(),
string(ecbuf, ec),
};
}
}
template<class executor>
void
ircd::exec::handler::on_fork_error(executor &ex,
const std::error_code &ec)
const noexcept
{
assert(e);
char ecbuf[64];
log::critical
{
log, "id:%lu pid:%ld `%s' fork() #%ld :%s",
e->id,
e->pid,
e->path,
ec.value(),
string(ecbuf, ec),
};
}