]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_utilities/lib/unixchild.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_utilities / lib / unixchild.cpp
diff --git a/libsecurity_utilities/lib/unixchild.cpp b/libsecurity_utilities/lib/unixchild.cpp
new file mode 100644 (file)
index 0000000..c65ee5a
--- /dev/null
@@ -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 <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