Path: blob/main/contrib/kyua/utils/process/child.cpp
103970 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}235236237std::unique_ptr< process::child >238process::child::fork_interactive(void)239{240std::cout.flush();241std::cerr.flush();242243std::unique_ptr< signals::interrupts_inhibiter > inhibiter(244new signals::interrupts_inhibiter);245pid_t pid = detail::syscall_fork();246if (pid == -1) {247inhibiter.reset(); // Unblock signals.248throw process::system_error("fork(2) failed", errno);249} else if (pid == 0) {250inhibiter.reset(); // Unblock signals.251return {};252} else {253signals::add_pid_to_kill(pid);254inhibiter.reset(NULL); // Unblock signals.255return std::unique_ptr< process::child >(256new process::child(new impl(pid, NULL)));257}258}259260261/// Helper function for fork().262///263/// Please note: if you update this function to change the return type or to264/// raise different errors, do not forget to update fork() accordingly.265///266/// \param stdout_file The name of the file in which to store the stdout.267/// If this has the magic value /dev/stdout, then the parent's stdout is268/// reused without applying any redirection.269/// \param stderr_file The name of the file in which to store the stderr.270/// If this has the magic value /dev/stderr, then the parent's stderr is271/// reused without applying any redirection.272///273/// \return In the case of the parent, a new child object returned as a274/// dynamically-allocated object because children classes are unique and thus275/// noncopyable. In the case of the child, a NULL pointer.276///277/// \throw process::system_error If the call to fork(2) fails.278std::unique_ptr< process::child >279process::child::fork_files_aux(const fs::path& stdout_file,280const fs::path& stderr_file)281{282std::cout.flush();283std::cerr.flush();284285std::unique_ptr< signals::interrupts_inhibiter > inhibiter(286new signals::interrupts_inhibiter);287pid_t pid = detail::syscall_fork();288if (pid == -1) {289inhibiter.reset(); // Unblock signals.290throw process::system_error("fork(2) failed", errno);291} else if (pid == 0) {292inhibiter.reset(); // Unblock signals.293::setsid();294295try {296if (stdout_file != fs::path("/dev/stdout")) {297const int stdout_fd = open_for_append(stdout_file);298safe_dup(stdout_fd, STDOUT_FILENO);299::close(stdout_fd);300}301if (stderr_file != fs::path("/dev/stderr")) {302const int stderr_fd = open_for_append(stderr_file);303safe_dup(stderr_fd, STDERR_FILENO);304::close(stderr_fd);305}306} catch (const system_error& e) {307std::cerr << F("Failed to set up subprocess: %s\n") % e.what();308std::abort();309}310return {};311} else {312LD(F("Spawned process %s: stdout=%s, stderr=%s") % pid % stdout_file %313stderr_file);314signals::add_pid_to_kill(pid);315inhibiter.reset(); // Unblock signals.316return std::unique_ptr< process::child >(317new process::child(new impl(pid, NULL)));318}319}320321322/// Spawns a new binary and multiplexes and captures its stdout and stderr.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///330/// \return A new child object, returned as a dynamically-allocated object331/// because children classes are unique and thus noncopyable.332///333/// \throw process::system_error If the process cannot be spawned due to a334/// system call error.335std::unique_ptr< process::child >336process::child::spawn_capture(const fs::path& program, const args_vector& args)337{338std::unique_ptr< child > child = fork_capture_aux();339if (child.get() == NULL)340exec(program, args);341log_exec(program, args);342return child;343}344345346/// Spawns a new binary and redirects its stdout and stderr to files.347///348/// If the subprocess cannot be completely set up for any reason, it attempts to349/// dump an error message to its stderr channel and it then calls std::abort().350///351/// \param program The binary to execute.352/// \param args The arguments to pass to the binary, without the program name.353/// \param stdout_file The name of the file in which to store the stdout.354/// \param stderr_file The name of the file in which to store the stderr.355///356/// \return A new child object, returned as a dynamically-allocated object357/// because children classes are unique and thus noncopyable.358///359/// \throw process::system_error If the process cannot be spawned due to a360/// system call error.361std::unique_ptr< process::child >362process::child::spawn_files(const fs::path& program,363const args_vector& args,364const fs::path& stdout_file,365const fs::path& stderr_file)366{367std::unique_ptr< child > child = fork_files_aux(stdout_file, stderr_file);368if (child.get() == NULL)369exec(program, args);370log_exec(program, args);371return child;372}373374375/// Returns the process identifier of this child.376///377/// \return A process identifier.378int379process::child::pid(void) const380{381return _pimpl->_pid;382}383384385/// Gets the input stream corresponding to the stdout and stderr of the child.386///387/// \pre The child must have been started by fork_capture().388///389/// \return A reference to the input stream connected to the output of the test390/// case.391std::istream&392process::child::output(void)393{394PRE(_pimpl->_output.get() != NULL);395return *_pimpl->_output;396}397398399/// Blocks to wait for completion.400///401/// \return The termination status of the child process.402///403/// \throw process::system_error If the call to waitpid(2) fails.404process::status405process::child::wait(void)406{407return process::wait(_pimpl->_pid);408}409410411