X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/libsecurity_utilities/lib/unixchild.cpp?ds=inline diff --git a/libsecurity_utilities/lib/unixchild.cpp b/libsecurity_utilities/lib/unixchild.cpp deleted file mode 100644 index c65ee5a9..00000000 --- a/libsecurity_utilities/lib/unixchild.cpp +++ /dev/null @@ -1,502 +0,0 @@ -/* - * 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