// The Construct // // Copyright (C) The Construct Developers, Authors & Contributors // Copyright (C) 2016-2020 Jason Volk // // 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 #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 { "exec" }; decltype(ircd::exec::exec::id_ctr) ircd::exec::exec::id_ctr; template<> decltype(ircd::util::instance_list::allocator) ircd::util::instance_list::allocator{}; template<> decltype(ircd::util::instance_list::list) ircd::util::instance_list::list { allocator }; ircd::exec::exec(const args &args, const opts &opt) :opt { std::make_unique(opt) } ,path ( args.at(0) ) ,argv ( std::next(begin(args), 1), end(args) ) ,pipe { std::make_unique> ( static_cast(ios::main.context()), static_cast(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 ( 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(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 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 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 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 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 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), }; }