]> git.saurik.com Git - apple/security.git/blame - cdsa/cdsa_utilities/machserver.cpp
Security-163.tar.gz
[apple/security.git] / cdsa / cdsa_utilities / machserver.cpp
CommitLineData
bac41a7b
A
1/*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19//
20// machserver - C++ shell for writing Mach 3 servers
21//
22#include "machserver.h"
23#include <servers/bootstrap.h>
24#include <mach/kern_return.h>
25#include <mach/message.h>
26#include <mach/mig_errors.h>
27#include "mach_notify.h"
28#include <Security/debugging.h>
29
30#if defined(USECFCURRENTTIME)
31# include <CoreFoundation/CFDate.h>
32#else
33# include <sys/time.h>
34#endif
35
36namespace Security {
37namespace MachPlusPlus {
38
39
40//
41// Global per-thread information
42//
43ModuleNexus< ThreadNexus<MachServer::PerThread> > MachServer::thread;
44
45
46//
47// Create a server object.
48// The resulting object is not "active", and any number of server objects
49// can be in this "prepared" state at the same time.
50//
51MachServer::MachServer(const char *name)
52: mServerPort(name, bootstrap)
53{ setup(name); }
54
55MachServer::MachServer(const char *name, const Bootstrap &boot)
56: bootstrap(boot), mServerPort(name, bootstrap)
57{ setup(name); }
58
59void MachServer::setup(const char *name)
60{
df0e469f 61 secdebug("machsrv", "%p preparing service for \"%s\"", this, name);
bac41a7b
A
62 workerTimeout = 60 * 2; // 2 minutes default timeout
63 maxWorkerCount = 100; // sanity check limit
64
65 mPortSet += mServerPort;
66}
67
68MachServer::~MachServer()
69{
70 // The ReceivePort members will clean themselves up.
71 // The bootstrap server will clear us from its map when our receive port dies.
df0e469f 72 secdebug("machsrv", "%p destroyed", this);
bac41a7b
A
73}
74
75
76//
29654253
A
77// Add and remove extra listening ports.
78// Messages directed to those ports are dispatched through the main handler.
79// To get automatic call-out to another handler, use the Handler class.
bac41a7b 80//
29654253 81void MachServer::add(Port receiver)
bac41a7b 82{
df0e469f 83 secdebug("machsrv", "adding port %d to primary dispatch", receiver.port());
29654253
A
84 mPortSet += receiver;
85}
86
87void MachServer::remove(Port receiver)
88{
df0e469f 89 secdebug("machsrv", "removing port %d from primary dispatch", receiver.port());
29654253
A
90 mPortSet -= receiver;
91}
92
93
94//
95// Register for mach port notifications
96//
97void MachServer::notifyIfDead(Port port, bool doNotify) const
98{
99 if (doNotify)
100 port.requestNotify(mServerPort, MACH_NOTIFY_DEAD_NAME, true);
101 else
102 port.cancelNotify(MACH_NOTIFY_DEAD_NAME);
103}
104
105void MachServer::notifyIfUnused(Port port, bool doNotify) const
106{
107 if (doNotify)
108 port.requestNotify(port, MACH_NOTIFY_NO_SENDERS, true);
109 else
110 port.cancelNotify(MACH_NOTIFY_NO_SENDERS);
bac41a7b
A
111}
112
113
114//
115// Initiate service.
116// This call will take control of the current thread and use it to service
29654253
A
117// incoming requests. The thread will not be released until an error happens, which
118// will cause an exception to be thrown. In other words, this never returns normally.
bac41a7b
A
119// We may also be creating additional threads to service concurrent requests
120// as appropriate.
bac41a7b
A
121// @@@ Msg-errors in additional threads are not acted upon.
122//
123void MachServer::run(size_t maxSize, mach_msg_options_t options)
124{
125 // establish server-global (thread-shared) parameters
126 mMaxSize = maxSize;
127 mMsgOptions = options;
128
129 // establish the thread pool state
130 // (don't need managerLock since we're the only thread as of yet)
131 idleCount = workerCount = 1;
132 nextCheckTime = Time::now() + workerTimeout;
133 leastIdleWorkers = 1;
134 highestWorkerCount = 1;
135
136 // run server loop in initial (immortal) thread
137 runServerThread(false);
138
139 // primary server thread exited somehow (not currently possible)
140 assert(false);
141}
142
143
144//
145// This is the core of a server thread at work. It takes over the thread until
29654253
A
146// (a) an error occurs, throwing an exception
147// (b) low-load timeout happens, causing a normal return (doTimeout only)
bac41a7b
A
148// This code is loosely based on mach_msg_server.c, but is drifting away for
149// various reasons of flexibility and resilience.
150//
151extern "C" boolean_t cdsa_notify_server(mach_msg_header_t *in, mach_msg_header_t *out);
152
153void MachServer::runServerThread(bool doTimeout)
154{
155 // allocate request/reply buffers
156 Message bufRequest(mMaxSize);
157 Message bufReply(mMaxSize);
158
29654253 159 // all exits from runServerThread are through exceptions
bac41a7b
A
160 try {
161 // register as a worker thread
df0e469f 162 secdebug("machsrv", "%p starting service on port %d", this, int(mServerPort));
bac41a7b
A
163 perThread().server = this;
164
165 for (;;) {
166 // process all pending timers
167 while (processTimer()) ;
168
169 // check for worker idle timeout
170 { StLock<Mutex> _(managerLock);
171 // record idle thread low-water mark in scan interval
172 if (idleCount < leastIdleWorkers)
173 leastIdleWorkers = idleCount;
174
175 // perform self-timeout processing
176 if (doTimeout) {
177 if (workerCount > maxWorkerCount) {
df0e469f 178 secdebug("machsrv", "%p too many threads; reaping immediately", this);
bac41a7b
A
179 break;
180 }
181 Time::Absolute rightNow = Time::now();
182 if (rightNow >= nextCheckTime) { // reaping period complete; process
183 uint32 idlers = leastIdleWorkers;
df0e469f 184 secdebug("machsrv", "%p end of reaping period: %ld (min) idle of %ld total",
bac41a7b
A
185 this, idlers, workerCount);
186 nextCheckTime = rightNow + workerTimeout;
187 leastIdleWorkers = INT_MAX;
188 if (idlers > 1)
189 break;
190 }
191 }
192 }
193
194 // release deferred-release memory
195 releaseDeferredAllocations();
196
197 // determine next timeout, or zero for infinity
198 bool indefinite = false;
199 Time::Interval timeout;
200 { StLock<Mutex> _(managerLock);
201 if (timers.empty()) {
202 if (doTimeout)
203 timeout = workerTimeout;
204 else
205 indefinite = true;
206 } else {
207 timeout = doTimeout
208 ? min(workerTimeout, timers.next() - Time::now())
209 : timers.next() - Time::now();
210 }
211 }
212
213 // receive next IPC request (or wait for timeout)
214 switch (mach_msg_return_t mr = indefinite ?
29654253 215 mach_msg_overwrite(bufRequest,
bac41a7b
A
216 MACH_RCV_MSG | mMsgOptions,
217 0, mMaxSize, mPortSet,
218 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
219 (mach_msg_header_t *) 0, 0)
220 :
29654253 221 mach_msg_overwrite(bufRequest,
bac41a7b
A
222 MACH_RCV_MSG | MACH_RCV_TIMEOUT | mMsgOptions,
223 0, mMaxSize, mPortSet,
224 mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL,
225 (mach_msg_header_t *) 0, 0)) {
226 case MACH_MSG_SUCCESS:
227 // process received request message below
228 break;
229 case MACH_RCV_TIMED_OUT:
230 // back to top for time-related processing
231 continue;
232 case MACH_RCV_TOO_LARGE:
233 // the kernel destroyed the request
234 continue;
235 case MACH_RCV_INTERRUPTED:
236 // receive interrupted, try again
237 continue;
238 default:
239 Error::throwMe(mr);
240 }
241
242 // process received message
243 if (bufRequest.msgId() >= MACH_NOTIFY_FIRST &&
244 bufRequest.msgId() <= MACH_NOTIFY_LAST) {
245 // mach kernel notification message
246 // we assume this is quick, so no thread arbitration here
247 cdsa_notify_server(bufRequest, bufReply);
248 } else {
249 // normal request message
250 { StLock<Mutex> _(managerLock); idleCount--; }
df0e469f 251 secdebug("machsrvreq",
bac41a7b
A
252 "servicing port %d request id=%d",
253 bufRequest.localPort().port(), bufRequest.msgId());
29654253
A
254
255 // try subsidiary handlers first
256 bool handled = false;
257 for (HandlerSet::const_iterator it = mHandlers.begin();
258 it != mHandlers.end(); it++)
259 if (bufRequest.localPort() == (*it)->port()) {
260 (*it)->handle(bufRequest, bufReply);
261 handled = true;
262 }
263 if (!handled) {
264 // unclaimed, send to main handler
bac41a7b 265 handle(bufRequest, bufReply);
bac41a7b 266 }
29654253 267
df0e469f 268 secdebug("machsrvreq", "request complete");
bac41a7b
A
269 { StLock<Mutex> _(managerLock); idleCount++; }
270 }
271
272 // process reply generated by handler
273 if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) &&
274 bufReply.returnCode() != KERN_SUCCESS) {
275 if (bufReply.returnCode() == MIG_NO_REPLY)
276 continue;
277 // don't destroy the reply port right, so we can send an error message
278 bufRequest.remotePort(MACH_PORT_NULL);
279 mach_msg_destroy(bufRequest);
280 }
281
282 if (bufReply.remotePort() == MACH_PORT_NULL) {
283 // no reply port, so destroy the reply
284 if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX)
285 bufReply.destroy();
286 continue;
287 }
288
289 /*
290 * We don't want to block indefinitely because the client
291 * isn't receiving messages from the reply port.
292 * If we have a send-once right for the reply port, then
293 * this isn't a concern because the send won't block.
294 * If we have a send right, we need to use MACH_SEND_TIMEOUT.
295 * To avoid falling off the kernel's fast RPC path unnecessarily,
296 * we only supply MACH_SEND_TIMEOUT when absolutely necessary.
297 */
29654253 298 switch (mach_msg_return_t mr = mach_msg_overwrite(bufReply,
bac41a7b
A
299 (MACH_MSGH_BITS_REMOTE(bufReply.bits()) ==
300 MACH_MSG_TYPE_MOVE_SEND_ONCE) ?
301 MACH_SEND_MSG | mMsgOptions :
302 MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions,
303 bufReply.length(), 0, MACH_PORT_NULL,
304 0, MACH_PORT_NULL, NULL, 0)) {
305 case MACH_MSG_SUCCESS:
306 break;
307 case MACH_SEND_INVALID_DEST:
308 case MACH_SEND_TIMED_OUT:
309 /* the reply can't be delivered, so destroy it */
310 mach_msg_destroy(bufRequest);
311 break;
312 default:
313 Error::throwMe(mr);
314 }
315 }
316 perThread().server = NULL;
df0e469f 317 secdebug("machsrv", "%p ending service on port %d", this, int(mServerPort));
bac41a7b
A
318
319 } catch (...) {
320 perThread().server = NULL;
df0e469f 321 secdebug("machsrv", "%p aborted by exception (port %d)", this, int(mServerPort));
bac41a7b
A
322 throw;
323 }
324}
325
326
327//
328// Manage subsidiary ports
329//
330void MachServer::add(Handler &handler)
331{
332 assert(mHandlers.find(&handler) == mHandlers.end());
333 assert(handler.port() != MACH_PORT_NULL);
334 mHandlers.insert(&handler);
335 mPortSet += handler.port();
336}
337
338void MachServer::remove(Handler &handler)
339{
340 assert(mHandlers.find(&handler) != mHandlers.end());
341 mHandlers.erase(&handler);
342 mPortSet -= handler.port();
343}
344
345
346//
347// Implement a Handler that sends no reply
348//
349boolean_t MachServer::NoReplyHandler::handle(mach_msg_header_t *in, mach_msg_header_t *out)
350{
351 // set up reply message to be valid (enough) and read "do not send reply"
352 out->msgh_bits = 0;
353 out->msgh_remote_port = MACH_PORT_NULL;
354 out->msgh_size = sizeof(mig_reply_error_t);
355 ((mig_reply_error_t *)out)->RetCode = MIG_NO_REPLY;
356
357 // call input-only handler
358 return handle(in);
359}
360
361
362//
363// Register a memory block for deferred release.
364//
365void MachServer::releaseWhenDone(CssmAllocator &alloc, void *memory)
366{
367 if (memory) {
368 set<Allocation> &releaseSet = perThread().deferredAllocations;
369 assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end());
df0e469f 370 secdebug("machsrvmem", "%p register %p for release with %p",
bac41a7b
A
371 this, memory, &alloc);
372 releaseSet.insert(Allocation(memory, alloc));
373 }
374}
375
376
377//
378// Run through the accumulated deferred allocations and release them.
379// This is done automatically on every pass through the server loop;
380// it must be called by subclasses that implement their loop in some
381// other way.
382// @@@X Needs to be thread local
383//
384void MachServer::releaseDeferredAllocations()
385{
386 set<Allocation> &releaseSet = perThread().deferredAllocations;
387 for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) {
df0e469f 388 secdebug("machsrvmem", "%p release %p with %p", this, it->addr, it->allocator);
bac41a7b
A
389 it->allocator->free(it->addr);
390 }
391 releaseSet.erase(releaseSet.begin(), releaseSet.end());
392}
393
394
395//
396// The handler function calls this if it realizes that it might be blocked
397// (or doing something that takes a long time). We respond by ensuring that
398// at least one more thread is ready to serve requests.
399//
400void MachServer::longTermActivity()
401{
402 StLock<Mutex> _(managerLock);
403 if (idleCount == 0 && workerCount < maxWorkerCount) {
404 // spawn a new thread of activity that shares in the server main loop
405 (new LoadThread(*this))->run();
406 }
407}
408
409void MachServer::LoadThread::action()
410{
411 //@@@ race condition?! can server exit before helpers thread gets here?
412
413 // register the worker thread and go
414 server.addThread(this);
415 try {
416 server.runServerThread(true);
417 } catch (...) {
418 // fell out of server loop by error. Let the thread go quietly
419 }
420 server.removeThread(this);
421}
422
423void MachServer::addThread(Thread *thread)
424{
425 StLock<Mutex> _(managerLock);
426 workerCount++;
427 idleCount++;
df0e469f 428 secdebug("machsrv", "%p adding worker thread (%ld workers, %ld idle)",
bac41a7b
A
429 this, workerCount, idleCount);
430 workers.insert(thread);
431}
432
433void MachServer::removeThread(Thread *thread)
434{
435 StLock<Mutex> _(managerLock);
436 workerCount--;
437 idleCount--;
df0e469f 438 secdebug("machsrv", "%p removing worker thread (%ld workers, %ld idle)",
bac41a7b
A
439 this, workerCount, idleCount);
440 workers.erase(thread);
441}
442
443
444//
445// Timer management
446//
447bool MachServer::processTimer()
448{
449 Timer *top;
450 { StLock<Mutex> _(managerLock); // could have multiple threads trying this
451 if (!(top = static_cast<Timer *>(timers.pop(Time::now()))))
452 return false; // nothing (more) to be done now
453 } // drop lock; work has been retrieved
df0e469f 454 secdebug("machsrvtime", "%p timer %p executing at %.3f",
bac41a7b
A
455 this, top, Time::now().internalForm());
456 try {
457 top->action();
df0e469f 458 secdebug("machsrvtime", "%p timer %p done", this, top);
bac41a7b 459 } catch (...) {
df0e469f 460 secdebug("machsrvtime", "%p server timer %p failed with exception", this, top);
bac41a7b
A
461 }
462 return true;
463}
464
465void MachServer::setTimer(Timer *timer, Time::Absolute when)
466{
467 StLock<Mutex> _(managerLock);
468 timers.schedule(timer, when);
469}
470
471void MachServer::clearTimer(Timer *timer)
472{
473 StLock<Mutex> _(managerLock);
474 if (timer->scheduled())
475 timers.unschedule(timer);
476}
477
478
479//
480// Notification hooks and shims. Defaults do nothing.
481//
482void cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port)
483{ MachServer::active().notifyDeadName(port); }
484
485void MachServer::notifyDeadName(Port) { }
486
487void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port)
488{ MachServer::active().notifyPortDeleted(port); }
489
490void MachServer::notifyPortDeleted(Port) { }
491
492void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port)
493{ MachServer::active().notifyPortDestroyed(port); }
494
495void MachServer::notifyPortDestroyed(Port) { }
496
29654253
A
497void cdsa_mach_notify_send_once(mach_port_t port)
498{ MachServer::active().notifySendOnce(port); }
499
500void MachServer::notifySendOnce(Port) { }
bac41a7b 501
29654253
A
502void cdsa_mach_notify_no_senders(mach_port_t port, mach_port_mscount_t count)
503{ MachServer::active().notifyNoSenders(port, count); }
bac41a7b 504
29654253 505void MachServer::notifyNoSenders(Port, mach_port_mscount_t) { }
bac41a7b
A
506
507
508} // end namespace MachPlusPlus
509
510} // end namespace Security