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