child_process.cpp
Go to the documentation of this file.
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/child_process.h>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <atomic>
#include <fstream>
#include <mutex>
#include <unordered_map>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <sys/signalfd.h>
namespace io = boost::iostreams;
namespace
{
struct DeathObserverImpl : public core::posix::ChildProcess::DeathObserver
{
DeathObserverImpl(const std::shared_ptr<core::posix::SignalTrap>& 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<std::mutex> 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<std::mutex> lg(guard);
return children.count(process.pid()) > 0;
}
const core::Signal<core::posix::ChildProcess>& 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<std::mutex> 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<pid_t, core::posix::ChildProcess> children;
core::ScopedConnection on_sig_child_connection;
struct
{
core::Signal<core::posix::ChildProcess> child_died;
} signals;
};
}
std::unique_ptr<core::posix::ChildProcess::DeathObserver>
core::posix::ChildProcess::DeathObserver::create_once_with_signal_trap(
std::shared_ptr<core::posix::SignalTrap> trap)
{
static std::atomic<bool> 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<core::posix::ChildProcess::DeathObserver> 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<core::posix::ChildProcess::DeathObserver>{};
}
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},
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),
original_parent_pid(::getpid()),
original_child_pid(pid)
{
}
~Private()
{
// Check if we are in the original parent process.
if (original_parent_pid == getpid())
{
// 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;
io::stream_buffer<io::file_descriptor_source> serr;
io::stream_buffer<io::file_descriptor_sink> sin;
io::stream_buffer<io::file_descriptor_source> sout;
std::istream cerr;
std::ostream cin;
std::istream cout;
// 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;
};
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<int>(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<exit::Status>(WEXITSTATUS(status));
} else if (WIFSIGNALED(status))
{
result.status = wait::Result::Status::signaled;
result.detail.if_signaled.signal = static_cast<Signal>(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<Signal>(WSTOPSIG(status));
} else if (WIFCONTINUED(status))
{
result.status = wait::Result::Status::continued;
}
return result;
}
std::istream& ChildProcess::cerr()
{
return d->cerr;
}
std::ostream& ChildProcess::cin()
{
return d->cin;
}
std::istream& ChildProcess::cout()
{
return d->cout;
}
}
}
child_process.h
core::posix::ChildProcess::DeathObserver
The DeathObserver class observes child process' states and emits a signal when a monitored child has ...
Definition child_process.h:57
core::posix::ChildProcess::DeathObserver::add
virtual bool add(const ChildProcess &child)=0
add adds the specified child to the list of observed child processes.
core::posix::ChildProcess::DeathObserver::create_once_with_signal_trap
static std::unique_ptr< DeathObserver > create_once_with_signal_trap(std::shared_ptr< SignalTrap > trap)
Creates the unique instance of class DeathObserver.
Definition child_process.cpp:152
core::posix::ChildProcess::DeathObserver::has
virtual bool has(const ChildProcess &child) const =0
has checks whether the specified child is observed.
core::posix::ChildProcess::DeathObserver::on_sig_child
virtual void on_sig_child()=0
Checks and reaps all child processes registered with the observer instance.
core::posix::ChildProcess::DeathObserver::child_died
virtual const core::Signal< ChildProcess > & child_died() const =0
child_died is emitted whenever an observed child ceases to exist.
core::posix::ChildProcess
The Process class models a child process of this process.
Definition child_process.h:44
core::posix::ChildProcess::invalid
static ChildProcess invalid()
Creates an invalid ChildProcess.
Definition child_process.cpp:325
core::posix::ChildProcess::wait_for
wait::Result wait_for(const wait::Flags &flags)
Wait for the child process to change state.
Definition child_process.cpp:345
core::posix::ChildProcess::~ChildProcess
~ChildProcess()
Definition child_process.cpp:341
core::posix::ChildProcess::cin
std::ostream & cin()
Access this process's stdin.
Definition child_process.cpp:387
core::posix::ChildProcess::cerr
std::istream & cerr()
Access this process's stderr.
Definition child_process.cpp:382
core::posix::ChildProcess::cout
std::istream & cout()
Access this process's stdout.
Definition child_process.cpp:392
core::posix::Process
The Process class models a process and possible operations on it.
Definition process.h:45
core::posix::Process::pid
virtual pid_t pid() const
Query the pid of the process.
Definition process.cpp:59
core::posix::exit::Status
Status
The Status enum wrap's the posix exit status.
Definition exit.h:34
core::posix::wait::Flags
Flags
Flags enumerates different behavior when waiting for a child process to change state.
Definition wait.h:43
core::posix::Signal
Signal
The Signal enum collects the most common POSIX signals.
Definition signal.h:39
core::posix::Signal::sig_chld
@ sig_chld
core::posix::StandardStream::stdin
@ stdin
core::posix::StandardStream::stderr
@ stderr
core::posix::StandardStream::stdout
@ stdout
core
Definition child_process.h:32
core::posix::ChildProcess::Private
Definition child_process.cpp:274
core::posix::ChildProcess::Private::stderr
ChildProcess::Pipe stderr
Definition child_process.cpp:309
core::posix::ChildProcess::Private::sout
io::stream_buffer< io::file_descriptor_source > sout
Definition child_process.cpp:313
core::posix::ChildProcess::Private::sin
io::stream_buffer< io::file_descriptor_sink > sin
Definition child_process.cpp:312
core::posix::ChildProcess::Private::original_parent_pid
pid_t original_parent_pid
Definition child_process.cpp:321
core::posix::ChildProcess::Private::serr
io::stream_buffer< io::file_descriptor_source > serr
Definition child_process.cpp:311
core::posix::ChildProcess::Private::original_child_pid
pid_t original_child_pid
Definition child_process.cpp:322
core::posix::ChildProcess::Private::cin
std::ostream cin
Definition child_process.cpp:315
core::posix::ChildProcess::Private::stdout
ChildProcess::Pipe stdout
Definition child_process.cpp:308
core::posix::ChildProcess::Private::stdin
ChildProcess::Pipe stdin
Definition child_process.cpp:307
core::posix::ChildProcess::Private::cout
std::istream cout
Definition child_process.cpp:316
core::posix::ChildProcess::Private::cerr
std::istream cerr
Definition child_process.cpp:314
core::posix::ChildProcess::Private::~Private
~Private()
Definition child_process.cpp:293
core::posix::ChildProcess::Private::Private
Private(pid_t pid, const ChildProcess::Pipe &stderr, const ChildProcess::Pipe &stdin, const ChildProcess::Pipe &stdout)
Definition child_process.cpp:277
core::posix::wait::Result
The Result struct encapsulates the result of waiting for a process state change.
Definition wait.h:55
core::posix::wait::Result::core_dumped
bool core_dumped
true if the process termination resulted in a core dump.
Definition wait.h:88
core::posix::wait::Result::Status::signaled
@ signaled
The process was signalled and terminated.
core::posix::wait::Result::Status::no_state_change
@ no_state_change
No state change occurred.
core::posix::wait::Result::Status::exited
@ exited
The process exited normally.
core::posix::wait::Result::Status::continued
@ continued
The process resumed operation.
core::posix::wait::Result::Status::stopped
@ stopped
The process was signalled and stopped.
core::posix::wait::Result::detail
union core::posix::wait::Result::@4 detail
Union of result-specific details.
core::posix::wait::Result::if_signaled
struct core::posix::wait::Result::@4::@6 if_signaled
core::posix::wait::Result::status
enum core::posix::wait::Result::Status status
core::posix::wait::Result::signal
Signal signal
Signal that caused the process to terminate.
Definition wait.h:87
core::posix::wait::Result::if_exited
struct core::posix::wait::Result::@4::@5 if_exited
core::posix::wait::Result::if_stopped
struct core::posix::wait::Result::@4::@7 if_stopped