#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_utilities/debugging.h>
#include <malloc/malloc.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()
{ setup("(anonymous)"); }
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)
{
workerTimeout = 60 * 2; maxWorkerCount = 100; useFloatingThread = false;
mPortSet += mServerPort;
}
MachServer::~MachServer()
{
}
void MachServer::add(Port receiver)
{
SECURITY_MACHSERVER_PORT_ADD(receiver);
mPortSet += receiver;
}
void MachServer::remove(Port receiver)
{
SECURITY_MACHSERVER_PORT_REMOVE(receiver);
mPortSet -= receiver;
}
void MachServer::notifyIfDead(Port port, bool doNotify) const
{
if (doNotify)
port.requestNotify(mServerPort);
else
port.cancelNotify();
}
void MachServer::notifyIfUnused(Port port, bool doNotify) const
{
if (doNotify)
port.requestNotify(port, MACH_NOTIFY_NO_SENDERS, true);
else
port.cancelNotify(MACH_NOTIFY_NO_SENDERS);
}
void MachServer::run(mach_msg_size_t maxSize, mach_msg_options_t options)
{
mMaxSize = maxSize;
mMsgOptions = options;
idleCount = workerCount = 1;
nextCheckTime = Time::now() + workerTimeout;
leastIdleWorkers = 1;
highestWorkerCount = 1;
SECURITY_MACHSERVER_START_THREAD(false);
runServerThread(false);
SECURITY_MACHSERVER_END_THREAD(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 {
perThread().server = this;
for (;;) {
eventDone();
while (processTimer()) {}
{ StLock<Mutex> _(managerLock);
if (idleCount < leastIdleWorkers)
leastIdleWorkers = idleCount;
if (doTimeout) {
if (workerCount > maxWorkerCount) break; Time::Absolute rightNow = Time::now();
if (rightNow >= nextCheckTime) { UInt32 idlers = leastIdleWorkers;
SECURITY_MACHSERVER_REAP(workerCount, idlers);
nextCheckTime = rightNow + workerTimeout;
leastIdleWorkers = INT_MAX;
if (idlers > 1) break; }
}
}
bool indefinite = false;
Time::Interval timeout = workerTimeout;
{ StLock<Mutex> _(managerLock);
if (timers.empty()) {
indefinite = !doTimeout;
} else {
timeout = max(Time::Interval(0), timers.next() - Time::now());
if (doTimeout && workerTimeout < timeout)
timeout = workerTimeout;
}
}
if (SECURITY_MACHSERVER_RECEIVE_ENABLED())
SECURITY_MACHSERVER_RECEIVE(indefinite ? 0 : timeout.seconds());
mach_msg_return_t mr = indefinite ?
mach_msg_overwrite(bufRequest,
MACH_RCV_MSG | mMsgOptions,
0, mMaxSize, mPortSet,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
(mach_msg_header_t *) 0, 0)
:
mach_msg_overwrite(bufRequest,
MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_INTERRUPT | mMsgOptions,
0, mMaxSize, mPortSet,
mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL,
(mach_msg_header_t *) 0, 0);
switch (mr) {
case MACH_MSG_SUCCESS:
break;
default:
SECURITY_MACHSERVER_RECEIVE_ERROR(mr);
continue;
}
if (bufRequest.msgId() >= MACH_NOTIFY_FIRST &&
bufRequest.msgId() <= MACH_NOTIFY_LAST) {
cdsa_notify_server(bufRequest, bufReply);
} else {
StLock<MachServer, &MachServer::busy, &MachServer::idle> _(*this);
SECURITY_MACHSERVER_BEGIN(bufRequest.localPort(), bufRequest.msgId());
bool handled = false;
for (HandlerSet::const_iterator it = mHandlers.begin();
it != mHandlers.end(); it++)
if (bufRequest.localPort() == (*it)->port()) {
(*it)->handle(bufRequest, bufReply);
handled = true;
}
if (!handled) {
handle(bufRequest, bufReply);
}
SECURITY_MACHSERVER_END();
}
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;
}
mr = mach_msg_overwrite(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);
switch (mr) {
case MACH_MSG_SUCCESS:
break;
default:
SECURITY_MACHSERVER_SEND_ERROR(mr, bufReply.remotePort());
bufReply.destroy();
break;
}
releaseDeferredAllocations();
}
perThread().server = NULL;
} catch (...) {
perThread().server = NULL;
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();
}
MachServer::Handler::~Handler()
{ }
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(Allocator &alloc, void *memory)
{
if (memory) {
set<Allocation> &releaseSet = perThread().deferredAllocations;
assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end());
SECURITY_MACHSERVER_ALLOC_REGISTER(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++) {
SECURITY_MACHSERVER_ALLOC_RELEASE(it->addr, it->allocator);
size_t memSize = malloc_size(it->addr);
bzero(it->addr, memSize);
it->allocator->free(it->addr);
}
releaseSet.erase(releaseSet.begin(), releaseSet.end());
}
void MachServer::longTermActivity()
{
if (!useFloatingThread) {
StLock<Mutex> _(managerLock);
ensureReadyThread();
}
}
void MachServer::busy()
{
StLock<Mutex> _(managerLock);
idleCount--;
if (useFloatingThread)
ensureReadyThread();
}
void MachServer::idle()
{
StLock<Mutex> _(managerLock);
idleCount++;
}
void MachServer::ensureReadyThread()
{
if (idleCount == 0) {
if (workerCount >= maxWorkerCount) {
this->threadLimitReached(workerCount); }
if (workerCount < maxWorkerCount) { (new LoadThread(*this))->run();
}
}
}
void MachServer::threadLimitReached(UInt32 limit)
{
}
void MachServer::LoadThread::action()
{
server.addThread(this);
try {
SECURITY_MACHSERVER_START_THREAD(true);
server.runServerThread(true);
SECURITY_MACHSERVER_END_THREAD(false);
} catch (...) {
SECURITY_MACHSERVER_END_THREAD(true);
}
server.removeThread(this);
}
void MachServer::addThread(Thread *thread)
{
StLock<Mutex> _(managerLock);
workerCount++;
idleCount++;
workers.insert(thread);
}
void MachServer::removeThread(Thread *thread)
{
StLock<Mutex> _(managerLock);
workerCount--;
idleCount--;
workers.erase(thread);
}
MachServer::Timer::~Timer()
{ }
void MachServer::Timer::select()
{ }
void MachServer::Timer::unselect()
{ }
bool MachServer::processTimer()
{
Timer *top;
{ StLock<Mutex> _(managerLock); if (!(top = static_cast<Timer *>(timers.pop(Time::now()))))
return false; } try {
SECURITY_MACHSERVER_TIMER_START(top, top->longTerm(), Time::now().internalForm());
StLock<MachServer::Timer,
&MachServer::Timer::select, &MachServer::Timer::unselect> _t(*top);
if (top->longTerm()) {
StLock<MachServer, &MachServer::busy, &MachServer::idle> _(*this);
top->action();
} else {
top->action();
}
SECURITY_MACHSERVER_TIMER_END(false);
} catch (...) {
SECURITY_MACHSERVER_TIMER_END(true);
}
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)
{
try {
MachServer::active().notifyDeadName(port);
} catch (...) {
}
}
void MachServer::notifyDeadName(Port) { }
void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port)
{
try {
MachServer::active().notifyPortDeleted(port);
} catch (...) {
}
}
void MachServer::notifyPortDeleted(Port) { }
void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port)
{
try {
MachServer::active().notifyPortDestroyed(port);
} catch (...) {
}
}
void MachServer::notifyPortDestroyed(Port) { }
void cdsa_mach_notify_send_once(mach_port_t port)
{
try {
MachServer::active().notifySendOnce(port);
} catch (...) {
}
}
void MachServer::notifySendOnce(Port) { }
void cdsa_mach_notify_no_senders(mach_port_t port, mach_port_mscount_t count)
{
try {
MachServer::active().notifyNoSenders(port, count);
} catch (...) {
}
}
void MachServer::notifyNoSenders(Port, mach_port_mscount_t) { }
void MachServer::eventDone() { }
}
}