#include "unixchild.h"
#include <security_utilities/debugging.h>
#include <signal.h>
namespace Security {
namespace UnixPlusPlus {
ModuleNexus<Child::Children> Child::mChildren;
Child::Child()
: mState(unborn), mPid(0), mStatus(0)
{
}
Child::~Child()
{
assert(mState != alive); }
void Child::reset()
{
switch (mState) {
case alive:
assert(false); case unborn:
break; default:
secinfo("unixchild", "%p reset (from state %d)", this, mState);
mState = unborn;
mPid = 0;
mStatus = 0;
break;
}
}
void Child::sharedChildren(bool s)
{
StLock<Mutex> _(mChildren());
mChildren().shared = s;
}
bool Child::sharedChildren()
{
StLock<Mutex> _(mChildren());
return mChildren().shared;
}
Child::State Child::check()
{
Child::State state;
bool reaped = false;
{
StLock<Mutex> _(mChildren());
state = mState;
switch (mState) {
case alive:
reaped = checkStatus(WNOHANG);
break;
default:
break;
}
}
if (reaped)
this->dying();
return state;
}
void Child::wait()
{
bool reaped = false;
{
StLock<Mutex> _(mChildren());
switch (mState) {
case alive:
reaped = checkStatus(0); break;
case unborn:
assert(false); default:
break;
}
}
if (reaped)
this->dying();
}
void Child::tryKill(int signal)
{
assert(mState == alive); secinfo("unixchild", "%p (pid %d) sending signal(%d)", this, pid(), signal);
if (::kill(pid(), signal))
switch (errno) {
case ESRCH: secinfo("unixchild", "%p (pid %d) has disappeared!", this, pid());
mState = invalid;
mChildren().erase(pid());
default:
UnixError::throwMe();
}
}
void Child::kill(int signal)
{
StLock<Mutex> _(mChildren());
if (mState == alive)
tryKill(signal);
else
secinfo("unixchild", "%p (pid %d) not alive; cannot send signal %d",
this, pid(), signal);
}
void Child::kill()
{
switch (this->state()) {
case alive:
if (this->state() == alive) {
this->kill(SIGTERM); checkChildren(); if (this->state() == alive) {
usleep(200000); if (this->state() == alive) { checkChildren(); if (this->state() == alive) { this->kill(SIGKILL); checkChildren();
if (this->state() == alive) this->abandon(); }
}
}
}
break;
case dead:
secinfo("unixchild", "%p (pid %d) already dead; ignoring kill request", this, pid());
break;
default:
secinfo("unixchild", "%p state %d; ignoring kill request", this, this->state());
break;
}
}
void Child::abandon()
{
StLock<Mutex> _(mChildren());
if (mState == alive) {
secinfo("unixchild", "%p (pid %d) abandoned", this, pid());
mState = abandoned;
mChildren().erase(pid());
} else {
secinfo("unixchild", "%p (pid %d) is not alive; abandon() ignored",
this, pid());
}
}
int Child::waitStatus() const
{
assert(mState == dead);
return mStatus;
}
bool Child::bySignal() const
{
assert(mState == dead);
return WIFSIGNALED(mStatus);
}
int Child::exitCode() const
{
assert(mState == dead);
assert(WIFEXITED(mStatus));
return WEXITSTATUS(mStatus);
}
int Child::exitSignal() const
{
assert(mState == dead);
assert(WIFSIGNALED(mStatus));
return WTERMSIG(mStatus);
}
bool Child::coreDumped() const
{
assert(mState == dead);
assert(WIFSIGNALED(mStatus));
return WCOREDUMP(mStatus);
}
Child *Child::findGeneric(pid_t pid)
{
StLock<Mutex> _(mChildren());
Children::iterator it = mChildren().find(pid);
if (it == mChildren().end())
return NULL;
else
return it->second;
}
void Child::fork()
{
static const unsigned maxDelay = 30;
assert(mState == unborn);
for (unsigned delay = 1; ;) {
switch (pid_t pid = ::fork()) {
case -1: switch (errno) {
case EINTR:
secinfo("unixchild", "%p fork EINTR; retrying", this);
continue; case EAGAIN:
if (delay < maxDelay) {
secinfo("unixchild", "%p fork EAGAIN; delaying %d seconds",
this, delay);
sleep(delay);
delay *= 2;
continue;
}
default:
UnixError::throwMe();
}
assert(false);
case 0: try {
this->childAction();
} catch (...) {
}
_exit(1);
default: {
StLock<Mutex> _(mChildren());
mState = alive;
mPid = pid;
mChildren().insert(make_pair(pid, this));
}
secinfo("unixchild", "%p (parent) running parent action", this);
this->parentAction();
break;
}
break;
}
}
bool Child::checkStatus(int options)
{
assert(state() == alive);
secinfo("unixchild", "checking %p (pid %d)", this, this->pid());
int status;
again:
switch (IFDEBUG(pid_t pid =) ::wait4(this->pid(), &status, options, NULL)) {
case pid_t(-1):
switch (errno) {
case EINTR:
goto again; case ECHILD:
secinfo("unixchild", "%p (pid=%d) unknown to kernel", this, this->pid());
mState = invalid;
mChildren().erase(this->pid());
return false;
default:
UnixError::throwMe();
}
case 0:
return false; default:
assert(pid == this->pid());
bury(status);
return true;
}
}
void Child::checkChildren()
{
Bier casualties;
{
StLock<Mutex> _(mChildren());
if (mChildren().shared) {
for (Children::iterator it = mChildren().begin(); it != mChildren().end(); it++)
if (it->second->checkStatus(WNOHANG))
casualties.add(it->second);
} else if (!mChildren().empty()) {
int status;
while (pid_t pid = ::wait4(0, &status, WNOHANG, NULL)) {
secinfo("unixchild", "universal child check (%ld children known alive)", mChildren().size());
switch (pid) {
case pid_t(-1):
switch (errno) {
case EINTR:
secinfo("unixchild", "EINTR on wait4; retrying");
continue; case ECHILD:
secinfo("unixchild", "ECHILD with filled nursery (ignored)");
goto no_more;
default:
UnixError::throwMe();
}
default:
if (Child *child = mChildren()[pid]) {
child->bury(status);
casualties.add(child);
} else
secinfo("unixchild", "reaping feral child pid=%d", pid);
if (mChildren().empty())
goto no_more; break;
}
}
no_more: ;
} else {
secinfo("unixchild", "spurious checkChildren (the nursery is empty)");
}
} casualties.notify();
}
void Child::bury(int status)
{
assert(mState == alive);
mState = dead;
mStatus = status;
mChildren().erase(mPid);
#if !defined(NDEBUG)
if (bySignal())
secinfo("unixchild", "%p (pid %d) died by signal %d%s",
this, mPid, exitSignal(),
coreDumped() ? " and dumped core" : "");
else
secinfo("unixchild", "%p (pid %d) died by exit(%d)",
this, mPid, exitCode());
#endif //NDEBUG
}
void Child::parentAction()
{ }
void Child::dying()
{ }
void Child::Bier::notify()
{
for (const_iterator it = begin(); it != end(); ++it)
(*it)->dying();
}
} }