/* * Copyright © 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authored by: Thomas Voß */ #include #ifndef ANDROID #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifndef ANDROID namespace io = boost::iostreams; #endif namespace { struct DeathObserverImpl : public core::posix::ChildProcess::DeathObserver { DeathObserverImpl(const std::shared_ptr& trap) : on_sig_child_connection { trap->signal_raised().connect([this](core::posix::Signal signal) { switch (signal) { case core::posix::Signal::sig_chld: on_sig_child(); break; default: break; } }) } { if (!trap->has(core::posix::Signal::sig_chld)) throw std::logic_error( "DeathObserver::DeathObserverImpl: Given SignalTrap" " instance does not trap Signal::sig_chld."); } bool add(const core::posix::ChildProcess& process) override { if (process.pid() == -1) return false; std::lock_guard lg(guard); bool added = false; auto new_process = std::make_pair(process.pid(), process); std::tie(std::ignore, added) = children.insert(new_process); if (added) { // The process may have died between it's instantiation and it // being added to the children map. Check that it's still alive. int status{-1}; if (::waitpid(process.pid(), &status, WNOHANG) != 0) // child no longer alive { // we missed the SIGCHLD signal so we must now manually // inform our subscribers. signals.child_died(new_process.second); children.erase(new_process.first); return false; } } return added; } bool has(const core::posix::ChildProcess& process) const override { std::lock_guard lg(guard); return children.count(process.pid()) > 0; } const core::Signal& child_died() const override { return signals.child_died; } void on_sig_child() override { pid_t pid{-1}; int status{-1}; while (true) { pid = ::waitpid(0, &status, WNOHANG); if (pid == -1) { if (errno == ECHILD) { break; // No more children } continue; // Ignore stray SIGCHLD signals } else if (pid == 0) { break; // No more children } else { std::lock_guard lg(guard); auto it = children.find(pid); if (it != children.end()) { if (WIFSIGNALED(status) || WIFEXITED(status)) { signals.child_died(it->second); children.erase(it); } } } } } mutable std::mutex guard; std::unordered_map children; core::ScopedConnection on_sig_child_connection; struct { core::Signal child_died; } signals; }; } std::unique_ptr core::posix::ChildProcess::DeathObserver::create_once_with_signal_trap( std::shared_ptr trap) { static std::atomic has_been_created_once{false}; if (has_been_created_once.exchange(true)) throw std::runtime_error { "DeathObserver::create_once_with_signal_trap: " "Cannot create more than one instance." }; try { std::unique_ptr result { new DeathObserverImpl{trap} }; return result; } catch(...) { // We make sure that a throwing c'tor does not impact our ability to // retry creation of a DeathObserver instance. has_been_created_once.store(false); std::rethrow_exception(std::current_exception()); } assert(false && "We should never reach here."); // Silence the compiler. return std::unique_ptr{}; } namespace core { namespace posix { ChildProcess::Pipe ChildProcess::Pipe::invalid() { static Pipe p; static std::once_flag flag; std::call_once(flag, [&]() { p.close_read_fd(); p.close_write_fd(); }); return p; } ChildProcess::Pipe::Pipe() { int rc = ::pipe(fds); if (rc == -1) throw std::system_error(errno, std::system_category()); } ChildProcess::Pipe::Pipe(const ChildProcess::Pipe& rhs) : fds{-1, -1} { if (rhs.fds[0] != -1) fds[0] = ::dup(rhs.fds[0]); if (rhs.fds[1] != -1) fds[1] = ::dup(rhs.fds[1]); } ChildProcess::Pipe::~Pipe() { if (fds[0] != -1) ::close(fds[0]); if (fds[1] != -1) ::close(fds[1]); } int ChildProcess::Pipe::read_fd() const { return fds[0]; } void ChildProcess::Pipe::close_read_fd() { if (fds[0] != -1) { ::close(fds[0]); fds[0] = -1; } } int ChildProcess::Pipe::write_fd() const { return fds[1]; } void ChildProcess::Pipe::close_write_fd() { if (fds[1] != -1) { ::close(fds[1]); fds[1] = -1; } } ChildProcess::Pipe& ChildProcess::Pipe::operator=(const ChildProcess::Pipe& rhs) { if (fds[0] != -1) ::close(fds[0]); if (fds[1] != -1) ::close(fds[1]); if (rhs.fds[0] != -1) fds[0] = ::dup(rhs.fds[0]); else fds[0] = -1; if (rhs.fds[1] != -1) fds[1] = ::dup(rhs.fds[1]); else fds[1] = -1; return *this; } struct ChildProcess::Private { // stdin and stdout are always "relative" to the childprocess, i.e., we // write to stdin of the child process and read from its stdout. Private(pid_t pid, const ChildProcess::Pipe& stderr, const ChildProcess::Pipe& stdin, const ChildProcess::Pipe& stdout) : pipes{stderr, stdin, stdout}, #ifndef ANDROID serr(pipes.stderr.read_fd(), io::never_close_handle), sin(pipes.stdin.write_fd(), io::never_close_handle), sout(pipes.stdout.read_fd(), io::never_close_handle), cerr(&serr), cin(&sin), cout(&sout), #endif original_parent_pid(::getpid()), original_child_pid(pid) { } ~Private() { // Check if we are in the original parent process. if (original_parent_pid == getpid() && !dont_kill_on_cleanup) { // If so, check if we are considering a valid pid here. // If so, we kill the original child. if (original_child_pid != -1) ::kill(original_child_pid, SIGKILL); } } struct { ChildProcess::Pipe stdin; ChildProcess::Pipe stdout; ChildProcess::Pipe stderr; } pipes; #ifndef ANDROID io::stream_buffer serr; io::stream_buffer sin; io::stream_buffer sout; std::istream cerr; std::ostream cin; std::istream cout; #endif // We need to store the original parent pid as we might have been forked // and with our automatic cleanup in place, it might happen that the d'tor // is called from the child process. pid_t original_parent_pid; pid_t original_child_pid; bool dont_kill_on_cleanup = false; }; ChildProcess ChildProcess::invalid() { // We take the init process as child. static const pid_t invalid_pid = 1; return ChildProcess(invalid_pid, Pipe::invalid(), Pipe::invalid(), Pipe::invalid()); } ChildProcess::ChildProcess(pid_t pid, const ChildProcess::Pipe& stdin_pipe, const ChildProcess::Pipe& stdout_pipe, const ChildProcess::Pipe& stderr_pipe) : Process(pid), d(new Private{pid, stdin_pipe, stdout_pipe, stderr_pipe}) { } ChildProcess::~ChildProcess() { } wait::Result ChildProcess::wait_for(const wait::Flags& flags) { int status = -1; pid_t result_pid = ::waitpid(pid(), std::addressof(status), static_cast(flags)); if (result_pid == -1) throw std::system_error(errno, std::system_category()); wait::Result result; if (result_pid == 0) { result.status = wait::Result::Status::no_state_change; return result; } if (WIFEXITED(status)) { result.status = wait::Result::Status::exited; result.detail.if_exited.status = static_cast(WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { result.status = wait::Result::Status::signaled; result.detail.if_signaled.signal = static_cast(WTERMSIG(status)); result.detail.if_signaled.core_dumped = WCOREDUMP(status); } else if (WIFSTOPPED(status)) { result.status = wait::Result::Status::stopped; result.detail.if_stopped.signal = static_cast(WSTOPSIG(status)); } #ifndef ANDROID else if (WIFCONTINUED(status)) { result.status = wait::Result::Status::continued; } #endif return result; } void ChildProcess::dont_kill_on_cleanup() { d->dont_kill_on_cleanup = true; } #ifndef ANDROID std::istream& ChildProcess::cerr() { return d->cerr; } std::ostream& ChildProcess::cin() { return d->cin; } std::istream& ChildProcess::cout() { return d->cout; } #endif } }