]> git.saurik.com Git - apple/security.git/blame - cdsa/cdsa_utilities/machserver.cpp
Security-30.1.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{
61 debug("machsrv", "%p preparing service for \"%s\"", this, name);
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.
72 debug("machsrv", "%p destroyed", this);
73}
74
75
76//
77// Utility access
78//
79void MachServer::notifyIfDead(Port port) const
80{
81 port.requestNotify(mServerPort, MACH_NOTIFY_DEAD_NAME, true);
82}
83
84
85//
86// Initiate service.
87// This call will take control of the current thread and use it to service
88// incoming requests. The thread will not be released until an error happens.
89// We may also be creating additional threads to service concurrent requests
90// as appropriate.
91// @@@ Additional threads are not being reaped at this point.
92// @@@ Msg-errors in additional threads are not acted upon.
93//
94void MachServer::run(size_t maxSize, mach_msg_options_t options)
95{
96 // establish server-global (thread-shared) parameters
97 mMaxSize = maxSize;
98 mMsgOptions = options;
99
100 // establish the thread pool state
101 // (don't need managerLock since we're the only thread as of yet)
102 idleCount = workerCount = 1;
103 nextCheckTime = Time::now() + workerTimeout;
104 leastIdleWorkers = 1;
105 highestWorkerCount = 1;
106
107 // run server loop in initial (immortal) thread
108 runServerThread(false);
109
110 // primary server thread exited somehow (not currently possible)
111 assert(false);
112}
113
114
115//
116// This is the core of a server thread at work. It takes over the thread until
117// something makes it exit normally. Then it returns. Errors cause exceptions.
118// This code is loosely based on mach_msg_server.c, but is drifting away for
119// various reasons of flexibility and resilience.
120//
121extern "C" boolean_t cdsa_notify_server(mach_msg_header_t *in, mach_msg_header_t *out);
122
123void MachServer::runServerThread(bool doTimeout)
124{
125 // allocate request/reply buffers
126 Message bufRequest(mMaxSize);
127 Message bufReply(mMaxSize);
128
129 // all exits from runServerThread are through exceptions or "goto exit"
130 try {
131 // register as a worker thread
132 debug("machsrv", "%p starting service on port %d", this, int(mServerPort));
133 perThread().server = this;
134
135 for (;;) {
136 // process all pending timers
137 while (processTimer()) ;
138
139 // check for worker idle timeout
140 { StLock<Mutex> _(managerLock);
141 // record idle thread low-water mark in scan interval
142 if (idleCount < leastIdleWorkers)
143 leastIdleWorkers = idleCount;
144
145 // perform self-timeout processing
146 if (doTimeout) {
147 if (workerCount > maxWorkerCount) {
148 debug("machsrv", "%p too many threads; reaping immediately", this);
149 break;
150 }
151 Time::Absolute rightNow = Time::now();
152 if (rightNow >= nextCheckTime) { // reaping period complete; process
153 uint32 idlers = leastIdleWorkers;
154 debug("machsrv", "%p end of reaping period: %ld (min) idle of %ld total",
155 this, idlers, workerCount);
156 nextCheckTime = rightNow + workerTimeout;
157 leastIdleWorkers = INT_MAX;
158 if (idlers > 1)
159 break;
160 }
161 }
162 }
163
164 // release deferred-release memory
165 releaseDeferredAllocations();
166
167 // determine next timeout, or zero for infinity
168 bool indefinite = false;
169 Time::Interval timeout;
170 { StLock<Mutex> _(managerLock);
171 if (timers.empty()) {
172 if (doTimeout)
173 timeout = workerTimeout;
174 else
175 indefinite = true;
176 } else {
177 timeout = doTimeout
178 ? min(workerTimeout, timers.next() - Time::now())
179 : timers.next() - Time::now();
180 }
181 }
182
183 // receive next IPC request (or wait for timeout)
184 switch (mach_msg_return_t mr = indefinite ?
185 mach_msg_overwrite_trap(bufRequest,
186 MACH_RCV_MSG | mMsgOptions,
187 0, mMaxSize, mPortSet,
188 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
189 (mach_msg_header_t *) 0, 0)
190 :
191 mach_msg_overwrite_trap(bufRequest,
192 MACH_RCV_MSG | MACH_RCV_TIMEOUT | mMsgOptions,
193 0, mMaxSize, mPortSet,
194 mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL,
195 (mach_msg_header_t *) 0, 0)) {
196 case MACH_MSG_SUCCESS:
197 // process received request message below
198 break;
199 case MACH_RCV_TIMED_OUT:
200 // back to top for time-related processing
201 continue;
202 case MACH_RCV_TOO_LARGE:
203 // the kernel destroyed the request
204 continue;
205 case MACH_RCV_INTERRUPTED:
206 // receive interrupted, try again
207 continue;
208 default:
209 Error::throwMe(mr);
210 }
211
212 // process received message
213 if (bufRequest.msgId() >= MACH_NOTIFY_FIRST &&
214 bufRequest.msgId() <= MACH_NOTIFY_LAST) {
215 // mach kernel notification message
216 // we assume this is quick, so no thread arbitration here
217 cdsa_notify_server(bufRequest, bufReply);
218 } else {
219 // normal request message
220 { StLock<Mutex> _(managerLock); idleCount--; }
221 debug("machsrvreq",
222 "servicing port %d request id=%d",
223 bufRequest.localPort().port(), bufRequest.msgId());
224 if (bufRequest.localPort() == mServerPort) { // primary
225 handle(bufRequest, bufReply);
226 } else {
227 for (HandlerSet::const_iterator it = mHandlers.begin();
228 it != mHandlers.end(); it++)
229 if (bufRequest.localPort() == (*it)->port())
230 (*it)->handle(bufRequest, bufReply);
231 }
232 debug("machsrvreq", "request complete");
233 { StLock<Mutex> _(managerLock); idleCount++; }
234 }
235
236 // process reply generated by handler
237 if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) &&
238 bufReply.returnCode() != KERN_SUCCESS) {
239 if (bufReply.returnCode() == MIG_NO_REPLY)
240 continue;
241 // don't destroy the reply port right, so we can send an error message
242 bufRequest.remotePort(MACH_PORT_NULL);
243 mach_msg_destroy(bufRequest);
244 }
245
246 if (bufReply.remotePort() == MACH_PORT_NULL) {
247 // no reply port, so destroy the reply
248 if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX)
249 bufReply.destroy();
250 continue;
251 }
252
253 /*
254 * We don't want to block indefinitely because the client
255 * isn't receiving messages from the reply port.
256 * If we have a send-once right for the reply port, then
257 * this isn't a concern because the send won't block.
258 * If we have a send right, we need to use MACH_SEND_TIMEOUT.
259 * To avoid falling off the kernel's fast RPC path unnecessarily,
260 * we only supply MACH_SEND_TIMEOUT when absolutely necessary.
261 */
262 switch (mach_msg_return_t mr = mach_msg_overwrite_trap(bufReply,
263 (MACH_MSGH_BITS_REMOTE(bufReply.bits()) ==
264 MACH_MSG_TYPE_MOVE_SEND_ONCE) ?
265 MACH_SEND_MSG | mMsgOptions :
266 MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions,
267 bufReply.length(), 0, MACH_PORT_NULL,
268 0, MACH_PORT_NULL, NULL, 0)) {
269 case MACH_MSG_SUCCESS:
270 break;
271 case MACH_SEND_INVALID_DEST:
272 case MACH_SEND_TIMED_OUT:
273 /* the reply can't be delivered, so destroy it */
274 mach_msg_destroy(bufRequest);
275 break;
276 default:
277 Error::throwMe(mr);
278 }
279 }
280 perThread().server = NULL;
281 debug("machsrv", "%p ending service on port %d", this, int(mServerPort));
282
283 } catch (...) {
284 perThread().server = NULL;
285 debug("machsrv", "%p aborted by exception (port %d)", this, int(mServerPort));
286 throw;
287 }
288}
289
290
291//
292// Manage subsidiary ports
293//
294void MachServer::add(Handler &handler)
295{
296 assert(mHandlers.find(&handler) == mHandlers.end());
297 assert(handler.port() != MACH_PORT_NULL);
298 mHandlers.insert(&handler);
299 mPortSet += handler.port();
300}
301
302void MachServer::remove(Handler &handler)
303{
304 assert(mHandlers.find(&handler) != mHandlers.end());
305 mHandlers.erase(&handler);
306 mPortSet -= handler.port();
307}
308
309
310//
311// Implement a Handler that sends no reply
312//
313boolean_t MachServer::NoReplyHandler::handle(mach_msg_header_t *in, mach_msg_header_t *out)
314{
315 // set up reply message to be valid (enough) and read "do not send reply"
316 out->msgh_bits = 0;
317 out->msgh_remote_port = MACH_PORT_NULL;
318 out->msgh_size = sizeof(mig_reply_error_t);
319 ((mig_reply_error_t *)out)->RetCode = MIG_NO_REPLY;
320
321 // call input-only handler
322 return handle(in);
323}
324
325
326//
327// Register a memory block for deferred release.
328//
329void MachServer::releaseWhenDone(CssmAllocator &alloc, void *memory)
330{
331 if (memory) {
332 set<Allocation> &releaseSet = perThread().deferredAllocations;
333 assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end());
334 debug("machsrvmem", "%p register %p for release with %p",
335 this, memory, &alloc);
336 releaseSet.insert(Allocation(memory, alloc));
337 }
338}
339
340
341//
342// Run through the accumulated deferred allocations and release them.
343// This is done automatically on every pass through the server loop;
344// it must be called by subclasses that implement their loop in some
345// other way.
346// @@@X Needs to be thread local
347//
348void MachServer::releaseDeferredAllocations()
349{
350 set<Allocation> &releaseSet = perThread().deferredAllocations;
351 for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) {
352 debug("machsrvmem", "%p release %p with %p", this, it->addr, it->allocator);
353 it->allocator->free(it->addr);
354 }
355 releaseSet.erase(releaseSet.begin(), releaseSet.end());
356}
357
358
359//
360// The handler function calls this if it realizes that it might be blocked
361// (or doing something that takes a long time). We respond by ensuring that
362// at least one more thread is ready to serve requests.
363//
364void MachServer::longTermActivity()
365{
366 StLock<Mutex> _(managerLock);
367 if (idleCount == 0 && workerCount < maxWorkerCount) {
368 // spawn a new thread of activity that shares in the server main loop
369 (new LoadThread(*this))->run();
370 }
371}
372
373void MachServer::LoadThread::action()
374{
375 //@@@ race condition?! can server exit before helpers thread gets here?
376
377 // register the worker thread and go
378 server.addThread(this);
379 try {
380 server.runServerThread(true);
381 } catch (...) {
382 // fell out of server loop by error. Let the thread go quietly
383 }
384 server.removeThread(this);
385}
386
387void MachServer::addThread(Thread *thread)
388{
389 StLock<Mutex> _(managerLock);
390 workerCount++;
391 idleCount++;
392 debug("machsrv", "%p adding worker thread (%ld workers, %ld idle)",
393 this, workerCount, idleCount);
394 workers.insert(thread);
395}
396
397void MachServer::removeThread(Thread *thread)
398{
399 StLock<Mutex> _(managerLock);
400 workerCount--;
401 idleCount--;
402 debug("machsrv", "%p removing worker thread (%ld workers, %ld idle)",
403 this, workerCount, idleCount);
404 workers.erase(thread);
405}
406
407
408//
409// Timer management
410//
411bool MachServer::processTimer()
412{
413 Timer *top;
414 { StLock<Mutex> _(managerLock); // could have multiple threads trying this
415 if (!(top = static_cast<Timer *>(timers.pop(Time::now()))))
416 return false; // nothing (more) to be done now
417 } // drop lock; work has been retrieved
418 debug("machsrvtime", "%p timer %p executing at %.3f",
419 this, top, Time::now().internalForm());
420 try {
421 top->action();
422 debug("machsrvtime", "%p timer %p done", this, top);
423 } catch (...) {
424 debug("machsrvtime", "%p server timer %p failed with exception", this, top);
425 }
426 return true;
427}
428
429void MachServer::setTimer(Timer *timer, Time::Absolute when)
430{
431 StLock<Mutex> _(managerLock);
432 timers.schedule(timer, when);
433}
434
435void MachServer::clearTimer(Timer *timer)
436{
437 StLock<Mutex> _(managerLock);
438 if (timer->scheduled())
439 timers.unschedule(timer);
440}
441
442
443//
444// Notification hooks and shims. Defaults do nothing.
445//
446void cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port)
447{ MachServer::active().notifyDeadName(port); }
448
449void MachServer::notifyDeadName(Port) { }
450
451void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port)
452{ MachServer::active().notifyPortDeleted(port); }
453
454void MachServer::notifyPortDeleted(Port) { }
455
456void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port)
457{ MachServer::active().notifyPortDestroyed(port); }
458
459void MachServer::notifyPortDestroyed(Port) { }
460
461void cdsa_mach_notify_send_once(mach_port_t)
462{ MachServer::active().notifySendOnce(); }
463
464void MachServer::notifySendOnce() { }
465
466void cdsa_mach_notify_no_senders(mach_port_t)
467{ /* legacy handler - not used by system */ }
468
469
470} // end namespace MachPlusPlus
471
472} // end namespace Security