*
* @APPLE_LICENSE_HEADER_START@
*
- * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
- *
* 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
// child - track a single child process and its belongings
//
#include "child.h"
-#include <sys/wait.h>
+#include "dtrace.h"
#include <security_utilities/debugging.h>
-#include <pthread.h>
-
-kern_return_t
-ucsp_server_handleSignal(mach_port_t sport,
- mach_port_t task_port,
- int signal_number)
-{
- try {
- if (task_port != mach_task_self()) {
- Syslog::error("handleSignal: recieved from someone other than myself");
- } else {
- ChildManager::childManager.handleSignal(signal_number);
- }
- } catch(...) {}
- mach_port_deallocate(mach_task_self(), task_port);
-
- return 0;
-}
-
-kern_return_t
-ucsp_server_registerChild(mach_port_t sport,
- mach_port_t rport,
- mach_port_t task_port)
-{
- mach_port_t childPort = rport;
- try {
- pid_t pid;
- kern_return_t kt = pid_for_task(task_port, &pid);
- if (kt) {
- Syslog::error("registerChild: pid_for_task returned: %d", kt);
- } else {
- ChildManager::childManager.registerChild(pid, childPort);
- }
- } catch(...) {}
-
- if (childPort) {
- // Dealloc the childPort unless registerChild set it to zero, which indicates it took over ownership.
- mach_port_deallocate(mach_task_self(), childPort);
- }
-
- if (task_port)
- mach_port_deallocate(mach_task_self(), task_port);
-
- return 0;
-}
//
-// A Child object represents a UNIX process that was forked by us
-// and may have some state associated with it.
-// Although some children may be created per session (such as SecurityAgent instances)
-// in general child processes are PerGlobal.
+// We use a static Mutex to coordinate checkin
//
-class Child
-{
-public:
- Child(pid_t pid);
- ~Child();
-
- void waitStatus(int status);
- int waitStatus() const { return mStatus; }
-
- void childPort(mach_port_t &childPort);
-
- // Return our childPort and transfer ownership of it.
- mach_port_t childPort() { mach_port_t childPort = mChildPort; mChildPort = 0; return childPort; }
-
- void eraseFromMap();
- void insertInMap();
-
- // Wait for the child to register or die. If it registered mChildPort will be non zero, it will be 0 if it didn't.
- void waitForRegister();
-
-private:
- Mutex mLockInternal;
- StLock<Mutex> mLock;
- pid_t mPid;
-
- // Service port for this child.
- // This should only be nonzero while this object owns the port. Once it is retrieved ownership is handed off.
- mach_port_t mChildPort;
-
- int mStatus; // Exit status if child died.
- bool mInMap;
-};
-
+Mutex ServerChild::mCheckinLock;
//
-// Construct a Child object.
+// Make and break ServerChildren
//
-Child::Child(pid_t pid) :
- mLock(mLockInternal), mPid(pid), mChildPort(0), mStatus(0), mInMap(false)
+ServerChild::ServerChild()
+ : mCheckinCond(mCheckinLock)
{
}
-Child::~Child()
-{
- try {
- eraseFromMap();
- if (mChildPort) {
- // Dealloc our mChildPort someone else took over ownership.
- mach_port_deallocate(mach_task_self(), mChildPort);
- }
- } catch (...) {}
-}
-
-void
-Child::eraseFromMap()
-{
- if (mInMap)
- ChildManager::childManager.eraseChild(mPid);
-}
-
-void
-Child::insertInMap()
-{
- ChildManager::childManager.insertChild(mPid, this);
- mInMap = true;
-}
-
-void
-Child::waitStatus(int status)
-{
- mStatus = status;
- mLock.unlock(); // Unlock the lock to unblock waitForRegister()
-}
-
-void
-Child::childPort(mach_port_t &childPort)
-{
- mChildPort = childPort;
- childPort = 0; // Tell our caller that we consumed the child port.
-
- mLock.unlock(); // Unlock the lock to unblock waitForRegister()
-}
-
-void
-Child::waitForRegister()
-{
- // Try to lock the lock again (this won't succeed until we get a register or wait result).
- mLock.lock();
- // Unlock as soon as we get the lock.
- mLock.unlock();
-}
-
//
-// ChildManager - Singleton Child Manager class
+// If the ServerChild is destroyed, kill its process, nice or hard.
//
-
-// The signleton ChildManager.
-ChildManager ChildManager::childManager;
-
-ChildManager::ChildManager()
-{
-}
-
-ChildManager::~ChildManager()
-{
-}
-
-bool
-ChildManager::forkChild(mach_port_t &outChildPort, int &outWaitStatus)
-{
- StLock<Mutex> lock(mLock, false);
- pid_t pid;
-
- /* Retry fork 10 times on failure after that just give up. */
- for (int tries = 0; tries < 10; ++tries)
- {
- lock.lock(); // aquire the lock
- pid = fork();
- if (pid != pid_t(-1))
- break;
-
- /* Something went wrong. */
- lock.unlock(); // Release the lock so we aren't holding it during the usleep below.
-
- int err = errno;
- if (err == EINTR)
- continue;
-
- if (err == EAGAIN || err == ENOMEM)
- usleep(100 * tries);
- else
- UnixError::throwMe(err);
- }
-
- if (pid == 0)
- {
- // Child - return true.
- return true;
- }
- else
- {
- Child child(pid);
- child.insertInMap(); // Insert child into the map.
- lock.unlock(); // Unlock as soon as we are done accessing mChildMap.
-
- child.waitForRegister();
-
- // Remove child from the map.
- child.eraseFromMap();
-
- // Transfer ownership of child's childPort to our caller.
- outChildPort = child.childPort();
-
- if (!outChildPort)
- outWaitStatus = child.waitStatus();
-
- // Parent - return false
- return false;
- }
-}
-
-void
-ChildManager::handleSignal(int signal_number)
-{
- if (signal_number == SIGCHLD)
- handleSigChild();
-}
-
-void
-ChildManager::registerChild(pid_t pid, mach_port_t &childPort)
-{
- StLock<Mutex> _(mLock);
- Child *child = findChild(pid);
- if (child)
- child->childPort(childPort);
-}
-
-// Assumes mLock is not locked.
-void
-ChildManager::eraseChild(pid_t pid)
-{
- StLock<Mutex> _(mLock);
- mChildMap.erase(pid);
-}
-
-// Assumes mLock is already locked.
-void
-ChildManager::insertChild(pid_t pid, Child *child)
-{
- mChildMap[pid] = child;
+// In case you wonder about the tango below, it's making sure we
+// get to "It's dead, Jim" with the minimum number of checkChildren()
+// calls while still working correctly if this is the only thread alive.
+//
+//@@@ We *could* define a "soft shutdown" MIG message to send to all
+//@@@ ServerChildren in this situation.
+//
+ServerChild::~ServerChild()
+{
+ mServicePort.destroy();
+
+ if (state() == alive) {
+ this->kill(SIGTERM); // shoot it once
+ checkChildren(); // check for quick death
+ if (state() == alive) {
+ usleep(300000); // give it some grace
+ if (state() == alive) { // could have been reaped by another thread
+ checkChildren(); // check again
+ if (state() == alive) { // it... just... won't... die...
+ this->kill(SIGKILL); // take THAT!
+ checkChildren();
+ if (state() == alive) // stuck zombie
+ abandon(); // leave the body behind
+ }
+ }
+ }
+ }
}
//
-// Private functions
+// Parent action during fork: wait until ready or dead, then return
//
-
-Child *
-ChildManager::findChild(pid_t pid)
+void ServerChild::parentAction()
{
- ChildMap::iterator it = mChildMap.find(pid);
- if (it == mChildMap.end())
- return NULL;
+ // wait for either checkin or (premature) death
+ secdebug("serverchild", "%p (pid %d) waiting for checkin", this, pid());
+ StLock<Mutex> _(mCheckinLock);
+ while (!ready() && state() == alive)
+ mCheckinCond.wait();
- return it->second;
+ // so what happened?
+ if (state() == dead) {
+ // our child died
+ secdebug("serverchild", "%p (pid %d) died before checking in", this, pid());
+ SECURITYD_CHILD_STILLBORN(this->pid());
+ } else if (ready()) {
+ // child has checked in and is ready for service
+ secdebug("serverchild", "%p (pid %d) ready for service on port %d",
+ this, pid(), mServicePort.port());
+ SECURITYD_CHILD_READY(this->pid());
+ } else
+ assert(false); // how did we ever get here?!
}
-void
-ChildManager::handleSigChild()
-{
- for (int tries = 0; tries < 10; ++tries)
- {
- int status;
- pid_t pid = waitpid(-1, &status, WNOHANG|WUNTRACED);
- switch (pid)
- {
- case 0:
- if (tries == 0)
- Syslog::notice("Spurious SIGCHLD ignored");
- break;
- case -1:
- {
- int err = errno;
- if (err == ECHILD)
- {
- if (tries == 0)
- Syslog::notice("Spurious SIGCHLD ignored");
- break;
- }
-
- if (err != EINTR)
- {
- Syslog::error("waitpid after SIGCHLD failed: %s", strerror(err));
- break;
- }
- }
- default:
- if (WIFEXITED(status) || WIFSIGNALED(status))
- {
- if (WIFEXITED(status))
- secdebug("child", "child with pid: %d exited: %d", pid, WEXITSTATUS(status));
- else if (WCOREDUMP(status))
- secdebug("child", "child with pid: %d terminated by signal: %d and dumped core", pid, WTERMSIG(status));
- else
- secdebug("child", "child with pid: %d terminated by signal: %d", pid, WTERMSIG(status));
-
- waitStatusForPid(pid, status);
- }
- else if (WSTOPSIG(status))
- {
- secdebug("child", "child with pid: %d stopped by signal: %d", pid, WSTOPSIG(status));
- }
- else
- {
- Syslog::error("child with pid: %d bogus waitpid status: %d", pid, status);
- }
- tries = 1;
- }
- }
-}
-
-void
-ChildManager::waitStatusForPid(pid_t pid, int status)
-{
- StLock<Mutex> _(mLock);
- Child *child = findChild(pid);
- if (child)
- child->waitStatus(status);
+//
+// Death action during fork: release the waiting creator thread, if any
+//
+void ServerChild::dying()
+{
+ SECURITYD_CHILD_DYING(this->pid());
+ secdebug("serverchild", "%p is dead; resuming parent thread (if any)", this);
+ mCheckinCond.signal();
+}
+
+
+void ServerChild::checkIn(Port servicePort, pid_t pid)
+{
+ if (ServerChild *child = Child::find<ServerChild>(pid)) {
+ // Child was alive when last seen. Store service port and signal parent thread
+ {
+ StLock<Mutex> _(mCheckinLock);
+ child->mServicePort = servicePort;
+ servicePort.modRefs(MACH_PORT_RIGHT_SEND, +1); // retain send right
+ secdebug("serverchild", "%p (pid %d) checking in; resuming parent thread",
+ child, pid);
+ }
+ SECURITYD_CHILD_CHECKIN(pid, servicePort);
+ child->mCheckinCond.signal();
+ } else {
+ // Child has died; is wrong kind; or spurious checkin.
+ // If it was a proper child, death notifications will wake up the parent thread
+ secdebug("serverchild", "pid %d not in child set; checkin ignored", pid);
+ SECURITYD_CHILD_CHECKIN(pid, 0);
+ }
}