]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
1 | /* |
2 | * Copyright (c) 2000-2007 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 | SECURITY_MACHSERVER_PORT_ADD(receiver); | |
93 | mPortSet += receiver; | |
94 | } | |
95 | ||
96 | void MachServer::remove(Port receiver) | |
97 | { | |
98 | SECURITY_MACHSERVER_PORT_REMOVE(receiver); | |
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(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 | SECURITY_MACHSERVER_START_THREAD(false); | |
147 | runServerThread(false); | |
148 | SECURITY_MACHSERVER_END_THREAD(false); | |
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 | SECURITY_MACHSERVER_REAP(workerCount, 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 | if (SECURITY_MACHSERVER_RECEIVE_ENABLED()) | |
217 | SECURITY_MACHSERVER_RECEIVE(indefinite ? 0 : timeout.seconds()); | |
218 | ||
219 | // receive next IPC request (or wait for timeout) | |
220 | mach_msg_return_t mr = indefinite ? | |
221 | mach_msg_overwrite(bufRequest, | |
222 | MACH_RCV_MSG | mMsgOptions, | |
223 | 0, mMaxSize, mPortSet, | |
224 | MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, | |
225 | (mach_msg_header_t *) 0, 0) | |
226 | : | |
227 | mach_msg_overwrite(bufRequest, | |
228 | MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_INTERRUPT | mMsgOptions, | |
229 | 0, mMaxSize, mPortSet, | |
230 | mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL, | |
231 | (mach_msg_header_t *) 0, 0); | |
232 | ||
233 | switch (mr) { | |
234 | case MACH_MSG_SUCCESS: | |
235 | // process received request message below | |
236 | break; | |
237 | default: | |
238 | SECURITY_MACHSERVER_RECEIVE_ERROR(mr); | |
239 | continue; | |
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<MachServer, &MachServer::busy, &MachServer::idle> _(*this); | |
251 | SECURITY_MACHSERVER_BEGIN(bufRequest.localPort(), bufRequest.msgId()); | |
252 | ||
253 | // try subsidiary handlers first | |
254 | bool handled = false; | |
255 | for (HandlerSet::const_iterator it = mHandlers.begin(); | |
256 | it != mHandlers.end(); it++) | |
257 | if (bufRequest.localPort() == (*it)->port()) { | |
258 | (*it)->handle(bufRequest, bufReply); | |
259 | handled = true; | |
260 | } | |
261 | if (!handled) { | |
262 | // unclaimed, send to main handler | |
263 | handle(bufRequest, bufReply); | |
264 | } | |
265 | ||
266 | SECURITY_MACHSERVER_END(); | |
267 | } | |
268 | ||
269 | // process reply generated by handler | |
270 | if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) && | |
271 | bufReply.returnCode() != KERN_SUCCESS) { | |
272 | if (bufReply.returnCode() == MIG_NO_REPLY) | |
273 | continue; | |
274 | // don't destroy the reply port right, so we can send an error message | |
275 | bufRequest.remotePort(MACH_PORT_NULL); | |
276 | mach_msg_destroy(bufRequest); | |
277 | } | |
278 | ||
279 | if (bufReply.remotePort() == MACH_PORT_NULL) { | |
280 | // no reply port, so destroy the reply | |
281 | if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX) | |
282 | bufReply.destroy(); | |
283 | continue; | |
284 | } | |
285 | ||
286 | /* | |
287 | * We don't want to block indefinitely because the client | |
288 | * isn't receiving messages from the reply port. | |
289 | * If we have a send-once right for the reply port, then | |
290 | * this isn't a concern because the send won't block. | |
291 | * If we have a send right, we need to use MACH_SEND_TIMEOUT. | |
292 | * To avoid falling off the kernel's fast RPC path unnecessarily, | |
293 | * we only supply MACH_SEND_TIMEOUT when absolutely necessary. | |
294 | */ | |
295 | mr = mach_msg_overwrite(bufReply, | |
296 | (MACH_MSGH_BITS_REMOTE(bufReply.bits()) == | |
297 | MACH_MSG_TYPE_MOVE_SEND_ONCE) ? | |
298 | MACH_SEND_MSG | mMsgOptions : | |
299 | MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions, | |
300 | bufReply.length(), 0, MACH_PORT_NULL, | |
301 | 0, MACH_PORT_NULL, NULL, 0); | |
302 | switch (mr) { | |
303 | case MACH_MSG_SUCCESS: | |
304 | break; | |
305 | default: | |
306 | SECURITY_MACHSERVER_SEND_ERROR(mr, bufReply.remotePort()); | |
307 | bufReply.destroy(); | |
308 | break; | |
309 | } | |
310 | ||
311 | ||
312 | // clean up after the transaction | |
313 | releaseDeferredAllocations(); | |
314 | } | |
315 | perThread().server = NULL; | |
316 | ||
317 | } catch (...) { | |
318 | perThread().server = NULL; | |
319 | throw; | |
320 | } | |
321 | } | |
322 | ||
323 | ||
324 | // | |
325 | // Manage subsidiary port handlers | |
326 | // | |
327 | void MachServer::add(Handler &handler) | |
328 | { | |
329 | assert(mHandlers.find(&handler) == mHandlers.end()); | |
330 | assert(handler.port() != MACH_PORT_NULL); | |
331 | mHandlers.insert(&handler); | |
332 | mPortSet += handler.port(); | |
333 | } | |
334 | ||
335 | void MachServer::remove(Handler &handler) | |
336 | { | |
337 | assert(mHandlers.find(&handler) != mHandlers.end()); | |
338 | mHandlers.erase(&handler); | |
339 | mPortSet -= handler.port(); | |
340 | } | |
341 | ||
342 | ||
343 | // | |
344 | // Abstract auxiliary message handlers | |
345 | // | |
346 | MachServer::Handler::~Handler() | |
347 | { /* virtual */ } | |
348 | ||
349 | ||
350 | // | |
351 | // Implement a Handler that sends no reply | |
352 | // | |
353 | boolean_t MachServer::NoReplyHandler::handle(mach_msg_header_t *in, mach_msg_header_t *out) | |
354 | { | |
355 | // set up reply message to be valid (enough) and read "do not send reply" | |
356 | out->msgh_bits = 0; | |
357 | out->msgh_remote_port = MACH_PORT_NULL; | |
358 | out->msgh_size = sizeof(mig_reply_error_t); | |
359 | ((mig_reply_error_t *)out)->RetCode = MIG_NO_REPLY; | |
360 | ||
361 | // call input-only handler | |
362 | return handle(in); | |
363 | } | |
364 | ||
365 | ||
366 | // | |
367 | // Register a memory block for deferred release. | |
368 | // | |
369 | void MachServer::releaseWhenDone(Allocator &alloc, void *memory) | |
370 | { | |
371 | if (memory) { | |
372 | set<Allocation> &releaseSet = perThread().deferredAllocations; | |
373 | assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end()); | |
374 | SECURITY_MACHSERVER_ALLOC_REGISTER(memory, &alloc); | |
375 | releaseSet.insert(Allocation(memory, alloc)); | |
376 | } | |
377 | } | |
378 | ||
379 | ||
380 | // | |
381 | // Run through the accumulated deferred allocations and release them. | |
382 | // This is done automatically on every pass through the server loop; | |
383 | // it must be called by subclasses that implement their loop in some | |
384 | // other way. | |
385 | // @@@X Needs to be thread local | |
386 | // | |
387 | void MachServer::releaseDeferredAllocations() | |
388 | { | |
389 | set<Allocation> &releaseSet = perThread().deferredAllocations; | |
390 | for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) { | |
391 | SECURITY_MACHSERVER_ALLOC_RELEASE(it->addr, it->allocator); | |
392 | ||
393 | // before we release the deferred allocation, zap it so that secrets aren't left in memory | |
394 | size_t memSize = malloc_size(it->addr); | |
395 | bzero(it->addr, memSize); | |
396 | it->allocator->free(it->addr); | |
397 | } | |
398 | releaseSet.erase(releaseSet.begin(), releaseSet.end()); | |
399 | } | |
400 | ||
401 | ||
402 | // | |
403 | // The handler function calls this if it realizes that it might be blocked | |
404 | // (or doing something that takes a long time). We respond by ensuring that | |
405 | // at least one more thread is ready to serve requests. | |
406 | // Calls the threadLimitReached callback in the server object if the thread | |
407 | // limit has been exceeded and a needed new thread was not created. | |
408 | // | |
409 | void MachServer::longTermActivity() | |
410 | { | |
411 | if (!useFloatingThread) { | |
412 | StLock<Mutex> _(managerLock); | |
413 | ensureReadyThread(); | |
414 | } | |
415 | } | |
416 | ||
417 | void MachServer::busy() | |
418 | { | |
419 | StLock<Mutex> _(managerLock); | |
420 | idleCount--; | |
421 | if (useFloatingThread) | |
422 | ensureReadyThread(); | |
423 | } | |
424 | ||
425 | void MachServer::idle() | |
426 | { | |
427 | StLock<Mutex> _(managerLock); | |
428 | idleCount++; | |
429 | } | |
430 | ||
431 | ||
432 | void MachServer::ensureReadyThread() | |
433 | { | |
434 | if (idleCount == 0) { | |
435 | if (workerCount >= maxWorkerCount) { | |
436 | this->threadLimitReached(workerCount); // call remedial handler | |
437 | } | |
438 | if (workerCount < maxWorkerCount) { // threadLimit() may have raised maxWorkerCount | |
439 | (new LoadThread(*this))->run(); | |
440 | } | |
441 | } | |
442 | } | |
443 | ||
444 | ||
445 | // | |
446 | // The callback hook for our subclasses. | |
447 | // The default does nothing, thereby denying further thread creation. | |
448 | // You could do something like maxThreads(limit+1) here to grant an variance; | |
449 | // or throw an exception to avoid possible deadlocks (this would abort the current | |
450 | // request but not otherwise impact the server's operation). | |
451 | // | |
452 | void MachServer::threadLimitReached(UInt32 limit) | |
453 | { | |
454 | } | |
455 | ||
456 | ||
457 | // | |
458 | // What our (non-primary) load threads do | |
459 | // | |
460 | void MachServer::LoadThread::action() | |
461 | { | |
462 | //@@@ race condition?! can server exit before helpers thread gets here? | |
463 | ||
464 | // register the worker thread and go | |
465 | server.addThread(this); | |
466 | try { | |
467 | SECURITY_MACHSERVER_START_THREAD(true); | |
468 | server.runServerThread(true); | |
469 | SECURITY_MACHSERVER_END_THREAD(false); | |
470 | } catch (...) { | |
471 | // fell out of server loop by error. Let the thread go quietly | |
472 | SECURITY_MACHSERVER_END_THREAD(true); | |
473 | } | |
474 | server.removeThread(this); | |
475 | } | |
476 | ||
477 | ||
478 | // | |
479 | // Thread accounting | |
480 | // | |
481 | void MachServer::addThread(Thread *thread) | |
482 | { | |
483 | StLock<Mutex> _(managerLock); | |
484 | workerCount++; | |
485 | idleCount++; | |
486 | workers.insert(thread); | |
487 | } | |
488 | ||
489 | void MachServer::removeThread(Thread *thread) | |
490 | { | |
491 | StLock<Mutex> _(managerLock); | |
492 | workerCount--; | |
493 | idleCount--; | |
494 | workers.erase(thread); | |
495 | } | |
496 | ||
497 | ||
498 | // | |
499 | // Timer management | |
500 | // | |
501 | MachServer::Timer::~Timer() | |
502 | { } | |
503 | ||
504 | void MachServer::Timer::select() | |
505 | { } | |
506 | ||
507 | void MachServer::Timer::unselect() | |
508 | { } | |
509 | ||
510 | bool MachServer::processTimer() | |
511 | { | |
512 | Timer *top; | |
513 | { StLock<Mutex> _(managerLock); // could have multiple threads trying this | |
514 | if (!(top = static_cast<Timer *>(timers.pop(Time::now())))) | |
515 | return false; // nothing (more) to be done now | |
516 | } // drop lock; work has been retrieved | |
517 | try { | |
518 | SECURITY_MACHSERVER_TIMER_START(top, top->longTerm(), Time::now().internalForm()); | |
519 | StLock<MachServer::Timer, | |
520 | &MachServer::Timer::select, &MachServer::Timer::unselect> _t(*top); | |
521 | if (top->longTerm()) { | |
522 | StLock<MachServer, &MachServer::busy, &MachServer::idle> _(*this); | |
523 | top->action(); | |
524 | } else { | |
525 | top->action(); | |
526 | } | |
527 | SECURITY_MACHSERVER_TIMER_END(false); | |
528 | } catch (...) { | |
529 | SECURITY_MACHSERVER_TIMER_END(true); | |
530 | } | |
531 | return true; | |
532 | } | |
533 | ||
534 | void MachServer::setTimer(Timer *timer, Time::Absolute when) | |
535 | { | |
536 | StLock<Mutex> _(managerLock); | |
537 | timers.schedule(timer, when); | |
538 | } | |
539 | ||
540 | void MachServer::clearTimer(Timer *timer) | |
541 | { | |
542 | StLock<Mutex> _(managerLock); | |
543 | if (timer->scheduled()) | |
544 | timers.unschedule(timer); | |
545 | } | |
546 | ||
547 | ||
548 | // | |
549 | // Notification hooks and shims. Defaults do nothing. | |
550 | // | |
551 | void cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port) | |
552 | { | |
553 | try { | |
554 | MachServer::active().notifyDeadName(port); | |
555 | } catch (...) { | |
556 | } | |
557 | } | |
558 | ||
559 | void MachServer::notifyDeadName(Port) { } | |
560 | ||
561 | void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port) | |
562 | { | |
563 | try { | |
564 | MachServer::active().notifyPortDeleted(port); | |
565 | } catch (...) { | |
566 | } | |
567 | } | |
568 | ||
569 | void MachServer::notifyPortDeleted(Port) { } | |
570 | ||
571 | void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port) | |
572 | { | |
573 | try { | |
574 | MachServer::active().notifyPortDestroyed(port); | |
575 | } catch (...) { | |
576 | } | |
577 | } | |
578 | ||
579 | void MachServer::notifyPortDestroyed(Port) { } | |
580 | ||
581 | void cdsa_mach_notify_send_once(mach_port_t port) | |
582 | { | |
583 | try { | |
584 | MachServer::active().notifySendOnce(port); | |
585 | } catch (...) { | |
586 | } | |
587 | } | |
588 | ||
589 | void MachServer::notifySendOnce(Port) { } | |
590 | ||
591 | void cdsa_mach_notify_no_senders(mach_port_t port, mach_port_mscount_t count) | |
592 | { | |
593 | try { | |
594 | MachServer::active().notifyNoSenders(port, count); | |
595 | } catch (...) { | |
596 | } | |
597 | } | |
598 | ||
599 | void MachServer::notifyNoSenders(Port, mach_port_mscount_t) { } | |
600 | ||
601 | void MachServer::eventDone() { } | |
602 | ||
603 | ||
604 | } // end namespace MachPlusPlus | |
605 | ||
606 | } // end namespace Security |