]> git.saurik.com Git - apple/security.git/blob - cdsa/cdsa_utilities/machserver.cpp
Security-54.tar.gz
[apple/security.git] / cdsa / cdsa_utilities / machserver.cpp
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
36 namespace Security {
37 namespace MachPlusPlus {
38
39
40 //
41 // Global per-thread information
42 //
43 ModuleNexus< 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 //
51 MachServer::MachServer(const char *name)
52 : mServerPort(name, bootstrap)
53 { setup(name); }
54
55 MachServer::MachServer(const char *name, const Bootstrap &boot)
56 : bootstrap(boot), mServerPort(name, bootstrap)
57 { setup(name); }
58
59 void 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
68 MachServer::~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 // 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.
80 //
81 void MachServer::add(Port receiver)
82 {
83 debug("machsrv", "adding port %d to primary dispatch", receiver.port());
84 mPortSet += receiver;
85 }
86
87 void MachServer::remove(Port receiver)
88 {
89 debug("machsrv", "removing port %d from primary dispatch", receiver.port());
90 mPortSet -= receiver;
91 }
92
93
94 //
95 // Register for mach port notifications
96 //
97 void 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
105 void 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);
111 }
112
113
114 //
115 // Initiate service.
116 // This call will take control of the current thread and use it to service
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.
119 // We may also be creating additional threads to service concurrent requests
120 // as appropriate.
121 // @@@ Msg-errors in additional threads are not acted upon.
122 //
123 void 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
146 // (a) an error occurs, throwing an exception
147 // (b) low-load timeout happens, causing a normal return (doTimeout only)
148 // This code is loosely based on mach_msg_server.c, but is drifting away for
149 // various reasons of flexibility and resilience.
150 //
151 extern "C" boolean_t cdsa_notify_server(mach_msg_header_t *in, mach_msg_header_t *out);
152
153 void MachServer::runServerThread(bool doTimeout)
154 {
155 // allocate request/reply buffers
156 Message bufRequest(mMaxSize);
157 Message bufReply(mMaxSize);
158
159 // all exits from runServerThread are through exceptions
160 try {
161 // register as a worker thread
162 debug("machsrv", "%p starting service on port %d", this, int(mServerPort));
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) {
178 debug("machsrv", "%p too many threads; reaping immediately", this);
179 break;
180 }
181 Time::Absolute rightNow = Time::now();
182 if (rightNow >= nextCheckTime) { // reaping period complete; process
183 uint32 idlers = leastIdleWorkers;
184 debug("machsrv", "%p end of reaping period: %ld (min) idle of %ld total",
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 ?
215 mach_msg_overwrite(bufRequest,
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 :
221 mach_msg_overwrite(bufRequest,
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--; }
251 debug("machsrvreq",
252 "servicing port %d request id=%d",
253 bufRequest.localPort().port(), bufRequest.msgId());
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
265 handle(bufRequest, bufReply);
266 }
267
268 debug("machsrvreq", "request complete");
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 */
298 switch (mach_msg_return_t mr = mach_msg_overwrite(bufReply,
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;
317 debug("machsrv", "%p ending service on port %d", this, int(mServerPort));
318
319 } catch (...) {
320 perThread().server = NULL;
321 debug("machsrv", "%p aborted by exception (port %d)", this, int(mServerPort));
322 throw;
323 }
324 }
325
326
327 //
328 // Manage subsidiary ports
329 //
330 void 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
338 void 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 //
349 boolean_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 //
365 void 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());
370 debug("machsrvmem", "%p register %p for release with %p",
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 //
384 void MachServer::releaseDeferredAllocations()
385 {
386 set<Allocation> &releaseSet = perThread().deferredAllocations;
387 for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) {
388 debug("machsrvmem", "%p release %p with %p", this, it->addr, it->allocator);
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 //
400 void 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
409 void 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
423 void MachServer::addThread(Thread *thread)
424 {
425 StLock<Mutex> _(managerLock);
426 workerCount++;
427 idleCount++;
428 debug("machsrv", "%p adding worker thread (%ld workers, %ld idle)",
429 this, workerCount, idleCount);
430 workers.insert(thread);
431 }
432
433 void MachServer::removeThread(Thread *thread)
434 {
435 StLock<Mutex> _(managerLock);
436 workerCount--;
437 idleCount--;
438 debug("machsrv", "%p removing worker thread (%ld workers, %ld idle)",
439 this, workerCount, idleCount);
440 workers.erase(thread);
441 }
442
443
444 //
445 // Timer management
446 //
447 bool 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
454 debug("machsrvtime", "%p timer %p executing at %.3f",
455 this, top, Time::now().internalForm());
456 try {
457 top->action();
458 debug("machsrvtime", "%p timer %p done", this, top);
459 } catch (...) {
460 debug("machsrvtime", "%p server timer %p failed with exception", this, top);
461 }
462 return true;
463 }
464
465 void MachServer::setTimer(Timer *timer, Time::Absolute when)
466 {
467 StLock<Mutex> _(managerLock);
468 timers.schedule(timer, when);
469 }
470
471 void 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 //
482 void cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port)
483 { MachServer::active().notifyDeadName(port); }
484
485 void MachServer::notifyDeadName(Port) { }
486
487 void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port)
488 { MachServer::active().notifyPortDeleted(port); }
489
490 void MachServer::notifyPortDeleted(Port) { }
491
492 void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port)
493 { MachServer::active().notifyPortDestroyed(port); }
494
495 void MachServer::notifyPortDestroyed(Port) { }
496
497 void cdsa_mach_notify_send_once(mach_port_t port)
498 { MachServer::active().notifySendOnce(port); }
499
500 void MachServer::notifySendOnce(Port) { }
501
502 void cdsa_mach_notify_no_senders(mach_port_t port, mach_port_mscount_t count)
503 { MachServer::active().notifyNoSenders(port, count); }
504
505 void MachServer::notifyNoSenders(Port, mach_port_mscount_t) { }
506
507
508 } // end namespace MachPlusPlus
509
510 } // end namespace Security