diff --git a/include/ircd/exec.h b/include/ircd/exec.h index 893cce90a..db2d5e07a 100644 --- a/include/ircd/exec.h +++ b/include/ircd/exec.h @@ -35,6 +35,7 @@ struct ircd::exec :instance_list { struct opts; + struct handler; using args = vector_view; using const_buffers = vector_view; using mutable_buffers = vector_view; @@ -48,14 +49,17 @@ struct ircd::exec std::vector argv; std::unique_ptr> pipe; std::unique_ptr child; - long pid {0}; // set on spawn - long code {0}; // set on exit + std::exception_ptr eptr; + ctx::dock dock; + long pid {-1L}; // > 0 when running; <= 0 during exec/halt + long code {0}; // set on exit public: size_t read(const mutable_buffers &); size_t write(const const_buffers &); bool signal(const int &sig); - long join(const int &sig = 0); + long join(const int &sig = 0); // returns exit code + long run(); // returns pid or throws exec(const args &, const opts &); exec(const args &); @@ -70,12 +74,6 @@ struct ircd::exec /// struct ircd::exec::opts { - /// When false (default) the child is terminated by the dtor of - /// ircd::exec. Note setting this to true does not detach the boost - /// handles until destruction time; it also does not affect calling - /// the interface join() manually. - bool detach {false}; - /// Child executions will be logged at this level (use DEBUG to quiet) ircd::log::level exec_log_level = ircd::log::level::NOTICE; diff --git a/ircd/exec.cc b/ircd/exec.cc index 23b30cdbc..1ff34ed37 100644 --- a/ircd/exec.cc +++ b/ircd/exec.cc @@ -9,6 +9,30 @@ // full license for this software is available in the LICENSE file. #include +#include + +struct ircd::exec::handler +:boost::process::extend::async_handler +{ + exec *e {nullptr}; + + template void on_fork_error(executor &, const std::error_code &) const noexcept; + template void on_exec_error(executor &, const std::error_code &) const noexcept; + template void on_error(executor&, const std::error_code &) noexcept; + + static void handle_exit(exec &, int, const std::error_code &) noexcept; + + template void on_success(executor &) const noexcept; + template void on_exec_setup(executor &) const noexcept; + template void on_setup(executor &) noexcept; + + template + std::function + on_exit_handler(executor &) const noexcept; + + handler(exec *const &); + ~handler() noexcept; +}; decltype(ircd::exec::log) ircd::exec::log @@ -32,11 +56,7 @@ ircd::util::instance_list::list ircd::exec::exec(const args &args, const opts &opt) -:id -{ - ++id_ctr -} -,opt +:opt { std::make_unique(opt) } @@ -56,40 +76,17 @@ ircd::exec::exec(const args &args, static_cast(ios::main.context()) ) } -,child { - std::make_unique - ( - fs::_path(path), - argv, - (boost::process::std_in) = pipe->first, - (boost::process::std_out & boost::process::std_err) = pipe->second - ) -} -,pid -{ - child->id() -} -{ - log::logf - { - log, this->opt->exec_log_level, - "id:%lu pid:%ld `%s' exec argc:%zu", - id, - pid, - path, - argv.size(), - }; } ircd::exec::~exec() noexcept try { - assert(opt); - if(opt->detach) - return; - - join(SIGTERM); + join(SIGKILL); + dock.wait([this] + { + return this->pid <= 0; + }); } catch(const std::exception &e) { @@ -100,6 +97,41 @@ catch(const std::exception &e) }; } +long +ircd::exec::run() +try +{ + assert(pid <= 0); + assert(!child); + + eptr = {}; + child = std::make_unique + ( + fs::_path(path), + argv, + (boost::process::std_in) = pipe->first, + (boost::process::std_out & boost::process::std_err) = pipe->second, + handler{this}, + static_cast(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 @@ -117,28 +149,7 @@ try }; child->wait(); - assert(!child->running()); - code = child->exit_code(); - - assert(opt); - const auto &level - { - code == 0? - opt->exit_log_level: - log::level::ERROR - }; - - log::logf - { - log, level, - "id:%lu pid:%ld `%s' exit (%ld)", - id, - pid, - path, - code, - }; - return code; } catch(const std::exception &e) @@ -158,6 +169,9 @@ catch(const std::exception &e) bool ircd::exec::signal(const int &sig) { + if(pid <= 0) + return false; + if(!child) return false; @@ -194,7 +208,6 @@ size_t ircd::exec::write(const const_buffers &bufs) { assert(pipe); - assert(child); auto &pipe { this->pipe->first @@ -225,7 +238,6 @@ size_t ircd::exec::read(const mutable_buffers &bufs) { assert(pipe); - assert(child); auto &pipe { this->pipe->second @@ -263,3 +275,204 @@ ircd::exec::read(const mutable_buffers &bufs) assert(ret); return ret; } + +// +// exec::handler +// + +ircd::exec::handler::handler(exec *const &e) +:e{e} +{ +} + +ircd::exec::handler::~handler() +noexcept +{ +} + +template +std::function +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 +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 +void +ircd::exec::handler::on_exec_setup(executor &ex) +const noexcept +{ + #if 0 // 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, + }; + #endif +} + +template +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 +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 +void +ircd::exec::handler::on_exec_error(executor &ex, + const std::error_code &ec) +const noexcept +{ + #if 0 // 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), + }; + #endif +} + +template +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), + }; +} diff --git a/modules/console.cc b/modules/console.cc index 2078dfde6..f86d7bfd7 100644 --- a/modules/console.cc +++ b/modules/console.cc @@ -16257,6 +16257,11 @@ console_cmd__exec(opt &out, const string_view &line) { argv, argc } }; + const auto pid + { + p.run() + }; + unique_mutable_buffer buf { 4_KiB, 4_KiB