Path: blob/main/contrib/kyua/utils/process/executor.cpp
48178 views
// Copyright 2015 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/process/executor.ipp"2930#if defined(HAVE_CONFIG_H)31#include "config.h"32#endif3334extern "C" {35#include <sys/types.h>36#include <sys/wait.h>3738#include <signal.h>39}4041#include <forward_list>42#include <fstream>43#include <map>44#include <memory>45#include <stdexcept>46#include <utility>4748#include "utils/datetime.hpp"49#include "utils/format/macros.hpp"50#include "utils/fs/auto_cleaners.hpp"51#include "utils/fs/exceptions.hpp"52#include "utils/fs/operations.hpp"53#include "utils/fs/path.hpp"54#include "utils/logging/macros.hpp"55#include "utils/logging/operations.hpp"56#include "utils/noncopyable.hpp"57#include "utils/optional.ipp"58#include "utils/passwd.hpp"59#include "utils/process/child.ipp"60#include "utils/process/deadline_killer.hpp"61#include "utils/process/isolation.hpp"62#include "utils/process/operations.hpp"63#include "utils/process/status.hpp"64#include "utils/sanity.hpp"65#include "utils/signals/interrupts.hpp"66#include "utils/signals/timer.hpp"6768namespace datetime = utils::datetime;69namespace executor = utils::process::executor;70namespace fs = utils::fs;71namespace logging = utils::logging;72namespace passwd = utils::passwd;73namespace process = utils::process;74namespace signals = utils::signals;7576using utils::none;77using utils::optional;787980namespace {818283/// Template for temporary directories created by the executor.84static const char* work_directory_template = PACKAGE_TARNAME ".XXXXXX";858687/// Mapping of active subprocess PIDs to their execution data.88typedef std::map< int, executor::exec_handle > exec_handles_map;899091} // anonymous namespace929394/// Basename of the file containing the stdout of the subprocess.95const char* utils::process::executor::detail::stdout_name = "stdout.txt";969798/// Basename of the file containing the stderr of the subprocess.99const char* utils::process::executor::detail::stderr_name = "stderr.txt";100101102/// Basename of the subdirectory in which the subprocess is actually executed.103///104/// This is a subdirectory of the "unique work directory" generated for the105/// subprocess so that our code can create control files on disk and not106/// get them clobbered by the subprocess's activity.107const char* utils::process::executor::detail::work_subdir = "work";108109110/// Prepares a subprocess to run a user-provided hook in a controlled manner.111///112/// \param unprivileged_user User to switch to if not none.113/// \param control_directory Path to the subprocess-specific control directory.114/// \param work_directory Path to the subprocess-specific work directory.115void116utils::process::executor::detail::setup_child(117const optional< passwd::user > unprivileged_user,118const fs::path& control_directory,119const fs::path& work_directory)120{121logging::set_inmemory();122process::isolate_path(unprivileged_user, control_directory);123process::isolate_child(unprivileged_user, work_directory);124}125126127/// Internal implementation for the exec_handle class.128struct utils::process::executor::exec_handle::impl : utils::noncopyable {129/// PID of the process being run.130int pid;131132/// Path to the subprocess-specific work directory.133fs::path control_directory;134135/// Path to the subprocess's stdout file.136const fs::path stdout_file;137138/// Path to the subprocess's stderr file.139const fs::path stderr_file;140141/// Start time.142datetime::timestamp start_time;143144/// User the subprocess is running as if different than the current one.145const optional< passwd::user > unprivileged_user;146147/// Timer to kill the subprocess on activation.148process::deadline_killer timer;149150/// Number of owners of the on-disk state.151executor::detail::refcnt_t state_owners;152153/// Constructor.154///155/// \param pid_ PID of the forked process.156/// \param control_directory_ Path to the subprocess-specific work157/// directory.158/// \param stdout_file_ Path to the subprocess's stdout file.159/// \param stderr_file_ Path to the subprocess's stderr file.160/// \param start_time_ Timestamp of when this object was constructed.161/// \param timeout Maximum amount of time the subprocess can run for.162/// \param unprivileged_user_ User the subprocess is running as if163/// different than the current one.164/// \param [in,out] state_owners_ Number of owners of the on-disk state.165/// For first-time processes, this should be a new counter set to 0;166/// for followup processes, this should point to the same counter used167/// by the preceding process.168impl(const int pid_,169const fs::path& control_directory_,170const fs::path& stdout_file_,171const fs::path& stderr_file_,172const datetime::timestamp& start_time_,173const datetime::delta& timeout,174const optional< passwd::user > unprivileged_user_,175executor::detail::refcnt_t state_owners_) :176pid(pid_),177control_directory(control_directory_),178stdout_file(stdout_file_),179stderr_file(stderr_file_),180start_time(start_time_),181unprivileged_user(unprivileged_user_),182timer(timeout, pid_),183state_owners(state_owners_)184{185(*state_owners)++;186POST(*state_owners > 0);187}188};189190191/// Constructor.192///193/// \param pimpl Constructed internal implementation.194executor::exec_handle::exec_handle(std::shared_ptr< impl > pimpl) :195_pimpl(pimpl)196{197}198199200/// Destructor.201executor::exec_handle::~exec_handle(void)202{203}204205206/// Returns the PID of the process being run.207///208/// \return A PID.209int210executor::exec_handle::pid(void) const211{212return _pimpl->pid;213}214215216/// Returns the path to the subprocess-specific control directory.217///218/// This is where the executor may store control files.219///220/// \return The path to a directory that exists until cleanup() is called.221fs::path222executor::exec_handle::control_directory(void) const223{224return _pimpl->control_directory;225}226227228/// Returns the path to the subprocess-specific work directory.229///230/// This is guaranteed to be clear of files created by the executor.231///232/// \return The path to a directory that exists until cleanup() is called.233fs::path234executor::exec_handle::work_directory(void) const235{236return _pimpl->control_directory / detail::work_subdir;237}238239240/// Returns the path to the subprocess's stdout file.241///242/// \return The path to a file that exists until cleanup() is called.243const fs::path&244executor::exec_handle::stdout_file(void) const245{246return _pimpl->stdout_file;247}248249250/// Returns the path to the subprocess's stderr file.251///252/// \return The path to a file that exists until cleanup() is called.253const fs::path&254executor::exec_handle::stderr_file(void) const255{256return _pimpl->stderr_file;257}258259260/// Internal implementation for the exit_handle class.261struct utils::process::executor::exit_handle::impl : utils::noncopyable {262/// Original PID of the terminated subprocess.263///264/// Note that this PID is no longer valid and cannot be used on system265/// tables!266const int original_pid;267268/// Termination status of the subprocess, or none if it timed out.269const optional< process::status > status;270271/// The user the process ran as, if different than the current one.272const optional< passwd::user > unprivileged_user;273274/// Timestamp of when the subprocess was spawned.275const datetime::timestamp start_time;276277/// Timestamp of when wait() or wait_any() returned this object.278const datetime::timestamp end_time;279280/// Path to the subprocess-specific work directory.281const fs::path control_directory;282283/// Path to the subprocess's stdout file.284const fs::path stdout_file;285286/// Path to the subprocess's stderr file.287const fs::path stderr_file;288289/// Number of owners of the on-disk state.290///291/// This will be 1 if this exit_handle is the last holder of the on-disk292/// state, in which case cleanup() invocations will wipe the disk state.293/// For all other cases, this will hold a higher value.294detail::refcnt_t state_owners;295296/// Mutable pointer to the corresponding executor state.297///298/// This object references a member of the executor_handle that yielded this299/// exit_handle instance. We need this direct access to clean up after300/// ourselves when the handle is destroyed.301exec_handles_map& all_exec_handles;302303/// Whether the subprocess state has been cleaned yet or not.304///305/// Used to keep track of explicit calls to the public cleanup().306bool cleaned;307308/// Constructor.309///310/// \param original_pid_ Original PID of the terminated subprocess.311/// \param status_ Termination status of the subprocess, or none if312/// timed out.313/// \param unprivileged_user_ The user the process ran as, if different than314/// the current one.315/// \param start_time_ Timestamp of when the subprocess was spawned.316/// \param end_time_ Timestamp of when wait() or wait_any() returned this317/// object.318/// \param control_directory_ Path to the subprocess-specific work319/// directory.320/// \param stdout_file_ Path to the subprocess's stdout file.321/// \param stderr_file_ Path to the subprocess's stderr file.322/// \param [in,out] state_owners_ Number of owners of the on-disk state.323/// \param [in,out] all_exec_handles_ Global object keeping track of all324/// active executions for an executor. This is a pointer to a member of325/// the executor_handle object.326impl(const int original_pid_,327const optional< process::status > status_,328const optional< passwd::user > unprivileged_user_,329const datetime::timestamp& start_time_,330const datetime::timestamp& end_time_,331const fs::path& control_directory_,332const fs::path& stdout_file_,333const fs::path& stderr_file_,334detail::refcnt_t state_owners_,335exec_handles_map& all_exec_handles_) :336original_pid(original_pid_), status(status_),337unprivileged_user(unprivileged_user_),338start_time(start_time_), end_time(end_time_),339control_directory(control_directory_),340stdout_file(stdout_file_), stderr_file(stderr_file_),341state_owners(state_owners_),342all_exec_handles(all_exec_handles_), cleaned(false)343{344}345346/// Destructor.347~impl(void)348{349if (!cleaned) {350LW(F("Implicitly cleaning up exit_handle for exec_handle %s; "351"ignoring errors!") % original_pid);352try {353cleanup();354} catch (const std::runtime_error& error) {355LE(F("Subprocess cleanup failed: %s") % error.what());356}357}358}359360/// Cleans up the subprocess on-disk state.361///362/// \throw engine::error If the cleanup fails, especially due to the363/// inability to remove the work directory.364void365cleanup(void)366{367PRE(*state_owners > 0);368if (*state_owners == 1) {369LI(F("Cleaning up exit_handle for exec_handle %s") % original_pid);370fs::rm_r(control_directory);371} else {372LI(F("Not cleaning up exit_handle for exec_handle %s; "373"%s owners left") % original_pid % (*state_owners - 1));374}375// We must decrease our reference only after we have successfully376// cleaned up the control directory. Otherwise, the rm_r call would377// throw an exception, which would in turn invoke the implicit cleanup378// from the destructor, which would make us crash due to an invalid379// reference count.380(*state_owners)--;381// Marking this object as clean here, even if we did not do actually the382// cleaning above, is fine (albeit a bit confusing). Note that "another383// owner" refers to a handle for a different PID, so that handle will be384// the one issuing the cleanup.385all_exec_handles.erase(original_pid);386cleaned = true;387}388};389390391/// Constructor.392///393/// \param pimpl Constructed internal implementation.394executor::exit_handle::exit_handle(std::shared_ptr< impl > pimpl) :395_pimpl(pimpl)396{397}398399400/// Destructor.401executor::exit_handle::~exit_handle(void)402{403}404405406/// Cleans up the subprocess status.407///408/// This function should be called explicitly as it provides the means to409/// control any exceptions raised during cleanup. Do not rely on the destructor410/// to clean things up.411///412/// \throw engine::error If the cleanup fails, especially due to the inability413/// to remove the work directory.414void415executor::exit_handle::cleanup(void)416{417PRE(!_pimpl->cleaned);418_pimpl->cleanup();419POST(_pimpl->cleaned);420}421422423/// Gets the current number of owners of the on-disk data.424///425/// \return A shared reference counter. Even though this function is marked as426/// const, the return value is intentionally mutable because we need to update427/// reference counts from different but related processes. This is why this428/// method is not public.429std::shared_ptr< std::size_t >430executor::exit_handle::state_owners(void) const431{432return _pimpl->state_owners;433}434435436/// Returns the original PID corresponding to the terminated subprocess.437///438/// \return An exec_handle.439int440executor::exit_handle::original_pid(void) const441{442return _pimpl->original_pid;443}444445446/// Returns the process termination status of the subprocess.447///448/// \return A process termination status, or none if the subprocess timed out.449const optional< process::status >&450executor::exit_handle::status(void) const451{452return _pimpl->status;453}454455456/// Returns the user the process ran as if different than the current one.457///458/// \return None if the credentials of the process were the same as the current459/// one, or else a user.460const optional< passwd::user >&461executor::exit_handle::unprivileged_user(void) const462{463return _pimpl->unprivileged_user;464}465466467/// Returns the timestamp of when the subprocess was spawned.468///469/// \return A timestamp.470const datetime::timestamp&471executor::exit_handle::start_time(void) const472{473return _pimpl->start_time;474}475476477/// Returns the timestamp of when wait() or wait_any() returned this object.478///479/// \return A timestamp.480const datetime::timestamp&481executor::exit_handle::end_time(void) const482{483return _pimpl->end_time;484}485486487/// Returns the path to the subprocess-specific control directory.488///489/// This is where the executor may store control files.490///491/// \return The path to a directory that exists until cleanup() is called.492fs::path493executor::exit_handle::control_directory(void) const494{495return _pimpl->control_directory;496}497498499/// Returns the path to the subprocess-specific work directory.500///501/// This is guaranteed to be clear of files created by the executor.502///503/// \return The path to a directory that exists until cleanup() is called.504fs::path505executor::exit_handle::work_directory(void) const506{507return _pimpl->control_directory / detail::work_subdir;508}509510511/// Returns the path to the subprocess's stdout file.512///513/// \return The path to a file that exists until cleanup() is called.514const fs::path&515executor::exit_handle::stdout_file(void) const516{517return _pimpl->stdout_file;518}519520521/// Returns the path to the subprocess's stderr file.522///523/// \return The path to a file that exists until cleanup() is called.524const fs::path&525executor::exit_handle::stderr_file(void) const526{527return _pimpl->stderr_file;528}529530531/// Internal implementation for the executor_handle.532///533/// Because the executor is a singleton, these essentially is a container for534/// global variables.535struct utils::process::executor::executor_handle::impl : utils::noncopyable {536/// Numeric counter of executed subprocesses.537///538/// This is used to generate a unique identifier for each subprocess as an539/// easy mechanism to discern their unique work directories.540size_t last_subprocess;541542/// Interrupts handler.543std::unique_ptr< signals::interrupts_handler > interrupts_handler;544545/// Root work directory for all executed subprocesses.546std::unique_ptr< fs::auto_directory > root_work_directory;547548/// Mapping of PIDs to the data required at run time.549exec_handles_map all_exec_handles;550551/// Former members of all_exec_handles removed due to PID reuse.552std::forward_list<exec_handle> stale_exec_handles;553554/// Whether the executor state has been cleaned yet or not.555///556/// Used to keep track of explicit calls to the public cleanup().557bool cleaned;558559/// Constructor.560impl(void) :561last_subprocess(0),562interrupts_handler(new signals::interrupts_handler()),563root_work_directory(new fs::auto_directory(564fs::auto_directory::mkdtemp_public(work_directory_template))),565all_exec_handles(),566stale_exec_handles(),567cleaned(false)568{569}570571/// Destructor.572~impl(void)573{574if (!cleaned) {575LW("Implicitly cleaning up executor; ignoring errors!");576try {577cleanup();578cleaned = true;579} catch (const std::runtime_error& error) {580LE(F("Executor global cleanup failed: %s") % error.what());581}582}583}584585/// Cleans up the executor state.586void587cleanup(void)588{589PRE(!cleaned);590591for (exec_handles_map::const_iterator iter = all_exec_handles.begin();592iter != all_exec_handles.end(); ++iter) {593const int& pid = (*iter).first;594const exec_handle& data = (*iter).second;595596process::terminate_group(pid);597int status;598if (::waitpid(pid, &status, 0) == -1) {599// Should not happen.600LW(F("Failed to wait for PID %s") % pid);601}602603try {604fs::rm_r(data.control_directory());605} catch (const fs::error& e) {606LE(F("Failed to clean up subprocess work directory %s: %s") %607data.control_directory() % e.what());608}609}610all_exec_handles.clear();611612for (auto iter : stale_exec_handles) {613// The process already exited, so no need to kill and wait.614try {615fs::rm_r(iter.control_directory());616} catch (const fs::error& e) {617LE(F("Failed to clean up stale subprocess work directory "618"%s: %s") % iter.control_directory() % e.what());619}620}621stale_exec_handles.clear();622623try {624// The following only causes the work directory to be deleted, not625// any of its contents, so we expect this to always succeed. This626// *should* be sufficient because, in the loop above, we have627// individually wiped the subdirectories of any still-unclean628// subprocesses.629root_work_directory->cleanup();630} catch (const fs::error& e) {631LE(F("Failed to clean up executor work directory %s: %s; "632"this could be an internal error or a buggy test") %633root_work_directory->directory() % e.what());634}635root_work_directory.reset();636637interrupts_handler->unprogram();638interrupts_handler.reset();639}640641/// Common code to run after any of the wait calls.642///643/// \param original_pid The PID of the terminated subprocess.644/// \param status The exit status of the terminated subprocess.645///646/// \return A pointer to an object describing the waited-for subprocess.647executor::exit_handle648post_wait(const int original_pid, const process::status& status)649{650PRE(original_pid == status.dead_pid());651LI(F("Waited for subprocess with exec_handle %s") % original_pid);652653process::terminate_group(status.dead_pid());654655const exec_handles_map::iterator iter = all_exec_handles.find(656original_pid);657exec_handle& data = (*iter).second;658data._pimpl->timer.unprogram();659660// It is tempting to assert here (and old code did) that, if the timer661// has fired, the process has been forcibly killed by us. This is not662// always the case though: for short-lived processes and with very short663// timeouts (think 1ms), it is possible for scheduling decisions to664// allow the subprocess to finish while at the same time cause the timer665// to fire. So we do not assert this any longer and just rely on the666// timer expiration to check if the process timed out or not. If the667// process did finish but the timer expired... oh well, we do not detect668// this correctly but we don't care because this should not really669// happen.670671if (!fs::exists(data.stdout_file())) {672std::ofstream new_stdout(data.stdout_file().c_str());673}674if (!fs::exists(data.stderr_file())) {675std::ofstream new_stderr(data.stderr_file().c_str());676}677678return exit_handle(std::shared_ptr< exit_handle::impl >(679new exit_handle::impl(680data.pid(),681data._pimpl->timer.fired() ?682none : utils::make_optional(status),683data._pimpl->unprivileged_user,684data._pimpl->start_time, datetime::timestamp::now(),685data.control_directory(),686data.stdout_file(),687data.stderr_file(),688data._pimpl->state_owners,689all_exec_handles)));690}691692executor::exit_handle693reap(const pid_t original_pid)694{695const exec_handles_map::iterator iter = all_exec_handles.find(696original_pid);697exec_handle& data = (*iter).second;698data._pimpl->timer.unprogram();699700if (!fs::exists(data.stdout_file())) {701std::ofstream new_stdout(data.stdout_file().c_str());702}703if (!fs::exists(data.stderr_file())) {704std::ofstream new_stderr(data.stderr_file().c_str());705}706707return exit_handle(std::shared_ptr< exit_handle::impl >(708new exit_handle::impl(709data.pid(),710none,711data._pimpl->unprivileged_user,712data._pimpl->start_time, datetime::timestamp::now(),713data.control_directory(),714data.stdout_file(),715data.stderr_file(),716data._pimpl->state_owners,717all_exec_handles)));718}719};720721722/// Constructor.723executor::executor_handle::executor_handle(void) throw() : _pimpl(new impl())724{725}726727728/// Destructor.729executor::executor_handle::~executor_handle(void)730{731}732733734/// Queries the path to the root of the work directory for all subprocesses.735///736/// \return A path.737const fs::path&738executor::executor_handle::root_work_directory(void) const739{740return _pimpl->root_work_directory->directory();741}742743744/// Cleans up the executor state.745///746/// This function should be called explicitly as it provides the means to747/// control any exceptions raised during cleanup. Do not rely on the destructor748/// to clean things up.749///750/// \throw engine::error If there are problems cleaning up the executor.751void752executor::executor_handle::cleanup(void)753{754PRE(!_pimpl->cleaned);755_pimpl->cleanup();756_pimpl->cleaned = true;757}758759760/// Initializes the executor.761///762/// \pre This function can only be called if there is no other executor_handle763/// object alive.764///765/// \return A handle to the operations of the executor.766executor::executor_handle767executor::setup(void)768{769return executor_handle();770}771772773/// Pre-helper for the spawn() method.774///775/// \return The created control directory for the subprocess.776fs::path777executor::executor_handle::spawn_pre(void)778{779signals::check_interrupt();780781++_pimpl->last_subprocess;782783const fs::path control_directory =784_pimpl->root_work_directory->directory() /785(F("%s") % _pimpl->last_subprocess);786fs::mkdir_p(control_directory / detail::work_subdir, 0755);787788return control_directory;789}790791792/// Post-helper for the spawn() method.793///794/// \param control_directory Control directory as returned by spawn_pre().795/// \param stdout_file Path to the subprocess' stdout.796/// \param stderr_file Path to the subprocess' stderr.797/// \param timeout Maximum amount of time the subprocess can run for.798/// \param unprivileged_user If not none, user to switch to before execution.799/// \param child The process created by spawn().800///801/// \return The execution handle of the started subprocess.802executor::exec_handle803executor::executor_handle::spawn_post(804const fs::path& control_directory,805const fs::path& stdout_file,806const fs::path& stderr_file,807const datetime::delta& timeout,808const optional< passwd::user > unprivileged_user,809std::unique_ptr< process::child > child)810{811const exec_handle handle(std::shared_ptr< exec_handle::impl >(812new exec_handle::impl(813child->pid(),814control_directory,815stdout_file,816stderr_file,817datetime::timestamp::now(),818timeout,819unprivileged_user,820detail::refcnt_t(new detail::refcnt_t::element_type(0)))));821const auto value = exec_handles_map::value_type(handle.pid(), handle);822auto insert_pair = _pimpl->all_exec_handles.insert(value);823if (!insert_pair.second) {824LI(F("PID %s already in all_exec_handles") % handle.pid());825_pimpl->stale_exec_handles.push_front(insert_pair.first->second);826_pimpl->all_exec_handles.erase(insert_pair.first);827insert_pair = _pimpl->all_exec_handles.insert(value);828INV_MSG(insert_pair.second, F("PID %s still in all_exec_handles") %829handle.pid());830}831LI(F("Spawned subprocess with exec_handle %s") % handle.pid());832return handle;833}834835836/// Pre-helper for the spawn_followup() method.837void838executor::executor_handle::spawn_followup_pre(void)839{840signals::check_interrupt();841}842843844/// Post-helper for the spawn_followup() method.845///846/// \param base Exit handle of the subprocess to use as context.847/// \param timeout Maximum amount of time the subprocess can run for.848/// \param child The process created by spawn_followup().849///850/// \return The execution handle of the started subprocess.851executor::exec_handle852executor::executor_handle::spawn_followup_post(853const exit_handle& base,854const datetime::delta& timeout,855std::unique_ptr< process::child > child)856{857INV(*base.state_owners() > 0);858const exec_handle handle(std::shared_ptr< exec_handle::impl >(859new exec_handle::impl(860child->pid(),861base.control_directory(),862base.stdout_file(),863base.stderr_file(),864datetime::timestamp::now(),865timeout,866base.unprivileged_user(),867base.state_owners())));868const auto value = exec_handles_map::value_type(handle.pid(), handle);869auto insert_pair = _pimpl->all_exec_handles.insert(value);870if (!insert_pair.second) {871LI(F("PID %s already in all_exec_handles") % handle.pid());872_pimpl->stale_exec_handles.push_front(insert_pair.first->second);873_pimpl->all_exec_handles.erase(insert_pair.first);874insert_pair = _pimpl->all_exec_handles.insert(value);875INV_MSG(insert_pair.second, F("PID %s still in all_exec_handles") %876handle.pid());877}878LI(F("Spawned subprocess with exec_handle %s") % handle.pid());879return handle;880}881882883/// Waits for completion of any forked process.884///885/// \param exec_handle The handle of the process to wait for.886///887/// \return A pointer to an object describing the waited-for subprocess.888executor::exit_handle889executor::executor_handle::wait(const exec_handle exec_handle)890{891signals::check_interrupt();892const process::status status = process::wait(exec_handle.pid());893return _pimpl->post_wait(exec_handle.pid(), status);894}895896897/// Waits for completion of any forked process.898///899/// \return A pointer to an object describing the waited-for subprocess.900executor::exit_handle901executor::executor_handle::wait_any(void)902{903signals::check_interrupt();904const process::status status = process::wait_any();905return _pimpl->post_wait(status.dead_pid(), status);906}907908909/// Forms exit_handle for the given PID subprocess.910///911/// Can be used in the cases when we want to do cleanup(s) of a killed test912/// subprocess, but we do not have exit handle as we usually do after normal913/// wait mechanism.914///915/// \return A pointer to an object describing the subprocess.916executor::exit_handle917executor::executor_handle::reap(const int pid)918{919return _pimpl->reap(pid);920}921922923/// Checks if an interrupt has fired.924///925/// Calls to this function should be sprinkled in strategic places through the926/// code protected by an interrupts_handler object.927///928/// This is just a wrapper over signals::check_interrupt() to avoid leaking this929/// dependency to the caller.930///931/// \throw signals::interrupted_error If there has been an interrupt.932void933executor::executor_handle::check_interrupt(void) const934{935signals::check_interrupt();936}937938939