X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_utilities/lib/unixchild.cpp diff --git a/libsecurity_utilities/lib/unixchild.cpp b/libsecurity_utilities/lib/unixchild.cpp new file mode 100644 index 00000000..c65ee5a9 --- /dev/null +++ b/libsecurity_utilities/lib/unixchild.cpp @@ -0,0 +1,502 @@ +/* + * 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 +#include + + +namespace Security { +namespace UnixPlusPlus { + + +// +// All our globals are in a ModuleNexus, for that special lazy-init goodness +// +ModuleNexus 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 _(mChildren()); + mChildren().shared = s; +} + +bool Child::sharedChildren() +{ + StLock _(mChildren()); + return mChildren().shared; +} + + +// +// Check status for one Child +// +Child::State Child::check() +{ + Child::State state; + bool reaped = false; + { + StLock _(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 _(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 _(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 _(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 _(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 _(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 _(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