+++ /dev/null
-/*
- * Copyright (c) 2000-2001,2003-2004 Apple Computer, Inc. All Rights Reserved.
- *
- * @APPLE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_LICENSE_HEADER_END@
- */
-
-
-//
-// unixchild - low-level UNIX process child management.
-//
-// Note that the map-of-children (mChildren) only holds children presumed to
-// be alive. Neither unborn nor dead children are included. This is important
-// for how children are reaped and death notifications dispatched, and should
-// not be changed without prior deep contemplation.
-//
-// A note on locking:
-// All Child objects in this subsystem are mutated under control of a single
-// lock (mChildren). This means that children will not step on each other.
-// However, death callbacks (Child::dying) are made outside the lock's scope
-// to avoid deadlock scenarios with outside locking hierarchies. When Child::dying
-// is called, the child has already transitioned to "dead" state and is no longer
-// in the (live) children map.
-//
-#include "unixchild.h"
-#include <security_utilities/debugging.h>
-#include <signal.h>
-
-
-namespace Security {
-namespace UnixPlusPlus {
-
-
-//
-// All our globals are in a ModuleNexus, for that special lazy-init goodness
-//
-ModuleNexus<Child::Children> Child::mChildren;
-
-
-//
-// Make and break Children
-//
-Child::Child()
- : mState(unborn), mPid(0), mStatus(0)
-{
-}
-
-
-Child::~Child()
-{
- assert(mState != alive); // not allowed by protocol
-}
-
-
-//
-// Take a Child object that is not alive (i.e. is either unborn or dead),
-// and reset it to unborn, so you can fork() it again.
-// This call forgets everything about the previous process.
-//
-void Child::reset()
-{
- switch (mState) {
- case alive:
- assert(false); // bad boy; can't do that
- case unborn:
- break; // s'okay
- default:
- secdebug("unixchild", "%p reset (from state %d)", this, mState);
- mState = unborn;
- mPid = 0;
- mStatus = 0;
- break;
- }
-}
-
-
-//
-// Global inquiries and setup
-//
-void Child::sharedChildren(bool s)
-{
- StLock<Mutex> _(mChildren());
- mChildren().shared = s;
-}
-
-bool Child::sharedChildren()
-{
- StLock<Mutex> _(mChildren());
- return mChildren().shared;
-}
-
-
-//
-// Check status for one Child
-//
-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;
-}
-
-
-//
-// Wait for a particular child to be dead.
-// This call cannot wait for multiple children; you'll have
-// to program that yourself using whatever event loop you're using.
-//
-void Child::wait()
-{
- bool reaped = false;
- {
- StLock<Mutex> _(mChildren());
- switch (mState) {
- case alive:
- reaped = checkStatus(0); // wait for it
- break;
- case unborn:
- assert(false); // don't do that
- default:
- break;
- }
- }
- if (reaped)
- this->dying();
-}
-
-
-//
-// Common kill code.
-// Requires caller to hold mChildren() lock.
-//
-void Child::tryKill(int signal)
-{
- assert(mState == alive); // ... or don't bother us
- secdebug("unixchild", "%p (pid %d) sending signal(%d)", this, pid(), signal);
- if (::kill(pid(), signal))
- switch (errno) {
- case ESRCH: // someone else reaped ths child; or things are just wacky
- secdebug("unixchild", "%p (pid %d) has disappeared!", this, pid());
- mState = invalid;
- mChildren().erase(pid());
- // fall through
- default:
- UnixError::throwMe();
- }
-}
-
-
-//
-// Send a signal to the Child.
-// This will succeed (and do nothing) if the Child is not alive.
-//
-void Child::kill(int signal)
-{
- StLock<Mutex> _(mChildren());
- if (mState == alive)
- tryKill(signal);
- else
- secdebug("unixchild", "%p (pid %d) not alive; cannot send signal %d",
- this, pid(), signal);
-}
-
-
-//
-// Kill with prejudice.
-// This will make a serious attempt to *synchronously* kill the process before
-// returning. If that doesn't work for some reason, abandon the child.
-// This is one thing you can do in the destructor of your subclass to legally
-// dispose of your Child's process.
-//
-void Child::kill()
-{
- // note that we mustn't hold the lock across these calls
- if (this->state() == alive) {
- this->kill(SIGTERM); // shoot it once
- checkChildren(); // check for quick death
- if (this->state() == alive) {
- usleep(200000); // give it some time to die
- if (this->state() == alive) { // could have been reaped by another thread
- checkChildren(); // check again
- if (this->state() == alive) { // it... just... won't... die...
- this->kill(SIGKILL); // take THAT!
- checkChildren();
- if (this->state() == alive) // stuck zombie
- this->abandon(); // leave the body behind
- }
- }
- }
- } else
- secdebug("unixchild", "%p (pid %d) not alive; ignoring request to kill it", this, pid());
-}
-
-
-//
-// Take a living child and cut it loose. This sets its state to abandoned
-// and removes it from the child registry.
-// This is one thing you can do in the destructor of your subclass to legally
-// dispose of your child's process.
-//
-void Child::abandon()
-{
- StLock<Mutex> _(mChildren());
- if (mState == alive) {
- secdebug("unixchild", "%p (pid %d) abandoned", this, pid());
- mState = abandoned;
- mChildren().erase(pid());
- } else {
- secdebug("unixchild", "%p (pid %d) is not alive; abandon() ignored",
- this, pid());
- }
-}
-
-
-//
-// Forensic examination of the Child's cadaver.
-// Not interlocked because you have to check for state() == dead first,
-// and these values are const ever after.
-//
-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);
-}
-
-
-//
-// Find a child in the child map, by pid
-// This will only find live children, and return NULL for all others.
-//
-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;
-}
-
-
-//
-// Do the actual fork job.
-// At this layer, the client side does nothing but run childAction(). Any plumbing
-// or cleanup is up to that function (which runs in the child) and the caller (after
-// fork() returns). If childAction() returns at all, we will call exit(1) to get
-// rid of the child.
-//
-void Child::fork()
-{
- static const unsigned maxDelay = 30; // seconds increment, i.e. 5 retries
-
- assert(mState == unborn);
- for (unsigned delay = 1; ;) {
- switch (pid_t pid = ::fork()) {
- case -1: // fork failed
- switch (errno) {
- case EINTR:
- secdebug("unixchild", "%p fork EINTR; retrying", this);
- continue; // no problem
- case EAGAIN:
- if (delay < maxDelay) {
- secdebug("unixchild", "%p fork EAGAIN; delaying %d seconds",
- this, delay);
- sleep(delay);
- delay *= 2;
- continue;
- }
- // fall through
- default:
- UnixError::throwMe();
- }
- assert(false); // unreached
-
- case 0: // child
- //@@@ bother to clean child map?
- secdebug("unixchild", "%p (child pid %d) running child action",
- this, getpid());
- secdelay("/tmp/delay/unixchild");
- try {
- this->childAction();
- secdebug("unixchild", "%p (pid %d) child action returned; exiting",
- this, getpid());
- } catch (...) {
- secdebug("unixchild", "%p (pid %d) child action had uncaught exception",
- this, getpid());
- }
- _exit(1);
-
- default: // parent
- {
- StLock<Mutex> _(mChildren());
- mState = alive;
- mPid = pid;
- mChildren().insert(make_pair(pid, this));
- }
- secdebug("unixchild", "%p (parent) running parent action", this);
- this->parentAction();
- break;
- }
- break;
- }
-}
-
-
-//
-// Check the status of this child by explicitly probing it.
-// Caller must hold master lock.
-//
-bool Child::checkStatus(int options)
-{
- assert(state() == alive);
- secdebug("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; // retry
- case ECHILD:
- secdebug("unixchild", "%p (pid=%d) unknown to kernel", this, this->pid());
- mState = invalid;
- mChildren().erase(this->pid());
- return false;
- default:
- UnixError::throwMe();
- }
- break; // placebo
- case 0:
- return false; // child not ready (do nothing)
- default:
- assert(pid == this->pid());
- bury(status);
- return true;
- }
-}
-
-
-//
-// Perform an idempotent check for dead children, as per the UNIX wait() system calls.
-// This can be called at any time, and will reap all children that have died since
-// last time. The obvious time to call this is after a SIGCHLD has been received;
-// however signal dispatch is so - uh, interesting - in UNIX that we don't even try
-// to deal with it at this level. Suffice to say that calling checkChildren directly
-// from within a signal handler is NOT generally safe due to locking constraints.
-//
-// If the shared() flag is on, we explicitly poll each child known to be recently
-// alive. That is less efficient than reaping any and all, but leaves any children
-// alone that someone else may have created without our knowledge. The default is
-// not shared(), which will reap (and discard) any unrelated children without letting
-// the caller know about it.
-//
-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)) {
- secdebug("unixchild", "universal child check (%ld children known alive)", mChildren().size());
- switch (pid) {
- case pid_t(-1):
- switch (errno) {
- case EINTR:
- secdebug("unixchild", "EINTR on wait4; retrying");
- continue; // benign, but retry the wait()
- case ECHILD:
- // Should not normally happen (there *is* a child around),
- // but gets returned anyway if the child is stopped in the debugger.
- // Treat like a zero return (no children ready to be buried).
- secdebug("unixchild", "ECHILD with filled nursery (ignored)");
- goto no_more;
- default:
- UnixError::throwMe();
- }
- break;
- default:
- if (Child *child = mChildren()[pid]) {
- child->bury(status);
- casualties.add(child);
- } else
- secdebug("unixchild", "reaping feral child pid=%d", pid);
- if (mChildren().empty())
- goto no_more; // none left
- break;
- }
- }
- no_more: ;
- } else {
- secdebug("unixchild", "spurious checkChildren (the nursery is empty)");
- }
- } // release master lock
- casualties.notify();
-}
-
-
-//
-// Perform the canonical last rites for a formerly alive child.
-// Requires master lock held throughout.
-//
-void Child::bury(int status)
-{
- assert(mState == alive);
- mState = dead;
- mStatus = status;
- mChildren().erase(mPid);
-#if !defined(NDEBUG)
- if (bySignal())
- secdebug("unixchild", "%p (pid %d) died by signal %d%s",
- this, mPid, exitSignal(),
- coreDumped() ? " and dumped core" : "");
- else
- secdebug("unixchild", "%p (pid %d) died by exit(%d)",
- this, mPid, exitCode());
-#endif //NDEBUG
-}
-
-
-//
-// Default hooks
-//
-void Child::parentAction()
-{ /* nothing */ }
-
-void Child::dying()
-{ /* nothing */ }
-
-
-//
-// Biers
-//
-void Child::Bier::notify()
-{
- for (const_iterator it = begin(); it != end(); ++it)
- (*it)->dying();
-}
-
-
-} // end namespace IPPlusPlus
-} // end namespace Security