diff options
Diffstat (limited to 'utils/process/executor.cpp')
-rw-r--r-- | utils/process/executor.cpp | 869 |
1 files changed, 869 insertions, 0 deletions
diff --git a/utils/process/executor.cpp b/utils/process/executor.cpp new file mode 100644 index 000000000000..dbdf31268f86 --- /dev/null +++ b/utils/process/executor.cpp @@ -0,0 +1,869 @@ +// Copyright 2015 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "utils/process/executor.ipp" + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +extern "C" { +#include <sys/types.h> +#include <sys/wait.h> + +#include <signal.h> +} + +#include <fstream> +#include <map> +#include <memory> +#include <stdexcept> + +#include "utils/datetime.hpp" +#include "utils/format/macros.hpp" +#include "utils/fs/auto_cleaners.hpp" +#include "utils/fs/exceptions.hpp" +#include "utils/fs/operations.hpp" +#include "utils/fs/path.hpp" +#include "utils/logging/macros.hpp" +#include "utils/logging/operations.hpp" +#include "utils/noncopyable.hpp" +#include "utils/optional.ipp" +#include "utils/passwd.hpp" +#include "utils/process/child.ipp" +#include "utils/process/deadline_killer.hpp" +#include "utils/process/isolation.hpp" +#include "utils/process/operations.hpp" +#include "utils/process/status.hpp" +#include "utils/sanity.hpp" +#include "utils/signals/interrupts.hpp" +#include "utils/signals/timer.hpp" + +namespace datetime = utils::datetime; +namespace executor = utils::process::executor; +namespace fs = utils::fs; +namespace logging = utils::logging; +namespace passwd = utils::passwd; +namespace process = utils::process; +namespace signals = utils::signals; + +using utils::none; +using utils::optional; + + +namespace { + + +/// Template for temporary directories created by the executor. +static const char* work_directory_template = PACKAGE_TARNAME ".XXXXXX"; + + +/// Mapping of active subprocess PIDs to their execution data. +typedef std::map< int, executor::exec_handle > exec_handles_map; + + +} // anonymous namespace + + +/// Basename of the file containing the stdout of the subprocess. +const char* utils::process::executor::detail::stdout_name = "stdout.txt"; + + +/// Basename of the file containing the stderr of the subprocess. +const char* utils::process::executor::detail::stderr_name = "stderr.txt"; + + +/// Basename of the subdirectory in which the subprocess is actually executed. +/// +/// This is a subdirectory of the "unique work directory" generated for the +/// subprocess so that our code can create control files on disk and not +/// get them clobbered by the subprocess's activity. +const char* utils::process::executor::detail::work_subdir = "work"; + + +/// Prepares a subprocess to run a user-provided hook in a controlled manner. +/// +/// \param unprivileged_user User to switch to if not none. +/// \param control_directory Path to the subprocess-specific control directory. +/// \param work_directory Path to the subprocess-specific work directory. +void +utils::process::executor::detail::setup_child( + const optional< passwd::user > unprivileged_user, + const fs::path& control_directory, + const fs::path& work_directory) +{ + logging::set_inmemory(); + process::isolate_path(unprivileged_user, control_directory); + process::isolate_child(unprivileged_user, work_directory); +} + + +/// Internal implementation for the exit_handle class. +struct utils::process::executor::exec_handle::impl : utils::noncopyable { + /// PID of the process being run. + int pid; + + /// Path to the subprocess-specific work directory. + fs::path control_directory; + + /// Path to the subprocess's stdout file. + const fs::path stdout_file; + + /// Path to the subprocess's stderr file. + const fs::path stderr_file; + + /// Start time. + datetime::timestamp start_time; + + /// User the subprocess is running as if different than the current one. + const optional< passwd::user > unprivileged_user; + + /// Timer to kill the subprocess on activation. + process::deadline_killer timer; + + /// Number of owners of the on-disk state. + executor::detail::refcnt_t state_owners; + + /// Constructor. + /// + /// \param pid_ PID of the forked process. + /// \param control_directory_ Path to the subprocess-specific work + /// directory. + /// \param stdout_file_ Path to the subprocess's stdout file. + /// \param stderr_file_ Path to the subprocess's stderr file. + /// \param start_time_ Timestamp of when this object was constructed. + /// \param timeout Maximum amount of time the subprocess can run for. + /// \param unprivileged_user_ User the subprocess is running as if + /// different than the current one. + /// \param [in,out] state_owners_ Number of owners of the on-disk state. + /// For first-time processes, this should be a new counter set to 0; + /// for followup processes, this should point to the same counter used + /// by the preceding process. + impl(const int pid_, + const fs::path& control_directory_, + const fs::path& stdout_file_, + const fs::path& stderr_file_, + const datetime::timestamp& start_time_, + const datetime::delta& timeout, + const optional< passwd::user > unprivileged_user_, + executor::detail::refcnt_t state_owners_) : + pid(pid_), + control_directory(control_directory_), + stdout_file(stdout_file_), + stderr_file(stderr_file_), + start_time(start_time_), + unprivileged_user(unprivileged_user_), + timer(timeout, pid_), + state_owners(state_owners_) + { + (*state_owners)++; + POST(*state_owners > 0); + } +}; + + +/// Constructor. +/// +/// \param pimpl Constructed internal implementation. +executor::exec_handle::exec_handle(std::shared_ptr< impl > pimpl) : + _pimpl(pimpl) +{ +} + + +/// Destructor. +executor::exec_handle::~exec_handle(void) +{ +} + + +/// Returns the PID of the process being run. +/// +/// \return A PID. +int +executor::exec_handle::pid(void) const +{ + return _pimpl->pid; +} + + +/// Returns the path to the subprocess-specific control directory. +/// +/// This is where the executor may store control files. +/// +/// \return The path to a directory that exists until cleanup() is called. +fs::path +executor::exec_handle::control_directory(void) const +{ + return _pimpl->control_directory; +} + + +/// Returns the path to the subprocess-specific work directory. +/// +/// This is guaranteed to be clear of files created by the executor. +/// +/// \return The path to a directory that exists until cleanup() is called. +fs::path +executor::exec_handle::work_directory(void) const +{ + return _pimpl->control_directory / detail::work_subdir; +} + + +/// Returns the path to the subprocess's stdout file. +/// +/// \return The path to a file that exists until cleanup() is called. +const fs::path& +executor::exec_handle::stdout_file(void) const +{ + return _pimpl->stdout_file; +} + + +/// Returns the path to the subprocess's stderr file. +/// +/// \return The path to a file that exists until cleanup() is called. +const fs::path& +executor::exec_handle::stderr_file(void) const +{ + return _pimpl->stderr_file; +} + + +/// Internal implementation for the exit_handle class. +struct utils::process::executor::exit_handle::impl : utils::noncopyable { + /// Original PID of the terminated subprocess. + /// + /// Note that this PID is no longer valid and cannot be used on system + /// tables! + const int original_pid; + + /// Termination status of the subprocess, or none if it timed out. + const optional< process::status > status; + + /// The user the process ran as, if different than the current one. + const optional< passwd::user > unprivileged_user; + + /// Timestamp of when the subprocess was spawned. + const datetime::timestamp start_time; + + /// Timestamp of when wait() or wait_any() returned this object. + const datetime::timestamp end_time; + + /// Path to the subprocess-specific work directory. + const fs::path control_directory; + + /// Path to the subprocess's stdout file. + const fs::path stdout_file; + + /// Path to the subprocess's stderr file. + const fs::path stderr_file; + + /// Number of owners of the on-disk state. + /// + /// This will be 1 if this exit_handle is the last holder of the on-disk + /// state, in which case cleanup() invocations will wipe the disk state. + /// For all other cases, this will hold a higher value. + detail::refcnt_t state_owners; + + /// Mutable pointer to the corresponding executor state. + /// + /// This object references a member of the executor_handle that yielded this + /// exit_handle instance. We need this direct access to clean up after + /// ourselves when the handle is destroyed. + exec_handles_map& all_exec_handles; + + /// Whether the subprocess state has been cleaned yet or not. + /// + /// Used to keep track of explicit calls to the public cleanup(). + bool cleaned; + + /// Constructor. + /// + /// \param original_pid_ Original PID of the terminated subprocess. + /// \param status_ Termination status of the subprocess, or none if + /// timed out. + /// \param unprivileged_user_ The user the process ran as, if different than + /// the current one. + /// \param start_time_ Timestamp of when the subprocess was spawned. + /// \param end_time_ Timestamp of when wait() or wait_any() returned this + /// object. + /// \param control_directory_ Path to the subprocess-specific work + /// directory. + /// \param stdout_file_ Path to the subprocess's stdout file. + /// \param stderr_file_ Path to the subprocess's stderr file. + /// \param [in,out] state_owners_ Number of owners of the on-disk state. + /// \param [in,out] all_exec_handles_ Global object keeping track of all + /// active executions for an executor. This is a pointer to a member of + /// the executor_handle object. + impl(const int original_pid_, + const optional< process::status > status_, + const optional< passwd::user > unprivileged_user_, + const datetime::timestamp& start_time_, + const datetime::timestamp& end_time_, + const fs::path& control_directory_, + const fs::path& stdout_file_, + const fs::path& stderr_file_, + detail::refcnt_t state_owners_, + exec_handles_map& all_exec_handles_) : + original_pid(original_pid_), status(status_), + unprivileged_user(unprivileged_user_), + start_time(start_time_), end_time(end_time_), + control_directory(control_directory_), + stdout_file(stdout_file_), stderr_file(stderr_file_), + state_owners(state_owners_), + all_exec_handles(all_exec_handles_), cleaned(false) + { + } + + /// Destructor. + ~impl(void) + { + if (!cleaned) { + LW(F("Implicitly cleaning up exit_handle for exec_handle %s; " + "ignoring errors!") % original_pid); + try { + cleanup(); + } catch (const std::runtime_error& error) { + LE(F("Subprocess cleanup failed: %s") % error.what()); + } + } + } + + /// Cleans up the subprocess on-disk state. + /// + /// \throw engine::error If the cleanup fails, especially due to the + /// inability to remove the work directory. + void + cleanup(void) + { + PRE(*state_owners > 0); + if (*state_owners == 1) { + LI(F("Cleaning up exit_handle for exec_handle %s") % original_pid); + fs::rm_r(control_directory); + } else { + LI(F("Not cleaning up exit_handle for exec_handle %s; " + "%s owners left") % original_pid % (*state_owners - 1)); + } + // We must decrease our reference only after we have successfully + // cleaned up the control directory. Otherwise, the rm_r call would + // throw an exception, which would in turn invoke the implicit cleanup + // from the destructor, which would make us crash due to an invalid + // reference count. + (*state_owners)--; + // Marking this object as clean here, even if we did not do actually the + // cleaning above, is fine (albeit a bit confusing). Note that "another + // owner" refers to a handle for a different PID, so that handle will be + // the one issuing the cleanup. + all_exec_handles.erase(original_pid); + cleaned = true; + } +}; + + +/// Constructor. +/// +/// \param pimpl Constructed internal implementation. +executor::exit_handle::exit_handle(std::shared_ptr< impl > pimpl) : + _pimpl(pimpl) +{ +} + + +/// Destructor. +executor::exit_handle::~exit_handle(void) +{ +} + + +/// Cleans up the subprocess status. +/// +/// This function should be called explicitly as it provides the means to +/// control any exceptions raised during cleanup. Do not rely on the destructor +/// to clean things up. +/// +/// \throw engine::error If the cleanup fails, especially due to the inability +/// to remove the work directory. +void +executor::exit_handle::cleanup(void) +{ + PRE(!_pimpl->cleaned); + _pimpl->cleanup(); + POST(_pimpl->cleaned); +} + + +/// Gets the current number of owners of the on-disk data. +/// +/// \return A shared reference counter. Even though this function is marked as +/// const, the return value is intentionally mutable because we need to update +/// reference counts from different but related processes. This is why this +/// method is not public. +std::shared_ptr< std::size_t > +executor::exit_handle::state_owners(void) const +{ + return _pimpl->state_owners; +} + + +/// Returns the original PID corresponding to the terminated subprocess. +/// +/// \return An exec_handle. +int +executor::exit_handle::original_pid(void) const +{ + return _pimpl->original_pid; +} + + +/// Returns the process termination status of the subprocess. +/// +/// \return A process termination status, or none if the subprocess timed out. +const optional< process::status >& +executor::exit_handle::status(void) const +{ + return _pimpl->status; +} + + +/// Returns the user the process ran as if different than the current one. +/// +/// \return None if the credentials of the process were the same as the current +/// one, or else a user. +const optional< passwd::user >& +executor::exit_handle::unprivileged_user(void) const +{ + return _pimpl->unprivileged_user; +} + + +/// Returns the timestamp of when the subprocess was spawned. +/// +/// \return A timestamp. +const datetime::timestamp& +executor::exit_handle::start_time(void) const +{ + return _pimpl->start_time; +} + + +/// Returns the timestamp of when wait() or wait_any() returned this object. +/// +/// \return A timestamp. +const datetime::timestamp& +executor::exit_handle::end_time(void) const +{ + return _pimpl->end_time; +} + + +/// Returns the path to the subprocess-specific control directory. +/// +/// This is where the executor may store control files. +/// +/// \return The path to a directory that exists until cleanup() is called. +fs::path +executor::exit_handle::control_directory(void) const +{ + return _pimpl->control_directory; +} + + +/// Returns the path to the subprocess-specific work directory. +/// +/// This is guaranteed to be clear of files created by the executor. +/// +/// \return The path to a directory that exists until cleanup() is called. +fs::path +executor::exit_handle::work_directory(void) const +{ + return _pimpl->control_directory / detail::work_subdir; +} + + +/// Returns the path to the subprocess's stdout file. +/// +/// \return The path to a file that exists until cleanup() is called. +const fs::path& +executor::exit_handle::stdout_file(void) const +{ + return _pimpl->stdout_file; +} + + +/// Returns the path to the subprocess's stderr file. +/// +/// \return The path to a file that exists until cleanup() is called. +const fs::path& +executor::exit_handle::stderr_file(void) const +{ + return _pimpl->stderr_file; +} + + +/// Internal implementation for the executor_handle. +/// +/// Because the executor is a singleton, these essentially is a container for +/// global variables. +struct utils::process::executor::executor_handle::impl : utils::noncopyable { + /// Numeric counter of executed subprocesses. + /// + /// This is used to generate a unique identifier for each subprocess as an + /// easy mechanism to discern their unique work directories. + size_t last_subprocess; + + /// Interrupts handler. + std::auto_ptr< signals::interrupts_handler > interrupts_handler; + + /// Root work directory for all executed subprocesses. + std::auto_ptr< fs::auto_directory > root_work_directory; + + /// Mapping of PIDs to the data required at run time. + exec_handles_map all_exec_handles; + + /// Whether the executor state has been cleaned yet or not. + /// + /// Used to keep track of explicit calls to the public cleanup(). + bool cleaned; + + /// Constructor. + impl(void) : + last_subprocess(0), + interrupts_handler(new signals::interrupts_handler()), + root_work_directory(new fs::auto_directory( + fs::auto_directory::mkdtemp_public(work_directory_template))), + cleaned(false) + { + } + + /// Destructor. + ~impl(void) + { + if (!cleaned) { + LW("Implicitly cleaning up executor; ignoring errors!"); + try { + cleanup(); + cleaned = true; + } catch (const std::runtime_error& error) { + LE(F("Executor global cleanup failed: %s") % error.what()); + } + } + } + + /// Cleans up the executor state. + void + cleanup(void) + { + PRE(!cleaned); + + for (exec_handles_map::const_iterator iter = all_exec_handles.begin(); + iter != all_exec_handles.end(); ++iter) { + const int& pid = (*iter).first; + const exec_handle& data = (*iter).second; + + process::terminate_group(pid); + int status; + if (::waitpid(pid, &status, 0) == -1) { + // Should not happen. + LW(F("Failed to wait for PID %s") % pid); + } + + try { + fs::rm_r(data.control_directory()); + } catch (const fs::error& e) { + LE(F("Failed to clean up subprocess work directory %s: %s") % + data.control_directory() % e.what()); + } + } + all_exec_handles.clear(); + + try { + // The following only causes the work directory to be deleted, not + // any of its contents, so we expect this to always succeed. This + // *should* be sufficient because, in the loop above, we have + // individually wiped the subdirectories of any still-unclean + // subprocesses. + root_work_directory->cleanup(); + } catch (const fs::error& e) { + LE(F("Failed to clean up executor work directory %s: %s; this is " + "an internal error") % root_work_directory->directory() + % e.what()); + } + root_work_directory.reset(NULL); + + interrupts_handler->unprogram(); + interrupts_handler.reset(NULL); + } + + /// Common code to run after any of the wait calls. + /// + /// \param original_pid The PID of the terminated subprocess. + /// \param status The exit status of the terminated subprocess. + /// + /// \return A pointer to an object describing the waited-for subprocess. + executor::exit_handle + post_wait(const int original_pid, const process::status& status) + { + PRE(original_pid == status.dead_pid()); + LI(F("Waited for subprocess with exec_handle %s") % original_pid); + + process::terminate_group(status.dead_pid()); + + const exec_handles_map::iterator iter = all_exec_handles.find( + original_pid); + exec_handle& data = (*iter).second; + data._pimpl->timer.unprogram(); + + // It is tempting to assert here (and old code did) that, if the timer + // has fired, the process has been forcibly killed by us. This is not + // always the case though: for short-lived processes and with very short + // timeouts (think 1ms), it is possible for scheduling decisions to + // allow the subprocess to finish while at the same time cause the timer + // to fire. So we do not assert this any longer and just rely on the + // timer expiration to check if the process timed out or not. If the + // process did finish but the timer expired... oh well, we do not detect + // this correctly but we don't care because this should not really + // happen. + + if (!fs::exists(data.stdout_file())) { + std::ofstream new_stdout(data.stdout_file().c_str()); + } + if (!fs::exists(data.stderr_file())) { + std::ofstream new_stderr(data.stderr_file().c_str()); + } + + return exit_handle(std::shared_ptr< exit_handle::impl >( + new exit_handle::impl( + data.pid(), + data._pimpl->timer.fired() ? + none : utils::make_optional(status), + data._pimpl->unprivileged_user, + data._pimpl->start_time, datetime::timestamp::now(), + data.control_directory(), + data.stdout_file(), + data.stderr_file(), + data._pimpl->state_owners, + all_exec_handles))); + } +}; + + +/// Constructor. +executor::executor_handle::executor_handle(void) throw() : _pimpl(new impl()) +{ +} + + +/// Destructor. +executor::executor_handle::~executor_handle(void) +{ +} + + +/// Queries the path to the root of the work directory for all subprocesses. +/// +/// \return A path. +const fs::path& +executor::executor_handle::root_work_directory(void) const +{ + return _pimpl->root_work_directory->directory(); +} + + +/// Cleans up the executor state. +/// +/// This function should be called explicitly as it provides the means to +/// control any exceptions raised during cleanup. Do not rely on the destructor +/// to clean things up. +/// +/// \throw engine::error If there are problems cleaning up the executor. +void +executor::executor_handle::cleanup(void) +{ + PRE(!_pimpl->cleaned); + _pimpl->cleanup(); + _pimpl->cleaned = true; +} + + +/// Initializes the executor. +/// +/// \pre This function can only be called if there is no other executor_handle +/// object alive. +/// +/// \return A handle to the operations of the executor. +executor::executor_handle +executor::setup(void) +{ + return executor_handle(); +} + + +/// Pre-helper for the spawn() method. +/// +/// \return The created control directory for the subprocess. +fs::path +executor::executor_handle::spawn_pre(void) +{ + signals::check_interrupt(); + + ++_pimpl->last_subprocess; + + const fs::path control_directory = + _pimpl->root_work_directory->directory() / + (F("%s") % _pimpl->last_subprocess); + fs::mkdir_p(control_directory / detail::work_subdir, 0755); + + return control_directory; +} + + +/// Post-helper for the spawn() method. +/// +/// \param control_directory Control directory as returned by spawn_pre(). +/// \param stdout_file Path to the subprocess' stdout. +/// \param stderr_file Path to the subprocess' stderr. +/// \param timeout Maximum amount of time the subprocess can run for. +/// \param unprivileged_user If not none, user to switch to before execution. +/// \param child The process created by spawn(). +/// +/// \return The execution handle of the started subprocess. +executor::exec_handle +executor::executor_handle::spawn_post( + const fs::path& control_directory, + const fs::path& stdout_file, + const fs::path& stderr_file, + const datetime::delta& timeout, + const optional< passwd::user > unprivileged_user, + std::auto_ptr< process::child > child) +{ + const exec_handle handle(std::shared_ptr< exec_handle::impl >( + new exec_handle::impl( + child->pid(), + control_directory, + stdout_file, + stderr_file, + datetime::timestamp::now(), + timeout, + unprivileged_user, + detail::refcnt_t(new detail::refcnt_t::element_type(0))))); + INV_MSG(_pimpl->all_exec_handles.find(handle.pid()) == + _pimpl->all_exec_handles.end(), + F("PID %s already in all_exec_handles; not properly cleaned " + "up or reused too fast") % handle.pid());; + _pimpl->all_exec_handles.insert(exec_handles_map::value_type( + handle.pid(), handle)); + LI(F("Spawned subprocess with exec_handle %s") % handle.pid()); + return handle; +} + + +/// Pre-helper for the spawn_followup() method. +void +executor::executor_handle::spawn_followup_pre(void) +{ + signals::check_interrupt(); +} + + +/// Post-helper for the spawn_followup() method. +/// +/// \param base Exit handle of the subprocess to use as context. +/// \param timeout Maximum amount of time the subprocess can run for. +/// \param child The process created by spawn_followup(). +/// +/// \return The execution handle of the started subprocess. +executor::exec_handle +executor::executor_handle::spawn_followup_post( + const exit_handle& base, + const datetime::delta& timeout, + std::auto_ptr< process::child > child) +{ + INV(*base.state_owners() > 0); + const exec_handle handle(std::shared_ptr< exec_handle::impl >( + new exec_handle::impl( + child->pid(), + base.control_directory(), + base.stdout_file(), + base.stderr_file(), + datetime::timestamp::now(), + timeout, + base.unprivileged_user(), + base.state_owners()))); + INV_MSG(_pimpl->all_exec_handles.find(handle.pid()) == + _pimpl->all_exec_handles.end(), + F("PID %s already in all_exec_handles; not properly cleaned " + "up or reused too fast") % handle.pid());; + _pimpl->all_exec_handles.insert(exec_handles_map::value_type( + handle.pid(), handle)); + LI(F("Spawned subprocess with exec_handle %s") % handle.pid()); + return handle; +} + + +/// Waits for completion of any forked process. +/// +/// \param exec_handle The handle of the process to wait for. +/// +/// \return A pointer to an object describing the waited-for subprocess. +executor::exit_handle +executor::executor_handle::wait(const exec_handle exec_handle) +{ + signals::check_interrupt(); + const process::status status = process::wait(exec_handle.pid()); + return _pimpl->post_wait(exec_handle.pid(), status); +} + + +/// Waits for completion of any forked process. +/// +/// \return A pointer to an object describing the waited-for subprocess. +executor::exit_handle +executor::executor_handle::wait_any(void) +{ + signals::check_interrupt(); + const process::status status = process::wait_any(); + return _pimpl->post_wait(status.dead_pid(), status); +} + + +/// Checks if an interrupt has fired. +/// +/// Calls to this function should be sprinkled in strategic places through the +/// code protected by an interrupts_handler object. +/// +/// This is just a wrapper over signals::check_interrupt() to avoid leaking this +/// dependency to the caller. +/// +/// \throw signals::interrupted_error If there has been an interrupt. +void +executor::executor_handle::check_interrupt(void) const +{ + signals::check_interrupt(); +} |