Path: blob/main/contrib/kyua/utils/process/child.cpp
48199 views
// Copyright 2010 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/child.ipp"2930extern "C" {31#include <sys/stat.h>32#include <sys/wait.h>3334#include <fcntl.h>35#include <signal.h>36#include <unistd.h>37}3839#include <cerrno>40#include <iostream>41#include <memory>4243#include "utils/defs.hpp"44#include "utils/format/macros.hpp"45#include "utils/fs/path.hpp"46#include "utils/logging/macros.hpp"47#include "utils/noncopyable.hpp"48#include "utils/process/exceptions.hpp"49#include "utils/process/fdstream.hpp"50#include "utils/process/operations.hpp"51#include "utils/process/system.hpp"52#include "utils/process/status.hpp"53#include "utils/sanity.hpp"54#include "utils/signals/interrupts.hpp"555657namespace utils {58namespace process {596061/// Private implementation fields for child objects.62struct child::impl : utils::noncopyable {63/// The process identifier.64pid_t _pid;6566/// The input stream for the process' stdout and stderr. May be NULL.67std::unique_ptr< process::ifdstream > _output;6869/// Initializes private implementation data.70///71/// \param pid The process identifier.72/// \param output The input stream. Grabs ownership of the pointer.73impl(const pid_t pid, process::ifdstream* output) :74_pid(pid), _output(output) {}75};767778} // namespace process79} // namespace utils808182namespace fs = utils::fs;83namespace process = utils::process;84namespace signals = utils::signals;858687namespace {888990/// Exception-based version of dup(2).91///92/// \param old_fd The file descriptor to duplicate.93/// \param new_fd The file descriptor to use as the duplicate. This is94/// closed if it was open before the copy happens.95///96/// \throw process::system_error If the call to dup2(2) fails.97static void98safe_dup(const int old_fd, const int new_fd)99{100if (process::detail::syscall_dup2(old_fd, new_fd) == -1) {101const int original_errno = errno;102throw process::system_error(F("dup2(%s, %s) failed") % old_fd % new_fd,103original_errno);104}105}106107108/// Exception-based version of open(2) to open (or create) a file for append.109///110/// \param filename The file to open in append mode.111///112/// \return The file descriptor for the opened or created file.113///114/// \throw process::system_error If the call to open(2) fails.115static int116open_for_append(const fs::path& filename)117{118const int fd = process::detail::syscall_open(119filename.c_str(), O_CREAT | O_WRONLY | O_APPEND,120S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);121if (fd == -1) {122const int original_errno = errno;123throw process::system_error(F("Failed to create %s because open(2) "124"failed") % filename, original_errno);125}126return fd;127}128129130/// Logs the execution of another program.131///132/// \param program The binary to execute.133/// \param args The arguments to pass to the binary, without the program name.134static void135log_exec(const fs::path& program, const process::args_vector& args)136{137std::string plain_command = program.str();138for (process::args_vector::const_iterator iter = args.begin();139iter != args.end(); ++iter)140plain_command += F(" %s") % *iter;141LD(F("Executing %s") % plain_command);142}143144145} // anonymous namespace146147148/// Prints out a fatal error and aborts.149void150utils::process::detail::report_error_and_abort(void)151{152std::cerr << "Caught unknown exception\n";153std::abort();154}155156157/// Prints out a fatal error and aborts.158///159/// \param error The error to display.160void161utils::process::detail::report_error_and_abort(const std::runtime_error& error)162{163std::cerr << "Caught runtime_error: " << error.what() << '\n';164std::abort();165}166167168/// Creates a new child.169///170/// \param implptr A dynamically-allocated impl object with the contents of the171/// new child.172process::child::child(impl *implptr) :173_pimpl(implptr)174{175}176177178/// Destructor for child.179process::child::~child(void)180{181}182183184/// Helper function for fork().185///186/// Please note: if you update this function to change the return type or to187/// raise different errors, do not forget to update fork() accordingly.188///189/// \return In the case of the parent, a new child object returned as a190/// dynamically-allocated object because children classes are unique and thus191/// noncopyable. In the case of the child, a NULL pointer.192///193/// \throw process::system_error If the calls to pipe(2) or fork(2) fail.194std::unique_ptr< process::child >195process::child::fork_capture_aux(void)196{197std::cout.flush();198std::cerr.flush();199200int fds[2];201if (detail::syscall_pipe(fds) == -1)202throw process::system_error("pipe(2) failed", errno);203204std::unique_ptr< signals::interrupts_inhibiter > inhibiter(205new signals::interrupts_inhibiter);206pid_t pid = detail::syscall_fork();207if (pid == -1) {208inhibiter.reset(); // Unblock signals.209::close(fds[0]);210::close(fds[1]);211throw process::system_error("fork(2) failed", errno);212} else if (pid == 0) {213inhibiter.reset(); // Unblock signals.214::setsid();215216try {217::close(fds[0]);218safe_dup(fds[1], STDOUT_FILENO);219safe_dup(fds[1], STDERR_FILENO);220::close(fds[1]);221} catch (const system_error& e) {222std::cerr << F("Failed to set up subprocess: %s\n") % e.what();223std::abort();224}225return {};226} else {227::close(fds[1]);228LD(F("Spawned process %s: stdout and stderr inherited") % pid);229signals::add_pid_to_kill(pid);230inhibiter.reset(NULL); // Unblock signals.231return std::unique_ptr< process::child >(232new process::child(new impl(pid, new process::ifdstream(fds[0]))));233}234}235236237/// Helper function for fork().238///239/// Please note: if you update this function to change the return type or to240/// raise different errors, do not forget to update fork() accordingly.241///242/// \param stdout_file The name of the file in which to store the stdout.243/// If this has the magic value /dev/stdout, then the parent's stdout is244/// reused without applying any redirection.245/// \param stderr_file The name of the file in which to store the stderr.246/// If this has the magic value /dev/stderr, then the parent's stderr is247/// reused without applying any redirection.248///249/// \return In the case of the parent, a new child object returned as a250/// dynamically-allocated object because children classes are unique and thus251/// noncopyable. In the case of the child, a NULL pointer.252///253/// \throw process::system_error If the call to fork(2) fails.254std::unique_ptr< process::child >255process::child::fork_files_aux(const fs::path& stdout_file,256const fs::path& stderr_file)257{258std::cout.flush();259std::cerr.flush();260261std::unique_ptr< signals::interrupts_inhibiter > inhibiter(262new signals::interrupts_inhibiter);263pid_t pid = detail::syscall_fork();264if (pid == -1) {265inhibiter.reset(); // Unblock signals.266throw process::system_error("fork(2) failed", errno);267} else if (pid == 0) {268inhibiter.reset(); // Unblock signals.269::setsid();270271try {272if (stdout_file != fs::path("/dev/stdout")) {273const int stdout_fd = open_for_append(stdout_file);274safe_dup(stdout_fd, STDOUT_FILENO);275::close(stdout_fd);276}277if (stderr_file != fs::path("/dev/stderr")) {278const int stderr_fd = open_for_append(stderr_file);279safe_dup(stderr_fd, STDERR_FILENO);280::close(stderr_fd);281}282} catch (const system_error& e) {283std::cerr << F("Failed to set up subprocess: %s\n") % e.what();284std::abort();285}286return {};287} else {288LD(F("Spawned process %s: stdout=%s, stderr=%s") % pid % stdout_file %289stderr_file);290signals::add_pid_to_kill(pid);291inhibiter.reset(); // Unblock signals.292return std::unique_ptr< process::child >(293new process::child(new impl(pid, NULL)));294}295}296297298/// Spawns a new binary and multiplexes and captures its stdout and stderr.299///300/// If the subprocess cannot be completely set up for any reason, it attempts to301/// dump an error message to its stderr channel and it then calls std::abort().302///303/// \param program The binary to execute.304/// \param args The arguments to pass to the binary, without the program name.305///306/// \return A new child object, returned as a dynamically-allocated object307/// because children classes are unique and thus noncopyable.308///309/// \throw process::system_error If the process cannot be spawned due to a310/// system call error.311std::unique_ptr< process::child >312process::child::spawn_capture(const fs::path& program, const args_vector& args)313{314std::unique_ptr< child > child = fork_capture_aux();315if (child.get() == NULL)316exec(program, args);317log_exec(program, args);318return child;319}320321322/// Spawns a new binary and redirects its stdout and stderr to files.323///324/// If the subprocess cannot be completely set up for any reason, it attempts to325/// dump an error message to its stderr channel and it then calls std::abort().326///327/// \param program The binary to execute.328/// \param args The arguments to pass to the binary, without the program name.329/// \param stdout_file The name of the file in which to store the stdout.330/// \param stderr_file The name of the file in which to store the stderr.331///332/// \return A new child object, returned as a dynamically-allocated object333/// because children classes are unique and thus noncopyable.334///335/// \throw process::system_error If the process cannot be spawned due to a336/// system call error.337std::unique_ptr< process::child >338process::child::spawn_files(const fs::path& program,339const args_vector& args,340const fs::path& stdout_file,341const fs::path& stderr_file)342{343std::unique_ptr< child > child = fork_files_aux(stdout_file, stderr_file);344if (child.get() == NULL)345exec(program, args);346log_exec(program, args);347return child;348}349350351/// Returns the process identifier of this child.352///353/// \return A process identifier.354int355process::child::pid(void) const356{357return _pimpl->_pid;358}359360361/// Gets the input stream corresponding to the stdout and stderr of the child.362///363/// \pre The child must have been started by fork_capture().364///365/// \return A reference to the input stream connected to the output of the test366/// case.367std::istream&368process::child::output(void)369{370PRE(_pimpl->_output.get() != NULL);371return *_pimpl->_output;372}373374375/// Blocks to wait for completion.376///377/// \return The termination status of the child process.378///379/// \throw process::system_error If the call to waitpid(2) fails.380process::status381process::child::wait(void)382{383return process::wait(_pimpl->_pid);384}385386387