#include "machserver.h"
#include <servers/bootstrap.h>
#include <mach/kern_return.h>
#include <mach/message.h>
#include <mach/mig_errors.h>
#include "mach_notify.h"
#include <Security/debugging.h>
#if defined(USECFCURRENTTIME)
# include <CoreFoundation/CFDate.h>
#else
# include <sys/time.h>
#endif
namespace Security {
namespace MachPlusPlus {
ModuleNexus< ThreadNexus<MachServer::PerThread> > MachServer::thread;
MachServer::MachServer(const char *name)
: mServerPort(name, bootstrap)
{ setup(name); }
MachServer::MachServer(const char *name, const Bootstrap &boot)
: bootstrap(boot), mServerPort(name, bootstrap)
{ setup(name); }
void MachServer::setup(const char *name)
{
debug("machsrv", "%p preparing service for \"%s\"", this, name);
workerTimeout = 60 * 2; maxWorkerCount = 100;
mPortSet += mServerPort;
}
MachServer::~MachServer()
{
debug("machsrv", "%p destroyed", this);
}
void MachServer::notifyIfDead(Port port) const
{
port.requestNotify(mServerPort, MACH_NOTIFY_DEAD_NAME, true);
}
void MachServer::run(size_t maxSize, mach_msg_options_t options)
{
mMaxSize = maxSize;
mMsgOptions = options;
idleCount = workerCount = 1;
nextCheckTime = Time::now() + workerTimeout;
leastIdleWorkers = 1;
highestWorkerCount = 1;
runServerThread(false);
assert(false);
}
extern "C" boolean_t cdsa_notify_server(mach_msg_header_t *in, mach_msg_header_t *out);
void MachServer::runServerThread(bool doTimeout)
{
Message bufRequest(mMaxSize);
Message bufReply(mMaxSize);
try {
debug("machsrv", "%p starting service on port %d", this, int(mServerPort));
perThread().server = this;
for (;;) {
while (processTimer()) ;
{ StLock<Mutex> _(managerLock);
if (idleCount < leastIdleWorkers)
leastIdleWorkers = idleCount;
if (doTimeout) {
if (workerCount > maxWorkerCount) {
debug("machsrv", "%p too many threads; reaping immediately", this);
break;
}
Time::Absolute rightNow = Time::now();
if (rightNow >= nextCheckTime) { uint32 idlers = leastIdleWorkers;
debug("machsrv", "%p end of reaping period: %ld (min) idle of %ld total",
this, idlers, workerCount);
nextCheckTime = rightNow + workerTimeout;
leastIdleWorkers = INT_MAX;
if (idlers > 1)
break;
}
}
}
releaseDeferredAllocations();
bool indefinite = false;
Time::Interval timeout;
{ StLock<Mutex> _(managerLock);
if (timers.empty()) {
if (doTimeout)
timeout = workerTimeout;
else
indefinite = true;
} else {
timeout = doTimeout
? min(workerTimeout, timers.next() - Time::now())
: timers.next() - Time::now();
}
}
switch (mach_msg_return_t mr = indefinite ?
mach_msg_overwrite_trap(bufRequest,
MACH_RCV_MSG | mMsgOptions,
0, mMaxSize, mPortSet,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
(mach_msg_header_t *) 0, 0)
:
mach_msg_overwrite_trap(bufRequest,
MACH_RCV_MSG | MACH_RCV_TIMEOUT | mMsgOptions,
0, mMaxSize, mPortSet,
mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL,
(mach_msg_header_t *) 0, 0)) {
case MACH_MSG_SUCCESS:
break;
case MACH_RCV_TIMED_OUT:
continue;
case MACH_RCV_TOO_LARGE:
continue;
case MACH_RCV_INTERRUPTED:
continue;
default:
Error::throwMe(mr);
}
if (bufRequest.msgId() >= MACH_NOTIFY_FIRST &&
bufRequest.msgId() <= MACH_NOTIFY_LAST) {
cdsa_notify_server(bufRequest, bufReply);
} else {
{ StLock<Mutex> _(managerLock); idleCount--; }
debug("machsrvreq",
"servicing port %d request id=%d",
bufRequest.localPort().port(), bufRequest.msgId());
if (bufRequest.localPort() == mServerPort) { handle(bufRequest, bufReply);
} else {
for (HandlerSet::const_iterator it = mHandlers.begin();
it != mHandlers.end(); it++)
if (bufRequest.localPort() == (*it)->port())
(*it)->handle(bufRequest, bufReply);
}
debug("machsrvreq", "request complete");
{ StLock<Mutex> _(managerLock); idleCount++; }
}
if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) &&
bufReply.returnCode() != KERN_SUCCESS) {
if (bufReply.returnCode() == MIG_NO_REPLY)
continue;
bufRequest.remotePort(MACH_PORT_NULL);
mach_msg_destroy(bufRequest);
}
if (bufReply.remotePort() == MACH_PORT_NULL) {
if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX)
bufReply.destroy();
continue;
}
switch (mach_msg_return_t mr = mach_msg_overwrite_trap(bufReply,
(MACH_MSGH_BITS_REMOTE(bufReply.bits()) ==
MACH_MSG_TYPE_MOVE_SEND_ONCE) ?
MACH_SEND_MSG | mMsgOptions :
MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions,
bufReply.length(), 0, MACH_PORT_NULL,
0, MACH_PORT_NULL, NULL, 0)) {
case MACH_MSG_SUCCESS:
break;
case MACH_SEND_INVALID_DEST:
case MACH_SEND_TIMED_OUT:
mach_msg_destroy(bufRequest);
break;
default:
Error::throwMe(mr);
}
}
perThread().server = NULL;
debug("machsrv", "%p ending service on port %d", this, int(mServerPort));
} catch (...) {
perThread().server = NULL;
debug("machsrv", "%p aborted by exception (port %d)", this, int(mServerPort));
throw;
}
}
void MachServer::add(Handler &handler)
{
assert(mHandlers.find(&handler) == mHandlers.end());
assert(handler.port() != MACH_PORT_NULL);
mHandlers.insert(&handler);
mPortSet += handler.port();
}
void MachServer::remove(Handler &handler)
{
assert(mHandlers.find(&handler) != mHandlers.end());
mHandlers.erase(&handler);
mPortSet -= handler.port();
}
boolean_t MachServer::NoReplyHandler::handle(mach_msg_header_t *in, mach_msg_header_t *out)
{
out->msgh_bits = 0;
out->msgh_remote_port = MACH_PORT_NULL;
out->msgh_size = sizeof(mig_reply_error_t);
((mig_reply_error_t *)out)->RetCode = MIG_NO_REPLY;
return handle(in);
}
void MachServer::releaseWhenDone(CssmAllocator &alloc, void *memory)
{
if (memory) {
set<Allocation> &releaseSet = perThread().deferredAllocations;
assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end());
debug("machsrvmem", "%p register %p for release with %p",
this, memory, &alloc);
releaseSet.insert(Allocation(memory, alloc));
}
}
void MachServer::releaseDeferredAllocations()
{
set<Allocation> &releaseSet = perThread().deferredAllocations;
for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) {
debug("machsrvmem", "%p release %p with %p", this, it->addr, it->allocator);
it->allocator->free(it->addr);
}
releaseSet.erase(releaseSet.begin(), releaseSet.end());
}
void MachServer::longTermActivity()
{
StLock<Mutex> _(managerLock);
if (idleCount == 0 && workerCount < maxWorkerCount) {
(new LoadThread(*this))->run();
}
}
void MachServer::LoadThread::action()
{
server.addThread(this);
try {
server.runServerThread(true);
} catch (...) {
}
server.removeThread(this);
}
void MachServer::addThread(Thread *thread)
{
StLock<Mutex> _(managerLock);
workerCount++;
idleCount++;
debug("machsrv", "%p adding worker thread (%ld workers, %ld idle)",
this, workerCount, idleCount);
workers.insert(thread);
}
void MachServer::removeThread(Thread *thread)
{
StLock<Mutex> _(managerLock);
workerCount--;
idleCount--;
debug("machsrv", "%p removing worker thread (%ld workers, %ld idle)",
this, workerCount, idleCount);
workers.erase(thread);
}
bool MachServer::processTimer()
{
Timer *top;
{ StLock<Mutex> _(managerLock); if (!(top = static_cast<Timer *>(timers.pop(Time::now()))))
return false; } debug("machsrvtime", "%p timer %p executing at %.3f",
this, top, Time::now().internalForm());
try {
top->action();
debug("machsrvtime", "%p timer %p done", this, top);
} catch (...) {
debug("machsrvtime", "%p server timer %p failed with exception", this, top);
}
return true;
}
void MachServer::setTimer(Timer *timer, Time::Absolute when)
{
StLock<Mutex> _(managerLock);
timers.schedule(timer, when);
}
void MachServer::clearTimer(Timer *timer)
{
StLock<Mutex> _(managerLock);
if (timer->scheduled())
timers.unschedule(timer);
}
void cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port)
{ MachServer::active().notifyDeadName(port); }
void MachServer::notifyDeadName(Port) { }
void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port)
{ MachServer::active().notifyPortDeleted(port); }
void MachServer::notifyPortDeleted(Port) { }
void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port)
{ MachServer::active().notifyPortDestroyed(port); }
void MachServer::notifyPortDestroyed(Port) { }
void cdsa_mach_notify_send_once(mach_port_t)
{ MachServer::active().notifySendOnce(); }
void MachServer::notifySendOnce() { }
void cdsa_mach_notify_no_senders(mach_port_t)
{ }
}
}